8214491: Upgrade to JLine 3.9.0

Upgrading JLine to 3.9.0 and updating jshell and jjs to the new JLine.

Reviewed-by: rfield, sundar
This commit is contained in:
Jan Lahoda 2018-12-11 11:29:28 +01:00
parent 418ce1d421
commit cc116b1259
188 changed files with 25003 additions and 12552 deletions

View file

@ -321,7 +321,7 @@ jdk.jshell_COPY += .jsh .properties
################################################################################ ################################################################################
jdk.internal.le_COPY += .properties jdk.internal.le_COPY += .properties .caps .txt
################################################################################ ################################################################################

View file

@ -1,135 +0,0 @@
/*
* Copyright (c) 2002-2016, 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 java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import jdk.internal.jline.internal.InfoCmp;
/**
* Terminal wrapper with default ansi capabilities
*/
public class DefaultTerminal2 implements Terminal2 {
private final Terminal terminal;
private final Set<String> bools = new HashSet<String>();
private final Map<String, String> strings = new HashMap<String, String>();
public DefaultTerminal2(Terminal terminal) {
this.terminal = terminal;
registerCap("key_backspace", "^H");
registerCap("bell", "^G");
registerCap("carriage_return", "^M");
if (true/*isSupported() && isAnsiSupported()*/) {
registerCap("clr_eol", "\\E[K");
registerCap("clr_bol", "\\E[1K");
registerCap("cursor_up", "\\E[A");
registerCap("cursor_down", "^J");
registerCap("column_address", "\\E[%i%p1%dG");
registerCap("clear_screen", "\\E[H\\E[2J");
registerCap("parm_down_cursor", "\\E[%p1%dB");
registerCap("cursor_left", "^H");
registerCap("cursor_right", "\\E[C");
}
if (hasWeirdWrap()) {
registerCap("eat_newline_glitch");
registerCap("auto_right_margin");
}
}
public void init() throws Exception {
terminal.init();
}
public void restore() throws Exception {
terminal.restore();
}
public void reset() throws Exception {
terminal.reset();
}
public boolean isSupported() {
return terminal.isSupported();
}
public int getWidth() {
return terminal.getWidth();
}
public int getHeight() {
return terminal.getHeight();
}
public boolean isAnsiSupported() {
return terminal.isAnsiSupported();
}
public OutputStream wrapOutIfNeeded(OutputStream out) {
return terminal.wrapOutIfNeeded(out);
}
public InputStream wrapInIfNeeded(InputStream in) throws IOException {
return terminal.wrapInIfNeeded(in);
}
public boolean hasWeirdWrap() {
return terminal.hasWeirdWrap();
}
public boolean isEchoEnabled() {
return terminal.isEchoEnabled();
}
public void setEchoEnabled(boolean enabled) {
terminal.setEchoEnabled(enabled);
}
public void disableInterruptCharacter() {
terminal.disableInterruptCharacter();
}
public void enableInterruptCharacter() {
terminal.enableInterruptCharacter();
}
public String getOutputEncoding() {
return terminal.getOutputEncoding();
}
private void registerCap(String cap, String value) {
for (String key : InfoCmp.getNames(cap)) {
strings.put(key, value);
}
}
private void registerCap(String cap) {
Collections.addAll(bools, InfoCmp.getNames(cap));
}
public boolean getBooleanCapability(String capability) {
return bools.contains(capability);
}
public Integer getNumericCapability(String capability) {
return null;
}
public String getStringCapability(String capability) {
return strings.get(capability);
}
}

View file

@ -1,46 +0,0 @@
/*
* Copyright (c) 2002-2016, 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
{
private String intr;
public NoInterruptUnixTerminal() throws Exception {
super();
}
@Override
public void init() throws Exception {
super.init();
intr = getSettings().getPropertyAsString("intr");
if ("<undef>".equals(intr)) {
intr = null;
}
if (intr != null) {
getSettings().undef("intr");
}
}
@Override
public void restore() throws Exception {
if (intr != null) {
getSettings().set("intr", intr);
}
super.restore();
}
}

View file

@ -1,68 +0,0 @@
/*
* Copyright (c) 2002-2016, 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;
/**
* Terminal that is used for OSv. This is seperate to unix terminal
* implementation because exec cannot be used as currently used by UnixTerminal.
*
* This implimentation is derrived from the implementation at
* https://github.com/cloudius-systems/mgmt/blob/master/crash/src/main/java/com/cloudius/cli/OSvTerminal.java
* authored by Or Cohen.
*
* @author <a href-"mailto:orc@fewbytes.com">Or Cohen</a>
* @author <a href="mailto:arun.neelicattu@gmail.com">Arun Neelicattu</a>
* @since 2.13
*/
public class OSvTerminal
extends TerminalSupport
{
public Class<?> sttyClass = null;
public Object stty = null;
@SuppressWarnings("deprecation")
public OSvTerminal() {
super(true);
setAnsiSupported(true);
try {
if (stty == null) {
sttyClass = Class.forName("com.cloudius.util.Stty");
stty = sttyClass.newInstance();
}
} catch (Exception e) {
Log.warn("Failed to load com.cloudius.util.Stty", e);
}
}
@Override
public void init() throws Exception {
super.init();
if (stty != null) {
sttyClass.getMethod("jlineMode").invoke(stty);
}
}
@Override
public void restore() throws Exception {
if (stty != null) {
sttyClass.getMethod("reset").invoke(stty);
}
super.restore();
// Newline in end of restore like in jline.UnixTerminal
System.out.println();
}
}

View file

@ -1,68 +0,0 @@
/*
* Copyright (c) 2002-2016, 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);
void disableInterruptCharacter();
void enableInterruptCharacter();
String getOutputEncoding();
}

View file

@ -1,211 +0,0 @@
/*
* Copyright (c) 2002-2016, 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 jdk.internal.jline.internal.Configuration;
import jdk.internal.jline.internal.Log;
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 OSV = "osv";
public static final String WIN = "win";
public static final String WINDOWS = "windows";
public static final String FREEBSD = "freebsd";
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() {
return create(null);
}
public static synchronized Terminal create(String ttyDevice) {
if (Log.TRACE) {
//noinspection ThrowableInstanceNeverThrown
Log.trace(new Throwable("CREATE MARKER"));
}
String defaultType = "dumb".equals(System.getenv("TERM")) ? NONE : AUTO;
String type = Configuration.getString(JLINE_TERMINAL, defaultType);
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(OSV)) {
t = getFlavor(Flavor.OSV);
}
else if (tmp.equals(WIN) || tmp.equals(WINDOWS)) {
t = getFlavor(Flavor.WINDOWS);
}
else if (tmp.equals(NONE) || tmp.equals(OFF) || tmp.equals(FALSE)) {
if (System.getenv("INSIDE_EMACS") != null) {
// emacs requires ansi on and echo off
t = new UnsupportedTerminal(true, false);
} else {
// others the other way round
t = new UnsupportedTerminal(false, true);
}
}
else {
if (tmp.equals(AUTO)) {
String os = Configuration.getOsName();
Flavor flavor = Flavor.UNIX;
if (os.contains(WINDOWS)) {
flavor = Flavor.WINDOWS;
} else if (System.getenv("OSV_CPUS") != null) {
flavor = Flavor.OSV;
}
t = getFlavor(flavor, ttyDevice);
}
else {
try {
@SuppressWarnings("deprecation")
Object o = Thread.currentThread().getContextClassLoader().loadClass(type).newInstance();
t = (Terminal) o;
}
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,
OSV,
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,
OSV
}
private static final Map<Flavor, TerminalConstructor> FLAVORS = new HashMap<>();
static {
registerFlavor(Flavor.WINDOWS, ttyDevice -> new WindowsTerminal());
registerFlavor(Flavor.UNIX, ttyDevice -> new UnixTerminal(ttyDevice));
registerFlavor(Flavor.OSV, ttyDevice -> new OSvTerminal());
}
public static synchronized Terminal get(String ttyDevice) {
// The code is assuming we've got only one terminal per process.
// Continuing this assumption, if this terminal is already initialized,
// we don't check if it's using the same tty line either. Both assumptions
// are a bit crude. TODO: check single terminal assumption.
if (term == null) {
term = create(ttyDevice);
}
return term;
}
public static synchronized Terminal get() {
return get(null);
}
public static Terminal getFlavor(final Flavor flavor) throws Exception {
return getFlavor(flavor, null);
}
@SuppressWarnings("deprecation")
public static Terminal getFlavor(final Flavor flavor, String ttyDevice) throws Exception {
TerminalConstructor factory = FLAVORS.get(flavor);
if (factory != null) {
return factory.createTerminal(ttyDevice);
} else {
throw new InternalError();
}
}
public static void registerFlavor(final Flavor flavor, final TerminalConstructor factory) {
FLAVORS.put(flavor, factory);
}
public interface TerminalConstructor {
public Terminal createTerminal(String str) throws Exception;
}
}

View file

@ -1,129 +0,0 @@
/*
* Copyright (c) 2002-2016, 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 void disableInterruptCharacter() {
}
public void enableInterruptCharacter() {
}
public InputStream wrapInIfNeeded(InputStream in) throws IOException {
return in;
}
public String getOutputEncoding() {
// null for unknown
return null;
}
}

View file

@ -1,244 +0,0 @@
/*
* Copyright (c) 2002-2016, 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.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import jdk.internal.jline.internal.Configuration;
import jdk.internal.jline.internal.InfoCmp;
import jdk.internal.jline.internal.Log;
import jdk.internal.jline.internal.TerminalLineSettings;
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
/**
* 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
implements Terminal2
{
private final TerminalLineSettings settings;
private final String type;
private String intr;
private String lnext;
private Set<String> bools = new HashSet<String>();
private Map<String, Integer> ints = new HashMap<String, Integer>();
private Map<String, String> strings = new HashMap<String, String>();
public UnixTerminal() throws Exception {
this(TerminalLineSettings.DEFAULT_TTY, null);
}
public UnixTerminal(String ttyDevice) throws Exception {
this(ttyDevice, null);
}
public UnixTerminal(String ttyDevice, String type) throws Exception {
super(true);
checkNotNull(ttyDevice);
this.settings = TerminalLineSettings.getSettings(ttyDevice);
if (type == null) {
type = System.getenv("TERM");
}
this.type = type;
parseInfoCmp();
}
public 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)
//
// Please note that FreeBSD does not seem to support -icrnl and thus
// has to be handled separately. Otherwise the console will be "stuck"
// and will neither accept input nor print anything to stdout.
if (Configuration.getOsName().contains(TerminalFactory.FREEBSD)) {
settings.set("-icanon min 1 -inlcr -ixon");
} else {
settings.set("-icanon min 1 -icrnl -inlcr -ixon");
}
settings.undef("dsusp");
setEchoEnabled(false);
parseInfoCmp();
}
/**
* 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 boolean hasWeirdWrap() {
return getBooleanCapability("auto_right_margin")
&& getBooleanCapability("eat_newline_glitch");
}
@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 {
intr = getSettings().getPropertyAsString("intr");
if ("<undef>".equals(intr)) {
intr = null;
}
settings.undef("intr");
}
catch (Exception e) {
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
Log.error("Failed to disable interrupt character", e);
}
}
public void enableInterruptCharacter()
{
try {
if (intr != null) {
settings.set("intr", intr);
}
}
catch (Exception e) {
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
Log.error("Failed to enable interrupt character", e);
}
}
public void disableLitteralNextCharacter()
{
try {
lnext = getSettings().getPropertyAsString("lnext");
if ("<undef>".equals(lnext)) {
lnext = null;
}
settings.undef("lnext");
}
catch (Exception e) {
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
Log.error("Failed to disable litteral next character", e);
}
}
public void enableLitteralNextCharacter()
{
try {
if (lnext != null) {
settings.set("lnext", lnext);
}
}
catch (Exception e) {
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
Log.error("Failed to enable litteral next character", e);
}
}
public boolean getBooleanCapability(String capability) {
return bools.contains(capability);
}
public Integer getNumericCapability(String capability) {
return ints.get(capability);
}
public String getStringCapability(String capability) {
return strings.get(capability);
}
private void parseInfoCmp() {
String capabilities = null;
if (type != null) {
try {
capabilities = InfoCmp.getInfoCmp(type);
} catch (Exception e) {
}
}
if (capabilities == null) {
capabilities = InfoCmp.getAnsiCaps();
}
InfoCmp.parseInfoCmp(capabilities, bools, ints, strings);
}
}

View file

@ -1,30 +0,0 @@
/*
* Copyright (c) 2002-2016, 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() {
this(false, true);
}
public UnsupportedTerminal(boolean ansiSupported, boolean echoEnabled) {
super(false);
setAnsiSupported(ansiSupported);
setEchoEnabled(echoEnabled);
}
}

View file

@ -1,617 +0,0 @@
/*
* Copyright (c) 2002-2016, 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 java.io.OutputStream;
import jdk.internal.jline.extra.AnsiInterpretingOutputStream;
import jdk.internal.jline.extra.AnsiInterpretingOutputStream.BufferState;
import jdk.internal.jline.extra.AnsiInterpretingOutputStream.Performer;
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(true);
//
// 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_WINDOW_INPUT.code);
}
else {
setConsoleMode(getConsoleMode() &
~(ENABLE_LINE_INPUT.code |
ENABLE_ECHO_INPUT.code |
ENABLE_WINDOW_INPUT.code));
}
super.setEchoEnabled(enabled);
}
public void disableInterruptCharacter() {
setConsoleMode(getConsoleMode() &
~(ENABLE_PROCESSED_INPUT.code));
}
public void enableInterruptCharacter() {
setConsoleMode(getConsoleMode() |
ENABLE_PROCESSED_INPUT.code);
}
/**
* 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 OutputStream wrapOutIfNeeded(OutputStream out) {
return new AnsiInterpretingOutputStream(getOutputEncoding(), out, new Performer() {
@Override
public BufferState getBufferState() throws IOException {
out.flush();
return WindowsTerminal.this.getBufferState();
}
@Override
public void setCursorPosition(int cursorX, int cursorY) throws IOException {
out.flush();
WindowsTerminal.this.setCursorPosition(cursorX, cursorY);
}
});
}
@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();
protected native int getConsoleMode();
protected 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
// xterm escape codes: E. Moy, S. Gildea and T. Dickey, "XTerm Control Sequences":
// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
// http://xorg.freedesktop.org/releases/X11R6.8.1/PDF/ctlseqs.pdf
// just add support for basic editing keys and function 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;
case 0x70: // VK_F1
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001BOP", "\u001BO%dP");
break;
case 0x71: // VK_F2
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001BOQ", "\u001BO%dQ");
break;
case 0x72: // VK_F3
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001BOR", "\u001BO%dR");
break;
case 0x73: // VK_F4
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001BOS", "\u001BO%dS");
break;
case 0x74: // VK_F5
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[15~", "\u001B[15;%d~");
break;
case 0x75: // VK_F6
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[17~", "\u001B[17;%d~");
break;
case 0x76: // VK_F7
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[18~", "\u001B[18;%d~");
break;
case 0x77: // VK_F8
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[19~", "\u001B[19;%d~");
break;
case 0x78: // VK_F9
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[20~", "\u001B[20;%d~");
break;
case 0x79: // VK_F10
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[21~", "\u001B[21;%d~");
break;
case 0x7A: // VK_F11
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[23~", "\u001B[23;%d~");
break;
case 0x7B: // VK_F12
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[24~", "\u001B[24;%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();
private native BufferState getBufferState();
private native void setCursorPosition(int x, int y);
/**
* 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;
}
}
}

View file

@ -1,387 +0,0 @@
/*
* Copyright (c) 2002-2016, 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();
setVar("editing-mode", "emacs");
loadKeys(appName, inputrcUrl);
String editingMode = variables.get("editing-mode");
if ("vi".equalsIgnoreCase(editingMode)) {
keys = keyMaps.get(KeyMap.VI_INSERT);
} else if ("emacs".equalsIgnoreCase(editingMode)) {
keys = keyMaps.get(KeyMap.EMACS);
}
}
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 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=")) {
String mode = variables.get("editing-mode");
parsing = args.substring("mode=".length()).equalsIgnoreCase(mode);
} 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 static 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 static 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 ("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);
}
}

View file

@ -1,121 +0,0 @@
/*
* Copyright (c) 2002-2016, 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();
}
}

View file

@ -1,577 +0,0 @@
/*
* Copyright (c) 2002-2016, 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;
public KeyMap(String name) {
this(name, new Object[KEYMAP_LENGTH]);
}
@Deprecated
public KeyMap(String name, boolean unused) {
this(name);
}
protected KeyMap(String name, Object[] mapping) {
this.mapping = mapping;
this.name = name;
}
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");
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);
keyMaps.put("vi", viMov);
KeyMap viIns = viInsertion();
bindArrowKeys(viIns);
keyMaps.put(VI_INSERT, 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);
}
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);
}
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);
}
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);
}
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_BEGINNING_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);
}
}

View file

@ -1,161 +0,0 @@
/*
* Copyright (c) 2002-2016, 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,
QUIT,
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_BEGINNING_OF_LINE_OR_ARG_DIGIT,
}

View file

@ -1,124 +0,0 @@
/*
* Copyright (c) 2002-2016, 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);
}
}
}

View file

@ -1,72 +0,0 @@
/*
* Copyright (c) 2002-2016, 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.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import jdk.internal.jline.internal.Ansi;
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 AnsiStringsCompleter
implements Completer
{
private final SortedMap<String, String> strings = new TreeMap<String, String>();
public AnsiStringsCompleter() {
// empty
}
public AnsiStringsCompleter(final Collection<String> strings) {
checkNotNull(strings);
for (String str : strings) {
this.strings.put(Ansi.stripAnsi(str), str);
}
}
public AnsiStringsCompleter(final String... strings) {
this(Arrays.asList(strings));
}
public Collection<String> getStrings() {
return strings.values();
}
public int complete(String buffer, final int cursor, final List<CharSequence> candidates) {
// buffer could be null
checkNotNull(candidates);
if (buffer == null) {
candidates.addAll(strings.values());
}
else {
buffer = Ansi.stripAnsi(buffer);
for (Map.Entry<String, String> match : strings.tailMap(buffer).entrySet()) {
if (!match.getKey().startsWith(buffer)) {
break;
}
candidates.add(match.getValue());
}
}
return candidates.isEmpty() ? -1 : 0;
}
}

View file

@ -1,457 +0,0 @@
/*
* Copyright (c) 2002-2016, 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.contains(arg)) {
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)
*/
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;
}
}
}

View file

@ -1,236 +0,0 @@
/*
* Copyright (c) 2002-2016, 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 jdk.internal.jline.internal.Ansi;
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
{
private boolean printSpaceAfterFullCompletion = true;
private boolean stripAnsi;
public boolean getPrintSpaceAfterFullCompletion() {
return printSpaceAfterFullCompletion;
}
public void setPrintSpaceAfterFullCompletion(boolean printSpaceAfterFullCompletion) {
this.printSpaceAfterFullCompletion = printSpaceAfterFullCompletion;
}
public boolean isStripAnsi() {
return stripAnsi;
}
public void setStripAnsi(boolean stripAnsi) {
this.stripAnsi = stripAnsi;
}
// 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) {
String value = Ansi.stripAnsi(candidates.get(0).toString());
if (buf.cursor == buf.buffer.length()
&& printSpaceAfterFullCompletion
&& !value.endsWith(" ")) {
value += " ";
}
// 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.println();
reader.print(Messages.DISPLAY_CANDIDATES.format(distinct.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;
}
if (candidates.size() == 1) {
return candidates.get(0).toString();
}
// convert to an array for speed
String first = null;
String[] strings = new String[candidates.size() - 1];
for (int i = 0; i < candidates.size(); i++) {
String str = candidates.get(i).toString();
if (stripAnsi) {
str = Ansi.stripAnsi(str);
}
if (first == null) {
first = str;
} else {
strings[i - 1] = str;
}
}
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 static boolean startsWith(final String starts, final String[] candidates) {
for (String candidate : candidates) {
if (!candidate.toLowerCase().startsWith(starts.toLowerCase())) {
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);
}
}
}

View file

@ -1,13 +0,0 @@
#
# Copyright (c) 2002-2016, 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--

View file

@ -1,38 +0,0 @@
/*
* Copyright (c) 2002-2016, 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);
}

View file

@ -1,26 +0,0 @@
/*
* Copyright (c) 2002-2016, 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;
}

View file

@ -1,133 +0,0 @@
/*
* Copyright (c) 2002-2016, 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;
}
}

View file

@ -1,66 +0,0 @@
/*
* Copyright (c) 2002-2016, 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);
}
}
return candidates.isEmpty() ? -1 : 0;
}
}

View file

@ -1,135 +0,0 @@
/*
* Copyright (c) 2002-2016, 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;
/**
* Load a history file into memory, truncating to default max size.
*/
public FileHistory(final File file) throws IOException {
this(file, true);
}
/**
* Create a FileHistory, but only initialize if doInit is true. This allows
* setting maxSize or other settings; call load() before using if doInit is
* false.
*/
public FileHistory(final File file, final boolean doInit) throws IOException {
this.file = checkNotNull(file).getAbsoluteFile();
if (doInit) {
load();
}
}
/**
* Load history from file, e.g. if using delayed init.
*/
public void load() throws IOException {
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);
FileReader reader = null;
try{
reader = new FileReader(file);
load(reader);
} finally{
if(reader != null){
reader.close();
}
}
}
}
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);
}
}
}

View file

@ -1,106 +0,0 @@
/*
* Copyright (c) 2002-2016, 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();
}

View file

@ -1,346 +0,0 @@
/*
* Copyright (c) 2002-2016, 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
*/
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();
}
}

View file

@ -1,35 +0,0 @@
/*
* Copyright (c) 2002-2016, 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;
}

View file

@ -1,123 +0,0 @@
/*
* Copyright (c) 2002-2016, 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++);
}
}
}

View file

@ -1,99 +0,0 @@
/*
* Copyright (c) 2002-2016, 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.console.history.PersistentHistory;
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();) {
@SuppressWarnings("deprecation")
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);
String[] mainArgs = argList.toArray(new String[argList.size()]);
method.invoke(null, (Object) mainArgs);
}
finally {
// just in case this main method is called from another program
ConsoleReaderInputStream.restoreIn();
if (reader.getHistory() instanceof PersistentHistory) {
((PersistentHistory) reader.getHistory()).flush();
}
}
}
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.");
}
}

View file

@ -1,162 +0,0 @@
/*
* Copyright (c) 2018, 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.
*/
package jdk.internal.jline.extra;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import jdk.internal.jline.internal.Ansi;
/**A stream that interprets some escape code sequences, and ignores those it does not support.
*/
public class AnsiInterpretingOutputStream extends OutputStream {
private final String encoding;
private final OutputStream out;
private final Performer performer;
private final Map<Character, AnsiCodeHandler> ESCAPE_CODE_ACTIONS = new HashMap<>();
private boolean inEscapeSequence;
private ByteArrayOutputStream escape = new ByteArrayOutputStream();
public AnsiInterpretingOutputStream(String encoding, OutputStream output, Performer performer) {
this.encoding = encoding;
this.out = output;
this.performer = performer;
ESCAPE_CODE_ACTIONS.put('A', code -> {
moveCursor(code, 0, -1);
});
ESCAPE_CODE_ACTIONS.put('B', code -> {
moveCursor(code, 0, +1);
});
ESCAPE_CODE_ACTIONS.put('C', code -> {
moveCursor(code, +1, 0);
});
ESCAPE_CODE_ACTIONS.put('D', code -> {
moveCursor(code, -1, 0);
});
ESCAPE_CODE_ACTIONS.put('K', code -> {
BufferState buffer = performer.getBufferState();
switch (parseOutIntValue(code, 0)) {
case 0:
for (int i = buffer.cursorX; i < buffer.sizeX - 1; i++) {
out.write(' ');
}
performer.setCursorPosition(buffer.cursorX, buffer.cursorY);
break;
case 1:
performer.setCursorPosition(0, buffer.cursorY);
for (int i = 0; i < buffer.cursorX; i++) {
out.write(' ');
}
break;
case 2:
for (int i = 0; i < buffer.sizeX - 1; i++) {
out.write(' ');
}
performer.setCursorPosition(buffer.cursorX, buffer.cursorY);
break;
}
out.flush();
});
}
@Override
public void write(int d) throws IOException {
if (inEscapeSequence) {
escape.write(d);
String escapeCandidate = new String(escape.toByteArray(), encoding);
if (Ansi.ANSI_CODE_PATTERN.asPredicate().test(escapeCandidate)) {
//escape sequence:
char key = escapeCandidate.charAt(escapeCandidate.length() - 1);
AnsiCodeHandler handler =
ESCAPE_CODE_ACTIONS.get(key);
if (handler != null) {
handler.handle(escapeCandidate);
} else {
//unknown escape sequence, ignore
}
inEscapeSequence = false;
escape = null;
}
} else if (d == '\033') {
inEscapeSequence = true;
escape = new ByteArrayOutputStream();
escape.write(d);
} else {
out.write(d);
}
}
@Override
public void flush() throws IOException {
out.flush();
}
private void moveCursor(String code, int dx, int dy) throws IOException {
int delta = parseOutIntValue(code, 1);
BufferState buffer = performer.getBufferState();
int tx = buffer.cursorX + dx * delta;
int ty = buffer.cursorY + dy * delta;
tx = Math.max(0, Math.min(buffer.sizeX - 1, tx));
ty = Math.max(0, Math.min(buffer.sizeY - 1, ty));
performer.setCursorPosition(tx, ty);
}
private int parseOutIntValue(String code, int def) {
try {
return Integer.parseInt(code.substring(code.indexOf('[') + 1, code.length() - 1));
} catch (NumberFormatException ex) {
return def;
}
}
interface AnsiCodeHandler {
public void handle(String code) throws IOException;
}
public interface Performer {
public BufferState getBufferState() throws IOException;
public void setCursorPosition(int cursorX, int cursorY) throws IOException;
}
public static class BufferState {
public final int cursorX;
public final int cursorY;
public final int sizeX;
public final int sizeY;
public BufferState(int cursorX, int cursorY, int sizeX, int sizeY) {
this.cursorX = cursorX;
this.cursorY = cursorY;
this.sizeX = sizeX;
this.sizeY = sizeY;
}
}
}

View file

@ -1,422 +0,0 @@
/*
* Copyright (c) 2015, 2017, 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.
*/
package jdk.internal.jline.extra;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.function.Supplier;
import jdk.internal.jline.console.ConsoleReader;
import jdk.internal.jline.console.KeyMap;
import jdk.internal.jline.console.history.History;
import jdk.internal.jline.console.history.History.Entry;
import jdk.internal.jline.console.history.MemoryHistory;
/*Public for tests (HistoryTest).
*/
public abstract class EditingHistory implements History {
private final History fullHistory;
private History currentDelegate;
protected EditingHistory(ConsoleReader in, Iterable<? extends String> originalHistory) {
MemoryHistory fullHistory = new MemoryHistory();
fullHistory.setIgnoreDuplicates(false);
this.fullHistory = fullHistory;
this.currentDelegate = fullHistory;
bind(in, CTRL_UP,
(Runnable) () -> moveHistoryToSnippet(in, ((EditingHistory) in.getHistory())::previousSnippet));
bind(in, CTRL_DOWN,
(Runnable) () -> moveHistoryToSnippet(in, ((EditingHistory) in.getHistory())::nextSnippet));
if (originalHistory != null) {
load(originalHistory);
}
}
private void moveHistoryToSnippet(ConsoleReader in, Supplier<Boolean> action) {
if (!action.get()) {
try {
in.beep();
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
} else {
try {
//could use:
//in.resetPromptLine(in.getPrompt(), in.getHistory().current().toString(), -1);
//but that would mean more re-writing on the screen, (and prints an additional
//empty line), so using setBuffer directly:
Method setBuffer = ConsoleReader.class.getDeclaredMethod("setBuffer", String.class);
setBuffer.setAccessible(true);
setBuffer.invoke(in, in.getHistory().current().toString());
in.flush();
} catch (ReflectiveOperationException | IOException ex) {
throw new IllegalStateException(ex);
}
}
}
private void bind(ConsoleReader in, String shortcut, Object action) {
KeyMap km = in.getKeys();
for (int i = 0; i < shortcut.length(); i++) {
Object value = km.getBound(Character.toString(shortcut.charAt(i)));
if (value instanceof KeyMap) {
km = (KeyMap) value;
} else {
km.bind(shortcut.substring(i), action);
}
}
}
private static final String CTRL_UP = "\033\133\061\073\065\101"; //Ctrl-UP
private static final String CTRL_DOWN = "\033\133\061\073\065\102"; //Ctrl-DOWN
@Override
public int size() {
return currentDelegate.size();
}
@Override
public boolean isEmpty() {
return currentDelegate.isEmpty();
}
@Override
public int index() {
return currentDelegate.index();
}
@Override
public void clear() {
if (currentDelegate != fullHistory)
throw new IllegalStateException("narrowed");
currentDelegate.clear();
}
@Override
public CharSequence get(int index) {
return currentDelegate.get(index);
}
@Override
public void add(CharSequence line) {
NarrowingHistoryLine currentLine = null;
int origIndex = fullHistory.index();
int fullSize;
try {
fullHistory.moveToEnd();
fullSize = fullHistory.index();
if (currentDelegate == fullHistory) {
if (origIndex < fullHistory.index()) {
for (Entry entry : fullHistory) {
if (!(entry.value() instanceof NarrowingHistoryLine))
continue;
int[] cluster = ((NarrowingHistoryLine) entry.value()).span;
if (cluster[0] == origIndex && cluster[1] > cluster[0]) {
currentDelegate = new MemoryHistory();
for (int i = cluster[0]; i <= cluster[1]; i++) {
currentDelegate.add(fullHistory.get(i));
}
}
}
}
}
fullHistory.moveToEnd();
while (fullHistory.previous()) {
CharSequence c = fullHistory.current();
if (c instanceof NarrowingHistoryLine) {
currentLine = (NarrowingHistoryLine) c;
break;
}
}
} finally {
fullHistory.moveTo(origIndex);
}
if (currentLine == null || currentLine.span[1] != (-1)) {
line = currentLine = new NarrowingHistoryLine(line, fullSize);
}
StringBuilder complete = new StringBuilder();
for (int i = currentLine.span[0]; i < fullSize; i++) {
complete.append(fullHistory.get(i));
}
complete.append(line);
if (isComplete(complete)) {
currentLine.span[1] = fullSize; //TODO: +1?
currentDelegate = fullHistory;
}
fullHistory.add(line);
}
protected abstract boolean isComplete(CharSequence input);
@Override
public void set(int index, CharSequence item) {
if (currentDelegate != fullHistory)
throw new IllegalStateException("narrowed");
currentDelegate.set(index, item);
}
@Override
public CharSequence remove(int i) {
if (currentDelegate != fullHistory)
throw new IllegalStateException("narrowed");
return currentDelegate.remove(i);
}
@Override
public CharSequence removeFirst() {
if (currentDelegate != fullHistory)
throw new IllegalStateException("narrowed");
return currentDelegate.removeFirst();
}
@Override
public CharSequence removeLast() {
if (currentDelegate != fullHistory)
throw new IllegalStateException("narrowed");
return currentDelegate.removeLast();
}
@Override
public void replace(CharSequence item) {
if (currentDelegate != fullHistory)
throw new IllegalStateException("narrowed");
currentDelegate.replace(item);
}
@Override
public ListIterator<Entry> entries(int index) {
return currentDelegate.entries(index);
}
@Override
public ListIterator<Entry> entries() {
return currentDelegate.entries();
}
@Override
public Iterator<Entry> iterator() {
return currentDelegate.iterator();
}
@Override
public CharSequence current() {
return currentDelegate.current();
}
@Override
public boolean previous() {
return currentDelegate.previous();
}
@Override
public boolean next() {
return currentDelegate.next();
}
@Override
public boolean moveToFirst() {
return currentDelegate.moveToFirst();
}
@Override
public boolean moveToLast() {
return currentDelegate.moveToLast();
}
@Override
public boolean moveTo(int index) {
return currentDelegate.moveTo(index);
}
@Override
public void moveToEnd() {
currentDelegate.moveToEnd();
}
public boolean previousSnippet() {
while (previous()) {
if (current() instanceof NarrowingHistoryLine) {
return true;
}
}
return false;
}
public boolean nextSnippet() {
boolean success = false;
while (next()) {
success = true;
if (current() instanceof NarrowingHistoryLine) {
return true;
}
}
return success;
}
public final void load(Iterable<? extends String> originalHistory) {
NarrowingHistoryLine currentHistoryLine = null;
boolean start = true;
int currentLine = 0;
for (String historyItem : originalHistory) {
StringBuilder line = new StringBuilder(historyItem);
int trailingBackSlashes = countTrailintBackslashes(line);
boolean continuation = trailingBackSlashes % 2 != 0;
line.delete(line.length() - trailingBackSlashes / 2 - (continuation ? 1 : 0), line.length());
if (start) {
class PersistentNarrowingHistoryLine extends NarrowingHistoryLine implements PersistentEntryMarker {
public PersistentNarrowingHistoryLine(CharSequence delegate, int start) {
super(delegate, start);
}
}
fullHistory.add(currentHistoryLine = new PersistentNarrowingHistoryLine(line, currentLine));
} else {
class PersistentLine implements CharSequence, PersistentEntryMarker {
private final CharSequence delegate;
public PersistentLine(CharSequence delegate) {
this.delegate = delegate;
}
@Override public int length() {
return delegate.length();
}
@Override public char charAt(int index) {
return delegate.charAt(index);
}
@Override public CharSequence subSequence(int start, int end) {
return delegate.subSequence(start, end);
}
@Override public String toString() {
return delegate.toString();
}
}
fullHistory.add(new PersistentLine(line));
}
start = !continuation;
currentHistoryLine.span[1] = currentLine;
currentLine++;
}
}
public Collection<? extends String> save() {
Collection<String> result = new ArrayList<>();
Iterator<Entry> entries = fullHistory.iterator();
if (entries.hasNext()) {
Entry entry = entries.next();
while (entry != null) {
StringBuilder historyLine = new StringBuilder(entry.value());
int trailingBackSlashes = countTrailintBackslashes(historyLine);
for (int i = 0; i < trailingBackSlashes; i++) {
historyLine.append("\\");
}
entry = entries.hasNext() ? entries.next() : null;
if (entry != null && !(entry.value() instanceof NarrowingHistoryLine)) {
historyLine.append("\\");
}
result.add(historyLine.toString());
}
}
return result;
}
private int countTrailintBackslashes(CharSequence text) {
int count = 0;
for (int i = text.length() - 1; i >= 0; i--) {
if (text.charAt(i) == '\\') {
count++;
} else {
break;
}
}
return count;
}
public List<String> entries(boolean currentSession) {
List<String> result = new ArrayList<>();
for (Entry e : fullHistory) {
if (!currentSession || !(e.value() instanceof PersistentEntryMarker)) {
result.add(e.value().toString());
}
}
return result;
}
public void fullHistoryReplace(String source) {
fullHistory.removeLast();
for (String line : source.split("\\R")) {
fullHistory.add(line);
}
}
private class NarrowingHistoryLine implements CharSequence {
private final CharSequence delegate;
private final int[] span;
public NarrowingHistoryLine(CharSequence delegate, int start) {
this.delegate = delegate;
this.span = new int[] {start, -1};
}
@Override
public int length() {
return delegate.length();
}
@Override
public char charAt(int index) {
return delegate.charAt(index);
}
@Override
public CharSequence subSequence(int start, int end) {
return delegate.subSequence(start, end);
}
@Override
public String toString() {
return delegate.toString();
}
}
private interface PersistentEntryMarker {}
}

View file

@ -1,38 +0,0 @@
/*
* Copyright (c) 2002-2016, 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.regex.Pattern;
/**
* Ansi support.
*
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
* @since 2.13
*/
public class Ansi {
public static String stripAnsi(String str) {
if (str == null) return "";
return ANSI_CODE_PATTERN.matcher(str).replaceAll("");
//was:
// try {
// ByteArrayOutputStream baos = new ByteArrayOutputStream();
// AnsiOutputStream aos = new AnsiOutputStream(baos);
// aos.write(str.getBytes());
// aos.close();
// return baos.toString();
// } catch (IOException e) {
// return str;
// }
}
public static final Pattern ANSI_CODE_PATTERN = Pattern.compile("\033\\[[\060-\077]*[\040-\057]*[\100-\176]");
}

View file

@ -1,261 +0,0 @@
/*
* Copyright (c) 2002-2016, 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.io.FileNotFoundException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
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 (FileNotFoundException e) {
// debug here and no stack trace, as this can happen normally if default jline.rc file is missing
Log.debug("Unable to read configuration: ", e.toString());
}
catch (IOException e) {
Log.warn("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) {
return getBoolean(name, false);
}
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");
}
public static boolean isHpux() {
return getOsName().startsWith("hp");
}
// FIXME: Sort out use of property access of file.encoding in InputStreamReader, should consolidate configuration access here
public static String getFileEncoding() {
return System.getProperty("file.encoding");
}
/**
* Get the default encoding. Will first look at the LC_ALL, LC_CTYPE, and LANG environment variables, 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() {
// Check for standard locale environment variables, in order of precedence, first.
// See http://www.gnu.org/s/libc/manual/html_node/Locale-Categories.html
for (String envOption : new String[]{"LC_ALL", "LC_CTYPE", "LANG"}) {
String envEncoding = extractEncodingFromCtype(System.getenv(envOption));
if (envEncoding != null) {
try {
if (Charset.isSupported(envEncoding)) {
return envEncoding;
}
} catch (IllegalCharsetNameException e) {
continue;
}
}
}
return getString("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;
}
}

View file

@ -1,591 +0,0 @@
/*
* Copyright (c) 2002-2016, 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.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Infocmp helper methods.
*
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
*/
public class InfoCmp {
private static final Map<String, String> CAPS = new HashMap<String, String>();
public static String getInfoCmp(
String terminal
) throws IOException, InterruptedException {
String caps = CAPS.get(terminal);
if (caps == null) {
Process p = new ProcessBuilder("infocmp", terminal).start();
caps = TerminalLineSettings.waitAndCapture(p);
CAPS.put(terminal, caps);
}
return caps;
}
public static String getAnsiCaps() {
return ANSI_CAPS;
}
public static void parseInfoCmp(
String capabilities,
Set<String> bools,
Map<String, Integer> ints,
Map<String, String> strings
) {
String[] lines = capabilities.split("\n");
for (int i = 2; i < lines.length; i++) {
Matcher m = Pattern.compile("\\s*(([^,]|\\\\,)+)\\s*[,$]").matcher(lines[i]);
while (m.find()) {
String cap = m.group(1);
if (cap.contains("#")) {
int index = cap.indexOf('#');
String key = cap.substring(0, index);
String val = cap.substring(index + 1);
int iVal;
if (val.startsWith("0x")) {
iVal = Integer.parseInt(val.substring(2), 16);
} else {
iVal = Integer.parseInt(val);
}
for (String name : getNames(key)) {
ints.put(name, iVal);
}
} else if (cap.contains("=")) {
int index = cap.indexOf('=');
String key = cap.substring(0, index);
String val = cap.substring(index + 1);
for (String name : getNames(key)) {
strings.put(name, val);
}
} else {
for (String name : getNames(cap)) {
bools.add(name);
}
}
}
}
}
public static String[] getNames(String name) {
String[] names = NAMES.get(name);
return names != null ? names : new String[] { name };
}
private static final Map<String, String[]> NAMES;
static {
String[][] list = {
{ "auto_left_margin", "bw", "bw" },
{ "auto_right_margin", "am", "am" },
{ "back_color_erase", "bce", "ut" },
{ "can_change", "ccc", "cc" },
{ "ceol_standout_glitch", "xhp", "xs" },
{ "col_addr_glitch", "xhpa", "YA" },
{ "cpi_changes_res", "cpix", "YF" },
{ "cr_cancels_micro_mode", "crxm", "YB" },
{ "dest_tabs_magic_smso", "xt", "xt" },
{ "eat_newline_glitch", "xenl", "xn" },
{ "erase_overstrike", "eo", "eo" },
{ "generic_type", "gn", "gn" },
{ "hard_copy", "hc", "hc" },
{ "hard_cursor", "chts", "HC" },
{ "has_meta_key", "km", "km" },
{ "has_print_wheel", "daisy", "YC" },
{ "has_status_line", "hs", "hs" },
{ "hue_lightness_saturation", "hls", "hl" },
{ "insert_null_glitch", "in", "in" },
{ "lpi_changes_res", "lpix", "YG" },
{ "memory_above", "da", "da" },
{ "memory_below", "db", "db" },
{ "move_insert_mode", "mir", "mi" },
{ "move_standout_mode", "msgr", "ms" },
{ "needs_xon_xoff", "nxon", "nx" },
{ "no_esc_ctlc", "xsb", "xb" },
{ "no_pad_char", "npc", "NP" },
{ "non_dest_scroll_region", "ndscr", "ND" },
{ "non_rev_rmcup", "nrrmc", "NR" },
{ "over_strike", "os", "os" },
{ "prtr_silent", "mc5i", "5i" },
{ "row_addr_glitch", "xvpa", "YD" },
{ "semi_auto_right_margin", "sam", "YE" },
{ "status_line_esc_ok", "eslok", "es" },
{ "tilde_glitch", "hz", "hz" },
{ "transparent_underline", "ul", "ul" },
{ "xon_xoff", "xon", "xo" },
{ "columns", "cols", "co" },
{ "init_tabs", "it", "it" },
{ "label_height", "lh", "lh" },
{ "label_width", "lw", "lw" },
{ "lines", "lines", "li" },
{ "lines_of_memory", "lm", "lm" },
{ "magic_cookie_glitch", "xmc", "sg" },
{ "max_attributes", "ma", "ma" },
{ "max_colors", "colors", "Co" },
{ "max_pairs", "pairs", "pa" },
{ "maximum_windows", "wnum", "MW" },
{ "no_color_video", "ncv", "NC" },
{ "num_labels", "nlab", "Nl" },
{ "padding_baud_rate", "pb", "pb" },
{ "virtual_terminal", "vt", "vt" },
{ "width_status_line", "wsl", "ws" },
{ "bit_image_entwining", "bitwin", "Yo" },
{ "bit_image_type", "bitype", "Yp" },
{ "buffer_capacity", "bufsz", "Ya" },
{ "buttons", "btns", "BT" },
{ "dot_horz_spacing", "spinh", "Yc" },
{ "dot_vert_spacing", "spinv", "Yb" },
{ "max_micro_address", "maddr", "Yd" },
{ "max_micro_jump", "mjump", "Ye" },
{ "micro_col_size", "mcs", "Yf" },
{ "micro_line_size", "mls", "Yg" },
{ "number_of_pins", "npins", "Yh" },
{ "output_res_char", "orc", "Yi" },
{ "output_res_horz_inch", "orhi", "Yk" },
{ "output_res_line", "orl", "Yj" },
{ "output_res_vert_inch", "orvi", "Yl" },
{ "print_rate", "cps", "Ym" },
{ "wide_char_size", "widcs", "Yn" },
{ "acs_chars", "acsc", "ac" },
{ "back_tab", "cbt", "bt" },
{ "bell", "bel", "bl" },
{ "carriage_return", "cr", "cr" },
{ "change_char_pitch", "cpi", "ZA" },
{ "change_line_pitch", "lpi", "ZB" },
{ "change_res_horz", "chr", "ZC" },
{ "change_res_vert", "cvr", "ZD" },
{ "change_scroll_region", "csr", "cs" },
{ "char_padding", "rmp", "rP" },
{ "clear_all_tabs", "tbc", "ct" },
{ "clear_margins", "mgc", "MC" },
{ "clear_screen", "clear", "cl" },
{ "clr_bol", "el1", "cb" },
{ "clr_eol", "el", "ce" },
{ "clr_eos", "ed", "cd" },
{ "column_address", "hpa", "ch" },
{ "command_character", "cmdch", "CC" },
{ "create_window", "cwin", "CW" },
{ "cursor_address", "cup", "cm" },
{ "cursor_down", "cud1", "do" },
{ "cursor_home", "home", "ho" },
{ "cursor_invisible", "civis", "vi" },
{ "cursor_left", "cub1", "le" },
{ "cursor_mem_address", "mrcup", "CM" },
{ "cursor_normal", "cnorm", "ve" },
{ "cursor_right", "cuf1", "nd" },
{ "cursor_to_ll", "ll", "ll" },
{ "cursor_up", "cuu1", "up" },
{ "cursor_visible", "cvvis", "vs" },
{ "define_char", "defc", "ZE" },
{ "delete_character", "dch1", "dc" },
{ "delete_line", "dl1", "dl" },
{ "dial_phone", "dial", "DI" },
{ "dis_status_line", "dsl", "ds" },
{ "display_clock", "dclk", "DK" },
{ "down_half_line", "hd", "hd" },
{ "ena_acs", "enacs", "eA" },
{ "enter_alt_charset_mode", "smacs", "as" },
{ "enter_am_mode", "smam", "SA" },
{ "enter_blink_mode", "blink", "mb" },
{ "enter_bold_mode", "bold", "md" },
{ "enter_ca_mode", "smcup", "ti" },
{ "enter_delete_mode", "smdc", "dm" },
{ "enter_dim_mode", "dim", "mh" },
{ "enter_doublewide_mode", "swidm", "ZF" },
{ "enter_draft_quality", "sdrfq", "ZG" },
{ "enter_insert_mode", "smir", "im" },
{ "enter_italics_mode", "sitm", "ZH" },
{ "enter_leftward_mode", "slm", "ZI" },
{ "enter_micro_mode", "smicm", "ZJ" },
{ "enter_near_letter_quality", "snlq", "ZK" },
{ "enter_normal_quality", "snrmq", "ZL" },
{ "enter_protected_mode", "prot", "mp" },
{ "enter_reverse_mode", "rev", "mr" },
{ "enter_secure_mode", "invis", "mk" },
{ "enter_shadow_mode", "sshm", "ZM" },
{ "enter_standout_mode", "smso", "so" },
{ "enter_subscript_mode", "ssubm", "ZN" },
{ "enter_superscript_mode", "ssupm", "ZO" },
{ "enter_underline_mode", "smul", "us" },
{ "enter_upward_mode", "sum", "ZP" },
{ "enter_xon_mode", "smxon", "SX" },
{ "erase_chars", "ech", "ec" },
{ "exit_alt_charset_mode", "rmacs", "ae" },
{ "exit_am_mode", "rmam", "RA" },
{ "exit_attribute_mode", "sgr0", "me" },
{ "exit_ca_mode", "rmcup", "te" },
{ "exit_delete_mode", "rmdc", "ed" },
{ "exit_doublewide_mode", "rwidm", "ZQ" },
{ "exit_insert_mode", "rmir", "ei" },
{ "exit_italics_mode", "ritm", "ZR" },
{ "exit_leftward_mode", "rlm", "ZS" },
{ "exit_micro_mode", "rmicm", "ZT" },
{ "exit_shadow_mode", "rshm", "ZU" },
{ "exit_standout_mode", "rmso", "se" },
{ "exit_subscript_mode", "rsubm", "ZV" },
{ "exit_superscript_mode", "rsupm", "ZW" },
{ "exit_underline_mode", "rmul", "ue" },
{ "exit_upward_mode", "rum", "ZX" },
{ "exit_xon_mode", "rmxon", "RX" },
{ "fixed_pause", "pause", "PA" },
{ "flash_hook", "hook", "fh" },
{ "flash_screen", "flash", "vb" },
{ "form_feed", "ff", "ff" },
{ "from_status_line", "fsl", "fs" },
{ "goto_window", "wingo", "WG" },
{ "hangup", "hup", "HU" },
{ "init_1string", "is1", "i1" },
{ "init_2string", "is2", "is" },
{ "init_3string", "is3", "i3" },
{ "init_file", "if", "if" },
{ "init_prog", "iprog", "iP" },
{ "initialize_color", "initc", "Ic" },
{ "initialize_pair", "initp", "Ip" },
{ "insert_character", "ich1", "ic" },
{ "insert_line", "il1", "al" },
{ "insert_padding", "ip", "ip" },
{ "key_a1", "ka1", "K1" },
{ "key_a3", "ka3", "K3" },
{ "key_b2", "kb2", "K2" },
{ "key_backspace", "kbs", "kb" },
{ "key_beg", "kbeg", "@1" },
{ "key_btab", "kcbt", "kB" },
{ "key_c1", "kc1", "K4" },
{ "key_c3", "kc3", "K5" },
{ "key_cancel", "kcan", "@2" },
{ "key_catab", "ktbc", "ka" },
{ "key_clear", "kclr", "kC" },
{ "key_close", "kclo", "@3" },
{ "key_command", "kcmd", "@4" },
{ "key_copy", "kcpy", "@5" },
{ "key_create", "kcrt", "@6" },
{ "key_ctab", "kctab", "kt" },
{ "key_dc", "kdch1", "kD" },
{ "key_dl", "kdl1", "kL" },
{ "key_down", "kcud1", "kd" },
{ "key_eic", "krmir", "kM" },
{ "key_end", "kend", "@7" },
{ "key_enter", "kent", "@8" },
{ "key_eol", "kel", "kE" },
{ "key_eos", "ked", "kS" },
{ "key_exit", "kext", "@9" },
{ "key_f0", "kf0", "k0" },
{ "key_f1", "kf1", "k1" },
{ "key_f10", "kf10", "k;" },
{ "key_f11", "kf11", "F1" },
{ "key_f12", "kf12", "F2" },
{ "key_f13", "kf13", "F3" },
{ "key_f14", "kf14", "F4" },
{ "key_f15", "kf15", "F5" },
{ "key_f16", "kf16", "F6" },
{ "key_f17", "kf17", "F7" },
{ "key_f18", "kf18", "F8" },
{ "key_f19", "kf19", "F9" },
{ "key_f2", "kf2", "k2" },
{ "key_f20", "kf20", "FA" },
{ "key_f21", "kf21", "FB" },
{ "key_f22", "kf22", "FC" },
{ "key_f23", "kf23", "FD" },
{ "key_f24", "kf24", "FE" },
{ "key_f25", "kf25", "FF" },
{ "key_f26", "kf26", "FG" },
{ "key_f27", "kf27", "FH" },
{ "key_f28", "kf28", "FI" },
{ "key_f29", "kf29", "FJ" },
{ "key_f3", "kf3", "k3" },
{ "key_f30", "kf30", "FK" },
{ "key_f31", "kf31", "FL" },
{ "key_f32", "kf32", "FM" },
{ "key_f33", "kf33", "FN" },
{ "key_f34", "kf34", "FO" },
{ "key_f35", "kf35", "FP" },
{ "key_f36", "kf36", "FQ" },
{ "key_f37", "kf37", "FR" },
{ "key_f38", "kf38", "FS" },
{ "key_f39", "kf39", "FT" },
{ "key_f4", "kf4", "k4" },
{ "key_f40", "kf40", "FU" },
{ "key_f41", "kf41", "FV" },
{ "key_f42", "kf42", "FW" },
{ "key_f43", "kf43", "FX" },
{ "key_f44", "kf44", "FY" },
{ "key_f45", "kf45", "FZ" },
{ "key_f46", "kf46", "Fa" },
{ "key_f47", "kf47", "Fb" },
{ "key_f48", "kf48", "Fc" },
{ "key_f49", "kf49", "Fd" },
{ "key_f5", "kf5", "k5" },
{ "key_f50", "kf50", "Fe" },
{ "key_f51", "kf51", "Ff" },
{ "key_f52", "kf52", "Fg" },
{ "key_f53", "kf53", "Fh" },
{ "key_f54", "kf54", "Fi" },
{ "key_f55", "kf55", "Fj" },
{ "key_f56", "kf56", "Fk" },
{ "key_f57", "kf57", "Fl" },
{ "key_f58", "kf58", "Fm" },
{ "key_f59", "kf59", "Fn" },
{ "key_f6", "kf6", "k6" },
{ "key_f60", "kf60", "Fo" },
{ "key_f61", "kf61", "Fp" },
{ "key_f62", "kf62", "Fq" },
{ "key_f63", "kf63", "Fr" },
{ "key_f7", "kf7", "k7" },
{ "key_f8", "kf8", "k8" },
{ "key_f9", "kf9", "k9" },
{ "key_find", "kfnd", "@0" },
{ "key_help", "khlp", "%1" },
{ "key_home", "khome", "kh" },
{ "key_ic", "kich1", "kI" },
{ "key_il", "kil1", "kA" },
{ "key_left", "kcub1", "kl" },
{ "key_ll", "kll", "kH" },
{ "key_mark", "kmrk", "%2" },
{ "key_message", "kmsg", "%3" },
{ "key_move", "kmov", "%4" },
{ "key_next", "knxt", "%5" },
{ "key_npage", "knp", "kN" },
{ "key_open", "kopn", "%6" },
{ "key_options", "kopt", "%7" },
{ "key_ppage", "kpp", "kP" },
{ "key_previous", "kprv", "%8" },
{ "key_print", "kprt", "%9" },
{ "key_redo", "krdo", "%0" },
{ "key_reference", "kref", "&1" },
{ "key_refresh", "krfr", "&2" },
{ "key_replace", "krpl", "&3" },
{ "key_restart", "krst", "&4" },
{ "key_resume", "kres", "&5" },
{ "key_right", "kcuf1", "kr" },
{ "key_save", "ksav", "&6" },
{ "key_sbeg", "kBEG", "&9" },
{ "key_scancel", "kCAN", "&0" },
{ "key_scommand", "kCMD", "*1" },
{ "key_scopy", "kCPY", "*2" },
{ "key_screate", "kCRT", "*3" },
{ "key_sdc", "kDC", "*4" },
{ "key_sdl", "kDL", "*5" },
{ "key_select", "kslt", "*6" },
{ "key_send", "kEND", "*7" },
{ "key_seol", "kEOL", "*8" },
{ "key_sexit", "kEXT", "*9" },
{ "key_sf", "kind", "kF" },
{ "key_sfind", "kFND", "*0" },
{ "key_shelp", "kHLP", "#1" },
{ "key_shome", "kHOM", "#2" },
{ "key_sic", "kIC", "#3" },
{ "key_sleft", "kLFT", "#4" },
{ "key_smessage", "kMSG", "%a" },
{ "key_smove", "kMOV", "%b" },
{ "key_snext", "kNXT", "%c" },
{ "key_soptions", "kOPT", "%d" },
{ "key_sprevious", "kPRV", "%e" },
{ "key_sprint", "kPRT", "%f" },
{ "key_sr", "kri", "kR" },
{ "key_sredo", "kRDO", "%g" },
{ "key_sreplace", "kRPL", "%h" },
{ "key_sright", "kRIT", "%i" },
{ "key_srsume", "kRES", "%j" },
{ "key_ssave", "kSAV", "!1" },
{ "key_ssuspend", "kSPD", "!2" },
{ "key_stab", "khts", "kT" },
{ "key_sundo", "kUND", "!3" },
{ "key_suspend", "kspd", "&7" },
{ "key_undo", "kund", "&8" },
{ "key_up", "kcuu1", "ku" },
{ "keypad_local", "rmkx", "ke" },
{ "keypad_xmit", "smkx", "ks" },
{ "lab_f0", "lf0", "l0" },
{ "lab_f1", "lf1", "l1" },
{ "lab_f10", "lf10", "la" },
{ "lab_f2", "lf2", "l2" },
{ "lab_f3", "lf3", "l3" },
{ "lab_f4", "lf4", "l4" },
{ "lab_f5", "lf5", "l5" },
{ "lab_f6", "lf6", "l6" },
{ "lab_f7", "lf7", "l7" },
{ "lab_f8", "lf8", "l8" },
{ "lab_f9", "lf9", "l9" },
{ "label_format", "fln", "Lf" },
{ "label_off", "rmln", "LF" },
{ "label_on", "smln", "LO" },
{ "meta_off", "rmm", "mo" },
{ "meta_on", "smm", "mm" },
{ "micro_column_address", "mhpa", "ZY" },
{ "micro_down", "mcud1", "ZZ" },
{ "micro_left", "mcub1", "Za" },
{ "micro_right", "mcuf1", "Zb" },
{ "micro_row_address", "mvpa", "Zc" },
{ "micro_up", "mcuu1", "Zd" },
{ "newline", "nel", "nw" },
{ "order_of_pins", "porder", "Ze" },
{ "orig_colors", "oc", "oc" },
{ "orig_pair", "op", "op" },
{ "pad_char", "pad", "pc" },
{ "parm_dch", "dch", "DC" },
{ "parm_delete_line", "dl", "DL" },
{ "parm_down_cursor", "cud", "DO" },
{ "parm_down_micro", "mcud", "Zf" },
{ "parm_ich", "ich", "IC" },
{ "parm_index", "indn", "SF" },
{ "parm_insert_line", "il", "AL" },
{ "parm_left_cursor", "cub", "LE" },
{ "parm_left_micro", "mcub", "Zg" },
{ "parm_right_cursor", "cuf", "RI" },
{ "parm_right_micro", "mcuf", "Zh" },
{ "parm_rindex", "rin", "SR" },
{ "parm_up_cursor", "cuu", "UP" },
{ "parm_up_micro", "mcuu", "Zi" },
{ "pkey_key", "pfkey", "pk" },
{ "pkey_local", "pfloc", "pl" },
{ "pkey_xmit", "pfx", "px" },
{ "plab_norm", "pln", "pn" },
{ "print_screen", "mc0", "ps" },
{ "prtr_non", "mc5p", "pO" },
{ "prtr_off", "mc4", "pf" },
{ "prtr_on", "mc5", "po" },
{ "pulse", "pulse", "PU" },
{ "quick_dial", "qdial", "QD" },
{ "remove_clock", "rmclk", "RC" },
{ "repeat_char", "rep", "rp" },
{ "req_for_input", "rfi", "RF" },
{ "reset_1string", "rs1", "r1" },
{ "reset_2string", "rs2", "r2" },
{ "reset_3string", "rs3", "r3" },
{ "reset_file", "rf", "rf" },
{ "restore_cursor", "rc", "rc" },
{ "row_address", "vpa", "cv" },
{ "save_cursor", "sc", "sc" },
{ "scroll_forward", "ind", "sf" },
{ "scroll_reverse", "ri", "sr" },
{ "select_char_set", "scs", "Zj" },
{ "set_attributes", "sgr", "sa" },
{ "set_background", "setb", "Sb" },
{ "set_bottom_margin", "smgb", "Zk" },
{ "set_bottom_margin_parm", "smgbp", "Zl" },
{ "set_clock", "sclk", "SC" },
{ "set_color_pair", "scp", "sp" },
{ "set_foreground", "setf", "Sf" },
{ "set_left_margin", "smgl", "ML" },
{ "set_left_margin_parm", "smglp", "Zm" },
{ "set_right_margin", "smgr", "MR" },
{ "set_right_margin_parm", "smgrp", "Zn" },
{ "set_tab", "hts", "st" },
{ "set_top_margin", "smgt", "Zo" },
{ "set_top_margin_parm", "smgtp", "Zp" },
{ "set_window", "wind", "wi" },
{ "start_bit_image", "sbim", "Zq" },
{ "start_char_set_def", "scsd", "Zr" },
{ "stop_bit_image", "rbim", "Zs" },
{ "stop_char_set_def", "rcsd", "Zt" },
{ "subscript_characters", "subcs", "Zu" },
{ "superscript_characters", "supcs", "Zv" },
{ "tab", "ht", "ta" },
{ "these_cause_cr", "docr", "Zw" },
{ "to_status_line", "tsl", "ts" },
{ "tone", "tone", "TO" },
{ "underline_char", "uc", "uc" },
{ "up_half_line", "hu", "hu" },
{ "user0", "u0", "u0" },
{ "user1", "u1", "u1" },
{ "user2", "u2", "u2" },
{ "user3", "u3", "u3" },
{ "user4", "u4", "u4" },
{ "user5", "u5", "u5" },
{ "user6", "u6", "u6" },
{ "user7", "u7", "u7" },
{ "user8", "u8", "u8" },
{ "user9", "u9", "u9" },
{ "wait_tone", "wait", "WA" },
{ "xoff_character", "xoffc", "XF" },
{ "xon_character", "xonc", "XN" },
{ "zero_motion", "zerom", "Zx" },
{ "alt_scancode_esc", "scesa", "S8" },
{ "bit_image_carriage_return", "bicr", "Yv" },
{ "bit_image_newline", "binel", "Zz" },
{ "bit_image_repeat", "birep", "Xy" },
{ "char_set_names", "csnm", "Zy" },
{ "code_set_init", "csin", "ci" },
{ "color_names", "colornm", "Yw" },
{ "define_bit_image_region", "defbi", "Yx" },
{ "device_type", "devt", "dv" },
{ "display_pc_char", "dispc", "S1" },
{ "end_bit_image_region", "endbi", "Yy" },
{ "enter_pc_charset_mode", "smpch", "S2" },
{ "enter_scancode_mode", "smsc", "S4" },
{ "exit_pc_charset_mode", "rmpch", "S3" },
{ "exit_scancode_mode", "rmsc", "S5" },
{ "get_mouse", "getm", "Gm" },
{ "key_mouse", "kmous", "Km" },
{ "mouse_info", "minfo", "Mi" },
{ "pc_term_options", "pctrm", "S6" },
{ "pkey_plab", "pfxl", "xl" },
{ "req_mouse_pos", "reqmp", "RQ" },
{ "scancode_escape", "scesc", "S7" },
{ "set0_des_seq", "s0ds", "s0" },
{ "set1_des_seq", "s1ds", "s1" },
{ "set2_des_seq", "s2ds", "s2" },
{ "set3_des_seq", "s3ds", "s3" },
{ "set_a_background", "setab", "AB" },
{ "set_a_foreground", "setaf", "AF" },
{ "set_color_band", "setcolor", "Yz" },
{ "set_lr_margin", "smglr", "ML" },
{ "set_page_length", "slines", "YZ" },
{ "set_tb_margin", "smgtb", "MT" },
{ "enter_horizontal_hl_mode", "ehhlm", "Xh" },
{ "enter_left_hl_mode", "elhlm", "Xl" },
{ "enter_low_hl_mode", "elohlm", "Xo" },
{ "enter_right_hl_mode", "erhlm", "Xr" },
{ "enter_top_hl_mode", "ethlm", "Xt" },
{ "enter_vertical_hl_mode", "evhlm", "Xv" },
{ "set_a_attributes", "sgr1", "sA" },
{ "set_pglen_inch", "slength", "sL" }
};
Map<String, String[]> map = new HashMap<String, String[]>();
for (String[] names : list) {
for (String name : names) {
map.put(name, names);
}
}
NAMES = Collections.unmodifiableMap(map);
}
private static String ANSI_CAPS =
"#\tReconstructed via infocmp from file: /usr/share/terminfo/61/ansi\n" +
"ansi|ansi/pc-term compatible with color,\n" +
"\tam, mc5i, mir, msgr,\n" +
"\tcolors#8, cols#80, it#8, lines#24, ncv#3, pairs#64,\n" +
"\tacsc=+\\020\\,\\021-\\030.^Y0\\333`\\004a\\261f\\370g\\361h\\260j\\331k\\277l\\332m\\300n\\305o~p\\304q\\304r\\304s_t\\303u\\264v\\301w\\302x\\263y\\363z\\362{\\343|\\330}\\234~\\376,\n" +
"\tbel=^G, blink=\\E[5m, bold=\\E[1m, cbt=\\E[Z, clear=\\E[H\\E[J,\n" +
"\tcr=^M, cub=\\E[%p1%dD, cub1=\\E[D, cud=\\E[%p1%dB, cud1=\\E[B,\n" +
"\tcuf=\\E[%p1%dC, cuf1=\\E[C, cup=\\E[%i%p1%d;%p2%dH,\n" +
"\tcuu=\\E[%p1%dA, cuu1=\\E[A, dch=\\E[%p1%dP, dch1=\\E[P,\n" +
"\tdl=\\E[%p1%dM, dl1=\\E[M, ech=\\E[%p1%dX, ed=\\E[J, el=\\E[K,\n" +
"\tel1=\\E[1K, home=\\E[H, hpa=\\E[%i%p1%dG, ht=\\E[I, hts=\\EH,\n" +
"\tich=\\E[%p1%d@, il=\\E[%p1%dL, il1=\\E[L, ind=^J,\n" +
"\tindn=\\E[%p1%dS, invis=\\E[8m, kbs=^H, kcbt=\\E[Z, kcub1=\\E[D,\n" +
"\tkcud1=\\E[B, kcuf1=\\E[C, kcuu1=\\E[A, khome=\\E[H, kich1=\\E[L,\n" +
"\tmc4=\\E[4i, mc5=\\E[5i, nel=\\r\\E[S, op=\\E[39;49m,\n" +
"\trep=%p1%c\\E[%p2%{1}%-%db, rev=\\E[7m, rin=\\E[%p1%dT,\n" +
"\trmacs=\\E[10m, rmpch=\\E[10m, rmso=\\E[m, rmul=\\E[m,\n" +
"\ts0ds=\\E(B, s1ds=\\E)B, s2ds=\\E*B, s3ds=\\E+B,\n" +
"\tsetab=\\E[4%p1%dm, setaf=\\E[3%p1%dm,\n" +
"\tsgr=\\E[0;10%?%p1%t;7%;%?%p2%t;4%;%?%p3%t;7%;%?%p4%t;5%;%?%p6%t;1%;%?%p7%t;8%;%?%p9%t;11%;m,\n" +
"\tsgr0=\\E[0;10m, smacs=\\E[11m, smpch=\\E[11m, smso=\\E[7m,\n" +
"\tsmul=\\E[4m, tbc=\\E[2g, u6=\\E[%i%d;%dR, u7=\\E[6n,\n" +
"\tu8=\\E[?%[;0123456789]c, u9=\\E[c, vpa=\\E[%i%p1%dd,";
}

View file

@ -1,165 +0,0 @@
/*
* Copyright (c) 2002-2016, 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.PrintStream;
//import java.util.logging.LogRecord;
//import java.util.logging.Logger;
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
/**
* Internal logger.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
* @since 2.0
*/
public final class Log
{
///CLOVER:OFF
public static enum Level
{
TRACE,
DEBUG,
INFO,
WARN,
ERROR
}
public static final boolean TRACE = Configuration.getBoolean(Log.class.getName() + ".trace");
public static final boolean DEBUG = TRACE || Configuration.getBoolean(Log.class.getName() + ".debug");
private static PrintStream output = System.err;
private static boolean useJul = Configuration.getBoolean("jline.log.jul");
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) {
if (useJul) {
logWithJul(level, messages);
return;
}
//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();
}
}
static void logWithJul(Level level, Object... messages) {
// Logger logger = Logger.getLogger("jline");
// Throwable cause = null;
// ByteArrayOutputStream baos = new ByteArrayOutputStream();
// PrintStream ps = new PrintStream(baos);
// 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) {
// cause = (Throwable) messages[i];
// }
// else {
// render(ps, messages[i]);
// }
// }
// ps.close();
// LogRecord r = new LogRecord(toJulLevel(level), baos.toString());
// r.setThrown(cause);
// logger.log(r);
}
// private static java.util.logging.Level toJulLevel(Level level) {
// switch (level) {
// case TRACE:
// return java.util.logging.Level.FINEST;
// case DEBUG:
// return java.util.logging.Level.FINE;
// case INFO:
// return java.util.logging.Level.INFO;
// case WARN:
// return java.util.logging.Level.WARNING;
// case ERROR:
// return java.util.logging.Level.SEVERE;
// default:
// throw new IllegalArgumentException();
// }
// }
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);
}
}

View file

@ -1,311 +0,0 @@
/*
* Copyright (c) 2002-2016, 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).
*/
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.
*/
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.
*/
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");
}
}

View file

@ -1,24 +0,0 @@
/*
* Copyright (c) 2002-2016, 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 "";
}

View file

@ -1,27 +0,0 @@
/*
* Copyright (c) 2002-2016, 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;
}
}

View file

@ -1,360 +0,0 @@
/*
* Copyright (c) 2002-2016, 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.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
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>
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</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 static final String UNDEFINED;
public static final String DEFAULT_TTY = "/dev/tty";
private static final boolean SUPPORTS_REDIRECT;
private static final Object REDIRECT_INHERIT;
private static final Method REDIRECT_INPUT_METHOD;
private static final Map<String, TerminalLineSettings> SETTINGS = new HashMap<String, TerminalLineSettings>();
static {
if (Configuration.isHpux()) {
UNDEFINED = "^-";
} else {
UNDEFINED = "undef";
}
boolean supportsRedirect;
Object redirectInherit = null;
Method redirectInputMethod = null;
try {
Class<?> redirect = Class.forName("java.lang.ProcessBuilder$Redirect");
redirectInherit = redirect.getField("INHERIT").get(null);
redirectInputMethod = ProcessBuilder.class.getMethod("redirectInput", redirect);
supportsRedirect = System.class.getMethod("console").invoke(null) != null;
} catch (Throwable t) {
supportsRedirect = false;
}
SUPPORTS_REDIRECT = supportsRedirect;
REDIRECT_INHERIT = redirectInherit;
REDIRECT_INPUT_METHOD = redirectInputMethod;
}
private String sttyCommand;
private String shCommand;
private String ttyDevice;
private String config;
private String initialConfig;
private long configLastFetched;
private boolean useRedirect;
@Deprecated
public TerminalLineSettings() throws IOException, InterruptedException {
this(DEFAULT_TTY);
}
@Deprecated
public TerminalLineSettings(String ttyDevice) throws IOException, InterruptedException {
this(ttyDevice, false);
}
private TerminalLineSettings(String ttyDevice, boolean unused) throws IOException, InterruptedException {
checkNotNull(ttyDevice);
this.sttyCommand = Configuration.getString(JLINE_STTY, DEFAULT_STTY);
this.shCommand = Configuration.getString(JLINE_SH, DEFAULT_SH);
this.ttyDevice = ttyDevice;
this.useRedirect = SUPPORTS_REDIRECT && DEFAULT_TTY.equals(ttyDevice);
this.initialConfig = get("-g").trim();
this.config = get("-a");
this.configLastFetched = System.currentTimeMillis();
Log.debug("Config: ", config);
// sanity check
if (config.length() == 0) {
throw new IOException(MessageFormat.format("Unrecognized stty code: {0}", config));
}
}
public static synchronized TerminalLineSettings getSettings(String device) throws IOException, InterruptedException {
TerminalLineSettings settings = SETTINGS.get(device);
if (settings == null) {
settings = new TerminalLineSettings(device, false);
SETTINGS.put(device, settings);
}
return settings;
}
public String getTtyDevice() {
return ttyDevice;
}
public String getConfig() {
return config;
}
public void restore() throws IOException, InterruptedException {
set(initialConfig);
}
public String get(final String args) throws IOException, InterruptedException {
checkNotNull(args);
return stty(args);
}
public void set(final String args) throws IOException, InterruptedException {
checkNotNull(args);
stty(args.split(" "));
}
public void set(final String... args) throws IOException, InterruptedException {
checkNotNull(args);
stty(args);
}
public void undef(final String name) throws IOException, InterruptedException {
checkNotNull(name);
stty(name, UNDEFINED);
}
/**
* <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);
if (!fetchConfig(name)) {
return -1;
}
return getProperty(name, config);
}
public String getPropertyAsString(String name) {
checkNotNull(name);
if (!fetchConfig(name)) {
return null;
}
return getPropertyAsString(name, config);
}
private boolean fetchConfig(String 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 false;
}
}
// always update the last fetched time and try to parse the output
if (currentTime - configLastFetched > 1000) {
configLastFetched = currentTime;
}
return true;
}
/**
* <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 String getPropertyAsString(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 null;
}
}
}
return matcher.group(1);
}
protected static int getProperty(String name, String stty) {
String str = getPropertyAsString(name, stty);
return str != null ? parseControlChar(str) : -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 {
String[] s = new String[args.length + 1];
s[0] = sttyCommand;
System.arraycopy(args, 0, s, 1, args.length);
return exec(s);
}
private String exec(final String... cmd) throws IOException, InterruptedException {
checkNotNull(cmd);
Log.trace("Running: ", cmd);
Process p = null;
if (useRedirect) {
try {
p = inheritInput(new ProcessBuilder(cmd)).start();
} catch (Throwable t) {
useRedirect = false;
}
}
if (p == null) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < cmd.length; i++) {
if (i > 0) {
sb.append(' ');
}
sb.append(cmd[i]);
}
sb.append(" < ");
sb.append(ttyDevice);
p = new ProcessBuilder(shCommand, "-c", sb.toString()).start();
}
String result = waitAndCapture(p);
Log.trace("Result: ", result);
return result;
}
private static ProcessBuilder inheritInput(ProcessBuilder pb) throws Exception {
REDIRECT_INPUT_METHOD.invoke(pb, REDIRECT_INHERIT);
return pb;
}
public static String waitAndCapture(Process p) throws IOException, InterruptedException {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
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);
}
return bout.toString();
}
private static void close(final Closeable... closeables) {
for (Closeable c : closeables) {
if (c != null) {
try {
c.close();
} catch (Exception e) {
// Ignore
}
}
}
}
}

View file

@ -1,33 +0,0 @@
/*
* Copyright (c) 2002-2016, 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
}

View file

@ -1,44 +0,0 @@
/*
* Copyright (c) 2002-2016, 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);
}
}
}

View file

@ -0,0 +1,170 @@
/*
* Copyright (c) 2002-2018, 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.org.jline.keymap;
import java.io.IOError;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Deque;
import jdk.internal.org.jline.reader.EndOfFileException;
import jdk.internal.org.jline.utils.ClosedException;
import jdk.internal.org.jline.utils.NonBlockingReader;
/**
* The BindingReader will transform incoming chars into
* key bindings
*
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
*/
public class BindingReader {
protected final NonBlockingReader reader;
protected final StringBuilder opBuffer = new StringBuilder();
protected final Deque<Integer> pushBackChar = new ArrayDeque<>();
protected String lastBinding;
public BindingReader(NonBlockingReader reader) {
this.reader = reader;
}
/**
* Read from the input stream and decode an operation from the key map.
*
* The input stream will be read character by character until a matching
* binding can be found. Characters that can't possibly be matched to
* any binding will be send with the {@link KeyMap#getNomatch()} binding.
* Unicode (&gt;= 128) characters will be matched to {@link KeyMap#getUnicode()}.
* If the current key sequence is ambiguous, i.e. the sequence is bound but
* it's also a prefix to other sequences, then the {@link KeyMap#getAmbiguousTimeout()}
* timeout will be used to wait for another incoming character.
* If a character comes, the disambiguation will be done. If the timeout elapses
* and no character came in, or if the timeout is &lt;= 0, the current bound operation
* will be returned.
*
* @param keys the KeyMap to use for decoding the input stream
* @param <T> the type of bindings to be read
* @return the decoded binding or <code>null</code> if the end of
* stream has been reached
*/
public <T> T readBinding(KeyMap<T> keys) {
return readBinding(keys, null, true);
}
public <T> T readBinding(KeyMap<T> keys, KeyMap<T> local) {
return readBinding(keys, local, true);
}
public <T> T readBinding(KeyMap<T> keys, KeyMap<T> local, boolean block) {
lastBinding = null;
T o = null;
int[] remaining = new int[1];
boolean hasRead = false;
for (;;) {
if (local != null) {
o = local.getBound(opBuffer, remaining);
}
if (o == null && (local == null || remaining[0] >= 0)) {
o = keys.getBound(opBuffer, remaining);
}
// We have a binding and additional chars
if (o != null) {
if (remaining[0] >= 0) {
runMacro(opBuffer.substring(opBuffer.length() - remaining[0]));
opBuffer.setLength(opBuffer.length() - remaining[0]);
}
else {
long ambiguousTimeout = keys.getAmbiguousTimeout();
if (ambiguousTimeout > 0 && peekCharacter(ambiguousTimeout) != NonBlockingReader.READ_EXPIRED) {
o = null;
}
}
if (o != null) {
lastBinding = opBuffer.toString();
opBuffer.setLength(0);
return o;
}
// We don't match anything
} else if (remaining[0] > 0) {
int cp = opBuffer.codePointAt(0);
String rem = opBuffer.substring(Character.charCount(cp));
lastBinding = opBuffer.substring(0, Character.charCount(cp));
// Unicode character
o = (cp >= KeyMap.KEYMAP_LENGTH) ? keys.getUnicode() : keys.getNomatch();
opBuffer.setLength(0);
opBuffer.append(rem);
if (o != null) {
return o;
}
}
if (!block && hasRead) {
break;
}
int c = readCharacter();
if (c == -1) {
return null;
}
opBuffer.appendCodePoint(c);
hasRead = true;
}
return null;
}
/**
* Read a codepoint from the terminal.
*
* @return the character, or -1 if an EOF is received.
*/
public int readCharacter() {
if (!pushBackChar.isEmpty()) {
return pushBackChar.pop();
}
try {
int c = NonBlockingReader.READ_EXPIRED;
int s = 0;
while (c == NonBlockingReader.READ_EXPIRED) {
c = reader.read(100L);
if (c >= 0 && Character.isHighSurrogate((char) c)) {
s = c;
c = NonBlockingReader.READ_EXPIRED;
}
}
return s != 0 ? Character.toCodePoint((char) s, (char) c) : c;
} catch (ClosedException e) {
throw new EndOfFileException(e);
} catch (IOException e) {
throw new IOError(e);
}
}
public int peekCharacter(long timeout) {
if (!pushBackChar.isEmpty()) {
return pushBackChar.peek();
}
try {
return reader.peek(timeout);
} catch (IOException e) {
throw new IOError(e);
}
}
public void runMacro(String macro) {
macro.codePoints().forEachOrdered(pushBackChar::addLast);
}
public String getCurrentBuffer() {
return opBuffer.toString();
}
public String getLastBinding() {
return lastBinding;
}
}

View file

@ -0,0 +1,460 @@
/*
* Copyright (c) 2002-2016, 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.org.jline.keymap;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
import jdk.internal.org.jline.terminal.Terminal;
import jdk.internal.org.jline.utils.Curses;
import jdk.internal.org.jline.utils.InfoCmp.Capability;
/**
* 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<T> {
public static final int KEYMAP_LENGTH = 128;
public static final long DEFAULT_AMBIGUOUS_TIMEOUT = 1000L;
private Object[] mapping = new Object[KEYMAP_LENGTH];
private T anotherKey = null;
private T unicode;
private T nomatch;
private long ambiguousTimeout = DEFAULT_AMBIGUOUS_TIMEOUT;
public static String display(String key) {
StringBuilder sb = new StringBuilder();
sb.append("\"");
for (int i = 0; i < key.length(); i++) {
char c = key.charAt(i);
if (c < 32) {
sb.append('^');
sb.append((char) (c + 'A' - 1));
} else if (c == 127) {
sb.append("^?");
} else if (c == '^' || c == '\\') {
sb.append('\\').append(c);
} else if (c >= 128) {
sb.append(String.format("\\u%04x", (int) c));
} else {
sb.append(c);
}
}
sb.append("\"");
return sb.toString();
}
public static String translate(String str) {
int i;
if (!str.isEmpty()) {
char c = str.charAt(0);
if ((c == '\'' || c == '"') && str.charAt(str.length() - 1) == c) {
str = str.substring(1, str.length() - 1);
}
}
StringBuilder keySeq = new StringBuilder();
for (i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c == '\\') {
if (++i >= str.length()) {
break;
}
c = str.charAt(i);
switch (c) {
case 'a':
c = 0x07;
break;
case 'b':
c = '\b';
break;
case 'd':
c = 0x7f;
break;
case 'e':
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);
}
i--;
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);
}
i--;
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;
case 'C':
if (++i >= str.length()) {
break;
}
c = str.charAt(i);
if (c == '-') {
if (++i >= str.length()) {
break;
}
c = str.charAt(i);
}
c = c == '?' ? 0x7f : (char) (Character.toUpperCase(c) & 0x1f);
break;
}
} else if (c == '^') {
if (++i >= str.length()) {
break;
}
c = str.charAt(i);
if (c != '^') {
c = c == '?' ? 0x7f : (char) (Character.toUpperCase(c) & 0x1f);
}
}
keySeq.append(c);
}
return keySeq.toString();
}
public static Collection<String> range(String range) {
String[] keys = range.split("-");
if (keys.length != 2) {
return null;
}
keys[0] = translate(keys[0]);
keys[1] = translate(keys[1]);
if (keys[0].length() != keys[1].length()) {
return null;
}
String pfx;
if (keys[0].length() > 1) {
pfx = keys[0].substring(0, keys[0].length() - 1);
if (!keys[1].startsWith(pfx)) {
return null;
}
} else {
pfx = "";
}
char c0 = keys[0].charAt(keys[0].length() - 1);
char c1 = keys[1].charAt(keys[1].length() - 1);
if (c0 > c1) {
return null;
}
Collection<String> seqs = new ArrayList<>();
for (char c = c0; c <= c1; c++) {
seqs.add(pfx + c);
}
return seqs;
}
public static String esc() {
return "\033";
}
public static String alt(char c) {
return "\033" + c;
}
public static String alt(String c) {
return "\033" + c;
}
public static String del() {
return "\177";
}
public static String ctrl(char key) {
return key == '?' ? del() : Character.toString((char) (Character.toUpperCase(key) & 0x1f));
}
public static String key(Terminal terminal, Capability capability) {
return Curses.tputs(terminal.getStringCapability(capability));
}
public static final Comparator<String> KEYSEQ_COMPARATOR = (s1, s2) -> {
int len1 = s1.length();
int len2 = s2.length();
int lim = Math.min(len1, len2);
int k = 0;
while (k < lim) {
char c1 = s1.charAt(k);
char c2 = s2.charAt(k);
if (c1 != c2) {
int l = len1 - len2;
return l != 0 ? l : c1 - c2;
}
k++;
}
return len1 - len2;
};
//
// Methods
//
public T getUnicode() {
return unicode;
}
public void setUnicode(T unicode) {
this.unicode = unicode;
}
public T getNomatch() {
return nomatch;
}
public void setNomatch(T nomatch) {
this.nomatch = nomatch;
}
public long getAmbiguousTimeout() {
return ambiguousTimeout;
}
public void setAmbiguousTimeout(long ambiguousTimeout) {
this.ambiguousTimeout = ambiguousTimeout;
}
public T getAnotherKey() {
return anotherKey;
}
public Map<String, T> getBoundKeys() {
Map<String, T> bound = new TreeMap<>(KEYSEQ_COMPARATOR);
doGetBoundKeys(this, "", bound);
return bound;
}
@SuppressWarnings("unchecked")
private static <T> void doGetBoundKeys(KeyMap<T> keyMap, String prefix, Map<String, T> bound) {
if (keyMap.anotherKey != null) {
bound.put(prefix, keyMap.anotherKey);
}
for (int c = 0; c < keyMap.mapping.length; c++) {
if (keyMap.mapping[c] instanceof KeyMap) {
doGetBoundKeys((KeyMap<T>) keyMap.mapping[c],
prefix + (char) (c),
bound);
} else if (keyMap.mapping[c] != null) {
bound.put(prefix + (char) (c), (T) keyMap.mapping[c]);
}
}
}
@SuppressWarnings("unchecked")
public T getBound(CharSequence keySeq, int[] remaining) {
remaining[0] = -1;
if (keySeq != null && keySeq.length() > 0) {
char c = keySeq.charAt(0);
if (c >= mapping.length) {
remaining[0] = Character.codePointCount(keySeq, 0, keySeq.length());
return null;
} else {
if (mapping[c] instanceof KeyMap) {
CharSequence sub = keySeq.subSequence(1, keySeq.length());
return ((KeyMap<T>) mapping[c]).getBound(sub, remaining);
} else if (mapping[c] != null) {
remaining[0] = keySeq.length() - 1;
return (T) mapping[c];
} else {
remaining[0] = keySeq.length();
return anotherKey;
}
}
} else {
return anotherKey;
}
}
public T getBound(CharSequence keySeq) {
int[] remaining = new int[1];
T res = getBound(keySeq, remaining);
return remaining[0] <= 0 ? res : null;
}
public void bindIfNotBound(T function, CharSequence keySeq) {
if (function != null && keySeq != null) {
bind(this, keySeq, function, true);
}
}
public void bind(T function, CharSequence... keySeqs) {
for (CharSequence keySeq : keySeqs) {
bind(function, keySeq);
}
}
public void bind(T function, Iterable<? extends CharSequence> keySeqs) {
for (CharSequence keySeq : keySeqs) {
bind(function, keySeq);
}
}
public void bind(T function, CharSequence keySeq) {
if (keySeq != null) {
if (function == null) {
unbind(keySeq);
} else {
bind(this, keySeq, function, false);
}
}
}
public void unbind(CharSequence... keySeqs) {
for (CharSequence keySeq : keySeqs) {
unbind(keySeq);
}
}
public void unbind(CharSequence keySeq) {
if (keySeq != null) {
unbind(this, keySeq);
}
}
@SuppressWarnings("unchecked")
private static <T> T unbind(KeyMap<T> map, CharSequence keySeq) {
KeyMap<T> prev = null;
if (keySeq != null && keySeq.length() > 0) {
for (int i = 0; i < keySeq.length() - 1; i++) {
char c = keySeq.charAt(i);
if (c > map.mapping.length) {
return null;
}
if (!(map.mapping[c] instanceof KeyMap)) {
return null;
}
prev = map;
map = (KeyMap<T>) map.mapping[c];
}
char c = keySeq.charAt(keySeq.length() - 1);
if (c > map.mapping.length) {
return null;
}
if (map.mapping[c] instanceof KeyMap) {
KeyMap<?> sub = (KeyMap) map.mapping[c];
Object res = sub.anotherKey;
sub.anotherKey = null;
return (T) res;
} else {
Object res = map.mapping[c];
map.mapping[c] = null;
int nb = 0;
for (int i = 0; i < map.mapping.length; i++) {
if (map.mapping[i] != null) {
nb++;
}
}
if (nb == 0 && prev != null) {
prev.mapping[keySeq.charAt(keySeq.length() - 2)] = map.anotherKey;
}
return (T) res;
}
}
return null;
}
@SuppressWarnings("unchecked")
private static <T> void bind(KeyMap<T> map, CharSequence keySeq, T 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<T> m = new KeyMap<>();
m.anotherKey = (T) map.mapping[c];
map.mapping[c] = m;
}
map = (KeyMap) map.mapping[c];
} else {
if (map.mapping[c] instanceof KeyMap) {
((KeyMap) map.mapping[c]).anotherKey = function;
} else {
Object op = map.mapping[c];
if (!onlyIfNotBound || op == null) {
map.mapping[c] = function;
}
}
}
}
}
}
}

View file

@ -6,20 +6,17 @@
* *
* http://www.opensource.org/licenses/bsd-license.php * http://www.opensource.org/licenses/bsd-license.php
*/ */
package jdk.internal.jline; package jdk.internal.org.jline.reader;
/** /**
* Terminal extension. * Marker interface for objects bound to key sequences.
*
* @see Macro
* @see Reference
* @see Widget
* @see org.jline.keymap.KeyMap
* *
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a> * @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
* @since 2.13
*/ */
public interface Terminal2 extends Terminal public interface Binding {
{
boolean getBooleanCapability(String capability);
Integer getNumericCapability(String capability);
String getStringCapability(String capability);
} }

View file

@ -0,0 +1,87 @@
/*
* Copyright (c) 2002-2017, 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.org.jline.reader;
public interface Buffer {
/*
* Read access
*/
int cursor();
int atChar(int i);
int length();
int currChar();
int prevChar();
int nextChar();
/*
* Movement
*/
boolean cursor(int position);
int move(int num);
boolean up();
boolean down();
boolean moveXY(int dx, int dy);
/*
* Modification
*/
boolean clear();
boolean currChar(int c);
void write(int c);
void write(int c, boolean overTyping);
void write(CharSequence str);
void write(CharSequence str, boolean overTyping);
boolean backspace();
int backspace(int num);
boolean delete();
int delete(int num);
/*
* String
*/
String substring(int start);
String substring(int start, int end);
String upToCursor();
String toString();
/*
* Copy
*/
Buffer copy();
void copyFrom(Buffer buffer);
}

View file

@ -0,0 +1,141 @@
/*
* Copyright (c) 2002-2018, 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.org.jline.reader;
import java.util.Objects;
/**
* A completion candidate.
*
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
*/
public class Candidate implements Comparable<Candidate> {
private final String value;
private final String displ;
private final String group;
private final String descr;
private final String suffix;
private final String key;
private final boolean complete;
/**
* Simple constructor with only a single String as an argument.
*
* @param value the candidate
*/
public Candidate(String value) {
this(value, value, null, null, null, null, true);
}
/**
* Constructs a new Candidate.
*
* @param value the value
* @param displ the display string
* @param group the group
* @param descr the description
* @param suffix the suffix
* @param key the key
* @param complete the complete flag
*/
public Candidate(String value, String displ, String group, String descr, String suffix, String key, boolean complete) {
Objects.requireNonNull(value);
this.value = value;
this.displ = displ;
this.group = group;
this.descr = descr;
this.suffix = suffix;
this.key = key;
this.complete = complete;
}
/**
* The value that will be used for the actual completion.
* This string should not contain ANSI sequences.
* @return the value
*/
public String value() {
return value;
}
/**
* The string that will be displayed to the user.
* This string may contain ANSI sequences.
* @return the display string
*/
public String displ() {
return displ;
}
/**
* The group name for this candidate.
* Candidates can be grouped together and this string is used
* as a key for the group and displayed to the user.
* @return the group
*
* @see LineReader.Option#GROUP
* @see LineReader.Option#AUTO_GROUP
*/
public String group() {
return group;
}
/**
* Description of this candidate, usually a small help message
* to understand the meaning of this candidate.
* This string may contain ANSI sequences.
* @return the description
*/
public String descr() {
return descr;
}
/**
* The suffix is added when this candidate is displayed.
* However, if the next character entered does not match,
* the suffix will be automatically removed.
* This string should not contain ANSI sequences.
* @return the suffix
*
* @see LineReader.Option#AUTO_REMOVE_SLASH
* @see LineReader#REMOVE_SUFFIX_CHARS
*/
public String suffix() {
return suffix;
}
/**
* Candidates which have the same key will be merged together.
* For example, if a command has multiple aliases, they can be merged
* if they are using the same key.
* @return the key
*/
public String key() {
return key;
}
/**
* Boolean indicating whether this candidate is complete or
* if the completer may further expand the candidate value
* after this candidate has been selected.
* This can be the case when completing folders for example.
* If the candidate is complete and is selected, a space
* separator will be added.
* @return the completion flag
*/
public boolean complete() {
return complete;
}
@Override
public int compareTo(Candidate o) {
return value.compareTo(o.value);
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2002-2018, 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.org.jline.reader;
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>
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
* @since 2.3
*/
public interface Completer
{
/**
* Populates <i>candidates</i> with a list of possible completions for the <i>command line</i>.
*
* The list of candidates will be sorted and filtered by the LineReader, so that
* the list of candidates displayed to the user will usually be smaller than
* the list given by the completer. Thus it is not necessary for the completer
* to do any matching based on the current buffer. On the contrary, in order
* for the typo matcher to work, all possible candidates for the word being
* completed should be returned.
*
* @param reader The line reader
* @param line The parsed command line
* @param candidates The {@link List} of candidates to populate
*/
void complete(LineReader reader, ParsedLine line, List<Candidate> candidates);
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2002-2018, 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.org.jline.reader;
/**
* An extension of {@link ParsedLine} that, being aware of the quoting and escaping rules
* of the {@link org.jline.reader.Parser} that produced it, knows if and how a completion candidate
* should be escaped/quoted.
*
* @author Eric Bottard
*/
public interface CompletingParsedLine extends ParsedLine {
CharSequence escape(CharSequence candidate, boolean complete);
int rawWordCursor();
int rawWordLength();
}

View file

@ -0,0 +1,39 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package jdk.internal.org.jline.reader;
public class EOFError extends SyntaxError {
private static final long serialVersionUID = 1L;
private final String missing;
public EOFError(int line, int column, String message) {
this(line, column, message, null);
}
public EOFError(int line, int column, String message, String missing) {
super(line, column, message);
this.missing = missing;
}
public String getMissing() {
return missing;
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2002-2016, 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.org.jline.reader;
/**
* This exception is thrown by {@link LineReader#readLine} when
* user the user types ctrl-D).
*/
public class EndOfFileException extends RuntimeException {
private static final long serialVersionUID = 528485360925144689L;
public EndOfFileException() {
}
public EndOfFileException(String message) {
super(message);
}
public EndOfFileException(String message, Throwable cause) {
super(message, cause);
}
public EndOfFileException(Throwable cause) {
super(cause);
}
public EndOfFileException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View file

@ -0,0 +1,17 @@
/*
* Copyright (c) 2002-2016, 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.org.jline.reader;
public interface Expander {
String expandHistory(History history, String line);
String expandVar(String word);
}

View file

@ -0,0 +1,16 @@
/*
* Copyright (c) 2002-2016, 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.org.jline.reader;
import jdk.internal.org.jline.utils.AttributedString;
public interface Highlighter {
AttributedString highlight(LineReader reader, String buffer);
}

View file

@ -0,0 +1,173 @@
/*
* Copyright (c) 2002-2018, 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.org.jline.reader;
import java.io.IOException;
import java.time.Instant;
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>
{
/**
* Initialize the history for the given reader.
* @param reader the reader to attach to
*/
void attach(LineReader reader);
/**
* Load history.
* @throws IOException if a problem occurs
*/
void load() throws IOException;
/**
* Save history.
* @throws IOException if a problem occurs
*/
void save() throws IOException;
/**
* Purge history.
* @throws IOException if a problem occurs
*/
void purge() throws IOException;
int size();
default boolean isEmpty() {
return size() == 0;
}
int index();
int first();
int last();
String get(int index);
default void add(String line) {
add(Instant.now(), line);
}
void add(Instant time, String line);
/**
* Check if an entry should be persisted or not.
*
* @param entry the entry to check
* @return <code>true</code> if the given entry should be persisted, <code>false</code> otherwise
*/
default boolean isPersistable(Entry entry) {
return true;
}
//
// Entries
//
interface Entry
{
int index();
Instant time();
String line();
}
ListIterator<Entry> iterator(int index);
default ListIterator<Entry> iterator() {
return iterator(first());
}
default Iterator<Entry> reverseIterator() {
return reverseIterator(last());
}
default Iterator<Entry> reverseIterator(int index) {
return new Iterator<Entry>() {
private final ListIterator<Entry> it = iterator(index + 1);
@Override
public boolean hasNext() {
return it.hasPrevious();
}
@Override
public Entry next() {
return it.previous();
}
};
}
//
// Navigation
//
/**
* Return the content of the current buffer.
*
* @return the content of the current buffer
*/
String current();
/**
* Move the pointer to the previous element in the buffer.
*
* @return true if we successfully went to the previous element
*/
boolean previous();
/**
* Move the pointer to the next element in the buffer.
*
* @return true if we successfully went to the next element
*/
boolean next();
/**
* Moves the history index to the first entry.
*
* @return Return false if there are no iterator in the history or if the
* history is already at the beginning.
*/
boolean moveToFirst();
/**
* 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 iterator or the history
* index was already at the last entry.
*/
boolean moveToLast();
/**
* Move to the specified index in the history
*
* @param index The index to move to.
* @return Returns true if the index was moved.
*/
boolean moveTo(int index);
/**
* Move to the end of the history buffer. This will be a blank entry, after
* all of the other iterator.
*/
void moveToEnd();
}

View file

@ -0,0 +1,655 @@
/*
* Copyright (c) 2002-2018, 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.org.jline.reader;
import java.io.InputStream;
import java.util.Map;
import java.util.function.IntConsumer;
import jdk.internal.org.jline.keymap.KeyMap;
import jdk.internal.org.jline.terminal.MouseEvent;
import jdk.internal.org.jline.terminal.Terminal;
import jdk.internal.org.jline.utils.AttributedString;
/** Read lines from the console, with input editing.
*
* <h3>Thread safety</h3>
* The <code>LineReader</code> implementations are not thread safe,
* thus you should not attempt to use a single reader in several threads.
* Any attempt to call one of the <code>readLine</code> call while one is
* already executing in a different thread will immediately result in an
* <code>IllegalStateException</code> being thrown. Other calls may lead to
* unknown behaviors. There is one exception though: users are allowed to call
* {@link #printAbove(String)} or {@link #printAbove(AttributedString)} at
* any time to allow text to be printed above the current prompt.
*
* <h3>Prompt strings</h3>
* It is traditional for an interactive console-based program
* to print a short prompt string to signal that the user is expected
* to type a command. JLine supports 3 kinds of prompt string:
* <ul>
* <li> The normal prompt at the start (left) of the initial line of a command.
* <li> An optional right prompt at the right border of the initial line.
* <li> A start (left) prompt for continuation lines. I.e. the lines
* after the first line of a multi-line command.
* </ul>
* <p>
* All of these are specified with prompt templates,
* which are similar to {@code printf} format strings,
* using the character {@code '%'} to indicate special functionality.
* </p>
* The pattern may include ANSI escapes.
* It may include these template markers:
* <dl>
* <dt>{@code %N}</dt>
* <dd>A line number. This is the sum of {@code getLineNumber()}
* and a counter starting with 1 for the first continuation line.
* </dd>
* <dt>{@code %M}</dt>
* <dd>A short word explaining what is "missing". This is supplied from
* the {@link EOFError#getMissing()} method, if provided.
* Defaults to an empty string.
* </dd>
* <dt>{@code %}<var>n</var>{@code P}<var>c</var></dt>
* <dd>Insert padding at this possion, repeating the following
* character <var>c</var> as needed to bring the total prompt
* column width as specified by the digits <var>n</var>.
* </dd>
* <dt>{@code %P}<var>c</var></dt>
* <dd>As before, but use width from the initial prompt.
* </dd>
* <dt>{@code %%}</dt>
* <dd>A literal {@code '%'}.
* </dd>
* <dt><code>%{</code></dt><dt><code>%}</code></dt>
* <dd>Text between a <code>%{</code>...<code>%}</code> pair is printed as
* part of a prompt, but not interpreted by JLine
* (except that {@code '%'}-escapes are processed). The text is assumed
* to take zero columns (not move the cursor). If it changes the style,
* you're responsible for changing it back. Standard ANSI escape sequences
* do not need to be within a <code>%{</code>...<code>%}</code> pair
* (though can be) since JLine knows how to deal with them. However,
* these delimiters are needed for unusual non-standard escape sequences.
* </dd>
* </dl>
*/
public interface LineReader {
/**
* System property that can be set to avoid a warning being logged
* when using a Parser which does not return {@link CompletingParsedLine} objects.
*/
String PROP_SUPPORT_PARSEDLINE = "org.jline.reader.support.parsedline";
//
// Widget names
//
String CALLBACK_INIT = "callback-init";
String CALLBACK_FINISH = "callback-finish";
String CALLBACK_KEYMAP = "callback-keymap";
String ACCEPT_LINE = "accept-line";
String ARGUMENT_BASE = "argument-base";
String BACKWARD_CHAR = "backward-char";
String BACKWARD_DELETE_CHAR = "backward-delete-char";
String BACKWARD_DELETE_WORD = "backward-delete-word";
String BACKWARD_KILL_LINE = "backward-kill-line";
String BACKWARD_KILL_WORD = "backward-kill-word";
String BACKWARD_WORD = "backward-word";
String BEEP = "beep";
String BEGINNING_OF_BUFFER_OR_HISTORY = "beginning-of-buffer-or-history";
String BEGINNING_OF_HISTORY = "beginning-of-history";
String BEGINNING_OF_LINE = "beginning-of-line";
String BEGINNING_OF_LINE_HIST = "beginning-of-line-hist";
String CAPITALIZE_WORD = "capitalize-word";
String CHARACTER_SEARCH = "character-search";
String CHARACTER_SEARCH_BACKWARD = "character-search-backward";
String CLEAR = "clear";
String CLEAR_SCREEN = "clear-screen";
String COMPLETE_PREFIX = "complete-prefix";
String COMPLETE_WORD = "complete-word";
String COPY_PREV_WORD = "copy-prev-word";
String COPY_REGION_AS_KILL = "copy-region-as-kill";
String DELETE_CHAR = "delete-char";
String DELETE_CHAR_OR_LIST = "delete-char-or-list";
String DELETE_WORD = "delete-word";
String DIGIT_ARGUMENT = "digit-argument";
String DO_LOWERCASE_VERSION = "do-lowercase-version";
String DOWN_CASE_WORD = "down-case-word";
String DOWN_HISTORY = "down-history";
String DOWN_LINE = "down-line";
String DOWN_LINE_OR_HISTORY = "down-line-or-history";
String DOWN_LINE_OR_SEARCH = "down-line-or-search";
String EMACS_BACKWARD_WORD = "emacs-backward-word";
String EMACS_EDITING_MODE = "emacs-editing-mode";
String EMACS_FORWARD_WORD = "emacs-forward-word";
String END_OF_BUFFER_OR_HISTORY = "end-of-buffer-or-history";
String END_OF_HISTORY = "end-of-history";
String END_OF_LINE = "end-of-line";
String END_OF_LINE_HIST = "end-of-line-hist";
String EXCHANGE_POINT_AND_MARK = "exchange-point-and-mark";
String EXECUTE_NAMED_CMD = "execute-named-cmd";
String EXPAND_HISTORY = "expand-history";
String EXPAND_OR_COMPLETE = "expand-or-complete";
String EXPAND_OR_COMPLETE_PREFIX = "expand-or-complete-prefix";
String EXPAND_WORD = "expand-word";
String FRESH_LINE = "fresh-line";
String FORWARD_CHAR = "forward-char";
String FORWARD_WORD = "forward-word";
String HISTORY_BEGINNING_SEARCH_BACKWARD = "history-beginning-search-backward";
String HISTORY_BEGINNING_SEARCH_FORWARD = "history-beginning-search-forward";
String HISTORY_INCREMENTAL_PATTERN_SEARCH_BACKWARD = "history-incremental-pattern-search-backward";
String HISTORY_INCREMENTAL_PATTERN_SEARCH_FORWARD = "history-incremental-pattern-search-forward";
String HISTORY_INCREMENTAL_SEARCH_BACKWARD = "history-incremental-search-backward";
String HISTORY_INCREMENTAL_SEARCH_FORWARD = "history-incremental-search-forward";
String HISTORY_SEARCH_BACKWARD = "history-search-backward";
String HISTORY_SEARCH_FORWARD = "history-search-forward";
String INSERT_CLOSE_CURLY = "insert-close-curly";
String INSERT_CLOSE_PAREN = "insert-close-paren";
String INSERT_CLOSE_SQUARE = "insert-close-square";
String INFER_NEXT_HISTORY = "infer-next-history";
String INSERT_COMMENT = "insert-comment";
String INSERT_LAST_WORD = "insert-last-word";
String KILL_BUFFER = "kill-buffer";
String KILL_LINE = "kill-line";
String KILL_REGION = "kill-region";
String KILL_WHOLE_LINE = "kill-whole-line";
String KILL_WORD = "kill-word";
String LIST_CHOICES = "list-choices";
String LIST_EXPAND = "list-expand";
String MAGIC_SPACE = "magic-space";
String MENU_EXPAND_OR_COMPLETE = "menu-expand-or-complete";
String MENU_COMPLETE = "menu-complete";
String MENU_SELECT = "menu-select";
String NEG_ARGUMENT = "neg-argument";
String OVERWRITE_MODE = "overwrite-mode";
String PUT_REPLACE_SELECTION = "put-replace-selection";
String QUOTED_INSERT = "quoted-insert";
String READ_COMMAND = "read-command";
String RECURSIVE_EDIT = "recursive-edit";
String REDISPLAY = "redisplay";
String REDRAW_LINE = "redraw-line";
String REDO = "redo";
String REVERSE_MENU_COMPLETE = "reverse-menu-complete";
String SELF_INSERT = "self-insert";
String SELF_INSERT_UNMETA = "self-insert-unmeta";
String SEND_BREAK = "abort";
String SET_LOCAL_HISTORY = "set-local-history";
String SET_MARK_COMMAND = "set-mark-command";
String SPELL_WORD = "spell-word";
String SPLIT_UNDO = "split-undo";
String TRANSPOSE_CHARS = "transpose-chars";
String TRANSPOSE_WORDS = "transpose-words";
String UNDEFINED_KEY = "undefined-key";
String UNDO = "undo";
String UNIVERSAL_ARGUMENT = "universal-argument";
String UP_CASE_WORD = "up-case-word";
String UP_HISTORY = "up-history";
String UP_LINE = "up-line";
String UP_LINE_OR_HISTORY = "up-line-or-history";
String UP_LINE_OR_SEARCH = "up-line-or-search";
String VI_ADD_EOL = "vi-add-eol";
String VI_ADD_NEXT = "vi-add-next";
String VI_BACKWARD_BLANK_WORD = "vi-backward-blank-word";
String VI_BACKWARD_BLANK_WORD_END = "vi-backward-blank-word-end";
String VI_BACKWARD_CHAR = "vi-backward-char";
String VI_BACKWARD_DELETE_CHAR = "vi-backward-delete-char";
String VI_BACKWARD_KILL_WORD = "vi-backward-kill-word";
String VI_BACKWARD_WORD = "vi-backward-word";
String VI_BACKWARD_WORD_END = "vi-backward-word-end";
String VI_BEGINNING_OF_LINE = "vi-beginning-of-line";
String VI_CHANGE = "vi-change-to";
String VI_CHANGE_EOL = "vi-change-eol";
String VI_CHANGE_WHOLE_LINE = "vi-change-whole-line";
String VI_CMD_MODE = "vi-cmd-mode";
String VI_DELETE = "vi-delete";
String VI_DELETE_CHAR = "vi-delete-char";
String VI_DIGIT_OR_BEGINNING_OF_LINE = "vi-digit-or-beginning-of-line";
String VI_DOWN_LINE_OR_HISTORY = "vi-down-line-or-history";
String VI_END_OF_LINE = "vi-end-of-line";
String VI_FETCH_HISTORY = "vi-fetch-history";
String VI_FIND_NEXT_CHAR = "vi-find-next-char";
String VI_FIND_NEXT_CHAR_SKIP = "vi-find-next-char-skip";
String VI_FIND_PREV_CHAR = "vi-find-prev-char";
String VI_FIND_PREV_CHAR_SKIP = "vi-find-prev-char-skip";
String VI_FIRST_NON_BLANK = "vi-first-non-blank";
String VI_FORWARD_BLANK_WORD = "vi-forward-blank-word";
String VI_FORWARD_BLANK_WORD_END = "vi-forward-blank-word-end";
String VI_FORWARD_CHAR = "vi-forward-char";
String VI_FORWARD_WORD = "vi-forward-word";
String VI_FORWARD_WORD_END = "vi-forward-word-end";
String VI_GOTO_COLUMN = "vi-goto-column";
String VI_HISTORY_SEARCH_BACKWARD = "vi-history-search-backward";
String VI_HISTORY_SEARCH_FORWARD = "vi-history-search-forward";
String VI_INSERT = "vi-insert";
String VI_INSERT_BOL = "vi-insert-bol";
String VI_INSERT_COMMENT = "vi-insert-comment";
String VI_JOIN = "vi-join";
String VI_KILL_EOL = "vi-kill-eol";
String VI_KILL_LINE = "vi-kill-line";
String VI_MATCH_BRACKET = "vi-match-bracket";
String VI_OPEN_LINE_ABOVE = "vi-open-line-above";
String VI_OPEN_LINE_BELOW = "vi-open-line-below";
String VI_OPER_SWAP_CASE = "vi-oper-swap-case";
String VI_PUT_AFTER = "vi-put-after";
String VI_PUT_BEFORE = "vi-put-before";
String VI_QUOTED_INSERT = "vi-quoted-insert";
String VI_REPEAT_CHANGE = "vi-repeat-change";
String VI_REPEAT_FIND = "vi-repeat-find";
String VI_REPEAT_SEARCH = "vi-repeat-search";
String VI_REPLACE = "vi-replace";
String VI_REPLACE_CHARS = "vi-replace-chars";
String VI_REV_REPEAT_FIND = "vi-rev-repeat-find";
String VI_REV_REPEAT_SEARCH = "vi-rev-repeat-search";
String VI_SET_BUFFER = "vi-set-buffer";
String VI_SUBSTITUTE = "vi-substitute";
String VI_SWAP_CASE = "vi-swap-case";
String VI_UNDO_CHANGE = "vi-undo-change";
String VI_UP_LINE_OR_HISTORY = "vi-up-line-or-history";
String VI_YANK = "vi-yank";
String VI_YANK_EOL = "vi-yank-eol";
String VI_YANK_WHOLE_LINE = "vi-yank-whole-line";
String VISUAL_LINE_MODE = "visual-line-mode";
String VISUAL_MODE = "visual-mode";
String WHAT_CURSOR_POSITION = "what-cursor-position";
String YANK = "yank";
String YANK_POP = "yank-pop";
String MOUSE = "mouse";
String FOCUS_IN = "terminal-focus-in";
String FOCUS_OUT = "terminal-focus-out";
String BEGIN_PASTE = "begin-paste";
//
// KeyMap names
//
String VICMD = "vicmd";
String VIINS = "viins";
String VIOPP = "viopp";
String VISUAL = "visual";
String MAIN = "main";
String EMACS = "emacs";
String SAFE = ".safe";
String MENU = "menu";
//
// Variable names
//
String BIND_TTY_SPECIAL_CHARS = "bind-tty-special-chars";
String COMMENT_BEGIN = "comment-begin";
String BELL_STYLE = "bell-style";
String PREFER_VISIBLE_BELL = "prefer-visible-bell";
String LIST_MAX = "list-max";
String DISABLE_HISTORY = "disable-history";
String DISABLE_COMPLETION = "disable-completion";
String EDITING_MODE = "editing-mode";
String KEYMAP = "keymap";
String BLINK_MATCHING_PAREN = "blink-matching-paren";
String WORDCHARS = "WORDCHARS";
String REMOVE_SUFFIX_CHARS = "REMOVE_SUFFIX_CHARS";
String SEARCH_TERMINATORS = "search-terminators";
String ERRORS = "errors";
/** Property for the "others" group name */
String OTHERS_GROUP_NAME = "OTHERS_GROUP_NAME";
/** Property for the "original" group name */
String ORIGINAL_GROUP_NAME = "ORIGINAL_GROUP_NAME";
/** Completion style for displaying groups name */
String COMPLETION_STYLE_GROUP = "COMPLETION_STYLE_GROUP";
/** Completion style for displaying the current selected item */
String COMPLETION_STYLE_SELECTION = "COMPLETION_STYLE_SELECTION";
/** Completion style for displaying the candidate description */
String COMPLETION_STYLE_DESCRIPTION = "COMPLETION_STYLE_DESCRIPTION";
/** Completion style for displaying the matching part of candidates */
String COMPLETION_STYLE_STARTING = "COMPLETION_STYLE_STARTING";
/**
* Set the template for prompts for secondary (continuation) lines.
* This is a prompt template as described in the class header.
*/
String SECONDARY_PROMPT_PATTERN = "secondary-prompt-pattern";
/**
* When in multiline edit mode, this variable can be used
* to offset the line number displayed.
*/
String LINE_OFFSET = "line-offset";
/**
* Timeout for ambiguous key sequences.
* If the key sequence is ambiguous, i.e. there is a matching
* sequence but the sequence is also a prefix for other bindings,
* the next key press will be waited for a specified amount of
* time. If the timeout elapses, the matched sequence will be
* used.
*/
String AMBIGUOUS_BINDING = "ambiguous-binding";
/**
* Columns separated list of patterns that will not be saved in history.
*/
String HISTORY_IGNORE = "history-ignore";
/**
* File system history path.
*/
String HISTORY_FILE = "history-file";
/**
* Number of history items to keep in memory.
*/
String HISTORY_SIZE = "history-size";
/**
* Number of history items to keep in the history file.
*/
String HISTORY_FILE_SIZE = "history-file-size";
Map<String, KeyMap<Binding>> defaultKeyMaps();
enum Option {
COMPLETE_IN_WORD,
DISABLE_EVENT_EXPANSION,
HISTORY_VERIFY,
HISTORY_IGNORE_SPACE(true),
HISTORY_IGNORE_DUPS(true),
HISTORY_REDUCE_BLANKS(true),
HISTORY_BEEP(true),
HISTORY_INCREMENTAL(true),
HISTORY_TIMESTAMPED(true),
/** when displaying candidates, group them by {@link Candidate#group()} */
AUTO_GROUP(true),
AUTO_MENU(true),
AUTO_LIST(true),
RECOGNIZE_EXACT,
/** display group name before each group (else display all group names first) */
GROUP(true),
/** if completion is case insensitive or not */
CASE_INSENSITIVE,
LIST_AMBIGUOUS,
LIST_PACKED,
LIST_ROWS_FIRST,
GLOB_COMPLETE,
MENU_COMPLETE,
/** if set and not at start of line before prompt, move to new line */
AUTO_FRESH_LINE,
/** After writing into the rightmost column, do we immediately
* move to the next line (the default)? Or do we wait until
* the next character.
* If set, an input line that is exactly {@code N*columns} wide will
* use {@code N} screen lines; otherwise it will use {@code N+1} lines.
* When the cursor position is the right margin of the last line
* (i.e. after {@code N*columns} normal characters), if this option
* it set, the cursor will be remain on the last line (line {@code N-1},
* zero-origin); if unset the cursor will be on the empty next line.
* Regardless, for all except the last screen line if the cursor is at
* the right margin, it will be shown at the start of the next line.
*/
DELAY_LINE_WRAP,
AUTO_PARAM_SLASH(true),
AUTO_REMOVE_SLASH(true),
/** When hitting the <code>&lt;tab&gt;</code> key at the beginning of the line, insert a tabulation
* instead of completing. This is mainly useful when {@link #BRACKETED_PASTE} is
* disabled, so that copy/paste of indented text does not trigger completion.
*/
INSERT_TAB,
MOUSE,
DISABLE_HIGHLIGHTER,
BRACKETED_PASTE(true),
/**
* Instead of printing a new line when the line is read, the entire line
* (including the prompt) will be erased, thereby leaving the screen as it
* was before the readLine call.
*/
ERASE_LINE_ON_FINISH,
/** if history search is fully case insensitive */
CASE_INSENSITIVE_SEARCH,
;
private final boolean def;
Option() {
this(false);
}
Option(boolean def) {
this.def = def;
}
public boolean isDef() {
return def;
}
}
enum RegionType {
NONE,
CHAR,
LINE,
PASTE
}
/**
* Read the next line and return the contents of the buffer.
*
* Equivalent to <code>readLine(null, null, null)</code>.
*
* @return the line read
* @throws UserInterruptException If the call was interrupted by the user.
* @throws EndOfFileException If the end of the input stream was reached.
*/
String readLine() throws UserInterruptException, EndOfFileException;
/**
* Read the next line with the specified character mask. If null, then
* characters will be echoed. If 0, then no characters will be echoed.
*
* Equivalent to <code>readLine(null, mask, null)</code>
*
* @param mask The mask character, <code>null</code> or <code>0</code>.
* @return A line that is read from the terminal, can never be null.
* @throws UserInterruptException If the call was interrupted by the user.
* @throws EndOfFileException If the end of the input stream was reached.
*/
String readLine(Character mask) throws UserInterruptException, EndOfFileException;
/**
* Read the next line with the specified prompt.
* If null, then the default prompt will be used.
*
* Equivalent to <code>readLine(prompt, null, null)</code>
*
* @param prompt The prompt to issue to the terminal, may be null.
* @return A line that is read from the terminal, can never be null.
* @throws UserInterruptException If the call was interrupted by the user.
* @throws EndOfFileException If the end of the input stream was reached.
*/
String readLine(String prompt) throws UserInterruptException, EndOfFileException;
/**
* Read a line from the <i>in</i> {@link InputStream}, and return the line
* (without any trailing newlines).
*
* Equivalent to <code>readLine(prompt, mask, null)</code>
*
* @param prompt The prompt to issue to the terminal, may be null.
* @param mask The mask character, <code>null</code> or <code>0</code>.
* @return A line that is read from the terminal, can never be null.
* @throws UserInterruptException If the call was interrupted by the user.
* @throws EndOfFileException If the end of the input stream was reached.
*/
String readLine(String prompt, Character mask) throws UserInterruptException, EndOfFileException;
/**
* Read a line from the <i>in</i> {@link InputStream}, and return the line
* (without any trailing newlines).
*
* Equivalent to <code>readLine(prompt, null, mask, buffer)</code>
*
* @param prompt The prompt to issue to the terminal, may be null.
* This is a template, with optional {@code '%'} escapes, as
* described in the class header.
* @param mask The character mask, may be null.
* @param buffer The default value presented to the user to edit, may be null.
* @return A line that is read from the terminal, can never be null.
* @throws UserInterruptException If the call was interrupted by the user.
* @throws EndOfFileException If the end of the input stream was reached.
*/
String readLine(String prompt, Character mask, String buffer) throws UserInterruptException, EndOfFileException;
/**
* Read a line from the <i>in</i> {@link InputStream}, and return the line
* (without any trailing newlines).
*
* @param prompt The prompt to issue to the terminal, may be null.
* This is a template, with optional {@code '%'} escapes, as
* described in the class header.
* @param rightPrompt The right prompt
* This is a template, with optional {@code '%'} escapes, as
* described in the class header.
* @param mask The character mask, may be null.
* @param buffer The default value presented to the user to edit, may be null.
* @return A line that is read from the terminal, can never be null.
*
* @throws UserInterruptException if readLine was interrupted (using Ctrl-C for example)
* @throws EndOfFileException if an EOF has been found (using Ctrl-D for example)
* @throws java.io.IOError in case of other i/o errors
* @throws UserInterruptException If the call was interrupted by the user.
* @throws EndOfFileException If the end of the input stream was reached.
*/
String readLine(String prompt, String rightPrompt, Character mask, String buffer) throws UserInterruptException, EndOfFileException;
/**
* Read a line from the <i>in</i> {@link InputStream}, and return the line
* (without any trailing newlines).
*
* @param prompt The prompt to issue to the terminal, may be null.
* This is a template, with optional {@code '%'} escapes, as
* described in the class header.
* @param rightPrompt The right prompt
* This is a template, with optional {@code '%'} escapes, as
* described in the class header.
* @param maskingCallback The {@link MaskingCallback} to use when displaying lines and adding them to the line {@link History}
* @param buffer The default value presented to the user to edit, may be null.
* @return A line that is read from the terminal, can never be null.
*
* @throws UserInterruptException if readLine was interrupted (using Ctrl-C for example)
* @throws EndOfFileException if an EOF has been found (using Ctrl-D for example)
* @throws java.io.IOError in case of other i/o errors
* @throws UserInterruptException If the call was interrupted by the user.
* @throws EndOfFileException If the end of the input stream was reached.
*/
String readLine(String prompt, String rightPrompt, MaskingCallback maskingCallback, String buffer) throws UserInterruptException, EndOfFileException;
/**
* Prints a line above the prompt and redraw everything.
* If the LineReader is not actually reading a line, the string will simply be printed to the terminal.
*
* @see #printAbove(AttributedString)
* @param str the string to print
*/
void printAbove(String str);
/**
* Prints a string before the prompt and redraw everything.
* If the LineReader is not actually reading a line, the string will simply be printed to the terminal.
*
* @see #printAbove(String)
* @param str the string to print
*/
void printAbove(AttributedString str);
/**
* Check if a thread is currently in a <code>readLine()</code> call.
*
* @return <code>true</code> if there is an ongoing <code>readLine()</code> call.
*/
boolean isReading();
//
// Chainable setters
//
LineReader variable(String name, Object value);
LineReader option(Option option, boolean value);
void callWidget(String name);
Map<String, Object> getVariables();
Object getVariable(String name);
void setVariable(String name, Object value);
boolean isSet(Option option);
void setOpt(Option option);
void unsetOpt(Option option);
Terminal getTerminal();
Map<String, Widget> getWidgets();
Map<String, Widget> getBuiltinWidgets();
Buffer getBuffer();
String getAppName();
/**
* Push back a key sequence that will be later consumed by the line reader.
* This method can be used after reading the cursor position using
* {@link Terminal#getCursorPosition(IntConsumer)}.
*
* @param macro the key sequence to push back
* @see Terminal#getCursorPosition(IntConsumer)
* @see #readMouseEvent()
*/
void runMacro(String macro);
/**
* Read a mouse event when the {@link org.jline.utils.InfoCmp.Capability#key_mouse} sequence
* has just been read on the input stream.
* Compared to {@link Terminal#readMouseEvent()}, this method takes into account keys
* that have been pushed back using {@link #runMacro(String)}.
*
* @return the mouse event
* @see #runMacro(String)
* @see Terminal#getCursorPosition(IntConsumer)
*/
MouseEvent readMouseEvent();
History getHistory();
Parser getParser();
Highlighter getHighlighter();
Expander getExpander();
Map<String, KeyMap<Binding>> getKeyMaps();
String getKeyMap();
boolean setKeyMap(String name);
KeyMap<Binding> getKeys();
ParsedLine getParsedLine();
String getSearchTerm();
RegionType getRegionActive();
int getRegionMark();
}

View file

@ -0,0 +1,142 @@
/*
* Copyright (c) 2002-2018, 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.org.jline.reader;
import java.io.IOError;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import jdk.internal.org.jline.reader.impl.LineReaderImpl;
import jdk.internal.org.jline.reader.impl.history.DefaultHistory;
import jdk.internal.org.jline.terminal.Terminal;
import jdk.internal.org.jline.terminal.TerminalBuilder;
import jdk.internal.org.jline.utils.Log;
public final class LineReaderBuilder {
public static LineReaderBuilder builder() {
return new LineReaderBuilder();
}
Terminal terminal;
String appName;
Map<String, Object> variables = new HashMap<>();
Map<LineReader.Option, Boolean> options = new HashMap<>();
History history;
Completer completer;
History memoryHistory;
Highlighter highlighter;
Parser parser;
Expander expander;
private LineReaderBuilder() {
}
public LineReaderBuilder terminal(Terminal terminal) {
this.terminal = terminal;
return this;
}
public LineReaderBuilder appName(String appName) {
this.appName = appName;
return this;
}
public LineReaderBuilder variables(Map<String, Object> variables) {
Map<String, Object> old = this.variables;
this.variables = Objects.requireNonNull(variables);
this.variables.putAll(old);
return this;
}
public LineReaderBuilder variable(String name, Object value) {
this.variables.put(name, value);
return this;
}
public LineReaderBuilder option(LineReader.Option option, boolean value) {
this.options.put(option, value);
return this;
}
public LineReaderBuilder history(History history) {
this.history = history;
return this;
}
public LineReaderBuilder completer(Completer completer) {
this.completer = completer;
return this;
}
public LineReaderBuilder highlighter(Highlighter highlighter) {
this.highlighter = highlighter;
return this;
}
public LineReaderBuilder parser(Parser parser) {
if (parser != null) {
try {
if (!Boolean.parseBoolean(LineReader.PROP_SUPPORT_PARSEDLINE)
&& !(parser.parse("", 0) instanceof CompletingParsedLine)) {
Log.warn("The Parser of class " + parser.getClass().getName() + " does not support the CompletingParsedLine interface. " +
"Completion with escaped or quoted words won't work correctly.");
}
} catch (Throwable t) {
// Ignore
}
}
this.parser = parser;
return this;
}
public LineReaderBuilder expander(Expander expander) {
this.expander = expander;
return this;
}
public LineReader build() {
Terminal terminal = this.terminal;
if (terminal == null) {
try {
terminal = TerminalBuilder.terminal();
} catch (IOException e) {
throw new IOError(e);
}
}
LineReaderImpl reader = new LineReaderImpl(terminal, appName, variables);
if (history != null) {
reader.setHistory(history);
} else {
if (memoryHistory == null) {
memoryHistory = new DefaultHistory();
}
reader.setHistory(memoryHistory);
}
if (completer != null) {
reader.setCompleter(completer);
}
if (highlighter != null) {
reader.setHighlighter(highlighter);
}
if (parser != null) {
reader.setParser(parser);
}
if (expander != null) {
reader.setExpander(expander);
}
for (Map.Entry<LineReader.Option, Boolean> e : options.entrySet()) {
reader.option(e.getKey(), e.getValue());
}
return reader;
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2002-2016, 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.org.jline.reader;
public class Macro implements Binding {
private final String sequence;
public Macro(String sequence) {
this.sequence = sequence;
}
public String getSequence() {
return sequence;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Macro macro = (Macro) o;
return sequence.equals(macro.sequence);
}
@Override
public int hashCode() {
return sequence.hashCode();
}
@Override
public String toString() {
return "Macro[" +
sequence + ']';
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2002-2018, 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.org.jline.reader;
/**
* Callback used to mask parts of the line
*/
public interface MaskingCallback {
/**
* Transforms the line before it is displayed so that
* some parts can be hidden.
*
* @param line the current line being edited
* @return the modified line to display
*/
String display(String line);
/**
* Transforms the line before storing in the history.
* If the return value is empty or null, it will not be saved
* in the history.
*
* @param line the line to be added to history
* @return the modified line
*/
String history(String line);
}

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2002-2018, 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.org.jline.reader;
import java.util.List;
/**
* <code>ParsedLine</code> objects are returned by the {@link Parser}
* during completion or when accepting the line.
*
* The instances should implement the {@link CompletingParsedLine}
* interface so that escape chars and quotes can be correctly handled.
*
* @see Parser
* @see CompletingParsedLine
*/
public interface ParsedLine {
/**
* The current word being completed.
* If the cursor is after the last word, an empty string is returned.
*
* @return the word being completed or an empty string
*/
String word();
/**
* The cursor position within the current word.
*
* @return the cursor position within the current word
*/
int wordCursor();
/**
* The index of the current word in the list of words.
*
* @return the index of the current word in the list of words
*/
int wordIndex();
/**
* The list of words.
*
* @return the list of words
*/
List<String> words();
/**
* The unparsed line.
*
* @return the unparsed line
*/
String line();
/**
* The cursor position within the line.
*
* @return the cursor position within the line
*/
int cursor();
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2002-2016, 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.org.jline.reader;
public interface Parser {
ParsedLine parse(String line, int cursor, ParseContext context) throws SyntaxError;
default ParsedLine parse(String line, int cursor) throws SyntaxError {
return parse(line, cursor, ParseContext.UNSPECIFIED);
}
enum ParseContext {
UNSPECIFIED,
/** Try a real "final" parse.
* May throw EOFError in which case we have incomplete input.
*/
ACCEPT_LINE,
/** Parse to find completions (typically after a Tab).
* We should tolerate and ignore errors.
*/
COMPLETE,
/** Called when we need to update the secondary prompts.
* Specifically, when we need the 'missing' field from EOFError,
* which is used by a "%M" in a prompt pattern.
*/
SECONDARY_PROMPT
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2002-2016, 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.org.jline.reader;
/**
* A reference to a {@link Widget}.
*/
public class Reference implements Binding {
private final String name;
public Reference(String name) {
this.name = name;
}
public String name() {
return name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Reference func = (Reference) o;
return name.equals(func.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public String toString() {
return "Reference[" +
name + ']';
}
}

View file

@ -0,0 +1,41 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package jdk.internal.org.jline.reader;
public class SyntaxError extends RuntimeException {
private static final long serialVersionUID = 1L;
private final int line;
private final int column;
public SyntaxError(int line, int column, String message) {
super(message);
this.line = line;
this.column = column;
}
public int column() {
return column;
}
public int line() {
return line;
}
}

View file

@ -6,10 +6,10 @@
* *
* http://www.opensource.org/licenses/bsd-license.php * http://www.opensource.org/licenses/bsd-license.php
*/ */
package jdk.internal.jline.console; package jdk.internal.org.jline.reader;
/** /**
* This exception is thrown by {@link ConsoleReader#readLine} when * This exception is thrown by {@link LineReader#readLine} when
* user interrupt handling is enabled and the user types the * user interrupt handling is enabled and the user types the
* interrupt character (ctrl-C). The partially entered line is * interrupt character (ctrl-C). The partially entered line is
* available via the {@link #getPartialLine()} method. * available via the {@link #getPartialLine()} method.

View file

@ -6,9 +6,14 @@
* *
* http://www.opensource.org/licenses/bsd-license.php * http://www.opensource.org/licenses/bsd-license.php
*/ */
package jdk.internal.org.jline.reader;
/** /**
* Console completer support.
* *
* @since 2.3
*/ */
package jdk.internal.jline.console.completer; @FunctionalInterface
public interface Widget extends Binding {
boolean apply();
}

View file

@ -0,0 +1,372 @@
/*
* Copyright (c) 2002-2017, 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.org.jline.reader.impl;
import java.util.Objects;
import jdk.internal.org.jline.reader.Buffer;
/**
* 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 BufferImpl implements Buffer
{
private int cursor = 0;
private int cursorCol = -1;
private int[] buffer;
private int g0;
private int g1;
public BufferImpl() {
this(64);
}
public BufferImpl(int size) {
buffer = new int[size];
g0 = 0;
g1 = buffer.length;
}
private BufferImpl(BufferImpl buffer) {
this.cursor = buffer.cursor;
this.cursorCol = buffer.cursorCol;
this.buffer = buffer.buffer.clone();
this.g0 = buffer.g0;
this.g1 = buffer.g1;
}
public BufferImpl copy () {
return new BufferImpl(this);
}
public int cursor() {
return cursor;
}
public int length() {
return buffer.length - (g1 - g0);
}
public boolean currChar(int ch) {
if (cursor == length()) {
return false;
} else {
buffer[adjust(cursor)] = ch;
return true;
}
}
public int currChar() {
if (cursor == length()) {
return 0;
} else {
return atChar(cursor);
}
}
public int prevChar() {
if (cursor <= 0) {
return 0;
}
return atChar(cursor - 1);
}
public int nextChar() {
if (cursor >= length() - 1) {
return 0;
}
return atChar(cursor + 1);
}
public int atChar(int i) {
if (i < 0 || i >= length()) {
return 0;
}
return buffer[adjust(i)];
}
private int adjust(int i) {
return (i >= g0) ? i + g1 - g0 : i;
}
/**
* Write the specific character into the buffer, setting the cursor position
* ahead one.
*
* @param c the character to insert
*/
public void write(int c) {
write(new int[] { c });
}
/**
* 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 {@code overTyping}.
*
* @param c the character to insert
*/
public void write(int c, boolean overTyping) {
if (overTyping) {
delete(1);
}
write(new int[] { c });
}
/**
* Insert the specified chars into the buffer, setting the cursor to the end of the insertion point.
*/
public void write(CharSequence str) {
Objects.requireNonNull(str);
write(str.codePoints().toArray());
}
public void write(CharSequence str, boolean overTyping) {
Objects.requireNonNull(str);
int[] ucps = str.codePoints().toArray();
if (overTyping) {
delete(ucps.length);
}
write(ucps);
}
private void write(int[] ucps) {
moveGapToCursor();
int len = length() + ucps.length;
int sz = buffer.length;
if (sz < len) {
while (sz < len) {
sz *= 2;
}
int[] nb = new int[sz];
System.arraycopy(buffer, 0, nb, 0, g0);
System.arraycopy(buffer, g1, nb, g1 + sz - buffer.length, buffer.length - g1);
g1 += sz - buffer.length;
buffer = nb;
}
System.arraycopy(ucps, 0, buffer, cursor, ucps.length);
g0 += ucps.length;
cursor += ucps.length;
cursorCol = -1;
}
public boolean clear() {
if (length() == 0) {
return false;
}
g0 = 0;
g1 = buffer.length;
cursor = 0;
cursorCol = -1;
return true;
}
public String substring(int start) {
return substring(start, length());
}
public String substring(int start, int end) {
if (start >= end || start < 0 || end > length()) {
return "";
}
if (end <= g0) {
return new String(buffer, start, end - start);
} else if (start > g0) {
return new String(buffer, g1 - g0 + start, end - start);
} else {
int[] b = buffer.clone();
System.arraycopy(b, g1, b, g0, b.length - g1);
return new String(b, start, end - start);
}
}
public String upToCursor() {
return substring(0, cursor);
}
/**
* Move the cursor position to the specified absolute index.
*/
public boolean cursor(int position) {
if (position == cursor) {
return true;
}
return move(position - cursor) != 0;
}
/**
* Move the cursor <i>where</i> characters.
*
* @param num If less than 0, move abs(<i>where</i>) to the left, otherwise move <i>where</i> to the right.
* @return The number of spaces we moved
*/
public int move(final int num) {
int where = num;
if ((cursor == 0) && (where <= 0)) {
return 0;
}
if ((cursor == length()) && (where >= 0)) {
return 0;
}
if ((cursor + where) < 0) {
where = -cursor;
}
else if ((cursor + where) > length()) {
where = length() - cursor;
}
cursor += where;
cursorCol = -1;
return where;
}
public boolean up() {
int col = getCursorCol();
int pnl = cursor - 1;
while (pnl >= 0 && atChar(pnl) != '\n') {
pnl--;
}
if (pnl < 0) {
return false;
}
int ppnl = pnl - 1;
while (ppnl >= 0 && atChar(ppnl) != '\n') {
ppnl--;
}
cursor = Math.min(ppnl + col + 1, pnl);
return true;
}
public boolean down() {
int col = getCursorCol();
int nnl = cursor;
while (nnl < length() && atChar(nnl) != '\n') {
nnl++;
}
if (nnl >= length()) {
return false;
}
int nnnl = nnl + 1;
while (nnnl < length() && atChar(nnnl) != '\n') {
nnnl++;
}
cursor = Math.min(nnl + col + 1, nnnl);
return true;
}
public boolean moveXY(int dx, int dy) {
int col = 0;
while (prevChar() != '\n' && move(-1) == -1) {
col++;
}
cursorCol = 0;
while (dy < 0) {
up();
dy++;
}
while (dy > 0) {
down();
dy--;
}
col = Math.max(col + dx, 0);
for (int i = 0; i < col; i++) {
if (move(1) != 1 || currChar() == '\n') {
break;
}
}
cursorCol = col;
return true;
}
private int getCursorCol() {
if (cursorCol < 0) {
cursorCol = 0;
int pnl = cursor - 1;
while (pnl >= 0 && atChar(pnl) != '\n') {
pnl--;
}
cursorCol = cursor - pnl - 1;
}
return cursorCol;
}
/**
* Issue <em>num</em> backspaces.
*
* @return the number of characters backed up
*/
public int backspace(final int num) {
int count = Math.max(Math.min(cursor, num), 0);
moveGapToCursor();
cursor -= count;
g0 -= count;
cursorCol = -1;
return count;
}
/**
* Issue a backspace.
*
* @return true if successful
*/
public boolean backspace() {
return backspace(1) == 1;
}
public int delete(int num) {
int count = Math.max(Math.min(length() - cursor, num), 0);
moveGapToCursor();
g1 += count;
cursorCol = -1;
return count;
}
public boolean delete() {
return delete(1) == 1;
}
@Override
public String toString() {
return substring(0, length());
}
public void copyFrom(Buffer buf) {
if (!(buf instanceof BufferImpl)) {
throw new IllegalStateException();
}
BufferImpl that = (BufferImpl) buf;
this.g0 = that.g0;
this.g1 = that.g1;
this.buffer = that.buffer.clone();
this.cursor = that.cursor;
this.cursorCol = that.cursorCol;
}
private void moveGapToCursor() {
if (cursor < g0) {
int l = g0 - cursor;
System.arraycopy(buffer, cursor, buffer, g1 - l, l);
g0 -= l;
g1 -= l;
} else if (cursor > g0) {
int l = cursor - g0;
System.arraycopy(buffer, g1, buffer, g0, l);
g0 += l;
g1 += l;
}
}
}

View file

@ -0,0 +1,207 @@
/*
* Copyright (c) 2002-2016, 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.org.jline.reader.impl;
import java.util.ListIterator;
import jdk.internal.org.jline.reader.Expander;
import jdk.internal.org.jline.reader.History;
import jdk.internal.org.jline.reader.History.Entry;
public class DefaultExpander implements Expander {
/**
* Expand event designator such as !!, !#, !3, etc...
* See http://www.gnu.org/software/bash/manual/html_node/Event-Designators.html
*/
@SuppressWarnings("fallthrough")
@Override
public String expandHistory(History history, String line) {
boolean inQuote = false;
StringBuilder sb = new StringBuilder();
boolean escaped = false;
int unicode = 0;
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);
if (unicode > 0) {
escaped = (--unicode >= 0);
sb.append(c);
}
else if (escaped) {
if (c == 'u') {
unicode = 4;
} else {
escaped = false;
}
sb.append(c);
}
else if (c == '\'') {
inQuote = !inQuote;
sb.append(c);
}
else if (inQuote) {
sb.append(c);
}
else {
switch (c) {
case '\\':
// any '\!' should be considered an expansion escape, so skip expansion and strip the escape character
// a leading '\^' should be considered an expansion escape, so skip expansion and strip the escape character
// otherwise, add the escape
escaped = true;
sb.append(c);
break;
case '!':
if (i + 1 < line.length()) {
c = line.charAt(++i);
boolean neg = false;
String rep = null;
int i1, idx;
switch (c) {
case '!':
if (history.size() == 0) {
throw new IllegalArgumentException("!!: event not found");
}
rep = history.get(history.index() - 1);
break;
case '#':
sb.append(sb.toString());
break;
case '?':
i1 = line.indexOf('?', i + 1);
if (i1 < 0) {
i1 = line.length();
}
String sc = line.substring(i + 1, i1);
i = i1;
idx = searchBackwards(history, sc, history.index(), false);
if (idx < 0) {
throw new IllegalArgumentException("!?" + sc + ": event not found");
} else {
rep = history.get(idx);
}
break;
case '$':
if (history.size() == 0) {
throw new IllegalArgumentException("!$: event not found");
}
String previous = history.get(history.index() - 1).trim();
int lastSpace = previous.lastIndexOf(' ');
if (lastSpace != -1) {
rep = previous.substring(lastSpace + 1);
} else {
rep = previous;
}
break;
case ' ':
case '\t':
sb.append('!');
sb.append(c);
break;
case '-':
neg = true;
i++;
// fall through
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
i1 = i;
for (; i < line.length(); i++) {
c = line.charAt(i);
if (c < '0' || c > '9') {
break;
}
}
try {
idx = Integer.parseInt(line.substring(i1, i));
} catch (NumberFormatException e) {
throw new IllegalArgumentException((neg ? "!-" : "!") + line.substring(i1, i) + ": event not found");
}
if (neg && idx > 0 && idx <= history.size()) {
rep = history.get(history.index() - idx);
} else if (!neg && idx > history.index() - history.size() && idx <= history.index()) {
rep = history.get(idx - 1);
} else {
throw new IllegalArgumentException((neg ? "!-" : "!") + line.substring(i1, i) + ": event not found");
}
break;
default:
String ss = line.substring(i);
i = line.length();
idx = searchBackwards(history, ss, history.index(), true);
if (idx < 0) {
throw new IllegalArgumentException("!" + ss + ": event not found");
} else {
rep = history.get(idx);
}
break;
}
if (rep != null) {
sb.append(rep);
}
} else {
sb.append(c);
}
break;
case '^':
if (i == 0) {
int i1 = line.indexOf('^', i + 1);
int i2 = line.indexOf('^', i1 + 1);
if (i2 < 0) {
i2 = line.length();
}
if (i1 > 0 && i2 > 0) {
String s1 = line.substring(i + 1, i1);
String s2 = line.substring(i1 + 1, i2);
String s = history.get(history.index() - 1).replace(s1, s2);
sb.append(s);
i = i2 + 1;
break;
}
}
sb.append(c);
break;
default:
sb.append(c);
break;
}
}
}
return sb.toString();
}
@Override
public String expandVar(String word) {
return word;
}
protected int searchBackwards(History history, String searchTerm, int startIndex, boolean startsWith) {
ListIterator<Entry> it = history.iterator(startIndex);
while (it.hasPrevious()) {
History.Entry e = it.previous();
if (startsWith) {
if (e.line().startsWith(searchTerm)) {
return e.index();
}
} else {
if (e.line().contains(searchTerm)) {
return e.index();
}
}
}
return -1;
}
}

View file

@ -0,0 +1,84 @@
/*
* Copyright (c) 2002-2016, 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.org.jline.reader.impl;
import jdk.internal.org.jline.reader.LineReader;
import jdk.internal.org.jline.reader.LineReader.RegionType;
import jdk.internal.org.jline.reader.Highlighter;
import jdk.internal.org.jline.utils.AttributedString;
import jdk.internal.org.jline.utils.AttributedStringBuilder;
import jdk.internal.org.jline.utils.AttributedStyle;
import jdk.internal.org.jline.utils.WCWidth;
public class DefaultHighlighter implements Highlighter {
@Override
public AttributedString highlight(LineReader reader, String buffer) {
int underlineStart = -1;
int underlineEnd = -1;
int negativeStart = -1;
int negativeEnd = -1;
String search = reader.getSearchTerm();
if (search != null && search.length() > 0) {
underlineStart = buffer.indexOf(search);
if (underlineStart >= 0) {
underlineEnd = underlineStart + search.length() - 1;
}
}
if (reader.getRegionActive() != RegionType.NONE) {
negativeStart = reader.getRegionMark();
negativeEnd = reader.getBuffer().cursor();
if (negativeStart > negativeEnd) {
int x = negativeEnd;
negativeEnd = negativeStart;
negativeStart = x;
}
if (reader.getRegionActive() == RegionType.LINE) {
while (negativeStart > 0 && reader.getBuffer().atChar(negativeStart - 1) != '\n') {
negativeStart--;
}
while (negativeEnd < reader.getBuffer().length() - 1 && reader.getBuffer().atChar(negativeEnd + 1) != '\n') {
negativeEnd++;
}
}
}
AttributedStringBuilder sb = new AttributedStringBuilder();
for (int i = 0; i < buffer.length(); i++) {
if (i == underlineStart) {
sb.style(AttributedStyle::underline);
}
if (i == negativeStart) {
sb.style(AttributedStyle::inverse);
}
char c = buffer.charAt(i);
if (c == '\t' || c == '\n') {
sb.append(c);
} else if (c < 32) {
sb.style(AttributedStyle::inverseNeg)
.append('^')
.append((char) (c + '@'))
.style(AttributedStyle::inverseNeg);
} else {
int w = WCWidth.wcwidth(c);
if (w > 0) {
sb.append(c);
}
}
if (i == underlineEnd) {
sb.style(AttributedStyle::underlineOff);
}
if (i == negativeEnd) {
sb.style(AttributedStyle::inverseOff);
}
}
return sb.toAttributedString();
}
}

View file

@ -0,0 +1,406 @@
/*
* Copyright (c) 2002-2018, 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.org.jline.reader.impl;
import java.util.*;
import java.util.function.Predicate;
import jdk.internal.org.jline.reader.CompletingParsedLine;
import jdk.internal.org.jline.reader.EOFError;
import jdk.internal.org.jline.reader.ParsedLine;
import jdk.internal.org.jline.reader.Parser;
public class DefaultParser implements Parser {
private char[] quoteChars = {'\'', '"'};
private char[] escapeChars = {'\\'};
private boolean eofOnUnclosedQuote;
private boolean eofOnEscapedNewLine;
//
// Chainable setters
//
public DefaultParser quoteChars(final char[] chars) {
this.quoteChars = chars;
return this;
}
public DefaultParser escapeChars(final char[] chars) {
this.escapeChars = chars;
return this;
}
public DefaultParser eofOnUnclosedQuote(boolean eofOnUnclosedQuote) {
this.eofOnUnclosedQuote = eofOnUnclosedQuote;
return this;
}
public DefaultParser eofOnEscapedNewLine(boolean eofOnEscapedNewLine) {
this.eofOnEscapedNewLine = eofOnEscapedNewLine;
return this;
}
//
// Java bean getters and setters
//
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 void setEofOnUnclosedQuote(boolean eofOnUnclosedQuote) {
this.eofOnUnclosedQuote = eofOnUnclosedQuote;
}
public boolean isEofOnUnclosedQuote() {
return eofOnUnclosedQuote;
}
public void setEofOnEscapedNewLine(boolean eofOnEscapedNewLine) {
this.eofOnEscapedNewLine = eofOnEscapedNewLine;
}
public boolean isEofOnEscapedNewLine() {
return eofOnEscapedNewLine;
}
public ParsedLine parse(final String line, final int cursor, ParseContext context) {
List<String> words = new LinkedList<>();
StringBuilder current = new StringBuilder();
int wordCursor = -1;
int wordIndex = -1;
int quoteStart = -1;
int rawWordCursor = -1;
int rawWordLength = -1;
int rawWordStart = 0;
for (int i = 0; (line != null) && (i < line.length()); i++) {
// once we reach the cursor, set the
// position of the selected index
if (i == cursor) {
wordIndex = words.size();
// the position in the current argument is just the
// length of the current argument
wordCursor = current.length();
rawWordCursor = i - rawWordStart;
}
if (quoteStart < 0 && isQuoteChar(line, i)) {
// Start a quote block
quoteStart = i;
} else if (quoteStart >= 0) {
// In a quote block
if (line.charAt(quoteStart) == line.charAt(i) && !isEscaped(line, i)) {
// End the block; arg could be empty, but that's fine
words.add(current.toString());
current.setLength(0);
quoteStart = -1;
if (rawWordCursor >= 0 && rawWordLength < 0) {
rawWordLength = i - rawWordStart + 1;
}
} else {
if (!isEscapeChar(line, i)) {
// Take the next character
current.append(line.charAt(i));
}
}
} else {
// Not in a quote block
if (isDelimiter(line, i)) {
if (current.length() > 0) {
words.add(current.toString());
current.setLength(0); // reset the arg
if (rawWordCursor >= 0 && rawWordLength < 0) {
rawWordLength = i - rawWordStart;
}
}
rawWordStart = i + 1;
} else {
if (!isEscapeChar(line, i)) {
current.append(line.charAt(i));
}
}
}
}
if (current.length() > 0 || cursor == line.length()) {
words.add(current.toString());
if (rawWordCursor >= 0 && rawWordLength < 0) {
rawWordLength = line.length() - rawWordStart;
}
}
if (cursor == line.length()) {
wordIndex = words.size() - 1;
wordCursor = words.get(words.size() - 1).length();
rawWordCursor = cursor - rawWordStart;
rawWordLength = rawWordCursor;
}
if (eofOnEscapedNewLine && isEscapeChar(line, line.length() - 1)) {
throw new EOFError(-1, -1, "Escaped new line", "newline");
}
if (eofOnUnclosedQuote && quoteStart >= 0 && context != ParseContext.COMPLETE) {
throw new EOFError(-1, -1, "Missing closing quote", line.charAt(quoteStart) == '\''
? "quote" : "dquote");
}
String openingQuote = quoteStart >= 0 ? line.substring(quoteStart, quoteStart + 1) : null;
return new ArgumentList(line, words, wordIndex, wordCursor, cursor, openingQuote, rawWordCursor, rawWordLength);
}
/**
* 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;
}
if (quoteChars != null) {
for (char e : quoteChars) {
if (e == buffer.charAt(pos)) {
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
* 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 isEscapeChar(final CharSequence buffer, final int pos) {
if (pos < 0) {
return false;
}
if (escapeChars != null) {
for (char e : escapeChars) {
if (e == buffer.charAt(pos)) {
return !isEscaped(buffer, pos);
}
}
}
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 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.
*
* @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 a delimiter.
*/
public boolean isDelimiterChar(CharSequence buffer, int pos) {
return Character.isWhitespace(buffer.charAt(pos));
}
private boolean isRawEscapeChar(char key) {
if (escapeChars != null) {
for (char e : escapeChars) {
if (e == key) {
return true;
}
}
}
return false;
}
private boolean isRawQuoteChar(char key) {
if (quoteChars != null) {
for (char e : quoteChars) {
if (e == key) {
return true;
}
}
}
return false;
}
/**
* The result of a delimited buffer.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
*/
public class ArgumentList implements ParsedLine, CompletingParsedLine
{
private final String line;
private final List<String> words;
private final int wordIndex;
private final int wordCursor;
private final int cursor;
private final String openingQuote;
private final int rawWordCursor;
private final int rawWordLength;
@Deprecated
public ArgumentList(final String line, final List<String> words,
final int wordIndex, final int wordCursor,
final int cursor) {
this(line, words, wordIndex, wordCursor, cursor,
null, wordCursor, words.get(wordIndex).length());
}
/**
*
* @param line the command line being edited
* @param words the list of words
* @param wordIndex the index of the current word in the list of words
* @param wordCursor the cursor position within the current word
* @param cursor the cursor position within the line
* @param openingQuote the opening quote (usually '\"' or '\'') or null
* @param rawWordCursor the cursor position inside the raw word (i.e. including quotes and escape characters)
* @param rawWordLength the raw word length, including quotes and escape characters
*/
public ArgumentList(final String line, final List<String> words,
final int wordIndex, final int wordCursor,
final int cursor, final String openingQuote,
final int rawWordCursor, final int rawWordLength) {
this.line = line;
this.words = Collections.unmodifiableList(Objects.requireNonNull(words));
this.wordIndex = wordIndex;
this.wordCursor = wordCursor;
this.cursor = cursor;
this.openingQuote = openingQuote;
this.rawWordCursor = rawWordCursor;
this.rawWordLength = rawWordLength;
}
public int wordIndex() {
return this.wordIndex;
}
public String word() {
// TODO: word() should always be contained in words()
if ((wordIndex < 0) || (wordIndex >= words.size())) {
return "";
}
return words.get(wordIndex);
}
public int wordCursor() {
return this.wordCursor;
}
public List<String> words() {
return this.words;
}
public int cursor() {
return this.cursor;
}
public String line() {
return line;
}
public CharSequence escape(CharSequence candidate, boolean complete) {
StringBuilder sb = new StringBuilder(candidate);
Predicate<Integer> needToBeEscaped;
// Completion is protected by an opening quote:
// Delimiters (spaces) don't need to be escaped, nor do other quotes, but everything else does.
// Also, close the quote at the end
if (openingQuote != null) {
needToBeEscaped = i -> isRawEscapeChar(sb.charAt(i)) || String.valueOf(sb.charAt(i)).equals(openingQuote);
}
// No quote protection, need to escape everything: delimiter chars (spaces), quote chars
// and escapes themselves
else {
needToBeEscaped = i -> isDelimiterChar(sb, i) || isRawEscapeChar(sb.charAt(i)) || isRawQuoteChar(sb.charAt(i));
}
for (int i = 0; i < sb.length(); i++) {
if (needToBeEscaped.test(i)) {
sb.insert(i++, escapeChars[0]);
}
}
if (openingQuote != null) {
sb.insert(0, openingQuote);
if (complete) {
sb.append(openingQuote);
}
}
return sb;
}
@Override
public int rawWordCursor() {
return rawWordCursor;
}
@Override
public int rawWordLength() {
return rawWordLength;
}
}
}

View file

@ -1,12 +1,12 @@
/* /*
* Copyright (c) 2002-2016, the original author or authors. * Copyright (c) 2002-2018, the original author or authors.
* *
* This software is distributable under the BSD license. See the terms of the * This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software. * BSD license in the documentation provided with this software.
* *
* http://www.opensource.org/licenses/bsd-license.php * http://www.opensource.org/licenses/bsd-license.php
*/ */
package jdk.internal.jline.console; package jdk.internal.org.jline.reader.impl;
/** /**
* The kill ring class keeps killed text in a fixed size ring. In this * The kill ring class keeps killed text in a fixed size ring. In this
@ -32,6 +32,8 @@ public final class KillRing {
/** /**
* Creates a new kill ring of the given size. * Creates a new kill ring of the given size.
*
* @param size the size of the ring
*/ */
public KillRing(int size) { public KillRing(int size) {
slots = new String[size]; slots = new String[size];
@ -60,6 +62,7 @@ public final class KillRing {
/** /**
* Returns {@code true} if the last command was a yank. * Returns {@code true} if the last command was a yank.
* @return {@code true} if the last command was a yank
*/ */
public boolean lastYank() { public boolean lastYank() {
return lastYank; return lastYank;
@ -68,6 +71,7 @@ public final class KillRing {
/** /**
* Adds the string to the kill-ring. Also sets lastYank to false * Adds the string to the kill-ring. Also sets lastYank to false
* and lastKill to true. * and lastKill to true.
* @param str the string to add
*/ */
public void add(String str) { public void add(String str) {
lastYank = false; lastYank = false;
@ -90,6 +94,7 @@ public final class KillRing {
* adds the text at the beginning of the previous kill to avoid * adds the text at the beginning of the previous kill to avoid
* that two consecutive backwards kills followed by a yank leaves * that two consecutive backwards kills followed by a yank leaves
* things reversed. * things reversed.
* @param str the string to add
*/ */
public void addBackwards(String str) { public void addBackwards(String str) {
lastYank = false; lastYank = false;
@ -109,6 +114,7 @@ public final class KillRing {
/** /**
* Yanks a previously killed text. Returns {@code null} if the * Yanks a previously killed text. Returns {@code null} if the
* ring is empty. * ring is empty.
* @return the text in the current position
*/ */
public String yank() { public String yank() {
lastKill = false; lastKill = false;
@ -120,6 +126,7 @@ public final class KillRing {
* Moves the pointer to the current slot back and returns the text * Moves the pointer to the current slot back and returns the text
* in that position. If the previous command was not yank returns * in that position. If the previous command was not yank returns
* null. * null.
* @return the text in the previous position
*/ */
public String yankPop() { public String yankPop() {
lastKill = false; lastKill = false;
@ -152,7 +159,7 @@ public final class KillRing {
private void prev() { private void prev() {
head--; head--;
if (head == -1) { if (head == -1) {
int x = slots.length - 1; int x = (slots.length - 1);
for (; x >= 0; x--) { for (; x >= 0; x--) {
if (slots[x] != null) { if (slots[x] != null) {
break; break;

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2002-2016, 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.org.jline.reader.impl;
import jdk.internal.org.jline.reader.LineReader;
public class ReaderUtils {
private ReaderUtils() { }
public static boolean isSet(LineReader reader, LineReader.Option option) {
return reader != null && reader.isSet(option);
}
public static String getString(LineReader reader, String name, String def) {
Object v = reader != null ? reader.getVariable(name) : null;
return v != null ? v.toString() : def;
}
public static boolean getBoolean(LineReader reader, String name, boolean def) {
Object v = reader != null ? reader.getVariable(name) : null;
if (v instanceof Boolean) {
return (Boolean) v;
} else if (v != null) {
String s = v.toString();
return s.isEmpty() || s.equalsIgnoreCase("on")
|| s.equalsIgnoreCase("1") || s.equalsIgnoreCase("true");
}
return def;
}
public static int getInt(LineReader reader, String name, int def) {
int nb = def;
Object v = reader != null ? reader.getVariable(name) : null;
if (v instanceof Number) {
return ((Number) v).intValue();
} else if (v != null) {
nb = 0;
try {
nb = Integer.parseInt(v.toString());
} catch (NumberFormatException e) {
// Ignore
}
}
return nb;
}
public static long getLong(LineReader reader, String name, long def) {
long nb = def;
Object v = reader != null ? reader.getVariable(name) : null;
if (v instanceof Number) {
return ((Number) v).longValue();
} else if (v != null) {
nb = 0;
try {
nb = Long.parseLong(v.toString());
} catch (NumberFormatException e) {
// Ignore
}
}
return nb;
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2002-2017, 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.org.jline.reader.impl;
import jdk.internal.org.jline.reader.MaskingCallback;
import java.util.Objects;
/**
* Simple {@link MaskingCallback} that will replace all the characters in the line with the given mask.
* If the given mask is equal to {@link LineReaderImpl#NULL_MASK} then the line will be replaced with an empty String.
*/
public final class SimpleMaskingCallback implements MaskingCallback {
private final Character mask;
public SimpleMaskingCallback(Character mask) {
this.mask = Objects.requireNonNull(mask, "mask must be a non null character");
}
@Override
public String display(String line) {
if (mask.equals(LineReaderImpl.NULL_MASK)) {
return "";
} else {
StringBuilder sb = new StringBuilder(line.length());
for (int i = line.length(); i-- > 0;) {
sb.append((char) mask);
}
return sb.toString();
}
}
@Override
public String history(String line) {
return null;
}
}

View file

@ -0,0 +1,75 @@
/*
* Copyright (c) 2002-2016, 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.org.jline.reader.impl;
import java.util.function.Consumer;
/**
* Simple undo tree.
* Note that the first added state can't be undone
*/
public class UndoTree<T> {
private final Consumer<T> state;
private final Node parent;
private Node current;
public UndoTree(Consumer<T> s) {
state = s;
parent = new Node(null);
parent.left = parent;
clear();
}
public void clear() {
current = parent;
}
public void newState(T state) {
Node node = new Node(state);
current.right = node;
node.left = current;
current = node;
}
public boolean canUndo() {
return current.left != parent;
}
public boolean canRedo() {
return current.right != null;
}
public void undo() {
if (!canUndo()) {
throw new IllegalStateException("Cannot undo.");
}
current = current.left;
state.accept(current.state);
}
public void redo() {
if (!canRedo()) {
throw new IllegalStateException("Cannot redo.");
}
current = current.right;
state.accept(current.state);
}
private class Node {
private final T state;
private Node left = null;
private Node right = null;
public Node(T s) {
state = s;
}
}
}

View file

@ -0,0 +1,86 @@
/*
* Copyright (c) 2002-2016, 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.org.jline.reader.impl.completer;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import jdk.internal.org.jline.reader.Candidate;
import jdk.internal.org.jline.reader.Completer;
import jdk.internal.org.jline.reader.LineReader;
import jdk.internal.org.jline.reader.ParsedLine;
/**
* 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 Collection<Completer> 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));
}
/**
* Construct an AggregateCompleter with the given completers.
* The completers will be used in the order given.
*
* @param completers the completers
*/
public AggregateCompleter(Collection<Completer> completers) {
assert completers != null;
this.completers = 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.
*
* The effect is similar to the following code:
* <blockquote><pre>{@code completers.forEach(c -> c.complete(reader, line, candidates));}</pre></blockquote>
*
* @see Completer#complete(LineReader, ParsedLine, List)
*/
public void complete(LineReader reader, final ParsedLine line, final List<Candidate> candidates) {
Objects.requireNonNull(line);
Objects.requireNonNull(candidates);
completers.forEach(c -> c.complete(reader, line, candidates));
}
/**
* @return a string representing the aggregated completers
*/
@Override
public String toString() {
return getClass().getSimpleName() + "{" +
"completers=" + completers +
'}';
}
}

View file

@ -0,0 +1,169 @@
/*
* Copyright (c) 2002-2018, 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.org.jline.reader.impl.completer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import jdk.internal.org.jline.reader.Candidate;
import jdk.internal.org.jline.reader.Completer;
import jdk.internal.org.jline.reader.LineReader;
import jdk.internal.org.jline.reader.ParsedLine;
/**
* 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 List<Completer> completers = new ArrayList<>();
private boolean strict = true;
/**
* Create a new completer.
*
* @param completers The embedded completers
*/
public ArgumentCompleter(final Collection<Completer> completers) {
Objects.requireNonNull(completers);
this.completers.addAll(completers);
}
/**
* Create a new completer.
*
* @param completers The embedded completers
*/
public ArgumentCompleter(final Completer... completers) {
this(Arrays.asList(completers));
}
/**
* If true, a completion at argument index N will only succeed
* if all the completions from 0-(N-1) also succeed.
*
* @param strict the strict flag
*/
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;
}
/**
* Returns the list of completers used inside this <code>ArgumentCompleter</code>.
* @return The list of completers.
* @since 2.3
*/
public List<Completer> getCompleters() {
return completers;
}
public void complete(LineReader reader, ParsedLine line, final List<Candidate> candidates) {
Objects.requireNonNull(line);
Objects.requireNonNull(candidates);
if (line.wordIndex() < 0) {
return;
}
List<Completer> completers = getCompleters();
Completer completer;
// if we are beyond the end of the completers, just use the last one
if (line.wordIndex() >= completers.size()) {
completer = completers.get(completers.size() - 1);
}
else {
completer = completers.get(line.wordIndex());
}
// ensure that all the previous completers are successful before allowing this completer to pass (only if strict).
for (int i = 0; isStrict() && (i < line.wordIndex()); i++) {
Completer sub = completers.get(i >= completers.size() ? (completers.size() - 1) : i);
List<? extends CharSequence> args = line.words();
String arg = (args == null || i >= args.size()) ? "" : args.get(i).toString();
List<Candidate> subCandidates = new LinkedList<>();
sub.complete(reader, new ArgumentLine(arg, arg.length()), subCandidates);
boolean found = false;
for (Candidate cand : subCandidates) {
if (cand.value().equals(arg)) {
found = true;
break;
}
}
if (!found) {
return;
}
}
completer.complete(reader, line, candidates);
}
public static class ArgumentLine implements ParsedLine {
private final String word;
private final int cursor;
public ArgumentLine(String word, int cursor) {
this.word = word;
this.cursor = cursor;
}
@Override
public String word() {
return word;
}
@Override
public int wordCursor() {
return cursor;
}
@Override
public int wordIndex() {
return 0;
}
@Override
public List<String> words() {
return Collections.singletonList(word);
}
@Override
public String line() {
return word;
}
@Override
public int cursor() {
return cursor;
}
}
}

View file

@ -6,9 +6,12 @@
* *
* http://www.opensource.org/licenses/bsd-license.php * http://www.opensource.org/licenses/bsd-license.php
*/ */
package jdk.internal.jline.console.completer; package jdk.internal.org.jline.reader.impl.completer;
import static jdk.internal.jline.internal.Preconditions.checkNotNull; import java.util.Objects;
import jdk.internal.org.jline.reader.Candidate;
import jdk.internal.org.jline.reader.Completer;
/** /**
* {@link Completer} for {@link Enum} names. * {@link Completer} for {@link Enum} names.
@ -16,18 +19,12 @@ import static jdk.internal.jline.internal.Preconditions.checkNotNull;
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a> * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.3 * @since 2.3
*/ */
public class EnumCompleter public class EnumCompleter extends StringsCompleter
extends StringsCompleter
{ {
public EnumCompleter(Class<? extends Enum<?>> source) { public EnumCompleter(Class<? extends Enum<?>> source) {
this(source, true); Objects.requireNonNull(source);
}
public EnumCompleter(Class<? extends Enum<?>> source, boolean toLowerCase) {
checkNotNull(source);
for (Enum<?> n : source.getEnumConstants()) { for (Enum<?> n : source.getEnumConstants()) {
this.getStrings().add(toLowerCase ? n.name().toLowerCase() : n.name()); candidates.add(new Candidate(n.name().toLowerCase()));
} }
} }
} }

View file

@ -0,0 +1,129 @@
/*
* Copyright (c) 2002-2018, 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.org.jline.reader.impl.completer;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import jdk.internal.org.jline.reader.Candidate;
import jdk.internal.org.jline.reader.Completer;
import jdk.internal.org.jline.reader.LineReader;
import jdk.internal.org.jline.reader.LineReader.Option;
import jdk.internal.org.jline.reader.ParsedLine;
import jdk.internal.org.jline.terminal.Terminal;
import jdk.internal.org.jline.utils.AttributedStringBuilder;
import jdk.internal.org.jline.utils.AttributedStyle;
/**
* 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:
* <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
* @deprecated use <code>org.jline.builtins.Completers$FileNameCompleter</code> instead
*/
@Deprecated
public class FileNameCompleter implements Completer
{
public void complete(LineReader reader, ParsedLine commandLine, final List<Candidate> candidates) {
assert commandLine != null;
assert candidates != null;
String buffer = commandLine.word().substring(0, commandLine.wordCursor());
Path current;
String curBuf;
String sep = getUserDir().getFileSystem().getSeparator();
int lastSep = buffer.lastIndexOf(sep);
if (lastSep >= 0) {
curBuf = buffer.substring(0, lastSep + 1);
if (curBuf.startsWith("~")) {
if (curBuf.startsWith("~" + sep)) {
current = getUserHome().resolve(curBuf.substring(2));
} else {
current = getUserHome().getParent().resolve(curBuf.substring(1));
}
} else {
current = getUserDir().resolve(curBuf);
}
} else {
curBuf = "";
current = getUserDir();
}
try {
Files.newDirectoryStream(current, this::accept).forEach(p -> {
String value = curBuf + p.getFileName().toString();
if (Files.isDirectory(p)) {
candidates.add(new Candidate(
value + (reader.isSet(Option.AUTO_PARAM_SLASH) ? sep : ""),
getDisplay(reader.getTerminal(), p),
null, null,
reader.isSet(Option.AUTO_REMOVE_SLASH) ? sep : null,
null,
false));
} else {
candidates.add(new Candidate(value, getDisplay(reader.getTerminal(), p),
null, null, null, null, true));
}
});
} catch (IOException e) {
// Ignore
}
}
protected boolean accept(Path path) {
try {
return !Files.isHidden(path);
} catch (IOException e) {
return false;
}
}
protected Path getUserDir() {
return Paths.get(System.getProperty("user.dir"));
}
protected Path getUserHome() {
return Paths.get(System.getProperty("user.home"));
}
protected String getDisplay(Terminal terminal, Path p) {
// TODO: use $LS_COLORS for output
String name = p.getFileName().toString();
if (Files.isDirectory(p)) {
AttributedStringBuilder sb = new AttributedStringBuilder();
sb.styled(AttributedStyle.BOLD.foreground(AttributedStyle.RED), name);
sb.append("/");
name = sb.toAnsi(terminal);
} else if (Files.isSymbolicLink(p)) {
AttributedStringBuilder sb = new AttributedStringBuilder();
sb.styled(AttributedStyle.BOLD.foreground(AttributedStyle.RED), name);
sb.append("@");
name = sb.toAnsi(terminal);
}
return name;
}
}

View file

@ -6,10 +6,15 @@
* *
* http://www.opensource.org/licenses/bsd-license.php * http://www.opensource.org/licenses/bsd-license.php
*/ */
package jdk.internal.jline.console.completer; package jdk.internal.org.jline.reader.impl.completer;
import java.util.List; import java.util.List;
import jdk.internal.org.jline.reader.Candidate;
import jdk.internal.org.jline.reader.Completer;
import jdk.internal.org.jline.reader.LineReader;
import jdk.internal.org.jline.reader.ParsedLine;
/** /**
* Null completer. * Null completer.
* *
@ -22,7 +27,6 @@ public final class NullCompleter
{ {
public static final NullCompleter INSTANCE = new NullCompleter(); public static final NullCompleter INSTANCE = new NullCompleter();
public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) { public void complete(LineReader reader, final ParsedLine line, final List<Candidate> candidates) {
return -1;
} }
} }

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2002-2016, 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.org.jline.reader.impl.completer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import jdk.internal.org.jline.reader.Candidate;
import jdk.internal.org.jline.reader.Completer;
import jdk.internal.org.jline.reader.LineReader;
import jdk.internal.org.jline.reader.ParsedLine;
import jdk.internal.org.jline.utils.AttributedString;
/**
* Completer for a set of strings.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.3
*/
public class StringsCompleter implements Completer
{
protected final Collection<Candidate> candidates = new ArrayList<>();
public StringsCompleter() {
}
public StringsCompleter(String... strings) {
this(Arrays.asList(strings));
}
public StringsCompleter(Iterable<String> strings) {
assert strings != null;
for (String string : strings) {
candidates.add(new Candidate(AttributedString.stripAnsi(string), string, null, null, null, null, true));
}
}
public void complete(LineReader reader, final ParsedLine commandLine, final List<Candidate> candidates) {
assert commandLine != null;
assert candidates != null;
candidates.addAll(this.candidates);
}
}

View file

@ -7,8 +7,8 @@
* http://www.opensource.org/licenses/bsd-license.php * http://www.opensource.org/licenses/bsd-license.php
*/ */
/** /**
* Console history support. * JLine 3.
* *
* @since 2.0 * @since 3.0
*/ */
package jdk.internal.jline.console.history; package jdk.internal.org.jline.reader.impl.completer;

View file

@ -0,0 +1,507 @@
/*
* Copyright (c) 2002-2018, 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.org.jline.reader.impl.history;
import java.io.*;
import java.nio.file.*;
import java.time.Instant;
import java.util.*;
import jdk.internal.org.jline.reader.History;
import jdk.internal.org.jline.reader.LineReader;
import jdk.internal.org.jline.utils.Log;
import static jdk.internal.org.jline.reader.LineReader.HISTORY_IGNORE;
import static jdk.internal.org.jline.reader.impl.ReaderUtils.*;
/**
* {@link History} using a file for persistent backing.
* <p>
* Implementers should install shutdown hook to call {@link DefaultHistory#save}
* to save history to disk.
* </p>
*/
public class DefaultHistory implements History {
public static final int DEFAULT_HISTORY_SIZE = 500;
public static final int DEFAULT_HISTORY_FILE_SIZE = 10000;
private final LinkedList<Entry> items = new LinkedList<>();
private LineReader reader;
private int lastLoaded = 0;
private int nbEntriesInFile = 0;
private int offset = 0;
private int index = 0;
public DefaultHistory() {
}
public DefaultHistory(LineReader reader) {
attach(reader);
}
private Path getPath() {
Object obj = reader != null ? reader.getVariables().get(LineReader.HISTORY_FILE) : null;
if (obj instanceof Path) {
return (Path) obj;
} else if (obj instanceof File) {
return ((File) obj).toPath();
} else if (obj != null) {
return Paths.get(obj.toString());
} else {
return null;
}
}
@Override
public void attach(LineReader reader) {
if (this.reader != reader) {
this.reader = reader;
try {
load();
}
catch (IOException e) {
Log.warn("Failed to load history", e);
}
}
}
@Override
public void load() throws IOException {
Path path = getPath();
if (path != null) {
try {
if (Files.exists(path)) {
Log.trace("Loading history from: ", path);
try (BufferedReader reader = Files.newBufferedReader(path)) {
internalClear();
reader.lines().forEach(line -> addHistoryLine(path, line));
lastLoaded = items.size();
nbEntriesInFile = lastLoaded;
maybeResize();
}
}
} catch (IOException e) {
Log.debug("Failed to load history; clearing", e);
internalClear();
throw e;
}
}
}
protected void addHistoryLine(Path path, String line) {
if (reader.isSet(LineReader.Option.HISTORY_TIMESTAMPED)) {
int idx = line.indexOf(':');
if (idx < 0) {
throw new IllegalArgumentException("Bad history file syntax! " +
"The history file `" + path + "` may be an older history: " +
"please remove it or use a different history file.");
}
Instant time = Instant.ofEpochMilli(Long.parseLong(line.substring(0, idx)));
String unescaped = unescape(line.substring(idx + 1));
internalAdd(time, unescaped);
}
else {
internalAdd(Instant.now(), unescape(line));
}
}
@Override
public void purge() throws IOException {
internalClear();
Path path = getPath();
if (path != null) {
Log.trace("Purging history from: ", path);
Files.deleteIfExists(path);
}
}
@Override
public void save() throws IOException {
Path path = getPath();
if (path != null) {
Log.trace("Saving history to: ", path);
Files.createDirectories(path.toAbsolutePath().getParent());
// Append new items to the history file
try (BufferedWriter writer = Files.newBufferedWriter(path.toAbsolutePath(),
StandardOpenOption.WRITE, StandardOpenOption.APPEND, StandardOpenOption.CREATE)) {
for (Entry entry : items.subList(lastLoaded, items.size())) {
if (isPersistable(entry)) {
writer.append(format(entry));
}
}
}
nbEntriesInFile += items.size() - lastLoaded;
// If we are over 25% max size, trim history file
int max = getInt(reader, LineReader.HISTORY_FILE_SIZE, DEFAULT_HISTORY_FILE_SIZE);
if (nbEntriesInFile > max + max / 4) {
trimHistory(path, max);
}
}
lastLoaded = items.size();
}
protected void trimHistory(Path path, int max) throws IOException {
Log.trace("Trimming history path: ", path);
// Load all history entries
LinkedList<Entry> allItems = new LinkedList<>();
try (BufferedReader reader = Files.newBufferedReader(path)) {
reader.lines().forEach(l -> {
int idx = l.indexOf(':');
Instant time = Instant.ofEpochMilli(Long.parseLong(l.substring(0, idx)));
String line = unescape(l.substring(idx + 1));
allItems.add(createEntry(allItems.size(), time, line));
});
}
// Remove duplicates
doTrimHistory(allItems, max);
// Write history
Path temp = Files.createTempFile(path.toAbsolutePath().getParent(), path.getFileName().toString(), ".tmp");
try (BufferedWriter writer = Files.newBufferedWriter(temp, StandardOpenOption.WRITE)) {
for (Entry entry : allItems) {
writer.append(format(entry));
}
}
Files.move(temp, path, StandardCopyOption.REPLACE_EXISTING);
// Keep items in memory
internalClear();
offset = allItems.get(0).index();
items.addAll(allItems);
lastLoaded = items.size();
nbEntriesInFile = items.size();
maybeResize();
}
/**
* Create a history entry. Subclasses may override to use their own entry implementations.
* @param index index of history entry
* @param time entry creation time
* @param line the entry text
* @return entry object
*/
protected EntryImpl createEntry(int index, Instant time, String line) {
return new EntryImpl(index, time, line);
}
private void internalClear() {
offset = 0;
index = 0;
lastLoaded = 0;
nbEntriesInFile = 0;
items.clear();
}
static void doTrimHistory(List<Entry> allItems, int max) {
int idx = 0;
while (idx < allItems.size()) {
int ridx = allItems.size() - idx - 1;
String line = allItems.get(ridx).line().trim();
ListIterator<Entry> iterator = allItems.listIterator(ridx);
while (iterator.hasPrevious()) {
String l = iterator.previous().line();
if (line.equals(l.trim())) {
iterator.remove();
}
}
idx++;
}
while (allItems.size() > max) {
allItems.remove(0);
}
}
public int size() {
return items.size();
}
public boolean isEmpty() {
return items.isEmpty();
}
public int index() {
return offset + index;
}
public int first() {
return offset;
}
public int last() {
return offset + items.size() - 1;
}
private String format(Entry entry) {
if (reader.isSet(LineReader.Option.HISTORY_TIMESTAMPED)) {
return Long.toString(entry.time().toEpochMilli()) + ":" + escape(entry.line()) + "\n";
}
return escape(entry.line()) + "\n";
}
public String get(final int index) {
return items.get(index - offset).line();
}
@Override
public void add(Instant time, String line) {
Objects.requireNonNull(time);
Objects.requireNonNull(line);
if (getBoolean(reader, LineReader.DISABLE_HISTORY, false)) {
return;
}
if (isSet(reader, LineReader.Option.HISTORY_IGNORE_SPACE) && line.startsWith(" ")) {
return;
}
if (isSet(reader, LineReader.Option.HISTORY_REDUCE_BLANKS)) {
line = line.trim();
}
if (isSet(reader, LineReader.Option.HISTORY_IGNORE_DUPS)) {
if (!items.isEmpty() && line.equals(items.getLast().line())) {
return;
}
}
if (matchPatterns(getString(reader, HISTORY_IGNORE, ""), line)) {
return;
}
internalAdd(time, line);
if (isSet(reader, LineReader.Option.HISTORY_INCREMENTAL)) {
try {
save();
}
catch (IOException e) {
Log.warn("Failed to save history", e);
}
}
}
protected boolean matchPatterns(String patterns, String line) {
if (patterns == null || patterns.isEmpty()) {
return false;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < patterns.length(); i++) {
char ch = patterns.charAt(i);
if (ch == '\\') {
ch = patterns.charAt(++i);
sb.append(ch);
} else if (ch == ':') {
sb.append('|');
} else if (ch == '*') {
sb.append('.').append('*');
}
}
return line.matches(sb.toString());
}
protected void internalAdd(Instant time, String line) {
Entry entry = new EntryImpl(offset + items.size(), time, line);
items.add(entry);
maybeResize();
}
private void maybeResize() {
while (size() > getInt(reader, LineReader.HISTORY_SIZE, DEFAULT_HISTORY_SIZE)) {
items.removeFirst();
lastLoaded--;
offset++;
}
index = size();
}
public ListIterator<Entry> iterator(int index) {
return items.listIterator(index - offset);
}
@Override
public Spliterator<Entry> spliterator() {
return items.spliterator();
}
protected static class EntryImpl implements Entry {
private final int index;
private final Instant time;
private final String line;
public EntryImpl(int index, Instant time, String line) {
this.index = index;
this.time = time;
this.line = line;
}
public int index() {
return index;
}
public Instant time() {
return time;
}
public String line() {
return line;
}
@Override
public String toString() {
return String.format("%d: %s", index, line);
}
}
//
// 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 iterator 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
*/
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 iterator 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 iterator.
*/
public void moveToEnd() {
index = size();
}
/**
* Return the content of the current buffer.
*/
public String current() {
if (index >= size()) {
return "";
}
return items.get(index).line();
}
/**
* 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()).append("\n");
}
return sb.toString();
}
private static String escape(String s) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
switch (ch) {
case '\n':
sb.append('\\');
sb.append('n');
break;
case '\r':
sb.append('\\');
sb.append('r');
break;
case '\\':
sb.append('\\');
sb.append('\\');
break;
default:
sb.append(ch);
break;
}
}
return sb.toString();
}
static String unescape(String s) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
switch (ch) {
case '\\':
ch = s.charAt(++i);
if (ch == 'n') {
sb.append('\n');
} else if (ch == 'r') {
sb.append('\r');
} else {
sb.append(ch);
}
break;
default:
sb.append(ch);
break;
}
}
return sb.toString();
}
}

View file

@ -0,0 +1,14 @@
/*
* Copyright (c) 2002-2016, 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 3.
*
* @since 3.0
*/
package jdk.internal.org.jline.reader.impl.history;

View file

@ -7,8 +7,8 @@
* http://www.opensource.org/licenses/bsd-license.php * http://www.opensource.org/licenses/bsd-license.php
*/ */
/** /**
* Console support. * JLine 3.
* *
* @since 2.0 * @since 3.0
*/ */
package jdk.internal.jline.console; package jdk.internal.org.jline.reader;

View file

@ -0,0 +1,349 @@
/*
* Copyright (c) 2002-2016, 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.org.jline.terminal;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.function.Function;
import java.util.stream.Collectors;
public class Attributes {
/**
* Control characters
*/
public enum ControlChar {
VEOF,
VEOL,
VEOL2,
VERASE,
VWERASE,
VKILL,
VREPRINT,
VINTR,
VQUIT,
VSUSP,
VDSUSP,
VSTART,
VSTOP,
VLNEXT,
VDISCARD,
VMIN,
VTIME,
VSTATUS
}
/**
* Input flags - software input processing
*/
public enum InputFlag {
IGNBRK, /* ignore BREAK condition */
BRKINT, /* map BREAK to SIGINTR */
IGNPAR, /* ignore (discard) parity errors */
PARMRK, /* mark parity and framing errors */
INPCK, /* enable checking of parity errors */
ISTRIP, /* strip 8th bit off chars */
INLCR, /* map NL into CR */
IGNCR, /* ignore CR */
ICRNL, /* map CR to NL (ala CRMOD) */
IXON, /* enable output flow control */
IXOFF, /* enable input flow control */
IXANY, /* any char will restart after stop */
IMAXBEL, /* ring bell on input queue full */
IUTF8 /* maintain state for UTF-8 VERASE */
}
/*
* Output flags - software output processing
*/
public enum OutputFlag {
OPOST, /* enable following output processing */
ONLCR, /* map NL to CR-NL (ala CRMOD) */
OXTABS, /* expand tabs to spaces */
ONOEOT, /* discard EOT's (^D) on output) */
OCRNL, /* map CR to NL on output */
ONOCR, /* no CR output at column 0 */
ONLRET, /* NL performs CR function */
OFILL, /* use fill characters for delay */
NLDLY, /* \n delay */
TABDLY, /* horizontal tab delay */
CRDLY, /* \r delay */
FFDLY, /* form feed delay */
BSDLY, /* \b delay */
VTDLY, /* vertical tab delay */
OFDEL /* fill is DEL, else NUL */
}
/*
* Control flags - hardware control of terminal
*/
public enum ControlFlag {
CIGNORE, /* ignore control flags */
CS5, /* 5 bits (pseudo) */
CS6, /* 6 bits */
CS7, /* 7 bits */
CS8, /* 8 bits */
CSTOPB, /* send 2 stop bits */
CREAD, /* enable receiver */
PARENB, /* parity enable */
PARODD, /* odd parity, else even */
HUPCL, /* hang up on last close */
CLOCAL, /* ignore modem status lines */
CCTS_OFLOW, /* CTS flow control of output */
CRTS_IFLOW, /* RTS flow control of input */
CDTR_IFLOW, /* DTR flow control of input */
CDSR_OFLOW, /* DSR flow control of output */
CCAR_OFLOW /* DCD flow control of output */
}
/*
* "Local" flags - dumping ground for other state
*
* Warning: some flags in this structure begin with
* the letter "I" and look like they belong in the
* input flag.
*/
public enum LocalFlag {
ECHOKE, /* visual erase for line kill */
ECHOE, /* visually erase chars */
ECHOK, /* echo NL after line kill */
ECHO, /* enable echoing */
ECHONL, /* echo NL even if ECHO is off */
ECHOPRT, /* visual erase mode for hardcopy */
ECHOCTL, /* echo control chars as ^(Char) */
ISIG, /* enable signals INTR, QUIT, [D]SUSP */
ICANON, /* canonicalize input lines */
ALTWERASE, /* use alternate WERASE algorithm */
IEXTEN, /* enable DISCARD and LNEXT */
EXTPROC, /* external processing */
TOSTOP, /* stop background jobs from output */
FLUSHO, /* output being flushed (state) */
NOKERNINFO, /* no kernel output from VSTATUS */
PENDIN, /* XXX retype pending input (state) */
NOFLSH /* don't flush after interrupt */
}
final EnumSet<InputFlag> iflag = EnumSet.noneOf(InputFlag.class);
final EnumSet<OutputFlag> oflag = EnumSet.noneOf(OutputFlag.class);
final EnumSet<ControlFlag> cflag = EnumSet.noneOf(ControlFlag.class);
final EnumSet<LocalFlag> lflag = EnumSet.noneOf(LocalFlag.class);
final EnumMap<ControlChar, Integer> cchars = new EnumMap<>(ControlChar.class);
public Attributes() {
}
public Attributes(Attributes attr) {
copy(attr);
}
//
// Input flags
//
public EnumSet<InputFlag> getInputFlags() {
return iflag;
}
public void setInputFlags(EnumSet<InputFlag> flags) {
iflag.clear();
iflag.addAll(flags);
}
public boolean getInputFlag(InputFlag flag) {
return iflag.contains(flag);
}
public void setInputFlags(EnumSet<InputFlag> flags, boolean value) {
if (value) {
iflag.addAll(flags);
} else {
iflag.removeAll(flags);
}
}
public void setInputFlag(InputFlag flag, boolean value) {
if (value) {
iflag.add(flag);
} else {
iflag.remove(flag);
}
}
//
// Output flags
//
public EnumSet<OutputFlag> getOutputFlags() {
return oflag;
}
public void setOutputFlags(EnumSet<OutputFlag> flags) {
oflag.clear();
oflag.addAll(flags);
}
public boolean getOutputFlag(OutputFlag flag) {
return oflag.contains(flag);
}
public void setOutputFlags(EnumSet<OutputFlag> flags, boolean value) {
if (value) {
oflag.addAll(flags);
} else {
oflag.removeAll(flags);
}
}
public void setOutputFlag(OutputFlag flag, boolean value) {
if (value) {
oflag.add(flag);
} else {
oflag.remove(flag);
}
}
//
// Control flags
//
public EnumSet<ControlFlag> getControlFlags() {
return cflag;
}
public void setControlFlags(EnumSet<ControlFlag> flags) {
cflag.clear();
cflag.addAll(flags);
}
public boolean getControlFlag(ControlFlag flag) {
return cflag.contains(flag);
}
public void setControlFlags(EnumSet<ControlFlag> flags, boolean value) {
if (value) {
cflag.addAll(flags);
} else {
cflag.removeAll(flags);
}
}
public void setControlFlag(ControlFlag flag, boolean value) {
if (value) {
cflag.add(flag);
} else {
cflag.remove(flag);
}
}
//
// Local flags
//
public EnumSet<LocalFlag> getLocalFlags() {
return lflag;
}
public void setLocalFlags(EnumSet<LocalFlag> flags) {
lflag.clear();
lflag.addAll(flags);
}
public boolean getLocalFlag(LocalFlag flag) {
return lflag.contains(flag);
}
public void setLocalFlags(EnumSet<LocalFlag> flags, boolean value) {
if (value) {
lflag.addAll(flags);
} else {
lflag.removeAll(flags);
}
}
public void setLocalFlag(LocalFlag flag, boolean value) {
if (value) {
lflag.add(flag);
} else {
lflag.remove(flag);
}
}
//
// Control chars
//
public EnumMap<ControlChar, Integer> getControlChars() {
return cchars;
}
public void setControlChars(EnumMap<ControlChar, Integer> chars) {
cchars.clear();
cchars.putAll(chars);
}
public int getControlChar(ControlChar c) {
Integer v = cchars.get(c);
return v != null ? v : -1;
}
public void setControlChar(ControlChar c, int value) {
cchars.put(c, value);
}
//
// Miscellaneous methods
//
public void copy(Attributes attributes) {
setControlFlags(attributes.getControlFlags());
setInputFlags(attributes.getInputFlags());
setLocalFlags(attributes.getLocalFlags());
setOutputFlags(attributes.getOutputFlags());
setControlChars(attributes.getControlChars());
}
@Override
public String toString() {
return "Attributes[" +
"lflags: " + append(lflag) + ", " +
"iflags: " + append(iflag) + ", " +
"oflags: " + append(oflag) + ", " +
"cflags: " + append(cflag) + ", " +
"cchars: " + append(EnumSet.allOf(ControlChar.class), this::display) +
"]";
}
private String display(ControlChar c) {
String value;
int ch = getControlChar(c);
if (c == ControlChar.VMIN || c == ControlChar.VTIME) {
value = Integer.toString(ch);
} else if (ch < 0) {
value = "<undef>";
} else if (ch < 32) {
value = "^" + (char) (ch + 'A' - 1);
} else if (ch == 127) {
value = "^?";
} else if (ch >= 128) {
value = String.format("\\u%04x", ch);
} else {
value = String.valueOf((char) ch);
}
return c.name().toLowerCase().substring(1) + "=" + value;
}
private <T extends Enum<T>> String append(EnumSet<T> set) {
return append(set, e -> e.name().toLowerCase());
}
private <T extends Enum<T>> String append(EnumSet<T> set, Function<T, String> toString) {
return set.stream().map(toString).collect(Collectors.joining(" "));
}
}

View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 2002-2016, 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.org.jline.terminal;
/**
* Class holding the cursor position.
*
* @see Terminal#getCursorPosition(java.util.function.IntConsumer)
*/
public class Cursor {
private final int x;
private final int y;
public Cursor(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override
public boolean equals(Object o) {
if (o instanceof Cursor) {
Cursor c = (Cursor) o;
return x == c.x && y == c.y;
} else {
return false;
}
}
@Override
public int hashCode() {
return x * 31 + y;
}
@Override
public String toString() {
return "Cursor[" + "x=" + x + ", y=" + y + ']';
}
}

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) 2002-2016, 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.org.jline.terminal;
import java.util.EnumSet;
public class MouseEvent {
public enum Type {
Released,
Pressed,
Wheel,
Moved,
Dragged
}
public enum Button {
NoButton,
Button1,
Button2,
Button3,
WheelUp,
WheelDown
}
public enum Modifier {
Shift,
Alt,
Control
}
private final Type type;
private final Button button;
private final EnumSet<Modifier> modifiers;
private final int x;
private final int y;
public MouseEvent(Type type, Button button, EnumSet<Modifier> modifiers, int x, int y) {
this.type = type;
this.button = button;
this.modifiers = modifiers;
this.x = x;
this.y = y;
}
public Type getType() {
return type;
}
public Button getButton() {
return button;
}
public EnumSet<Modifier> getModifiers() {
return modifiers;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override
public String toString() {
return "MouseEvent[" +
"type=" + type +
", button=" + button +
", modifiers=" + modifiers +
", x=" + x +
", y=" + y +
']';
}
}

View file

@ -0,0 +1,80 @@
/*
* Copyright (c) 2002-2018, 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.org.jline.terminal;
public class Size {
private int rows;
private int cols;
public Size() {
}
public Size(int columns, int rows) {
this();
setColumns(columns);
setRows(rows);
}
public int getColumns() {
return cols;
}
public void setColumns(int columns) {
cols = (short) columns;
}
public int getRows() {
return rows;
}
public void setRows(int rows) {
this.rows = (short) rows;
}
/**
* A cursor position combines a row number with a column position.
* <p>
* Note each row has {@code col+1} different column positions,
* including the right margin.
* </p>
*
* @param col the new column
* @param row the new row
* @return the cursor position
*/
public int cursorPos(int row, int col) {
return row * (cols+1) + col;
}
public void copy(Size size) {
setColumns(size.getColumns());
setRows(size.getRows());
}
@Override
public boolean equals(Object o) {
if (o instanceof Size) {
Size size = (Size) o;
return rows == size.rows && cols == size.cols;
} else {
return false;
}
}
@Override
public int hashCode() {
return rows * 31 + cols;
}
@Override
public String toString() {
return "Size[" + "cols=" + cols + ", rows=" + rows + ']';
}
}

View file

@ -0,0 +1,310 @@
/*
* Copyright (c) 2002-2018, 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.org.jline.terminal;
import java.io.Closeable;
import java.io.Flushable;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
import jdk.internal.org.jline.terminal.impl.NativeSignalHandler;
import jdk.internal.org.jline.utils.InfoCmp.Capability;
import jdk.internal.org.jline.utils.NonBlockingReader;
/**
* A terminal representing a virtual terminal on the computer.
*
* Terminals should be closed by calling the {@link #close()} method
* in order to restore their original state.
*/
public interface Terminal extends Closeable, Flushable {
/**
* Type used for dumb terminals.
*/
String TYPE_DUMB = "dumb";
String TYPE_DUMB_COLOR = "dumb-color";
String getName();
//
// Signal support
//
enum Signal {
INT,
QUIT,
TSTP,
CONT,
INFO,
WINCH
}
interface SignalHandler {
SignalHandler SIG_DFL = NativeSignalHandler.SIG_DFL;
SignalHandler SIG_IGN = NativeSignalHandler.SIG_IGN;
void handle(Signal signal);
}
SignalHandler handle(Signal signal, SignalHandler handler);
void raise(Signal signal);
//
// Input / output
//
/**
* Retrieve the <code>Reader</code> for this terminal.
* This is the standard way to read input from this terminal.
* The reader is non blocking.
*
* @return The non blocking reader
*/
NonBlockingReader reader();
/**
* Retrieve the <code>Writer</code> for this terminal.
* This is the standard way to write to this terminal.
*
* @return The writer
*/
PrintWriter writer();
/**
* Returns the {@link Charset} that should be used to encode characters
* for {@link #input()} and {@link #output()}.
*
* @return The terminal encoding
*/
Charset encoding();
/**
* Retrieve the input stream for this terminal.
* In some rare cases, there may be a need to access the
* terminal input stream directly. In the usual cases,
* use the {@link #reader()} instead.
*
* @return The input stream
*
* @see #reader()
*/
InputStream input();
/**
* Retrieve the output stream for this terminal.
* In some rare cases, there may be a need to access the
* terminal output stream directly. In the usual cases,
* use the {@link #writer()} instead.
*
* @return The output stream
*
* @see #writer();
*/
OutputStream output();
//
// Input control
//
/**
* Whether this terminal supports {@link #pause()} and {@link #resume()} calls.
*
* @return whether this terminal supports {@link #pause()} and {@link #resume()} calls.
* @see #paused()
* @see #pause()
* @see #resume()
*/
boolean canPauseResume();
/**
* Stop reading the input stream.
*
* @see #resume()
* @see #paused()
*/
void pause();
/**
* Stop reading the input stream and optionally wait for the underlying threads to finish.
*
* @param wait <code>true</code> to wait until the terminal is actually paused
* @throws InterruptedException if the call has been interrupted
*/
void pause(boolean wait) throws InterruptedException;
/**
* Resume reading the input stream.
*
* @see #pause()
* @see #paused()
*/
void resume();
/**
* Check whether the terminal is currently reading the input stream or not.
* In order to process signal as quickly as possible, the terminal need to read
* the input stream and buffer it internally so that it can detect specific
* characters in the input stream (Ctrl+C, Ctrl+D, etc...) and raise the
* appropriate signals.
* However, there are some cases where this processing should be disabled, for
* example when handing the terminal control to a subprocess.
*
* @return whether the terminal is currently reading the input stream or not
*
* @see #pause()
* @see #resume()
*/
boolean paused();
//
// Pty settings
//
Attributes enterRawMode();
boolean echo();
boolean echo(boolean echo);
Attributes getAttributes();
void setAttributes(Attributes attr);
Size getSize();
void setSize(Size size);
default int getWidth() {
return getSize().getColumns();
}
default int getHeight() {
return getSize().getRows();
}
void flush();
//
// Infocmp capabilities
//
String getType();
boolean puts(Capability capability, Object... params);
boolean getBooleanCapability(Capability capability);
Integer getNumericCapability(Capability capability);
String getStringCapability(Capability capability);
//
// Cursor support
//
/**
* Query the terminal to report the cursor position.
*
* As the response is read from the input stream, some
* characters may be read before the cursor position is actually
* read. Those characters can be given back using
* <code>org.jline.keymap.BindingReader#runMacro(String)</code>
*
* @param discarded a consumer receiving discarded characters
* @return <code>null</code> if cursor position reporting
* is not supported or a valid cursor position
*/
Cursor getCursorPosition(IntConsumer discarded);
//
// Mouse support
//
enum MouseTracking {
/**
* Disable mouse tracking
*/
Off,
/**
* Track button press and release.
*/
Normal,
/**
* Also report button-motion events. Mouse movements are reported if the mouse pointer
* has moved to a different character cell.
*/
Button,
/**
* Report all motions events, even if no mouse button is down.
*/
Any
}
/**
* Returns <code>true</code> if the terminal has support for mouse.
* @return whether mouse is supported by the terminal
* @see #trackMouse(MouseTracking)
*/
boolean hasMouseSupport();
/**
* Change the mouse tracking mouse.
* To start mouse tracking, this method must be called with a valid mouse tracking mode.
* Mouse events will be reported by writing the {@link Capability#key_mouse} to the input stream.
* When this character sequence is detected, the {@link #readMouseEvent()} method can be
* called to actually read the corresponding mouse event.
*
* @param tracking the mouse tracking mode
* @return <code>true</code> if mouse tracking is supported
*/
boolean trackMouse(MouseTracking tracking);
/**
* Read a MouseEvent from the terminal input stream.
* Such an event must have been detected by scanning the terminal's {@link Capability#key_mouse}
* in the stream immediately before reading the event.
*
* @return the decoded mouse event.
* @see #trackMouse(MouseTracking)
*/
MouseEvent readMouseEvent();
/**
* Read a MouseEvent from the given input stream.
*
* @param reader the input supplier
* @return the decoded mouse event
*/
MouseEvent readMouseEvent(IntSupplier reader);
/**
* Returns <code>true</code> if the terminal has support for focus tracking.
* @return whether focus tracking is supported by the terminal
* @see #trackFocus(boolean)
*/
boolean hasFocusSupport();
/**
* Enable or disable focus tracking mode.
* When focus tracking has been activated, each time the terminal grabs the focus,
* the string "\33[I" will be sent to the input stream and each time the focus is lost,
* the string "\33[O" will be sent to the input stream.
*
* @param tracking whether the focus tracking mode should be enabled or not
* @return <code>true</code> if focus tracking is supported
*/
boolean trackFocus(boolean tracking);
}

View file

@ -0,0 +1,474 @@
/*
* Copyright (c) 2002-2018, 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.org.jline.terminal;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.function.Function;
import jdk.internal.org.jline.terminal.impl.AbstractPosixTerminal;
import jdk.internal.org.jline.terminal.impl.DumbTerminal;
import jdk.internal.org.jline.terminal.impl.ExecPty;
import jdk.internal.org.jline.terminal.impl.ExternalTerminal;
import jdk.internal.org.jline.terminal.impl.PosixPtyTerminal;
import jdk.internal.org.jline.terminal.impl.PosixSysTerminal;
import jdk.internal.org.jline.terminal.spi.JansiSupport;
import jdk.internal.org.jline.terminal.spi.JnaSupport;
import jdk.internal.org.jline.terminal.spi.Pty;
import jdk.internal.org.jline.utils.Log;
import jdk.internal.org.jline.utils.OSUtils;
import static jdk.internal.org.jline.terminal.impl.AbstractWindowsTerminal.TYPE_WINDOWS;
import static jdk.internal.org.jline.terminal.impl.AbstractWindowsTerminal.TYPE_WINDOWS_256_COLOR;
/**
* Builder class to create terminals.
*/
public final class TerminalBuilder {
//
// System properties
//
public static final String PROP_ENCODING = "org.jline.terminal.encoding";
public static final String PROP_CODEPAGE = "org.jline.terminal.codepage";
public static final String PROP_TYPE = "org.jline.terminal.type";
public static final String PROP_JNA = "org.jline.terminal.jna";
public static final String PROP_JANSI = "org.jline.terminal.jansi";
public static final String PROP_EXEC = "org.jline.terminal.exec";
public static final String PROP_DUMB = "org.jline.terminal.dumb";
public static final String PROP_DUMB_COLOR = "org.jline.terminal.dumb.color";
//
// Other system properties controlling various jline parts
//
public static final String PROP_NON_BLOCKING_READS = "org.jline.terminal.pty.nonBlockingReads";
public static final String PROP_COLOR_DISTANCE = "org.jline.utils.colorDistance";
public static final String PROP_DISABLE_ALTERNATE_CHARSET = "org.jline.utils.disableAlternateCharset";
/**
* Returns the default system terminal.
* Terminals should be closed properly using the {@link Terminal#close()}
* method in order to restore the original terminal state.
*
* <p>
* This call is equivalent to:
* <code>builder().build()</code>
* </p>
*
* @return the default system terminal
* @throws IOException if an error occurs
*/
public static Terminal terminal() throws IOException {
return builder().build();
}
/**
* Creates a new terminal builder instance.
*
* @return a builder
*/
public static TerminalBuilder builder() {
return new TerminalBuilder();
}
private String name;
private InputStream in;
private OutputStream out;
private String type;
private Charset encoding;
private int codepage;
private Boolean system;
private Boolean jna;
private Boolean jansi;
private Boolean exec;
private Boolean dumb;
private Attributes attributes;
private Size size;
private boolean nativeSignals = false;
private Terminal.SignalHandler signalHandler = Terminal.SignalHandler.SIG_DFL;
private boolean paused = false;
private Function<InputStream, InputStream> inputStreamWrapper = in -> in;
private TerminalBuilder() {
}
public TerminalBuilder name(String name) {
this.name = name;
return this;
}
public TerminalBuilder streams(InputStream in, OutputStream out) {
this.in = in;
this.out = out;
return this;
}
public TerminalBuilder system(boolean system) {
this.system = system;
return this;
}
public TerminalBuilder jna(boolean jna) {
this.jna = jna;
return this;
}
public TerminalBuilder jansi(boolean jansi) {
this.jansi = jansi;
return this;
}
public TerminalBuilder exec(boolean exec) {
this.exec = exec;
return this;
}
public TerminalBuilder dumb(boolean dumb) {
this.dumb = dumb;
return this;
}
public TerminalBuilder type(String type) {
this.type = type;
return this;
}
/**
* Set the encoding to use for reading/writing from the console.
* If {@code null} (the default value), JLine will automatically select
* a {@link Charset}, usually the default system encoding. However,
* on some platforms (e.g. Windows) it may use a different one depending
* on the {@link Terminal} implementation.
*
* <p>Use {@link Terminal#encoding()} to get the {@link Charset} that
* should be used for a {@link Terminal}.</p>
*
* @param encoding The encoding to use or null to automatically select one
* @return The builder
* @throws UnsupportedCharsetException If the given encoding is not supported
* @see Terminal#encoding()
*/
public TerminalBuilder encoding(String encoding) throws UnsupportedCharsetException {
return encoding(encoding != null ? Charset.forName(encoding) : null);
}
/**
* Set the {@link Charset} to use for reading/writing from the console.
* If {@code null} (the default value), JLine will automatically select
* a {@link Charset}, usually the default system encoding. However,
* on some platforms (e.g. Windows) it may use a different one depending
* on the {@link Terminal} implementation.
*
* <p>Use {@link Terminal#encoding()} to get the {@link Charset} that
* should be used to read/write from a {@link Terminal}.</p>
*
* @param encoding The encoding to use or null to automatically select one
* @return The builder
* @see Terminal#encoding()
*/
public TerminalBuilder encoding(Charset encoding) {
this.encoding = encoding;
return this;
}
/**
* @param codepage the codepage
* @return The builder
* @deprecated JLine now writes Unicode output independently from the selected
* code page. Using this option will only make it emulate the selected code
* page for {@link Terminal#input()} and {@link Terminal#output()}.
*/
@Deprecated
public TerminalBuilder codepage(int codepage) {
this.codepage = codepage;
return this;
}
/**
* Attributes to use when creating a non system terminal,
* i.e. when the builder has been given the input and
* outut streams using the {@link #streams(InputStream, OutputStream)} method
* or when {@link #system(boolean)} has been explicitely called with
* <code>false</code>.
*
* @param attributes the attributes to use
* @return The builder
* @see #size(Size)
* @see #system(boolean)
*/
public TerminalBuilder attributes(Attributes attributes) {
this.attributes = attributes;
return this;
}
/**
* Initial size to use when creating a non system terminal,
* i.e. when the builder has been given the input and
* outut streams using the {@link #streams(InputStream, OutputStream)} method
* or when {@link #system(boolean)} has been explicitely called with
* <code>false</code>.
*
* @param size the initial size
* @return The builder
* @see #attributes(Attributes)
* @see #system(boolean)
*/
public TerminalBuilder size(Size size) {
this.size = size;
return this;
}
public TerminalBuilder nativeSignals(boolean nativeSignals) {
this.nativeSignals = nativeSignals;
return this;
}
public TerminalBuilder signalHandler(Terminal.SignalHandler signalHandler) {
this.signalHandler = signalHandler;
return this;
}
/**
* Initial paused state of the terminal (defaults to false).
* By default, the terminal is started, but in some cases,
* one might want to make sure the input stream is not consumed
* before needed, in which case the terminal needs to be created
* in a paused state.
* @param paused the initial paused state
* @return The builder
* @see Terminal#pause()
*/
public TerminalBuilder paused(boolean paused) {
this.paused = paused;
return this;
}
public TerminalBuilder inputStreamWrapper(Function<InputStream, InputStream> wrapper) {
this.inputStreamWrapper = wrapper;
return this;
}
public Terminal build() throws IOException {
Terminal terminal = doBuild();
Log.debug(() -> "Using terminal " + terminal.getClass().getSimpleName());
if (terminal instanceof AbstractPosixTerminal) {
Log.debug(() -> "Using pty " + ((AbstractPosixTerminal) terminal).getPty().getClass().getSimpleName());
}
return terminal;
}
private Terminal doBuild() throws IOException {
String name = this.name;
if (name == null) {
name = "JLine terminal";
}
Charset encoding = this.encoding;
if (encoding == null) {
String charsetName = System.getProperty(PROP_ENCODING);
if (charsetName != null && Charset.isSupported(charsetName)) {
encoding = Charset.forName(charsetName);
}
}
int codepage = this.codepage;
if (codepage <= 0) {
String str = System.getProperty(PROP_CODEPAGE);
if (str != null) {
codepage = Integer.parseInt(str);
}
}
String type = this.type;
if (type == null) {
type = System.getProperty(PROP_TYPE);
}
if (type == null) {
type = System.getenv("TERM");
}
Boolean jna = this.jna;
if (jna == null) {
jna = getBoolean(PROP_JNA, true);
}
Boolean jansi = this.jansi;
if (jansi == null) {
jansi = getBoolean(PROP_JANSI, true);
}
Boolean exec = this.exec;
if (exec == null) {
exec = getBoolean(PROP_EXEC, true);
}
Boolean dumb = this.dumb;
if (dumb == null) {
dumb = getBoolean(PROP_DUMB, null);
}
if ((system != null && system) || (system == null && in == null && out == null)) {
if (attributes != null || size != null) {
Log.warn("Attributes and size fields are ignored when creating a system terminal");
}
IllegalStateException exception = new IllegalStateException("Unable to create a system terminal");
if (OSUtils.IS_WINDOWS) {
boolean cygwinTerm = "cygwin".equals(System.getenv("TERM"));
boolean ansiPassThrough = OSUtils.IS_CONEMU;
//
// Cygwin support
//
if ((OSUtils.IS_CYGWIN || OSUtils.IS_MSYSTEM) && exec && !cygwinTerm) {
try {
Pty pty = ExecPty.current();
// Cygwin defaults to XTERM, but actually supports 256 colors,
// so if the value comes from the environment, change it to xterm-256color
if ("xterm".equals(type) && this.type == null && System.getProperty(PROP_TYPE) == null) {
type = "xterm-256color";
}
return new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
} catch (IOException e) {
// Ignore if not a tty
Log.debug("Error creating EXEC based terminal: ", e.getMessage(), e);
exception.addSuppressed(e);
}
}
if (jna) {
try {
return load(JnaSupport.class).winSysTerminal(name, type, ansiPassThrough, encoding, codepage, nativeSignals, signalHandler, paused, inputStreamWrapper);
} catch (Throwable t) {
Log.debug("Error creating JNA based terminal: ", t.getMessage(), t);
exception.addSuppressed(t);
}
}
if (jansi) {
try {
return load(JansiSupport.class).winSysTerminal(name, type, ansiPassThrough, encoding, codepage, nativeSignals, signalHandler, paused);
} catch (Throwable t) {
Log.debug("Error creating JANSI based terminal: ", t.getMessage(), t);
exception.addSuppressed(t);
}
}
} else {
if (jna) {
try {
Pty pty = load(JnaSupport.class).current();
return new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
} catch (Throwable t) {
// ignore
Log.debug("Error creating JNA based terminal: ", t.getMessage(), t);
exception.addSuppressed(t);
}
}
if (jansi) {
try {
Pty pty = load(JansiSupport.class).current();
return new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
} catch (Throwable t) {
Log.debug("Error creating JANSI based terminal: ", t.getMessage(), t);
exception.addSuppressed(t);
}
}
if (exec) {
try {
Pty pty = ExecPty.current();
return new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
} catch (Throwable t) {
// Ignore if not a tty
Log.debug("Error creating EXEC based terminal: ", t.getMessage(), t);
exception.addSuppressed(t);
}
}
}
if (dumb == null || dumb) {
// forced colored dumb terminal
boolean color = getBoolean(PROP_DUMB_COLOR, false);
// detect emacs using the env variable
if (!color) {
color = System.getenv("INSIDE_EMACS") != null;
}
// detect Intellij Idea
if (!color) {
String command = getParentProcessCommand();
color = command != null && command.contains("idea");
}
if (!color && dumb == null) {
if (Log.isDebugEnabled()) {
Log.warn("Creating a dumb terminal", exception);
} else {
Log.warn("Unable to create a system terminal, creating a dumb terminal (enable debug logging for more information)");
}
}
return new DumbTerminal(name, color ? Terminal.TYPE_DUMB_COLOR : Terminal.TYPE_DUMB,
new FileInputStream(FileDescriptor.in),
new FileOutputStream(FileDescriptor.out),
encoding, signalHandler);
} else {
throw exception;
}
} else {
if (jna) {
try {
Pty pty = load(JnaSupport.class).open(attributes, size);
return new PosixPtyTerminal(name, type, pty, in, out, encoding, signalHandler, paused);
} catch (Throwable t) {
Log.debug("Error creating JNA based terminal: ", t.getMessage(), t);
}
}
if (jansi) {
try {
Pty pty = load(JansiSupport.class).open(attributes, size);
return new PosixPtyTerminal(name, type, pty, in, out, encoding, signalHandler, paused);
} catch (Throwable t) {
Log.debug("Error creating JANSI based terminal: ", t.getMessage(), t);
}
}
Terminal terminal = new ExternalTerminal(name, type, in, out, encoding, signalHandler, paused);
if (attributes != null) {
terminal.setAttributes(attributes);
}
if (size != null) {
terminal.setSize(size);
}
return terminal;
}
}
private static String getParentProcessCommand() {
try {
Class<?> phClass = Class.forName("java.lang.ProcessHandle");
Object current = phClass.getMethod("current").invoke(null);
Object parent = ((Optional<?>) phClass.getMethod("parent").invoke(current)).orElse(null);
Method infoMethod = phClass.getMethod("info");
Object info = infoMethod.invoke(parent);
Object command = ((Optional<?>) infoMethod.getReturnType().getMethod("command").invoke(info)).orElse(null);
return (String) command;
} catch (Throwable t) {
return null;
}
}
private static Boolean getBoolean(String name, Boolean def) {
try {
String str = System.getProperty(name);
if (str != null) {
return Boolean.parseBoolean(str);
}
} catch (IllegalArgumentException | NullPointerException e) {
}
return def;
}
private <S> S load(Class<S> clazz) {
return ServiceLoader.load(clazz, clazz.getClassLoader()).iterator().next();
}
}

View file

@ -0,0 +1,85 @@
/*
* Copyright (c) 2002-2016, 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.org.jline.terminal.impl;
import java.io.IOError;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Objects;
import java.util.function.IntConsumer;
import jdk.internal.org.jline.terminal.Attributes;
import jdk.internal.org.jline.terminal.Cursor;
import jdk.internal.org.jline.terminal.Size;
import jdk.internal.org.jline.terminal.spi.Pty;
public abstract class AbstractPosixTerminal extends AbstractTerminal {
protected final Pty pty;
protected final Attributes originalAttributes;
public AbstractPosixTerminal(String name, String type, Pty pty) throws IOException {
this(name, type, pty, null, SignalHandler.SIG_DFL);
}
public AbstractPosixTerminal(String name, String type, Pty pty, Charset encoding, SignalHandler signalHandler) throws IOException {
super(name, type, encoding, signalHandler);
Objects.requireNonNull(pty);
this.pty = pty;
this.originalAttributes = this.pty.getAttr();
}
public Pty getPty() {
return pty;
}
public Attributes getAttributes() {
try {
return pty.getAttr();
} catch (IOException e) {
throw new IOError(e);
}
}
public void setAttributes(Attributes attr) {
try {
pty.setAttr(attr);
} catch (IOException e) {
throw new IOError(e);
}
}
public Size getSize() {
try {
return pty.getSize();
} catch (IOException e) {
throw new IOError(e);
}
}
public void setSize(Size size) {
try {
pty.setSize(size);
} catch (IOException e) {
throw new IOError(e);
}
}
public void close() throws IOException {
super.close();
pty.setAttr(originalAttributes);
pty.close();
}
@Override
public Cursor getCursorPosition(IntConsumer discarded) {
return CursorSupport.getCursorPosition(this, discarded);
}
}

View file

@ -0,0 +1,97 @@
package jdk.internal.org.jline.terminal.impl;
import jdk.internal.org.jline.terminal.Attributes;
import jdk.internal.org.jline.terminal.spi.Pty;
import jdk.internal.org.jline.utils.NonBlockingInputStream;
import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import static jdk.internal.org.jline.terminal.TerminalBuilder.PROP_NON_BLOCKING_READS;
public abstract class AbstractPty implements Pty {
private Attributes current;
@Override
public void setAttr(Attributes attr) throws IOException {
current = new Attributes(attr);
doSetAttr(attr);
}
@Override
public InputStream getSlaveInput() throws IOException {
InputStream si = doGetSlaveInput();
if (Boolean.parseBoolean(System.getProperty(PROP_NON_BLOCKING_READS, "true"))) {
return new PtyInputStream(si);
} else {
return si;
}
}
protected abstract void doSetAttr(Attributes attr) throws IOException;
protected abstract InputStream doGetSlaveInput() throws IOException;
protected void checkInterrupted() throws InterruptedIOException {
if (Thread.interrupted()) {
throw new InterruptedIOException();
}
}
class PtyInputStream extends NonBlockingInputStream {
final InputStream in;
int c = 0;
PtyInputStream(InputStream in) {
this.in = in;
}
@Override
public int read(long timeout, boolean isPeek) throws IOException {
checkInterrupted();
if (c != 0) {
int r = c;
if (!isPeek) {
c = 0;
}
return r;
} else {
setNonBlocking();
long start = System.currentTimeMillis();
while (true) {
int r = in.read();
if (r >= 0) {
if (isPeek) {
c = r;
}
return r;
}
checkInterrupted();
long cur = System.currentTimeMillis();
if (timeout > 0 && cur - start > timeout) {
return NonBlockingInputStream.READ_EXPIRED;
}
}
}
}
private void setNonBlocking() {
if (current == null
|| current.getControlChar(Attributes.ControlChar.VMIN) != 0
|| current.getControlChar(Attributes.ControlChar.VTIME) != 1) {
try {
Attributes attr = getAttr();
attr.setControlChar(Attributes.ControlChar.VMIN, 0);
attr.setControlChar(Attributes.ControlChar.VTIME, 1);
setAttr(attr);
} catch (IOException e) {
throw new IOError(e);
}
}
}
}
}

View file

@ -0,0 +1,271 @@
/*
* Copyright (c) 2002-2018, 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.org.jline.terminal.impl;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.nio.charset.Charset;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
import jdk.internal.org.jline.terminal.Attributes;
import jdk.internal.org.jline.terminal.Attributes.ControlChar;
import jdk.internal.org.jline.terminal.Attributes.InputFlag;
import jdk.internal.org.jline.terminal.Attributes.LocalFlag;
import jdk.internal.org.jline.terminal.Cursor;
import jdk.internal.org.jline.terminal.MouseEvent;
import jdk.internal.org.jline.terminal.Terminal;
import jdk.internal.org.jline.utils.Curses;
import jdk.internal.org.jline.utils.InfoCmp;
import jdk.internal.org.jline.utils.InfoCmp.Capability;
import jdk.internal.org.jline.utils.Log;
import jdk.internal.org.jline.utils.Status;
public abstract class AbstractTerminal implements Terminal {
protected final String name;
protected final String type;
protected final Charset encoding;
protected final Map<Signal, SignalHandler> handlers = new HashMap<>();
protected final Set<Capability> bools = new HashSet<>();
protected final Map<Capability, Integer> ints = new HashMap<>();
protected final Map<Capability, String> strings = new HashMap<>();
protected Status status;
public AbstractTerminal(String name, String type) throws IOException {
this(name, type, null, SignalHandler.SIG_DFL);
}
public AbstractTerminal(String name, String type, Charset encoding, SignalHandler signalHandler) throws IOException {
this.name = name;
this.type = type;
this.encoding = encoding != null ? encoding : Charset.defaultCharset();
for (Signal signal : Signal.values()) {
handlers.put(signal, signalHandler);
}
}
public Status getStatus() {
return getStatus(true);
}
public Status getStatus(boolean create) {
if (status == null && create) {
status = new Status(this);
}
return status;
}
public SignalHandler handle(Signal signal, SignalHandler handler) {
Objects.requireNonNull(signal);
Objects.requireNonNull(handler);
return handlers.put(signal, handler);
}
public void raise(Signal signal) {
Objects.requireNonNull(signal);
SignalHandler handler = handlers.get(signal);
if (handler != SignalHandler.SIG_DFL && handler != SignalHandler.SIG_IGN) {
handler.handle(signal);
}
if (status != null && signal == Signal.WINCH) {
status.resize();
}
}
public void close() throws IOException {
if (status != null) {
status.update(null);
flush();
}
}
protected void echoSignal(Signal signal) {
ControlChar cc = null;
switch (signal) {
case INT:
cc = ControlChar.VINTR;
break;
case QUIT:
cc = ControlChar.VQUIT;
break;
case TSTP:
cc = ControlChar.VSUSP;
break;
}
if (cc != null) {
int vcc = getAttributes().getControlChar(cc);
if (vcc > 0 && vcc < 32) {
writer().write(new char[]{'^', (char) (vcc + '@')}, 0, 2);
}
}
}
public Attributes enterRawMode() {
Attributes prvAttr = getAttributes();
Attributes newAttr = new Attributes(prvAttr);
newAttr.setLocalFlags(EnumSet.of(LocalFlag.ICANON, LocalFlag.ECHO, LocalFlag.IEXTEN), false);
newAttr.setInputFlags(EnumSet.of(InputFlag.IXON, InputFlag.ICRNL, InputFlag.INLCR), false);
newAttr.setControlChar(ControlChar.VMIN, 0);
newAttr.setControlChar(ControlChar.VTIME, 1);
setAttributes(newAttr);
return prvAttr;
}
public boolean echo() {
return getAttributes().getLocalFlag(LocalFlag.ECHO);
}
public boolean echo(boolean echo) {
Attributes attr = getAttributes();
boolean prev = attr.getLocalFlag(LocalFlag.ECHO);
if (prev != echo) {
attr.setLocalFlag(LocalFlag.ECHO, echo);
setAttributes(attr);
}
return prev;
}
public String getName() {
return name;
}
public String getType() {
return type;
}
public String getKind() {
return getClass().getSimpleName();
}
@Override
public Charset encoding() {
return this.encoding;
}
public void flush() {
writer().flush();
}
public boolean puts(Capability capability, Object... params) {
String str = getStringCapability(capability);
if (str == null) {
return false;
}
Curses.tputs(writer(), str, params);
return true;
}
public boolean getBooleanCapability(Capability capability) {
return bools.contains(capability);
}
public Integer getNumericCapability(Capability capability) {
return ints.get(capability);
}
public String getStringCapability(Capability capability) {
return strings.get(capability);
}
protected void parseInfoCmp() {
String capabilities = null;
if (type != null) {
try {
capabilities = InfoCmp.getInfoCmp(type);
} catch (Exception e) {
Log.warn("Unable to retrieve infocmp for type " + type, e);
}
}
if (capabilities == null) {
capabilities = InfoCmp.getLoadedInfoCmp("ansi");
}
InfoCmp.parseInfoCmp(capabilities, bools, ints, strings);
}
@Override
public Cursor getCursorPosition(IntConsumer discarded) {
return null;
}
private MouseEvent lastMouseEvent = new MouseEvent(
MouseEvent.Type.Moved, MouseEvent.Button.NoButton,
EnumSet.noneOf(MouseEvent.Modifier.class), 0, 0);
@Override
public boolean hasMouseSupport() {
return MouseSupport.hasMouseSupport(this);
}
@Override
public boolean trackMouse(MouseTracking tracking) {
return MouseSupport.trackMouse(this, tracking);
}
@Override
public MouseEvent readMouseEvent() {
return lastMouseEvent = MouseSupport.readMouse(this, lastMouseEvent);
}
@Override
public MouseEvent readMouseEvent(IntSupplier reader) {
return lastMouseEvent = MouseSupport.readMouse(reader, lastMouseEvent);
}
@Override
public boolean hasFocusSupport() {
return type != null && type.startsWith("xterm");
}
@Override
public boolean trackFocus(boolean tracking) {
if (hasFocusSupport()) {
writer().write(tracking ? "\033[?1004h" : "\033[?1004l");
writer().flush();
return true;
} else {
return false;
}
}
protected void checkInterrupted() throws InterruptedIOException {
if (Thread.interrupted()) {
throw new InterruptedIOException();
}
}
@Override
public boolean canPauseResume() {
return false;
}
@Override
public void pause() {
}
@Override
public void pause(boolean wait) throws InterruptedException {
}
@Override
public void resume() {
}
@Override
public boolean paused() {
return false;
}
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2002-2017, 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.org.jline.terminal.impl;
import java.io.IOException;
import java.io.Writer;
public abstract class AbstractWindowsConsoleWriter extends Writer {
protected abstract void writeConsole(char[] text, int len) throws IOException;
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
char[] text = cbuf;
if (off != 0) {
text = new char[len];
System.arraycopy(cbuf, off, text, 0, len);
}
synchronized (this.lock) {
writeConsole(text, len);
}
}
@Override
public void flush() {
}
@Override
public void close() {
}
}

View file

@ -0,0 +1,538 @@
/*
* Copyright (c) 2002-2018, 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.org.jline.terminal.impl;
import jdk.internal.org.jline.terminal.Attributes;
import jdk.internal.org.jline.terminal.Size;
import jdk.internal.org.jline.utils.Curses;
import jdk.internal.org.jline.utils.InfoCmp;
import jdk.internal.org.jline.utils.Log;
import jdk.internal.org.jline.utils.NonBlocking;
import jdk.internal.org.jline.utils.NonBlockingInputStream;
import jdk.internal.org.jline.utils.NonBlockingPumpReader;
import jdk.internal.org.jline.utils.NonBlockingReader;
import jdk.internal.org.jline.utils.ShutdownHooks;
import jdk.internal.org.jline.utils.Signals;
import jdk.internal.org.jline.utils.WriterOutputStream;
import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
* The AbstractWindowsTerminal is used as the base class for windows terminal.
* Due to windows limitations, mostly the missing support for ansi sequences,
* the only way to create a correct terminal is to use the windows api to set
* character attributes, move the cursor, erasing, etc...
*
* UTF-8 support is also lacking in windows and the code page supposed to
* emulate UTF-8 is a bit broken. In order to work around this broken
* code page, windows api WriteConsoleW is used directly. This means that
* the writer() becomes the primary output, while the output() is bridged
* to the writer() using a WriterOutputStream wrapper.
*/
public abstract class AbstractWindowsTerminal extends AbstractTerminal {
public static final String TYPE_WINDOWS = "windows";
public static final String TYPE_WINDOWS_256_COLOR = "windows-256color";
public static final String TYPE_WINDOWS_VTP = "windows-vtp";
public static final int ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
private static final int UTF8_CODE_PAGE = 65001;
protected static final int ENABLE_PROCESSED_INPUT = 0x0001;
protected static final int ENABLE_LINE_INPUT = 0x0002;
protected static final int ENABLE_ECHO_INPUT = 0x0004;
protected static final int ENABLE_WINDOW_INPUT = 0x0008;
protected static final int ENABLE_MOUSE_INPUT = 0x0010;
protected static final int ENABLE_INSERT_MODE = 0x0020;
protected static final int ENABLE_QUICK_EDIT_MODE = 0x0040;
protected final Writer slaveInputPipe;
protected final InputStream input;
protected final OutputStream output;
protected final NonBlockingReader reader;
protected final PrintWriter writer;
protected final Map<Signal, Object> nativeHandlers = new HashMap<>();
protected final ShutdownHooks.Task closer;
protected final Attributes attributes = new Attributes();
protected final int originalConsoleMode;
protected final Object lock = new Object();
protected boolean paused = true;
protected Thread pump;
protected MouseTracking tracking = MouseTracking.Off;
protected boolean focusTracking = false;
private volatile boolean closing;
public AbstractWindowsTerminal(Writer writer, String name, String type, Charset encoding, int codepage, boolean nativeSignals, SignalHandler signalHandler, Function<InputStream, InputStream> inputStreamWrapper) throws IOException {
super(name, type, selectCharset(encoding, codepage), signalHandler);
NonBlockingPumpReader reader = NonBlocking.nonBlockingPumpReader();
this.slaveInputPipe = reader.getWriter();
this.reader = reader;
this.input = inputStreamWrapper.apply(NonBlocking.nonBlockingStream(reader, encoding()));
this.writer = new PrintWriter(writer);
this.output = new WriterOutputStream(writer, encoding());
parseInfoCmp();
// Attributes
originalConsoleMode = getConsoleMode();
attributes.setLocalFlag(Attributes.LocalFlag.ISIG, true);
attributes.setControlChar(Attributes.ControlChar.VINTR, ctrl('C'));
attributes.setControlChar(Attributes.ControlChar.VEOF, ctrl('D'));
attributes.setControlChar(Attributes.ControlChar.VSUSP, ctrl('Z'));
// Handle signals
if (nativeSignals) {
for (final Signal signal : Signal.values()) {
if (signalHandler == SignalHandler.SIG_DFL) {
nativeHandlers.put(signal, Signals.registerDefault(signal.name()));
} else {
nativeHandlers.put(signal, Signals.register(signal.name(), () -> raise(signal)));
}
}
}
closer = this::close;
ShutdownHooks.add(closer);
// ConEMU extended fonts support
if (TYPE_WINDOWS_256_COLOR.equals(getType())
&& !Boolean.getBoolean("org.jline.terminal.conemu.disable-activate")) {
writer.write("\u001b[9999E");
writer.flush();
}
}
private static Charset selectCharset(Charset encoding, int codepage) {
if (encoding != null) {
return encoding;
}
if (codepage >= 0) {
return getCodepageCharset(codepage);
}
// Use UTF-8 as default
return StandardCharsets.UTF_8;
}
private static Charset getCodepageCharset(int codepage) {
//http://docs.oracle.com/javase/6/docs/technotes/guides/intl/encoding.doc.html
if (codepage == UTF8_CODE_PAGE) {
return StandardCharsets.UTF_8;
}
String charsetMS = "ms" + codepage;
if (Charset.isSupported(charsetMS)) {
return Charset.forName(charsetMS);
}
String charsetCP = "cp" + codepage;
if (Charset.isSupported(charsetCP)) {
return Charset.forName(charsetCP);
}
return Charset.defaultCharset();
}
@Override
public SignalHandler handle(Signal signal, SignalHandler handler) {
SignalHandler prev = super.handle(signal, handler);
if (prev != handler) {
if (handler == SignalHandler.SIG_DFL) {
Signals.registerDefault(signal.name());
} else {
Signals.register(signal.name(), () -> raise(signal));
}
}
return prev;
}
public NonBlockingReader reader() {
return reader;
}
public PrintWriter writer() {
return writer;
}
@Override
public InputStream input() {
return input;
}
@Override
public OutputStream output() {
return output;
}
public Attributes getAttributes() {
int mode = getConsoleMode();
if ((mode & ENABLE_ECHO_INPUT) != 0) {
attributes.setLocalFlag(Attributes.LocalFlag.ECHO, true);
}
if ((mode & ENABLE_LINE_INPUT) != 0) {
attributes.setLocalFlag(Attributes.LocalFlag.ICANON, true);
}
return new Attributes(attributes);
}
public void setAttributes(Attributes attr) {
attributes.copy(attr);
updateConsoleMode();
}
protected void updateConsoleMode() {
int mode = ENABLE_WINDOW_INPUT;
if (attributes.getLocalFlag(Attributes.LocalFlag.ECHO)) {
mode |= ENABLE_ECHO_INPUT;
}
if (attributes.getLocalFlag(Attributes.LocalFlag.ICANON)) {
mode |= ENABLE_LINE_INPUT;
}
if (tracking != MouseTracking.Off) {
mode |= ENABLE_MOUSE_INPUT;
}
setConsoleMode(mode);
}
protected int ctrl(char key) {
return (Character.toUpperCase(key) & 0x1f);
}
public void setSize(Size size) {
throw new UnsupportedOperationException("Can not resize windows terminal");
}
public void close() throws IOException {
super.close();
closing = true;
pump.interrupt();
ShutdownHooks.remove(closer);
for (Map.Entry<Signal, Object> entry : nativeHandlers.entrySet()) {
Signals.unregister(entry.getKey().name(), entry.getValue());
}
reader.close();
writer.close();
setConsoleMode(originalConsoleMode);
}
static final int SHIFT_FLAG = 0x01;
static final int ALT_FLAG = 0x02;
static final int CTRL_FLAG = 0x04;
static final int RIGHT_ALT_PRESSED = 0x0001;
static final int LEFT_ALT_PRESSED = 0x0002;
static final int RIGHT_CTRL_PRESSED = 0x0004;
static final int LEFT_CTRL_PRESSED = 0x0008;
static final int SHIFT_PRESSED = 0x0010;
static final int NUMLOCK_ON = 0x0020;
static final int SCROLLLOCK_ON = 0x0040;
static final int CAPSLOCK_ON = 0x0080;
protected void processKeyEvent(final boolean isKeyDown, final short virtualKeyCode, char ch, final int controlKeyState) throws IOException {
final boolean isCtrl = (controlKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED)) > 0;
final boolean isAlt = (controlKeyState & (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED)) > 0;
final boolean isShift = (controlKeyState & SHIFT_PRESSED) > 0;
// key down event
if (isKeyDown && ch != '\3') {
// 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
if (ch != 0
&& (controlKeyState & (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED | RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED | SHIFT_PRESSED))
== (RIGHT_ALT_PRESSED | LEFT_CTRL_PRESSED)) {
processInputChar(ch);
} else {
final String keySeq = getEscapeSequence(virtualKeyCode, (isCtrl ? CTRL_FLAG : 0) + (isAlt ? ALT_FLAG : 0) + (isShift ? SHIFT_FLAG : 0));
if (keySeq != null) {
for (char c : keySeq.toCharArray()) {
processInputChar(c);
}
return;
}
/* uchar value in Windows when CTRL is pressed:
* 1). Ctrl + <0x41 to 0x5e> : uchar=<keyCode> - 'A' + 1
* 2). Ctrl + Backspace(0x08) : uchar=0x7f
* 3). Ctrl + Enter(0x0d) : uchar=0x0a
* 4). Ctrl + Space(0x20) : uchar=0x20
* 5). Ctrl + <Other key> : uchar=0
* 6). Ctrl + Alt + <Any key> : uchar=0
*/
if (ch > 0) {
if (isAlt) {
processInputChar('\033');
}
if (isCtrl && ch != ' ' && ch != '\n' && ch != 0x7f) {
processInputChar((char) (ch == '?' ? 0x7f : Character.toUpperCase(ch) & 0x1f));
} else if (isCtrl && ch == '\n') {
//simulate Alt-Enter:
processInputChar('\033');
processInputChar('\r');
} else {
processInputChar(ch);
}
} else if (isCtrl) { //Handles the ctrl key events(uchar=0)
if (virtualKeyCode >= 'A' && virtualKeyCode <= 'Z') {
ch = (char) (virtualKeyCode - 0x40);
} else if (virtualKeyCode == 191) { //?
ch = 127;
}
if (ch > 0) {
if (isAlt) {
processInputChar('\033');
}
processInputChar(ch);
}
}
}
} else if (isKeyDown && ch == '\3') {
processInputChar('\3');
}
// key up event
else {
// support ALT+NumPad input method
if (virtualKeyCode == 0x12 /*VK_MENU ALT key*/ && ch > 0) {
processInputChar(ch); // no such combination in Windows
}
}
}
protected String getEscapeSequence(short keyCode, int keyState) {
// virtual keycodes: http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
// TODO: numpad keys, modifiers
String escapeSequence = null;
switch (keyCode) {
case 0x08: // VK_BACK BackSpace
escapeSequence = (keyState & ALT_FLAG) > 0 ? "\\E^H" : getRawSequence(InfoCmp.Capability.key_backspace);
break;
case 0x09:
escapeSequence = (keyState & SHIFT_FLAG) > 0 ? getRawSequence(InfoCmp.Capability.key_btab) : null;
break;
case 0x21: // VK_PRIOR PageUp
escapeSequence = getRawSequence(InfoCmp.Capability.key_ppage);
break;
case 0x22: // VK_NEXT PageDown
escapeSequence = getRawSequence(InfoCmp.Capability.key_npage);
break;
case 0x23: // VK_END
escapeSequence = keyState > 0 ? "\\E[1;%p1%dF" : getRawSequence(InfoCmp.Capability.key_end);
break;
case 0x24: // VK_HOME
escapeSequence = keyState > 0 ? "\\E[1;%p1%dH" : getRawSequence(InfoCmp.Capability.key_home);
break;
case 0x25: // VK_LEFT
escapeSequence = keyState > 0 ? "\\E[1;%p1%dD" : getRawSequence(InfoCmp.Capability.key_left);
break;
case 0x26: // VK_UP
escapeSequence = keyState > 0 ? "\\E[1;%p1%dA" : getRawSequence(InfoCmp.Capability.key_up);
break;
case 0x27: // VK_RIGHT
escapeSequence = keyState > 0 ? "\\E[1;%p1%dC" : getRawSequence(InfoCmp.Capability.key_right);
break;
case 0x28: // VK_DOWN
escapeSequence = keyState > 0 ? "\\E[1;%p1%dB" : getRawSequence(InfoCmp.Capability.key_down);
break;
case 0x2D: // VK_INSERT
escapeSequence = getRawSequence(InfoCmp.Capability.key_ic);
break;
case 0x2E: // VK_DELETE
escapeSequence = getRawSequence(InfoCmp.Capability.key_dc);
break;
case 0x70: // VK_F1
escapeSequence = keyState > 0 ? "\\E[1;%p1%dP" : getRawSequence(InfoCmp.Capability.key_f1);
break;
case 0x71: // VK_F2
escapeSequence = keyState > 0 ? "\\E[1;%p1%dQ" : getRawSequence(InfoCmp.Capability.key_f2);
break;
case 0x72: // VK_F3
escapeSequence = keyState > 0 ? "\\E[1;%p1%dR" : getRawSequence(InfoCmp.Capability.key_f3);
break;
case 0x73: // VK_F4
escapeSequence = keyState > 0 ? "\\E[1;%p1%dS" : getRawSequence(InfoCmp.Capability.key_f4);
break;
case 0x74: // VK_F5
escapeSequence = keyState > 0 ? "\\E[15;%p1%d~" : getRawSequence(InfoCmp.Capability.key_f5);
break;
case 0x75: // VK_F6
escapeSequence = keyState > 0 ? "\\E[17;%p1%d~" : getRawSequence(InfoCmp.Capability.key_f6);
break;
case 0x76: // VK_F7
escapeSequence = keyState > 0 ? "\\E[18;%p1%d~" : getRawSequence(InfoCmp.Capability.key_f7);
break;
case 0x77: // VK_F8
escapeSequence = keyState > 0 ? "\\E[19;%p1%d~" : getRawSequence(InfoCmp.Capability.key_f8);
break;
case 0x78: // VK_F9
escapeSequence = keyState > 0 ? "\\E[20;%p1%d~" : getRawSequence(InfoCmp.Capability.key_f9);
break;
case 0x79: // VK_F10
escapeSequence = keyState > 0 ? "\\E[21;%p1%d~" : getRawSequence(InfoCmp.Capability.key_f10);
break;
case 0x7A: // VK_F11
escapeSequence = keyState > 0 ? "\\E[23;%p1%d~" : getRawSequence(InfoCmp.Capability.key_f11);
break;
case 0x7B: // VK_F12
escapeSequence = keyState > 0 ? "\\E[24;%p1%d~" : getRawSequence(InfoCmp.Capability.key_f12);
break;
case 0x5D: // VK_CLOSE_BRACKET(Menu key)
case 0x5B: // VK_OPEN_BRACKET(Window key)
default:
return null;
}
return Curses.tputs(escapeSequence, keyState + 1);
}
protected String getRawSequence(InfoCmp.Capability cap) {
return strings.get(cap);
}
@Override
public boolean hasFocusSupport() {
return true;
}
@Override
public boolean trackFocus(boolean tracking) {
focusTracking = tracking;
return true;
}
@Override
public boolean canPauseResume() {
return true;
}
@Override
public void pause() {
synchronized (lock) {
paused = true;
}
}
@Override
public void pause(boolean wait) throws InterruptedException {
Thread p;
synchronized (lock) {
paused = true;
p = pump;
}
if (p != null) {
p.interrupt();
p.join();
}
}
@Override
public void resume() {
synchronized (lock) {
paused = false;
if (pump == null) {
pump = new Thread(this::pump, "WindowsStreamPump");
pump.setDaemon(true);
pump.start();
}
}
}
@Override
public boolean paused() {
synchronized (lock) {
return paused;
}
}
protected void pump() {
try {
while (!closing) {
synchronized (lock) {
if (paused) {
pump = null;
break;
}
}
if (processConsoleInput()) {
slaveInputPipe.flush();
}
}
} catch (IOException e) {
if (!closing) {
Log.warn("Error in WindowsStreamPump", e);
try {
close();
} catch (IOException e1) {
Log.warn("Error closing terminal", e);
}
}
} finally {
synchronized (lock) {
pump = null;
}
}
}
public void processInputChar(char c) throws IOException {
if (attributes.getLocalFlag(Attributes.LocalFlag.ISIG)) {
if (c == attributes.getControlChar(Attributes.ControlChar.VINTR)) {
raise(Signal.INT);
return;
} else if (c == attributes.getControlChar(Attributes.ControlChar.VQUIT)) {
raise(Signal.QUIT);
return;
} else if (c == attributes.getControlChar(Attributes.ControlChar.VSUSP)) {
raise(Signal.TSTP);
return;
} else if (c == attributes.getControlChar(Attributes.ControlChar.VSTATUS)) {
raise(Signal.INFO);
}
}
if (c == '\r') {
if (attributes.getInputFlag(Attributes.InputFlag.IGNCR)) {
return;
}
if (attributes.getInputFlag(Attributes.InputFlag.ICRNL)) {
c = '\n';
}
} else if (c == '\n' && attributes.getInputFlag(Attributes.InputFlag.INLCR)) {
c = '\r';
}
// if (attributes.getLocalFlag(Attributes.LocalFlag.ECHO)) {
// processOutputByte(c);
// masterOutput.flush();
// }
slaveInputPipe.write(c);
}
@Override
public boolean trackMouse(MouseTracking tracking) {
this.tracking = tracking;
updateConsoleMode();
return true;
}
protected abstract int getConsoleOutputCP();
protected abstract int getConsoleMode();
protected abstract void setConsoleMode(int mode);
/**
* Read a single input event from the input buffer and process it.
*
* @return true if new input was generated from the event
* @throws IOException if anything wrong happens
*/
protected abstract boolean processConsoleInput() throws IOException;
}

View file

@ -0,0 +1,109 @@
/*
* Copyright (c) 2002-2016, 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.org.jline.terminal.impl;
import jdk.internal.org.jline.terminal.Cursor;
import jdk.internal.org.jline.terminal.Terminal;
import jdk.internal.org.jline.utils.Curses;
import jdk.internal.org.jline.utils.InfoCmp;
import java.io.IOError;
import java.io.IOException;
import java.util.function.IntConsumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CursorSupport {
public static Cursor getCursorPosition(Terminal terminal, IntConsumer discarded) {
try {
String u6 = terminal.getStringCapability(InfoCmp.Capability.user6);
String u7 = terminal.getStringCapability(InfoCmp.Capability.user7);
if (u6 == null || u7 == null) {
return null;
}
// Prepare parser
boolean inc1 = false;
StringBuilder patb = new StringBuilder();
int index = 0;
while (index < u6.length()) {
char ch;
switch (ch = u6.charAt(index++)) {
case '\\':
switch (u6.charAt(index++)) {
case 'e':
case 'E':
patb.append("\\x1b");
break;
default:
throw new IllegalArgumentException();
}
break;
case '%':
ch = u6.charAt(index++);
switch (ch) {
case '%':
patb.append('%');
break;
case 'i':
inc1 = true;
break;
case 'd':
patb.append("([0-9]+)");
break;
default:
throw new IllegalArgumentException();
}
break;
default:
switch (ch) {
case '[':
patb.append('\\');
break;
}
patb.append(ch);
break;
}
}
Pattern pattern = Pattern.compile(patb.toString());
// Output cursor position request
Curses.tputs(terminal.writer(), u7);
terminal.flush();
StringBuilder sb = new StringBuilder();
int start = 0;
while (true) {
int c = terminal.reader().read();
if (c < 0) {
return null;
}
sb.append((char) c);
Matcher matcher = pattern.matcher(sb.substring(start));
if (matcher.matches()) {
int y = Integer.parseInt(matcher.group(1));
int x = Integer.parseInt(matcher.group(2));
if (inc1) {
x--;
y--;
}
if (discarded != null) {
for (int i = 0; i < start; i++) {
discarded.accept(sb.charAt(i));
}
}
return new Cursor(x, y);
} else if (!matcher.hitEnd()) {
start++;
}
}
} catch (IOException e) {
throw new IOError(e);
}
}
}

View file

@ -0,0 +1,129 @@
/*
* Copyright (c) 2002-2018, 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.org.jline.terminal.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import jdk.internal.org.jline.terminal.Attributes;
import jdk.internal.org.jline.terminal.Attributes.ControlChar;
import jdk.internal.org.jline.terminal.Size;
import jdk.internal.org.jline.utils.NonBlocking;
import jdk.internal.org.jline.utils.NonBlockingInputStream;
import jdk.internal.org.jline.utils.NonBlockingReader;
public class DumbTerminal extends AbstractTerminal {
private final NonBlockingInputStream input;
private final OutputStream output;
private final NonBlockingReader reader;
private final PrintWriter writer;
private final Attributes attributes;
private final Size size;
public DumbTerminal(InputStream in, OutputStream out) throws IOException {
this(TYPE_DUMB, TYPE_DUMB, in, out, null);
}
public DumbTerminal(String name, String type, InputStream in, OutputStream out, Charset encoding) throws IOException {
this(name, type, in, out, encoding, SignalHandler.SIG_DFL);
}
public DumbTerminal(String name, String type, InputStream in, OutputStream out, Charset encoding, SignalHandler signalHandler) throws IOException {
super(name, type, encoding, signalHandler);
NonBlockingInputStream nbis = NonBlocking.nonBlocking(getName(), in);
this.input = new NonBlockingInputStream() {
@Override
public int read(long timeout, boolean isPeek) throws IOException {
for (;;) {
int c = nbis.read(timeout, isPeek);
if (attributes.getLocalFlag(Attributes.LocalFlag.ISIG)) {
if (c == attributes.getControlChar(ControlChar.VINTR)) {
raise(Signal.INT);
continue;
} else if (c == attributes.getControlChar(ControlChar.VQUIT)) {
raise(Signal.QUIT);
continue;
} else if (c == attributes.getControlChar(ControlChar.VSUSP)) {
raise(Signal.TSTP);
continue;
} else if (c == attributes.getControlChar(ControlChar.VSTATUS)) {
raise(Signal.INFO);
continue;
}
}
if (c == '\r') {
if (attributes.getInputFlag(Attributes.InputFlag.IGNCR)) {
continue;
}
if (attributes.getInputFlag(Attributes.InputFlag.ICRNL)) {
c = '\n';
}
} else if (c == '\n' && attributes.getInputFlag(Attributes.InputFlag.INLCR)) {
c = '\r';
}
return c;
}
}
};
this.output = out;
this.reader = NonBlocking.nonBlocking(getName(), input, encoding());
this.writer = new PrintWriter(new OutputStreamWriter(output, encoding()));
this.attributes = new Attributes();
this.attributes.setControlChar(ControlChar.VERASE, (char) 127);
this.attributes.setControlChar(ControlChar.VWERASE, (char) 23);
this.attributes.setControlChar(ControlChar.VKILL, (char) 21);
this.attributes.setControlChar(ControlChar.VLNEXT, (char) 22);
this.size = new Size();
parseInfoCmp();
}
public NonBlockingReader reader() {
return reader;
}
public PrintWriter writer() {
return writer;
}
@Override
public InputStream input() {
return input;
}
@Override
public OutputStream output() {
return output;
}
public Attributes getAttributes() {
Attributes attr = new Attributes();
attr.copy(attributes);
return attr;
}
public void setAttributes(Attributes attr) {
attributes.copy(attr);
}
public Size getSize() {
Size sz = new Size();
sz.copy(size);
return sz;
}
public void setSize(Size sz) {
size.copy(sz);
}
}

View file

@ -0,0 +1,302 @@
/*
* Copyright (c) 2002-2016, 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.org.jline.terminal.impl;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.FileDescriptor;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jdk.internal.org.jline.terminal.Attributes;
import jdk.internal.org.jline.terminal.Attributes.ControlChar;
import jdk.internal.org.jline.terminal.Attributes.ControlFlag;
import jdk.internal.org.jline.terminal.Attributes.InputFlag;
import jdk.internal.org.jline.terminal.Attributes.LocalFlag;
import jdk.internal.org.jline.terminal.Attributes.OutputFlag;
import jdk.internal.org.jline.terminal.Size;
import jdk.internal.org.jline.terminal.spi.Pty;
import jdk.internal.org.jline.utils.OSUtils;
import static jdk.internal.org.jline.utils.ExecHelper.exec;
public class ExecPty extends AbstractPty implements Pty {
private final String name;
private final boolean system;
public static Pty current() throws IOException {
try {
String result = exec(true, OSUtils.TTY_COMMAND);
return new ExecPty(result.trim(), true);
} catch (IOException e) {
throw new IOException("Not a tty", e);
}
}
protected ExecPty(String name, boolean system) {
this.name = name;
this.system = system;
}
@Override
public void close() throws IOException {
}
public String getName() {
return name;
}
@Override
public InputStream getMasterInput() {
throw new UnsupportedOperationException();
}
@Override
public OutputStream getMasterOutput() {
throw new UnsupportedOperationException();
}
@Override
protected InputStream doGetSlaveInput() throws IOException {
return system
? new FileInputStream(FileDescriptor.in)
: new FileInputStream(getName());
}
@Override
public OutputStream getSlaveOutput() throws IOException {
return system
? new FileOutputStream(FileDescriptor.out)
: new FileOutputStream(getName());
}
@Override
public Attributes getAttr() throws IOException {
String cfg = doGetConfig();
return doGetAttr(cfg);
}
@Override
protected void doSetAttr(Attributes attr) throws IOException {
List<String> commands = getFlagsToSet(attr, getAttr());
if (!commands.isEmpty()) {
commands.add(0, OSUtils.STTY_COMMAND);
if (!system) {
commands.add(1, OSUtils.STTY_F_OPTION);
commands.add(2, getName());
}
try {
exec(system, commands.toArray(new String[commands.size()]));
} catch (IOException e) {
// Handle partial failures with GNU stty, see #97
if (e.toString().contains("unable to perform all requested operations")) {
commands = getFlagsToSet(attr, getAttr());
if (!commands.isEmpty()) {
throw new IOException("Could not set the following flags: " + String.join(", ", commands), e);
}
} else {
throw e;
}
}
}
}
protected List<String> getFlagsToSet(Attributes attr, Attributes current) {
List<String> commands = new ArrayList<>();
for (InputFlag flag : InputFlag.values()) {
if (attr.getInputFlag(flag) != current.getInputFlag(flag)) {
commands.add((attr.getInputFlag(flag) ? flag.name() : "-" + flag.name()).toLowerCase());
}
}
for (OutputFlag flag : OutputFlag.values()) {
if (attr.getOutputFlag(flag) != current.getOutputFlag(flag)) {
commands.add((attr.getOutputFlag(flag) ? flag.name() : "-" + flag.name()).toLowerCase());
}
}
for (ControlFlag flag : ControlFlag.values()) {
if (attr.getControlFlag(flag) != current.getControlFlag(flag)) {
commands.add((attr.getControlFlag(flag) ? flag.name() : "-" + flag.name()).toLowerCase());
}
}
for (LocalFlag flag : LocalFlag.values()) {
if (attr.getLocalFlag(flag) != current.getLocalFlag(flag)) {
commands.add((attr.getLocalFlag(flag) ? flag.name() : "-" + flag.name()).toLowerCase());
}
}
String undef = System.getProperty("os.name").toLowerCase().startsWith("hp") ? "^-" : "undef";
for (ControlChar cchar : ControlChar.values()) {
if (attr.getControlChar(cchar) != current.getControlChar(cchar)) {
String str = "";
int v = attr.getControlChar(cchar);
commands.add(cchar.name().toLowerCase().substring(1));
if (cchar == ControlChar.VMIN || cchar == ControlChar.VTIME) {
commands.add(Integer.toBinaryString(v));
}
else if (v == 0) {
commands.add(undef);
}
else {
if (v >= 128) {
v -= 128;
str += "M-";
}
if (v < 32 || v == 127) {
v ^= 0x40;
str += "^";
}
str += (char) v;
commands.add(str);
}
}
}
return commands;
}
@Override
public Size getSize() throws IOException {
String cfg = doGetConfig();
return doGetSize(cfg);
}
protected String doGetConfig() throws IOException {
return system
? exec(true, OSUtils.STTY_COMMAND, "-a")
: exec(false, OSUtils.STTY_COMMAND, OSUtils.STTY_F_OPTION, getName(), "-a");
}
static Attributes doGetAttr(String cfg) throws IOException {
Attributes attributes = new Attributes();
for (InputFlag flag : InputFlag.values()) {
Boolean value = doGetFlag(cfg, flag);
if (value != null) {
attributes.setInputFlag(flag, value);
}
}
for (OutputFlag flag : OutputFlag.values()) {
Boolean value = doGetFlag(cfg, flag);
if (value != null) {
attributes.setOutputFlag(flag, value);
}
}
for (ControlFlag flag : ControlFlag.values()) {
Boolean value = doGetFlag(cfg, flag);
if (value != null) {
attributes.setControlFlag(flag, value);
}
}
for (LocalFlag flag : LocalFlag.values()) {
Boolean value = doGetFlag(cfg, flag);
if (value != null) {
attributes.setLocalFlag(flag, value);
}
}
for (ControlChar cchar : ControlChar.values()) {
String name = cchar.name().toLowerCase().substring(1);
if ("reprint".endsWith(name)) {
name = "(?:reprint|rprnt)";
}
Matcher matcher = Pattern.compile("[\\s;]" + name + "\\s*=\\s*(.+?)[\\s;]").matcher(cfg);
if (matcher.find()) {
attributes.setControlChar(cchar, parseControlChar(matcher.group(1).toUpperCase()));
}
}
return attributes;
}
private static Boolean doGetFlag(String cfg, Enum<?> flag) {
Matcher matcher = Pattern.compile("(?:^|[\\s;])(\\-?" + flag.name().toLowerCase() + ")(?:[\\s;]|$)").matcher(cfg);
return matcher.find() ? !matcher.group(1).startsWith("-") : null;
}
static int parseControlChar(String str) {
// undef
if ("<UNDEF>".equals(str)) {
return -1;
}
// del
if ("DEL".equalsIgnoreCase(str)) {
return 127;
}
// 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);
}
}
static Size doGetSize(String cfg) throws IOException {
return new Size(doGetInt("columns", cfg), doGetInt("rows", cfg));
}
static int doGetInt(String name, String cfg) throws IOException {
String[] patterns = new String[] {
"\\b([0-9]+)\\s+" + name + "\\b",
"\\b" + name + "\\s+([0-9]+)\\b",
"\\b" + name + "\\s*=\\s*([0-9]+)\\b"
};
for (String pattern : patterns) {
Matcher matcher = Pattern.compile(pattern).matcher(cfg);
if (matcher.find()) {
return Integer.parseInt(matcher.group(1));
}
}
throw new IOException("Unable to parse " + name);
}
@Override
public void setSize(Size size) throws IOException {
if (system) {
exec(true,
OSUtils.STTY_COMMAND,
"columns", Integer.toString(size.getColumns()),
"rows", Integer.toString(size.getRows()));
} else {
exec(false,
OSUtils.STTY_COMMAND,
OSUtils.STTY_F_OPTION, getName(),
"columns", Integer.toString(size.getColumns()),
"rows", Integer.toString(size.getRows()));
}
}
@Override
public String toString() {
return "ExecPty[" + getName() + (system ? ", system]" : "]");
}
}

View file

@ -0,0 +1,155 @@
/*
* Copyright (c) 2002-2018, 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.org.jline.terminal.impl;
import jdk.internal.org.jline.terminal.Cursor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.IntConsumer;
/**
* Console implementation with embedded line disciplined.
*
* This terminal is well-suited for supporting incoming external
* connections, such as from the network (through telnet, ssh,
* or any kind of protocol).
* The terminal will start consuming the input in a separate thread
* to generate interruption events.
*
* @see LineDisciplineTerminal
*/
public class ExternalTerminal extends LineDisciplineTerminal {
protected final AtomicBoolean closed = new AtomicBoolean();
protected final InputStream masterInput;
protected final Object lock = new Object();
protected boolean paused = true;
protected Thread pumpThread;
public ExternalTerminal(String name, String type,
InputStream masterInput,
OutputStream masterOutput,
Charset encoding) throws IOException {
this(name, type, masterInput, masterOutput, encoding, SignalHandler.SIG_DFL);
}
public ExternalTerminal(String name, String type,
InputStream masterInput,
OutputStream masterOutput,
Charset encoding,
SignalHandler signalHandler) throws IOException {
this(name, type, masterInput, masterOutput, encoding, signalHandler, false);
}
public ExternalTerminal(String name, String type,
InputStream masterInput,
OutputStream masterOutput,
Charset encoding,
SignalHandler signalHandler,
boolean paused) throws IOException {
super(name, type, masterOutput, encoding, signalHandler);
this.masterInput = masterInput;
if (!paused) {
resume();
}
}
public void close() throws IOException {
if (closed.compareAndSet(false, true)) {
pause();
super.close();
}
}
@Override
public boolean canPauseResume() {
return true;
}
@Override
public void pause() {
synchronized (lock) {
paused = true;
}
}
@Override
public void pause(boolean wait) throws InterruptedException {
Thread p;
synchronized (lock) {
paused = true;
p = pumpThread;
}
if (p != null) {
p.interrupt();
p.join();
}
}
@Override
public void resume() {
synchronized (lock) {
paused = false;
if (pumpThread == null) {
pumpThread = new Thread(this::pump, toString() + " input pump thread");
pumpThread.setDaemon(true);
pumpThread.start();
}
}
}
@Override
public boolean paused() {
synchronized (lock) {
return paused;
}
}
public void pump() {
try {
byte[] buf = new byte[1024];
while (true) {
int c = masterInput.read(buf);
if (c >= 0) {
processInputBytes(buf, 0, c);
}
if (c < 0 || closed.get()) {
break;
}
synchronized (lock) {
if (paused) {
pumpThread = null;
return;
}
}
}
} catch (IOException e) {
processIOException(e);
} finally {
synchronized (lock) {
pumpThread = null;
}
}
try {
slaveInput.close();
} catch (IOException e) {
// ignore
}
}
@Override
public Cursor getCursorPosition(IntConsumer discarded) {
return CursorSupport.getCursorPosition(this, discarded);
}
}

View file

@ -0,0 +1,309 @@
/*
* Copyright (c) 2002-2018, 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.org.jline.terminal.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.Objects;
import jdk.internal.org.jline.terminal.Attributes;
import jdk.internal.org.jline.terminal.Attributes.ControlChar;
import jdk.internal.org.jline.terminal.Attributes.InputFlag;
import jdk.internal.org.jline.terminal.Attributes.LocalFlag;
import jdk.internal.org.jline.terminal.Attributes.OutputFlag;
import jdk.internal.org.jline.terminal.Size;
import jdk.internal.org.jline.terminal.Terminal;
import jdk.internal.org.jline.utils.NonBlocking;
import jdk.internal.org.jline.utils.NonBlockingPumpInputStream;
import jdk.internal.org.jline.utils.NonBlockingReader;
/**
* Abstract terminal with support for line discipline.
* The {@link Terminal} interface represents the slave
* side of a PTY, but implementations derived from this class
* will handle both the slave and master side of things.
*
* In order to correctly handle line discipline, the terminal
* needs to read the input in advance in order to raise the
* signals as fast as possible.
* For example, when the user hits Ctrl+C, we can't wait until
* the application consumes all the read events.
* The same applies to echoing, when enabled, as the echoing
* has to happen as soon as the user hit the keyboard, and not
* only when the application running in the terminal processes
* the input.
*/
public class LineDisciplineTerminal extends AbstractTerminal {
private static final String DEFAULT_TERMINAL_ATTRIBUTES =
"speed 9600 baud; 24 rows; 80 columns;\n" +
"lflags: icanon isig iexten echo echoe -echok echoke -echonl echoctl\n" +
"\t-echoprt -altwerase -noflsh -tostop -flusho pendin -nokerninfo\n" +
"\t-extproc\n" +
"iflags: -istrip icrnl -inlcr -igncr ixon -ixoff ixany imaxbel iutf8\n" +
"\t-ignbrk brkint -inpck -ignpar -parmrk\n" +
"oflags: opost onlcr -oxtabs -onocr -onlret\n" +
"cflags: cread cs8 -parenb -parodd hupcl -clocal -cstopb -crtscts -dsrflow\n" +
"\t-dtrflow -mdmbuf\n" +
"cchars: discard = ^O; dsusp = ^Y; eof = ^D; eol = <undef>;\n" +
"\teol2 = <undef>; erase = ^?; intr = ^C; kill = ^U; lnext = ^V;\n" +
"\tmin = 1; quit = ^\\; reprint = ^R; start = ^Q; status = ^T;\n" +
"\tstop = ^S; susp = ^Z; time = 0; werase = ^W;\n";
private static final int PIPE_SIZE = 1024;
/*
* Master output stream
*/
protected final OutputStream masterOutput;
/*
* Slave input pipe write side
*/
protected final OutputStream slaveInputPipe;
/*
* Slave streams
*/
protected final NonBlockingPumpInputStream slaveInput;
protected final NonBlockingReader slaveReader;
protected final PrintWriter slaveWriter;
protected final OutputStream slaveOutput;
/**
* Console data
*/
protected final Attributes attributes;
protected final Size size;
public LineDisciplineTerminal(String name,
String type,
OutputStream masterOutput,
Charset encoding) throws IOException {
this(name, type, masterOutput, encoding, SignalHandler.SIG_DFL);
}
public LineDisciplineTerminal(String name,
String type,
OutputStream masterOutput,
Charset encoding,
SignalHandler signalHandler) throws IOException {
super(name, type, encoding, signalHandler);
NonBlockingPumpInputStream input = NonBlocking.nonBlockingPumpInputStream(PIPE_SIZE);
this.slaveInputPipe = input.getOutputStream();
this.slaveInput = input;
this.slaveReader = NonBlocking.nonBlocking(getName(), slaveInput, encoding());
this.slaveOutput = new FilteringOutputStream();
this.slaveWriter = new PrintWriter(new OutputStreamWriter(slaveOutput, encoding()));
this.masterOutput = masterOutput;
this.attributes = ExecPty.doGetAttr(DEFAULT_TERMINAL_ATTRIBUTES);
this.size = new Size(160, 50);
parseInfoCmp();
}
public NonBlockingReader reader() {
return slaveReader;
}
public PrintWriter writer() {
return slaveWriter;
}
@Override
public InputStream input() {
return slaveInput;
}
@Override
public OutputStream output() {
return slaveOutput;
}
public Attributes getAttributes() {
Attributes attr = new Attributes();
attr.copy(attributes);
return attr;
}
public void setAttributes(Attributes attr) {
attributes.copy(attr);
}
public Size getSize() {
Size sz = new Size();
sz.copy(size);
return sz;
}
public void setSize(Size sz) {
size.copy(sz);
}
@Override
public void raise(Signal signal) {
Objects.requireNonNull(signal);
// Do not call clear() atm as this can cause
// deadlock between reading / writing threads
// TODO: any way to fix that ?
/*
if (!attributes.getLocalFlag(LocalFlag.NOFLSH)) {
try {
slaveReader.clear();
} catch (IOException e) {
// Ignore
}
}
*/
echoSignal(signal);
super.raise(signal);
}
/**
* Master input processing.
* All data coming to the terminal should be provided
* using this method.
*
* @param c the input byte
* @throws IOException if anything wrong happens
*/
public void processInputByte(int c) throws IOException {
boolean flushOut = doProcessInputByte(c);
slaveInputPipe.flush();
if (flushOut) {
masterOutput.flush();
}
}
public void processInputBytes(byte[] input) throws IOException {
processInputBytes(input, 0, input.length);
}
public void processInputBytes(byte[] input, int offset, int length) throws IOException {
boolean flushOut = false;
for (int i = 0; i < length; i++) {
flushOut |= doProcessInputByte(input[offset + i]);
}
slaveInputPipe.flush();
if (flushOut) {
masterOutput.flush();
}
}
protected boolean doProcessInputByte(int c) throws IOException {
if (attributes.getLocalFlag(LocalFlag.ISIG)) {
if (c == attributes.getControlChar(ControlChar.VINTR)) {
raise(Signal.INT);
return false;
} else if (c == attributes.getControlChar(ControlChar.VQUIT)) {
raise(Signal.QUIT);
return false;
} else if (c == attributes.getControlChar(ControlChar.VSUSP)) {
raise(Signal.TSTP);
return false;
} else if (c == attributes.getControlChar(ControlChar.VSTATUS)) {
raise(Signal.INFO);
}
}
if (c == '\r') {
if (attributes.getInputFlag(InputFlag.IGNCR)) {
return false;
}
if (attributes.getInputFlag(InputFlag.ICRNL)) {
c = '\n';
}
} else if (c == '\n' && attributes.getInputFlag(InputFlag.INLCR)) {
c = '\r';
}
boolean flushOut = false;
if (attributes.getLocalFlag(LocalFlag.ECHO)) {
processOutputByte(c);
flushOut = true;
}
slaveInputPipe.write(c);
return flushOut;
}
/**
* Master output processing.
* All data going to the master should be provided by this method.
*
* @param c the output byte
* @throws IOException if anything wrong happens
*/
protected void processOutputByte(int c) throws IOException {
if (attributes.getOutputFlag(OutputFlag.OPOST)) {
if (c == '\n') {
if (attributes.getOutputFlag(OutputFlag.ONLCR)) {
masterOutput.write('\r');
masterOutput.write('\n');
return;
}
}
}
masterOutput.write(c);
}
protected void processIOException(IOException ioException) {
this.slaveInput.setIoException(ioException);
}
public void close() throws IOException {
super.close();
try {
slaveReader.close();
} finally {
try {
slaveInputPipe.close();
} finally {
try {
} finally {
slaveWriter.close();
}
}
}
}
private class FilteringOutputStream extends OutputStream {
@Override
public void write(int b) throws IOException {
processOutputByte(b);
flush();
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
for (int i = 0 ; i < len ; i++) {
processOutputByte(b[off + i]);
}
flush();
}
@Override
public void flush() throws IOException {
masterOutput.flush();
}
@Override
public void close() throws IOException {
masterOutput.close();
}
}
}

View file

@ -0,0 +1,138 @@
/*
* Copyright (c) 2002-2016, 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.org.jline.terminal.impl;
import jdk.internal.org.jline.terminal.MouseEvent;
import jdk.internal.org.jline.terminal.Terminal;
import jdk.internal.org.jline.utils.InfoCmp;
import jdk.internal.org.jline.utils.InputStreamReader;
import java.io.EOFException;
import java.io.IOError;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.EnumSet;
import java.util.function.IntSupplier;
public class MouseSupport {
public static boolean hasMouseSupport(Terminal terminal) {
return terminal.getStringCapability(InfoCmp.Capability.key_mouse) != null;
}
public static boolean trackMouse(Terminal terminal, Terminal.MouseTracking tracking) {
if (hasMouseSupport(terminal)) {
switch (tracking) {
case Off:
terminal.writer().write("\033[?1000l");
break;
case Normal:
terminal.writer().write("\033[?1005h\033[?1000h");
break;
case Button:
terminal.writer().write("\033[?1005h\033[?1002h");
break;
case Any:
terminal.writer().write("\033[?1005h\033[?1003h");
break;
}
terminal.flush();
return true;
} else {
return false;
}
}
public static MouseEvent readMouse(Terminal terminal, MouseEvent last) {
return readMouse(() -> readExt(terminal), last);
}
public static MouseEvent readMouse(IntSupplier reader, MouseEvent last) {
int cb = reader.getAsInt() - ' ';
int cx = reader.getAsInt() - ' ' - 1;
int cy = reader.getAsInt() - ' ' - 1;
MouseEvent.Type type;
MouseEvent.Button button;
EnumSet<MouseEvent.Modifier> modifiers = EnumSet.noneOf(MouseEvent.Modifier.class);
if ((cb & 4) == 4) {
modifiers.add(MouseEvent.Modifier.Shift);
}
if ((cb & 8) == 8) {
modifiers.add(MouseEvent.Modifier.Alt);
}
if ((cb & 16) == 16) {
modifiers.add(MouseEvent.Modifier.Control);
}
if ((cb & 64) == 64) {
type = MouseEvent.Type.Wheel;
button = (cb & 1) == 1 ? MouseEvent.Button.WheelDown : MouseEvent.Button.WheelUp;
} else {
int b = (cb & 3);
switch (b) {
case 0:
button = MouseEvent.Button.Button1;
if (last.getButton() == button
&& (last.getType() == MouseEvent.Type.Pressed || last.getType() == MouseEvent.Type.Dragged)) {
type = MouseEvent.Type.Dragged;
} else {
type = MouseEvent.Type.Pressed;
}
break;
case 1:
button = MouseEvent.Button.Button2;
if (last.getButton() == button
&& (last.getType() == MouseEvent.Type.Pressed || last.getType() == MouseEvent.Type.Dragged)) {
type = MouseEvent.Type.Dragged;
} else {
type = MouseEvent.Type.Pressed;
}
break;
case 2:
button = MouseEvent.Button.Button3;
if (last.getButton() == button
&& (last.getType() == MouseEvent.Type.Pressed || last.getType() == MouseEvent.Type.Dragged)) {
type = MouseEvent.Type.Dragged;
} else {
type = MouseEvent.Type.Pressed;
}
break;
default:
if (last.getType() == MouseEvent.Type.Pressed || last.getType() == MouseEvent.Type.Dragged) {
button = last.getButton();
type = MouseEvent.Type.Released;
} else {
button = MouseEvent.Button.NoButton;
type = MouseEvent.Type.Moved;
}
break;
}
}
return new MouseEvent(type, button, modifiers, cx, cy);
}
private static int readExt(Terminal terminal) {
try {
// The coordinates are encoded in UTF-8, so if that's not the input encoding,
// we need to get around
int c;
if (terminal.encoding() != StandardCharsets.UTF_8) {
c = new InputStreamReader(terminal.input(), StandardCharsets.UTF_8).read();
} else {
c = terminal.reader().read();
}
if (c < 0) {
throw new EOFException();
}
return c;
} catch (IOException e) {
throw new IOError(e);
}
}
}

Some files were not shown because too many files have changed in this diff Show more