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. * Handle multiple architectures.
* IBM JVM. * IBM JVM.
* Convert to c. * 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); 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 #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; import java.io.PrintStream;
public class DefaultTerminal implements Terminal { public class TerminfoTerminal extends AbstractTerminal {
private final TerminalAccess.Output output; private final TerminalAccess.Output output;
private final PrintStream stream; private final PrintStream stream;
private Color foreground; private Color foreground;
public DefaultTerminal(TerminalAccess.Output output) { public TerminfoTerminal(TerminalAccess.Output output) {
this.output = output; this.output = output;
stream = output == TerminalAccess.Output.Stdout ? System.out : System.err; 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(); stream.flush();
FunctionResult result = new FunctionResult(); FunctionResult result = new FunctionResult();
TerminfoFunctions.initTerminal(output.ordinal(), result); TerminfoFunctions.initTerminal(output.ordinal(), result);
if (result.isFailed()) { 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 @Override
@@ -40,7 +40,7 @@ public class DefaultTerminal implements Terminal {
FunctionResult result = new FunctionResult(); FunctionResult result = new FunctionResult();
PosixTerminalFunctions.getTerminalSize(output.ordinal(), terminalSize, result); PosixTerminalFunctions.getTerminalSize(output.ordinal(), terminalSize, result);
if (result.isFailed()) { 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; return terminalSize;
} }
@@ -51,7 +51,7 @@ public class DefaultTerminal implements Terminal {
FunctionResult result = new FunctionResult(); FunctionResult result = new FunctionResult();
TerminfoFunctions.foreground(color.ordinal(), result); TerminfoFunctions.foreground(color.ordinal(), result);
if (result.isFailed()) { 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; foreground = color;
return this; return this;
@@ -63,7 +63,7 @@ public class DefaultTerminal implements Terminal {
FunctionResult result = new FunctionResult(); FunctionResult result = new FunctionResult();
TerminfoFunctions.bold(result); TerminfoFunctions.bold(result);
if (result.isFailed()) { 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; return this;
} }
@@ -83,7 +83,7 @@ public class DefaultTerminal implements Terminal {
FunctionResult result = new FunctionResult(); FunctionResult result = new FunctionResult();
TerminfoFunctions.reset(result); TerminfoFunctions.reset(result);
if (result.isFailed()) { 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; 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."); 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(); terminal.init();
currentlyOpen = output; currentlyOpen = output;

View File

@@ -6,41 +6,75 @@ import net.rubygrapefruit.platform.TerminalAccess;
import net.rubygrapefruit.platform.TerminalSize; import net.rubygrapefruit.platform.TerminalSize;
import net.rubygrapefruit.platform.internal.jni.WindowsConsoleFunctions; import net.rubygrapefruit.platform.internal.jni.WindowsConsoleFunctions;
public class WindowsTerminal implements Terminal { public class WindowsTerminal extends AbstractTerminal {
private final TerminalAccess.Output output; private final TerminalAccess.Output output;
public WindowsTerminal(TerminalAccess.Output output) { public WindowsTerminal(TerminalAccess.Output output) {
this.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 @Override
public TerminalSize getTerminalSize() { public TerminalSize getTerminalSize() {
FunctionResult result = new FunctionResult(); FunctionResult result = new FunctionResult();
MutableTerminalSize size = new MutableTerminalSize(); MutableTerminalSize size = new MutableTerminalSize();
WindowsConsoleFunctions.getConsoleSize(output.ordinal(), size, result); WindowsConsoleFunctions.getConsoleSize(output.ordinal(), size, result);
if (result.isFailed()) { 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; return size;
} }
@Override @Override
public Terminal bold() { 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 @Override
public Terminal foreground(Color color) { 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 @Override
public Terminal normal() { 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 @Override
public Terminal reset() { 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; import net.rubygrapefruit.platform.internal.jni.WindowsConsoleFunctions;
public class WindowsTerminalAccess implements TerminalAccess { public class WindowsTerminalAccess implements TerminalAccess {
private static Output currentlyOpen;
@Override @Override
public boolean isTerminal(Output output) { public boolean isTerminal(Output output) {
FunctionResult result = new FunctionResult(); FunctionResult result = new FunctionResult();
@@ -19,6 +21,14 @@ public class WindowsTerminalAccess implements TerminalAccess {
@Override @Override
public Terminal getTerminal(Output output) { 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 boolean isConsole(int filedes, FunctionResult result);
public static native void getConsoleSize(int filedes, MutableTerminalSize size, 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.Rule
import org.junit.rules.TemporaryFolder import org.junit.rules.TemporaryFolder
import spock.lang.Specification import spock.lang.Specification
import net.rubygrapefruit.platform.internal.Platform
import spock.lang.IgnoreIf
class TerminalTest extends Specification { class TerminalTest extends Specification {
@Rule TemporaryFolder tmpDir @Rule TemporaryFolder tmpDir
@@ -14,12 +16,23 @@ class TerminalTest extends Specification {
!terminal.isTerminal(TerminalAccess.Output.Stderr); !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: when:
terminal.getTerminal(TerminalAccess.Output.Stdout) terminal.getTerminal(TerminalAccess.Output.Stdout)
then: then:
NativeException e = thrown() 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'
} }
} }