8080679: Include jline in JDK for Java and JavaScript REPLs

Reviewed-by: alanb, erikj, forax, iris, sundar
This commit is contained in:
Jan Lahoda 2015-07-07 13:17:53 +02:00
parent aa2d62b688
commit 581470a6d1
50 changed files with 10369 additions and 0 deletions

@ -0,0 +1,60 @@
#
# Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 only, as
# published by the Free Software Foundation. Oracle designates this
# particular file as subject to the "Classpath" exception as provided
# by Oracle in the LICENSE file that accompanied this code.
#
# This code 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 General Public License
# version 2 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 2 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
# or visit www.oracle.com if you need additional information or have any
# questions.
#
include LibCommon.gmk
################################################################################
ifeq ($(OPENJDK_TARGET_OS), windows)
LIBLE_SRC := $(JDK_TOPDIR)/src/jdk.internal.le/$(OPENJDK_TARGET_OS_TYPE)/native/lible \
#
LIBLE_CPPFLAGS := \
$(addprefix -I, $(LIBLE_SRC)) \
-I$(SUPPORT_OUTPUTDIR)/headers/jdk.internal.le \
#
$(eval $(call SetupNativeCompilation,BUILD_LIBLE, \
LIBRARY := le, \
OUTPUT_DIR := $(INSTALL_LIBRARIES_HERE), \
SRC := $(LIBLE_SRC), \
OPTIMIZATION := LOW, \
CFLAGS := $(CFLAGS_JDKLIB) $(LIBJAVA_HEADER_FLAGS)\
$(LIBLE_CPPFLAGS), \
LDFLAGS := $(LDFLAGS_JDKLIB), \
LDFLAGS_SUFFIX := $(LDFLAGS_JDKLIB_SUFFIX) user32.lib, \
VERSIONINFO_RESOURCE := $(GLOBAL_VERSION_INFO_RESOURCE), \
RC_FLAGS := $(RC_FLAGS) \
-D "JDK_FNAME=le.dll" \
-D "JDK_INTERNAL_NAME=le" \
-D "JDK_FTYPE=0x2L", \
OBJECT_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/lible, \
DEBUG_SYMBOLS := $(DEBUG_ALL_BINARIES)))
TARGETS += $(BUILD_LIBLE)
endif # OPENJDK_TARGET_OS
################################################################################

@ -0,0 +1,36 @@
/*
* 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
*/
package jdk.internal.jline;
// Based on Apache Karaf impl
/**
* Non-interruptible (via CTRL-C) {@link UnixTerminal}.
*
* @since 2.0
*/
public class NoInterruptUnixTerminal
extends UnixTerminal
{
public NoInterruptUnixTerminal() throws Exception {
super();
}
@Override
public void init() throws Exception {
super.init();
getSettings().set("intr undef");
}
@Override
public void restore() throws Exception {
getSettings().set("intr ^C");
super.restore();
}
}

@ -0,0 +1,65 @@
/*
* 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
*/
package jdk.internal.jline;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Representation of the input terminal for a platform.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.0
*/
public interface Terminal
{
void init() throws Exception;
void restore() throws Exception;
void reset() throws Exception;
boolean isSupported();
int getWidth();
int getHeight();
boolean isAnsiSupported();
/**
* When ANSI is not natively handled, the output will have to be wrapped.
*/
OutputStream wrapOutIfNeeded(OutputStream out);
/**
* When using native support, return the InputStream to use for reading characters
* else return the input stream passed as a parameter.
*
* @since 2.6
*/
InputStream wrapInIfNeeded(InputStream in) throws IOException;
/**
* For terminals that don't wrap when character is written in last column,
* only when the next character is written.
* These are the ones that have 'am' and 'xn' termcap attributes (xterm and
* rxvt flavors falls under that category)
*/
boolean hasWeirdWrap();
boolean isEchoEnabled();
void setEchoEnabled(boolean enabled);
String getOutputEncoding();
}

@ -0,0 +1,173 @@
/*
* 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
*/
package jdk.internal.jline;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import jdk.internal.jline.internal.Configuration;
import jdk.internal.jline.internal.Log;
import jdk.internal.jline.internal.Preconditions;
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
/**
* Creates terminal instances.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.0
*/
public class TerminalFactory
{
public static final String JLINE_TERMINAL = "jline.terminal";
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";
private static Terminal term = null;
public static synchronized Terminal create() {
if (Log.TRACE) {
//noinspection ThrowableInstanceNeverThrown
Log.trace(new Throwable("CREATE MARKER"));
}
String type = Configuration.getString(JLINE_TERMINAL, AUTO);
if ("dumb".equals(System.getenv("TERM"))) {
type = "none";
Log.debug("$TERM=dumb; setting type=", type);
}
Log.debug("Creating terminal; type=", type);
Terminal t;
try {
String tmp = type.toLowerCase();
if (tmp.equals(UNIX)) {
t = getFlavor(Flavor.UNIX);
}
else if (tmp.equals(WIN) | tmp.equals(WINDOWS)) {
t = getFlavor(Flavor.WINDOWS);
}
else if (tmp.equals(NONE) || tmp.equals(OFF) || tmp.equals(FALSE)) {
t = new UnsupportedTerminal();
}
else {
if (tmp.equals(AUTO)) {
String os = Configuration.getOsName();
Flavor flavor = Flavor.UNIX;
if (os.contains(WINDOWS)) {
flavor = Flavor.WINDOWS;
}
t = getFlavor(flavor);
}
else {
try {
t = (Terminal) Thread.currentThread().getContextClassLoader().loadClass(type).newInstance();
}
catch (Exception e) {
throw new IllegalArgumentException(MessageFormat.format("Invalid terminal type: {0}", type), e);
}
}
}
}
catch (Exception e) {
Log.error("Failed to construct terminal; falling back to unsupported", e);
t = new UnsupportedTerminal();
}
Log.debug("Created Terminal: ", t);
try {
t.init();
}
catch (Throwable e) {
Log.error("Terminal initialization failed; falling back to unsupported", e);
return new UnsupportedTerminal();
}
return t;
}
public static synchronized void reset() {
term = null;
}
public static synchronized void resetIf(final Terminal t) {
if(t == term) {
reset();
}
}
public static enum Type
{
AUTO,
WINDOWS,
UNIX,
NONE
}
public static synchronized void configure(final String type) {
checkNotNull(type);
System.setProperty(JLINE_TERMINAL, type);
}
public static synchronized void configure(final Type type) {
checkNotNull(type);
configure(type.name().toLowerCase());
}
//
// Flavor Support
//
public static enum Flavor
{
WINDOWS,
UNIX
}
private static final Map<Flavor, Callable<? extends Terminal>> FLAVORS = new HashMap<>();
static {
// registerFlavor(Flavor.WINDOWS, AnsiWindowsTerminal.class);
// registerFlavor(Flavor.UNIX, UnixTerminal.class);
registerFlavor(Flavor.WINDOWS, WindowsTerminal :: new);
registerFlavor(Flavor.UNIX, UnixTerminal :: new);
}
public static synchronized Terminal get() {
if (term == null) {
term = create();
}
return term;
}
public static Terminal getFlavor(final Flavor flavor) throws Exception {
return FLAVORS.getOrDefault(flavor, () -> {throw new InternalError();}).call();
}
public static void registerFlavor(final Flavor flavor, final Callable<? extends Terminal> sup) {
FLAVORS.put(flavor, sup);
}
}

@ -0,0 +1,123 @@
/*
* 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
*/
package jdk.internal.jline;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import jdk.internal.jline.internal.Log;
import jdk.internal.jline.internal.ShutdownHooks;
import jdk.internal.jline.internal.ShutdownHooks.Task;
/**
* Provides support for {@link Terminal} instances.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.0
*/
public abstract class TerminalSupport
implements Terminal
{
public static final int DEFAULT_WIDTH = 80;
public static final int DEFAULT_HEIGHT = 24;
private Task shutdownTask;
private boolean supported;
private boolean echoEnabled;
private boolean ansiSupported;
protected TerminalSupport(final boolean supported) {
this.supported = supported;
}
public void init() throws Exception {
if (shutdownTask != null) {
ShutdownHooks.remove(shutdownTask);
}
// Register a task to restore the terminal on shutdown
this.shutdownTask = ShutdownHooks.add(new Task()
{
public void run() throws Exception {
restore();
}
});
}
public void restore() throws Exception {
TerminalFactory.resetIf(this);
if (shutdownTask != null) {
ShutdownHooks.remove(shutdownTask);
shutdownTask = null;
}
}
public void reset() throws Exception {
restore();
init();
}
public final boolean isSupported() {
return supported;
}
public synchronized boolean isAnsiSupported() {
return ansiSupported;
}
protected synchronized void setAnsiSupported(final boolean supported) {
this.ansiSupported = supported;
Log.debug("Ansi supported: ", supported);
}
/**
* Subclass to change behavior if needed.
* @return the passed out
*/
public OutputStream wrapOutIfNeeded(OutputStream out) {
return out;
}
/**
* Defaults to true which was the behaviour before this method was added.
*/
public boolean hasWeirdWrap() {
return true;
}
public int getWidth() {
return DEFAULT_WIDTH;
}
public int getHeight() {
return DEFAULT_HEIGHT;
}
public synchronized boolean isEchoEnabled() {
return echoEnabled;
}
public synchronized void setEchoEnabled(final boolean enabled) {
this.echoEnabled = enabled;
Log.debug("Echo enabled: ", enabled);
}
public InputStream wrapInIfNeeded(InputStream in) throws IOException {
return in;
}
public String getOutputEncoding() {
// null for unknown
return null;
}
}

@ -0,0 +1,133 @@
/*
* 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
*/
package jdk.internal.jline;
import jdk.internal.jline.internal.Log;
import jdk.internal.jline.internal.TerminalLineSettings;
/**
* Terminal that is used for unix platforms. Terminal initialization
* is handled by issuing the <em>stty</em> command against the
* <em>/dev/tty</em> file to disable character echoing and enable
* character input. All known unix systems (including
* Linux and Macintosh OS X) support the <em>stty</em>), so this
* implementation should work for an reasonable POSIX system.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @author <a href="mailto:dwkemp@gmail.com">Dale Kemp</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @author <a href="mailto:jbonofre@apache.org">Jean-Baptiste Onofr\u00E9</a>
* @since 2.0
*/
public class UnixTerminal
extends TerminalSupport
{
private final TerminalLineSettings settings = new TerminalLineSettings();
public UnixTerminal() throws Exception {
super(true);
}
protected TerminalLineSettings getSettings() {
return settings;
}
/**
* Remove line-buffered input by invoking "stty -icanon min 1"
* against the current terminal.
*/
@Override
public void init() throws Exception {
super.init();
setAnsiSupported(true);
// Set the console to be character-buffered instead of line-buffered.
// Make sure we're distinguishing carriage return from newline.
// Allow ctrl-s keypress to be used (as forward search)
settings.set("-icanon min 1 -icrnl -inlcr -ixon");
settings.set("dsusp undef");
setEchoEnabled(false);
}
/**
* 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 void restore() throws Exception {
settings.restore();
super.restore();
}
/**
* Returns the value of <tt>stty columns</tt> param.
*/
@Override
public int getWidth() {
int w = settings.getProperty("columns");
return w < 1 ? DEFAULT_WIDTH : w;
}
/**
* Returns the value of <tt>stty rows>/tt> param.
*/
@Override
public int getHeight() {
int h = settings.getProperty("rows");
return h < 1 ? DEFAULT_HEIGHT : h;
}
@Override
public synchronized void setEchoEnabled(final boolean enabled) {
try {
if (enabled) {
settings.set("echo");
}
else {
settings.set("-echo");
}
super.setEchoEnabled(enabled);
}
catch (Exception e) {
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
Log.error("Failed to ", (enabled ? "enable" : "disable"), " echo", e);
}
}
public void disableInterruptCharacter()
{
try {
settings.set("intr undef");
}
catch (Exception e) {
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
Log.error("Failed to disable interrupt character", e);
}
}
public void enableInterruptCharacter()
{
try {
settings.set("intr ^C");
}
catch (Exception e) {
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
Log.error("Failed to enable interrupt character", e);
}
}
}

@ -0,0 +1,26 @@
/*
* 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
*/
package jdk.internal.jline;
/**
* An unsupported terminal.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.0
*/
public class UnsupportedTerminal
extends TerminalSupport
{
public UnsupportedTerminal() {
super(false);
setAnsiSupported(false);
setEchoEnabled(true);
}
}

@ -0,0 +1,546 @@
/*
* 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
*/
package jdk.internal.jline;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import jdk.internal.jline.internal.Configuration;
import jdk.internal.jline.internal.Log;
//import org.fusesource.jansi.internal.WindowsSupport;
//import org.fusesource.jansi.internal.Kernel32;
//import static org.fusesource.jansi.internal.Kernel32.*;
import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_ECHO_INPUT;
import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_LINE_INPUT;
import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_PROCESSED_INPUT;
import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_WINDOW_INPUT;
/**
* Terminal implementation for Microsoft Windows. Terminal initialization in
* {@link #init} is accomplished by extracting the
* <em>jline_<i>version</i>.dll</em>, saving it to the system temporary
* directoy (determined by the setting of the <em>java.io.tmpdir</em> System
* property), loading the library, and then 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/>
* <p>
* By default, the {@link #wrapInIfNeeded(java.io.InputStream)} method will attempt
* to test to see if the specified {@link InputStream} is {@link System#in} or a wrapper
* around {@link FileDescriptor#in}, and if so, will bypass the character reading to
* directly invoke the readc() method in the JNI library. This is so the class
* can read special keys (like arrow keys) which are otherwise inaccessible via
* the {@link System#in} stream. Using JNI reading can be bypassed by setting
* the <code>jline.WindowsTerminal.directConsole</code> system property
* to <code>false</code>.
* </p>
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.0
*/
public class WindowsTerminal
extends TerminalSupport
{
public static final String DIRECT_CONSOLE = WindowsTerminal.class.getName() + ".directConsole";
public static final String ANSI = WindowsTerminal.class.getName() + ".ansi";
private boolean directConsole;
private int originalMode;
public WindowsTerminal() throws Exception {
super(true);
}
@Override
public void init() throws Exception {
super.init();
// setAnsiSupported(Configuration.getBoolean(ANSI, true));
setAnsiSupported(false);
//
// FIXME: Need a way to disable direct console and sysin detection muck
//
setDirectConsole(Configuration.getBoolean(DIRECT_CONSOLE, true));
this.originalMode = getConsoleMode();
setConsoleMode(originalMode & ~ENABLE_ECHO_INPUT.code);
setEchoEnabled(false);
}
/**
* 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 void restore() throws Exception {
// restore the old console mode
setConsoleMode(originalMode);
super.restore();
}
@Override
public int getWidth() {
int w = getWindowsTerminalWidth();
return w < 1 ? DEFAULT_WIDTH : w;
}
@Override
public int getHeight() {
int h = getWindowsTerminalHeight();
return h < 1 ? DEFAULT_HEIGHT : h;
}
@Override
public void setEchoEnabled(final boolean enabled) {
// Must set these four modes at the same time to make it work fine.
if (enabled) {
setConsoleMode(getConsoleMode() |
ENABLE_ECHO_INPUT.code |
ENABLE_LINE_INPUT.code |
ENABLE_PROCESSED_INPUT.code |
ENABLE_WINDOW_INPUT.code);
}
else {
setConsoleMode(getConsoleMode() &
~(ENABLE_LINE_INPUT.code |
ENABLE_ECHO_INPUT.code |
ENABLE_PROCESSED_INPUT.code |
ENABLE_WINDOW_INPUT.code));
}
super.setEchoEnabled(enabled);
}
/**
* Whether or not to allow the use of the JNI console interaction.
*/
public void setDirectConsole(final boolean flag) {
this.directConsole = flag;
Log.debug("Direct console: ", flag);
}
/**
* Whether or not to allow the use of the JNI console interaction.
*/
public Boolean getDirectConsole() {
return directConsole;
}
@Override
public InputStream wrapInIfNeeded(InputStream in) throws IOException {
if (directConsole && isSystemIn(in)) {
return new InputStream() {
private byte[] buf = null;
int bufIdx = 0;
@Override
public int read() throws IOException {
while (buf == null || bufIdx == buf.length) {
buf = readConsoleInput();
bufIdx = 0;
}
int c = buf[bufIdx] & 0xFF;
bufIdx++;
return c;
}
};
} else {
return super.wrapInIfNeeded(in);
}
}
protected boolean isSystemIn(final InputStream in) throws IOException {
if (in == null) {
return false;
}
else if (in == System.in) {
return true;
}
else if (in instanceof FileInputStream && ((FileInputStream) in).getFD() == FileDescriptor.in) {
return true;
}
return false;
}
@Override
public String getOutputEncoding() {
int codepage = getConsoleOutputCodepage();
//http://docs.oracle.com/javase/6/docs/technotes/guides/intl/encoding.doc.html
String charsetMS = "ms" + codepage;
if (java.nio.charset.Charset.isSupported(charsetMS)) {
return charsetMS;
}
String charsetCP = "cp" + codepage;
if (java.nio.charset.Charset.isSupported(charsetCP)) {
return charsetCP;
}
Log.debug("can't figure out the Java Charset of this code page (" + codepage + ")...");
return super.getOutputEncoding();
}
//
// Original code:
//
// private int getConsoleMode() {
// return WindowsSupport.getConsoleMode();
// }
//
// private void setConsoleMode(int mode) {
// WindowsSupport.setConsoleMode(mode);
// }
//
// private byte[] readConsoleInput() {
// // XXX does how many events to read in one call matter?
// INPUT_RECORD[] events = null;
// try {
// events = WindowsSupport.readConsoleInput(1);
// } catch (IOException e) {
// Log.debug("read Windows console input error: ", e);
// }
// if (events == null) {
// return new byte[0];
// }
// StringBuilder sb = new StringBuilder();
// 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) {
// // support some C1 control sequences: ALT + [@-_] (and [a-z]?) => ESC <ascii>
// // http://en.wikipedia.org/wiki/C0_and_C1_control_codes#C1_set
// final int altState = KEY_EVENT_RECORD.LEFT_ALT_PRESSED | KEY_EVENT_RECORD.RIGHT_ALT_PRESSED;
// // Pressing "Alt Gr" is translated to Alt-Ctrl, hence it has to be checked that Ctrl is _not_ pressed,
// // otherwise inserting of "Alt Gr" codes on non-US keyboards would yield errors
// final int ctrlState = KEY_EVENT_RECORD.LEFT_CTRL_PRESSED | KEY_EVENT_RECORD.RIGHT_CTRL_PRESSED;
// if (((keyEvent.uchar >= '@' && keyEvent.uchar <= '_') || (keyEvent.uchar >= 'a' && keyEvent.uchar <= 'z'))
// && ((keyEvent.controlKeyState & altState) != 0) && ((keyEvent.controlKeyState & ctrlState) == 0)) {
// sb.append('\u001B'); // ESC
// }
//
// sb.append(keyEvent.uchar);
// continue;
// }
// // virtual keycodes: http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
// // just add support for basic editing keys (no control state, no numpad keys)
// String escapeSequence = null;
// switch (keyEvent.keyCode) {
// case 0x21: // VK_PRIOR PageUp
// escapeSequence = "\u001B[5~";
// break;
// case 0x22: // VK_NEXT PageDown
// escapeSequence = "\u001B[6~";
// break;
// case 0x23: // VK_END
// escapeSequence = "\u001B[4~";
// break;
// case 0x24: // VK_HOME
// escapeSequence = "\u001B[1~";
// break;
// case 0x25: // VK_LEFT
// escapeSequence = "\u001B[D";
// break;
// case 0x26: // VK_UP
// escapeSequence = "\u001B[A";
// break;
// case 0x27: // VK_RIGHT
// escapeSequence = "\u001B[C";
// break;
// case 0x28: // VK_DOWN
// escapeSequence = "\u001B[B";
// break;
// case 0x2D: // VK_INSERT
// escapeSequence = "\u001B[2~";
// break;
// case 0x2E: // VK_DELETE
// escapeSequence = "\u001B[3~";
// break;
// default:
// break;
// }
// if (escapeSequence != null) {
// for (int k = 0; k < keyEvent.repeatCount; k++) {
// sb.append(escapeSequence);
// }
// }
// } else {
// // key up event
// // support ALT+NumPad input method
// if (keyEvent.keyCode == 0x12/*VK_MENU ALT key*/ && keyEvent.uchar > 0) {
// sb.append(keyEvent.uchar);
// }
// }
// }
// return sb.toString().getBytes();
// }
//
// private int getConsoleOutputCodepage() {
// return Kernel32.GetConsoleOutputCP();
// }
//
// private int getWindowsTerminalWidth() {
// return WindowsSupport.getWindowsTerminalWidth();
// }
//
// private int getWindowsTerminalHeight() {
// return WindowsSupport.getWindowsTerminalHeight();
// }
//
// Native Bits
//
static {
System.loadLibrary("le");
initIDs();
}
private static native void initIDs();
private native int getConsoleMode();
private native void setConsoleMode(int mode);
private byte[] readConsoleInput() {
KEY_EVENT_RECORD keyEvent = readKeyEvent();
return convertKeys(keyEvent).getBytes();
}
public static String convertKeys(KEY_EVENT_RECORD keyEvent) {
if (keyEvent == null) {
return "";
}
StringBuilder sb = new StringBuilder();
if (keyEvent.keyDown) {
if (keyEvent.uchar > 0) {
// support some C1 control sequences: ALT + [@-_] (and [a-z]?) => ESC <ascii>
// http://en.wikipedia.org/wiki/C0_and_C1_control_codes#C1_set
final int altState = KEY_EVENT_RECORD.ALT_PRESSED;
// Pressing "Alt Gr" is translated to Alt-Ctrl, hence it has to be checked that Ctrl is _not_ pressed,
// otherwise inserting of "Alt Gr" codes on non-US keyboards would yield errors
final int ctrlState = KEY_EVENT_RECORD.CTRL_PRESSED;
boolean handled = false;
if ((keyEvent.controlKeyState & ctrlState) != 0) {
switch (keyEvent.keyCode) {
case 0x43: //Ctrl-C
sb.append("\003");
handled = true;
break;
}
}
if ((keyEvent.controlKeyState & KEY_EVENT_RECORD.SHIFT_PRESSED) != 0) {
switch (keyEvent.keyCode) {
case 0x09: //Shift-Tab
sb.append("\033\133\132");
handled = true;
break;
}
}
if (!handled) {
if (((keyEvent.uchar >= '@' && keyEvent.uchar <= '_') || (keyEvent.uchar >= 'a' && keyEvent.uchar <= 'z'))
&& ((keyEvent.controlKeyState & altState) != 0) && ((keyEvent.controlKeyState & ctrlState) == 0)) {
sb.append('\u001B'); // ESC
}
sb.append(keyEvent.uchar);
}
} else {
// virtual keycodes: http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
// just add support for basic editing keys (no control state, no numpad keys)
String escapeSequence = null;
switch (keyEvent.keyCode) {
case 0x21: // VK_PRIOR PageUp
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[5~", "\u001B[5;%d~");
break;
case 0x22: // VK_NEXT PageDown
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[6~", "\u001B[6;%d~");
break;
case 0x23: // VK_END
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[4~", "\u001B[4;%d~");
break;
case 0x24: // VK_HOME
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[1~", "\u001B[1;%d~");
break;
case 0x25: // VK_LEFT
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[D", "\u001B[1;%dD");
break;
case 0x26: // VK_UP
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[A", "\u001B[1;%dA");
break;
case 0x27: // VK_RIGHT
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[C", "\u001B[1;%dC");
break;
case 0x28: // VK_DOWN
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[B", "\u001B[1;%dB");
break;
case 0x2D: // VK_INSERT
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[2~", "\u001B[2;%d~");
break;
case 0x2E: // VK_DELETE
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[3~", "\u001B[3;%d~");
break;
default:
break;
}
if (escapeSequence != null) {
for (int k = 0; k < keyEvent.repeatCount; k++) {
sb.append(escapeSequence);
}
}
}
} else {
// key up event
// support ALT+NumPad input method
if (keyEvent.keyCode == 0x12/*VK_MENU ALT key*/ && keyEvent.uchar > 0) {
sb.append(keyEvent.uchar);
}
}
return sb.toString();
}
private static String escapeSequence(int controlKeyState, String noControlSequence, String withControlSequence) {
int controlNum = 1;
if ((controlKeyState & KEY_EVENT_RECORD.SHIFT_PRESSED) != 0) {
controlNum += 1;
}
if ((controlKeyState & KEY_EVENT_RECORD.ALT_PRESSED) != 0) {
controlNum += 2;
}
if ((controlKeyState & KEY_EVENT_RECORD.CTRL_PRESSED) != 0) {
controlNum += 4;
}
if (controlNum > 1) {
return String.format(withControlSequence, controlNum);
} else {
return noControlSequence;
}
}
private native KEY_EVENT_RECORD readKeyEvent();
public static class KEY_EVENT_RECORD {
public final static int ALT_PRESSED = 0x3;
public final static int CTRL_PRESSED = 0xC;
public final static int SHIFT_PRESSED = 0x10;
public final boolean keyDown;
public final char uchar;
public final int controlKeyState;
public final int keyCode;
public final int repeatCount;
public KEY_EVENT_RECORD(boolean keyDown, char uchar, int controlKeyState, int keyCode, int repeatCount) {
this.keyDown = keyDown;
this.uchar = uchar;
this.controlKeyState = controlKeyState;
this.keyCode = keyCode;
this.repeatCount = repeatCount;
}
}
private native int getConsoleOutputCodepage();
private native int getWindowsTerminalWidth();
private native int getWindowsTerminalHeight();
/**
* Console mode
* <p/>
* Constants copied <tt>wincon.h</tt>.
*/
public static enum ConsoleMode
{
/**
* 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.
*/
ENABLE_LINE_INPUT(2),
/**
* Characters read by the ReadFile or ReadConsole function are written to
* the active screen buffer as they are read. This mode can be used only if
* the ENABLE_LINE_INPUT mode is also enabled.
*/
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 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),
/**
* When enabled, text entered in a console window will be inserted at the
* current cursor location and all text following that location will not be
* overwritten. When disabled, all following text will be overwritten. An OR
* operation must be performed with this flag and the ENABLE_EXTENDED_FLAGS
* flag to enable this functionality.
*/
ENABLE_PROCESSED_OUTPUT(1),
/**
* This flag enables the user to use the mouse to select and edit text. To
* enable this option, use the OR to combine this flag with
* ENABLE_EXTENDED_FLAGS.
*/
ENABLE_WRAP_AT_EOL_OUTPUT(2),;
public final int code;
ConsoleMode(final int code) {
this.code = code;
}
}
}

@ -0,0 +1,399 @@
/*
* 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
*/
package jdk.internal.jline.console;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jdk.internal.jline.internal.Log;
/**
* @author St\u00E5le W. Pedersen <stale.pedersen@jboss.org>
*/
public class ConsoleKeys {
private KeyMap keys;
private Map<String, KeyMap> keyMaps;
private Map<String, String> variables = new HashMap<String,String>();
public ConsoleKeys(String appName, URL inputrcUrl) {
keyMaps = KeyMap.keyMaps();
loadKeys(appName, inputrcUrl);
}
protected boolean isViEditMode() {
return keys.isViKeyMap();
}
protected boolean setKeyMap (String name) {
KeyMap map = keyMaps.get(name);
if (map == null) {
return false;
}
this.keys = map;
return true;
}
protected Map<String, KeyMap> getKeyMaps() {
return keyMaps;
}
protected KeyMap getKeys() {
return keys;
}
protected void setKeys(KeyMap keys) {
this.keys = keys;
}
protected boolean getViEditMode() {
return keys.isViKeyMap ();
}
protected void loadKeys(String appName, URL inputrcUrl) {
keys = keyMaps.get(KeyMap.EMACS);
try {
InputStream input = inputrcUrl.openStream();
try {
loadKeys(input, appName);
Log.debug("Loaded user configuration: ", inputrcUrl);
}
finally {
try {
input.close();
} catch (IOException e) {
// Ignore
}
}
}
catch (IOException e) {
if (inputrcUrl.getProtocol().equals("file")) {
File file = new File(inputrcUrl.getPath());
if (file.exists()) {
Log.warn("Unable to read user configuration: ", inputrcUrl, e);
}
} else {
Log.warn("Unable to read user configuration: ", inputrcUrl, e);
}
}
}
private void loadKeys(InputStream input, String appName) throws IOException {
BufferedReader reader = new BufferedReader( new java.io.InputStreamReader( input ) );
String line;
boolean parsing = true;
List<Boolean> ifsStack = new ArrayList<Boolean>();
while ( (line = reader.readLine()) != null ) {
try {
line = line.trim();
if (line.length() == 0) {
continue;
}
if (line.charAt(0) == '#') {
continue;
}
int i = 0;
if (line.charAt(i) == '$') {
String cmd;
String args;
for (++i; i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t'); i++);
int s = i;
for (; i < line.length() && (line.charAt(i) != ' ' && line.charAt(i) != '\t'); i++);
cmd = line.substring(s, i);
for (; i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t'); i++);
s = i;
for (; i < line.length() && (line.charAt(i) != ' ' && line.charAt(i) != '\t'); i++);
args = line.substring(s, i);
if ("if".equalsIgnoreCase(cmd)) {
ifsStack.add( parsing );
if (!parsing) {
continue;
}
if (args.startsWith("term=")) {
// TODO
} else if (args.startsWith("mode=")) {
if (args.equalsIgnoreCase("mode=vi")) {
parsing = isViEditMode();
} else if (args.equals("mode=emacs")) {
parsing = !isViEditMode();
} else {
parsing = false;
}
} else {
parsing = args.equalsIgnoreCase(appName);
}
} else if ("else".equalsIgnoreCase(cmd)) {
if (ifsStack.isEmpty()) {
throw new IllegalArgumentException("$else found without matching $if");
}
boolean invert = true;
for (boolean b : ifsStack) {
if (!b) {
invert = false;
break;
}
}
if (invert) {
parsing = !parsing;
}
} else if ("endif".equalsIgnoreCase(cmd)) {
if (ifsStack.isEmpty()) {
throw new IllegalArgumentException("endif found without matching $if");
}
parsing = ifsStack.remove( ifsStack.size() - 1 );
} else if ("include".equalsIgnoreCase(cmd)) {
// TODO
}
continue;
}
if (!parsing) {
continue;
}
boolean equivalency;
String keySeq = "";
if (line.charAt(i++) == '"') {
boolean esc = false;
for (;; i++) {
if (i >= line.length()) {
throw new IllegalArgumentException("Missing closing quote on line '" + line + "'");
}
if (esc) {
esc = false;
} else if (line.charAt(i) == '\\') {
esc = true;
} else if (line.charAt(i) == '"') {
break;
}
}
}
for (; i < line.length() && line.charAt(i) != ':'
&& line.charAt(i) != ' ' && line.charAt(i) != '\t'
; i++);
keySeq = line.substring(0, i);
equivalency = (i + 1 < line.length() && line.charAt(i) == ':' && line.charAt(i + 1) == '=');
i++;
if (equivalency) {
i++;
}
if (keySeq.equalsIgnoreCase("set")) {
String key;
String val;
for (; i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t'); i++);
int s = i;
for (; i < line.length() && (line.charAt(i) != ' ' && line.charAt(i) != '\t'); i++);
key = line.substring( s, i );
for (; i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t'); i++);
s = i;
for (; i < line.length() && (line.charAt(i) != ' ' && line.charAt(i) != '\t'); i++);
val = line.substring( s, i );
setVar( key, val );
} else {
for (; i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t'); i++);
int start = i;
if (i < line.length() && (line.charAt(i) == '\'' || line.charAt(i) == '\"')) {
char delim = line.charAt(i++);
boolean esc = false;
for (;; i++) {
if (i >= line.length()) {
break;
}
if (esc) {
esc = false;
} else if (line.charAt(i) == '\\') {
esc = true;
} else if (line.charAt(i) == delim) {
break;
}
}
}
for (; i < line.length() && line.charAt(i) != ' ' && line.charAt(i) != '\t'; i++);
String val = line.substring(Math.min(start, line.length()), Math.min(i, line.length()));
if (keySeq.charAt(0) == '"') {
keySeq = translateQuoted(keySeq);
} else {
// Bind key name
String keyName = keySeq.lastIndexOf('-') > 0 ? keySeq.substring( keySeq.lastIndexOf('-') + 1 ) : keySeq;
char key = getKeyFromName(keyName);
keyName = keySeq.toLowerCase();
keySeq = "";
if (keyName.contains("meta-") || keyName.contains("m-")) {
keySeq += "\u001b";
}
if (keyName.contains("control-") || keyName.contains("c-") || keyName.contains("ctrl-")) {
key = (char)(Character.toUpperCase( key ) & 0x1f);
}
keySeq += key;
}
if (val.length() > 0 && (val.charAt(0) == '\'' || val.charAt(0) == '\"')) {
keys.bind( keySeq, translateQuoted(val) );
} else {
String operationName = val.replace('-', '_').toUpperCase();
try {
keys.bind(keySeq, Operation.valueOf(operationName));
} catch(IllegalArgumentException e) {
Log.info("Unable to bind key for unsupported operation: ", val);
}
}
}
} catch (IllegalArgumentException e) {
Log.warn("Unable to parse user configuration: ", e);
}
}
}
private String translateQuoted(String keySeq) {
int i;
String str = keySeq.substring( 1, keySeq.length() - 1 );
keySeq = "";
for (i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c == '\\') {
boolean ctrl = str.regionMatches(i, "\\C-", 0, 3)|| str.regionMatches(i, "\\M-\\C-", 0, 6);
boolean meta = str.regionMatches(i, "\\M-", 0, 3)|| str.regionMatches(i, "\\C-\\M-", 0, 6);
i += (meta ? 3 : 0) + (ctrl ? 3 : 0) + (!meta && !ctrl ? 1 : 0);
if (i >= str.length()) {
break;
}
c = str.charAt(i);
if (meta) {
keySeq += "\u001b";
}
if (ctrl) {
c = c == '?' ? 0x7f : (char)(Character.toUpperCase( c ) & 0x1f);
}
if (!meta && !ctrl) {
switch (c) {
case 'a': c = 0x07; break;
case 'b': c = '\b'; break;
case 'd': c = 0x7f; break;
case 'e': c = 0x1b; break;
case 'f': c = '\f'; break;
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case 'v': c = 0x0b; break;
case '\\': c = '\\'; break;
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
c = 0;
for (int j = 0; j < 3; j++, i++) {
if (i >= str.length()) {
break;
}
int k = Character.digit(str.charAt(i), 8);
if (k < 0) {
break;
}
c = (char)(c * 8 + k);
}
c &= 0xFF;
break;
case 'x':
i++;
c = 0;
for (int j = 0; j < 2; j++, i++) {
if (i >= str.length()) {
break;
}
int k = Character.digit(str.charAt(i), 16);
if (k < 0) {
break;
}
c = (char)(c * 16 + k);
}
c &= 0xFF;
break;
case 'u':
i++;
c = 0;
for (int j = 0; j < 4; j++, i++) {
if (i >= str.length()) {
break;
}
int k = Character.digit(str.charAt(i), 16);
if (k < 0) {
break;
}
c = (char)(c * 16 + k);
}
break;
}
}
keySeq += c;
} else {
keySeq += c;
}
}
return keySeq;
}
private char getKeyFromName(String name) {
if ("DEL".equalsIgnoreCase(name) || "Rubout".equalsIgnoreCase(name)) {
return 0x7f;
} else if ("ESC".equalsIgnoreCase(name) || "Escape".equalsIgnoreCase(name)) {
return '\033';
} else if ("LFD".equalsIgnoreCase(name) || "NewLine".equalsIgnoreCase(name)) {
return '\n';
} else if ("RET".equalsIgnoreCase(name) || "Return".equalsIgnoreCase(name)) {
return '\r';
} else if ("SPC".equalsIgnoreCase(name) || "Space".equalsIgnoreCase(name)) {
return ' ';
} else if ("Tab".equalsIgnoreCase(name)) {
return '\t';
} else {
return name.charAt(0);
}
}
private void setVar(String key, String val) {
if ("keymap".equalsIgnoreCase(key)) {
if (keyMaps.containsKey(val)) {
keys = keyMaps.get(val);
}
} else if ("editing-mode".equals(key)) {
if ("vi".equalsIgnoreCase(val)) {
keys = keyMaps.get(KeyMap.VI_INSERT);
} else if ("emacs".equalsIgnoreCase(key)) {
keys = keyMaps.get(KeyMap.EMACS);
}
} else if ("blink-matching-paren".equals(key)) {
if ("on".equalsIgnoreCase(val)) {
keys.setBlinkMatchingParen(true);
} else if ("off".equalsIgnoreCase(val)) {
keys.setBlinkMatchingParen(false);
}
}
/*
* Technically variables should be defined as a functor class
* so that validation on the variable value can be done at parse
* time. This is a stop-gap.
*/
variables.put(key, val);
}
/**
* Retrieves the value of a variable that was set in the .inputrc file
* during processing
* @param var The variable name
* @return The variable value.
*/
public String getVariable(String var) {
return variables.get (var);
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,121 @@
/*
* 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
*/
package jdk.internal.jline.console;
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
/**
* A holder for a {@link StringBuilder} that also contains the current cursor position.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.0
*/
public class CursorBuffer
{
private boolean overTyping = false;
public int cursor = 0;
public final StringBuilder buffer = new StringBuilder();
public CursorBuffer copy () {
CursorBuffer that = new CursorBuffer();
that.overTyping = this.overTyping;
that.cursor = this.cursor;
that.buffer.append (this.toString());
return that;
}
public boolean isOverTyping() {
return overTyping;
}
public void setOverTyping(final boolean b) {
overTyping = b;
}
public int length() {
return buffer.length();
}
public char nextChar() {
if (cursor == buffer.length()) {
return 0;
} else {
return buffer.charAt(cursor);
}
}
public char current() {
if (cursor <= 0) {
return 0;
}
return buffer.charAt(cursor - 1);
}
/**
* Write the specific character into the buffer, setting the cursor position
* ahead one. The text may overwrite or insert based on the current setting
* of {@link #isOverTyping}.
*
* @param c the character to insert
*/
public void write(final char c) {
buffer.insert(cursor++, c);
if (isOverTyping() && cursor < buffer.length()) {
buffer.deleteCharAt(cursor);
}
}
/**
* Insert the specified chars into the buffer, setting the cursor to the end of the insertion point.
*/
public void write(final CharSequence str) {
checkNotNull(str);
if (buffer.length() == 0) {
buffer.append(str);
}
else {
buffer.insert(cursor, str);
}
cursor += str.length();
if (isOverTyping() && cursor < buffer.length()) {
buffer.delete(cursor, (cursor + str.length()));
}
}
public boolean clear() {
if (buffer.length() == 0) {
return false;
}
buffer.delete(0, buffer.length());
cursor = 0;
return true;
}
public String upToCursor() {
if (cursor <= 0) {
return "";
}
return buffer.substring(0, cursor);
}
@Override
public String toString() {
return buffer.toString();
}
}

@ -0,0 +1,578 @@
/*
* 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
*/
package jdk.internal.jline.console;
import java.util.HashMap;
import java.util.Map;
/**
* The KeyMap class contains all bindings from keys to operations.
*
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
* @since 2.6
*/
public class KeyMap {
public static final String VI_MOVE = "vi-move";
public static final String VI_INSERT = "vi-insert";
public static final String EMACS = "emacs";
public static final String EMACS_STANDARD = "emacs-standard";
public static final String EMACS_CTLX = "emacs-ctlx";
public static final String EMACS_META = "emacs-meta";
private static final int KEYMAP_LENGTH = 256;
private static final Object NULL_FUNCTION = new Object();
private Object[] mapping = new Object[KEYMAP_LENGTH];
private Object anotherKey = null;
private String name;
private boolean isViKeyMap;
public KeyMap(String name, boolean isViKeyMap) {
this(name, new Object[KEYMAP_LENGTH], isViKeyMap);
}
protected KeyMap(String name, Object[] mapping, boolean isViKeyMap) {
this.mapping = mapping;
this.name = name;
this.isViKeyMap = isViKeyMap;
}
public boolean isViKeyMap() {
return isViKeyMap;
}
public String getName() {
return name;
}
public Object getAnotherKey() {
return anotherKey;
}
public void from(KeyMap other) {
this.mapping = other.mapping;
this.anotherKey = other.anotherKey;
}
public Object getBound( CharSequence keySeq ) {
if (keySeq != null && keySeq.length() > 0) {
KeyMap map = this;
for (int i = 0; i < keySeq.length(); i++) {
char c = keySeq.charAt(i);
if (c > 255) {
return Operation.SELF_INSERT;
}
if (map.mapping[c] instanceof KeyMap) {
if (i == keySeq.length() - 1) {
return map.mapping[c];
} else {
map = (KeyMap) map.mapping[c];
}
} else {
return map.mapping[c];
}
}
}
return null;
}
public void bindIfNotBound( CharSequence keySeq, Object function ) {
bind (this, keySeq, function, true);
}
public void bind( CharSequence keySeq, Object function ) {
bind (this, keySeq, function, false);
}
private static void bind( KeyMap map, CharSequence keySeq, Object function ) {
bind (map, keySeq, function, false);
}
private static void bind( KeyMap map, CharSequence keySeq, Object function,
boolean onlyIfNotBound ) {
if (keySeq != null && keySeq.length() > 0) {
for (int i = 0; i < keySeq.length(); i++) {
char c = keySeq.charAt(i);
if (c >= map.mapping.length) {
return;
}
if (i < keySeq.length() - 1) {
if (!(map.mapping[c] instanceof KeyMap)) {
KeyMap m = new KeyMap("anonymous", false);
if (map.mapping[c] != Operation.DO_LOWERCASE_VERSION) {
m.anotherKey = map.mapping[c];
}
map.mapping[c] = m;
}
map = (KeyMap) map.mapping[c];
} else {
if (function == null) {
function = NULL_FUNCTION;
}
if (map.mapping[c] instanceof KeyMap) {
map.anotherKey = function;
} else {
Object op = map.mapping[c];
if (onlyIfNotBound == false
|| op == null
|| op == Operation.DO_LOWERCASE_VERSION
|| op == Operation.VI_MOVEMENT_MODE ) {
}
map.mapping[c] = function;
}
}
}
}
}
public void setBlinkMatchingParen(boolean on) {
if (on) {
bind( "}", Operation.INSERT_CLOSE_CURLY );
bind( ")", Operation.INSERT_CLOSE_PAREN );
bind( "]", Operation.INSERT_CLOSE_SQUARE );
}
}
private static void bindArrowKeys(KeyMap map) {
// MS-DOS
bind( map, "\033[0A", Operation.PREVIOUS_HISTORY );
bind( map, "\033[0B", Operation.BACKWARD_CHAR );
bind( map, "\033[0C", Operation.FORWARD_CHAR );
bind( map, "\033[0D", Operation.NEXT_HISTORY );
// Windows
bind( map, "\340\000", Operation.KILL_WHOLE_LINE );
bind( map, "\340\107", Operation.BEGINNING_OF_LINE );
bind( map, "\340\110", Operation.PREVIOUS_HISTORY );
bind( map, "\340\111", Operation.BEGINNING_OF_HISTORY );
bind( map, "\340\113", Operation.BACKWARD_CHAR );
bind( map, "\340\115", Operation.FORWARD_CHAR );
bind( map, "\340\117", Operation.END_OF_LINE );
bind( map, "\340\120", Operation.NEXT_HISTORY );
bind( map, "\340\121", Operation.END_OF_HISTORY );
bind( map, "\340\122", Operation.OVERWRITE_MODE );
bind( map, "\340\123", Operation.DELETE_CHAR );
bind( map, "\000\107", Operation.BEGINNING_OF_LINE );
bind( map, "\000\110", Operation.PREVIOUS_HISTORY );
bind( map, "\000\111", Operation.BEGINNING_OF_HISTORY );
bind( map, "\000\110", Operation.PREVIOUS_HISTORY );
bind( map, "\000\113", Operation.BACKWARD_CHAR );
bind( map, "\000\115", Operation.FORWARD_CHAR );
bind( map, "\000\117", Operation.END_OF_LINE );
bind( map, "\000\120", Operation.NEXT_HISTORY );
bind( map, "\000\121", Operation.END_OF_HISTORY );
bind( map, "\000\122", Operation.OVERWRITE_MODE );
bind( map, "\000\123", Operation.DELETE_CHAR );
bind( map, "\033[A", Operation.PREVIOUS_HISTORY );
bind( map, "\033[B", Operation.NEXT_HISTORY );
bind( map, "\033[C", Operation.FORWARD_CHAR );
bind( map, "\033[D", Operation.BACKWARD_CHAR );
bind( map, "\033[H", Operation.BEGINNING_OF_LINE );
bind( map, "\033[F", Operation.END_OF_LINE );
bind( map, "\033OA", Operation.PREVIOUS_HISTORY );
bind( map, "\033OB", Operation.NEXT_HISTORY );
bind( map, "\033OC", Operation.FORWARD_CHAR );
bind( map, "\033OD", Operation.BACKWARD_CHAR );
bind( map, "\033OH", Operation.BEGINNING_OF_LINE );
bind( map, "\033OF", Operation.END_OF_LINE );
bind( map, "\033[1~", Operation.BEGINNING_OF_LINE);
bind( map, "\033[4~", Operation.END_OF_LINE);
bind( map, "\033[3~", Operation.DELETE_CHAR);
// MINGW32
bind( map, "\0340H", Operation.PREVIOUS_HISTORY );
bind( map, "\0340P", Operation.NEXT_HISTORY );
bind( map, "\0340M", Operation.FORWARD_CHAR );
bind( map, "\0340K", Operation.BACKWARD_CHAR );
}
// public boolean isConvertMetaCharsToAscii() {
// return convertMetaCharsToAscii;
// }
// public void setConvertMetaCharsToAscii(boolean convertMetaCharsToAscii) {
// this.convertMetaCharsToAscii = convertMetaCharsToAscii;
// }
public static boolean isMeta( char c ) {
return c > 0x7f && c <= 0xff;
}
public static char unMeta( char c ) {
return (char) (c & 0x7F);
}
public static char meta( char c ) {
return (char) (c | 0x80);
}
public static Map<String, KeyMap> keyMaps() {
Map<String, KeyMap> keyMaps = new HashMap<String, KeyMap>();
KeyMap emacs = emacs();
bindArrowKeys(emacs);
keyMaps.put(EMACS, emacs);
keyMaps.put(EMACS_STANDARD, emacs);
keyMaps.put(EMACS_CTLX, (KeyMap) emacs.getBound("\u0018"));
keyMaps.put(EMACS_META, (KeyMap) emacs.getBound("\u001b"));
KeyMap viMov = viMovement();
bindArrowKeys(viMov);
keyMaps.put(VI_MOVE, viMov);
keyMaps.put("vi-command", viMov);
KeyMap viIns = viInsertion();
bindArrowKeys(viIns);
keyMaps.put(VI_INSERT, viIns);
keyMaps.put("vi", viIns);
return keyMaps;
}
public static KeyMap emacs() {
Object[] map = new Object[KEYMAP_LENGTH];
Object[] ctrl = new Object[] {
// Control keys.
Operation.SET_MARK, /* Control-@ */
Operation.BEGINNING_OF_LINE, /* Control-A */
Operation.BACKWARD_CHAR, /* Control-B */
Operation.INTERRUPT, /* Control-C */
Operation.EXIT_OR_DELETE_CHAR, /* Control-D */
Operation.END_OF_LINE, /* Control-E */
Operation.FORWARD_CHAR, /* Control-F */
Operation.ABORT, /* Control-G */
Operation.BACKWARD_DELETE_CHAR, /* Control-H */
Operation.COMPLETE, /* Control-I */
Operation.ACCEPT_LINE, /* Control-J */
Operation.KILL_LINE, /* Control-K */
Operation.CLEAR_SCREEN, /* Control-L */
Operation.ACCEPT_LINE, /* Control-M */
Operation.NEXT_HISTORY, /* Control-N */
null, /* Control-O */
Operation.PREVIOUS_HISTORY, /* Control-P */
Operation.QUOTED_INSERT, /* Control-Q */
Operation.REVERSE_SEARCH_HISTORY, /* Control-R */
Operation.FORWARD_SEARCH_HISTORY, /* Control-S */
Operation.TRANSPOSE_CHARS, /* Control-T */
Operation.UNIX_LINE_DISCARD, /* Control-U */
Operation.QUOTED_INSERT, /* Control-V */
Operation.UNIX_WORD_RUBOUT, /* Control-W */
emacsCtrlX(), /* Control-X */
Operation.YANK, /* Control-Y */
null, /* Control-Z */
emacsMeta(), /* Control-[ */
null, /* Control-\ */
Operation.CHARACTER_SEARCH, /* Control-] */
null, /* Control-^ */
Operation.UNDO, /* Control-_ */
};
System.arraycopy( ctrl, 0, map, 0, ctrl.length );
for (int i = 32; i < 256; i++) {
map[i] = Operation.SELF_INSERT;
}
map[DELETE] = Operation.BACKWARD_DELETE_CHAR;
return new KeyMap(EMACS, map, false);
}
public static final char CTRL_D = (char) 4;
public static final char CTRL_G = (char) 7;
public static final char CTRL_H = (char) 8;
public static final char CTRL_I = (char) 9;
public static final char CTRL_J = (char) 10;
public static final char CTRL_M = (char) 13;
public static final char CTRL_R = (char) 18;
public static final char CTRL_S = (char) 19;
public static final char CTRL_U = (char) 21;
public static final char CTRL_X = (char) 24;
public static final char CTRL_Y = (char) 25;
public static final char ESCAPE = (char) 27; /* Ctrl-[ */
public static final char CTRL_OB = (char) 27; /* Ctrl-[ */
public static final char CTRL_CB = (char) 29; /* Ctrl-] */
public static final int DELETE = (char) 127;
public static KeyMap emacsCtrlX() {
Object[] map = new Object[KEYMAP_LENGTH];
map[CTRL_G] = Operation.ABORT;
map[CTRL_R] = Operation.RE_READ_INIT_FILE;
map[CTRL_U] = Operation.UNDO;
map[CTRL_X] = Operation.EXCHANGE_POINT_AND_MARK;
map['('] = Operation.START_KBD_MACRO;
map[')'] = Operation.END_KBD_MACRO;
for (int i = 'A'; i <= 'Z'; i++) {
map[i] = Operation.DO_LOWERCASE_VERSION;
}
map['e'] = Operation.CALL_LAST_KBD_MACRO;
map[DELETE] = Operation.KILL_LINE;
return new KeyMap(EMACS_CTLX, map, false);
}
public static KeyMap emacsMeta() {
Object[] map = new Object[KEYMAP_LENGTH];
map[CTRL_G] = Operation.ABORT;
map[CTRL_H] = Operation.BACKWARD_KILL_WORD;
map[CTRL_I] = Operation.TAB_INSERT;
map[CTRL_J] = Operation.VI_EDITING_MODE;
map[CTRL_M] = Operation.VI_EDITING_MODE;
map[CTRL_R] = Operation.REVERT_LINE;
map[CTRL_Y] = Operation.YANK_NTH_ARG;
map[CTRL_OB] = Operation.COMPLETE;
map[CTRL_CB] = Operation.CHARACTER_SEARCH_BACKWARD;
map[' '] = Operation.SET_MARK;
map['#'] = Operation.INSERT_COMMENT;
map['&'] = Operation.TILDE_EXPAND;
map['*'] = Operation.INSERT_COMPLETIONS;
map['-'] = Operation.DIGIT_ARGUMENT;
map['.'] = Operation.YANK_LAST_ARG;
map['<'] = Operation.BEGINNING_OF_HISTORY;
map['='] = Operation.POSSIBLE_COMPLETIONS;
map['>'] = Operation.END_OF_HISTORY;
map['?'] = Operation.POSSIBLE_COMPLETIONS;
for (int i = 'A'; i <= 'Z'; i++) {
map[i] = Operation.DO_LOWERCASE_VERSION;
}
map['\\'] = Operation.DELETE_HORIZONTAL_SPACE;
map['_'] = Operation.YANK_LAST_ARG;
map['b'] = Operation.BACKWARD_WORD;
map['c'] = Operation.CAPITALIZE_WORD;
map['d'] = Operation.KILL_WORD;
map['f'] = Operation.FORWARD_WORD;
map['l'] = Operation.DOWNCASE_WORD;
map['p'] = Operation.NON_INCREMENTAL_REVERSE_SEARCH_HISTORY;
map['r'] = Operation.REVERT_LINE;
map['t'] = Operation.TRANSPOSE_WORDS;
map['u'] = Operation.UPCASE_WORD;
map['y'] = Operation.YANK_POP;
map['~'] = Operation.TILDE_EXPAND;
map[DELETE] = Operation.BACKWARD_KILL_WORD;
return new KeyMap(EMACS_META, map, false);
}
public static KeyMap viInsertion() {
Object[] map = new Object[KEYMAP_LENGTH];
Object[] ctrl = new Object[] {
// Control keys.
null, /* Control-@ */
Operation.SELF_INSERT, /* Control-A */
Operation.SELF_INSERT, /* Control-B */
Operation.SELF_INSERT, /* Control-C */
Operation.VI_EOF_MAYBE, /* Control-D */
Operation.SELF_INSERT, /* Control-E */
Operation.SELF_INSERT, /* Control-F */
Operation.SELF_INSERT, /* Control-G */
Operation.BACKWARD_DELETE_CHAR, /* Control-H */
Operation.COMPLETE, /* Control-I */
Operation.ACCEPT_LINE, /* Control-J */
Operation.SELF_INSERT, /* Control-K */
Operation.SELF_INSERT, /* Control-L */
Operation.ACCEPT_LINE, /* Control-M */
Operation.MENU_COMPLETE, /* Control-N */
Operation.SELF_INSERT, /* Control-O */
Operation.MENU_COMPLETE_BACKWARD, /* Control-P */
Operation.SELF_INSERT, /* Control-Q */
Operation.REVERSE_SEARCH_HISTORY, /* Control-R */
Operation.FORWARD_SEARCH_HISTORY, /* Control-S */
Operation.TRANSPOSE_CHARS, /* Control-T */
Operation.UNIX_LINE_DISCARD, /* Control-U */
Operation.QUOTED_INSERT, /* Control-V */
Operation.UNIX_WORD_RUBOUT, /* Control-W */
Operation.SELF_INSERT, /* Control-X */
Operation.YANK, /* Control-Y */
Operation.SELF_INSERT, /* Control-Z */
Operation.VI_MOVEMENT_MODE, /* Control-[ */
Operation.SELF_INSERT, /* Control-\ */
Operation.SELF_INSERT, /* Control-] */
Operation.SELF_INSERT, /* Control-^ */
Operation.UNDO, /* Control-_ */
};
System.arraycopy( ctrl, 0, map, 0, ctrl.length );
for (int i = 32; i < 256; i++) {
map[i] = Operation.SELF_INSERT;
}
map[DELETE] = Operation.BACKWARD_DELETE_CHAR;
return new KeyMap(VI_INSERT, map, false);
}
public static KeyMap viMovement() {
Object[] map = new Object[KEYMAP_LENGTH];
Object[] low = new Object[] {
// Control keys.
null, /* Control-@ */
null, /* Control-A */
null, /* Control-B */
Operation.INTERRUPT, /* Control-C */
/*
* ^D is supposed to move down half a screen. In bash
* appears to be ignored.
*/
Operation.VI_EOF_MAYBE, /* Control-D */
Operation.EMACS_EDITING_MODE, /* Control-E */
null, /* Control-F */
Operation.ABORT, /* Control-G */
Operation.BACKWARD_CHAR, /* Control-H */
null, /* Control-I */
Operation.VI_MOVE_ACCEPT_LINE, /* Control-J */
Operation.KILL_LINE, /* Control-K */
Operation.CLEAR_SCREEN, /* Control-L */
Operation.VI_MOVE_ACCEPT_LINE, /* Control-M */
Operation.VI_NEXT_HISTORY, /* Control-N */
null, /* Control-O */
Operation.VI_PREVIOUS_HISTORY, /* Control-P */
/*
* My testing with readline is the ^Q is ignored.
* Maybe this should be null?
*/
Operation.QUOTED_INSERT, /* Control-Q */
/*
* TODO - Very broken. While in forward/reverse
* history search the VI keyset should go out the
* window and we need to enter a very simple keymap.
*/
Operation.REVERSE_SEARCH_HISTORY, /* Control-R */
/* TODO */
Operation.FORWARD_SEARCH_HISTORY, /* Control-S */
Operation.TRANSPOSE_CHARS, /* Control-T */
Operation.UNIX_LINE_DISCARD, /* Control-U */
/* TODO */
Operation.QUOTED_INSERT, /* Control-V */
Operation.UNIX_WORD_RUBOUT, /* Control-W */
null, /* Control-X */
/* TODO */
Operation.YANK, /* Control-Y */
null, /* Control-Z */
emacsMeta(), /* Control-[ */
null, /* Control-\ */
/* TODO */
Operation.CHARACTER_SEARCH, /* Control-] */
null, /* Control-^ */
/* TODO */
Operation.UNDO, /* Control-_ */
Operation.FORWARD_CHAR, /* SPACE */
null, /* ! */
null, /* " */
Operation.VI_INSERT_COMMENT, /* # */
Operation.END_OF_LINE, /* $ */
Operation.VI_MATCH, /* % */
Operation.VI_TILDE_EXPAND, /* & */
null, /* ' */
null, /* ( */
null, /* ) */
/* TODO */
Operation.VI_COMPLETE, /* * */
Operation.VI_NEXT_HISTORY, /* + */
Operation.VI_CHAR_SEARCH, /* , */
Operation.VI_PREVIOUS_HISTORY, /* - */
/* TODO */
Operation.VI_REDO, /* . */
Operation.VI_SEARCH, /* / */
Operation.VI_BEGNNING_OF_LINE_OR_ARG_DIGIT, /* 0 */
Operation.VI_ARG_DIGIT, /* 1 */
Operation.VI_ARG_DIGIT, /* 2 */
Operation.VI_ARG_DIGIT, /* 3 */
Operation.VI_ARG_DIGIT, /* 4 */
Operation.VI_ARG_DIGIT, /* 5 */
Operation.VI_ARG_DIGIT, /* 6 */
Operation.VI_ARG_DIGIT, /* 7 */
Operation.VI_ARG_DIGIT, /* 8 */
Operation.VI_ARG_DIGIT, /* 9 */
null, /* : */
Operation.VI_CHAR_SEARCH, /* ; */
null, /* < */
Operation.VI_COMPLETE, /* = */
null, /* > */
Operation.VI_SEARCH, /* ? */
null, /* @ */
Operation.VI_APPEND_EOL, /* A */
Operation.VI_PREV_WORD, /* B */
Operation.VI_CHANGE_TO_EOL, /* C */
Operation.VI_DELETE_TO_EOL, /* D */
Operation.VI_END_WORD, /* E */
Operation.VI_CHAR_SEARCH, /* F */
/* I need to read up on what this does */
Operation.VI_FETCH_HISTORY, /* G */
null, /* H */
Operation.VI_INSERT_BEG, /* I */
null, /* J */
null, /* K */
null, /* L */
null, /* M */
Operation.VI_SEARCH_AGAIN, /* N */
null, /* O */
Operation.VI_PUT, /* P */
null, /* Q */
/* TODO */
Operation.VI_REPLACE, /* R */
Operation.VI_KILL_WHOLE_LINE, /* S */
Operation.VI_CHAR_SEARCH, /* T */
/* TODO */
Operation.REVERT_LINE, /* U */
null, /* V */
Operation.VI_NEXT_WORD, /* W */
Operation.VI_RUBOUT, /* X */
Operation.VI_YANK_TO, /* Y */
null, /* Z */
null, /* [ */
Operation.VI_COMPLETE, /* \ */
null, /* ] */
Operation.VI_FIRST_PRINT, /* ^ */
Operation.VI_YANK_ARG, /* _ */
Operation.VI_GOTO_MARK, /* ` */
Operation.VI_APPEND_MODE, /* a */
Operation.VI_PREV_WORD, /* b */
Operation.VI_CHANGE_TO, /* c */
Operation.VI_DELETE_TO, /* d */
Operation.VI_END_WORD, /* e */
Operation.VI_CHAR_SEARCH, /* f */
null, /* g */
Operation.BACKWARD_CHAR, /* h */
Operation.VI_INSERTION_MODE, /* i */
Operation.NEXT_HISTORY, /* j */
Operation.PREVIOUS_HISTORY, /* k */
Operation.FORWARD_CHAR, /* l */
Operation.VI_SET_MARK, /* m */
Operation.VI_SEARCH_AGAIN, /* n */
null, /* o */
Operation.VI_PUT, /* p */
null, /* q */
Operation.VI_CHANGE_CHAR, /* r */
Operation.VI_SUBST, /* s */
Operation.VI_CHAR_SEARCH, /* t */
Operation.UNDO, /* u */
null, /* v */
Operation.VI_NEXT_WORD, /* w */
Operation.VI_DELETE, /* x */
Operation.VI_YANK_TO, /* y */
null, /* z */
null, /* { */
Operation.VI_COLUMN, /* | */
null, /* } */
Operation.VI_CHANGE_CASE, /* ~ */
Operation.VI_DELETE /* DEL */
};
System.arraycopy( low, 0, map, 0, low.length );
for (int i = 128; i < 256; i++) {
map[i] = null;
}
return new KeyMap(VI_MOVE, map, false);
}
}

@ -0,0 +1,164 @@
/*
* Copyright (c) 2002-2013, 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
*/
package jdk.internal.jline.console;
/**
* The kill ring class keeps killed text in a fixed size ring. In this
* class we also keep record of whether or not the last command was a
* kill or a yank. Depending on this, the class may behave
* different. For instance, two consecutive kill-word commands fill
* the same slot such that the next yank will return the two
* previously killed words instead that only the last one. Likewise
* yank pop requires that the previous command was either a yank or a
* yank-pop.
*/
public final class KillRing {
/**
* Default size is 60, like in emacs.
*/
private static final int DEFAULT_SIZE = 60;
private final String[] slots;
private int head = 0;
private boolean lastKill = false;
private boolean lastYank = false;
/**
* Creates a new kill ring of the given size.
*/
public KillRing(int size) {
slots = new String[size];
}
/**
* Creates a new kill ring of the default size. {@see DEFAULT_SIZE}.
*/
public KillRing() {
this(DEFAULT_SIZE);
}
/**
* Resets the last-yank state.
*/
public void resetLastYank() {
lastYank = false;
}
/**
* Resets the last-kill state.
*/
public void resetLastKill() {
lastKill = false;
}
/**
* Returns {@code true} if the last command was a yank.
*/
public boolean lastYank() {
return lastYank;
}
/**
* Adds the string to the kill-ring. Also sets lastYank to false
* and lastKill to true.
*/
public void add(String str) {
lastYank = false;
if (lastKill) {
if (slots[head] != null) {
slots[head] += str;
return;
}
}
lastKill = true;
next();
slots[head] = str;
}
/**
* Adds the string to the kill-ring product of killing
* backwards. If the previous command was a kill text one then
* adds the text at the beginning of the previous kill to avoid
* that two consecutive backwards kills followed by a yank leaves
* things reversed.
*/
public void addBackwards(String str) {
lastYank = false;
if (lastKill) {
if (slots[head] != null) {
slots[head] = str + slots[head];
return;
}
}
lastKill = true;
next();
slots[head] = str;
}
/**
* Yanks a previously killed text. Returns {@code null} if the
* ring is empty.
*/
public String yank() {
lastKill = false;
lastYank = true;
return slots[head];
}
/**
* Moves the pointer to the current slot back and returns the text
* in that position. If the previous command was not yank returns
* null.
*/
public String yankPop() {
lastKill = false;
if (lastYank) {
prev();
return slots[head];
}
return null;
}
/**
* Moves the pointer to the current slot forward. If the end of
* the slots is reached then points back to the beginning.
*/
private void next() {
if (head == 0 && slots[0] == null) {
return;
}
head++;
if (head == slots.length) {
head = 0;
}
}
/**
* Moves the pointer to the current slot backwards. If the
* beginning of the slots is reached then traverses the slot
* backwards until one with not null content is found.
*/
private void prev() {
head--;
if (head == -1) {
int x = (slots.length - 1);
for (; x >= 0; x--) {
if (slots[x] != null) {
break;
}
}
head = x;
}
}
}

@ -0,0 +1,160 @@
/*
* 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
*/
package jdk.internal.jline.console;
/**
* List of all operations.
*
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
* @since 2.6
*/
public enum Operation {
ABORT,
ACCEPT_LINE,
ARROW_KEY_PREFIX,
BACKWARD_BYTE,
BACKWARD_CHAR,
BACKWARD_DELETE_CHAR,
BACKWARD_KILL_LINE,
BACKWARD_KILL_WORD,
BACKWARD_WORD,
BEGINNING_OF_HISTORY,
BEGINNING_OF_LINE,
CALL_LAST_KBD_MACRO,
CAPITALIZE_WORD,
CHARACTER_SEARCH,
CHARACTER_SEARCH_BACKWARD,
CLEAR_SCREEN,
COMPLETE,
COPY_BACKWARD_WORD,
COPY_FORWARD_WORD,
COPY_REGION_AS_KILL,
DELETE_CHAR,
DELETE_CHAR_OR_LIST,
DELETE_HORIZONTAL_SPACE,
DIGIT_ARGUMENT,
DO_LOWERCASE_VERSION,
DOWNCASE_WORD,
DUMP_FUNCTIONS,
DUMP_MACROS,
DUMP_VARIABLES,
EMACS_EDITING_MODE,
END_KBD_MACRO,
END_OF_HISTORY,
END_OF_LINE,
EXCHANGE_POINT_AND_MARK,
EXIT_OR_DELETE_CHAR,
FORWARD_BACKWARD_DELETE_CHAR,
FORWARD_BYTE,
FORWARD_CHAR,
FORWARD_SEARCH_HISTORY,
FORWARD_WORD,
HISTORY_SEARCH_BACKWARD,
HISTORY_SEARCH_FORWARD,
INSERT_CLOSE_CURLY,
INSERT_CLOSE_PAREN,
INSERT_CLOSE_SQUARE,
INSERT_COMMENT,
INSERT_COMPLETIONS,
INTERRUPT,
KILL_WHOLE_LINE,
KILL_LINE,
KILL_REGION,
KILL_WORD,
MENU_COMPLETE,
MENU_COMPLETE_BACKWARD,
NEXT_HISTORY,
NON_INCREMENTAL_FORWARD_SEARCH_HISTORY,
NON_INCREMENTAL_REVERSE_SEARCH_HISTORY,
NON_INCREMENTAL_FORWARD_SEARCH_HISTORY_AGAIN,
NON_INCREMENTAL_REVERSE_SEARCH_HISTORY_AGAIN,
OLD_MENU_COMPLETE,
OVERWRITE_MODE,
PASTE_FROM_CLIPBOARD,
POSSIBLE_COMPLETIONS,
PREVIOUS_HISTORY,
QUOTED_INSERT,
RE_READ_INIT_FILE,
REDRAW_CURRENT_LINE,
REVERSE_SEARCH_HISTORY,
REVERT_LINE,
SELF_INSERT,
SET_MARK,
SKIP_CSI_SEQUENCE,
START_KBD_MACRO,
TAB_INSERT,
TILDE_EXPAND,
TRANSPOSE_CHARS,
TRANSPOSE_WORDS,
TTY_STATUS,
UNDO,
UNIVERSAL_ARGUMENT,
UNIX_FILENAME_RUBOUT,
UNIX_LINE_DISCARD,
UNIX_WORD_RUBOUT,
UPCASE_WORD,
YANK,
YANK_LAST_ARG,
YANK_NTH_ARG,
YANK_POP,
VI_APPEND_EOL,
VI_APPEND_MODE,
VI_ARG_DIGIT,
VI_BACK_TO_INDENT,
VI_BACKWARD_BIGWORD,
VI_BACKWARD_WORD,
VI_BWORD,
VI_CHANGE_CASE,
VI_CHANGE_CHAR,
VI_CHANGE_TO,
VI_CHANGE_TO_EOL,
VI_CHAR_SEARCH,
VI_COLUMN,
VI_COMPLETE,
VI_DELETE,
VI_DELETE_TO,
VI_DELETE_TO_EOL,
VI_EDITING_MODE,
VI_END_BIGWORD,
VI_END_WORD,
VI_EOF_MAYBE,
VI_EWORD,
VI_FWORD,
VI_FETCH_HISTORY,
VI_FIRST_PRINT,
VI_FORWARD_BIGWORD,
VI_FORWARD_WORD,
VI_GOTO_MARK,
VI_INSERT_BEG,
VI_INSERTION_MODE,
VI_KILL_WHOLE_LINE,
VI_MATCH,
VI_MOVEMENT_MODE,
VI_NEXT_WORD,
VI_OVERSTRIKE,
VI_OVERSTRIKE_DELETE,
VI_PREV_WORD,
VI_PUT,
VI_REDO,
VI_REPLACE,
VI_RUBOUT,
VI_SEARCH,
VI_SEARCH_AGAIN,
VI_SET_MARK,
VI_SUBST,
VI_TILDE_EXPAND,
VI_YANK_ARG,
VI_YANK_TO,
VI_MOVE_ACCEPT_LINE,
VI_NEXT_HISTORY,
VI_PREVIOUS_HISTORY,
VI_INSERT_COMMENT,
VI_BEGNNING_OF_LINE_OR_ARG_DIGIT,
}

@ -0,0 +1,36 @@
/*
* 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
*/
package jdk.internal.jline.console;
/**
* This exception is thrown by {@link ConsoleReader#readLine} when
* user interrupt handling is enabled and the user types the
* interrupt character (ctrl-C). The partially entered line is
* available via the {@link #getPartialLine()} method.
*/
public class UserInterruptException
extends RuntimeException
{
private static final long serialVersionUID = 6172232572140736750L;
private final String partialLine;
public UserInterruptException(String partialLine)
{
this.partialLine = partialLine;
}
/**
* @return the partially entered line when ctrl-C was pressed
*/
public String getPartialLine()
{
return partialLine;
}
}

@ -0,0 +1,124 @@
/*
* 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
*/
package jdk.internal.jline.console.completer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
/**
* Completer which contains multiple completers and aggregates them together.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.3
*/
public class AggregateCompleter
implements Completer
{
private final List<Completer> completers = new ArrayList<Completer>();
public AggregateCompleter() {
// empty
}
/**
* Construct an AggregateCompleter with the given collection of completers.
* The completers will be used in the iteration order of the collection.
*
* @param completers the collection of completers
*/
public AggregateCompleter(final Collection<Completer> completers) {
checkNotNull(completers);
this.completers.addAll(completers);
}
/**
* Construct an AggregateCompleter with the given completers.
* The completers will be used in the order given.
*
* @param completers the completers
*/
public AggregateCompleter(final Completer... completers) {
this(Arrays.asList(completers));
}
/**
* Retrieve the collection of completers currently being aggregated.
*
* @return the aggregated completers
*/
public Collection<Completer> getCompleters() {
return completers;
}
/**
* Perform a completion operation across all aggregated completers.
*
* @see Completer#complete(String, int, java.util.List)
* @return the highest completion return value from all completers
*/
public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
// buffer could be null
checkNotNull(candidates);
List<Completion> completions = new ArrayList<Completion>(completers.size());
// Run each completer, saving its completion results
int max = -1;
for (Completer completer : completers) {
Completion completion = new Completion(candidates);
completion.complete(completer, buffer, cursor);
// Compute the max cursor position
max = Math.max(max, completion.cursor);
completions.add(completion);
}
// Append candidates from completions which have the same cursor position as max
for (Completion completion : completions) {
if (completion.cursor == max) {
candidates.addAll(completion.candidates);
}
}
return max;
}
/**
* @return a string representing the aggregated completers
*/
@Override
public String toString() {
return getClass().getSimpleName() + "{" +
"completers=" + completers +
'}';
}
private class Completion
{
public final List<CharSequence> candidates;
public int cursor;
public Completion(final List<CharSequence> candidates) {
checkNotNull(candidates);
this.candidates = new LinkedList<CharSequence>(candidates);
}
public void complete(final Completer completer, final String buffer, final int cursor) {
checkNotNull(completer);
this.cursor = completer.complete(buffer, cursor, candidates);
}
}
}

@ -0,0 +1,461 @@
/*
* 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
*/
package jdk.internal.jline.console.completer;
import jdk.internal.jline.internal.Log;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
/**
* A {@link Completer} implementation that invokes a child completer using the appropriate <i>separator</i> argument.
* This can be used instead of the individual completers having to know about argument parsing semantics.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.3
*/
public class ArgumentCompleter
implements Completer
{
private final ArgumentDelimiter delimiter;
private final List<Completer> completers = new ArrayList<Completer>();
private boolean strict = true;
/**
* Create a new completer with the specified argument delimiter.
*
* @param delimiter The delimiter for parsing arguments
* @param completers The embedded completers
*/
public ArgumentCompleter(final ArgumentDelimiter delimiter, final Collection<Completer> completers) {
this.delimiter = checkNotNull(delimiter);
checkNotNull(completers);
this.completers.addAll(completers);
}
/**
* Create a new completer with the specified argument delimiter.
*
* @param delimiter The delimiter for parsing arguments
* @param completers The embedded completers
*/
public ArgumentCompleter(final ArgumentDelimiter delimiter, final Completer... completers) {
this(delimiter, Arrays.asList(completers));
}
/**
* Create a new completer with the default {@link WhitespaceArgumentDelimiter}.
*
* @param completers The embedded completers
*/
public ArgumentCompleter(final Completer... completers) {
this(new WhitespaceArgumentDelimiter(), completers);
}
/**
* Create a new completer with the default {@link WhitespaceArgumentDelimiter}.
*
* @param completers The embedded completers
*/
public ArgumentCompleter(final List<Completer> completers) {
this(new WhitespaceArgumentDelimiter(), completers);
}
/**
* If true, a completion at argument index N will only succeed
* if all the completions from 0-(N-1) also succeed.
*/
public void setStrict(final boolean strict) {
this.strict = strict;
}
/**
* Returns whether a completion at argument index N will success
* if all the completions from arguments 0-(N-1) also succeed.
*
* @return True if strict.
* @since 2.3
*/
public boolean isStrict() {
return this.strict;
}
/**
* @since 2.3
*/
public ArgumentDelimiter getDelimiter() {
return delimiter;
}
/**
* @since 2.3
*/
public List<Completer> getCompleters() {
return completers;
}
public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
// buffer can be null
checkNotNull(candidates);
ArgumentDelimiter delim = getDelimiter();
ArgumentList list = delim.delimit(buffer, cursor);
int argpos = list.getArgumentPosition();
int argIndex = list.getCursorArgumentIndex();
if (argIndex < 0) {
return -1;
}
List<Completer> completers = getCompleters();
Completer completer;
// if we are beyond the end of the completers, just use the last one
if (argIndex >= completers.size()) {
completer = completers.get(completers.size() - 1);
}
else {
completer = completers.get(argIndex);
}
// ensure that all the previous completers are successful before allowing this completer to pass (only if strict).
for (int i = 0; isStrict() && (i < argIndex); i++) {
Completer sub = completers.get(i >= completers.size() ? (completers.size() - 1) : i);
String[] args = list.getArguments();
String arg = (args == null || i >= args.length) ? "" : args[i];
List<CharSequence> subCandidates = new LinkedList<CharSequence>();
if (sub.complete(arg, arg.length(), subCandidates) == -1) {
return -1;
}
if (subCandidates.size() == 0) {
return -1;
}
}
int ret = completer.complete(list.getCursorArgument(), argpos, candidates);
if (ret == -1) {
return -1;
}
int pos = ret + list.getBufferPosition() - argpos;
// Special case: when completing in the middle of a line, and the area under the cursor is a delimiter,
// then trim any delimiters from the candidates, since we do not need to have an extra delimiter.
//
// E.g., if we have a completion for "foo", and we enter "f bar" into the buffer, and move to after the "f"
// and hit TAB, we want "foo bar" instead of "foo bar".
if ((cursor != buffer.length()) && delim.isDelimiter(buffer, cursor)) {
for (int i = 0; i < candidates.size(); i++) {
CharSequence val = candidates.get(i);
while (val.length() > 0 && delim.isDelimiter(val, val.length() - 1)) {
val = val.subSequence(0, val.length() - 1);
}
candidates.set(i, val);
}
}
Log.trace("Completing ", buffer, " (pos=", cursor, ") with: ", candidates, ": offset=", pos);
return pos;
}
/**
* The {@link ArgumentCompleter.ArgumentDelimiter} allows custom breaking up of a {@link String} into individual
* arguments in order to dispatch the arguments to the nested {@link Completer}.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
*/
public static interface ArgumentDelimiter
{
/**
* Break the specified buffer into individual tokens that can be completed on their own.
*
* @param buffer The buffer to split
* @param pos The current position of the cursor in the buffer
* @return The tokens
*/
ArgumentList delimit(CharSequence buffer, int pos);
/**
* Returns true if the specified character is a whitespace parameter.
*
* @param buffer The complete command buffer
* @param pos The index of the character in the buffer
* @return True if the character should be a delimiter
*/
boolean isDelimiter(CharSequence buffer, int pos);
}
/**
* Abstract implementation of a delimiter that uses the {@link #isDelimiter} method to determine if a particular
* character should be used as a delimiter.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
*/
public abstract static class AbstractArgumentDelimiter
implements ArgumentDelimiter
{
private char[] quoteChars = {'\'', '"'};
private char[] escapeChars = {'\\'};
public void setQuoteChars(final char[] chars) {
this.quoteChars = chars;
}
public char[] getQuoteChars() {
return this.quoteChars;
}
public void setEscapeChars(final char[] chars) {
this.escapeChars = chars;
}
public char[] getEscapeChars() {
return this.escapeChars;
}
public ArgumentList delimit(final CharSequence buffer, final int cursor) {
List<String> args = new LinkedList<String>();
StringBuilder arg = new StringBuilder();
int argpos = -1;
int bindex = -1;
int quoteStart = -1;
for (int i = 0; (buffer != null) && (i < buffer.length()); i++) {
// once we reach the cursor, set the
// position of the selected index
if (i == cursor) {
bindex = args.size();
// the position in the current argument is just the
// length of the current argument
argpos = arg.length();
}
if (quoteStart < 0 && isQuoteChar(buffer, i)) {
// Start a quote block
quoteStart = i;
} else if (quoteStart >= 0) {
// In a quote block
if (buffer.charAt(quoteStart) == buffer.charAt(i) && !isEscaped(buffer, i)) {
// End the block; arg could be empty, but that's fine
args.add(arg.toString());
arg.setLength(0);
quoteStart = -1;
} else if (!isEscapeChar(buffer, i)) {
// Take the next character
arg.append(buffer.charAt(i));
}
} else {
// Not in a quote block
if (isDelimiter(buffer, i)) {
if (arg.length() > 0) {
args.add(arg.toString());
arg.setLength(0); // reset the arg
}
} else if (!isEscapeChar(buffer, i)) {
arg.append(buffer.charAt(i));
}
}
}
if (cursor == buffer.length()) {
bindex = args.size();
// the position in the current argument is just the
// length of the current argument
argpos = arg.length();
}
if (arg.length() > 0) {
args.add(arg.toString());
}
return new ArgumentList(args.toArray(new String[args.size()]), bindex, argpos, cursor);
}
/**
* Returns true if the specified character is a whitespace parameter. Check to ensure that the character is not
* escaped by any of {@link #getQuoteChars}, and is not escaped by ant of the {@link #getEscapeChars}, and
* returns true from {@link #isDelimiterChar}.
*
* @param buffer The complete command buffer
* @param pos The index of the character in the buffer
* @return True if the character should be a delimiter
*/
public boolean isDelimiter(final CharSequence buffer, final int pos) {
return !isQuoted(buffer, pos) && !isEscaped(buffer, pos) && isDelimiterChar(buffer, pos);
}
public boolean isQuoted(final CharSequence buffer, final int pos) {
return false;
}
public boolean isQuoteChar(final CharSequence buffer, final int pos) {
if (pos < 0) {
return false;
}
for (int i = 0; (quoteChars != null) && (i < quoteChars.length); i++) {
if (buffer.charAt(pos) == quoteChars[i]) {
return !isEscaped(buffer, pos);
}
}
return false;
}
/**
* Check if this character is a valid escape char (i.e. one that has not been escaped)
*
* @param buffer
* @param pos
* @return
*/
public boolean isEscapeChar(final CharSequence buffer, final int pos) {
if (pos < 0) {
return false;
}
for (int i = 0; (escapeChars != null) && (i < escapeChars.length); i++) {
if (buffer.charAt(pos) == escapeChars[i]) {
return !isEscaped(buffer, pos); // escape escape
}
}
return false;
}
/**
* Check if a character is escaped (i.e. if the previous character is an escape)
*
* @param buffer
* the buffer to check in
* @param pos
* the position of the character to check
* @return true if the character at the specified position in the given buffer is an escape character and the character immediately preceding it is not an
* escape character.
*/
public boolean isEscaped(final CharSequence buffer, final int pos) {
if (pos <= 0) {
return false;
}
return isEscapeChar(buffer, pos - 1);
}
/**
* Returns true if the character at the specified position if a delimiter. This method will only be called if
* the character is not enclosed in any of the {@link #getQuoteChars}, and is not escaped by ant of the
* {@link #getEscapeChars}. To perform escaping manually, override {@link #isDelimiter} instead.
*/
public abstract boolean isDelimiterChar(CharSequence buffer, int pos);
}
/**
* {@link ArgumentCompleter.ArgumentDelimiter} implementation that counts all whitespace (as reported by
* {@link Character#isWhitespace}) as being a delimiter.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
*/
public static class WhitespaceArgumentDelimiter
extends AbstractArgumentDelimiter
{
/**
* The character is a delimiter if it is whitespace, and the
* preceding character is not an escape character.
*/
@Override
public boolean isDelimiterChar(final CharSequence buffer, final int pos) {
return Character.isWhitespace(buffer.charAt(pos));
}
}
/**
* The result of a delimited buffer.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
*/
public static class ArgumentList
{
private String[] arguments;
private int cursorArgumentIndex;
private int argumentPosition;
private int bufferPosition;
/**
* @param arguments The array of tokens
* @param cursorArgumentIndex The token index of the cursor
* @param argumentPosition The position of the cursor in the current token
* @param bufferPosition The position of the cursor in the whole buffer
*/
public ArgumentList(final String[] arguments, final int cursorArgumentIndex, final int argumentPosition, final int bufferPosition) {
this.arguments = checkNotNull(arguments);
this.cursorArgumentIndex = cursorArgumentIndex;
this.argumentPosition = argumentPosition;
this.bufferPosition = bufferPosition;
}
public void setCursorArgumentIndex(final int i) {
this.cursorArgumentIndex = i;
}
public int getCursorArgumentIndex() {
return this.cursorArgumentIndex;
}
public String getCursorArgument() {
if ((cursorArgumentIndex < 0) || (cursorArgumentIndex >= arguments.length)) {
return null;
}
return arguments[cursorArgumentIndex];
}
public void setArgumentPosition(final int pos) {
this.argumentPosition = pos;
}
public int getArgumentPosition() {
return this.argumentPosition;
}
public void setArguments(final String[] arguments) {
this.arguments = arguments;
}
public String[] getArguments() {
return this.arguments;
}
public void setBufferPosition(final int pos) {
this.bufferPosition = pos;
}
public int getBufferPosition() {
return this.bufferPosition;
}
}
}

@ -0,0 +1,194 @@
/*
* 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
*/
package jdk.internal.jline.console.completer;
import jdk.internal.jline.console.ConsoleReader;
import jdk.internal.jline.console.CursorBuffer;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.Set;
/**
* A {@link CompletionHandler} that deals with multiple distinct completions
* by outputting the complete list of possibilities to the console. This
* mimics the behavior of the
* <a href="http://www.gnu.org/directory/readline.html">readline</a> library.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.3
*/
public class CandidateListCompletionHandler
implements CompletionHandler
{
// TODO: handle quotes and escaped quotes && enable automatic escaping of whitespace
public boolean complete(final ConsoleReader reader, final List<CharSequence> candidates, final int pos) throws
IOException
{
CursorBuffer buf = reader.getCursorBuffer();
// if there is only one completion, then fill in the buffer
if (candidates.size() == 1) {
CharSequence value = candidates.get(0);
// fail if the only candidate is the same as the current buffer
if (value.equals(buf.toString())) {
return false;
}
setBuffer(reader, value, pos);
return true;
}
else if (candidates.size() > 1) {
String value = getUnambiguousCompletions(candidates);
setBuffer(reader, value, pos);
}
printCandidates(reader, candidates);
// redraw the current console buffer
reader.drawLine();
return true;
}
public static void setBuffer(final ConsoleReader reader, final CharSequence value, final int offset) throws
IOException
{
while ((reader.getCursorBuffer().cursor > offset) && reader.backspace()) {
// empty
}
reader.putString(value);
reader.setCursorPosition(offset + value.length());
}
/**
* Print out the candidates. If the size of the candidates is greater than the
* {@link ConsoleReader#getAutoprintThreshold}, they prompt with a warning.
*
* @param candidates the list of candidates to print
*/
public static void printCandidates(final ConsoleReader reader, Collection<CharSequence> candidates) throws
IOException
{
Set<CharSequence> distinct = new HashSet<CharSequence>(candidates);
if (distinct.size() > reader.getAutoprintThreshold()) {
//noinspection StringConcatenation
reader.print(Messages.DISPLAY_CANDIDATES.format(candidates.size()));
reader.flush();
int c;
String noOpt = Messages.DISPLAY_CANDIDATES_NO.format();
String yesOpt = Messages.DISPLAY_CANDIDATES_YES.format();
char[] allowed = {yesOpt.charAt(0), noOpt.charAt(0)};
while ((c = reader.readCharacter(allowed)) != -1) {
String tmp = new String(new char[]{(char) c});
if (noOpt.startsWith(tmp)) {
reader.println();
return;
}
else if (yesOpt.startsWith(tmp)) {
break;
}
else {
reader.beep();
}
}
}
// copy the values and make them distinct, without otherwise affecting the ordering. Only do it if the sizes differ.
if (distinct.size() != candidates.size()) {
Collection<CharSequence> copy = new ArrayList<CharSequence>();
for (CharSequence next : candidates) {
if (!copy.contains(next)) {
copy.add(next);
}
}
candidates = copy;
}
reader.println();
reader.printColumns(candidates);
}
/**
* Returns a root that matches all the {@link String} elements of the specified {@link List},
* or null if there are no commonalities. For example, if the list contains
* <i>foobar</i>, <i>foobaz</i>, <i>foobuz</i>, the method will return <i>foob</i>.
*/
private String getUnambiguousCompletions(final List<CharSequence> candidates) {
if (candidates == null || candidates.isEmpty()) {
return null;
}
// convert to an array for speed
String[] strings = candidates.toArray(new String[candidates.size()]);
String first = strings[0];
StringBuilder candidate = new StringBuilder();
for (int i = 0; i < first.length(); i++) {
if (startsWith(first.substring(0, i + 1), strings)) {
candidate.append(first.charAt(i));
}
else {
break;
}
}
return candidate.toString();
}
/**
* @return true is all the elements of <i>candidates</i> start with <i>starts</i>
*/
private boolean startsWith(final String starts, final String[] candidates) {
for (String candidate : candidates) {
if (!candidate.startsWith(starts)) {
return false;
}
}
return true;
}
private static enum Messages
{
DISPLAY_CANDIDATES,
DISPLAY_CANDIDATES_YES,
DISPLAY_CANDIDATES_NO,;
private static final
ResourceBundle
bundle =
ResourceBundle.getBundle(CandidateListCompletionHandler.class.getName(), Locale.getDefault());
public String format(final Object... args) {
if (bundle == null)
return "";
else
return String.format(bundle.getString(name()), args);
}
}
}

@ -0,0 +1,13 @@
#
# 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
#
DISPLAY_CANDIDATES=Display all %d possibilities? (y or n)
DISPLAY_CANDIDATES_YES=y
DISPLAY_CANDIDATES_NO=n
DISPLAY_MORE=--More--

@ -0,0 +1,38 @@
/*
* 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
*/
package jdk.internal.jline.console.completer;
import java.util.List;
/**
* A completer is the mechanism by which tab-completion candidates will be resolved.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.3
*/
public interface Completer
{
//
// FIXME: Check if we can use CharSequece for buffer?
//
/**
* Populates <i>candidates</i> with a list of possible completions for the <i>buffer</i>.
*
* The <i>candidates</i> list will not be sorted before being displayed to the user: thus, the
* complete method should sort the {@link List} before returning.
*
* @param buffer The buffer
* @param cursor The current position of the cursor in the <i>buffer</i>
* @param candidates The {@link List} of candidates to populate
* @return The index of the <i>buffer</i> for which the completion will be relative
*/
int complete(String buffer, int cursor, List<CharSequence> candidates);
}

@ -0,0 +1,26 @@
/*
* 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
*/
package jdk.internal.jline.console.completer;
import jdk.internal.jline.console.ConsoleReader;
import java.io.IOException;
import java.util.List;
/**
* Handler for dealing with candidates for tab-completion.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.3
*/
public interface CompletionHandler
{
boolean complete(ConsoleReader reader, List<CharSequence> candidates, int position) throws IOException;
}

@ -0,0 +1,29 @@
/*
* 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
*/
package jdk.internal.jline.console.completer;
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
/**
* {@link Completer} for {@link Enum} names.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.3
*/
public class EnumCompleter
extends StringsCompleter
{
public EnumCompleter(Class<? extends Enum<?>> source) {
checkNotNull(source);
for (Enum<?> n : source.getEnumConstants()) {
this.getStrings().add(n.name().toLowerCase());
}
}
}

@ -0,0 +1,133 @@
/*
* 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
*/
package jdk.internal.jline.console.completer;
import jdk.internal.jline.internal.Configuration;
import java.io.File;
import java.util.List;
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
/**
* A file name completer takes the buffer and issues a list of
* potential completions.
* <p/>
* This completer tries to behave as similar as possible to
* <i>bash</i>'s file name completion (using GNU readline)
* with the following exceptions:
* <p/>
* <ul>
* <li>Candidates that are directories will end with "/"</li>
* <li>Wildcard regular expressions are not evaluated or replaced</li>
* <li>The "~" character can be used to represent the user's home,
* but it cannot complete to other users' homes, since java does
* not provide any way of determining that easily</li>
* </ul>
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.3
*/
public class FileNameCompleter
implements Completer
{
// TODO: Handle files with spaces in them
private static final boolean OS_IS_WINDOWS;
static {
String os = Configuration.getOsName();
OS_IS_WINDOWS = os.contains("windows");
}
public int complete(String buffer, final int cursor, final List<CharSequence> candidates) {
// buffer can be null
checkNotNull(candidates);
if (buffer == null) {
buffer = "";
}
if (OS_IS_WINDOWS) {
buffer = buffer.replace('/', '\\');
}
String translated = buffer;
File homeDir = getUserHome();
// Special character: ~ maps to the user's home directory
if (translated.startsWith("~" + separator())) {
translated = homeDir.getPath() + translated.substring(1);
}
else if (translated.startsWith("~")) {
translated = homeDir.getParentFile().getAbsolutePath();
}
else if (!(new File(translated).isAbsolute())) {
String cwd = getUserDir().getAbsolutePath();
translated = cwd + separator() + translated;
}
File file = new File(translated);
final File dir;
if (translated.endsWith(separator())) {
dir = file;
}
else {
dir = file.getParentFile();
}
File[] entries = dir == null ? new File[0] : dir.listFiles();
return matchFiles(buffer, translated, entries, candidates);
}
protected String separator() {
return File.separator;
}
protected File getUserHome() {
return Configuration.getUserHome();
}
protected File getUserDir() {
return new File(".");
}
protected int matchFiles(final String buffer, final String translated, final File[] files, final List<CharSequence> candidates) {
if (files == null) {
return -1;
}
int matches = 0;
// first pass: just count the matches
for (File file : files) {
if (file.getAbsolutePath().startsWith(translated)) {
matches++;
}
}
for (File file : files) {
if (file.getAbsolutePath().startsWith(translated)) {
CharSequence name = file.getName() + (matches == 1 && file.isDirectory() ? separator() : " ");
candidates.add(render(file, name).toString());
}
}
final int index = buffer.lastIndexOf(separator());
return index + separator().length();
}
protected CharSequence render(final File file, final CharSequence name) {
return name;
}
}

@ -0,0 +1,28 @@
/*
* 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
*/
package jdk.internal.jline.console.completer;
import java.util.List;
/**
* Null completer.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.3
*/
public final class NullCompleter
implements Completer
{
public static final NullCompleter INSTANCE = new NullCompleter();
public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
return -1;
}
}

@ -0,0 +1,70 @@
/*
* 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
*/
package jdk.internal.jline.console.completer;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
/**
* Completer for a set of strings.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.3
*/
public class StringsCompleter
implements Completer
{
private final SortedSet<String> strings = new TreeSet<String>();
public StringsCompleter() {
// empty
}
public StringsCompleter(final Collection<String> strings) {
checkNotNull(strings);
getStrings().addAll(strings);
}
public StringsCompleter(final String... strings) {
this(Arrays.asList(strings));
}
public Collection<String> getStrings() {
return strings;
}
public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
// buffer could be null
checkNotNull(candidates);
if (buffer == null) {
candidates.addAll(strings);
}
else {
for (String match : strings.tailSet(buffer)) {
if (!match.startsWith(buffer)) {
break;
}
candidates.add(match);
}
}
if (candidates.size() == 1) {
candidates.set(0, candidates.get(0) + " ");
}
return candidates.isEmpty() ? -1 : 0;
}
}

@ -0,0 +1,14 @@
/*
* 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
*/
/**
* Console completer support.
*
* @since 2.3
*/
package jdk.internal.jline.console.completer;

@ -0,0 +1,106 @@
/*
* 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
*/
package jdk.internal.jline.console.history;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.Flushable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Reader;
import jdk.internal.jline.internal.Log;
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
/**
* {@link History} using a file for persistent backing.
* <p/>
* Implementers should install shutdown hook to call {@link FileHistory#flush}
* to save history to disk.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.0
*/
public class FileHistory
extends MemoryHistory
implements PersistentHistory, Flushable
{
private final File file;
public FileHistory(final File file) throws IOException {
this.file = checkNotNull(file);
load(file);
}
public File getFile() {
return file;
}
public void load(final File file) throws IOException {
checkNotNull(file);
if (file.exists()) {
Log.trace("Loading history from: ", file);
load(new FileReader(file));
}
}
public void load(final InputStream input) throws IOException {
checkNotNull(input);
load(new InputStreamReader(input));
}
public void load(final Reader reader) throws IOException {
checkNotNull(reader);
BufferedReader input = new BufferedReader(reader);
String item;
while ((item = input.readLine()) != null) {
internalAdd(item);
}
}
public void flush() throws IOException {
Log.trace("Flushing history");
if (!file.exists()) {
File dir = file.getParentFile();
if (!dir.exists() && !dir.mkdirs()) {
Log.warn("Failed to create directory: ", dir);
}
if (!file.createNewFile()) {
Log.warn("Failed to create file: ", file);
}
}
PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream(file)));
try {
for (Entry entry : this) {
out.println(entry.value());
}
}
finally {
out.close();
}
}
public void purge() throws IOException {
Log.trace("Purging history");
clear();
if (!file.delete()) {
Log.warn("Failed to delete history file: ", file);
}
}
}

@ -0,0 +1,106 @@
/*
* 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
*/
package jdk.internal.jline.console.history;
import java.util.Iterator;
import java.util.ListIterator;
/**
* Console history.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.3
*/
public interface History
extends Iterable<History.Entry>
{
int size();
boolean isEmpty();
int index();
void clear();
CharSequence get(int index);
void add(CharSequence line);
/**
* Set the history item at the given index to the given CharSequence.
*
* @param index the index of the history offset
* @param item the new item
* @since 2.7
*/
void set(int index, CharSequence item);
/**
* Remove the history element at the given index.
*
* @param i the index of the element to remove
* @return the removed element
* @since 2.7
*/
CharSequence remove(int i);
/**
* Remove the first element from history.
*
* @return the removed element
* @since 2.7
*/
CharSequence removeFirst();
/**
* Remove the last element from history
*
* @return the removed element
* @since 2.7
*/
CharSequence removeLast();
void replace(CharSequence item);
//
// Entries
//
interface Entry
{
int index();
CharSequence value();
}
ListIterator<Entry> entries(int index);
ListIterator<Entry> entries();
Iterator<Entry> iterator();
//
// Navigation
//
CharSequence current();
boolean previous();
boolean next();
boolean moveToFirst();
boolean moveToLast();
boolean moveTo(int index);
void moveToEnd();
}

@ -0,0 +1,348 @@
/*
* 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
*/
package jdk.internal.jline.console.history;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
/**
* Non-persistent {@link History}.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.3
*/
public class MemoryHistory
implements History
{
public static final int DEFAULT_MAX_SIZE = 500;
private final LinkedList<CharSequence> items = new LinkedList<CharSequence>();
private int maxSize = DEFAULT_MAX_SIZE;
private boolean ignoreDuplicates = true;
private boolean autoTrim = false;
// NOTE: These are all ideas from looking at the Bash man page:
// TODO: Add ignore space? (lines starting with a space are ignored)
// TODO: Add ignore patterns?
// TODO: Add history timestamp?
// TODO: Add erase dups?
private int offset = 0;
private int index = 0;
public void setMaxSize(final int maxSize) {
this.maxSize = maxSize;
maybeResize();
}
public int getMaxSize() {
return maxSize;
}
public boolean isIgnoreDuplicates() {
return ignoreDuplicates;
}
public void setIgnoreDuplicates(final boolean flag) {
this.ignoreDuplicates = flag;
}
public boolean isAutoTrim() {
return autoTrim;
}
public void setAutoTrim(final boolean flag) {
this.autoTrim = flag;
}
public int size() {
return items.size();
}
public boolean isEmpty() {
return items.isEmpty();
}
public int index() {
return offset + index;
}
public void clear() {
items.clear();
offset = 0;
index = 0;
}
public CharSequence get(final int index) {
return items.get(index - offset);
}
public void set(int index, CharSequence item) {
items.set(index - offset, item);
}
public void add(CharSequence item) {
checkNotNull(item);
if (isAutoTrim()) {
item = String.valueOf(item).trim();
}
if (isIgnoreDuplicates()) {
if (!items.isEmpty() && item.equals(items.getLast())) {
return;
}
}
internalAdd(item);
}
public CharSequence remove(int i) {
return items.remove(i);
}
public CharSequence removeFirst() {
return items.removeFirst();
}
public CharSequence removeLast() {
return items.removeLast();
}
protected void internalAdd(CharSequence item) {
items.add(item);
maybeResize();
}
public void replace(final CharSequence item) {
items.removeLast();
add(item);
}
private void maybeResize() {
while (size() > getMaxSize()) {
items.removeFirst();
offset++;
}
index = size();
}
public ListIterator<Entry> entries(final int index) {
return new EntriesIterator(index - offset);
}
public ListIterator<Entry> entries() {
return entries(offset);
}
public Iterator<Entry> iterator() {
return entries();
}
private static class EntryImpl
implements Entry
{
private final int index;
private final CharSequence value;
public EntryImpl(int index, CharSequence value) {
this.index = index;
this.value = value;
}
public int index() {
return index;
}
public CharSequence value() {
return value;
}
@Override
public String toString() {
return String.format("%d: %s", index, value);
}
}
private class EntriesIterator
implements ListIterator<Entry>
{
private final ListIterator<CharSequence> source;
private EntriesIterator(final int index) {
source = items.listIterator(index);
}
public Entry next() {
if (!source.hasNext()) {
throw new NoSuchElementException();
}
return new EntryImpl(offset + source.nextIndex(), source.next());
}
public Entry previous() {
if (!source.hasPrevious()) {
throw new NoSuchElementException();
}
return new EntryImpl(offset + source.previousIndex(), source.previous());
}
public int nextIndex() {
return offset + source.nextIndex();
}
public int previousIndex() {
return offset + source.previousIndex();
}
public boolean hasNext() {
return source.hasNext();
}
public boolean hasPrevious() {
return source.hasPrevious();
}
public void remove() {
throw new UnsupportedOperationException();
}
public void set(final Entry entry) {
throw new UnsupportedOperationException();
}
public void add(final Entry entry) {
throw new UnsupportedOperationException();
}
}
//
// Navigation
//
/**
* This moves the history to the last entry. This entry is one position
* before the moveToEnd() position.
*
* @return Returns false if there were no history entries or the history
* index was already at the last entry.
*/
public boolean moveToLast() {
int lastEntry = size() - 1;
if (lastEntry >= 0 && lastEntry != index) {
index = size() - 1;
return true;
}
return false;
}
/**
* Move to the specified index in the history
* @param index
* @return
*/
public boolean moveTo(int index) {
index -= offset;
if (index >= 0 && index < size() ) {
this.index = index;
return true;
}
return false;
}
/**
* Moves the history index to the first entry.
*
* @return Return false if there are no entries in the history or if the
* history is already at the beginning.
*/
public boolean moveToFirst() {
if (size() > 0 && index != 0) {
index = 0;
return true;
}
return false;
}
/**
* Move to the end of the history buffer. This will be a blank entry, after
* all of the other entries.
*/
public void moveToEnd() {
index = size();
}
/**
* Return the content of the current buffer.
*/
public CharSequence current() {
if (index >= size()) {
return "";
}
return items.get(index);
}
/**
* Move the pointer to the previous element in the buffer.
*
* @return true if we successfully went to the previous element
*/
public boolean previous() {
if (index <= 0) {
return false;
}
index--;
return true;
}
/**
* Move the pointer to the next element in the buffer.
*
* @return true if we successfully went to the next element
*/
public boolean next() {
if (index >= size()) {
return false;
}
index++;
return true;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (Entry e : this) {
sb.append(e.toString() + "\n");
}
return sb.toString();
}
}

@ -0,0 +1,35 @@
/*
* 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
*/
package jdk.internal.jline.console.history;
import java.io.IOException;
/**
* Persistent {@link History}.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.3
*/
public interface PersistentHistory
extends History
{
/**
* Flush all items to persistent storage.
*
* @throws IOException Flush failed
*/
void flush() throws IOException;
/**
* Purge persistent storage and {@link #clear}.
*
* @throws IOException Purge failed
*/
void purge() throws IOException;
}

@ -0,0 +1,14 @@
/*
* 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
*/
/**
* Console history support.
*
* @since 2.0
*/
package jdk.internal.jline.console.history;

@ -0,0 +1,123 @@
/*
* 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
*/
package jdk.internal.jline.console.internal;
import jdk.internal.jline.console.ConsoleReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.util.Enumeration;
// FIXME: Clean up API and move to jline.console.runner package
/**
* An {@link InputStream} implementation that wraps a {@link ConsoleReader}.
* It is useful for setting up the {@link System#in} for a generic console.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @since 2.7
*/
class ConsoleReaderInputStream
extends SequenceInputStream
{
private static InputStream systemIn = System.in;
public static void setIn() throws IOException {
setIn(new ConsoleReader());
}
public static void setIn(final ConsoleReader reader) {
System.setIn(new ConsoleReaderInputStream(reader));
}
/**
* Restore the original {@link System#in} input stream.
*/
public static void restoreIn() {
System.setIn(systemIn);
}
public ConsoleReaderInputStream(final ConsoleReader reader) {
super(new ConsoleEnumeration(reader));
}
private static class ConsoleEnumeration
implements Enumeration<InputStream>
{
private final ConsoleReader reader;
private ConsoleLineInputStream next = null;
private ConsoleLineInputStream prev = null;
public ConsoleEnumeration(final ConsoleReader reader) {
this.reader = reader;
}
public InputStream nextElement() {
if (next != null) {
InputStream n = next;
prev = next;
next = null;
return n;
}
return new ConsoleLineInputStream(reader);
}
public boolean hasMoreElements() {
// the last line was null
if ((prev != null) && (prev.wasNull == true)) {
return false;
}
if (next == null) {
next = (ConsoleLineInputStream) nextElement();
}
return next != null;
}
}
private static class ConsoleLineInputStream
extends InputStream
{
private final ConsoleReader reader;
private String line = null;
private int index = 0;
private boolean eol = false;
protected boolean wasNull = false;
public ConsoleLineInputStream(final ConsoleReader reader) {
this.reader = reader;
}
public int read() throws IOException {
if (eol) {
return -1;
}
if (line == null) {
line = reader.readLine();
}
if (line == null) {
wasNull = true;
return -1;
}
if (index >= line.length()) {
eol = true;
return '\n'; // lines are ended with a newline
}
return line.charAt(index++);
}
}
}

@ -0,0 +1,93 @@
/*
* 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
*/
package jdk.internal.jline.console.internal;
import jdk.internal.jline.console.ConsoleReader;
import jdk.internal.jline.console.completer.ArgumentCompleter;
import jdk.internal.jline.console.completer.Completer;
import jdk.internal.jline.console.history.FileHistory;
import jdk.internal.jline.internal.Configuration;
import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
// FIXME: Clean up API and move to jline.console.runner package
/**
* A pass-through application that sets the system input stream to a
* {@link ConsoleReader} and invokes the specified main method.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @since 2.7
*/
public class ConsoleRunner
{
public static final String property = "jline.history";
// FIXME: This is really ugly... re-write this
public static void main(final String[] args) throws Exception {
List<String> argList = new ArrayList<String>(Arrays.asList(args));
if (argList.size() == 0) {
usage();
return;
}
String historyFileName = System.getProperty(ConsoleRunner.property, null);
String mainClass = argList.remove(0);
ConsoleReader reader = new ConsoleReader();
if (historyFileName != null) {
reader.setHistory(new FileHistory(new File(Configuration.getUserHome(),
String.format(".jline-%s.%s.history", mainClass, historyFileName))));
}
else {
reader.setHistory(new FileHistory(new File(Configuration.getUserHome(),
String.format(".jline-%s.history", mainClass))));
}
String completors = System.getProperty(ConsoleRunner.class.getName() + ".completers", "");
List<Completer> completorList = new ArrayList<Completer>();
for (StringTokenizer tok = new StringTokenizer(completors, ","); tok.hasMoreTokens();) {
Object obj = Class.forName(tok.nextToken()).newInstance();
completorList.add((Completer) obj);
}
if (completorList.size() > 0) {
reader.addCompleter(new ArgumentCompleter(completorList));
}
ConsoleReaderInputStream.setIn(reader);
try {
Class<?> type = Class.forName(mainClass);
Method method = type.getMethod("main", String[].class);
method.invoke(null);
}
finally {
// just in case this main method is called from another program
ConsoleReaderInputStream.restoreIn();
}
}
private static void usage() {
System.out.println("Usage: \n java " + "[-Djline.history='name'] "
+ ConsoleRunner.class.getName()
+ " <target class name> [args]"
+ "\n\nThe -Djline.history option will avoid history"
+ "\nmangling when running ConsoleRunner on the same application."
+ "\n\nargs will be passed directly to the target class name.");
}
}

@ -0,0 +1,14 @@
/*
* 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
*/
/**
* Console support.
*
* @since 2.0
*/
package jdk.internal.jline.console;

@ -0,0 +1,239 @@
/*
* 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
*/
package jdk.internal.jline.internal;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.Properties;
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
/**
* Provides access to configuration values.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
* @since 2.4
*/
public class Configuration
{
/**
* System property which can point to a file or URL containing configuration properties to load.
*
* @since 2.7
*/
public static final String JLINE_CONFIGURATION = "jline.configuration";
/**
* Default configuration file name loaded from user's home directory.
*/
public static final String JLINE_RC = ".jline.rc";
private static volatile Properties properties;
private static Properties initProperties() {
URL url = determineUrl();
Properties props = new Properties();
try {
loadProperties(url, props);
}
catch (IOException e) {
// debug here instead of warn, as this can happen normally if default jline.rc file is missing
Log.debug("Unable to read configuration from: ", url, e);
}
return props;
}
private static void loadProperties(final URL url, final Properties props) throws IOException {
Log.debug("Loading properties from: ", url);
InputStream input = url.openStream();
try {
props.load(new BufferedInputStream(input));
}
finally {
try {
input.close();
}
catch (IOException e) {
// ignore
}
}
if (Log.DEBUG) {
Log.debug("Loaded properties:");
for (Map.Entry<Object,Object> entry : props.entrySet()) {
Log.debug(" ", entry.getKey(), "=", entry.getValue());
}
}
}
private static URL determineUrl() {
// See if user has customized the configuration location via sysprop
String tmp = System.getProperty(JLINE_CONFIGURATION);
if (tmp != null) {
return Urls.create(tmp);
}
else {
// Otherwise try the default
File file = new File(getUserHome(), JLINE_RC);
return Urls.create(file);
}
}
/**
* @since 2.7
*/
public static void reset() {
Log.debug("Resetting");
properties = null;
// force new properties to load
getProperties();
}
/**
* @since 2.7
*/
public static Properties getProperties() {
// Not sure its worth to guard this with any synchronization, volatile field probably sufficient
if (properties == null) {
properties = initProperties();
}
return properties;
}
public static String getString(final String name, final String defaultValue) {
checkNotNull(name);
String value;
// Check sysprops first, it always wins
value = System.getProperty(name);
if (value == null) {
// Next try userprops
value = getProperties().getProperty(name);
if (value == null) {
// else use the default
value = defaultValue;
}
}
return value;
}
public static String getString(final String name) {
return getString(name, null);
}
public static boolean getBoolean(final String name, final boolean defaultValue) {
String value = getString(name);
if (value == null) {
return defaultValue;
}
return value.length() == 0
|| value.equalsIgnoreCase("1")
|| value.equalsIgnoreCase("on")
|| value.equalsIgnoreCase("true");
}
/**
* @since 2.6
*/
public static int getInteger(final String name, final int defaultValue) {
String str = getString(name);
if (str == null) {
return defaultValue;
}
return Integer.parseInt(str);
}
/**
* @since 2.6
*/
public static long getLong(final String name, final long defaultValue) {
String str = getString(name);
if (str == null) {
return defaultValue;
}
return Long.parseLong(str);
}
//
// System property helpers
//
/**
* @since 2.7
*/
public static String getLineSeparator() {
return System.getProperty("line.separator");
}
public static File getUserHome() {
return new File(System.getProperty("user.home"));
}
public static String getOsName() {
return System.getProperty("os.name").toLowerCase();
}
/**
* @since 2.7
*/
public static boolean isWindows() {
return getOsName().startsWith("windows");
}
// FIXME: Sort out use of property access of file.encoding in InputStreamReader, consolidate should configuration access here
public static String getFileEncoding() {
return System.getProperty("file.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 getEncoding() {
// 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
*/
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('@'));
} else {
return encodingAndModifier;
}
}
return null;
}
}

@ -0,0 +1,338 @@
/*
* 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
*/
package jdk.internal.jline.internal;
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
*/
public 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()
encoding = System.getProperty("file.encoding", "ISO8859_1"); //$NON-NLS-1$//$NON-NLS-2$
decoder = Charset.forName(encoding).newDecoder().onMalformedInput(
CodingErrorAction.REPLACE).onUnmappableCharacter(
CodingErrorAction.REPLACE);
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 {
decoder = Charset.forName(enc).newDecoder().onMalformedInput(
CodingErrorAction.REPLACE).onUnmappableCharacter(
CodingErrorAction.REPLACE);
} catch (IllegalArgumentException e) {
throw (UnsupportedEncodingException)
new UnsupportedEncodingException(enc).initCause(e);
}
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;
decoder = dec;
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;
decoder = charset.newDecoder().onMalformedInput(
CodingErrorAction.REPLACE).onUnmappableCharacter(
CodingErrorAction.REPLACE);
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 (lock) {
decoder = null;
if (in != null) {
in.close();
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 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 (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 (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 = !bytes.hasRemaining();
while (out.hasRemaining()) {
// fill the buffer if needed
if (needInput) {
try {
if ((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 = bytes.capacity() - bytes.limit();
int off = bytes.arrayOffset() + bytes.limit();
int was_red = in.read(bytes.array(), off, to_read);
if (was_red == -1) {
endOfInput = true;
break;
} else if (was_red == 0) {
break;
}
bytes.limit(bytes.limit() + was_red);
needInput = false;
}
// decode bytes
result = decoder.decode(bytes, out, false);
if (result.isUnderflow()) {
// compact the buffer if no space left
if (bytes.limit() == bytes.capacity()) {
bytes.compact();
bytes.limit(bytes.position());
bytes.position(0);
}
needInput = true;
} else {
break;
}
}
if (result == CoderResult.UNDERFLOW && endOfInput) {
result = decoder.decode(bytes, out, true);
decoder.flush(out);
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 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 (lock) {
if (in == null) {
throw new IOException("InputStreamReader is closed.");
}
try {
return bytes.hasRemaining() || in.available() > 0;
} catch (IOException e) {
return false;
}
}
}
}

@ -0,0 +1,120 @@
/*
* 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
*/
package jdk.internal.jline.internal;
import java.io.PrintStream;
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
/**
* Internal logger.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.0
*/
public final class Log
{
///CLOVER:OFF
public static enum Level
{
TRACE,
DEBUG,
INFO,
WARN,
ERROR
}
@SuppressWarnings({"StringConcatenation"})
public static final boolean TRACE = Boolean.getBoolean(Log.class.getName() + ".trace");
@SuppressWarnings({"StringConcatenation"})
public static final boolean DEBUG = TRACE || Boolean.getBoolean(Log.class.getName() + ".debug");
private static PrintStream output = System.err;
public static PrintStream getOutput() {
return output;
}
public static void setOutput(final PrintStream out) {
output = checkNotNull(out);
}
/**
* Helper to support rendering messages.
*/
@TestAccessible
static void render(final PrintStream out, final Object message) {
if (message.getClass().isArray()) {
Object[] array = (Object[]) message;
out.print("[");
for (int i = 0; i < array.length; i++) {
out.print(array[i]);
if (i + 1 < array.length) {
out.print(",");
}
}
out.print("]");
}
else {
out.print(message);
}
}
@TestAccessible
static void log(final Level level, final Object... messages) {
//noinspection SynchronizeOnNonFinalField
synchronized (output) {
output.format("[%s] ", level);
for (int i=0; i<messages.length; i++) {
// Special handling for the last message if its a throwable, render its stack on the next line
if (i + 1 == messages.length && messages[i] instanceof Throwable) {
output.println();
((Throwable)messages[i]).printStackTrace(output);
}
else {
render(output, messages[i]);
}
}
output.println();
output.flush();
}
}
public static void trace(final Object... messages) {
if (TRACE) {
log(Level.TRACE, messages);
}
}
public static void debug(final Object... messages) {
if (TRACE || DEBUG) {
log(Level.DEBUG, messages);
}
}
/**
* @since 2.7
*/
public static void info(final Object... messages) {
log(Level.INFO, messages);
}
public static void warn(final Object... messages) {
log(Level.WARN, messages);
}
public static void error(final Object... messages) {
log(Level.ERROR, messages);
}
}

@ -0,0 +1,314 @@
/*
* 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
*/
package jdk.internal.jline.internal;
import java.io.IOException;
import java.io.InputStream;
/**
* This class wraps a regular input stream and allows it to appear as if it
* is non-blocking; that is, reads can be performed against it that timeout
* if no data is seen for a period of time. This effect is achieved by having
* a separate thread perform all non-blocking read requests and then
* waiting on the thread to complete.
*
* <p>VERY IMPORTANT NOTES
* <ul>
* <li> This class is not thread safe. It expects at most one reader.
* <li> The {@link #shutdown()} method must be called in order to shut down
* the thread that handles blocking I/O.
* </ul>
* @since 2.7
* @author Scott C. Gray <scottgray1@gmail.com>
*/
public class NonBlockingInputStream
extends InputStream
implements Runnable
{
private InputStream in; // The actual input stream
private int ch = -2; // Recently read character
private boolean threadIsReading = false;
private boolean isShutdown = false;
private IOException exception = null;
private boolean nonBlockingEnabled;
/**
* Creates a <code>NonBlockingInputStream</code> out of a normal blocking
* stream. Note that this call also spawn a separate thread to perform the
* blocking I/O on behalf of the thread that is using this class. The
* {@link #shutdown()} method must be called in order to shut this thread down.
* @param in The input stream to wrap
* @param isNonBlockingEnabled If true, then the non-blocking methods
* {@link #read(long)} and {@link #peek(long)} will be available and,
* more importantly, the thread will be started to provide support for the
* feature. If false, then this class acts as a clean-passthru for the
* underlying I/O stream and provides very little overhead.
*/
public NonBlockingInputStream (InputStream in, boolean isNonBlockingEnabled) {
this.in = in;
this.nonBlockingEnabled = isNonBlockingEnabled;
if (isNonBlockingEnabled) {
Thread t = new Thread(this);
t.setName("NonBlockingInputStreamThread");
t.setDaemon(true);
t.start();
}
}
/**
* Shuts down the thread that is handling blocking I/O. Note that if the
* thread is currently blocked waiting for I/O it will not actually
* shut down until the I/O is received. Shutting down the I/O thread
* does not prevent this class from being used, but causes the
* non-blocking methods to fail if called and causes {@link #isNonBlockingEnabled()}
* to return false.
*/
public synchronized void shutdown() {
if (!isShutdown && nonBlockingEnabled) {
isShutdown = true;
notify();
}
}
/**
* Non-blocking is considered enabled if the feature is enabled and the
* I/O thread has not been shut down.
* @return true if non-blocking mode is enabled.
*/
public boolean isNonBlockingEnabled() {
return nonBlockingEnabled && !isShutdown;
}
@Override
public void close() throws IOException {
/*
* The underlying input stream is closed first. This means that if the
* I/O thread was blocked waiting on input, it will be woken for us.
*/
in.close();
shutdown();
}
@Override
public int read() throws IOException {
if (nonBlockingEnabled)
return read(0L, false);
return in.read ();
}
/**
* Peeks to see if there is a byte waiting in the input stream without
* actually consuming the byte.
*
* @param timeout The amount of time to wait, 0 == forever
* @return -1 on eof, -2 if the timeout expired with no available input
* or the character that was read (without consuming it).
* @throws IOException
*/
public int peek(long timeout) throws IOException {
if (!nonBlockingEnabled || isShutdown) {
throw new UnsupportedOperationException ("peek() "
+ "cannot be called as non-blocking operation is disabled");
}
return read(timeout, true);
}
/**
* Attempts to read a character from the input stream for a specific
* period of time.
* @param timeout The amount of time to wait for the character
* @return The character read, -1 if EOF is reached, or -2 if the
* read timed out.
* @throws IOException
*/
public int read(long timeout) throws IOException {
if (!nonBlockingEnabled || isShutdown) {
throw new UnsupportedOperationException ("read() with timeout "
+ "cannot be called as non-blocking operation is disabled");
}
return read(timeout, false);
}
/**
* Attempts to read a character from the input stream for a specific
* period of time.
* @param timeout The amount of time to wait for the character
* @return The character read, -1 if EOF is reached, or -2 if the
* read timed out.
* @throws IOException
*/
private synchronized int read(long timeout, boolean isPeek) throws IOException {
/*
* If the thread hit an IOException, we report it.
*/
if (exception != null) {
assert ch == -2;
IOException toBeThrown = exception;
if (!isPeek)
exception = null;
throw toBeThrown;
}
/*
* If there was a pending character from the thread, then
* we send it. If the timeout is 0L or the thread was shut down
* then do a local read.
*/
if (ch >= -1) {
assert exception == null;
}
else if ((timeout == 0L || isShutdown) && !threadIsReading) {
ch = in.read();
}
else {
/*
* If the thread isn't reading already, then ask it to do so.
*/
if (!threadIsReading) {
threadIsReading = true;
notify();
}
boolean isInfinite = (timeout <= 0L);
/*
* So the thread is currently doing the reading for us. So
* now we play the waiting game.
*/
while (isInfinite || timeout > 0L) {
long start = System.currentTimeMillis ();
try {
wait(timeout);
}
catch (InterruptedException e) {
/* IGNORED */
}
if (exception != null) {
assert ch == -2;
IOException toBeThrown = exception;
if (!isPeek)
exception = null;
throw toBeThrown;
}
if (ch >= -1) {
assert exception == null;
break;
}
if (!isInfinite) {
timeout -= System.currentTimeMillis() - start;
}
}
}
/*
* ch is the character that was just read. Either we set it because
* a local read was performed or the read thread set it (or failed to
* change it). We will return it's value, but if this was a peek
* operation, then we leave it in place.
*/
int ret = ch;
if (!isPeek) {
ch = -2;
}
return ret;
}
/**
* This version of read() is very specific to jline's purposes, it
* will always always return a single byte at a time, rather than filling
* the entire buffer.
*/
@Override
public int read (byte[] b, int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c;
if (nonBlockingEnabled)
c = this.read(0L);
else
c = in.read();
if (c == -1) {
return -1;
}
b[off] = (byte)c;
return 1;
}
//@Override
public void run () {
Log.debug("NonBlockingInputStream start");
boolean needToShutdown = false;
boolean needToRead = false;
while (!needToShutdown) {
/*
* Synchronize to grab variables accessed by both this thread
* and the accessing thread.
*/
synchronized (this) {
needToShutdown = this.isShutdown;
needToRead = this.threadIsReading;
try {
/*
* Nothing to do? Then wait.
*/
if (!needToShutdown && !needToRead) {
wait(0);
}
}
catch (InterruptedException e) {
/* IGNORED */
}
}
/*
* We're not shutting down, but we need to read. This cannot
* happen while we are holding the lock (which we aren't now).
*/
if (!needToShutdown && needToRead) {
int charRead = -2;
IOException failure = null;
try {
charRead = in.read();
}
catch (IOException e) {
failure = e;
}
/*
* Re-grab the lock to update the state.
*/
synchronized (this) {
exception = failure;
ch = charRead;
threadIsReading = false;
notify();
}
}
}
Log.debug("NonBlockingInputStream shutdown");
}
}

@ -0,0 +1,24 @@
/*
* 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
*/
package jdk.internal.jline.internal;
import java.lang.annotation.*;
/**
* Marker for reference which can be a null value.
*
* @since 2.7
*/
@Documented
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
public @interface Nullable
{
String value() default "";
}

@ -0,0 +1,27 @@
/*
* 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
*/
package jdk.internal.jline.internal;
// Some bits lifted from Guava's ( http://code.google.com/p/guava-libraries/ ) Preconditions.
/**
* Preconditions.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.7
*/
public class Preconditions
{
public static <T> T checkNotNull(final T reference) {
if (reference == null) {
throw new NullPointerException();
}
return reference;
}
}

@ -0,0 +1,128 @@
/*
* 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
*/
package jdk.internal.jline.internal;
import java.util.ArrayList;
import java.util.List;
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
/**
* Manages the JLine shutdown-hook thread and tasks to execute on shutdown.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.7
*/
public class ShutdownHooks
{
public static final String JLINE_SHUTDOWNHOOK = "jline.shutdownhook";
private static final boolean enabled = Configuration.getBoolean(JLINE_SHUTDOWNHOOK, true);
private static final List<Task> tasks = new ArrayList<Task>();
private static Thread hook;
public static synchronized <T extends Task> T add(final T task) {
checkNotNull(task);
// If not enabled ignore
if (!enabled) {
Log.debug("Shutdown-hook is disabled; not installing: ", task);
return task;
}
// Install the hook thread if needed
if (hook == null) {
hook = addHook(new Thread("JLine Shutdown Hook")
{
@Override
public void run() {
runTasks();
}
});
}
// Track the task
Log.debug("Adding shutdown-hook task: ", task);
tasks.add(task);
return task;
}
private static synchronized void runTasks() {
Log.debug("Running all shutdown-hook tasks");
// Iterate through copy of tasks list
for (Task task : tasks.toArray(new Task[tasks.size()])) {
Log.debug("Running task: ", task);
try {
task.run();
}
catch (Throwable e) {
Log.warn("Task failed", e);
}
}
tasks.clear();
}
private static Thread addHook(final Thread thread) {
Log.debug("Registering shutdown-hook: ", thread);
try {
Runtime.getRuntime().addShutdownHook(thread);
}
catch (AbstractMethodError e) {
// JDK 1.3+ only method. Bummer.
Log.debug("Failed to register shutdown-hook", e);
}
return thread;
}
public static synchronized void remove(final Task task) {
checkNotNull(task);
// ignore if not enabled or hook never installed
if (!enabled || hook == null) {
return;
}
// Drop the task
tasks.remove(task);
// If there are no more tasks, then remove the hook thread
if (tasks.isEmpty()) {
removeHook(hook);
hook = null;
}
}
private static void removeHook(final Thread thread) {
Log.debug("Removing shutdown-hook: ", thread);
try {
Runtime.getRuntime().removeShutdownHook(thread);
}
catch (AbstractMethodError e) {
// JDK 1.3+ only method. Bummer.
Log.debug("Failed to remove shutdown-hook", e);
}
catch (IllegalStateException e) {
// The VM is shutting down, not a big deal; ignore
}
}
/**
* Essentially a {@link Runnable} which allows running to throw an exception.
*/
public static interface Task
{
void run() throws Exception;
}
}

@ -0,0 +1,236 @@
/*
* 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
*/
package jdk.internal.jline.internal;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
/**
* Provides access to terminal line settings via <tt>stty</tt>.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @author <a href="mailto:dwkemp@gmail.com">Dale Kemp</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @author <a href="mailto:jbonofre@apache.org">Jean-Baptiste Onofr\u00E9</a>
* @since 2.0
*/
public final class TerminalLineSettings
{
public static final String JLINE_STTY = "jline.stty";
public static final String DEFAULT_STTY = "stty";
public static final String JLINE_SH = "jline.sh";
public static final String DEFAULT_SH = "sh";
private String sttyCommand;
private String shCommand;
private String config;
private String initialConfig;
private long configLastFetched;
public TerminalLineSettings() throws IOException, InterruptedException {
sttyCommand = Configuration.getString(JLINE_STTY, DEFAULT_STTY);
shCommand = Configuration.getString(JLINE_SH, DEFAULT_SH);
initialConfig = get("-g").trim();
config = get("-a");
configLastFetched = System.currentTimeMillis();
Log.debug("Config: ", config);
// sanity check
if (config.length() == 0) {
throw new IOException(MessageFormat.format("Unrecognized stty code: {0}", config));
}
}
public String getConfig() {
return config;
}
public void restore() throws IOException, InterruptedException {
set(initialConfig);
}
public String get(final String args) throws IOException, InterruptedException {
return stty(args);
}
public void set(final String args) throws IOException, InterruptedException {
stty(args);
}
/**
* <p>
* Get the value of a stty property, including the management of a cache.
* </p>
*
* @param name the stty property.
* @return the stty property value.
*/
public int getProperty(String name) {
checkNotNull(name);
long currentTime = System.currentTimeMillis();
try {
// tty properties are cached so we don't have to worry too much about getting term width/height
if (config == null || currentTime - configLastFetched > 1000) {
config = get("-a");
}
} catch (Exception e) {
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
Log.debug("Failed to query stty ", name, "\n", e);
if (config == null) {
return -1;
}
}
// always update the last fetched time and try to parse the output
if (currentTime - configLastFetched > 1000) {
configLastFetched = currentTime;
}
return getProperty(name, config);
}
/**
* <p>
* Parses a stty output (provided by stty -a) and return the value of a given property.
* </p>
*
* @param name property name.
* @param stty string resulting of stty -a execution.
* @return value of the given property.
*/
protected static int getProperty(String name, String stty) {
// try the first kind of regex
Pattern pattern = Pattern.compile(name + "\\s+=\\s+(.*?)[;\\n\\r]");
Matcher matcher = pattern.matcher(stty);
if (!matcher.find()) {
// try a second kind of regex
pattern = Pattern.compile(name + "\\s+([^;]*)[;\\n\\r]");
matcher = pattern.matcher(stty);
if (!matcher.find()) {
// try a second try of regex
pattern = Pattern.compile("(\\S*)\\s+" + name);
matcher = pattern.matcher(stty);
if (!matcher.find()) {
return -1;
}
}
}
return parseControlChar(matcher.group(1));
}
private static int parseControlChar(String str) {
// under
if ("<undef>".equals(str)) {
return -1;
}
// octal
if (str.charAt(0) == '0') {
return Integer.parseInt(str, 8);
}
// decimal
if (str.charAt(0) >= '1' && str.charAt(0) <= '9') {
return Integer.parseInt(str, 10);
}
// control char
if (str.charAt(0) == '^') {
if (str.charAt(1) == '?') {
return 127;
} else {
return str.charAt(1) - 64;
}
} else if (str.charAt(0) == 'M' && str.charAt(1) == '-') {
if (str.charAt(2) == '^') {
if (str.charAt(3) == '?') {
return 127 + 128;
} else {
return str.charAt(3) - 64 + 128;
}
} else {
return str.charAt(2) + 128;
}
} else {
return str.charAt(0);
}
}
private String stty(final String args) throws IOException, InterruptedException {
checkNotNull(args);
return exec(String.format("%s %s < /dev/tty", sttyCommand, args));
}
private String exec(final String cmd) throws IOException, InterruptedException {
checkNotNull(cmd);
return exec(shCommand, "-c", cmd);
}
private String exec(final String... cmd) throws IOException, InterruptedException {
checkNotNull(cmd);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
Log.trace("Running: ", cmd);
Process p = Runtime.getRuntime().exec(cmd);
InputStream in = null;
InputStream err = null;
OutputStream out = null;
try {
int c;
in = p.getInputStream();
while ((c = in.read()) != -1) {
bout.write(c);
}
err = p.getErrorStream();
while ((c = err.read()) != -1) {
bout.write(c);
}
out = p.getOutputStream();
p.waitFor();
}
finally {
close(in, out, err);
}
String result = bout.toString();
Log.trace("Result: ", result);
return result;
}
private static void close(final Closeable... closeables) {
for (Closeable c : closeables) {
try {
c.close();
}
catch (Exception e) {
// Ignore
}
}
}
}

@ -0,0 +1,33 @@
/*
* 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
*/
package jdk.internal.jline.internal;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Marker annotation for members which are exposed for testing access.
*
* @since 2.7
*/
@Retention(RUNTIME)
@Target({TYPE, CONSTRUCTOR, METHOD, FIELD, PARAMETER})
@Documented
public @interface TestAccessible
{
// empty
}

@ -0,0 +1,44 @@
/*
* 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
*/
package jdk.internal.jline.internal;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
/**
* URL helpers.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
* @since 2.7
*/
public class Urls
{
public static URL create(final String input) {
if (input == null) {
return null;
}
try {
return new URL(input);
}
catch (MalformedURLException e) {
return create(new File(input));
}
}
public static URL create(final File file) {
try {
return file != null ? file.toURI().toURL() : null;
}
catch (MalformedURLException e) {
throw new IllegalStateException(e);
}
}
}

@ -0,0 +1,14 @@
/*
* 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
*/
/**
* Internal support.
*
* @since 2.0
*/
package jdk.internal.jline.internal;

@ -0,0 +1,14 @@
/*
* 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
*/
/**
* JLine 2.
*
* @since 2.0
*/
package jdk.internal.jline;

@ -0,0 +1,127 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "jni.h"
#include "jni_util.h"
#include "jvm.h"
#include "jdk_internal_jline_WindowsTerminal.h"
#include <stdlib.h>
#include <Wincon.h>
#include <Winuser.h>
static jclass recordClass;
static jmethodID recordConstructor;
JNIEXPORT void JNICALL Java_jdk_internal_jline_WindowsTerminal_initIDs
(JNIEnv *env, jclass) {
jclass cls = env->FindClass("jdk/internal/jline/WindowsTerminal$KEY_EVENT_RECORD");
CHECK_NULL(cls);
recordClass = (jclass) env->NewGlobalRef(cls);
CHECK_NULL(recordClass);
recordConstructor = env->GetMethodID(cls, "<init>", "(ZCIII)V");
CHECK_NULL(recordConstructor);
}
JNIEXPORT jint JNICALL Java_jdk_internal_jline_WindowsTerminal_getConsoleMode
(JNIEnv *, jobject) {
HANDLE hStdIn;
if ((hStdIn = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE) {
return -1;
}
DWORD fdwMode;
if (! GetConsoleMode(hStdIn, &fdwMode)) {
return -1;
}
return fdwMode;
}
JNIEXPORT void JNICALL Java_jdk_internal_jline_WindowsTerminal_setConsoleMode
(JNIEnv *, jobject, jint mode) {
HANDLE hStdIn;
if ((hStdIn = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE) {
return ;
}
DWORD fdwMode = mode;
SetConsoleMode(hStdIn, fdwMode);
}
JNIEXPORT jobject JNICALL Java_jdk_internal_jline_WindowsTerminal_readKeyEvent
(JNIEnv *env, jobject) {
HANDLE hStdIn;
if ((hStdIn = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE) {
return NULL;
}
INPUT_RECORD record;
DWORD n;
while (TRUE) {
if (ReadConsoleInput(hStdIn, &record, 1, &n) == 0) {
return NULL;
}
if (record.EventType == KEY_EVENT) {
jclass clazz = env->FindClass("jdk/internal/jline/WindowsTerminal$KEY_EVENT_RECORD");
jmethodID constr = env->GetMethodID(clazz, "<init>", "(ZCIII)V");
return env->NewObject(recordClass,
recordConstructor,
record.Event.KeyEvent.bKeyDown,
record.Event.KeyEvent.uChar.UnicodeChar,
record.Event.KeyEvent.dwControlKeyState,
record.Event.KeyEvent.wVirtualKeyCode,
record.Event.KeyEvent.wRepeatCount);
}
continue;
}
}
JNIEXPORT jint JNICALL Java_jdk_internal_jline_WindowsTerminal_getConsoleOutputCodepage
(JNIEnv *, jobject) {
return GetConsoleCP();
}
JNIEXPORT jint JNICALL Java_jdk_internal_jline_WindowsTerminal_getWindowsTerminalWidth
(JNIEnv *, jobject) {
HANDLE hStdIn;
if ((hStdIn = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE) {
return -1;
}
CONSOLE_SCREEN_BUFFER_INFO info;
if (! GetConsoleScreenBufferInfo(hStdIn, &info)) {
return -1;
}
return info.dwSize.X;
}
JNIEXPORT jint JNICALL Java_jdk_internal_jline_WindowsTerminal_getWindowsTerminalHeight
(JNIEnv *, jobject) {
HANDLE hStdIn;
if ((hStdIn = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE) {
return -1;
}
CONSOLE_SCREEN_BUFFER_INFO info;
if (! GetConsoleScreenBufferInfo(hStdIn, &info)) {
return -1;
}
return info.dwSize.Y;
}

@ -243,6 +243,7 @@ jdk_other = \
javax/xml \
-javax/xml/crypto \
jdk/asm \
jdk/internal/jline \
com/sun/jndi \
com/sun/corba \
lib/testlibrary \

@ -0,0 +1,59 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8080679
* @summary Verify the conversion from key events to escape sequences works properly.
* @requires os.family == "windows"
*/
import jdk.internal.jline.WindowsTerminal;
import jdk.internal.jline.WindowsTerminal.KEY_EVENT_RECORD;
public class KeyConversionTest {
public static void main(String... args) throws Exception {
new KeyConversionTest().run();
}
void run() throws Exception {
checkKeyConversion(new KEY_EVENT_RECORD(true, '\0', 256, 37, 1), "\033[D"); //LEFT
checkKeyConversion(new KEY_EVENT_RECORD(true, '\0', 264, 37, 1), "\033[1;5D"); //Ctrl-LEFT
checkKeyConversion(new KEY_EVENT_RECORD(true, '\0', 258, 37, 1), "\033[1;3D"); //Alt-LEFT
checkKeyConversion(new KEY_EVENT_RECORD(true, '\0', 256, 46, 1), "\033[3~"); //delete
checkKeyConversion(new KEY_EVENT_RECORD(true, '\0', 264, 46, 1), "\033[3;5~"); //Ctrl-delete
checkKeyConversion(new KEY_EVENT_RECORD(true, '\0', 258, 46, 1), "\033[3;3~"); //Alt-delete
checkKeyConversion(new KEY_EVENT_RECORD(true, '\0', 272, 46, 1), "\033[3;2~"); //Shift-delete
checkKeyConversion(new KEY_EVENT_RECORD(true, '\0', 280, 46, 1), "\033[3;6~"); //Ctrl-Shift-delete
checkKeyConversion(new KEY_EVENT_RECORD(true, '\0', 274, 46, 1), "\033[3;4~"); //Alt-Shift-delete
checkKeyConversion(new KEY_EVENT_RECORD(true, '\0', 282, 46, 1), "\033[3;8~"); //Ctrl-Alt-Shift-delete
}
void checkKeyConversion(KEY_EVENT_RECORD event, String expected) {
String actual = WindowsTerminal.convertKeys(event);
if (!expected.equals(actual)) {
throw new AssertionError("Expected: " + expected + "; actual: " + actual);
}
}
}

@ -0,0 +1,56 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8080679
* @summary Verify ConsoleReader.stripAnsi strips escape sequences from its input correctly.
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Method;
import jdk.internal.jline.console.ConsoleReader;
public class StripAnsiTest {
public static void main(String... args) throws Exception {
new StripAnsiTest().run();
}
void run() throws Exception {
ByteArrayInputStream in = new ByteArrayInputStream(new byte[0]);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ConsoleReader reader = new ConsoleReader(in, out);
String withAnsi = "0\033[s1\033[2J2\033[37;4m3";
String expected = "0123";
Method stripAnsi = ConsoleReader.class.getDeclaredMethod("stripAnsi", String.class);
stripAnsi.setAccessible(true);
String actual = (String) stripAnsi.invoke(reader, withAnsi);
if (!expected.equals(actual)) {
throw new IllegalStateException("Did not correctly strip escape sequences: " + actual);
}
}
}