Implemented Terminal.bold(), foreground(), normal() and reset().

This commit is contained in:
Adam Murdoch
2012-08-04 14:08:19 +10:00
parent e9b300f610
commit 7ee843612a
9 changed files with 196 additions and 24 deletions

View File

@@ -29,3 +29,5 @@ You need to install the `libncurses5-dev` package to pick up the ncurses header
* Handle multiple architectures.
* IBM JVM.
* Convert to c.
* Thread safety.
* Windows: flush System.out or System.err on attribute change.

View File

@@ -70,4 +70,90 @@ Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_getConsole
env->SetIntField(dimension, heightField, console_info.srWindow.Bottom - console_info.srWindow.Top + 1);
}
HANDLE current_console = NULL;
WORD original_attributes = 0;
WORD current_attributes = 0;
JNIEXPORT void JNICALL
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_initConsole(JNIEnv *env, jclass target, jint output, jobject result) {
CONSOLE_SCREEN_BUFFER_INFO console_info;
HANDLE handle = getHandle(env, output, result);
if (handle == NULL) {
mark_failed_with_message(env, "not a terminal", result);
return;
}
if (!GetConsoleScreenBufferInfo(handle, &console_info)) {
if (GetLastError() == ERROR_INVALID_HANDLE) {
mark_failed_with_message(env, "not a console", result);
} else {
mark_failed_with_errno(env, "could not get console buffer", result);
}
return;
}
current_console = handle;
original_attributes = console_info.wAttributes;
current_attributes = original_attributes;
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_normal(env, target, result);
}
JNIEXPORT void JNICALL
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_bold(JNIEnv *env, jclass target, jobject result) {
current_attributes |= FOREGROUND_INTENSITY;
if (!SetConsoleTextAttribute(current_console, current_attributes)) {
mark_failed_with_errno(env, "could not set text attributes", result);
}
}
JNIEXPORT void JNICALL
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_normal(JNIEnv *env, jclass target, jobject result) {
current_attributes &= ~FOREGROUND_INTENSITY;
SetConsoleTextAttribute(current_console, current_attributes);
if (!SetConsoleTextAttribute(current_console, current_attributes)) {
mark_failed_with_errno(env, "could not set text attributes", result);
}
}
JNIEXPORT void JNICALL
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_reset(JNIEnv *env, jclass target, jobject result) {
SetConsoleTextAttribute(current_console, original_attributes);
if (!SetConsoleTextAttribute(current_console, current_attributes)) {
mark_failed_with_errno(env, "could not set text attributes", result);
}
}
JNIEXPORT void JNICALL
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_foreground(JNIEnv *env, jclass target, jint color, jobject result) {
current_attributes &= ~ (FOREGROUND_BLUE|FOREGROUND_RED|FOREGROUND_GREEN);
switch (color) {
case 0:
break;
case 1:
current_attributes |= FOREGROUND_RED;
break;
case 2:
current_attributes |= FOREGROUND_GREEN;
break;
case 3:
current_attributes |= FOREGROUND_RED|FOREGROUND_GREEN;
break;
case 4:
current_attributes |= FOREGROUND_BLUE;
break;
case 5:
current_attributes |= FOREGROUND_RED|FOREGROUND_BLUE;
break;
case 6:
current_attributes |= FOREGROUND_GREEN|FOREGROUND_BLUE;
break;
default:
current_attributes |= FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE;
break;
}
SetConsoleTextAttribute(current_console, current_attributes);
if (!SetConsoleTextAttribute(current_console, current_attributes)) {
mark_failed_with_errno(env, "could not set text attributes", result);
}
}
#endif

View File

@@ -0,0 +1,17 @@
package net.rubygrapefruit.platform.internal;
import net.rubygrapefruit.platform.Terminal;
public abstract class AbstractTerminal implements Terminal {
public final void init() {
doInit();
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
reset();
}
});
}
protected abstract void doInit();
}

View File

@@ -9,29 +9,29 @@ import net.rubygrapefruit.platform.internal.jni.TerminfoFunctions;
import java.io.PrintStream;
public class DefaultTerminal implements Terminal {
public class TerminfoTerminal extends AbstractTerminal {
private final TerminalAccess.Output output;
private final PrintStream stream;
private Color foreground;
public DefaultTerminal(TerminalAccess.Output output) {
public TerminfoTerminal(TerminalAccess.Output output) {
this.output = output;
stream = output == TerminalAccess.Output.Stdout ? System.out : System.err;
}
public void init() {
@Override
public String toString() {
return output.toString().toLowerCase();
}
@Override
protected void doInit() {
stream.flush();
FunctionResult result = new FunctionResult();
TerminfoFunctions.initTerminal(output.ordinal(), result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not open terminal: %s", result.getMessage()));
throw new NativeException(String.format("Could not open terminal for %s: %s", this, result.getMessage()));
}
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
reset();
}
});
}
@Override
@@ -40,7 +40,7 @@ public class DefaultTerminal implements Terminal {
FunctionResult result = new FunctionResult();
PosixTerminalFunctions.getTerminalSize(output.ordinal(), terminalSize, result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not get terminal size: %s", result.getMessage()));
throw new NativeException(String.format("Could not get terminal size for %s: %s", this, result.getMessage()));
}
return terminalSize;
}
@@ -51,7 +51,7 @@ public class DefaultTerminal implements Terminal {
FunctionResult result = new FunctionResult();
TerminfoFunctions.foreground(color.ordinal(), result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not switch foreground color: %s", result.getMessage()));
throw new NativeException(String.format("Could not switch foreground color for %s: %s", this, result.getMessage()));
}
foreground = color;
return this;
@@ -63,7 +63,7 @@ public class DefaultTerminal implements Terminal {
FunctionResult result = new FunctionResult();
TerminfoFunctions.bold(result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not switch to bold mode: %s", result.getMessage()));
throw new NativeException(String.format("Could not switch to bold mode for %s: %s", this, result.getMessage()));
}
return this;
}
@@ -83,7 +83,7 @@ public class DefaultTerminal implements Terminal {
FunctionResult result = new FunctionResult();
TerminfoFunctions.reset(result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not reset terminal: %s", result.getMessage()));
throw new NativeException(String.format("Could not reset terminal for %s: %s", this, result.getMessage()));
}
return this;
}

View File

@@ -18,7 +18,7 @@ public class TerminfoTerminalAccess implements TerminalAccess {
throw new UnsupportedOperationException("Currently only one output can be used as a terminal.");
}
DefaultTerminal terminal = new DefaultTerminal(output);
TerminfoTerminal terminal = new TerminfoTerminal(output);
terminal.init();
currentlyOpen = output;

View File

@@ -6,41 +6,75 @@ import net.rubygrapefruit.platform.TerminalAccess;
import net.rubygrapefruit.platform.TerminalSize;
import net.rubygrapefruit.platform.internal.jni.WindowsConsoleFunctions;
public class WindowsTerminal implements Terminal {
public class WindowsTerminal extends AbstractTerminal {
private final TerminalAccess.Output output;
public WindowsTerminal(TerminalAccess.Output output) {
this.output = output;
}
@Override
public String toString() {
return output.toString().toLowerCase();
}
@Override
protected void doInit() {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.initConsole(output.ordinal(), result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not open console for %s: %s", this, result.getMessage()));
}
}
@Override
public TerminalSize getTerminalSize() {
FunctionResult result = new FunctionResult();
MutableTerminalSize size = new MutableTerminalSize();
WindowsConsoleFunctions.getConsoleSize(output.ordinal(), size, result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not determine terminal size: %s", result.getMessage()));
throw new NativeException(String.format("Could not determine console size for %s: %s", this, result.getMessage()));
}
return size;
}
@Override
public Terminal bold() {
throw new UnsupportedOperationException();
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.bold(result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not switch console to bold mode for %s: %s", this, result.getMessage()));
}
return this;
}
@Override
public Terminal foreground(Color color) {
throw new UnsupportedOperationException();
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.foreground(color.ordinal(), result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not change console foreground color for %s: %s", this, result.getMessage()));
}
return this;
}
@Override
public Terminal normal() {
throw new UnsupportedOperationException();
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.normal(result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not switch console to normal mode for %s: %s", this, result.getMessage()));
}
return this;
}
@Override
public Terminal reset() {
throw new UnsupportedOperationException();
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.reset(result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not reset console for %s: %s", this, result.getMessage()));
}
return this;
}
}

View File

@@ -6,6 +6,8 @@ import net.rubygrapefruit.platform.TerminalAccess;
import net.rubygrapefruit.platform.internal.jni.WindowsConsoleFunctions;
public class WindowsTerminalAccess implements TerminalAccess {
private static Output currentlyOpen;
@Override
public boolean isTerminal(Output output) {
FunctionResult result = new FunctionResult();
@@ -19,6 +21,14 @@ public class WindowsTerminalAccess implements TerminalAccess {
@Override
public Terminal getTerminal(Output output) {
return new WindowsTerminal(output);
if (currentlyOpen != null) {
throw new UnsupportedOperationException("Currently only one output can be used as a terminal.");
}
WindowsTerminal terminal = new WindowsTerminal(output);
terminal.init();
currentlyOpen = output;
return terminal;
}
}

View File

@@ -7,4 +7,14 @@ public class WindowsConsoleFunctions {
public static native boolean isConsole(int filedes, FunctionResult result);
public static native void getConsoleSize(int filedes, MutableTerminalSize size, FunctionResult result);
public static native void initConsole(int filedes, FunctionResult result);
public static native void bold(FunctionResult result);
public static native void normal(FunctionResult result);
public static native void reset(FunctionResult result);
public static native void foreground(int ansiColor, FunctionResult result);
}

View File

@@ -3,6 +3,8 @@ package net.rubygrapefruit.platform
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import spock.lang.Specification
import net.rubygrapefruit.platform.internal.Platform
import spock.lang.IgnoreIf
class TerminalTest extends Specification {
@Rule TemporaryFolder tmpDir
@@ -14,12 +16,23 @@ class TerminalTest extends Specification {
!terminal.isTerminal(TerminalAccess.Output.Stderr);
}
def "cannot access terminal from a test"() {
@IgnoreIf({Platform.current().windows})
def "cannot access posix terminal from a test"() {
when:
terminal.getTerminal(TerminalAccess.Output.Stdout)
then:
NativeException e = thrown()
e.message == 'Could not open terminal: not a terminal'
e.message == 'Could not open terminal for stdout: not a terminal'
}
@IgnoreIf({!Platform.current().windows})
def "cannot access windows console from a test"() {
when:
terminal.getTerminal(TerminalAccess.Output.Stdout)
then:
NativeException e = thrown()
e.message == 'Could not open console for stdout: not a console'
}
}