WIP - initial consolidation of input/output functionality.

This commit is contained in:
nathan 2016-05-28 11:30:46 +02:00
parent cc38ee754f
commit f190a31c39
51 changed files with 5218 additions and 1707 deletions

View File

@ -1,229 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="EclipseModuleManager">
<libelement value="jar://$MODULE_DIR$/libs/jansi/jansi-1.11b.jar!/" />
<libelement value="jar://$MODULE_DIR$/libs/jna/jna-4.1.0.jar!/" />
<libelement value="jar://$MODULE_DIR$/libs/logging/slf4j-api-1.7.5.jar!/" />
<libelement value="jar://$MODULE_DIR$/libs/slf4j-api-1.7.5.jar!/" />
<libelement value="jar://$MODULE_DIR$/libs/ObjectPool_v1.1.jar!/" />
<libelement value="jar://$MODULE_DIR$/libs/dorkboxUtil_v1.1.jar!/" />
<src_description expected_position="0">
<src_folder value="file://$MODULE_DIR$/src" expected_position="0" />
</src_description>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_6" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/dist" />
<excludeFolder url="file://$MODULE_DIR$/libs" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="jdk" jdkName="1.6" jdkType="JavaSDK" />
<orderEntry type="library" name="logging slf4j-api" level="application" />
<orderEntry type="module" module-name="Dorkbox-Util" />
<orderEntry type="module" module-name="JavaLauncher-Util" />
<orderEntry type="module" module-name="ObjectPool" />
<orderEntry type="library" name="jansi" level="application" />
<orderEntry type="library" name="jna" level="application" />
</component>
<component name="org.twodividedbyzero.idea.findbugs">
<option name="_basePreferences">
<map>
<entry key="property.analysisEffortLevel" value="default" />
<entry key="property.analyzeAfterCompile" value="false" />
<entry key="property.annotationGutterIconEnabled" value="true" />
<entry key="property.annotationSuppressWarningsClass" value="edu.umd.cs.findbugs.annotations.SuppressFBWarnings" />
<entry key="property.annotationTextRangeMarkupEnabled" value="true" />
<entry key="property.exportAsHtml" value="true" />
<entry key="property.exportAsXml" value="true" />
<entry key="property.exportBaseDir" value="" />
<entry key="property.exportCreateArchiveDir" value="false" />
<entry key="property.exportOpenBrowser" value="true" />
<entry key="property.minPriorityToReport" value="Medium" />
<entry key="property.runAnalysisInBackground" value="false" />
<entry key="property.showHiddenDetectors" value="false" />
<entry key="property.toolWindowToFront" value="true" />
</map>
</option>
<option name="_detectors">
<map>
<entry key="AppendingToAnObjectOutputStream" value="true" />
<entry key="AtomicityProblem" value="true" />
<entry key="BadAppletConstructor" value="false" />
<entry key="BadResultSetAccess" value="true" />
<entry key="BadSyntaxForRegularExpression" value="true" />
<entry key="BadUseOfReturnValue" value="true" />
<entry key="BadlyOverriddenAdapter" value="true" />
<entry key="BooleanReturnNull" value="true" />
<entry key="BuildInterproceduralCallGraph" value="false" />
<entry key="BuildObligationPolicyDatabase" value="true" />
<entry key="BuildStringPassthruGraph" value="true" />
<entry key="CallToUnsupportedMethod" value="false" />
<entry key="CalledMethods" value="true" />
<entry key="CheckCalls" value="false" />
<entry key="CheckExpectedWarnings" value="false" />
<entry key="CheckImmutableAnnotation" value="true" />
<entry key="CheckRelaxingNullnessAnnotation" value="true" />
<entry key="CheckTypeQualifiers" value="true" />
<entry key="CloneIdiom" value="true" />
<entry key="ComparatorIdiom" value="true" />
<entry key="ConfusedInheritance" value="true" />
<entry key="ConfusionBetweenInheritedAndOuterMethod" value="true" />
<entry key="CrossSiteScripting" value="true" />
<entry key="DefaultEncodingDetector" value="true" />
<entry key="DoInsideDoPrivileged" value="true" />
<entry key="DontCatchIllegalMonitorStateException" value="true" />
<entry key="DontIgnoreResultOfPutIfAbsent" value="true" />
<entry key="DontUseEnum" value="true" />
<entry key="DroppedException" value="true" />
<entry key="DumbMethodInvocations" value="true" />
<entry key="DumbMethods" value="true" />
<entry key="DuplicateBranches" value="true" />
<entry key="EmptyZipFileEntry" value="false" />
<entry key="EqualsOperandShouldHaveClassCompatibleWithThis" value="true" />
<entry key="ExplicitSerialization" value="true" />
<entry key="FieldItemSummary" value="true" />
<entry key="FinalizerNullsFields" value="true" />
<entry key="FindBadCast2" value="true" />
<entry key="FindBadForLoop" value="true" />
<entry key="FindBugsSummaryStats" value="true" />
<entry key="FindCircularDependencies" value="false" />
<entry key="FindDeadLocalStores" value="true" />
<entry key="FindDoubleCheck" value="true" />
<entry key="FindEmptySynchronizedBlock" value="true" />
<entry key="FindFieldSelfAssignment" value="true" />
<entry key="FindFinalizeInvocations" value="true" />
<entry key="FindFloatEquality" value="true" />
<entry key="FindFloatMath" value="false" />
<entry key="FindHEmismatch" value="true" />
<entry key="FindInconsistentSync2" value="true" />
<entry key="FindJSR166LockMonitorenter" value="true" />
<entry key="FindLocalSelfAssignment2" value="true" />
<entry key="FindMaskedFields" value="true" />
<entry key="FindMismatchedWaitOrNotify" value="true" />
<entry key="FindNakedNotify" value="true" />
<entry key="FindNoSideEffectMethods" value="true" />
<entry key="FindNonSerializableStoreIntoSession" value="false" />
<entry key="FindNonSerializableValuePassedToWriteObject" value="false" />
<entry key="FindNonShortCircuit" value="true" />
<entry key="FindNullDeref" value="true" />
<entry key="FindNullDerefsInvolvingNonShortCircuitEvaluation" value="true" />
<entry key="FindOpenStream" value="true" />
<entry key="FindPuzzlers" value="true" />
<entry key="FindRefComparison" value="true" />
<entry key="FindReturnRef" value="true" />
<entry key="FindRoughConstants" value="true" />
<entry key="FindRunInvocations" value="true" />
<entry key="FindSelfComparison" value="true" />
<entry key="FindSelfComparison2" value="true" />
<entry key="FindSleepWithLockHeld" value="true" />
<entry key="FindSpinLoop" value="true" />
<entry key="FindSqlInjection" value="true" />
<entry key="FindTwoLockWait" value="true" />
<entry key="FindUncalledPrivateMethods" value="true" />
<entry key="FindUnconditionalWait" value="true" />
<entry key="FindUninitializedGet" value="true" />
<entry key="FindUnrelatedTypesInGenericContainer" value="true" />
<entry key="FindUnreleasedLock" value="true" />
<entry key="FindUnsatisfiedObligation" value="true" />
<entry key="FindUnsyncGet" value="true" />
<entry key="FindUseOfNonSerializableValue" value="true" />
<entry key="FindUselessControlFlow" value="true" />
<entry key="FormatStringChecker" value="true" />
<entry key="FunctionsThatMightBeMistakenForProcedures" value="true" />
<entry key="HugeSharedStringConstants" value="true" />
<entry key="IDivResultCastToDouble" value="true" />
<entry key="IncompatMask" value="true" />
<entry key="InconsistentAnnotations" value="true" />
<entry key="InefficientIndexOf" value="true" />
<entry key="InefficientInitializationInsideLoop" value="true" />
<entry key="InefficientMemberAccess" value="false" />
<entry key="InefficientToArray" value="true" />
<entry key="InfiniteLoop" value="true" />
<entry key="InfiniteRecursiveLoop" value="true" />
<entry key="InheritanceUnsafeGetResource" value="true" />
<entry key="InitializationChain" value="true" />
<entry key="InitializeNonnullFieldsInConstructor" value="true" />
<entry key="InstantiateStaticClass" value="true" />
<entry key="IntCast2LongAsInstant" value="true" />
<entry key="InvalidJUnitTest" value="true" />
<entry key="IteratorIdioms" value="true" />
<entry key="LazyInit" value="true" />
<entry key="LoadOfKnownNullValue" value="true" />
<entry key="LostLoggerDueToWeakReference" value="true" />
<entry key="MethodReturnCheck" value="true" />
<entry key="Methods" value="true" />
<entry key="MultithreadedInstanceAccess" value="true" />
<entry key="MutableLock" value="true" />
<entry key="MutableStaticFields" value="true" />
<entry key="Naming" value="true" />
<entry key="Noise" value="false" />
<entry key="NoiseNullDeref" value="false" />
<entry key="NoteAnnotationRetention" value="true" />
<entry key="NoteCheckReturnValueAnnotations" value="true" />
<entry key="NoteDirectlyRelevantTypeQualifiers" value="true" />
<entry key="NoteJCIPAnnotation" value="true" />
<entry key="NoteNonNullAnnotations" value="false" />
<entry key="NoteNonnullReturnValues" value="false" />
<entry key="NoteSuppressedWarnings" value="true" />
<entry key="NoteUnconditionalParamDerefs" value="true" />
<entry key="NumberConstructor" value="true" />
<entry key="OptionalReturnNull" value="true" />
<entry key="OverridingEqualsNotSymmetrical" value="true" />
<entry key="PreferZeroLengthArrays" value="true" />
<entry key="PublicSemaphores" value="false" />
<entry key="QuestionableBooleanAssignment" value="true" />
<entry key="ReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass" value="true" />
<entry key="ReadReturnShouldBeChecked" value="true" />
<entry key="RedundantInterfaces" value="true" />
<entry key="ReflectiveClasses" value="true" />
<entry key="RepeatedConditionals" value="true" />
<entry key="ResolveAllReferences" value="false" />
<entry key="RuntimeExceptionCapture" value="true" />
<entry key="SerializableIdiom" value="true" />
<entry key="StartInConstructor" value="true" />
<entry key="StaticCalendarDetector" value="true" />
<entry key="StringConcatenation" value="true" />
<entry key="SuperfluousInstanceOf" value="true" />
<entry key="SuspiciousThreadInterrupted" value="true" />
<entry key="SwitchFallthrough" value="true" />
<entry key="SynchronizationOnSharedBuiltinConstant" value="true" />
<entry key="SynchronizeAndNullCheckField" value="true" />
<entry key="SynchronizeOnClassLiteralNotGetClass" value="true" />
<entry key="SynchronizingOnContentsOfFieldToProtectField" value="true" />
<entry key="TestASM" value="false" />
<entry key="TestDataflowAnalysis" value="false" />
<entry key="TestingGround" value="false" />
<entry key="TestingGround2" value="false" />
<entry key="TrainFieldStoreTypes" value="true" />
<entry key="TrainLongInstantfParams" value="true" />
<entry key="TrainNonNullAnnotations" value="true" />
<entry key="TrainUnconditionalDerefParams" value="true" />
<entry key="URLProblems" value="true" />
<entry key="UncallableMethodOfAnonymousClass" value="true" />
<entry key="UnnecessaryMath" value="true" />
<entry key="UnreadFields" value="true" />
<entry key="UselessSubclassMethod" value="false" />
<entry key="VarArgsProblems" value="true" />
<entry key="VolatileUsage" value="true" />
<entry key="WaitInLoop" value="true" />
<entry key="WrongMapIterator" value="true" />
<entry key="XMLFactoryBypass" value="true" />
</map>
</option>
<option name="_reportCategories">
<map>
<entry key="BAD_PRACTICE" value="true" />
<entry key="CORRECTNESS" value="true" />
<entry key="EXPERIMENTAL" value="true" />
<entry key="I18N" value="true" />
<entry key="MALICIOUS_CODE" value="true" />
<entry key="MT_CORRECTNESS" value="true" />
<entry key="PERFORMANCE" value="true" />
<entry key="SECURITY" value="true" />
<entry key="STYLE" value="true" />
</map>
</option>
</component>
</module>

View File

@ -9,7 +9,7 @@ This small library is very similar to what JLine provides, however it does 4 thi
- Backspace functionality for line input is preserved.
- Ctrl-C (SIGINT) is also preserved in windows
2. Uses native calls via JNA (instead of shell execution) for linux & mac terminal configuration
3. Supports unsupported teminals (for example, while in an IDE ), so in.read() will still return (a line is split into chars, then fed to consumer). The enter key must still be pressed.
3. Supports unsupported terminals (for example, while in an IDE ), `in.read()` will still return (a line is split into chars, then fed to consumer). The enter key must still be pressed.
4. Multi-threaded, intelligent buffering of command input for simultaneous input readers on different threads

3
src/META-INF/MANIFEST.MF Normal file
View File

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: com.dorkbox.console.AnsiRendererTestExample

View File

@ -0,0 +1,47 @@
package dorkbox.console;
/**
*
*/
public
class Console {
public static volatile boolean ENABLE_ECHO = true;
public static boolean PASSWORD_ECHO = true;
public static char PASSWORD_ECHO_CHAR = '*';
// how many threads can read from this input at the same time
public static int NUMBER_OF_READERS = 32;
public final static boolean ENABLE_BACKSPACE = true;
// enableBackspace = Boolean.parseBoolean(System.getProperty(Console.ENABLE_BACKSPACE, "true"));
public static String ENABLE_BACKSPACEs = "input.enableBackspace";
// OS types supported by the input console. Default is AUTO
public static final String AUTO = "auto";
public static final String UNIX = "unix";
public static final String WIN = "win";
public static final String WINDOWS = "windows";
public static final String NONE = "none"; // this is the same as unsupported
public static final String OFF = "off"; // this is the same as unsupported
public static final String FALSE = "false"; // this is the same as unsupported
public static final String INPUT_CONSOLE_OSTYPE = "AUTO";
/**
* Gets the version number.
*/
public static
String getVersion() {
return "2.9";
}
public static Input in = new Input();
public static String out;
public static String err;
}

View File

@ -0,0 +1,15 @@
package dorkbox.console;
/**
*
*/
public
class Input {
public
Input() {
}
public int read() {
return InputConsole.read();
}
}

View File

@ -0,0 +1,478 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.console;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CopyOnWriteArrayList;
import org.slf4j.Logger;
import dorkbox.console.input.PosixTerminal;
import dorkbox.console.input.Terminal;
import dorkbox.console.input.UnsupportedTerminal;
import dorkbox.console.input.WindowsTerminal;
import dorkbox.console.output.Ansi;
import dorkbox.objectPool.ObjectPool;
import dorkbox.util.FastThreadLocal;
import dorkbox.util.OS;
import dorkbox.util.bytes.ByteBuffer2;
import dorkbox.util.bytes.ByteBuffer2Poolable;
public
class InputConsole {
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(InputConsole.class);
private static final char[] emptyLine = new char[0];
private final static ObjectPool<ByteBuffer2> pool;
private final static Terminal terminal;
private static final Object inputLock = new Object();
private static final Object inputLockSingle = new Object();
private static final Object inputLockLine = new Object();
private static final FastThreadLocal<ByteBuffer2> readBuff = new FastThreadLocal<ByteBuffer2>();
private static final List<ByteBuffer2> readBuffers = new CopyOnWriteArrayList<ByteBuffer2>();
private static final FastThreadLocal<Integer> threadBufferCounter = new FastThreadLocal<Integer>();
private static final FastThreadLocal<ByteBuffer2> readLineBuff = new FastThreadLocal<ByteBuffer2>();
private static final List<ByteBuffer2> readLineBuffers = new CopyOnWriteArrayList<ByteBuffer2>();
static {
pool = ObjectPool.Blocking(new ByteBuffer2Poolable(), Console.NUMBER_OF_READERS);
String type = Console.INPUT_CONSOLE_OSTYPE.toUpperCase(Locale.ENGLISH);
Throwable didFallbackE = null;
Class<? extends Terminal> t;
try {
if (type.equals(Console.UNIX)) {
t = PosixTerminal.class;
}
else if (type.equals(Console.WIN) || type.equals(Console.WINDOWS)) {
t = WindowsTerminal.class;
}
else if (type.equals(Console.NONE) || type.equals(Console.OFF) || type.equals(Console.FALSE)) {
t = UnsupportedTerminal.class;
}
else {
// if these cannot be created, because we are in an IDE, an error will be thrown
if (OS.isWindows()) {
t = WindowsTerminal.class;
}
else {
t = PosixTerminal.class;
}
}
} catch (Exception e) {
didFallbackE = e;
t = UnsupportedTerminal.class;
}
Terminal term = null;
try {
term = t.newInstance();
} catch (Throwable e) {
didFallbackE = e;
t = UnsupportedTerminal.class;
try {
term = t.newInstance();
} catch (Exception e1) {
// UnsupportedTerminal can't do this
}
}
terminal = term;
if (logger.isTraceEnabled()) {
logger.trace("Creating terminal based on type: " + type);
}
if (logger.isDebugEnabled()) {
logger.debug("Created Terminal: {} ({}w x {}h)", InputConsole.terminal.getClass().getSimpleName(),
InputConsole.terminal.getWidth(), InputConsole.terminal.getHeight());
}
if (didFallbackE != null && !didFallbackE.getMessage().equals(Terminal.CONSOLE_ERROR_INIT)) {
logger.error("Failed to construct terminal, falling back to unsupported.", didFallbackE);
} else if (term instanceof UnsupportedTerminal) {
logger.debug("Terminal is in UNSUPPORTED (best guess). Unable to support single key input. Only line input available.");
}
// echo and backspace
term.setEchoEnabled(Console.ENABLE_ECHO);
term.setInterruptEnabled(Console.ENABLE_BACKSPACE);
Thread consoleThread = new Thread(new Runnable() {
@Override
public
void run() {
InputConsole.run();
}
});
consoleThread.setDaemon(true);
consoleThread.setName("Console Input Reader");
consoleThread.start();
// has to be NOT DAEMON thread, since it must run before the app closes.
// don't forget we have to shut down the ansi console as well
// alternatively, shut everything down when the JVM closes.
Thread shutdownThread = new Thread() {
@Override
public
void run() {
// called when the JVM is shutting down.
release0();
try {
terminal.restore();
// this will 'hang' our shutdown, and honestly, who cares? We're shutting down anyways.
// inputConsole.reader.close(); // hangs on shutdown
} catch (IOException ignored) {
ignored.printStackTrace();
}
}
};
shutdownThread.setName("Console Input Shutdown");
Runtime.getRuntime().addShutdownHook(shutdownThread);
}
private static InputStream wrappedInputStream = new InputStream() {
@Override
public
int read() throws IOException {
return InputConsole.read();
}
@Override
public
void close() throws IOException {
InputConsole.release0();
}
};
public static
InputStream getInputStream() {
return wrappedInputStream;
}
private
InputConsole() {
}
/**
* return -1 if no data or bunged-up
*/
public static
int read() {
Integer bufferCounter = threadBufferCounter.get();
ByteBuffer2 buffer = readBuff.get();
if (buffer == null) {
bufferCounter = 0;
threadBufferCounter.set(bufferCounter);
try {
buffer = pool.takeInterruptibly();
buffer.clear();
} catch (InterruptedException e) {
logger.error("Interrupted while receiving buffer from pool.");
buffer = pool.newInstance();
}
readBuff.set(buffer);
readBuffers.add(buffer);
}
if (bufferCounter == buffer.position()) {
synchronized (inputLockSingle) {
buffer.setPosition(0);
threadBufferCounter.set(0);
try {
inputLockSingle.wait();
} catch (InterruptedException e) {
return -1;
}
}
}
bufferCounter = threadBufferCounter.get();
char c = buffer.readChar(bufferCounter);
bufferCounter += 2;
threadBufferCounter.set(bufferCounter);
return c;
}
/**
* return empty char[] if no data
*/
public static
char[] readLinePassword() {
// don't bother in an IDE. it won't work.
boolean echoEnabled = Console.ENABLE_ECHO;
Console.ENABLE_ECHO = false;
char[] readLine0 = readLineChars();
Console.ENABLE_ECHO = echoEnabled;
return readLine0;
}
/**
* return null if no data
*/
public static
String readLine() {
char[] line = InputConsole.readLineChars();
if (line == null) {
return null;
}
return new String(line);
}
/**
* return empty char[] if no data
*/
public static
char[] readLineChars() {
synchronized (inputLock) {
// empty here, because we don't want to register a readLine WHILE we are still processing
// the current line info.
// the threadBufferForRead getting added is the part that is important
if (readLineBuff.get() == null) {
ByteBuffer2 buffer;
try {
buffer = pool.takeInterruptibly();
} catch (InterruptedException e) {
logger.error("Interrupted while receiving buffer from pool.");
buffer = pool.newInstance();
}
readLineBuff.set(buffer);
readLineBuffers.add(buffer);
}
else {
readLineBuff.get().clear();
}
}
synchronized (inputLockLine) {
try {
inputLockLine.wait();
} catch (InterruptedException e) {
return emptyLine;
}
}
ByteBuffer2 buffer = readLineBuff.get();
int len = buffer.position();
if (len == 0) {
return emptyLine;
}
buffer.rewind();
char[] readChars = buffer.readChars(len / 2); // java always stores chars in 2 bytes
// dump the chars in the buffer (safer for passwords, etc)
buffer.clearSecure();
readLineBuffers.remove(buffer);
pool.put(buffer);
readLineBuff.set(null);
return readChars;
}
/**
* releases any thread still waiting.
*/
private static
void release0() {
synchronized (inputLockSingle) {
inputLockSingle.notifyAll();
}
synchronized (inputLockLine) {
inputLockLine.notifyAll();
}
}
private static
void run() {
Logger logger2 = logger;
final boolean ansiEnabled = Ansi.isEnabled();
Ansi ansi = Ansi.ansi();
// PrintStream out = AnsiConsole.out;
PrintStream out = System.out;
int typedChar;
char asChar;
final char overWriteChar = ' ';
boolean enableBackspace = Console.ENABLE_BACKSPACE;
// don't type ; in a bash shell, it quits everything
// \n is replaced by \r in unix terminal?
while ((typedChar = terminal.read()) != -1) {
synchronized (inputLock) {
// don't let anyone add a new reader while we are still processing the current actions
asChar = (char) typedChar;
if (logger2.isTraceEnabled()) {
logger2.trace("READ: {} ({})", asChar, typedChar);
}
// notify everyone waiting for a character.
synchronized (inputLockSingle) {
// have to do readChar first (readLine has to deal with \b and \n
for (ByteBuffer2 buffer : readBuffers) {
buffer.writeChar(asChar);
}
inputLockSingle.notifyAll();
}
// now to handle readLine stuff
// if we type a backspace key, swallow it + previous in READLINE. READCHAR will have it passed anyways.
if (enableBackspace && asChar == '\b') {
int position = 0;
char[] overwrite = null;
// clear ourself + one extra.
for (ByteBuffer2 buffer : readLineBuffers) {
// size of the buffer BEFORE our backspace was typed
int length = buffer.position();
int amtToOverwrite = 4; // 2*2 backspace is always 2 chars (^?) * 2 because it's bytes
if (length > 1) {
char charAt = buffer.readChar(length - 2);
amtToOverwrite += getPrintableCharacters(charAt);
// delete last item in our buffer
length -= 2;
buffer.setPosition(length);
// now figure out where the cursor is really at.
// this is more memory friendly than buf.toString.length
for (int i = 0; i < length; i += 2) {
charAt = buffer.readChar(i);
position += getPrintableCharacters(charAt);
}
position++;
}
overwrite = new char[amtToOverwrite];
for (int i = 0; i < amtToOverwrite; i++) {
overwrite[i] = overWriteChar;
}
}
if (ansiEnabled && overwrite != null) {
// move back however many, over write, then go back again
out.print(ansi.cursorToColumn(position));
out.print(overwrite);
out.print(ansi.cursorToColumn(position));
out.flush();
}
}
else if (asChar == '\n') {
// ignoring \r, because \n is ALWAYS the last character in a new line sequence. (even for windows)
synchronized (inputLockLine) {
inputLockLine.notifyAll();
}
}
else {
// only append if we are not a new line.
// our windows console PREVENTS us from returning '\r' (it truncates '\r\n', and returns just '\n')
for (ByteBuffer2 buffer : readLineBuffers) {
buffer.writeChar(asChar);
}
}
}
}
}
private static final int PLUS_TWO_MAYBE = 128 + 32;
private static final int PLUS_ONE = 128 + 127;
/**
* Return the number of characters that will be printed when the specified character is echoed to the screen
* <p/>
* Adapted from cat by Torbjorn Granlund, as repeated in stty by David MacKenzie.
*/
private static
int getPrintableCharacters(final int ch) {
// StringBuilder sbuff = new StringBuilder();
if (ch >= 32) {
if (ch < 127) {
// sbuff.append((char) ch);
return 1;
}
else if (ch == 127) {
// sbuff.append('^');
// sbuff.append('?');
return 2;
}
else {
// sbuff.append('M');
// sbuff.append('-');
int count = 2;
if (ch >= PLUS_TWO_MAYBE) {
if (ch < PLUS_ONE) {
// sbuff.append((char) (ch - 128));
count++;
}
else {
// sbuff.append('^');
// sbuff.append('?');
count += 2;
}
}
else {
// sbuff.append('^');
// sbuff.append((char) (ch - 128 + 64));
count += 2;
}
return count;
}
}
else {
// sbuff.append('^');
// sbuff.append((char) (ch + 64));
return 2;
}
// return sbuff;
}
}

View File

@ -13,19 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.inputConsole;
package dorkbox.console;
public class TerminalType {
public static final String TYPE = "input.terminal";
public static final String READERS = "input.terminal.readers";
public static final String ENABLE_BACKSPACE = "input.enableBackspace";
public static final String AUTO = "auto";
public static final String UNIX = "unix";
public static final String WIN = "win";
public static final String WINDOWS = "windows";
public static final String NONE = "none";
public static final String OFF = "off";
public static final String FALSE = "false";
}

View File

@ -0,0 +1,184 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.console.input;
import java.io.IOException;
import java.nio.ByteBuffer;
import com.sun.jna.ptr.IntByReference;
import dorkbox.console.Console;
import dorkbox.console.util.posix.CLibraryPosix;
import dorkbox.console.util.posix.Termios;
/**
* Terminal that is used for unix platforms. Terminal initialization is handled via JNA and ioctl/tcgetattr/tcsetattr/cfmakeraw.
* <p>
* This implementation should work for an reasonable POSIX system.
*/
public
class PosixTerminal extends Terminal {
private final Termios original = new Termios();
private Termios termInfo = new Termios();
private ByteBuffer windowSizeBuffer = ByteBuffer.allocate(8);
private volatile boolean echo_enabled = !Console.ENABLE_ECHO;
private final IntByReference inputRef = new IntByReference();
public
PosixTerminal() throws IOException {
// save off the defaults
if (CLibraryPosix.tcgetattr(0, this.original) != 0) {
throw new IOException(CONSOLE_ERROR_INIT);
}
// COMPARABLE TO (from upstream)
//settings.set("-icanon min 1 -ixon");
//settings.set("dsusp undef");
// CTRL-I (tab), CTRL-M (enter) do not work
if (CLibraryPosix.tcgetattr(0, this.termInfo) != 0) {
throw new IOException(CONSOLE_ERROR_INIT);
}
// this.termInfo.inputFlags |= Termios.Input.INLCR; // Map NL to CR on input
// this.termInfo.inputFlags |= Termios.Input.ICRNL; // map CR to NL (ala CRMOD)
this.termInfo.inputFlags &= ~Termios.Input.IXON; // DISABLE - output flow control mediated by ^S and ^Q
this.termInfo.inputFlags &= ~Termios.Input.IXOFF; // DISABLE - input flow control mediated by ^S and ^Q
this.termInfo.inputFlags &= ~Termios.Input.BRKINT; // DISABLE - map BREAK to SIGINTR
this.termInfo.inputFlags &= ~Termios.Input.INPCK; // DISABLE - enable checking of parity errors
this.termInfo.inputFlags &= ~Termios.Input.PARMRK; // DISABLE - mark parity and framing errors
this.termInfo.inputFlags &= ~Termios.Input.ISTRIP; // DISABLE - strip 8th bit off chars
this.termInfo.inputFlags |= Termios.Input.IGNBRK; // ignore BREAK condition
this.termInfo.localFlags &= ~Termios.Local.ICANON; // DISABLE - pass chars straight through to terminal instantly
this.termInfo.localFlags |= Termios.Local.ECHOCTL; // echo control chars as ^(Char)
this.termInfo.controlFlags &= ~Termios.Control.CSIZE; // REMOVE character size mask
this.termInfo.controlFlags &= ~Termios.Control.PARENB; // DISABLE - parity enable
this.termInfo.controlFlags |= Termios.Control.CS8; // set character size mask 8 bits
this.termInfo.controlFlags |= Termios.Control.CREAD; // enable receiver
// If MIN > 0 and TIME = 0, MIN sets the number of characters to receive before the read is satisfied.
// As TIME is zero, the timer is not used.
this.termInfo.controlChars[Termios.ControlChars.VMIN] = (byte) 2; // Minimum number of characters for non-canonical read (MIN).
this.termInfo.controlChars[Termios.ControlChars.VTIME] = (byte) 5; // Timeout in deci-seconds for non-canonical read (TIME).
this.termInfo.controlChars[Termios.ControlChars.VSUSP] = (byte) 0; // suspend disabled
this.termInfo.controlChars[Termios.ControlChars.VEOF] = (byte) 0; // eof disabled
this.termInfo.controlChars[Termios.ControlChars.VEOL] = (byte) 0; // eol disabled
if (CLibraryPosix.tcsetattr(0, Termios.TCSANOW, this.termInfo) != 0) {
throw new IOException("Can not set terminal flags");
}
}
/**
* Restore the original terminal configuration, which can be used when shutting down the console reader. The ConsoleReader cannot be
* used after calling this method.
*/
@Override
public final
void restore() throws IOException {
if (CLibraryPosix.tcsetattr(0, Termios.TCSANOW, this.original) != 0) {
throw new IOException("Can not reset terminal to defaults");
}
}
/**
* Returns number of columns in the terminal.
*/
@SuppressWarnings("NumericCastThatLosesPrecision")
@Override
public final
int getWidth() {
if (CLibraryPosix.ioctl(0, CLibraryPosix.TIOCGWINSZ, this.windowSizeBuffer) != 0) {
return DEFAULT_WIDTH;
}
return (short) (0x000000FF & this.windowSizeBuffer.get(2) + (0x000000FF & this.windowSizeBuffer.get(3)) * 256);
}
/**
* Returns number of rows in the terminal.
*/
@SuppressWarnings("NumericCastThatLosesPrecision")
@Override
public final
int getHeight() {
if (CLibraryPosix.ioctl(0, CLibraryPosix.TIOCGWINSZ, this.windowSizeBuffer) != 0) {
return DEFAULT_HEIGHT;
}
return (short) (0x000000FF & this.windowSizeBuffer.get(0) + (0x000000FF & this.windowSizeBuffer.get(1)) * 256);
}
@Override
public final
int read() {
// TODO: should have a better way of doing this! (should use a method to set echo, instead of a field?)
// have to determine when it changes
if (echo_enabled != Console.ENABLE_ECHO) {
echo_enabled = Console.ENABLE_ECHO;
setEchoEnabled(echo_enabled);
setInterruptEnabled(false);
}
CLibraryPosix.read(0, inputRef, 1);
return inputRef.getValue();
}
public
void setEchoEnabled(final boolean enabled) {
// have to re-get them, since flags change everything
if (CLibraryPosix.tcgetattr(0, this.termInfo) != 0) {
this.logger.error("Failed to get terminal info");
}
if (enabled) {
this.termInfo.localFlags |= Termios.Local.ECHO; // ENABLE Echo input characters.
}
else {
this.termInfo.localFlags &= ~Termios.Local.ECHO; // DISABLE Echo input characters.
}
if (CLibraryPosix.tcsetattr(0, Termios.TCSANOW, this.termInfo) != 0) {
this.logger.error("Can not set terminal flags");
}
}
public
void setInterruptEnabled(final boolean enabled) {
// have to re-get them, since flags change everything
if (CLibraryPosix.tcgetattr(0, this.termInfo) != 0) {
this.logger.error("Failed to get terminal info");
}
if (enabled) {
this.termInfo.localFlags |= Termios.Local.ISIG; // ENABLE ctrl-C
}
else {
this.termInfo.localFlags &= ~Termios.Local.ISIG; // DISABLE ctrl-C
}
if (CLibraryPosix.tcsetattr(0, Termios.TCSANOW, this.termInfo) != 0) {
this.logger.error("Can not set terminal flags");
}
}
}

View File

@ -13,40 +13,40 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.inputConsole;
package dorkbox.console.input;
import java.io.IOException;
public abstract class Terminal {
protected final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(getClass());
public abstract
class Terminal {
public static final String CONSOLE_ERROR_INIT = "Unable to get input console mode.";
protected static final int DEFAULT_WIDTH = 80;
protected static final int DEFAULT_HEIGHT = 24;
protected final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(getClass());
private volatile boolean echoEnabled;
protected Terminal() {}
public abstract void init() throws IOException;
public abstract void restore() throws IOException;
public void setEchoEnabled(boolean enabled) {
this.echoEnabled = enabled;
protected
Terminal() {
}
public boolean isEchoEnabled() {
return this.echoEnabled;
}
public abstract
void restore() throws IOException;
public abstract int getWidth();
public abstract
int getWidth();
public abstract int getHeight();
public abstract
int getHeight();
public abstract
void setEchoEnabled(final boolean enabled);
public abstract
void setInterruptEnabled(final boolean enabled);
/**
* @return a character from whatever underlying input method the terminal has available.
*/
public abstract int read();
public abstract
int read();
}

View File

@ -13,43 +13,54 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.inputConsole.unsupported;
package dorkbox.console.input;
import java.io.IOException;
import java.io.InputStream;
import dorkbox.inputConsole.Terminal;
import dorkbox.util.bytes.ByteBuffer2;
public class UnsupportedTerminal extends Terminal {
public
class UnsupportedTerminal extends Terminal {
private final ByteBuffer2 buffer = new ByteBuffer2(8, -1);
private final InputStream in = System.in;
private int readerCount = -1;
private final InputStream in;
public UnsupportedTerminal() {
this.in = System.in;
public
UnsupportedTerminal() {
}
@Override
public final void init() throws IOException {}
public final
void restore() {
}
@Override
public final void restore() {}
@Override
public final int getWidth() {
public final
int getWidth() {
return 0;
}
@Override
public final int getHeight() {
public final
int getHeight() {
return 0;
}
@Override
public final int read() {
public
void setEchoEnabled(final boolean enabled) {
}
@Override
public
void setInterruptEnabled(final boolean enabled) {
}
@Override
public final
int read() {
// if we are reading data (because we are in IDE mode), we want to return ALL the chars of the line!
// so, 'readerCount' is REALLY the index at which we return letters (until the whole string is returned)
@ -67,7 +78,8 @@ public class UnsupportedTerminal extends Terminal {
this.readerCount = this.buffer.position();
this.buffer.rewind();
break;
} else {
}
else {
this.buffer.writeChar(asChar);
}
}
@ -79,7 +91,8 @@ public class UnsupportedTerminal extends Terminal {
if (this.readerCount == this.buffer.position()) {
this.readerCount = -1;
return '\n';
} else {
}
else {
return this.buffer.readChar();
}
}

View File

@ -0,0 +1,190 @@
/*
* Copyright (c) 2002-2012, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* http://www.opensource.org/licenses/bsd-license.php
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
*/
package dorkbox.console.input;
import static dorkbox.console.util.windows.Kernel32.GetConsoleScreenBufferInfo;
import static dorkbox.console.util.windows.Kernel32.STD_INPUT_HANDLE;
import static dorkbox.console.util.windows.Kernel32.STD_OUTPUT_HANDLE;
import java.io.IOException;
import com.sun.jna.ptr.IntByReference;
import dorkbox.console.util.windows.CONSOLE_SCREEN_BUFFER_INFO;
import dorkbox.console.util.windows.ConsoleMode;
import dorkbox.console.util.windows.HANDLE;
import dorkbox.console.util.windows.INPUT_RECORD;
import dorkbox.console.util.windows.KEY_EVENT_RECORD;
import dorkbox.console.util.windows.Kernel32;
/**
* Terminal implementation for Microsoft Windows.
*/
public
class WindowsTerminal extends Terminal {
private final HANDLE console;
private final HANDLE outputConsole;
private final CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO();
private final INPUT_RECORD.ByReference inputRecords = new INPUT_RECORD.ByReference();
private final IntByReference reference = new IntByReference();
private volatile int originalMode;
public
WindowsTerminal() throws IOException {
console = Kernel32.GetStdHandle(STD_INPUT_HANDLE);
if (console == HANDLE.INVALID_HANDLE_VALUE) {
throw new IOException("Unable to get input console handle.");
}
outputConsole = Kernel32.GetStdHandle(STD_OUTPUT_HANDLE);
if (outputConsole == HANDLE.INVALID_HANDLE_VALUE) {
throw new IOException("Unable to get output console handle.");
}
IntByReference mode = new IntByReference();
if (Kernel32.GetConsoleMode(console, mode) == 0) {
throw new IOException(CONSOLE_ERROR_INIT);
}
this.originalMode = mode.getValue();
int newMode = this.originalMode |
ConsoleMode.ENABLE_LINE_INPUT.code |
ConsoleMode.ENABLE_ECHO_INPUT.code |
ConsoleMode.ENABLE_PROCESSED_INPUT.code |
ConsoleMode.ENABLE_WINDOW_INPUT.code;
// Disable input echo
newMode = newMode & ~ConsoleMode.ENABLE_ECHO_INPUT.code;
// Must set these four modes at the same time to make it work fine.
Kernel32.SetConsoleMode(console, newMode);
}
/**
* Restore the original terminal configuration, which can be used when shutting down the console reader.
* The ConsoleReader cannot be used after calling this method.
*/
@Override
public final
void restore() throws IOException {
// restore the old console mode
Kernel32.SetConsoleMode(console, this.originalMode);
Kernel32.CloseHandle(console);
Kernel32.CloseHandle(outputConsole);
}
@Override
public final
int getWidth() {
GetConsoleScreenBufferInfo(outputConsole, info);
int w = info.window.width() + 1;
return w < 1 ? DEFAULT_WIDTH : w;
}
@Override
public final
int getHeight() {
GetConsoleScreenBufferInfo(outputConsole, info);
int h = info.window.height() + 1;
return h < 1 ? DEFAULT_HEIGHT : h;
}
@Override
public
void setEchoEnabled(final boolean enabled) {
IntByReference mode = new IntByReference();
Kernel32.GetConsoleMode(console, mode);
int newMode;
if (enabled) {
// Enable Ctrl+C
newMode = mode.getValue() | ConsoleMode.ENABLE_ECHO_INPUT.code;
} else {
// Disable Ctrl+C
newMode = mode.getValue() & ~ConsoleMode.ENABLE_ECHO_INPUT.code;
}
Kernel32.SetConsoleMode(console, newMode);
}
@Override
public
void setInterruptEnabled(final boolean enabled) {
IntByReference mode = new IntByReference();
Kernel32.GetConsoleMode(console, mode);
int newMode;
if (enabled) {
// Enable Ctrl+C
newMode = mode.getValue() | ConsoleMode.ENABLE_PROCESSED_INPUT.code;
} else {
// Disable Ctrl+C
newMode = mode.getValue() & ~ConsoleMode.ENABLE_PROCESSED_INPUT.code;
}
Kernel32.SetConsoleMode(console, newMode);
}
@Override
public final
int read() {
int input = readInput();
// if (Console.ENABLE_ECHO) {
// char asChar = (char) input;
// if (asChar == '\n') {
// System.out.println();
// }
// else {
// System.out.print(asChar);
// }
//
// // have to flush, otherwise we'll never see the chars on screen
// System.out.flush();
// }
return input;
}
private
int readInput() {
// keep reading input events until we find one that we are interested in (ie: keyboard input)
while (true) {
// blocks until there is (at least) 1 event on the buffer
Kernel32.ReadConsoleInputW(console, inputRecords, 1, reference);
for (int i = 0; i < reference.getValue(); ++i) {
if (inputRecords.EventType == INPUT_RECORD.KEY_EVENT) {
KEY_EVENT_RECORD keyEvent = inputRecords.Event.KeyEvent;
//logger.trace(keyEvent.bKeyDown ? "KEY_DOWN" : "KEY_UP", "key code:", keyEvent.wVirtualKeyCode, "char:", (long)keyEvent.uChar.unicodeChar);
if (keyEvent.keyDown) {
final char uChar = keyEvent.uChar.unicodeChar;
if (uChar > 0) {
if (uChar == '\r') {
// we purposefully swallow input after \r, and substitute it with \n
return '\n';
}
return uChar;
}
}
}
}
}
}
}

View File

@ -0,0 +1,979 @@
/**
* Copyright (C) 2009, Progress Software Corporation and/or its
* subsidiaries or affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a asValue of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.console.output;
import java.util.ArrayList;
import java.util.concurrent.Callable;
/**
* Provides a fluent API for generating ANSI escape sequences.
*
* See: https://en.wikipedia.org/wiki/ANSI_escape_code
*
* @author Dorkbox, LLC
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public class Ansi {
private static final String NEW_LINE = System.getProperty("line.separator");
private static final char FIRST_ESC_CHAR = 27;
private static final char SECOND_ESC_CHAR = '[';
private static int installed;
/**
* Override System.err and System.out with an ANSI capable {@link java.io.PrintStream}.
*/
public static synchronized
void systemInstall() {
installed++;
if (installed == 1) {
System.setOut(AnsiConsole.out);
System.setErr(AnsiConsole.err);
}
}
/**
* un-does a previous {@link #systemInstall()}.
*
* If {@link #systemInstall()} was called multiple times, then {@link #systemUninstall()} must be called the same number of
* times before it is uninstalled.
*/
public static synchronized
void systemUninstall() {
installed--;
if (installed == 0) {
if (AnsiConsole.out != AnsiConsole.system_out) {
AnsiConsole.out.close();
}
if (AnsiConsole.err != AnsiConsole.system_err) {
AnsiConsole.err.close();
}
System.setOut(AnsiConsole.system_out);
System.setErr(AnsiConsole.system_err);
}
}
public static final String DISABLE = Ansi.class.getName() + ".disable";
private static Callable<Boolean> detector = new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return !Boolean.getBoolean(DISABLE);
}
};
public static void setDetector(final Callable<Boolean> detector) {
if (detector == null) {
throw new IllegalArgumentException();
}
Ansi.detector = detector;
}
public static boolean isDetected() {
try {
return detector.call();
}
catch (Exception e) {
return true;
}
}
private static final InheritableThreadLocal<Boolean> holder = new InheritableThreadLocal<Boolean>()
{
@Override
protected Boolean initialValue() {
return isDetected();
}
};
public static void setEnabled(final boolean flag) {
holder.set(flag);
}
public static boolean isEnabled() {
return holder.get();
}
private static class NoAnsi extends Ansi
{
public
NoAnsi() {
super();
}
public
NoAnsi(final StringBuilder builder) {
super(builder);
}
public
NoAnsi(final int size) {
super(size);
}
@Override
public Ansi fg(Color color) {
return this;
}
@Override
public Ansi bg(Color color) {
return this;
}
@Override
public Ansi fgBright(Color color) {
return this;
}
@Override
public Ansi bgBright(Color color) {
return this;
}
@Override
public Ansi fgBrightDefault() { return this; }
@Override
public Ansi bgBrightDefault() { return this; }
@Override
public Ansi fgDefault() { return this; }
@Override
public Ansi bgDefault() { return this; }
@Override
public Ansi a(Attribute attribute) {
return this;
}
@Override
public Ansi cursor(int x, int y) {
return this;
}
@Override
public Ansi cursorToColumn(int x) {
return this;
}
@Override
public Ansi cursorUp(int y) {
return this;
}
@Override
public Ansi cursorRight(int x) {
return this;
}
@Override
public Ansi cursorDown(int y) {
return this;
}
@Override
public Ansi cursorLeft(int x) {
return this;
}
@Override
public Ansi cursorDownLine() {
return this;
}
@Override
public Ansi cursorDownLine(final int n) {
return this;
}
@Override
public Ansi cursorUpLine() {
return this;
}
@Override
public Ansi cursorUpLine(final int n) {
return this;
}
@Override
public Ansi eraseScreen() {
return this;
}
@Override
public Ansi eraseScreen(Erase kind) {
return this;
}
@Override
public Ansi eraseLine() {
return this;
}
@Override
public Ansi eraseLine(Erase kind) {
return this;
}
@Override
public Ansi scrollUp(int rows) {
return this;
}
@Override
public Ansi scrollDown(int rows) {
return this;
}
@Override
public Ansi saveCursorPosition() {
return this;
}
@Override
public Ansi restoreCursorPosition() {
return this;
}
@Override
public Ansi reset() {
return this;
}
}
/**
* Creates a new Ansi object and resets the output to the default.
*/
public static Ansi ansi() {
if (isEnabled()) {
return new Ansi();
}
else {
return new NoAnsi();
}
}
/**
* Creates a new Ansi object from the specified StringBuilder. This does NOT reset the output back to default.
*/
public static
Ansi ansi(StringBuilder builder) {
if (isEnabled()) {
return new Ansi(builder);
}
else {
return new NoAnsi(builder);
}
}
/**
* Creates a new Ansi object of the specified length and reset the output back to default.
*/
public static
Ansi ansi(int size) {
if (isEnabled()) {
return new Ansi(size);
}
else {
return new NoAnsi(size);
}
}
private final StringBuilder builder;
private final ArrayList<Integer> attributeOptions = new ArrayList<Integer>(5);
/**
* Creates a new Ansi object and resets the output to the default.
*/
public
Ansi() {
this(new StringBuilder());
reset(); // always reset a NEW Ansi object (w/ no parent)
}
/**
* Creates a new Ansi object from the parent. This does NOT reset the output back to default.
*/
public
Ansi(Ansi parent) {
this(new StringBuilder(parent.builder));
attributeOptions.addAll(parent.attributeOptions);
}
/**
* Creates a new Ansi object of the specified length and reset the output back to default.
*/
public
Ansi(int size) {
this(new StringBuilder(size));
reset(); // always reset a NEW Ansi object (w/ no parent)
}
/**
* Creates a new Ansi object from the specified StringBuilder. This does NOT reset the output back to default.
*/
public
Ansi(StringBuilder builder) {
this.builder = builder;
// don't know if there is a parent or not, so we don't reset()
}
public
Ansi fg(Color color) {
attributeOptions.add(color.fg());
return this;
}
public
Ansi fgDefault() {
attributeOptions.add(AnsiOutputStream.ATTRIBUTE_DEFAULT_FG);
return this;
}
public
Ansi fgBlack() {
return this.fg(Color.BLACK);
}
public
Ansi fgBlue() {
return this.fg(Color.BLUE);
}
public
Ansi fgCyan() {
return this.fg(Color.CYAN);
}
public
Ansi fgGreen() {
return this.fg(Color.GREEN);
}
public
Ansi fgMagenta() {
return this.fg(Color.MAGENTA);
}
public
Ansi fgRed() {
return this.fg(Color.RED);
}
public
Ansi fgYellow() {
return this.fg(Color.YELLOW);
}
public
Ansi fgWhite() {
return this.fg(Color.WHITE);
}
public
Ansi fgBright(Color color) {
attributeOptions.add(color.fgBright());
return this;
}
public
Ansi fgBrightDefault() {
attributeOptions.add(AnsiOutputStream.ATTRIBUTE_DEFAULT_FG);
attributeOptions.add(AnsiOutputStream.ATTRIBUTE_BOLD);
return this;
}
public
Ansi fgBrightBlack() {
return this.fgBright(Color.BLACK);
}
public
Ansi fgBrightBlue() {
return this.fgBright(Color.BLUE);
}
public
Ansi fgBrightCyan() {
return this.fgBright(Color.CYAN);
}
public
Ansi fgBrightGreen() {
return this.fgBright(Color.GREEN);
}
public
Ansi fgBrightMagenta() {
return this.fgBright(Color.MAGENTA);
}
public
Ansi fgBrightRed() {
return this.fgBright(Color.RED);
}
public
Ansi fgBrightYellow() {
return this.fgBright(Color.YELLOW);
}
public
Ansi fgBrightWhite() {
return this.fgBright(Color.WHITE);
}
public
Ansi bg(Color color) {
attributeOptions.add(color.bg());
return this;
}
public
Ansi bgDefault() {
attributeOptions.add(AnsiOutputStream.ATTRIBUTE_DEFAULT_BG);
return this;
}
public
Ansi bgBlack() {
return this.bg(Color.BLACK);
}
public
Ansi bgBlue() {
return this.bg(Color.BLUE);
}
public
Ansi bgCyan() {
return this.bg(Color.CYAN);
}
public
Ansi bgGreen() {
return this.bg(Color.GREEN);
}
public
Ansi bgMagenta() {
return this.bg(Color.MAGENTA);
}
public
Ansi bgRed() {
return this.bg(Color.RED);
}
public
Ansi bgYellow() {
return this.bg(Color.YELLOW);
}
public
Ansi bgWhite() {
return this.bg(Color.WHITE);
}
public
Ansi bgBright(Color color) {
attributeOptions.add(color.bgBright());
return this;
}
public
Ansi bgBrightDefault() {
attributeOptions.add(AnsiOutputStream.ATTRIBUTE_DEFAULT_BG);
attributeOptions.add(AnsiOutputStream.ATTRIBUTE_BOLD);
return this;
}
public
Ansi bgBrightBlack() {
return this.bgBright(Color.BLACK);
}
public
Ansi bgBrightBlue() {
return this.bgBright(Color.BLUE);
}
public
Ansi bgBrightCyan() {
return this.bgBright(Color.CYAN);
}
public
Ansi bgBrightGreen() {
return this.bgBright(Color.GREEN);
}
public
Ansi bgBrightMagenta() {
return this.bgBright(Color.MAGENTA);
}
public
Ansi bgBrightRed() {
return this.bgBright(Color.RED);
}
public
Ansi bgBrightYellow() {
return this.bgBright(Color.YELLOW);
}
public
Ansi bgBrightWhite() {
return this.bgBright(Color.WHITE);
}
public
Ansi a(Attribute attribute) {
attributeOptions.add(attribute.value());
return this;
}
/**
* @param x is 1 indexed (the very first value is 1, not 0)
* @param y is 1 indexed (the very first value is 1, not 0)
*/
public
Ansi cursor(final int x, final int y) {
return appendEscapeSequence(AnsiOutputStream.CURSOR_POS, x, y);
}
/**
* @param x is 1 indexed (the very first value is 1, not 0)
*/
public
Ansi cursorToColumn(final int x) {
return appendEscapeSequence(AnsiOutputStream.CURSOR_TO_COL, x);
}
public
Ansi cursorUp(final int y) {
return appendEscapeSequence(AnsiOutputStream.CURSOR_UP, y);
}
public
Ansi cursorDown(final int y) {
return appendEscapeSequence(AnsiOutputStream.CURSOR_DOWN, y);
}
public
Ansi cursorRight(final int x) {
return appendEscapeSequence(AnsiOutputStream.CURSOR_RIGHT, x);
}
public
Ansi cursorLeft(final int x) {
return appendEscapeSequence(AnsiOutputStream.CURSOR_LEFT, x);
}
public
Ansi cursorDownLine() {
return appendEscapeSequence(AnsiOutputStream.CURSOR_DOWN_LINE);
}
public
Ansi cursorDownLine(final int n) {
return appendEscapeSequence(AnsiOutputStream.CURSOR_DOWN_LINE, n);
}
public
Ansi cursorUpLine() {
return appendEscapeSequence(AnsiOutputStream.CURSOR_UP_LINE);
}
public
Ansi cursorUpLine(final int n) {
return appendEscapeSequence(AnsiOutputStream.CURSOR_UP_LINE, n);
}
public
Ansi eraseScreen() {
return appendEscapeSequence(AnsiOutputStream.CURSOR_ERASE_SCREEN, Erase.ALL.value());
}
public
Ansi eraseScreen(final Erase kind) {
return appendEscapeSequence(AnsiOutputStream.CURSOR_ERASE_SCREEN, kind.value());
}
public
Ansi eraseLine() {
return appendEscapeSequence(AnsiOutputStream.CURSOR_ERASE_LINE);
}
public
Ansi eraseLine(final Erase kind) {
return appendEscapeSequence(AnsiOutputStream.CURSOR_ERASE_LINE, kind.value());
}
public
Ansi scrollUp(final int rows) {
return appendEscapeSequence(AnsiOutputStream.PAGE_SCROLL_UP, rows);
}
public
Ansi scrollDown(final int rows) {
return appendEscapeSequence(AnsiOutputStream.PAGE_SCROLL_DOWN, rows);
}
public
Ansi saveCursorPosition() {
return appendEscapeSequence(AnsiOutputStream.SAVE_CURSOR_POS);
}
public
Ansi restoreCursorPosition() {
return appendEscapeSequence(AnsiOutputStream.RESTORE_CURSOR_POS);
}
public
Ansi reset() {
return a(Attribute.RESET);
}
public
Ansi bold() {
return a(Attribute.BOLD);
}
public
Ansi boldOff() {
return a(Attribute.BOLD_OFF);
}
public
Ansi faint() {
return a(Attribute.FAINT);
}
public
Ansi faintOff() {
return a(Attribute.FAINT_OFF);
}
public
Ansi italic() {
return a(Attribute.ITALIC);
}
public
Ansi italicOff() {
return a(Attribute.ITALIC_OFF);
}
public
Ansi underline() {
return a(Attribute.UNDERLINE);
}
public
Ansi underlineDouble() {
return a(Attribute.UNDERLINE_DOUBLE);
}
public
Ansi underlineOff() {
return a(Attribute.UNDERLINE_OFF);
}
public
Ansi blinkSlow() {
return a(Attribute.BLINK_SLOW);
}
public
Ansi blinkFast() {
return a(Attribute.BLINK_FAST);
}
public
Ansi blinkOff() {
return a(Attribute.BLINK_OFF);
}
public
Ansi negative() {
return a(Attribute.NEGATIVE);
}
public
Ansi negativeOff() {
return a(Attribute.NEGATIVE_OFF);
}
public
Ansi conceal() {
return a(Attribute.CONCEAL);
}
public
Ansi concealOff() {
return a(Attribute.CONCEAL_OFF);
}
public
Ansi strikethrough() {
return a(Attribute.STRIKETHROUGH);
}
public
Ansi strikethroughOff() {
return a(Attribute.STRIKETHROUGH_OFF);
}
public
Ansi a(final String value) {
flushAttributes();
builder.append(value);
return this;
}
public
Ansi a(final boolean value) {
flushAttributes();
builder.append(value);
return this;
}
public
Ansi a(final char value) {
flushAttributes();
builder.append(value);
return this;
}
public
Ansi a(final char[] value, final int offset, final int len) {
flushAttributes();
builder.append(value, offset, len);
return this;
}
public
Ansi a(final char[] value) {
flushAttributes();
builder.append(value);
return this;
}
public
Ansi a(final CharSequence value, final int start, final int end) {
flushAttributes();
builder.append(value, start, end);
return this;
}
public
Ansi a(final CharSequence value) {
flushAttributes();
builder.append(value);
return this;
}
public
Ansi a(final double value) {
flushAttributes();
builder.append(value);
return this;
}
public
Ansi a(final float value) {
flushAttributes();
builder.append(value);
return this;
}
public
Ansi a(final int value) {
flushAttributes();
builder.append(value);
return this;
}
public
Ansi a(final long value) {
flushAttributes();
builder.append(value);
return this;
}
public
Ansi a(final Object value) {
flushAttributes();
builder.append(value);
return this;
}
public
Ansi a(final StringBuilder value) {
flushAttributes();
builder.append(value);
return this;
}
public
Ansi a(final StringBuffer value) {
flushAttributes();
builder.append(value);
return this;
}
public
Ansi newline() {
flushAttributes();
builder.append(NEW_LINE);
return this;
}
public
Ansi format(final String pattern, final Object... args) {
flushAttributes();
builder.append(String.format(pattern, args));
return this;
}
/**
* Uses the {@link AnsiRenderer} to generate the ANSI escape sequences for the supplied text.
*/
public
Ansi render(final String text) {
a(AnsiRenderer.render(text));
return this;
}
/**
* String formats and renders the supplied arguments.
* Uses the {@link AnsiRenderer} to generate the ANSI escape sequences.
*/
public
Ansi render(final String text, final Object... args) {
a(String.format(AnsiRenderer.render(text), args));
return this;
}
@Override
public String toString() {
flushAttributes();
return builder.toString();
}
///////////////////////////////////////////////////////////////////
// Private Helper Methods
///////////////////////////////////////////////////////////////////
private
Ansi appendCommandSequence(final char command) {
flushAttributes();
builder.append(FIRST_ESC_CHAR);
builder.append(SECOND_ESC_CHAR);
builder.append(command);
return this;
}
private
Ansi appendEscapeSequence(final char command) {
flushAttributes();
builder.append(FIRST_ESC_CHAR);
builder.append(SECOND_ESC_CHAR);
builder.append(command);
return this;
}
private
Ansi appendEscapeSequence(final char command, final int option) {
flushAttributes();
builder.append(FIRST_ESC_CHAR);
builder.append(SECOND_ESC_CHAR);
builder.append(option);
builder.append(command);
return this;
}
private
Ansi appendEscapeSequence(final char command, final Object... options) {
flushAttributes();
return _appendEscapeSequence(command, options);
}
private
void flushAttributes() {
if( attributeOptions.isEmpty() ) {
return;
}
if (attributeOptions.size() == 1 && attributeOptions.get(0) == 0) {
builder.append(FIRST_ESC_CHAR);
builder.append(SECOND_ESC_CHAR);
builder.append(AnsiOutputStream.TEXT_ATTRIBUTE);
} else {
_appendEscapeSequence(AnsiOutputStream.TEXT_ATTRIBUTE, attributeOptions.toArray());
}
attributeOptions.clear();
}
private
Ansi _appendEscapeSequence(final char command, final Object... options) {
builder.append(FIRST_ESC_CHAR);
builder.append(SECOND_ESC_CHAR);
int size = options.length;
for (int i = 0; i < size; i++) {
if (i != 0) {
builder.append(';');
}
if (options[i] != null) {
builder.append(options[i]);
}
}
builder.append(command);
return this;
}
}

View File

@ -0,0 +1,42 @@
package dorkbox.console.output;
/**
*
*/
class AnsiCode {
Enum anEnum;
String formalName;
boolean isColorForBackground;
public
AnsiCode(final Enum anEnum, final String formalName, final boolean isColorForBackground) {
this.anEnum = anEnum;
this.formalName = formalName;
this.isColorForBackground = isColorForBackground;
}
public
boolean isColor() {
return anEnum instanceof Color;
}
public
boolean isBackgroundColor() {
return isColorForBackground;
}
public
Color getColor() {
return (Color) anEnum;
}
public
boolean isAttribute() {
return anEnum instanceof Attribute;
}
public
Attribute getAttribute() {
return (Attribute) anEnum;
}
}

View File

@ -0,0 +1,154 @@
/**
* Copyright (C) 2009, Progress Software Corporation and/or its
* subsidiaries or affiliates. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a asValue of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.console.output;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import dorkbox.console.util.posix.CLibraryPosix;
import dorkbox.console.util.windows.Kernel32;
/**
* Provides consistent access to an ANSI aware console PrintStream.
*
* See: https://en.wikipedia.org/wiki/ANSI_escape_code
*
* @author Dorkbox, LLC
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
@SuppressWarnings("SpellCheckingInspection")
class AnsiConsole {
static final int STDOUT_FILENO = 1;
static final int STDERR_FILENO = 2;
public static final PrintStream system_out = System.out;
public static final PrintStream out = new PrintStream(wrapOutputStream(system_out, STDOUT_FILENO));
public static final PrintStream system_err = System.err;
public static final PrintStream err = new PrintStream(wrapOutputStream(system_err, STDERR_FILENO));
private static boolean isXterm() {
String term = System.getenv("TERM");
return term != null && term.equals("xterm");
}
private static
OutputStream wrapOutputStream(final OutputStream stream, int fileno) {
// If the jansi.passthrough property is set, then don't interpret
// any of the ansi sequences.
if (Boolean.getBoolean("jansi.passthrough")) {
return stream;
}
// If the jansi.strip property is set, then we just strip the
// the ansi escapes.
if (Boolean.getBoolean("jansi.strip")) {
return new AnsiOutputStream(stream);
}
String os = System.getProperty("os.name");
if (os.startsWith("Windows") && !isXterm()) {
// check if windows10+ (which natively supports ANSI)
if (Kernel32.isWindows10OrGreater()) {
// Just wrap it up so that when we get closed, we reset the attributes.
return new FilterOutputStream(stream) {
@Override
public
void close() throws IOException {
write(AnsiOutputStream.RESET_CODE);
flush();
super.close();
}
};
}
// On windows we know the console does not interpret ANSI codes..
try {
return new WindowsAnsiOutputStream(stream, fileno);
} catch (Throwable ignore) {
ignore.printStackTrace();
// this happens when JNA is not in the path.. or
// this happens when the stdout is being redirected to a file.
// this happens when the stdout is being redirected to different console.
}
// Use the ANSIOutputStream to strip out the ANSI escape sequences.
return new AnsiOutputStream(stream);
}
// We must be on some unix variant..
try {
// If the jansi.force property is set, then we force to output
// the ansi escapes for piping it into ansi color aware commands (e.g. less -r)
boolean forceColored = Boolean.getBoolean("jansi.force");
// If we can detect that stdout is not a tty.. then setup to strip the ANSI sequences..
int rc = CLibraryPosix.isatty(fileno);
if (!isXterm() && !forceColored && rc == 0) {
return new AnsiOutputStream(stream);
}
// These errors happen if the JNI lib is not available for your platform.
} catch (NoClassDefFoundError ignore) {
} catch (UnsatisfiedLinkError ignore) {
}
// By default we assume your Unix tty can handle ANSI codes.
// Just wrap it up so that when we get closed, we reset the attributes.
return new FilterOutputStream(stream) {
@Override
public
void close() throws IOException {
write(AnsiOutputStream.RESET_CODE);
flush();
super.close();
}
};
}
/**
* If the standard out natively supports ANSI escape codes, then this just
* returns System.out, otherwise it will provide an ANSI aware PrintStream
* which strips out the ANSI escape sequences or which implement the escape
* sequences.
*
* @return a PrintStream which is ANSI aware.
*/
public static
PrintStream out() {
return out;
}
/**
* If the standard out natively supports ANSI escape codes, then this just
* returns System.err, otherwise it will provide an ANSI aware PrintStream
* which strips out the ANSI escape sequences or which implement the escape
* sequences.
*
* @return a PrintStream which is ANSI aware.
*/
public static
PrintStream err() {
return err;
}
}

View File

@ -0,0 +1,561 @@
/**
* Copyright (C) 2009, Progress Software Corporation and/or its
* subsidiaries or affiliates. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a asValue of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.console.output;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
/**
* A ANSI output stream extracts ANSI escape codes written to
* an output stream.
*
* For more information about ANSI escape codes, see:
* http://en.wikipedia.org/wiki/ANSI_escape_code
*
* This class just filters out the escape codes so that they are not
* sent out to the underlying OutputStream. Subclasses should
* actually perform the ANSI escape behaviors.
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
* @author Joris Kuipers
* @since 1.0
*/
@SuppressWarnings("NumericCastThatLosesPrecision")
public
class AnsiOutputStream extends FilterOutputStream {
private static final Charset CHARSET = Charset.forName("UTF-8");
static final int BLACK = 0;
static final int RED = 1;
static final int GREEN = 2;
static final int YELLOW = 3;
static final int BLUE = 4;
static final int MAGENTA = 5;
static final int CYAN = 6;
static final int WHITE = 7;
static final char CURSOR_UP = 'A';
static final char CURSOR_DOWN = 'B';
static final char CURSOR_RIGHT = 'C';
static final char CURSOR_LEFT = 'D';
static final char CURSOR_DOWN_LINE = 'E';
static final char CURSOR_UP_LINE = 'F';
static final char CURSOR_TO_COL = 'G';
static final char CURSOR_POS = 'H';
static final char CURSOR_POS_ALT = 'f';
static final char CURSOR_ERASE_SCREEN = 'J';
static final char CURSOR_ERASE_LINE = 'K';
static final char PAGE_SCROLL_UP = 'S';
static final char PAGE_SCROLL_DOWN = 'T';
static final char SAVE_CURSOR_POS = 's';
static final char RESTORE_CURSOR_POS = 'u';
static final char TEXT_ATTRIBUTE = 'm';
static final int ATTRIBUTE_RESET = 0; // Reset / Normal - all attributes off
static final int ATTRIBUTE_BOLD = 1; // Intensity: Bold
static final int ATTRIBUTE_FAINT = 2; // Intensity; Faint (not widely supported)
static final int ATTRIBUTE_ITALIC = 3; // Italic; (on not widely supported. Sometimes treated as inverse)
static final int ATTRIBUTE_UNDERLINE = 4; // Underline; Single
static final int ATTRIBUTE_BLINK_SLOW = 5; // Blink; Slow less than 150 per minute
static final int ATTRIBUTE_BLINK_FAST = 6; // Blink; Rapid 150 per minute or more
static final int ATTRIBUTE_NEGATIVE_ON = 7; // Negative inverse or reverse; swap foreground and background
static final int ATTRIBUTE_CONCEAL_ON = 8; // Conceal on
static final int ATTRIBUTE_STRIKETHROUGH_ON = 9; // Crossed-out
static final int ATTRIBUTE_UNDERLINE_DOUBLE = 21; // Underline; Double not widely supported
static final int ATTRIBUTE_NORMAL = 22; // Intensity; Normal not bold and not faint
static final int ATTRIBUTE_ITALIC_OFF = 23; // Not italic
static final int ATTRIBUTE_UNDERLINE_OFF = 24; // Underline; None
static final int ATTRIBUTE_BLINK_OFF = 25; // Blink; off
static final int ATTRIBUTE_NEGATIVE_OFF = 27; // Image; Positive
static final int ATTRIBUTE_CONCEAL_OFF = 28; // Reveal conceal off
static final int ATTRIBUTE_STRIKETHROUGH_OFF = 29; // Not crossed out
static final int ATTRIBUTE_DEFAULT_FG = 39; // Default text color (foreground)
static final int ATTRIBUTE_DEFAULT_BG = 49; // Default background color
// for Erase Screen/Line
static final int ERASE_TO_END = 0;
static final int ERASE_TO_BEGINNING = 1;
static final int ERASE_ALL = 2;
static final byte[] RESET_CODE = new Ansi().reset()
.toString()
.getBytes(CHARSET);
AnsiOutputStream(OutputStream os) {
super(os);
}
private final static int MAX_ESCAPE_SEQUENCE_LENGTH = 100;
private byte buffer[] = new byte[MAX_ESCAPE_SEQUENCE_LENGTH];
private int pos = 0;
private int startOfValue;
private final ArrayList<Object> options = new ArrayList<Object>();
private static final int LOOKING_FOR_FIRST_ESC_CHAR = 0;
private static final int LOOKING_FOR_SECOND_ESC_CHAR = 1;
private static final int LOOKING_FOR_NEXT_ARG = 2;
private static final int LOOKING_FOR_STR_ARG_END = 3;
private static final int LOOKING_FOR_INT_ARG_END = 4;
private static final int LOOKING_FOR_OSC_COMMAND = 5;
private static final int LOOKING_FOR_OSC_COMMAND_END = 6;
private static final int LOOKING_FOR_OSC_PARAM = 7;
private static final int LOOKING_FOR_ST = 8;
private int state = LOOKING_FOR_FIRST_ESC_CHAR;
private static final int FIRST_ESC_CHAR = 27;
private static final int SECOND_ESC_CHAR = '[';
private static final int SECOND_OSC_CHAR = ']';
private static final int BEL = 7;
private static final int SECOND_ST_CHAR = '\\';
// TODO: implement to get perf boost: public void write(byte[] b, int off, int len)
public
void write(int data) throws IOException {
switch (state) {
case LOOKING_FOR_FIRST_ESC_CHAR:
if (data == FIRST_ESC_CHAR) {
buffer[pos++] = (byte) data;
state = LOOKING_FOR_SECOND_ESC_CHAR;
}
else {
out.write(data);
}
break;
case LOOKING_FOR_SECOND_ESC_CHAR:
buffer[pos++] = (byte) data;
if (data == SECOND_ESC_CHAR) {
state = LOOKING_FOR_NEXT_ARG;
}
else if (data == SECOND_OSC_CHAR) {
state = LOOKING_FOR_OSC_COMMAND;
}
else {
reset(false);
}
break;
case LOOKING_FOR_NEXT_ARG:
buffer[pos++] = (byte) data;
if ('"' == data) {
startOfValue = pos - 1;
state = LOOKING_FOR_STR_ARG_END;
}
else if ('0' <= data && data <= '9') {
startOfValue = pos - 1;
state = LOOKING_FOR_INT_ARG_END;
}
else if (';' == data) {
options.add(null);
}
else if ('?' == data) {
options.add('?');
}
else if ('=' == data) {
options.add('=');
}
else {
reset(processEscapeCommand(options, data));
}
break;
case LOOKING_FOR_INT_ARG_END:
buffer[pos++] = (byte) data;
if (!('0' <= data && data <= '9')) {
String strValue = new String(buffer, startOfValue, (pos - 1) - startOfValue, CHARSET);
Integer value = Integer.valueOf(strValue);
options.add(value);
if (data == ';') {
state = LOOKING_FOR_NEXT_ARG;
}
else {
reset(processEscapeCommand(options, data));
}
}
break;
case LOOKING_FOR_STR_ARG_END:
buffer[pos++] = (byte) data;
if ('"' != data) {
String value = new String(buffer, startOfValue, (pos - 1) - startOfValue, CHARSET);
options.add(value);
if (data == ';') {
state = LOOKING_FOR_NEXT_ARG;
}
else {
reset(processEscapeCommand(options, data));
}
}
break;
case LOOKING_FOR_OSC_COMMAND:
buffer[pos++] = (byte) data;
if ('0' <= data && data <= '9') {
startOfValue = pos - 1;
state = LOOKING_FOR_OSC_COMMAND_END;
}
else {
reset(false);
}
break;
case LOOKING_FOR_OSC_COMMAND_END:
buffer[pos++] = (byte) data;
if (';' == data) {
String strValue = new String(buffer, startOfValue, (pos - 1) - startOfValue, CHARSET);
Integer value = Integer.valueOf(strValue);
options.add(value);
startOfValue = pos;
state = LOOKING_FOR_OSC_PARAM;
}
else if ('0' <= data && data <= '9') {
// already pushed digit to buffer, just keep looking
}
else {
// oops, did not expect this
reset(false);
}
break;
case LOOKING_FOR_OSC_PARAM:
buffer[pos++] = (byte) data;
if (BEL == data) {
String value = new String(buffer, startOfValue, (pos - 1) - startOfValue, CHARSET);
options.add(value);
reset(processOperatingSystemCommand(options));
}
else if (FIRST_ESC_CHAR == data) {
state = LOOKING_FOR_ST;
}
else {
// just keep looking while adding text
}
break;
case LOOKING_FOR_ST:
buffer[pos++] = (byte) data;
if (SECOND_ST_CHAR == data) {
String value = new String(buffer, startOfValue, (pos - 2) - startOfValue, CHARSET);
options.add(value);
reset(processOperatingSystemCommand(options));
}
else {
state = LOOKING_FOR_OSC_PARAM;
}
break;
}
// Is it just too long?
if (pos >= buffer.length) {
reset(false);
}
}
/**
* Resets all state to continue with regular parsing
* @param skipBuffer if current buffer should be skipped or written to out
* @throws IOException
*/
private
void reset(boolean skipBuffer) throws IOException {
if (!skipBuffer) {
out.write(buffer, 0, pos);
}
pos = 0;
startOfValue = 0;
options.clear();
state = LOOKING_FOR_FIRST_ESC_CHAR;
}
/**
* @return true if the escape command was processed.
*/
private
boolean processEscapeCommand(ArrayList<Object> options, int command) throws IOException {
try {
switch (command) {
case CURSOR_UP:
processCursorUp(optionInt(options, 0, 1));
return true;
case CURSOR_DOWN:
processCursorDown(optionInt(options, 0, 1));
return true;
case CURSOR_RIGHT:
processCursorRight(optionInt(options, 0, 1));
return true;
case CURSOR_LEFT:
processCursorLeft(optionInt(options, 0, 1));
return true;
case CURSOR_DOWN_LINE:
processCursorDownLine(optionInt(options, 0, 1));
return true;
case CURSOR_UP_LINE:
processCursorUpLine(optionInt(options, 0, 1));
return true;
case CURSOR_TO_COL:
processCursorToColumn(optionInt(options, 0));
return true;
case CURSOR_POS:
case CURSOR_POS_ALT:
processCursorTo(optionInt(options, 0, 1), optionInt(options, 1, 1));
return true;
case CURSOR_ERASE_SCREEN:
processEraseScreen(optionInt(options, 0, 0));
return true;
case CURSOR_ERASE_LINE:
processEraseLine(optionInt(options, 0, 0));
return true;
case PAGE_SCROLL_UP:
processScrollUp(optionInt(options, 0, 1));
return true;
case PAGE_SCROLL_DOWN:
processScrollDown(optionInt(options, 0, 1));
return true;
case TEXT_ATTRIBUTE:
int count = 0;
for (Object next : options) {
if (next != null) {
count++;
// will throw a ClassCast exception IF NOT an int.
int value = (Integer) next;
if (30 <= value && value <= 37) {
// foreground
processSetForegroundColor(value - 30);
}
else if (40 <= value && value <= 47) {
// background
processSetBackgroundColor(value - 40);
}
else {
switch (value) {
case ATTRIBUTE_DEFAULT_FG:
processDefaultTextColor();
break;
case ATTRIBUTE_DEFAULT_BG:
processDefaultBackgroundColor();
break;
case ATTRIBUTE_RESET:
processAttributeReset();
break;
default:
processSetAttribute(value);
}
}
}
}
if (count == 0) {
processAttributeReset();
}
return true;
case SAVE_CURSOR_POS:
processSaveCursorPosition();
return true;
case RESTORE_CURSOR_POS:
processRestoreCursorPosition();
return true;
default:
if ('a' <= command && command <= 'z') {
processUnknownExtension(options, command);
return true;
}
if ('A' <= command && command <= 'Z') {
processUnknownExtension(options, command);
return true;
}
return false;
}
} catch (IllegalArgumentException ignore) {
}
return false;
}
/**
* @return true if the operating system command was processed.
*/
private
boolean processOperatingSystemCommand(final ArrayList<Object> options) throws IOException {
final int command = optionInt(options, 0);
final String label = (String) options.get(1);
// for command > 2 label could be composed (i.e. contain ';'), but we'll leave
// it to processUnknownOperatingSystemCommand implementations to handle that
try {
switch (command) {
default:
// not exactly unknown, but not supported through dedicated process methods
processUnknownOperatingSystemCommand(command, label);
return true;
}
} catch (IllegalArgumentException ignore) {
}
return false;
}
protected
void processRestoreCursorPosition() throws IOException {
}
protected
void processSaveCursorPosition() throws IOException {
}
protected
void processScrollDown(int optionInt) throws IOException {
}
protected
void processScrollUp(int optionInt) throws IOException {
}
protected
void processEraseScreen(int eraseOption) throws IOException {
}
protected
void processEraseLine(int eraseOption) throws IOException {
}
protected
void processSetAttribute(int attribute) throws IOException {
}
protected
void processSetForegroundColor(int color) throws IOException {
}
protected
void processSetBackgroundColor(int color) throws IOException {
}
protected
void processDefaultTextColor() throws IOException {
}
protected
void processDefaultBackgroundColor() throws IOException {
}
protected
void processAttributeReset() throws IOException {
}
protected
void processCursorTo(int row, int col) throws IOException {
}
protected
void processCursorToColumn(int x) throws IOException {
}
protected
void processCursorUpLine(int count) throws IOException {
}
protected
void processCursorDownLine(int count) throws IOException {
}
protected
void processCursorLeft(int count) throws IOException {
}
protected
void processCursorRight(int count) throws IOException {
}
protected
void processCursorDown(int count) throws IOException {
}
protected
void processCursorUp(int count) throws IOException {
}
protected
void processUnknownExtension(ArrayList<Object> options, int command) {
}
protected
void processUnknownOperatingSystemCommand(int command, String param) {
}
private
int optionInt(final ArrayList<Object> options, final int index) {
if (options.size() <= index) {
throw new IllegalArgumentException();
}
Object value = options.get(index);
if (value == null) {
throw new IllegalArgumentException();
}
if (!value.getClass()
.equals(Integer.class)) {
throw new IllegalArgumentException();
}
return (Integer) value;
}
private
int optionInt(final ArrayList<Object> options, final int index, final int defaultValue) {
if (options.size() > index) {
Object value = options.get(index);
if (value == null) {
return defaultValue;
}
return (Integer) value;
}
return defaultValue;
}
@Override
public
void close() throws IOException {
write(RESET_CODE);
flush();
super.close();
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (C) 2009 the original author(s).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.console.output;
import static dorkbox.console.output.AnsiRenderer.render;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.Locale;
/**
* Print writer which supports automatic ANSI color rendering via {@link AnsiRenderer}.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
* @since 1.1
*/
public
class AnsiRenderWriter extends PrintWriter {
public
AnsiRenderWriter(final OutputStream out) {
super(out);
}
public
AnsiRenderWriter(final OutputStream out, final boolean autoFlush) {
super(out, autoFlush);
}
public
AnsiRenderWriter(final Writer out) {
super(out);
}
public
AnsiRenderWriter(final Writer out, final boolean autoFlush) {
super(out, autoFlush);
}
@Override
public
void write(final String s) {
if (s != null && s.contains(AnsiRenderer.BEGIN_TOKEN)) {
super.write(render(s));
}
else {
super.write(s);
}
}
//
// Need to prevent partial output from being written while formatting or we will get rendering exceptions
//
@Override
public
PrintWriter format(final String format, final Object... args) {
print(String.format(format, args));
return this;
}
@Override
public
PrintWriter format(final Locale l, final String format, final Object... args) {
print(String.format(l, format, args));
return this;
}
}

View File

@ -0,0 +1,180 @@
/*
* Copyright (C) 2009 the original author(s).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.console.output;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* Renders ANSI color escape-codes in strings by parsing out some special syntax to pick up the correct fluff to use.
* <p>
* <p/>
* The syntax for embedded ANSI codes is:
* <p>
* <pre>
* <tt>@|</tt><em>code</em>(<tt>,</tt><em>code</em>)* <em>text</em><tt>|@</tt>
* </pre>
* <p>
* Examples:
* <p>
* <pre>
* <tt>@|bold Hello|@</tt>
* </pre>
* <p>
* <pre>
* <tt>@|bold,red Warning!|@</tt>
* </pre>
*
* @author dorkbox, llc
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
@SuppressWarnings("WeakerAccess")
public
class AnsiRenderer {
public static final String BEGIN_TOKEN = "@|";
public static final String CODE_LIST_SEPARATOR = ",";
public static final String CODE_TEXT_SEPARATOR = " ";
public static final String END_TOKEN = "|@";
private static final int BEGIN_TOKEN_LEN = 2;
private static final int END_TOKEN_LEN = 2;
private static Map<String, AnsiCode> codeMap = new HashMap<String, AnsiCode>(32);
static
void reg(Enum anEnum, String codeName) {
reg(anEnum, codeName, false);
}
static
void reg(Enum anEnum, String codeName, boolean isBackgroundColor) {
codeMap.put(codeName, new AnsiCode(anEnum, codeName, isBackgroundColor));
}
/**
* Renders {@link AnsiCode} names on the given Ansi.
*
* @param ansi The Ansi to render upon
* @param codeNames The code names to render
*/
public static
Ansi render(Ansi ansi, final String... codeNames) {
for (String codeName : codeNames) {
render(ansi, codeName);
}
return ansi;
}
/**
* Renders a {@link AnsiCode} name on the given Ansi.
*
* @param ansi The Ansi to render upon
* @param codeName The code name to render
*/
public static
Ansi render(Ansi ansi, String codeName) {
AnsiCode ansiCode = codeMap.get(codeName.toUpperCase(Locale.ENGLISH));
assert ansiCode != null : "Invalid ANSI code name: '" + codeName + "'";
if (ansiCode.isColor()) {
if (ansiCode.isBackgroundColor()) {
ansi = ansi.bg(ansiCode.getColor());
}
else {
ansi = ansi.fg(ansiCode.getColor());
}
}
else if (ansiCode.isAttribute()) {
ansi = ansi.a(ansiCode.getAttribute());
} else {
assert false : "Undetermined ANSI code name: '" + codeName + "'";
}
return ansi;
}
/**
* Renders text using the {@link AnsiCode} names.
*
* @param text The text to render
* @param codeNames The code names to render
*/
public static
String render(final String text, final String... codeNames) {
Ansi ansi = render(Ansi.ansi(), codeNames);
return ansi.a(text)
.reset()
.toString();
}
public static
String render(final String input) throws IllegalArgumentException {
StringBuilder buff = new StringBuilder();
int i = 0;
int j, k;
while (true) {
j = input.indexOf(BEGIN_TOKEN, i);
if (j == -1) {
if (i == 0) {
return input;
}
else {
buff.append(input.substring(i, input.length()));
return buff.toString();
}
}
else {
buff.append(input.substring(i, j));
k = input.indexOf(END_TOKEN, j);
if (k == -1) {
return input;
}
else {
j += BEGIN_TOKEN_LEN;
String spec = input.substring(j, k);
String[] items = spec.split(CODE_TEXT_SEPARATOR, 2);
if (items.length == 1) {
return input;
}
String replacement = render(items[1], items[0].split(CODE_LIST_SEPARATOR));
buff.append(replacement);
i = k + END_TOKEN_LEN;
}
}
}
}
/**
* Renders {@link AnsiCode} names as an ANSI escape string.
*
* @param codeNames The code names to render
*
* @return an ANSI escape string.
*/
public static
String renderCodeNames(final String codeNames) {
return render(new Ansi(), codeNames.split("\\s")).toString();
}
}

View File

@ -0,0 +1,104 @@
/*
* Copyright (C) 2009 the original author(s).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.console.output;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* An ANSI string which reports the size of rendered text correctly (ignoring any ANSI escapes).
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 1.1
*/
public
class AnsiString implements CharSequence {
private final CharSequence encoded;
private final CharSequence plain;
public
AnsiString(final CharSequence str) {
assert str != null;
this.encoded = str;
this.plain = chew(str);
}
private
CharSequence chew(final CharSequence str) {
assert str != null;
ByteArrayOutputStream buff = new ByteArrayOutputStream();
AnsiOutputStream out = new AnsiOutputStream(buff);
try {
out.write(str.toString()
.getBytes());
out.flush();
out.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
return new String(buff.toByteArray());
}
public
CharSequence getEncoded() {
return encoded;
}
public
CharSequence getPlain() {
return plain;
}
// FIXME: charAt() and subSequence() will make things barf, need to call toString() first to get expected results
public
int length() {
return getPlain().length();
}
public
char charAt(final int index) {
return getEncoded().charAt(index);
}
public
CharSequence subSequence(final int start, final int end) {
return getEncoded().subSequence(start, end);
}
@Override
public
int hashCode() {
return getEncoded().hashCode();
}
@Override
public
boolean equals(final Object obj) {
return getEncoded().equals(obj);
}
@Override
public
String toString() {
return getEncoded().toString();
}
}

View File

@ -0,0 +1,74 @@
package dorkbox.console.output;
import static dorkbox.console.output.AnsiOutputStream.ATTRIBUTE_BLINK_FAST;
import static dorkbox.console.output.AnsiOutputStream.ATTRIBUTE_BLINK_OFF;
import static dorkbox.console.output.AnsiOutputStream.ATTRIBUTE_BLINK_SLOW;
import static dorkbox.console.output.AnsiOutputStream.ATTRIBUTE_BOLD;
import static dorkbox.console.output.AnsiOutputStream.ATTRIBUTE_CONCEAL_OFF;
import static dorkbox.console.output.AnsiOutputStream.ATTRIBUTE_CONCEAL_ON;
import static dorkbox.console.output.AnsiOutputStream.ATTRIBUTE_FAINT;
import static dorkbox.console.output.AnsiOutputStream.ATTRIBUTE_ITALIC;
import static dorkbox.console.output.AnsiOutputStream.ATTRIBUTE_ITALIC_OFF;
import static dorkbox.console.output.AnsiOutputStream.ATTRIBUTE_NEGATIVE_OFF;
import static dorkbox.console.output.AnsiOutputStream.ATTRIBUTE_NEGATIVE_ON;
import static dorkbox.console.output.AnsiOutputStream.ATTRIBUTE_NORMAL;
import static dorkbox.console.output.AnsiOutputStream.ATTRIBUTE_RESET;
import static dorkbox.console.output.AnsiOutputStream.ATTRIBUTE_STRIKETHROUGH_OFF;
import static dorkbox.console.output.AnsiOutputStream.ATTRIBUTE_STRIKETHROUGH_ON;
import static dorkbox.console.output.AnsiOutputStream.ATTRIBUTE_UNDERLINE;
import static dorkbox.console.output.AnsiOutputStream.ATTRIBUTE_UNDERLINE_DOUBLE;
import static dorkbox.console.output.AnsiOutputStream.ATTRIBUTE_UNDERLINE_OFF;
public
enum Attribute {
RESET(ATTRIBUTE_RESET, "RESET"),
BOLD(ATTRIBUTE_BOLD, "BOLD"),
BOLD_OFF(ATTRIBUTE_NORMAL, "BOLD_OFF"),
FAINT(ATTRIBUTE_FAINT, "FAINT"),
FAINT_OFF(ATTRIBUTE_NORMAL, "FAINT_OFF"),
ITALIC(ATTRIBUTE_ITALIC, "ITALIC"),
ITALIC_OFF(ATTRIBUTE_ITALIC_OFF, "ITALIC_OFF"),
UNDERLINE(ATTRIBUTE_UNDERLINE, "UNDERLINE"),
UNDERLINE_DOUBLE(ATTRIBUTE_UNDERLINE_DOUBLE, "UNDERLINE_DOUBLE"),
UNDERLINE_OFF(ATTRIBUTE_UNDERLINE_OFF, "UNDERLINE_OFF"),
BLINK_SLOW(ATTRIBUTE_BLINK_SLOW, "BLINK_SLOW"),
BLINK_FAST(ATTRIBUTE_BLINK_FAST, "BLINK_FAST"),
BLINK_OFF(ATTRIBUTE_BLINK_OFF, "BLINK_OFF"),
NEGATIVE(ATTRIBUTE_NEGATIVE_ON, "NEGATIVE"),
NEGATIVE_OFF(ATTRIBUTE_NEGATIVE_OFF, "NEGATIVE_OFF"),
CONCEAL(ATTRIBUTE_CONCEAL_ON, "CONCEAL"),
CONCEAL_OFF(ATTRIBUTE_CONCEAL_OFF, "CONCEAL_OFF"),
STRIKETHROUGH(ATTRIBUTE_STRIKETHROUGH_ON, "STRIKETHROUGH"),
STRIKETHROUGH_OFF(ATTRIBUTE_STRIKETHROUGH_OFF, "STRIKETHROUGH_OFF"),
;
private final int value;
private final String name;
Attribute(final int index, final String name) {
this.value = index;
this.name = name;
// register code names with the ANSI renderer
AnsiRenderer.reg(this, name);
}
@Override
public
String toString() {
return name;
}
public
int value() {
return value;
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.console.output;
public
enum Color {
BLACK (AnsiOutputStream.BLACK, "BLACK"),
RED (AnsiOutputStream.RED, "RED"),
GREEN (AnsiOutputStream.GREEN, "GREEN"),
YELLOW (AnsiOutputStream.YELLOW, "YELLOW"),
BLUE (AnsiOutputStream.BLUE, "BLUE"),
MAGENTA(AnsiOutputStream.MAGENTA, "MAGENTA"),
CYAN (AnsiOutputStream.CYAN, "CYAN"),
WHITE (AnsiOutputStream.WHITE, "WHITE");
private final int value;
private final String name;
Color(int index, String name) {
this.value = index;
this.name = name;
// register code names with the ANSI renderer
AnsiRenderer.reg(this, name, false);
AnsiRenderer.reg(this, "FG_" + name, false);
AnsiRenderer.reg(this, "BG_" + name, true);
}
@Override
public
String toString() {
return name;
}
public
int fg() {
return value + 30;
}
public
int bg() {
return value + 40;
}
public
int fgBright() {
return value + 90;
}
public
int bgBright() {
return value + 100;
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.console.output;
import static dorkbox.console.output.AnsiOutputStream.ERASE_ALL;
import static dorkbox.console.output.AnsiOutputStream.ERASE_TO_BEGINNING;
import static dorkbox.console.output.AnsiOutputStream.ERASE_TO_END;
public
enum Erase {
FORWARD(ERASE_TO_END, "FORWARD"),
BACKWARD(ERASE_TO_BEGINNING, "BACKWARD"),
ALL(ERASE_ALL, "ALL");
private final int value;
private final String name;
Erase(int index, String name) {
this.value = index;
this.name = name;
}
@Override
public
String toString() {
return name;
}
public
int value() {
return value;
}
}

View File

@ -0,0 +1,158 @@
/**
* Copyright (C) 2009, Progress Software Corporation and/or its
* subsidiaries or affiliates. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a asValue of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.console.output;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
/**
* @author <a href="http://code.dblock.org">Daniel Doubrovkine</a>
*/
public
class HtmlAnsiOutputStream extends AnsiOutputStream {
private static final String ANSI_COLOR_MAP[];
static {
ANSI_COLOR_MAP = new String[8];
ANSI_COLOR_MAP[BLACK] = "black";
ANSI_COLOR_MAP[RED] = "red";
ANSI_COLOR_MAP[GREEN] = "green";
ANSI_COLOR_MAP[YELLOW] = "yellow";
ANSI_COLOR_MAP[BLUE] = "blue";
ANSI_COLOR_MAP[MAGENTA] = "magenta";
ANSI_COLOR_MAP[CYAN] = "cyan";
ANSI_COLOR_MAP[WHITE] = "white";
}
private static final byte[] BYTES_QUOT = "&quot;".getBytes();
private static final byte[] BYTES_AMP = "&amp;".getBytes();
private static final byte[] BYTES_LT = "&lt;".getBytes();
private static final byte[] BYTES_GT = "&gt;".getBytes();
private boolean concealOn = false;
private List<String> closingAttributes = new ArrayList<String>();
public
HtmlAnsiOutputStream(OutputStream os) {
super(os);
}
private
void write(String s) throws IOException {
super.out.write(s.getBytes());
}
private
void writeAttribute(String s) throws IOException {
write("<" + s + ">");
closingAttributes.add(0, s.split(" ", 2)[0]);
}
private
void closeAttributes() throws IOException {
for (String attr : closingAttributes) {
write("</" + attr + ">");
}
closingAttributes.clear();
}
public
void write(int data) throws IOException {
switch (data) {
case 34: // "
out.write(BYTES_QUOT);
break;
case 38: // &
out.write(BYTES_AMP);
break;
case 60: // <
out.write(BYTES_LT);
break;
case 62: // >
out.write(BYTES_GT);
break;
default:
super.write(data);
}
}
@Override
protected
void processSetAttribute(int attribute) throws IOException {
switch (attribute) {
case ATTRIBUTE_CONCEAL_ON:
write("\u001B[8m");
concealOn = true;
break;
case ATTRIBUTE_BOLD:
writeAttribute("b");
break;
case ATTRIBUTE_NORMAL:
closeAttributes();
break;
case ATTRIBUTE_UNDERLINE:
writeAttribute("u");
break;
case ATTRIBUTE_UNDERLINE_OFF:
closeAttributes();
break;
case ATTRIBUTE_NEGATIVE_ON:
break;
case ATTRIBUTE_NEGATIVE_OFF:
break;
}
}
@Override
protected
void processSetForegroundColor(final int color) throws IOException {
writeAttribute("span style=\"color: " + ANSI_COLOR_MAP[color] + ";\"");
}
@Override
protected
void processSetBackgroundColor(final int color) throws IOException {
writeAttribute("span style=\"background-color: " + ANSI_COLOR_MAP[color] + ";\"");
}
@Override
protected
void processAttributeReset() throws IOException {
if (concealOn) {
write("\u001B[0m");
concealOn = false;
}
closeAttributes();
}
@Override
public
void close() throws IOException {
closeAttributes();
super.close();
}
public
void writeLine(final byte[] buf, final int offset, final int len) throws IOException {
write(buf, offset, len);
closeAttributes();
}
}

View File

@ -0,0 +1,375 @@
/*
* Copyright (C) 2009, Progress Software Corporation and/or its
* subsidiaries or affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a asValue of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.console.output;
import static dorkbox.console.util.windows.Kernel32.ASSERT;
import java.io.IOException;
import java.io.OutputStream;
import dorkbox.console.util.windows.CONSOLE_SCREEN_BUFFER_INFO;
import dorkbox.console.util.windows.COORD;
import dorkbox.console.util.windows.HANDLE;
import dorkbox.console.util.windows.Kernel32;
/**
* A Windows ANSI escape processor, uses JNA direct-mapping to access native platform API's to change the console attributes.
*
* See: https://en.wikipedia.org/wiki/ANSI_escape_code
*
* @author dorkbox, llc
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
* @author Joris Kuipers
*/
@SuppressWarnings("NumericCastThatLosesPrecision")
final class WindowsAnsiOutputStream extends AnsiOutputStream {
private static final short ANSI_FOREGROUND_COLOR_MAP[];
private static final short ANSI_BACKGROUND_COLOR_MAP[];
static {
ANSI_FOREGROUND_COLOR_MAP = new short[8];
ANSI_FOREGROUND_COLOR_MAP[BLACK] = Kernel32.FOREGROUND_BLACK;
ANSI_FOREGROUND_COLOR_MAP[RED] = Kernel32.FOREGROUND_RED;
ANSI_FOREGROUND_COLOR_MAP[GREEN] = Kernel32.FOREGROUND_GREEN;
ANSI_FOREGROUND_COLOR_MAP[YELLOW] = Kernel32.FOREGROUND_YELLOW;
ANSI_FOREGROUND_COLOR_MAP[BLUE] = Kernel32.FOREGROUND_BLUE;
ANSI_FOREGROUND_COLOR_MAP[MAGENTA] = Kernel32.FOREGROUND_MAGENTA;
ANSI_FOREGROUND_COLOR_MAP[CYAN] = Kernel32.FOREGROUND_CYAN;
ANSI_FOREGROUND_COLOR_MAP[WHITE] = Kernel32.FOREGROUND_GREY;
ANSI_BACKGROUND_COLOR_MAP = new short[8];
ANSI_BACKGROUND_COLOR_MAP[BLACK] = Kernel32.BACKGROUND_BLACK;
ANSI_BACKGROUND_COLOR_MAP[RED] = Kernel32.BACKGROUND_RED;
ANSI_BACKGROUND_COLOR_MAP[GREEN] = Kernel32.BACKGROUND_GREEN;
ANSI_BACKGROUND_COLOR_MAP[YELLOW] = Kernel32.BACKGROUND_YELLOW;
ANSI_BACKGROUND_COLOR_MAP[BLUE] = Kernel32.BACKGROUND_BLUE;
ANSI_BACKGROUND_COLOR_MAP[MAGENTA] = Kernel32.BACKGROUND_MAGENTA;
ANSI_BACKGROUND_COLOR_MAP[CYAN] = Kernel32.BACKGROUND_CYAN;
ANSI_BACKGROUND_COLOR_MAP[WHITE] = Kernel32.BACKGROUND_GREY;
}
private final HANDLE console;
private final CONSOLE_SCREEN_BUFFER_INFO originalInfo = new CONSOLE_SCREEN_BUFFER_INFO();
private volatile CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO();
private volatile boolean negative;
private volatile short savedX = (short) -1;
private volatile short savedY = (short) -1;
WindowsAnsiOutputStream(final OutputStream os, int fileHandle) throws IOException {
super(os);
if (fileHandle == AnsiConsole.STDOUT_FILENO) {
fileHandle = Kernel32.STD_OUTPUT_HANDLE;
} else if (fileHandle == AnsiConsole.STDERR_FILENO) {
fileHandle = Kernel32.STD_ERROR_HANDLE;
} else {
throw new IllegalArgumentException("Invalid file handle " + fileHandle);
}
console = Kernel32.GetStdHandle(fileHandle);
if (console == HANDLE.INVALID_HANDLE_VALUE) {
throw new IOException("Unable to get input console handle.");
}
out.flush();
ASSERT(Kernel32.GetConsoleScreenBufferInfo(console, originalInfo), "Could not get the screen info");
}
private
void getConsoleInfo() throws IOException {
out.flush();
ASSERT(Kernel32.GetConsoleScreenBufferInfo(console, info), "Could not get the screen info:");
}
private
void applyAttributes() throws IOException {
out.flush();
short attributes = info.attributes;
if (negative) {
// Swap the the Foreground and Background bits.
int fg = 0x000F & attributes;
fg <<= 8;
int bg = 0X00F0 * attributes;
bg >>= 8;
attributes = (short) (attributes & 0xFF00 | fg | bg);
}
ASSERT(Kernel32.SetConsoleTextAttribute(console, attributes), "Could not set text attributes");
}
private
void applyCursorPosition() throws IOException {
ASSERT(Kernel32.SetConsoleCursorPosition(console, info.cursorPosition.asValue()), "Could not set cursor position");
}
@Override
protected
void processRestoreCursorPosition() throws IOException {
// restore only if there was a save operation first
if (savedX != -1 && savedY != -1) {
out.flush();
info.cursorPosition.x = savedX;
info.cursorPosition.y = savedY;
applyCursorPosition();
}
}
@Override
protected
void processSaveCursorPosition() throws IOException {
getConsoleInfo();
savedX = info.cursorPosition.x;
savedY = info.cursorPosition.y;
}
@Override
protected
void processEraseScreen(final int eraseOption) throws IOException {
getConsoleInfo();
int[] written = new int[1];
switch (eraseOption) {
case ERASE_ALL:
COORD topLeft = new COORD();
topLeft.x = (short) 0;
topLeft.y = info.window.top;
int screenLength = info.window.height() * info.size.x;
ASSERT(Kernel32.FillConsoleOutputAttribute(console, originalInfo.attributes, screenLength, topLeft.asValue(), written), "Could not fill console");
ASSERT(Kernel32.FillConsoleOutputCharacterW(console, ' ', screenLength, topLeft.asValue(), written), "Could not fill console");
break;
case ERASE_TO_BEGINNING:
COORD topLeft2 = new COORD();
topLeft2.x = (short) 0;
topLeft2.y = info.window.top;
int lengthToCursor = (info.cursorPosition.y - info.window.top) * info.size.x + info.cursorPosition.x;
ASSERT(Kernel32.FillConsoleOutputAttribute(console, originalInfo.attributes, lengthToCursor, topLeft2.asValue(), written), "Could not fill console");
ASSERT(Kernel32.FillConsoleOutputCharacterW(console, ' ', lengthToCursor, topLeft2.asValue(), written), "Could not fill console");
break;
case ERASE_TO_END:
int lengthToEnd = (info.window.bottom - info.cursorPosition.y) * info.size.x + info.size.x - info.cursorPosition.x;
ASSERT(Kernel32.FillConsoleOutputAttribute(console, originalInfo.attributes, lengthToEnd, info.cursorPosition.asValue(), written), "Could not fill console");
ASSERT(Kernel32.FillConsoleOutputCharacterW(console, ' ', lengthToEnd, info.cursorPosition.asValue(), written), "Could not fill console");
}
}
@Override
protected
void processEraseLine(final int eraseOption) throws IOException {
getConsoleInfo();
int[] written = new int[1];
switch (eraseOption) {
case ERASE_ALL:
COORD currentRow = info.cursorPosition.asValue();
currentRow.x = (short) 0;
ASSERT(Kernel32.FillConsoleOutputAttribute(console, originalInfo.attributes, info.size.x, currentRow.asValue(), written), "Could not fill console");
ASSERT(Kernel32.FillConsoleOutputCharacterW(console, ' ', info.size.x, currentRow.asValue(), written), "Could not fill console");
break;
case ERASE_TO_BEGINNING:
COORD leftColCurrRow2 = info.cursorPosition.asValue();
leftColCurrRow2.x = (short) 0;
ASSERT(Kernel32.FillConsoleOutputAttribute(console, originalInfo.attributes, info.cursorPosition.x, leftColCurrRow2.asValue(), written), "Could not fill console");
ASSERT(Kernel32.FillConsoleOutputCharacterW(console, ' ', info.cursorPosition.x, leftColCurrRow2.asValue(), written), "Could not fill console");
break;
case ERASE_TO_END:
int lengthToLastCol = info.size.x - info.cursorPosition.x;
ASSERT(Kernel32.FillConsoleOutputAttribute(console, originalInfo.attributes, lengthToLastCol, info.cursorPosition.asValue(), written), "Could not fill console");
ASSERT(Kernel32.FillConsoleOutputCharacterW(console, ' ', lengthToLastCol, info.cursorPosition.asValue(), written), "Could not fill console");
}
}
@Override
protected
void processSetAttribute(final int attribute) throws IOException {
if (90 <= attribute && attribute <= 97) {
// foreground bright
info.attributes = (short) (info.attributes & ~0x000F | ANSI_FOREGROUND_COLOR_MAP[attribute - 90]);
info.attributes = (short) (info.attributes | Kernel32.FOREGROUND_INTENSITY);
applyAttributes();
return;
} else if (100 <= attribute && attribute <= 107) {
// background bright
info.attributes = (short) (info.attributes & ~0x00F0 | ANSI_BACKGROUND_COLOR_MAP[attribute - 100]);
info.attributes = (short) (info.attributes | Kernel32.BACKGROUND_INTENSITY);
applyAttributes();
return;
}
switch (attribute) {
case ATTRIBUTE_BOLD:
info.attributes = (short) (info.attributes | Kernel32.FOREGROUND_INTENSITY);
applyAttributes();
break;
case ATTRIBUTE_NORMAL:
info.attributes = (short) (info.attributes & ~Kernel32.FOREGROUND_INTENSITY);
applyAttributes();
break;
// Yeah, setting the background intensity is not underlining.. but it's best we can do using the Windows console API
case ATTRIBUTE_UNDERLINE:
info.attributes = (short) (info.attributes | Kernel32.BACKGROUND_INTENSITY);
applyAttributes();
break;
case ATTRIBUTE_UNDERLINE_OFF:
info.attributes = (short) (info.attributes & ~Kernel32.BACKGROUND_INTENSITY);
applyAttributes();
break;
case ATTRIBUTE_NEGATIVE_ON:
negative = true;
applyAttributes();
break;
case ATTRIBUTE_NEGATIVE_OFF:
negative = false;
applyAttributes();
break;
}
}
@Override
protected
void processSetForegroundColor(final int color) throws IOException {
info.attributes = (short) (info.attributes & ~0x000F | ANSI_FOREGROUND_COLOR_MAP[color]);
applyAttributes();
}
@Override
protected
void processSetBackgroundColor(final int color) throws IOException {
info.attributes = (short) (info.attributes & ~0x00F0 | ANSI_BACKGROUND_COLOR_MAP[color]);
applyAttributes();
}
@Override
protected
void processDefaultTextColor() throws IOException {
info.attributes = (short) (info.attributes & ~0x000F | originalInfo.attributes & 0x000F);
applyAttributes();
}
@Override
protected
void processDefaultBackgroundColor() throws IOException {
info.attributes = (short) (info.attributes & ~0x00F0 | originalInfo.attributes & 0x00F0);
applyAttributes();
}
@Override
protected
void processAttributeReset() throws IOException {
//info.attributes = originalInfo.attributes;
info.attributes = (short)((info.attributes & ~0x00FF ) | originalInfo.attributes);
this.negative = false;
applyAttributes();
}
@Override
protected
void processScrollDown(final int optionInt) throws IOException {
}
@Override
protected
void processScrollUp(final int optionInt) throws IOException {
}
protected
void processCursorUpLine(final int count) throws IOException {
}
protected
void processCursorDownLine(final int count) throws IOException {
}
@Override
protected
void processCursorTo(final int row, final int col) throws IOException {
getConsoleInfo();
info.cursorPosition.y = (short) Math.max(info.window.top, Math.min(info.size.y, info.window.top + row - 1));
info.cursorPosition.x = (short) Math.max(0, Math.min(info.window.width(), col - 1));
applyCursorPosition();
}
@Override
protected
void processCursorToColumn(final int x) throws IOException {
getConsoleInfo();
info.cursorPosition.x = (short) Math.max(0, Math.min(info.window.width(), x - 1));
applyCursorPosition();
}
@Override
protected
void processCursorLeft(final int count) throws IOException {
getConsoleInfo();
info.cursorPosition.x = (short) Math.max(0, info.cursorPosition.x - count);
applyCursorPosition();
}
@Override
protected
void processCursorRight(final int count) throws IOException {
getConsoleInfo();
info.cursorPosition.x = (short) Math.min(info.window.width(), info.cursorPosition.x + count);
applyCursorPosition();
}
@Override
protected
void processCursorDown(final int count) throws IOException {
getConsoleInfo();
info.cursorPosition.y = (short) Math.min(info.size.y, info.cursorPosition.y + count);
applyCursorPosition();
}
@Override
protected
void processCursorUp(final int count) throws IOException {
getConsoleInfo();
info.cursorPosition.y = (short) Math.max(info.window.top, info.cursorPosition.y - count);
applyCursorPosition();
}
@Override
public void close() throws IOException {
super.close();
if (console != null) {
Kernel32.CloseHandle(console);
}
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.console.util.posix;
import java.nio.ByteBuffer;
import com.sun.jna.Native;
import com.sun.jna.ptr.IntByReference;
@SuppressWarnings("ALL")
public
class CLibraryPosix {
static {
Native.register("c");
}
// MAGIC!
public static final int TIOCGWINSZ = System.getProperty("os.name").equalsIgnoreCase("linux") ? 0x5413 : 1074295912;
public static native
int isatty(int fd);
public static native
int read(int fd, IntByReference c, int count);
/**
* Original signature : <code>int ioctl(int, int, char*)</code><br>
*/
public static native
int ioctl(int d, int request, ByteBuffer data);
/**
* Put the state of FD into *TERMIOS_P.<br>
* <p>
* Original signature : <code>int tcgetattr(int, char*)</code><br>
*/
public static native
int tcgetattr(int fd, Termios termios_p);
/**
* Set the state of FD to *TERMIOS_P.<br>
* <p>
* Values for OPTIONAL_ACTIONS (TCSA*) are in <bits/termios.h>.<br>
* <p>
* Original signature : <code>int tcsetattr(int, int, char*)</code><br>
*/
public static native
int tcsetattr(int fd, int optional_actions, Termios termios_p);
}

View File

@ -0,0 +1,184 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.console.util.posix;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Structure;
@SuppressWarnings("ALL")
public
class Termios extends Structure {
// NOTE: MUST BE BITS!! from: /usr/include/x86_64-linux-gnu/bits/termios.h
// the one in octal WILL NOT WORK!! (you have been warned)
// Definitions at: http://linux.die.net/man/3/termios
// Input flags - software input processing
public static class Input {
public static final int IGNBRK = 0000001; // ignore BREAK condition
public static final int BRKINT = 0000002; // map BREAK to SIGINTR
public static final int IGNPAR = 0000004; // ignore (discard) parity errors
public static final int PARMRK = 0000010; // mark parity and framing errors
public static final int INPCK = 0000020; // enable checking of parity errors
public static final int ISTRIP = 0000040; // strip 8th bit off chars
public static final int INLCR = 0000100; // map NL into CR
public static final int IGNCR = 0000200; // ignore CR
public static final int ICRNL = 0000400; // map CR to NL (ala CRMOD)
// public static final int IUCLC = 0001000; // (not in POSIX) Map uppercase characters to lowercase on input.
public static final int IXON = 0002000; // enable output flow control
public static final int IXANY = 0004000; // any char will restart after stop
public static final int IXOFF = 0010000; // enable input flow control
public static final int IMAXBEL = 0020000; // ring bell on input queue full
// public static final int IUTF8 = 0040000; // (since Linux 2.6.4) (not in POSIX) Input is UTF8; this allows character-erase to be correctly performed in cooked mode.
}
public static class Output {
// Output flags - software output processing
public static final int OPOST = 0000001; // enable following output processing (not set = raw output)
// public static final int OLCUC = 0000002; // (not in POSIX) Map lowercase characters to uppercase on output.
public static final int ONLCR = 0000004; // map NL to CR-NL (ala CRMOD)
public static final int OCRNL = 0000010; // map CR to NL on output
public static final int ONOCR = 0000020; // no CR output at column 0
public static final int ONLRET = 0000040; // NL performs CR function
public static final int OFILL = 0000100; // Send fill characters for a delay, rather than using a timed delay.
public static final int OFDEL = 0000200; // Fill character is ASCII DEL (0177). If unset, fill character is ASCII NUL ('\0'). (Not implemented on Linux.)
}
public static class Control {
// Control flags - hardware control of terminal
public static final int CSIZE = 0000060; // character size mask
public static final int CS5 = 0000000; // 5 bits (pseudo)
public static final int CS6 = 0000020; // 6 bits
public static final int CS7 = 0000040; // 7 bits
public static final int CS8 = 0000060; // 8 bits
public static final int CSTOPB = 0000100; // send 2 stop bits
public static final int CREAD = 0000200; // enable receiver
public static final int PARENB = 0000400; // parity enable
public static final int PARODD = 0001000; // odd parity, else even
public static final int HUPCL = 0002000; // hang up on last close
public static final int CLOCAL = 0004000; // ignore modem status lines
}
public static class Local {
// "Local" flags - dumping ground for other state
// Warning: some flags in this structure begin with the letter "I" and look like they belong in the input flag.
public static final int ISIG = 0000001; // enable signals INTR, QUIT, [D]SUSP
public static final int ICANON = 0000002; // canonicalize input lines
//public static final int XCASE = 0000004; // (not in POSIX; not supported under Linux)
public static final int ECHO = 0000010; // enable echoing
public static final int ECHOE = 0000020; // visually erase chars
public static final int ECHOK = 0000040; // echo NL after line kill
public static final int ECHONL = 0000100; // echo NL even if ECHO is off
public static final int NOFLSH = 0000200; // don't flush after interrupt
public static final int TOSTOP = 0000400; // stop background jobs from output
public static final int ECHOCTL = 0001000; // echo control chars as ^(Char)
public static final int ECHOPRT = 0002000; // visual erase mode for hardcopy
public static final int ECHOKE = 0004000; // visual erase for line kill
public static final int FLUSHO = 0001000; // output being flushed (state)
public static final int PENDIN = 0004000; // XXX retype pending input (state)
public static final int IEXTEN = 0100000; // enable DISCARD and LNEXT
public static final int EXTPROC = 0200000; // external processing
}
public static class ControlChars {
// Special Control Characters
//
// the value is the index into c_cc[] character array.
public static final int VINTR = 0; // ISIG
public static final int VQUIT = 1; // ISIG
public static final int VERASE = 2; // ICANON
public static final int VKILL = 3; // ICANON
public static final int VEOF = 4; // ICANON
public static final int VTIME = 5; // !ICANON
public static final int VMIN = 6; // !ICANON
public static final int VSWTC = 7;
public static final int VSTART = 8; // IXON, IXOFF
public static final int VSTOP = 9; // IXON, IXOFF
public static final int VSUSP = 10;// ISIG
public static final int VEOL = 11;// ICANON
public static final int VREPRINT = 12;// ICANON together with IEXTEN
public static final int VDISCARD = 13;
public static final int VWERASE = 14;// ICANON together with IEXTEN
public static final int VLNEXT = 15;// IEXTEN
public static final int VEOL2 = 16;// ICANON together with IEXTEN
}
public static final int TCSANOW = 0;
/**
* input mode flags
*/
public int inputFlags;
/**
* output mode flags
*/
public int outputFlags;
/**
* control mode flags
*/
public int controlFlags;
/**
* local mode flags
*/
public int localFlags;
/**
* line discipline
*/
public char lineDiscipline;
/**
* control characters
*/
public byte[] controlChars = new byte[32];
/**
* input speed
*/
public int inputSpeed;
/**
* output speed
*/
public int outputSpeed;
public
Termios() {}
@Override
protected
List<String> getFieldOrder() {
return Arrays.asList("inputFlags",
"outputFlags",
"controlFlags",
"localFlags",
"lineDiscipline",
"controlChars",
"inputSpeed",
"outputSpeed");
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.console.util.windows;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Structure;
/**
* https://msdn.microsoft.com/en-us/library/ms682093%28VS.85%29.aspx
*/
public
class CONSOLE_SCREEN_BUFFER_INFO extends Structure {
public COORD size = new COORD();
public COORD cursorPosition = new COORD();
public short attributes = (short) 0;
public SMALL_RECT window = new SMALL_RECT();
public COORD maximumWindowSize = new COORD();
@Override
protected
List<String> getFieldOrder() {
return Arrays.asList("size", "cursorPosition", "attributes", "window", "maximumWindowSize");
}
@Override
public String toString() {
return "Size: " + size + " CursorPos: " + cursorPosition + " Attribs: " + attributes + " Window: " + window + " MaxWindowSize: " + maximumWindowSize;
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.console.util.windows;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Structure;
/**
* https://msdn.microsoft.com/en-us/library/ms682119(v=vs.85).aspx
*/
public
class COORD extends Structure {
static public class ByValue extends COORD implements Structure.ByValue { }
public short x;
public short y;
public
COORD.ByValue asValue() {
COORD.ByValue copy = new COORD.ByValue();
copy.x = this.x;
copy.y = this.y;
return copy;
}
@Override
protected
List<String> getFieldOrder() {
return Arrays.asList("x", "y");
}
@Override
public String toString() {
return x + ":" + y;
}
}

View File

@ -0,0 +1,31 @@
package dorkbox.console.util.windows;
import com.sun.jna.Union;
public class CharUnion extends Union {
public char unicodeChar;
public byte asciiChar;
public CharUnion() {
}
public CharUnion(char c) {
setType(char.class);
unicodeChar = c;
}
public CharUnion(byte c) {
setType(byte.class);
asciiChar = c;
}
public void set(char c) {
setType(char.class);
unicodeChar = c;
}
public void set(byte c) {
setType(byte.class);
asciiChar = c;
}
}

View File

@ -9,7 +9,7 @@
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
*/
package dorkbox.inputConsole.windows;
package dorkbox.console.util.windows;
/**
* Console mode
@ -17,6 +17,13 @@ package dorkbox.inputConsole.windows;
* Constants copied <tt>wincon.h</tt>.
*/
public enum ConsoleMode {
/**
* CTRL+C is processed by the system and is not placed in the input buffer. If the input buffer is being read by ReadFile or
* ReadConsole, other control keys are processed by the system and are not returned in the ReadFile or ReadConsole buffer. If the
* ENABLE_LINE_INPUT mode is also enabled, backspace, carriage return, and linefeed characters are handled by the system.
*/
ENABLE_PROCESSED_INPUT(1),
/**
* The ReadFile or ReadConsole function returns only when a carriage return character is read. If this mode is disable, the functions
* return when one or more characters are available.
@ -30,25 +37,12 @@ public enum ConsoleMode {
ENABLE_ECHO_INPUT(4),
/**
* CTRL+C is processed by the system and is not placed in the input buffer. If the input buffer is being read by ReadFile or
* ReadConsole, other control keys are processed by the system and are not returned in the ReadFile or ReadConsole buffer. If the
* ENABLE_LINE_INPUT mode is also enabled, backspace, carriage return, and linefeed characters are handled by the system.
*/
ENABLE_PROCESSED_INPUT(1),
/**
* User interactions that change the size of the console screen buffer are reported in the console's input buffee. Information about
* User interactions that change the size of the console screen buffer are reported in the console's input buffer. Information about
* these events can be read from the input buffer by applications using theReadConsoleInput function, but not by those using ReadFile
* orReadConsole.
*/
ENABLE_WINDOW_INPUT(8),
/**
* If the mouse pointer is within the borders of the console window and the window has the keyboard focus, mouse events generated by
* mouse movement and button presses are placed in the input buffer. These events are discarded by ReadFile or ReadConsole, even when
* this mode is enabled.
*/
ENABLE_MOUSE_INPUT(16), ;
;
public final int code;

View File

@ -0,0 +1,74 @@
/*
*Copyright (c) 2010 Daniel Doubrovkine, All Rights Reserved
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
*
* This library is licensed under the LGPL, version 2.1 or later, and
* (from version 4.0 onward) the Apache Software License, version 2.0.
* Commercial license arrangements are negotiable.
*/
package dorkbox.console.util.windows;
import com.sun.jna.FromNativeContext;
import com.sun.jna.Pointer;
import com.sun.jna.PointerType;
/**
* Handle to an object.
*/
public
class HANDLE extends PointerType {
/** Constant value representing an invalid HANDLE. */
public static final HANDLE INVALID_HANDLE_VALUE = new HANDLE(Pointer.createConstant(Pointer.SIZE == 8 ? -1 : 0xFFFFFFFFL));
private boolean immutable;
public
HANDLE() {
}
public
HANDLE(Pointer p) {
setPointer(p);
immutable = true;
}
@Override
public
void setPointer(Pointer p) {
if (immutable) {
throw new UnsupportedOperationException("immutable reference");
}
super.setPointer(p);
}
/**
* Override to the appropriate object for INVALID_HANDLE_VALUE.
*/
@Override
public
Object fromNative(Object nativeValue, FromNativeContext context) {
Object o = super.fromNative(nativeValue, context);
if (INVALID_HANDLE_VALUE.equals(o)) {
return INVALID_HANDLE_VALUE;
}
return o;
}
@Override
public
String toString() {
return String.valueOf(getPointer());
}
}

View File

@ -0,0 +1,44 @@
package dorkbox.console.util.windows;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Structure;
import com.sun.jna.Union;
/**
* https://msdn.microsoft.com/en-us/library/ms683499(v=VS.85).aspx
*/
public class INPUT_RECORD extends Structure {
static public class ByReference extends INPUT_RECORD implements Structure.ByReference {}
public static final short KEY_EVENT = 0x0001;
public static final short MOUSE_EVENT = 0x0002;
public short EventType;
public EventUnion Event;
public static class EventUnion extends Union {
public KEY_EVENT_RECORD KeyEvent;
public MOUSE_EVENT_RECORD MouseEvent;
}
@Override
public void read() {
readField("EventType");
switch (EventType) {
case KEY_EVENT:
Event.setType(KEY_EVENT_RECORD.class);
break;
case MOUSE_EVENT:
Event.setType(MOUSE_EVENT_RECORD.class);
break;
}
super.read();
}
@Override
protected List<String> getFieldOrder() {
return Arrays.asList("EventType", "Event");
}
}

View File

@ -0,0 +1,23 @@
package dorkbox.console.util.windows;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Structure;
/**
* https://msdn.microsoft.com/en-us/library/ms684166(v=VS.85).aspx
*/
public class KEY_EVENT_RECORD extends Structure {
public boolean keyDown;
public short repeatCount;
public short virtualKeyCode;
public short virtualScanCode;
public CharUnion uChar;
public int controlKeyState;
@Override
protected List<String> getFieldOrder() {
return Arrays.asList("keyDown", "repeatCount", "virtualKeyCode", "virtualScanCode", "uChar", "controlKeyState");
}
}

View File

@ -0,0 +1,173 @@
/*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.console.util.windows;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
public class Kernel32 {
static {
Native.register("kernel32");
}
// see: http://msdn.microsoft.com/en-us/library/ms682013%28VS.85%29.aspx
public static final short FOREGROUND_BLACK = (short) 0x0000;
public static final short FOREGROUND_BLUE = (short) 0x0001;
public static final short FOREGROUND_GREEN = (short) 0x0002;
public static final short FOREGROUND_CYAN = (short) 0x0003;
public static final short FOREGROUND_RED = (short) 0x0004;
public static final short FOREGROUND_MAGENTA = (short) 0x0005;
public static final short FOREGROUND_YELLOW = (short) 0x0006;
public static final short FOREGROUND_GREY = (short) 0x0007;
public static final short FOREGROUND_INTENSITY = (short) 0x0008; // foreground color is intensified.
public static final short BACKGROUND_BLACK = (short) 0x0000;
public static final short BACKGROUND_BLUE = (short) 0x0010;
public static final short BACKGROUND_GREEN = (short) 0x0020;
public static final short BACKGROUND_CYAN = (short) 0x0030;
public static final short BACKGROUND_RED = (short) 0x0040;
public static final short BACKGROUND_MAGENTA = (short) 0x0050;
public static final short BACKGROUND_YELLOW = (short) 0x0060;
public static final short BACKGROUND_GREY = (short) 0x0070;
public static final short BACKGROUND_INTENSITY = (short) 0x0080; // background color is intensified.
public static final short COMMON_LVB_LEADING_BYTE = (short) 0x0100;
public static final short COMMON_LVB_TRAILING_BYTE = (short) 0x0200;
public static final short COMMON_LVB_GRID_HORIZONTAL = (short) 0x0400;
public static final short COMMON_LVB_GRID_LVERTICAL = (short) 0x0800;
public static final short COMMON_LVB_GRID_RVERTICAL = (short) 0x1000;
public static final short COMMON_LVB_REVERSE_VIDEO = (short) 0x4000;
public static final short COMMON_LVB_UNDERSCORE = (short) 0x8000;
private static final int FORMAT_MESSAGE_FROM_SYSTEM = 0x1000;
public static final int STD_INPUT_HANDLE = -10;
public static final int STD_OUTPUT_HANDLE = -11;
public static final int STD_ERROR_HANDLE = -12;
/**
* https://msdn.microsoft.com/en-us/library/ms683231%28VS.85%29.aspx
*/
public static native
HANDLE GetStdHandle(int stdHandle);
/**
* https://msdn.microsoft.com/en-us/library/ms724211%28VS.85%29.aspx
*/
public static native
int CloseHandle(HANDLE handle);
/**
* https://msdn.microsoft.com/en-us/library/ms686047%28VS.85%29.aspx
*/
public static native
int SetConsoleTextAttribute(HANDLE consoleOutput, short attributes);
/**
* https://msdn.microsoft.com/en-us/library/windows/desktop/ms679351(v=vs.85).aspx
*/
public static native
int FormatMessageA(int flags, Pointer source, int messageId, int languageId, byte[] buffer, int size, long[] args);
/**
* https://msdn.microsoft.com/en-us/library/ms683171%28VS.85%29.aspx
*/
public static native
int GetConsoleScreenBufferInfo(HANDLE consoleOutput, CONSOLE_SCREEN_BUFFER_INFO consoleScreenBufferInfo);
/**
* https://msdn.microsoft.com/en-us/library/ms686025%28VS.85%29.aspx
*/
public static native
int SetConsoleCursorPosition(HANDLE consoleOutput, COORD.ByValue cursorPosition);
/**
* https://msdn.microsoft.com/en-us/library/ms682662%28VS.85%29.aspx
*/
public static native
int FillConsoleOutputAttribute(HANDLE consoleOutput, short attribute, int length, COORD.ByValue writeCoord, int[] numberOfAttrsWritten);
/**
* https://msdn.microsoft.com/en-us/library/ms682663%28VS.85%29.aspx
*/
public static native
int FillConsoleOutputCharacterW(HANDLE consoleOutput, char character, int length, COORD.ByValue writeCoord, int[] numberOfCharsWritten);
/**
* https://msdn.microsoft.com/en-us/library/ms683167%28VS.85%29.aspx
*/
public static native
int GetConsoleMode(HANDLE handle, IntByReference mode);
/**
* https://msdn.microsoft.com/en-us/library/ms686033%28VS.85%29.aspx
*/
public static native
int SetConsoleMode(HANDLE handle, int mode);
/**
* https://msdn.microsoft.com/en-us/library/ms684961(v=VS.85).aspx
*/
public static native
int ReadConsoleInputW(HANDLE handle, INPUT_RECORD.ByReference inputRecords, int length, IntByReference eventsCount);
public static void ASSERT(final int returnValue, final String message) {
// if returnValue == 0, throw assertion error
assert returnValue != 0 : message + " : " + getLastErrorMessage();
}
private interface Win10 {
boolean IsWindows10OrGreater();
}
private static
String getLastErrorMessage() {
int errorCode = Native.getLastError();
int bufferSize = 160;
byte data[] = new byte[bufferSize];
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, Pointer.NULL, errorCode, 0, data, bufferSize, null);
return new String(data);
}
/**
* Windows 10+ supports ANSI according to microsoft
*/
public static
boolean isWindows10OrGreater() {
try {
final Object kernel32 = Native.loadLibrary("kernel32", Win10.class);
if (kernel32 != null) {
boolean isWin10Plus = ((Win10)kernel32).IsWindows10OrGreater();
Native.unregister(Win10.class);
return isWin10Plus;
}
return false;
} catch (Exception e) {
return false;
}
}
}

View File

@ -0,0 +1,18 @@
package dorkbox.console.util.windows;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Structure;
public class MOUSE_EVENT_RECORD extends Structure {
public COORD mousePosition;
public int buttonState;
public int controlKeyState;
public int eventFlags;
@Override
protected List<String> getFieldOrder() {
return Arrays.asList("mousePosition", "buttonState", "controlKeyState", "eventFlags");
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.console.util.windows;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Structure;
/**
* https://msdn.microsoft.com/en-us/library/ms686311%28VS.85%29.aspx
*/
public class SMALL_RECT extends Structure {
public short left;
public short top;
public short right;
public short bottom;
public short width() {
return (short) (this.right-this.left);
}
public short height() {
return (short) (this.bottom-this.top);
}
@Override
protected List<String> getFieldOrder() {
return Arrays.asList("left", "top", "right", "bottom");
}
@Override
public String toString() {
return "LTRB: " + left + "," + top + "," + right + "," + bottom;
}
}

View File

@ -1,50 +0,0 @@
/*
* Copyright (c) 2002-2012, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* http://www.opensource.org/licenses/bsd-license.php
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
*/
package dorkbox.inputConsole;
import java.nio.charset.Charset;
public class Encoding {
/**
* Get the default encoding. Will first look at the LC_CTYPE environment variable, then the input.encoding system property, then the
* default charset according to the JVM.
*
* @return The default encoding to use when none is specified.
*/
public static String get() {
// LC_CTYPE is usually in the form en_US.UTF-8
String envEncoding = extractEncodingFromCtype(System.getenv("LC_CTYPE"));
if (envEncoding != null) {
return envEncoding;
}
return System.getProperty("input.encoding", Charset.defaultCharset().name());
}
/**
* Parses the LC_CTYPE value to extract the encoding according to the POSIX standard, which says that the LC_CTYPE environment variable
* may be of the format <code>[language[_territory][.codeset][@modifier]]</code>
*
* @param ctype The ctype to parse, may be null
* @return The encoding, if one was present, otherwise null
*/
private static String extractEncodingFromCtype(String ctype) {
if (ctype != null && ctype.indexOf('.') > 0) {
String encodingAndModifier = ctype.substring(ctype.indexOf('.') + 1);
if (encodingAndModifier.indexOf('@') > 0) {
return encodingAndModifier.substring(0, encodingAndModifier.indexOf('@'));
}
return encodingAndModifier;
}
return null;
}
}

View File

@ -1,580 +0,0 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.inputConsole;
import dorkbox.inputConsole.posix.UnixTerminal;
import dorkbox.inputConsole.unsupported.UnsupportedTerminal;
import dorkbox.inputConsole.windows.WindowsTerminal;
import dorkbox.objectPool.ObjectPool;
import dorkbox.util.FastThreadLocal;
import dorkbox.util.OS;
import dorkbox.util.bytes.ByteBuffer2;
import dorkbox.util.bytes.ByteBuffer2Poolable;
import org.fusesource.jansi.Ansi;
import org.fusesource.jansi.AnsiConsole;
import org.slf4j.Logger;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.URL;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public
class InputConsole {
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(InputConsole.class);
private static final InputConsole consoleProxyReader = new InputConsole();
private static final char[] emptyLine = new char[0];
// this is run when init is called
static {
AnsiConsole.systemInstall();
Thread consoleThread = new Thread(new Runnable() {
@Override
public
void run() {
consoleProxyReader.run();
}
});
consoleThread.setDaemon(true);
consoleThread.setName("Console Input Reader");
consoleThread.start();
// has to be NOT DAEMON thread, since it must run before the app closes.
// don't forget we have to shut down the ansi console as well
// alternatively, shut everything down when the JVM closes.
Thread shutdownThread = new Thread() {
@Override
public
void run() {
AnsiConsole.systemUninstall();
consoleProxyReader.shutdown0();
}
};
shutdownThread.setName("Console Input Shutdown");
Runtime.getRuntime().addShutdownHook(shutdownThread);
}
/**
* Permit our InputConsole to be initialized
*/
public static
void init() {
if (logger.isDebugEnabled()) {
logger.debug("Created Terminal: {} ({}w x {}h)", consoleProxyReader.terminal.getClass().getSimpleName(),
consoleProxyReader.terminal.getWidth(), consoleProxyReader.terminal.getHeight());
}
}
/**
* Gets the version number.
*/
public static
String getVersion() {
return "2.9";
}
/**
* return null if no data
*/
public static
String readLine() {
char[] line = consoleProxyReader.readLine0();
return new String(line);
}
private static InputStream wrappedInputStream = new InputStream() {
@Override
public
int read() throws IOException {
return consoleProxyReader.read0();
}
@Override
public
void close() throws IOException {
consoleProxyReader.release0();
}
};
/**
* return -1 if no data
*/
public static
int read() {
return consoleProxyReader.read0();
}
/**
* return null if no data
*/
public static
char[] readLinePassword() {
return consoleProxyReader.readLinePassword0();
}
public static
InputStream getInputStream() {
return wrappedInputStream;
}
public static
void echo(boolean enableEcho) {
consoleProxyReader.echo0(enableEcho);
}
public static
boolean echo() {
return consoleProxyReader.echo0();
}
private final Object inputLock = new Object();
private final Object inputLockSingle = new Object();
private final Object inputLockLine = new Object();
private final ObjectPool<ByteBuffer2> pool;
private FastThreadLocal<ByteBuffer2> readBuff = new FastThreadLocal<ByteBuffer2>();
private List<ByteBuffer2> readBuffers = new CopyOnWriteArrayList<ByteBuffer2>();
private FastThreadLocal<Integer> threadBufferCounter = new FastThreadLocal<Integer>();
private FastThreadLocal<ByteBuffer2> readLineBuff = new FastThreadLocal<ByteBuffer2>();
private List<ByteBuffer2> readLineBuffers = new CopyOnWriteArrayList<ByteBuffer2>();
private final Terminal terminal;
private final Boolean enableBackspace;
private
InputConsole() {
Logger logger = InputConsole.logger;
String readers = System.getProperty(TerminalType.READERS);
int readers2 = 32;
if (readers != null) {
try {
readers2 = Integer.parseInt(readers);
} catch (Exception e) {
e.printStackTrace();
}
}
this.pool = ObjectPool.Blocking(new ByteBuffer2Poolable(), readers2);
String type = System.getProperty(TerminalType.TYPE, TerminalType.AUTO).toLowerCase();
if ("dumb".equals(System.getenv("TERM"))) {
type = TerminalType.NONE;
if (logger.isTraceEnabled()) {
logger.trace("System environment 'TERM'=dumb, creating type=" + type);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Creating terminal, type=" + type);
}
}
Class<? extends Terminal> t;
try {
if (type.equals(TerminalType.UNIX)) {
t = UnixTerminal.class;
}
else if (type.equals(TerminalType.WIN) || type.equals(TerminalType.WINDOWS)) {
t = WindowsTerminal.class;
}
else if (type.equals(TerminalType.NONE) || type.equals(TerminalType.OFF) || type.equals(TerminalType.FALSE)) {
t = UnsupportedTerminal.class;
}
else {
if (isIDEAutoDetect()) {
logger.debug("Terminal is in UNSUPPORTED (best guess). Unable to support single key input. Only line input available.");
t = UnsupportedTerminal.class;
}
else {
if (OS.isWindows()) {
t = WindowsTerminal.class;
}
else {
t = UnixTerminal.class;
}
}
}
} catch (Exception e) {
logger.error("Failed to construct terminal, falling back to unsupported.", e);
t = UnsupportedTerminal.class;
}
Terminal terminal = null;
try {
terminal = t.newInstance();
terminal.init();
} catch (Throwable e) {
logger.error("Terminal initialization failed for {}, falling back to unsupported.", t.getSimpleName(), e);
t = UnsupportedTerminal.class;
try {
terminal = t.newInstance();
terminal.init();
} catch (Exception e1) {
// UnsupportedTerminal can't do this
}
}
if (terminal != null) {
terminal.setEchoEnabled(true);
}
this.terminal = terminal;
this.enableBackspace = Boolean.parseBoolean(System.getProperty(TerminalType.ENABLE_BACKSPACE, "true"));
}
// called when the JVM is shutting down.
private
void shutdown0() {
synchronized (this.inputLockSingle) {
this.inputLockSingle.notifyAll();
}
synchronized (this.inputLockLine) {
this.inputLockLine.notifyAll();
}
try {
InputConsole inputConsole = InputConsole.this;
inputConsole.terminal.restore();
// this will 'hang' our shutdown, and honestly, who cares? We're shutting down anyways.
// inputConsole.reader.close(); // hangs on shutdown
} catch (IOException ignored) {
ignored.printStackTrace();
}
}
private
void echo0(boolean enableEcho) {
this.terminal.setEchoEnabled(enableEcho);
}
private
boolean echo0() {
return this.terminal.isEchoEnabled();
}
/**
* return -1 if no data or bunged-up
*/
private
int read0() {
Integer bufferCounter = this.threadBufferCounter.get();
ByteBuffer2 buffer = this.readBuff.get();
if (buffer == null) {
bufferCounter = 0;
this.threadBufferCounter.set(bufferCounter);
try {
buffer = this.pool.takeInterruptibly();
buffer.clear();
} catch (InterruptedException e) {
logger.error("Interrupted while receiving buffer from pool.");
buffer = pool.newInstance();
}
this.readBuff.set(buffer);
this.readBuffers.add(buffer);
}
if (bufferCounter == buffer.position()) {
synchronized (this.inputLockSingle) {
buffer.setPosition(0);
this.threadBufferCounter.set(0);
try {
this.inputLockSingle.wait();
} catch (InterruptedException e) {
return -1;
}
}
}
bufferCounter = this.threadBufferCounter.get();
char c = buffer.readChar(bufferCounter);
bufferCounter += 2;
this.threadBufferCounter.set(bufferCounter);
return c;
}
/**
* return empty char[] if no data
*/
private
char[] readLinePassword0() {
// don't bother in an IDE. it won't work.
boolean echoEnabled = this.terminal.isEchoEnabled();
this.terminal.setEchoEnabled(false);
char[] readLine0 = readLine0();
this.terminal.setEchoEnabled(echoEnabled);
return readLine0;
}
/**
* return empty char[] if no data
*/
private
char[] readLine0() {
synchronized (this.inputLock) {
// empty here, because we don't want to register a readLine WHILE we are still processing
// the current line info.
// the threadBufferForRead getting added is the part that is important
if (this.readLineBuff.get() == null) {
ByteBuffer2 buffer;
try {
buffer = this.pool.takeInterruptibly();
} catch (InterruptedException e) {
logger.error("Interrupted while receiving buffer from pool.");
buffer = pool.newInstance();
}
this.readLineBuff.set(buffer);
this.readLineBuffers.add(buffer);
}
else {
this.readLineBuff.get().clear();
}
}
synchronized (this.inputLockLine) {
try {
this.inputLockLine.wait();
} catch (InterruptedException e) {
return emptyLine;
}
}
ByteBuffer2 buffer = this.readLineBuff.get();
int len = buffer.position();
if (len == 0) {
return emptyLine;
}
buffer.rewind();
char[] readChars = buffer.readChars(len / 2); // java always stores chars in 2 bytes
// dump the chars in the buffer (safer for passwords, etc)
buffer.clearSecure();
this.readLineBuffers.remove(buffer);
this.pool.put(buffer);
this.readLineBuff.set(null);
return readChars;
}
/**
* releases any thread still waiting.
*/
private
void release0() {
synchronized (this.inputLockSingle) {
this.inputLockSingle.notifyAll();
}
synchronized (this.inputLockLine) {
this.inputLockLine.notifyAll();
}
}
private
void run() {
Logger logger2 = logger;
final boolean ansiEnabled = Ansi.isEnabled();
Ansi ansi = Ansi.ansi();
PrintStream out = AnsiConsole.out;
int typedChar;
char asChar;
// don't type ; in a bash shell, it quits everything
// \n is replaced by \r in unix terminal?
while ((typedChar = this.terminal.read()) != -1) {
synchronized (this.inputLock) {
// don't let anyone add a new reader while we are still processing the current actions
asChar = (char) typedChar;
if (logger2.isTraceEnabled()) {
logger2.trace("READ: {} ({})", asChar, typedChar);
}
// notify everyone waiting for a character.
synchronized (this.inputLockSingle) {
// have to do readChar first (readLine has to deal with \b and \n
for (ByteBuffer2 buffer : this.readBuffers) {
buffer.writeChar(asChar);
}
this.inputLockSingle.notifyAll();
}
// now to handle readLine stuff
// if we type a backspace key, swallow it + previous in READLINE. READCHAR will have it passed.
if (this.enableBackspace && asChar == '\b') {
int position = 0;
// clear ourself + one extra.
if (ansiEnabled) {
for (ByteBuffer2 buffer : this.readLineBuffers) {
// size of the buffer BEFORE our backspace was typed
int length = buffer.position();
int amtToOverwrite = 2 * 2; // backspace is always 2 chars (^?) * 2 because it's bytes
if (length > 1) {
char charAt = buffer.readChar(length - 2);
amtToOverwrite += getPrintableCharacters(charAt);
// delete last item in our buffer
length -= 2;
buffer.setPosition(length);
// now figure out where the cursor is really at.
// this is more memory friendly than buf.toString.length
for (int i = 0; i < length; i += 2) {
charAt = buffer.readChar(i);
position += getPrintableCharacters(charAt);
}
position++;
}
char[] overwrite = new char[amtToOverwrite];
char c = ' ';
for (int i = 0; i < amtToOverwrite; i++) {
overwrite[i] = c;
}
// move back however many, over write, then go back again
out.print(ansi.cursorToColumn(position));
out.print(overwrite);
out.print(ansi.cursorToColumn(position));
out.flush();
}
}
}
else if (asChar == '\n') {
// ignoring \r, because \n is ALWAYS the last character in a new line sequence. (even for windows)
synchronized (this.inputLockLine) {
this.inputLockLine.notifyAll();
}
}
else {
// only append if we are not a new line.
// our windows console PREVENTS us from returning '\r' (it truncates '\r\n', and returns just '\n')
for (ByteBuffer2 buffer : this.readLineBuffers) {
buffer.writeChar(asChar);
}
}
}
}
}
/**
* try to guess if we are running inside an IDE
*/
private
boolean isIDEAutoDetect() {
try {
// Get the location of this class
ProtectionDomain pDomain = getClass().getProtectionDomain();
CodeSource cSource = pDomain.getCodeSource();
URL loc = cSource.getLocation(); // file:/X:/workspace/xxxx/classes/ when it's in eclipse
// if we are in eclipse, this won't be a jar -- it will be a class directory.
File locFile = new File(loc.getFile());
return locFile.isDirectory();
} catch (Exception ignored) {
}
// fall-back to unsupported
return true;
}
/**
* Return the number of characters that will be printed when the specified character is echoed to the screen
* <p/>
* Adapted from cat by Torbjorn Granlund, as repeated in stty by David MacKenzie.
*/
private static
int getPrintableCharacters(final int ch) {
// StringBuilder sbuff = new StringBuilder();
if (ch >= 32) {
if (ch < 127) {
// sbuff.append((char) ch);
return 1;
}
else if (ch == 127) {
// sbuff.append('^');
// sbuff.append('?');
return 2;
}
else {
// sbuff.append('M');
// sbuff.append('-');
int count = 2;
if (ch >= 128 + 32) {
if (ch < 128 + 127) {
// sbuff.append((char) (ch - 128));
count++;
}
else {
// sbuff.append('^');
// sbuff.append('?');
count += 2;
}
}
else {
// sbuff.append('^');
// sbuff.append((char) (ch - 128 + 64));
count += 2;
}
return count;
}
}
else {
// sbuff.append('^');
// sbuff.append((char) (ch + 64));
return 2;
}
// return sbuff;
}
}

View File

@ -1,306 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.inputConsole.posix;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.MalformedInputException;
import java.nio.charset.UnmappableCharacterException;
/**
* NOTE for JLine: the default InputStreamReader that comes from the JRE usually read more bytes than needed from the input stream, which is
* not usable in a character per character model used in the console. We thus use the harmony code which only reads the minimal number of
* bytes, with a modification to ensure we can read larger characters (UTF-16 has up to 4 bytes, and UTF-32, rare as it is, may have up to
* 8).
*
*
* A class for turning a byte stream into a character stream. Data read from the source input stream is converted into characters by either
* a default or a provided character converter. The default encoding is taken from the "file.encoding" system property.
* {@code InputStreamReader} contains a buffer of bytes read from the source stream and converts these into characters as needed. The buffer
* size is 8K.
*
* @see OutputStreamWriter
*/
@SuppressWarnings("ALL")
class InputStreamReader extends Reader {
private InputStream in;
private static final int BUFFER_SIZE = 8192;
private boolean endOfInput = false;
String encoding;
CharsetDecoder decoder;
ByteBuffer bytes = ByteBuffer.allocate(BUFFER_SIZE);
/**
* Constructs a new {@code InputStreamReader} on the {@link InputStream} {@code in}. This constructor sets the character converter to
* the encoding specified in the "file.encoding" property and falls back to ISO 8859_1 (ISO-Latin-1) if the property doesn't exist.
*
* @param in the input stream from which to read characters.
*/
public InputStreamReader(InputStream in) {
super(in);
this.in = in;
// FIXME: This should probably use Configuration.getFileEncoding()
this.encoding = System.getProperty("file.encoding", "ISO8859_1"); //$NON-NLS-1$//$NON-NLS-2$
this.decoder =
Charset.forName(this.encoding).newDecoder().onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
this.bytes.limit(0);
}
/**
* Constructs a new InputStreamReader on the InputStream {@code in}. The character converter that is used to decode bytes into
* characters is identified by name by {@code enc}. If the encoding cannot be found, an UnsupportedEncodingException error is thrown.
*
* @param in the InputStream from which to read characters.
* @param enc identifies the character converter to use.
* @throws NullPointerException if {@code enc} is {@code null}.
* @throws UnsupportedEncodingException if the encoding specified by {@code enc} cannot be found.
*/
public InputStreamReader(InputStream in, final String enc)
// throws UnsupportedEncodingException
{
super(in);
if (enc == null) {
throw new NullPointerException();
}
this.in = in;
try {
this.decoder =
Charset.forName(enc).newDecoder().onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
} catch (IllegalArgumentException e) {
e.printStackTrace();
// throw (UnsupportedEncodingException)
// new UnsupportedEncodingException(enc).initCause(e);
}
this.bytes.limit(0);
}
/**
* Constructs a new InputStreamReader on the InputStream {@code in} and CharsetDecoder {@code dec}.
*
* @param in the source InputStream from which to read characters.
* @param dec the CharsetDecoder used by the character conversion.
*/
public InputStreamReader(InputStream in, CharsetDecoder dec) {
super(in);
dec.averageCharsPerByte();
this.in = in;
this.decoder = dec;
this.bytes.limit(0);
}
/**
* Constructs a new InputStreamReader on the InputStream {@code in} and Charset {@code charset}.
*
* @param in the source InputStream from which to read characters.
* @param charset the Charset that defines the character converter
*/
public InputStreamReader(InputStream in, Charset charset) {
super(in);
this.in = in;
this.decoder = charset.newDecoder().onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(CodingErrorAction.REPLACE);
this.bytes.limit(0);
}
/**
* Closes this reader. This implementation closes the source InputStream and releases all local storage.
*
* @throws IOException if an error occurs attempting to close this reader.
*/
@Override
public void close() throws IOException {
synchronized (this.lock) {
this.decoder = null;
if (this.in != null) {
this.in.close();
this.in = null;
}
}
}
/**
* Returns the name of the encoding used to convert bytes into characters. The value {@code null} is returned if this reader has been
* closed.
*
* @return the name of the character converter or {@code null} if this reader is closed.
*/
public String getEncoding() {
if (!isOpen()) {
return null;
}
return this.encoding;
}
/**
* Reads a single character from this reader and returns it as an integer with the two higher-order bytes set to 0. Returns -1 if the
* end of the reader has been reached. The byte value is either obtained from converting bytes in this reader's buffer or by first
* filling the buffer from the source InputStream and then reading from the buffer.
*
* @return the character read or -1 if the end of the reader has been reached.
* @throws IOException if this reader is closed or some other I/O error occurs.
*/
@Override
public int read() throws IOException {
synchronized (this.lock) {
if (!isOpen()) {
throw new IOException("InputStreamReader is closed.");
}
char buf[] = new char[4];
return read(buf, 0, 4) != -1 ? Character.codePointAt(buf, 0) : -1;
}
}
/**
* Reads at most {@code length} characters from this reader and stores them at position {@code offset} in the character array
* {@code buf}. Returns the number of characters actually read or -1 if the end of the reader has been reached. The bytes are either
* obtained from converting bytes in this reader's buffer or by first filling the buffer from the source InputStream and then reading
* from the buffer.
*
* @param buf the array to store the characters read.
* @param offset the initial position in {@code buf} to store the characters read from this reader.
* @param length the maximum number of characters to read.
* @return the number of characters read or -1 if the end of the reader has been reached.
* @throws IndexOutOfBoundsException if {@code offset < 0} or {@code length < 0}, or if {@code offset + length} is greater than the
* length of {@code buf}.
* @throws IOException if this reader is closed or some other I/O error occurs.
*/
@Override
public int read(char[] buf, int offset, int length) throws IOException {
synchronized (this.lock) {
if (!isOpen()) {
throw new IOException("InputStreamReader is closed.");
}
if (offset < 0 || offset > buf.length - length || length < 0) {
throw new IndexOutOfBoundsException();
}
if (length == 0) {
return 0;
}
CharBuffer out = CharBuffer.wrap(buf, offset, length);
CoderResult result = CoderResult.UNDERFLOW;
// bytes.remaining() indicates number of bytes in buffer
// when 1-st time entered, it'll be equal to zero
boolean needInput = !this.bytes.hasRemaining();
while (out.hasRemaining()) {
// fill the buffer if needed
if (needInput) {
try {
if (this.in.available() == 0 && out.position() > offset) {
// we could return the result without blocking read
break;
}
} catch (IOException e) {
// available didn't work so just try the read
}
int to_read = this.bytes.capacity() - this.bytes.limit();
int off = this.bytes.arrayOffset() + this.bytes.limit();
int was_red = this.in.read(this.bytes.array(), off, to_read);
if (was_red == -1) {
this.endOfInput = true;
break;
} else if (was_red == 0) {
break;
}
this.bytes.limit(this.bytes.limit() + was_red);
needInput = false;
}
// decode bytes
result = this.decoder.decode(this.bytes, out, false);
if (result.isUnderflow()) {
// compact the buffer if no space left
if (this.bytes.limit() == this.bytes.capacity()) {
this.bytes.compact();
this.bytes.limit(this.bytes.position());
this.bytes.position(0);
}
needInput = true;
} else {
break;
}
}
if (result == CoderResult.UNDERFLOW && this.endOfInput) {
result = this.decoder.decode(this.bytes, out, true);
this.decoder.flush(out);
this.decoder.reset();
}
if (result.isMalformed()) {
throw new MalformedInputException(result.length());
} else if (result.isUnmappable()) {
throw new UnmappableCharacterException(result.length());
}
return out.position() - offset == 0 ? -1 : out.position() - offset;
}
}
/*
* Answer a boolean indicating whether or not this InputStreamReader is open.
*/
private boolean isOpen() {
return this.in != null;
}
/**
* Indicates whether this reader is ready to be read without blocking. If the result is {@code true}, the next {@code read()} will not
* block. If the result is {@code false} then this reader may or may not block when {@code read()} is called. This implementation
* returns {@code true} if there are bytes available in the buffer or the source stream has bytes available.
*
* @return {@code true} if the receiver will not block when {@code read()} is called, {@code false} if unknown or blocking will occur.
* @throws IOException if this reader is closed or some other I/O error occurs.
*/
@Override
public boolean ready() throws IOException {
synchronized (this.lock) {
if (this.in == null) {
throw new IOException("InputStreamReader is closed.");
}
try {
return this.bytes.hasRemaining() || this.in.available() > 0;
} catch (IOException e) {
return false;
}
}
}
}

View File

@ -1,85 +0,0 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.inputConsole.posix;
import com.sun.jna.Library;
import java.nio.ByteBuffer;
@SuppressWarnings("ALL")
interface PosixTerminalControl extends Library {
public static final int TCSANOW = 0;
public static final int TBUFLEN = 124;
// Definitions at: http://linux.die.net/man/3/termios
// also: http://code.metager.de/source/xref/DragonFly-BSD/sys/sys/termios.h
public static final int IGNBRK = 0x00000001; /* ignore BREAK condition */
public static final int BRKINT = 0x00000002; /* map BREAK to SIGINTR */
public static final int ISIG = 0000001;
public static final int ICANON = 0000002;
public static final int ECHO = 0000010;
public static final int IXON = 0002000;
public static final int VINTR = 0;
public static final int VQUIT = 1;
public static final int VERASE = 2;
public static final int VKILL = 3;
public static final int VEOF = 4;
public static final int VTIME = 5;
public static final int VMIN = 6;
public static final int VSWTC = 7;
public static final int VSTART = 8;
public static final int VSTOP = 9;
public static final int VSUSP = 10;
public static final int VEOL = 11;
public static final int VREPRINT = 12;
public static final int VDISCARD = 13;
public static final int VWERASE = 14;
public static final int VLNEXT = 15;
public static final int VEOL2 = 16;
// MAGIC!
public static final int TIOCGWINSZ = System.getProperty("os.name").equalsIgnoreCase("linux") ? 0x5413 : 1074295912;
public int open(String path, int flags);
public int close(int fd);
/**
* Original signature : <code>int ioctl(int, int, char*)</code><br>
*/
public int ioctl(int d, int request, ByteBuffer data);
/**
* Put the state of FD into *TERMIOS_P.<br>
*
* Original signature : <code>int tcgetattr(int, char*)</code><br>
*/
public int tcgetattr(int fd, TermiosStruct termios_p);
/**
* Set the state of FD to *TERMIOS_P.<br>
*
* Values for OPTIONAL_ACTIONS (TCSA*) are in <bits/termios.h>.<br>
*
* Original signature : <code>int tcsetattr(int, int, char*)</code><br>
*/
public int tcsetattr(int fd, int optional_actions, TermiosStruct termios_p);
}

View File

@ -1,75 +0,0 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.inputConsole.posix;
import com.sun.jna.Structure;
import java.util.Arrays;
import java.util.List;
@SuppressWarnings("ALL")
public
class TermiosStruct extends Structure {
/**
* input mode flags
*/
public int c_iflag;
/**
* output mode flags
*/
public int c_oflag;
/**
* control mode flags
*/
public int c_cflag;
/**
* local mode flags
*/
public int c_lflag;
/**
* line discipline
*/
public byte c_line;
/**
* control characters
*/
public byte[] c_cc = new byte[32];
/**
* input speed
*/
public int c_ispeed;
/**
* output speed
*/
public int c_ospeed;
public TermiosStruct() {}
@Override
protected List<String> getFieldOrder() {
return Arrays.asList(
"c_iflag",
"c_oflag",
"c_cflag",
"c_lflag",
"c_line",
"c_cc",
"c_ispeed",
"c_ospeed");
}
}

View File

@ -1,193 +0,0 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.inputConsole.posix;
import java.io.IOException;
import java.io.Reader;
import java.nio.ByteBuffer;
import com.sun.jna.Native;
import dorkbox.inputConsole.Encoding;
import dorkbox.inputConsole.Terminal;
/**
* Terminal that is used for unix platforms. Terminal initialization is handled via JNA and ioctl/tcgetattr/tcsetattr/cfmakeraw.
*
* This implementation should work for an reasonable POSIX system.
*/
public class UnixTerminal extends Terminal {
private volatile TermiosStruct termInfoDefault = new TermiosStruct();
private volatile TermiosStruct termInfo = new TermiosStruct();
private final Reader reader;
private final PosixTerminalControl term;
private ByteBuffer windowSizeBuffer = ByteBuffer.allocate(8);
public UnixTerminal() throws Exception {
String encoding = Encoding.get();
this.reader = new InputStreamReader(System.in, encoding);
this.term = (PosixTerminalControl) Native.loadLibrary("c", PosixTerminalControl.class);
// save off the defaults
if (this.term.tcgetattr(0, this.termInfoDefault) != 0) {
throw new IOException("Failed to get terminal info");
}
}
@Override
public void init() throws IOException {
// COMPARABLE TO (from upstream)
//settings.set("-icanon min 1 -ixon");
//settings.set("dsusp undef");
/*
* NOT done in constructor, since our unit test DOES NOT use this!
*
* Set the console to be character-buffered instead of line-buffered.
* Allow ctrl-s and ctrl-q keypress to be used (as forward search)
*/
// raw mode
// t->c_iflag &= ~(IMAXBEL|IXOFF|INPCK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON|IGNPAR);
// t->c_iflag |= IGNBRK;
// t->c_oflag &= ~OPOST;
// t->c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|ICANON|ISIG|IEXTEN|NOFLSH|TOSTOP|PENDIN);
// t->c_cflag &= ~(CSIZE|PARENB);
// t->c_cflag |= CS8|CREAD;
// t->c_cc[VMIN] = 1;
// t->c_cc[VTIME] = 0;
if (this.term.tcgetattr(0, this.termInfo) != 0) {
throw new IOException("Failed to get terminal info");
}
this.termInfo.c_iflag &= ~PosixTerminalControl.IXON; // DISABLE - flow control mediated by ^S and ^Q
// struct.c_iflag |= PosixTerminalControl.IUTF8; // DISABLE - flow control mediated by ^S and ^Q
this.termInfo.c_lflag &= ~PosixTerminalControl.ICANON; // DISABLE - canonical mode (pass chars straight through to terminal)
// struct.c_lflag &= ~PosixTerminalControl.ISIG; // DISABLE - When any of the characters INTR, QUIT, SUSP, or DSUSP are received,
// generate the corresponding signal.
// If MIN > 0 and TIME = 0, MIN sets the number of characters to receive before the read is satisfied. As TIME is zero, the timer is
// not used.
this.termInfo.c_cc[PosixTerminalControl.VMIN] = 1; // Minimum number of characters for noncanonical read (MIN).
this.termInfo.c_cc[PosixTerminalControl.VTIME] = 0; // Timeout in deciseconds for noncanonical read (TIME).
this.termInfo.c_cc[PosixTerminalControl.VSUSP] = 0; // suspend disabled
this.termInfo.c_cc[PosixTerminalControl.VEOF] = 0; // eof disabled
this.termInfo.c_cc[PosixTerminalControl.VEOL] = 0; // eol disabled
if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfo) != 0) {
throw new IOException("Can not set terminal flags");
}
}
/**
* Restore the original terminal configuration, which can be used when shutting down the console reader. The ConsoleReader cannot be
* used after calling this method.
*/
@Override
public final void restore() throws IOException {
if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfoDefault) != 0) {
throw new IOException("Can not reset terminal to defaults");
}
}
/**
* Returns number of columns in the terminal.
*/
@Override
public final int getWidth() {
if (this.term.ioctl(0, PosixTerminalControl.TIOCGWINSZ, this.windowSizeBuffer) != 0) {
return DEFAULT_WIDTH;
}
return (short) (0x000000FF & this.windowSizeBuffer.get(2) + (0x000000FF & this.windowSizeBuffer.get(3)) * 256);
}
/**
* Returns number of rows in the terminal.
*/
@Override
public final int getHeight() {
if (this.term.ioctl(0, PosixTerminalControl.TIOCGWINSZ, this.windowSizeBuffer) != 0) {
return DEFAULT_HEIGHT;
}
return (short) (0x000000FF & this.windowSizeBuffer.get(0) + (0x000000FF & this.windowSizeBuffer.get(1)) * 256);
}
@Override
public final synchronized void setEchoEnabled(final boolean enabled) {
// have to reget them, since flags change everything
if (this.term.tcgetattr(0, this.termInfo) != 0) {
this.logger.error("Failed to get terminal info");
}
if (enabled) {
this.termInfo.c_lflag |= PosixTerminalControl.ECHO; // ENABLE Echo input characters.
} else {
this.termInfo.c_lflag &= ~PosixTerminalControl.ECHO; // DISABLE Echo input characters.
}
if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfo) != 0) {
this.logger.error("Can not set terminal flags");
}
super.setEchoEnabled(enabled);
}
// public final void disableInterruptCharacter() {
// // have to re-get them, since flags change everything
// if (this.term.tcgetattr(0, this.termInfo) !=0) {
// this.logger.error("Failed to get terminal info");
// }
//
// this.termInfo.c_cc[PosixTerminalControl.VINTR] = 0; // interrupt disabled
//
// if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfo) != 0) {
// this.logger.error("Can not set terminal flags");
// }
// }
//
// public final void enableInterruptCharacter() {
// // have to re-get them, since flags change everything
// if (this.term.tcgetattr(0, this.termInfo) !=0) {
// this.logger.error("Failed to get terminal info");
// }
//
// this.termInfo.c_cc[PosixTerminalControl.VINTR] = 3; // interrupt is ctrl-c
//
// if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfo) != 0) {
// this.logger.error("Can not set terminal flags");
// }
// }
@Override
public final int read() {
try {
return this.reader.read();
} catch (IOException ignored) {
return -1;
}
}
}

View File

@ -1,124 +0,0 @@
/*
* Copyright (c) 2002-2012, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* http://www.opensource.org/licenses/bsd-license.php
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
*/
package dorkbox.inputConsole.windows;
import java.io.IOException;
import java.io.PrintStream;
import dorkbox.inputConsole.Terminal;
import org.fusesource.jansi.internal.Kernel32.INPUT_RECORD;
import org.fusesource.jansi.internal.Kernel32.KEY_EVENT_RECORD;
import org.fusesource.jansi.internal.WindowsSupport;
/**
* Terminal implementation for Microsoft Windows. Terminal initialization in {@link #init} is accomplished by calling the Win32 APIs <a
* href="http://msdn.microsoft.com/library/default.asp? url=/library/en-us/dllproc/base/setconsolemode.asp">SetConsoleMode</a> and <a
* href="http://msdn.microsoft.com/library/default.asp? url=/library/en-us/dllproc/base/getconsolemode.asp">GetConsoleMode</a> to disable
* character echoing.
* <p/>
*
* @since 2.0 (customized)
*/
public class WindowsTerminal extends Terminal {
private volatile int originalMode;
private final PrintStream out;
public WindowsTerminal() {
this.out = System.out;
}
@Override
public final void init() throws IOException {
this.originalMode = WindowsSupport.getConsoleMode();
// Must set these four modes at the same time to make it work fine.
WindowsSupport.setConsoleMode(this.originalMode |
ConsoleMode.ENABLE_LINE_INPUT.code |
ConsoleMode.ENABLE_ECHO_INPUT.code |
ConsoleMode.ENABLE_PROCESSED_INPUT.code |
ConsoleMode.ENABLE_WINDOW_INPUT.code);
}
/**
* Restore the original terminal configuration, which can be used when shutting down the console reader. The ConsoleReader cannot be
* used after calling this method.
*/
@Override
public final void restore() throws IOException {
// restore the old console mode
WindowsSupport.setConsoleMode(this.originalMode);
}
@Override
public final int getWidth() {
int w = WindowsSupport.getWindowsTerminalWidth();
return w < 1 ? DEFAULT_WIDTH : w;
}
@Override
public final int getHeight() {
int h = WindowsSupport.getWindowsTerminalHeight();
return h < 1 ? DEFAULT_HEIGHT : h;
}
@Override
public final int read() {
int input = readInput();
if (isEchoEnabled()) {
char asChar = (char) input;
if (asChar == '\n') {
this.out.println();
} else {
this.out.print(asChar);
}
// have to flush, otherwise we'll never see the chars on screen
this.out.flush();
}
return input;
}
private int readInput() {
// this HOOKS the input event, and prevents it from going to the console "proper"
try {
INPUT_RECORD[] events;
while (true) {
// we ALWAYS read until we have an event we care about!
events = WindowsSupport.readConsoleInput(1);
if (events != null) {
for (int i = 0; i < events.length; i++) {
KEY_EVENT_RECORD keyEvent = events[i].keyEvent;
// Log.trace(keyEvent.keyDown? "KEY_DOWN" : "KEY_UP", "key code:", keyEvent.keyCode, "char:", (long)keyEvent.uchar);
if (keyEvent.keyDown) {
if (keyEvent.uchar > 0) {
char uchar = keyEvent.uchar;
if (uchar == '\r') {
// we purposefully swallow input after \r, and substitute it with \n
return '\n';
}
return uchar;
}
}
}
}
}
} catch (IOException e) {
this.logger.error("Windows console input error: ", e);
}
return -1;
}
}

View File

@ -0,0 +1,50 @@
/**
* Copyright (C) 2009, Progress Software Corporation and/or its
* subsidiaries or affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a asValue of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.dorkbox.console;
import java.io.FileInputStream;
import java.io.IOException;
import dorkbox.console.output.Ansi;
/**
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class AnsiConsoleExample {
private AnsiConsoleExample() {}
public static void main(String[] args) throws IOException {
String file = "jansi.ans";
if( args.length>0 )
file = args[0];
// Allows us to disable ANSI processing.
if( "true".equals(System.getProperty("jansi", "true")) ) {
Ansi.systemInstall();
}
FileInputStream f = new FileInputStream(file);
int c;
while( (c=f.read())>=0 ) {
System.out.write(c);
}
f.close();
}
}

View File

@ -0,0 +1,55 @@
/**
* Copyright (C) 2009, Progress Software Corporation and/or its
* subsidiaries or affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a asValue of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.dorkbox.console;
import static dorkbox.console.output.Ansi.ansi;
import java.io.FileInputStream;
import java.io.IOException;
import dorkbox.console.output.Ansi;
/**
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class AnsiConsoleExample2 {
private AnsiConsoleExample2() {}
public static void main(String[] args) throws IOException {
String file = "jansi.ans";
if( args.length>0 )
file = args[0];
// Allows us to disable ANSI processing.
if( "true".equals(System.getProperty("jansi", "true")) ) {
Ansi.systemInstall();
}
System.out.print(ansi().reset().eraseScreen().cursor(1, 1));
System.out.print("=======================================================================");
FileInputStream f = new FileInputStream(file);
int c;
while( (c=f.read())>=0 ) {
System.out.write(c);
}
f.close();
System.out.println("=======================================================================");
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright (C) 2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.dorkbox.console;
import static org.junit.Assert.assertEquals;
import java.io.ByteArrayOutputStream;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import dorkbox.console.output.AnsiRenderWriter;
/**
* Tests for the {@link AnsiRenderWriter} class.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
*/
public class AnsiRenderWriterTest
{
private ByteArrayOutputStream baos;
private AnsiRenderWriter out;
@Before
public void setUp() {
baos = new ByteArrayOutputStream();
out = new AnsiRenderWriter(baos);
}
@After
public void tearDown() {
out = null;
baos = null;
}
@Test
public void testRenderNothing() {
out.print("foo");
out.flush();
String result = new String(baos.toByteArray());
assertEquals("foo", result);
}
}

View File

@ -0,0 +1,116 @@
/*
* Copyright (C) 2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.dorkbox.console;
import static dorkbox.console.output.AnsiRenderer.render;
import static java.awt.Font.BOLD;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import dorkbox.console.output.Ansi;
import dorkbox.console.output.AnsiRenderer;
import dorkbox.console.output.Color;
/**
* Tests for the {@link AnsiRenderer} class.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
*/
public class AnsiRendererTest
{
public static
boolean test(final String text) {
return text != null && text.contains(AnsiRenderer.BEGIN_TOKEN);
}
@Before
public void setUp() {
Ansi.setEnabled(true);
}
@Test
public void testTest() throws Exception {
assertFalse(test("foo"));
assertTrue(test("@|foo|"));
assertTrue(test("@|foo"));
}
@Test
public void testRender() {
String str = render("@|bold foo|@");
System.out.println(str);
assertEquals(Ansi.ansi().a(BOLD).a("foo").reset().toString(), str);
assertEquals(Ansi.ansi().bold().a("foo").reset().toString(), str);
}
@Test
public void testRender2() {
String str = render("@|bold,red foo|@");
System.out.println(str);
assertEquals(Ansi.ansi().a(BOLD).fg(Color.RED).a("foo").reset().toString(), str);
assertEquals(Ansi.ansi().bold().fgRed().a("foo").reset().toString(), str);
}
@Test
public void testRender3() {
String str = render("@|bold,red foo bar baz|@");
System.out.println(str);
assertEquals(Ansi.ansi().a(BOLD).fg(Color.RED).a("foo bar baz").reset().toString(), str);
assertEquals(Ansi.ansi().bold().fgRed().a("foo bar baz").reset().toString(), str);
}
@Test
public void testRender4() {
String str = render("@|bold,red foo bar baz|@ ick @|bold,red foo bar baz|@");
System.out.println(str);
assertEquals(Ansi.ansi()
.a(BOLD).fg(Color.RED).a("foo bar baz").reset()
.a(" ick ")
.a(BOLD).fg(Color.RED).a("foo bar baz").reset()
.toString(), str);
}
@Test
public void testRender5() {
// Check the ansi() render method.
String str = Ansi.ansi().render("@|bold Hello|@").toString();
System.out.println(str);
assertEquals(Ansi.ansi().a(BOLD).a("Hello").reset().toString(), str);
}
@Test
public void testRenderNothing() {
assertEquals("foo", render("foo"));
}
@Test
public void testRenderInvalidMissingEnd() {
String str = render("@|bold foo");
assertEquals("@|bold foo", str);
}
@Test
public void testRenderInvalidMissingText() {
String str = render("@|bold|@");
assertEquals("@|bold|@", str);
}
}

View File

@ -0,0 +1,172 @@
package com.dorkbox.console;
import static dorkbox.console.output.Ansi.ansi;
import java.io.IOException;
import dorkbox.console.Console;
import dorkbox.console.output.Ansi;
import dorkbox.console.output.Erase;
/**
* System output for visual confirmation of ANSI codes.
* <p>
* Must enable assertions to verify no errors!! (ie: java -ea -jar blah.jar)
*/
public
class AnsiRendererTestExample {
public static
void main(String[] args) throws IOException {
Ansi.systemInstall();
Ansi.setEnabled(true);
System.out.println(ansi()
.fgBlack().a("black").bgBlack().a("black")
.reset()
.fgBrightBlack().a("b-black").bgBrightBlack().a("b-black")
.reset());
System.out.println(ansi()
.fgBlue().a("blue").bgBlue().a("blue")
.reset()
.fgBrightBlue().a("b-blue").bgBrightBlue().a("b-blue")
.reset());
System.out.println(ansi()
.fgCyan().a("cyan").bgCyan().a("cyan")
.reset()
.fgBrightCyan().a("b-cyan").bgBrightCyan().a("b-cyan")
.reset());
System.out.println(ansi()
.fgGreen().a("green").bgGreen().a("green")
.reset()
.fgBrightGreen().a("b-green").bgBrightGreen().a("b-green")
.reset());
System.out.println(ansi()
.fgMagenta().a("magenta").bgMagenta().a("magenta")
.reset()
.fgBrightMagenta().a("b-magenta").bgBrightMagenta().a("b-magenta")
.reset());
System.out.println(ansi()
.fgRed().a("red").bgRed().a("red")
.reset()
.fgBrightRed().a("b-red").bgBrightRed().a("b-red")
.reset());
System.out.println(ansi()
.fgYellow().a("yellow").bgYellow().a("yellow")
.reset()
.fgBrightYellow().a("b-yellow").bgBrightYellow().a("b-yellow")
.reset());
System.out.println(ansi()
.fgWhite().a("white").bgWhite().a("white")
.reset()
.fgBrightWhite().a("b-white").bgBrightWhite().a("b-white")
.reset());
System.out.println(ansi()); // reset the ansi stream. Can ALSO have ansi().reset(), but that would be redundant
System.out.println("The following line should be blank except for the first '>'");
System.out.println(ansi()
.a(">THIS SHOULD BE BLANK")
.cursorToColumn(2)
.eraseLine());
System.out.println("The following line should be blank");
System.out.println(ansi()
.a(">THIS SHOULD BE BLANK")
.eraseLine(Erase.ALL));
System.out.println(ansi()
.a(">THIS SHOULD BE BLANK")
.eraseLine(Erase.BACKWARD)
.a("Everything before this should be blank"));
System.out.println(ansi()
.a("Everything after this should be blank")
.saveCursorPosition()
.a(">THIS SHOULD BE BLANK")
.restoreCursorPosition()
.eraseLine(Erase.FORWARD));
System.out.println("00000000000000000000000000");
System.out.println("00000000000000000000000000");
System.out.println("00000000000000000000000000");
System.out.println("00000000000000000000000000");
System.out.println("00000000000000000000000000");
System.out.println(ansi()
.a("Should have two blank spots in the above 0's")
.saveCursorPosition()
.cursorUp(4)
.cursorLeft(30)
.a(" ")
.cursorDownLine()
.cursorRight(5)
.a(" ")
.restoreCursorPosition());
// verify the output renderer
// String str = render("@|bold foo|@foo");
// System.out.println(str);
// assertEquals(ansi().a(BOLD).a("foo").reset().a("foo").toString(), str);
// assertEquals(ansi().bold().a("foo").reset().a("foo").toString(), str);
//
//
// str = render("@|bold,red foo|@");
// System.out.println(str);
// assertEquals(ansi().a(BOLD).fg(RED).a("foo").reset().toString(), str);
// assertEquals(ansi().bold().fgRed().a("foo").reset().toString(), str);
//
// str = render("@|bold,red foo bar baz|@");
// System.out.println(str);
// assertEquals(ansi().a(BOLD).fg(RED).a("foo bar baz").reset().toString(), str);
// assertEquals(ansi().bold().fgRed().a("foo bar baz").reset().toString(), str);
//
//
// str = render("@|bold,red foo bar baz|@ ick @|bold,red foo bar baz|@");
// System.out.println(str);
// String expected = ansi().a(BOLD)
// .fg(RED)
// .a("foo bar baz")
// .reset()
// .a(" ick ")
// .a(BOLD)
// .fg(RED)
// .a("foo bar baz")
// .reset()
// .toString();
//
// assertEquals(expected, str);
//
//
// str = render("@|bold foo"); // shouldn't work
// System.err.println(str + " <- shouldn't work");
//
// str = render("@|bold|@"); // shouldn't work
// System.err.println(str + " <- shouldn't work");
//
// Ansi.systemUninstall();
//
// str = render("@|bold foo|@foo");
// System.out.println(str + " <- shouldn't work");
System.err.println("ver : " + Console.getVersion());
System.out.println("Now testing the input console. '?' to quit");
Console.ENABLE_ECHO = true;
int read;
while ((read = Console.in.read()) != '?') {
// System.err.println("READ :" + read + " (" + (char) read + ")");
}
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.dorkbox.console;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import dorkbox.console.output.Ansi;
import dorkbox.console.output.AnsiString;
import dorkbox.console.output.Attribute;
/**
* Tests for {@link AnsiString}.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
*/
public class AnsiStringTest
{
@Test
public void testNotEncoded() throws Exception {
AnsiString as = new AnsiString("foo");
assertEquals("foo", as.getEncoded());
assertEquals("foo", as.getPlain());
assertEquals(3, as.length());
}
@Test
public void testEncoded() throws Exception {
AnsiString as = new AnsiString(Ansi.ansi().a(Attribute.BOLD).a("foo").reset().toString());
assertEquals("foo", as.getPlain());
assertEquals(3, as.length());
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2009 the original author(s).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.dorkbox.console;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import dorkbox.console.output.Ansi;
import dorkbox.console.output.Color;
/**
* Tests for the {@link Ansi} class.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
*/
public class AnsiTest
{
@Test
public void testSetEnabled() throws Exception {
Ansi.setEnabled(false);
new Thread()
{
@Override
public void run() {
assertEquals(false, Ansi.isEnabled());
}
}.run();
Ansi.setEnabled(true);
new Thread()
{
@Override
public void run() {
assertEquals(true, Ansi.isEnabled());
}
}.run();
}
@Test
public void testClone() throws CloneNotSupportedException {
Ansi ansi = Ansi.ansi().a("Some text").bg(Color.BLACK).fg(Color.WHITE);
Ansi clone = new Ansi(ansi);
assertEquals(ansi.a("test").reset().toString(), clone.a("test").reset().toString());
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright (C) 2009 the original author(s).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.dorkbox.console;
import static org.junit.Assert.assertEquals;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.junit.Test;
import dorkbox.console.output.HtmlAnsiOutputStream;
/**
* @author <a href="http://code.dblock.org">Daniel Doubrovkine</a>
*/
public class HtmlAnsiOutputStreamTest {
@Test
public void testNoMarkup() throws IOException {
assertEquals("line", colorize("line"));
}
@Test
public void testClear() throws IOException {
assertEquals("", colorize(""));
assertEquals("hello world", colorize("hello world"));
}
@Test
public void testBold() throws IOException {
assertEquals("<b>hello world</b>", colorize("hello world"));
}
@Test
public void testGreen() throws IOException {
assertEquals("<span style=\"color: green;\">hello world</span>",
colorize("hello world"));
}
@Test
public void testGreenOnWhite() throws IOException {
assertEquals("<span style=\"background-color: white;\"><span style=\"color: green;\">hello world</span></span>",
colorize("hello world"));
}
@Test
public void testEscapeHtml() throws IOException {
assertEquals("&quot;", colorize("\""));
assertEquals("&amp;", colorize("&"));
assertEquals("&lt;", colorize("<"));
assertEquals("&gt;", colorize(">"));
assertEquals("&quot;&amp;&lt;&gt;", colorize("\"&<>"));
}
@Test
public void testResetOnOpen() throws IOException {
assertEquals("<span style=\"color: red;\">red</span>",
colorize("red"));
}
@Test
public void testUTF8Character() throws IOException {
assertEquals("<b>\u3053\u3093\u306b\u3061\u306f</b>",
colorize("\u3053\u3093\u306b\u3061\u306f"));
}
private String colorize(String text) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
HtmlAnsiOutputStream hos = new HtmlAnsiOutputStream(os);
hos.write(text.getBytes("UTF-8"));
hos.close();
return new String(os.toByteArray(), "UTF-8");
}
}

View File

@ -0,0 +1,8 @@
[?7h
レトトソレトトトトトソ レトトトトトソ レトトトトトトツトトソ
ウロロテルロロロロロタツルロロロロロタツルロロロロロロヴンウ
レトトソ ウロロウロロワワワロロウロロレトソロロウロロワワワワ ウワワウ
ウアアタトルアロウアロレトソアロウアロウ ウアロウ ゚゚゚゚アロウアロウ
タソイイイイイレエイイウ ウイイウイイウ ウイイウ゚イイイイイ゚ウイイウ
タトトトトトルタトトル タトトチトトル タトトチトトトトトトトチトトル