8080679: Include jline in JDK for Java and JavaScript REPLs
Reviewed-by: alanb, erikj, forax, iris, sundar
This commit is contained in:
parent
aa2d62b688
commit
581470a6d1
jdk
make/lib
src/jdk.internal.le
share/classes/jdk/internal/jline
NoInterruptUnixTerminal.javaTerminal.javaTerminalFactory.javaTerminalSupport.javaUnixTerminal.javaUnsupportedTerminal.javaWindowsTerminal.java
console
ConsoleKeys.javaConsoleReader.javaCursorBuffer.javaKeyMap.javaKillRing.javaOperation.javaUserInterruptException.java
completer
AggregateCompleter.javaArgumentCompleter.javaCandidateListCompletionHandler.javaCandidateListCompletionHandler.propertiesCompleter.javaCompletionHandler.javaEnumCompleter.javaFileNameCompleter.javaNullCompleter.javaStringsCompleter.javapackage-info.java
history
internal
package-info.javainternal
Configuration.javaInputStreamReader.javaLog.javaNonBlockingInputStream.javaNullable.javaPreconditions.javaShutdownHooks.javaTerminalLineSettings.javaTestAccessible.javaUrls.javapackage-info.java
package-info.javawindows/native/lible
test
60
jdk/make/lib/Lib-jdk.internal.le.gmk
Normal file
60
jdk/make/lib/Lib-jdk.internal.le.gmk
Normal file
@ -0,0 +1,60 @@
|
||||
#
|
||||
# Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
#
|
||||
# This code is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 2 only, as
|
||||
# published by the Free Software Foundation. Oracle designates this
|
||||
# particular file as subject to the "Classpath" exception as provided
|
||||
# by Oracle in the LICENSE file that accompanied this code.
|
||||
#
|
||||
# This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
# version 2 for more details (a copy is included in the LICENSE file that
|
||||
# accompanied this code).
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License version
|
||||
# 2 along with this work; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
# or visit www.oracle.com if you need additional information or have any
|
||||
# questions.
|
||||
#
|
||||
|
||||
include LibCommon.gmk
|
||||
|
||||
################################################################################
|
||||
|
||||
ifeq ($(OPENJDK_TARGET_OS), windows)
|
||||
|
||||
LIBLE_SRC := $(JDK_TOPDIR)/src/jdk.internal.le/$(OPENJDK_TARGET_OS_TYPE)/native/lible \
|
||||
#
|
||||
LIBLE_CPPFLAGS := \
|
||||
$(addprefix -I, $(LIBLE_SRC)) \
|
||||
-I$(SUPPORT_OUTPUTDIR)/headers/jdk.internal.le \
|
||||
#
|
||||
|
||||
$(eval $(call SetupNativeCompilation,BUILD_LIBLE, \
|
||||
LIBRARY := le, \
|
||||
OUTPUT_DIR := $(INSTALL_LIBRARIES_HERE), \
|
||||
SRC := $(LIBLE_SRC), \
|
||||
OPTIMIZATION := LOW, \
|
||||
CFLAGS := $(CFLAGS_JDKLIB) $(LIBJAVA_HEADER_FLAGS)\
|
||||
$(LIBLE_CPPFLAGS), \
|
||||
LDFLAGS := $(LDFLAGS_JDKLIB), \
|
||||
LDFLAGS_SUFFIX := $(LDFLAGS_JDKLIB_SUFFIX) user32.lib, \
|
||||
VERSIONINFO_RESOURCE := $(GLOBAL_VERSION_INFO_RESOURCE), \
|
||||
RC_FLAGS := $(RC_FLAGS) \
|
||||
-D "JDK_FNAME=le.dll" \
|
||||
-D "JDK_INTERNAL_NAME=le" \
|
||||
-D "JDK_FTYPE=0x2L", \
|
||||
OBJECT_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/lible, \
|
||||
DEBUG_SYMBOLS := $(DEBUG_ALL_BINARIES)))
|
||||
|
||||
TARGETS += $(BUILD_LIBLE)
|
||||
|
||||
endif # OPENJDK_TARGET_OS
|
||||
|
||||
################################################################################
|
36
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/NoInterruptUnixTerminal.java
Normal file
36
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/NoInterruptUnixTerminal.java
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline;
|
||||
|
||||
// Based on Apache Karaf impl
|
||||
|
||||
/**
|
||||
* Non-interruptible (via CTRL-C) {@link UnixTerminal}.
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class NoInterruptUnixTerminal
|
||||
extends UnixTerminal
|
||||
{
|
||||
public NoInterruptUnixTerminal() throws Exception {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
super.init();
|
||||
getSettings().set("intr undef");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restore() throws Exception {
|
||||
getSettings().set("intr ^C");
|
||||
super.restore();
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Representation of the input terminal for a platform.
|
||||
*
|
||||
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface Terminal
|
||||
{
|
||||
void init() throws Exception;
|
||||
|
||||
void restore() throws Exception;
|
||||
|
||||
void reset() throws Exception;
|
||||
|
||||
boolean isSupported();
|
||||
|
||||
int getWidth();
|
||||
|
||||
int getHeight();
|
||||
|
||||
boolean isAnsiSupported();
|
||||
|
||||
/**
|
||||
* When ANSI is not natively handled, the output will have to be wrapped.
|
||||
*/
|
||||
OutputStream wrapOutIfNeeded(OutputStream out);
|
||||
|
||||
/**
|
||||
* When using native support, return the InputStream to use for reading characters
|
||||
* else return the input stream passed as a parameter.
|
||||
*
|
||||
* @since 2.6
|
||||
*/
|
||||
InputStream wrapInIfNeeded(InputStream in) throws IOException;
|
||||
|
||||
/**
|
||||
* For terminals that don't wrap when character is written in last column,
|
||||
* only when the next character is written.
|
||||
* These are the ones that have 'am' and 'xn' termcap attributes (xterm and
|
||||
* rxvt flavors falls under that category)
|
||||
*/
|
||||
boolean hasWeirdWrap();
|
||||
|
||||
boolean isEchoEnabled();
|
||||
|
||||
void setEchoEnabled(boolean enabled);
|
||||
|
||||
String getOutputEncoding();
|
||||
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import jdk.internal.jline.internal.Configuration;
|
||||
import jdk.internal.jline.internal.Log;
|
||||
import jdk.internal.jline.internal.Preconditions;
|
||||
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Creates terminal instances.
|
||||
*
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.0
|
||||
*/
|
||||
public class TerminalFactory
|
||||
{
|
||||
public static final String JLINE_TERMINAL = "jline.terminal";
|
||||
|
||||
public static final String AUTO = "auto";
|
||||
|
||||
public static final String UNIX = "unix";
|
||||
|
||||
public static final String WIN = "win";
|
||||
|
||||
public static final String WINDOWS = "windows";
|
||||
|
||||
public static final String NONE = "none";
|
||||
|
||||
public static final String OFF = "off";
|
||||
|
||||
public static final String FALSE = "false";
|
||||
|
||||
private static Terminal term = null;
|
||||
|
||||
public static synchronized Terminal create() {
|
||||
if (Log.TRACE) {
|
||||
//noinspection ThrowableInstanceNeverThrown
|
||||
Log.trace(new Throwable("CREATE MARKER"));
|
||||
}
|
||||
|
||||
String type = Configuration.getString(JLINE_TERMINAL, AUTO);
|
||||
if ("dumb".equals(System.getenv("TERM"))) {
|
||||
type = "none";
|
||||
Log.debug("$TERM=dumb; setting type=", type);
|
||||
}
|
||||
|
||||
Log.debug("Creating terminal; type=", type);
|
||||
|
||||
Terminal t;
|
||||
try {
|
||||
String tmp = type.toLowerCase();
|
||||
|
||||
if (tmp.equals(UNIX)) {
|
||||
t = getFlavor(Flavor.UNIX);
|
||||
}
|
||||
else if (tmp.equals(WIN) | tmp.equals(WINDOWS)) {
|
||||
t = getFlavor(Flavor.WINDOWS);
|
||||
}
|
||||
else if (tmp.equals(NONE) || tmp.equals(OFF) || tmp.equals(FALSE)) {
|
||||
t = new UnsupportedTerminal();
|
||||
}
|
||||
else {
|
||||
if (tmp.equals(AUTO)) {
|
||||
String os = Configuration.getOsName();
|
||||
Flavor flavor = Flavor.UNIX;
|
||||
if (os.contains(WINDOWS)) {
|
||||
flavor = Flavor.WINDOWS;
|
||||
}
|
||||
t = getFlavor(flavor);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
t = (Terminal) Thread.currentThread().getContextClassLoader().loadClass(type).newInstance();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalArgumentException(MessageFormat.format("Invalid terminal type: {0}", type), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Log.error("Failed to construct terminal; falling back to unsupported", e);
|
||||
t = new UnsupportedTerminal();
|
||||
}
|
||||
|
||||
Log.debug("Created Terminal: ", t);
|
||||
|
||||
try {
|
||||
t.init();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Log.error("Terminal initialization failed; falling back to unsupported", e);
|
||||
return new UnsupportedTerminal();
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
public static synchronized void reset() {
|
||||
term = null;
|
||||
}
|
||||
|
||||
public static synchronized void resetIf(final Terminal t) {
|
||||
if(t == term) {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
public static enum Type
|
||||
{
|
||||
AUTO,
|
||||
WINDOWS,
|
||||
UNIX,
|
||||
NONE
|
||||
}
|
||||
|
||||
public static synchronized void configure(final String type) {
|
||||
checkNotNull(type);
|
||||
System.setProperty(JLINE_TERMINAL, type);
|
||||
}
|
||||
|
||||
public static synchronized void configure(final Type type) {
|
||||
checkNotNull(type);
|
||||
configure(type.name().toLowerCase());
|
||||
}
|
||||
|
||||
//
|
||||
// Flavor Support
|
||||
//
|
||||
|
||||
public static enum Flavor
|
||||
{
|
||||
WINDOWS,
|
||||
UNIX
|
||||
}
|
||||
|
||||
private static final Map<Flavor, Callable<? extends Terminal>> FLAVORS = new HashMap<>();
|
||||
|
||||
static {
|
||||
// registerFlavor(Flavor.WINDOWS, AnsiWindowsTerminal.class);
|
||||
// registerFlavor(Flavor.UNIX, UnixTerminal.class);
|
||||
registerFlavor(Flavor.WINDOWS, WindowsTerminal :: new);
|
||||
registerFlavor(Flavor.UNIX, UnixTerminal :: new);
|
||||
}
|
||||
|
||||
public static synchronized Terminal get() {
|
||||
if (term == null) {
|
||||
term = create();
|
||||
}
|
||||
return term;
|
||||
}
|
||||
|
||||
public static Terminal getFlavor(final Flavor flavor) throws Exception {
|
||||
return FLAVORS.getOrDefault(flavor, () -> {throw new InternalError();}).call();
|
||||
}
|
||||
|
||||
public static void registerFlavor(final Flavor flavor, final Callable<? extends Terminal> sup) {
|
||||
FLAVORS.put(flavor, sup);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import jdk.internal.jline.internal.Log;
|
||||
import jdk.internal.jline.internal.ShutdownHooks;
|
||||
import jdk.internal.jline.internal.ShutdownHooks.Task;
|
||||
|
||||
/**
|
||||
* Provides support for {@link Terminal} instances.
|
||||
*
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.0
|
||||
*/
|
||||
public abstract class TerminalSupport
|
||||
implements Terminal
|
||||
{
|
||||
public static final int DEFAULT_WIDTH = 80;
|
||||
|
||||
public static final int DEFAULT_HEIGHT = 24;
|
||||
|
||||
private Task shutdownTask;
|
||||
|
||||
private boolean supported;
|
||||
|
||||
private boolean echoEnabled;
|
||||
|
||||
private boolean ansiSupported;
|
||||
|
||||
protected TerminalSupport(final boolean supported) {
|
||||
this.supported = supported;
|
||||
}
|
||||
|
||||
public void init() throws Exception {
|
||||
if (shutdownTask != null) {
|
||||
ShutdownHooks.remove(shutdownTask);
|
||||
}
|
||||
// Register a task to restore the terminal on shutdown
|
||||
this.shutdownTask = ShutdownHooks.add(new Task()
|
||||
{
|
||||
public void run() throws Exception {
|
||||
restore();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void restore() throws Exception {
|
||||
TerminalFactory.resetIf(this);
|
||||
if (shutdownTask != null) {
|
||||
ShutdownHooks.remove(shutdownTask);
|
||||
shutdownTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() throws Exception {
|
||||
restore();
|
||||
init();
|
||||
}
|
||||
|
||||
public final boolean isSupported() {
|
||||
return supported;
|
||||
}
|
||||
|
||||
public synchronized boolean isAnsiSupported() {
|
||||
return ansiSupported;
|
||||
}
|
||||
|
||||
protected synchronized void setAnsiSupported(final boolean supported) {
|
||||
this.ansiSupported = supported;
|
||||
Log.debug("Ansi supported: ", supported);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass to change behavior if needed.
|
||||
* @return the passed out
|
||||
*/
|
||||
public OutputStream wrapOutIfNeeded(OutputStream out) {
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults to true which was the behaviour before this method was added.
|
||||
*/
|
||||
public boolean hasWeirdWrap() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return DEFAULT_WIDTH;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return DEFAULT_HEIGHT;
|
||||
}
|
||||
|
||||
public synchronized boolean isEchoEnabled() {
|
||||
return echoEnabled;
|
||||
}
|
||||
|
||||
public synchronized void setEchoEnabled(final boolean enabled) {
|
||||
this.echoEnabled = enabled;
|
||||
Log.debug("Echo enabled: ", enabled);
|
||||
}
|
||||
|
||||
public InputStream wrapInIfNeeded(InputStream in) throws IOException {
|
||||
return in;
|
||||
}
|
||||
|
||||
public String getOutputEncoding() {
|
||||
// null for unknown
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline;
|
||||
|
||||
import jdk.internal.jline.internal.Log;
|
||||
import jdk.internal.jline.internal.TerminalLineSettings;
|
||||
|
||||
/**
|
||||
* Terminal that is used for unix platforms. Terminal initialization
|
||||
* is handled by issuing the <em>stty</em> command against the
|
||||
* <em>/dev/tty</em> file to disable character echoing and enable
|
||||
* character input. All known unix systems (including
|
||||
* Linux and Macintosh OS X) support the <em>stty</em>), so this
|
||||
* implementation should work for an reasonable POSIX system.
|
||||
*
|
||||
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||
* @author <a href="mailto:dwkemp@gmail.com">Dale Kemp</a>
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @author <a href="mailto:jbonofre@apache.org">Jean-Baptiste Onofr\u00E9</a>
|
||||
* @since 2.0
|
||||
*/
|
||||
public class UnixTerminal
|
||||
extends TerminalSupport
|
||||
{
|
||||
private final TerminalLineSettings settings = new TerminalLineSettings();
|
||||
|
||||
public UnixTerminal() throws Exception {
|
||||
super(true);
|
||||
}
|
||||
|
||||
protected TerminalLineSettings getSettings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove line-buffered input by invoking "stty -icanon min 1"
|
||||
* against the current terminal.
|
||||
*/
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
super.init();
|
||||
|
||||
setAnsiSupported(true);
|
||||
|
||||
// Set the console to be character-buffered instead of line-buffered.
|
||||
// Make sure we're distinguishing carriage return from newline.
|
||||
// Allow ctrl-s keypress to be used (as forward search)
|
||||
settings.set("-icanon min 1 -icrnl -inlcr -ixon");
|
||||
settings.set("dsusp undef");
|
||||
|
||||
setEchoEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the original terminal configuration, which can be used when
|
||||
* shutting down the console reader. The ConsoleReader cannot be
|
||||
* used after calling this method.
|
||||
*/
|
||||
@Override
|
||||
public void restore() throws Exception {
|
||||
settings.restore();
|
||||
super.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of <tt>stty columns</tt> param.
|
||||
*/
|
||||
@Override
|
||||
public int getWidth() {
|
||||
int w = settings.getProperty("columns");
|
||||
return w < 1 ? DEFAULT_WIDTH : w;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of <tt>stty rows>/tt> param.
|
||||
*/
|
||||
@Override
|
||||
public int getHeight() {
|
||||
int h = settings.getProperty("rows");
|
||||
return h < 1 ? DEFAULT_HEIGHT : h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setEchoEnabled(final boolean enabled) {
|
||||
try {
|
||||
if (enabled) {
|
||||
settings.set("echo");
|
||||
}
|
||||
else {
|
||||
settings.set("-echo");
|
||||
}
|
||||
super.setEchoEnabled(enabled);
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (e instanceof InterruptedException) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
Log.error("Failed to ", (enabled ? "enable" : "disable"), " echo", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void disableInterruptCharacter()
|
||||
{
|
||||
try {
|
||||
settings.set("intr undef");
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (e instanceof InterruptedException) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
Log.error("Failed to disable interrupt character", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void enableInterruptCharacter()
|
||||
{
|
||||
try {
|
||||
settings.set("intr ^C");
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (e instanceof InterruptedException) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
Log.error("Failed to enable interrupt character", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline;
|
||||
|
||||
/**
|
||||
* An unsupported terminal.
|
||||
*
|
||||
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.0
|
||||
*/
|
||||
public class UnsupportedTerminal
|
||||
extends TerminalSupport
|
||||
{
|
||||
public UnsupportedTerminal() {
|
||||
super(false);
|
||||
setAnsiSupported(false);
|
||||
setEchoEnabled(true);
|
||||
}
|
||||
}
|
@ -0,0 +1,546 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import jdk.internal.jline.internal.Configuration;
|
||||
import jdk.internal.jline.internal.Log;
|
||||
//import org.fusesource.jansi.internal.WindowsSupport;
|
||||
//import org.fusesource.jansi.internal.Kernel32;
|
||||
//import static org.fusesource.jansi.internal.Kernel32.*;
|
||||
|
||||
import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_ECHO_INPUT;
|
||||
import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_LINE_INPUT;
|
||||
import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_PROCESSED_INPUT;
|
||||
import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_WINDOW_INPUT;
|
||||
|
||||
/**
|
||||
* Terminal implementation for Microsoft Windows. Terminal initialization in
|
||||
* {@link #init} is accomplished by extracting the
|
||||
* <em>jline_<i>version</i>.dll</em>, saving it to the system temporary
|
||||
* directoy (determined by the setting of the <em>java.io.tmpdir</em> System
|
||||
* property), loading the library, and then calling the Win32 APIs <a
|
||||
* href="http://msdn.microsoft.com/library/default.asp?
|
||||
* url=/library/en-us/dllproc/base/setconsolemode.asp">SetConsoleMode</a> and
|
||||
* <a href="http://msdn.microsoft.com/library/default.asp?
|
||||
* url=/library/en-us/dllproc/base/getconsolemode.asp">GetConsoleMode</a> to
|
||||
* disable character echoing.
|
||||
* <p/>
|
||||
* <p>
|
||||
* By default, the {@link #wrapInIfNeeded(java.io.InputStream)} method will attempt
|
||||
* to test to see if the specified {@link InputStream} is {@link System#in} or a wrapper
|
||||
* around {@link FileDescriptor#in}, and if so, will bypass the character reading to
|
||||
* directly invoke the readc() method in the JNI library. This is so the class
|
||||
* can read special keys (like arrow keys) which are otherwise inaccessible via
|
||||
* the {@link System#in} stream. Using JNI reading can be bypassed by setting
|
||||
* the <code>jline.WindowsTerminal.directConsole</code> system property
|
||||
* to <code>false</code>.
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.0
|
||||
*/
|
||||
public class WindowsTerminal
|
||||
extends TerminalSupport
|
||||
{
|
||||
public static final String DIRECT_CONSOLE = WindowsTerminal.class.getName() + ".directConsole";
|
||||
|
||||
public static final String ANSI = WindowsTerminal.class.getName() + ".ansi";
|
||||
|
||||
private boolean directConsole;
|
||||
|
||||
private int originalMode;
|
||||
|
||||
public WindowsTerminal() throws Exception {
|
||||
super(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
super.init();
|
||||
|
||||
// setAnsiSupported(Configuration.getBoolean(ANSI, true));
|
||||
setAnsiSupported(false);
|
||||
|
||||
//
|
||||
// FIXME: Need a way to disable direct console and sysin detection muck
|
||||
//
|
||||
|
||||
setDirectConsole(Configuration.getBoolean(DIRECT_CONSOLE, true));
|
||||
|
||||
this.originalMode = getConsoleMode();
|
||||
setConsoleMode(originalMode & ~ENABLE_ECHO_INPUT.code);
|
||||
setEchoEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the original terminal configuration, which can be used when
|
||||
* shutting down the console reader. The ConsoleReader cannot be
|
||||
* used after calling this method.
|
||||
*/
|
||||
@Override
|
||||
public void restore() throws Exception {
|
||||
// restore the old console mode
|
||||
setConsoleMode(originalMode);
|
||||
super.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
int w = getWindowsTerminalWidth();
|
||||
return w < 1 ? DEFAULT_WIDTH : w;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
int h = getWindowsTerminalHeight();
|
||||
return h < 1 ? DEFAULT_HEIGHT : h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEchoEnabled(final boolean enabled) {
|
||||
// Must set these four modes at the same time to make it work fine.
|
||||
if (enabled) {
|
||||
setConsoleMode(getConsoleMode() |
|
||||
ENABLE_ECHO_INPUT.code |
|
||||
ENABLE_LINE_INPUT.code |
|
||||
ENABLE_PROCESSED_INPUT.code |
|
||||
ENABLE_WINDOW_INPUT.code);
|
||||
}
|
||||
else {
|
||||
setConsoleMode(getConsoleMode() &
|
||||
~(ENABLE_LINE_INPUT.code |
|
||||
ENABLE_ECHO_INPUT.code |
|
||||
ENABLE_PROCESSED_INPUT.code |
|
||||
ENABLE_WINDOW_INPUT.code));
|
||||
}
|
||||
super.setEchoEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not to allow the use of the JNI console interaction.
|
||||
*/
|
||||
public void setDirectConsole(final boolean flag) {
|
||||
this.directConsole = flag;
|
||||
Log.debug("Direct console: ", flag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not to allow the use of the JNI console interaction.
|
||||
*/
|
||||
public Boolean getDirectConsole() {
|
||||
return directConsole;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public InputStream wrapInIfNeeded(InputStream in) throws IOException {
|
||||
if (directConsole && isSystemIn(in)) {
|
||||
return new InputStream() {
|
||||
private byte[] buf = null;
|
||||
int bufIdx = 0;
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
while (buf == null || bufIdx == buf.length) {
|
||||
buf = readConsoleInput();
|
||||
bufIdx = 0;
|
||||
}
|
||||
int c = buf[bufIdx] & 0xFF;
|
||||
bufIdx++;
|
||||
return c;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return super.wrapInIfNeeded(in);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isSystemIn(final InputStream in) throws IOException {
|
||||
if (in == null) {
|
||||
return false;
|
||||
}
|
||||
else if (in == System.in) {
|
||||
return true;
|
||||
}
|
||||
else if (in instanceof FileInputStream && ((FileInputStream) in).getFD() == FileDescriptor.in) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOutputEncoding() {
|
||||
int codepage = getConsoleOutputCodepage();
|
||||
//http://docs.oracle.com/javase/6/docs/technotes/guides/intl/encoding.doc.html
|
||||
String charsetMS = "ms" + codepage;
|
||||
if (java.nio.charset.Charset.isSupported(charsetMS)) {
|
||||
return charsetMS;
|
||||
}
|
||||
String charsetCP = "cp" + codepage;
|
||||
if (java.nio.charset.Charset.isSupported(charsetCP)) {
|
||||
return charsetCP;
|
||||
}
|
||||
Log.debug("can't figure out the Java Charset of this code page (" + codepage + ")...");
|
||||
return super.getOutputEncoding();
|
||||
}
|
||||
|
||||
//
|
||||
// Original code:
|
||||
//
|
||||
// private int getConsoleMode() {
|
||||
// return WindowsSupport.getConsoleMode();
|
||||
// }
|
||||
//
|
||||
// private void setConsoleMode(int mode) {
|
||||
// WindowsSupport.setConsoleMode(mode);
|
||||
// }
|
||||
//
|
||||
// private byte[] readConsoleInput() {
|
||||
// // XXX does how many events to read in one call matter?
|
||||
// INPUT_RECORD[] events = null;
|
||||
// try {
|
||||
// events = WindowsSupport.readConsoleInput(1);
|
||||
// } catch (IOException e) {
|
||||
// Log.debug("read Windows console input error: ", e);
|
||||
// }
|
||||
// if (events == null) {
|
||||
// return new byte[0];
|
||||
// }
|
||||
// StringBuilder sb = new StringBuilder();
|
||||
// for (int i = 0; i < events.length; i++ ) {
|
||||
// KEY_EVENT_RECORD keyEvent = events[i].keyEvent;
|
||||
// //Log.trace(keyEvent.keyDown? "KEY_DOWN" : "KEY_UP", "key code:", keyEvent.keyCode, "char:", (long)keyEvent.uchar);
|
||||
// if (keyEvent.keyDown) {
|
||||
// if (keyEvent.uchar > 0) {
|
||||
// // support some C1 control sequences: ALT + [@-_] (and [a-z]?) => ESC <ascii>
|
||||
// // http://en.wikipedia.org/wiki/C0_and_C1_control_codes#C1_set
|
||||
// final int altState = KEY_EVENT_RECORD.LEFT_ALT_PRESSED | KEY_EVENT_RECORD.RIGHT_ALT_PRESSED;
|
||||
// // Pressing "Alt Gr" is translated to Alt-Ctrl, hence it has to be checked that Ctrl is _not_ pressed,
|
||||
// // otherwise inserting of "Alt Gr" codes on non-US keyboards would yield errors
|
||||
// final int ctrlState = KEY_EVENT_RECORD.LEFT_CTRL_PRESSED | KEY_EVENT_RECORD.RIGHT_CTRL_PRESSED;
|
||||
// if (((keyEvent.uchar >= '@' && keyEvent.uchar <= '_') || (keyEvent.uchar >= 'a' && keyEvent.uchar <= 'z'))
|
||||
// && ((keyEvent.controlKeyState & altState) != 0) && ((keyEvent.controlKeyState & ctrlState) == 0)) {
|
||||
// sb.append('\u001B'); // ESC
|
||||
// }
|
||||
//
|
||||
// sb.append(keyEvent.uchar);
|
||||
// continue;
|
||||
// }
|
||||
// // virtual keycodes: http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
|
||||
// // just add support for basic editing keys (no control state, no numpad keys)
|
||||
// String escapeSequence = null;
|
||||
// switch (keyEvent.keyCode) {
|
||||
// case 0x21: // VK_PRIOR PageUp
|
||||
// escapeSequence = "\u001B[5~";
|
||||
// break;
|
||||
// case 0x22: // VK_NEXT PageDown
|
||||
// escapeSequence = "\u001B[6~";
|
||||
// break;
|
||||
// case 0x23: // VK_END
|
||||
// escapeSequence = "\u001B[4~";
|
||||
// break;
|
||||
// case 0x24: // VK_HOME
|
||||
// escapeSequence = "\u001B[1~";
|
||||
// break;
|
||||
// case 0x25: // VK_LEFT
|
||||
// escapeSequence = "\u001B[D";
|
||||
// break;
|
||||
// case 0x26: // VK_UP
|
||||
// escapeSequence = "\u001B[A";
|
||||
// break;
|
||||
// case 0x27: // VK_RIGHT
|
||||
// escapeSequence = "\u001B[C";
|
||||
// break;
|
||||
// case 0x28: // VK_DOWN
|
||||
// escapeSequence = "\u001B[B";
|
||||
// break;
|
||||
// case 0x2D: // VK_INSERT
|
||||
// escapeSequence = "\u001B[2~";
|
||||
// break;
|
||||
// case 0x2E: // VK_DELETE
|
||||
// escapeSequence = "\u001B[3~";
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// if (escapeSequence != null) {
|
||||
// for (int k = 0; k < keyEvent.repeatCount; k++) {
|
||||
// sb.append(escapeSequence);
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// // key up event
|
||||
// // support ALT+NumPad input method
|
||||
// if (keyEvent.keyCode == 0x12/*VK_MENU ALT key*/ && keyEvent.uchar > 0) {
|
||||
// sb.append(keyEvent.uchar);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return sb.toString().getBytes();
|
||||
// }
|
||||
//
|
||||
// private int getConsoleOutputCodepage() {
|
||||
// return Kernel32.GetConsoleOutputCP();
|
||||
// }
|
||||
//
|
||||
// private int getWindowsTerminalWidth() {
|
||||
// return WindowsSupport.getWindowsTerminalWidth();
|
||||
// }
|
||||
//
|
||||
// private int getWindowsTerminalHeight() {
|
||||
// return WindowsSupport.getWindowsTerminalHeight();
|
||||
// }
|
||||
|
||||
//
|
||||
// Native Bits
|
||||
//
|
||||
static {
|
||||
System.loadLibrary("le");
|
||||
initIDs();
|
||||
}
|
||||
|
||||
private static native void initIDs();
|
||||
|
||||
private native int getConsoleMode();
|
||||
|
||||
private native void setConsoleMode(int mode);
|
||||
|
||||
private byte[] readConsoleInput() {
|
||||
KEY_EVENT_RECORD keyEvent = readKeyEvent();
|
||||
|
||||
return convertKeys(keyEvent).getBytes();
|
||||
}
|
||||
|
||||
public static String convertKeys(KEY_EVENT_RECORD keyEvent) {
|
||||
if (keyEvent == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (keyEvent.keyDown) {
|
||||
if (keyEvent.uchar > 0) {
|
||||
// support some C1 control sequences: ALT + [@-_] (and [a-z]?) => ESC <ascii>
|
||||
// http://en.wikipedia.org/wiki/C0_and_C1_control_codes#C1_set
|
||||
final int altState = KEY_EVENT_RECORD.ALT_PRESSED;
|
||||
// Pressing "Alt Gr" is translated to Alt-Ctrl, hence it has to be checked that Ctrl is _not_ pressed,
|
||||
// otherwise inserting of "Alt Gr" codes on non-US keyboards would yield errors
|
||||
final int ctrlState = KEY_EVENT_RECORD.CTRL_PRESSED;
|
||||
|
||||
boolean handled = false;
|
||||
|
||||
if ((keyEvent.controlKeyState & ctrlState) != 0) {
|
||||
switch (keyEvent.keyCode) {
|
||||
case 0x43: //Ctrl-C
|
||||
sb.append("\003");
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((keyEvent.controlKeyState & KEY_EVENT_RECORD.SHIFT_PRESSED) != 0) {
|
||||
switch (keyEvent.keyCode) {
|
||||
case 0x09: //Shift-Tab
|
||||
sb.append("\033\133\132");
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
if (((keyEvent.uchar >= '@' && keyEvent.uchar <= '_') || (keyEvent.uchar >= 'a' && keyEvent.uchar <= 'z'))
|
||||
&& ((keyEvent.controlKeyState & altState) != 0) && ((keyEvent.controlKeyState & ctrlState) == 0)) {
|
||||
sb.append('\u001B'); // ESC
|
||||
}
|
||||
|
||||
sb.append(keyEvent.uchar);
|
||||
}
|
||||
} else {
|
||||
// virtual keycodes: http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
|
||||
// just add support for basic editing keys (no control state, no numpad keys)
|
||||
String escapeSequence = null;
|
||||
switch (keyEvent.keyCode) {
|
||||
case 0x21: // VK_PRIOR PageUp
|
||||
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[5~", "\u001B[5;%d~");
|
||||
break;
|
||||
case 0x22: // VK_NEXT PageDown
|
||||
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[6~", "\u001B[6;%d~");
|
||||
break;
|
||||
case 0x23: // VK_END
|
||||
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[4~", "\u001B[4;%d~");
|
||||
break;
|
||||
case 0x24: // VK_HOME
|
||||
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[1~", "\u001B[1;%d~");
|
||||
break;
|
||||
case 0x25: // VK_LEFT
|
||||
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[D", "\u001B[1;%dD");
|
||||
break;
|
||||
case 0x26: // VK_UP
|
||||
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[A", "\u001B[1;%dA");
|
||||
break;
|
||||
case 0x27: // VK_RIGHT
|
||||
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[C", "\u001B[1;%dC");
|
||||
break;
|
||||
case 0x28: // VK_DOWN
|
||||
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[B", "\u001B[1;%dB");
|
||||
break;
|
||||
case 0x2D: // VK_INSERT
|
||||
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[2~", "\u001B[2;%d~");
|
||||
break;
|
||||
case 0x2E: // VK_DELETE
|
||||
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[3~", "\u001B[3;%d~");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (escapeSequence != null) {
|
||||
for (int k = 0; k < keyEvent.repeatCount; k++) {
|
||||
sb.append(escapeSequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// key up event
|
||||
// support ALT+NumPad input method
|
||||
if (keyEvent.keyCode == 0x12/*VK_MENU ALT key*/ && keyEvent.uchar > 0) {
|
||||
sb.append(keyEvent.uchar);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String escapeSequence(int controlKeyState, String noControlSequence, String withControlSequence) {
|
||||
int controlNum = 1;
|
||||
|
||||
if ((controlKeyState & KEY_EVENT_RECORD.SHIFT_PRESSED) != 0) {
|
||||
controlNum += 1;
|
||||
}
|
||||
|
||||
if ((controlKeyState & KEY_EVENT_RECORD.ALT_PRESSED) != 0) {
|
||||
controlNum += 2;
|
||||
}
|
||||
|
||||
if ((controlKeyState & KEY_EVENT_RECORD.CTRL_PRESSED) != 0) {
|
||||
controlNum += 4;
|
||||
}
|
||||
|
||||
if (controlNum > 1) {
|
||||
return String.format(withControlSequence, controlNum);
|
||||
} else {
|
||||
return noControlSequence;
|
||||
}
|
||||
}
|
||||
|
||||
private native KEY_EVENT_RECORD readKeyEvent();
|
||||
|
||||
public static class KEY_EVENT_RECORD {
|
||||
public final static int ALT_PRESSED = 0x3;
|
||||
public final static int CTRL_PRESSED = 0xC;
|
||||
public final static int SHIFT_PRESSED = 0x10;
|
||||
public final boolean keyDown;
|
||||
public final char uchar;
|
||||
public final int controlKeyState;
|
||||
public final int keyCode;
|
||||
public final int repeatCount;
|
||||
|
||||
public KEY_EVENT_RECORD(boolean keyDown, char uchar, int controlKeyState, int keyCode, int repeatCount) {
|
||||
this.keyDown = keyDown;
|
||||
this.uchar = uchar;
|
||||
this.controlKeyState = controlKeyState;
|
||||
this.keyCode = keyCode;
|
||||
this.repeatCount = repeatCount;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private native int getConsoleOutputCodepage();
|
||||
|
||||
private native int getWindowsTerminalWidth();
|
||||
|
||||
private native int getWindowsTerminalHeight();
|
||||
|
||||
/**
|
||||
* Console mode
|
||||
* <p/>
|
||||
* Constants copied <tt>wincon.h</tt>.
|
||||
*/
|
||||
public static enum ConsoleMode
|
||||
{
|
||||
/**
|
||||
* The ReadFile or ReadConsole function returns only when a carriage return
|
||||
* character is read. If this mode is disable, the functions return when one
|
||||
* or more characters are available.
|
||||
*/
|
||||
ENABLE_LINE_INPUT(2),
|
||||
|
||||
/**
|
||||
* Characters read by the ReadFile or ReadConsole function are written to
|
||||
* the active screen buffer as they are read. This mode can be used only if
|
||||
* the ENABLE_LINE_INPUT mode is also enabled.
|
||||
*/
|
||||
ENABLE_ECHO_INPUT(4),
|
||||
|
||||
/**
|
||||
* CTRL+C is processed by the system and is not placed in the input buffer.
|
||||
* If the input buffer is being read by ReadFile or ReadConsole, other
|
||||
* control keys are processed by the system and are not returned in the
|
||||
* ReadFile or ReadConsole buffer. If the ENABLE_LINE_INPUT mode is also
|
||||
* enabled, backspace, carriage return, and linefeed characters are handled
|
||||
* by the system.
|
||||
*/
|
||||
ENABLE_PROCESSED_INPUT(1),
|
||||
|
||||
/**
|
||||
* User interactions that change the size of the console screen buffer are
|
||||
* reported in the console's input buffee. Information about these events
|
||||
* can be read from the input buffer by applications using
|
||||
* theReadConsoleInput function, but not by those using ReadFile
|
||||
* orReadConsole.
|
||||
*/
|
||||
ENABLE_WINDOW_INPUT(8),
|
||||
|
||||
/**
|
||||
* If the mouse pointer is within the borders of the console window and the
|
||||
* window has the keyboard focus, mouse events generated by mouse movement
|
||||
* and button presses are placed in the input buffer. These events are
|
||||
* discarded by ReadFile or ReadConsole, even when this mode is enabled.
|
||||
*/
|
||||
ENABLE_MOUSE_INPUT(16),
|
||||
|
||||
/**
|
||||
* When enabled, text entered in a console window will be inserted at the
|
||||
* current cursor location and all text following that location will not be
|
||||
* overwritten. When disabled, all following text will be overwritten. An OR
|
||||
* operation must be performed with this flag and the ENABLE_EXTENDED_FLAGS
|
||||
* flag to enable this functionality.
|
||||
*/
|
||||
ENABLE_PROCESSED_OUTPUT(1),
|
||||
|
||||
/**
|
||||
* This flag enables the user to use the mouse to select and edit text. To
|
||||
* enable this option, use the OR to combine this flag with
|
||||
* ENABLE_EXTENDED_FLAGS.
|
||||
*/
|
||||
ENABLE_WRAP_AT_EOL_OUTPUT(2),;
|
||||
|
||||
public final int code;
|
||||
|
||||
ConsoleMode(final int code) {
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,399 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.console;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jdk.internal.jline.internal.Log;
|
||||
|
||||
/**
|
||||
* @author St\u00E5le W. Pedersen <stale.pedersen@jboss.org>
|
||||
*/
|
||||
public class ConsoleKeys {
|
||||
|
||||
private KeyMap keys;
|
||||
|
||||
private Map<String, KeyMap> keyMaps;
|
||||
private Map<String, String> variables = new HashMap<String,String>();
|
||||
|
||||
public ConsoleKeys(String appName, URL inputrcUrl) {
|
||||
keyMaps = KeyMap.keyMaps();
|
||||
loadKeys(appName, inputrcUrl);
|
||||
}
|
||||
|
||||
protected boolean isViEditMode() {
|
||||
return keys.isViKeyMap();
|
||||
}
|
||||
|
||||
protected boolean setKeyMap (String name) {
|
||||
KeyMap map = keyMaps.get(name);
|
||||
if (map == null) {
|
||||
return false;
|
||||
}
|
||||
this.keys = map;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Map<String, KeyMap> getKeyMaps() {
|
||||
return keyMaps;
|
||||
}
|
||||
|
||||
protected KeyMap getKeys() {
|
||||
return keys;
|
||||
}
|
||||
|
||||
protected void setKeys(KeyMap keys) {
|
||||
this.keys = keys;
|
||||
}
|
||||
|
||||
protected boolean getViEditMode() {
|
||||
return keys.isViKeyMap ();
|
||||
}
|
||||
|
||||
protected void loadKeys(String appName, URL inputrcUrl) {
|
||||
keys = keyMaps.get(KeyMap.EMACS);
|
||||
|
||||
try {
|
||||
InputStream input = inputrcUrl.openStream();
|
||||
try {
|
||||
loadKeys(input, appName);
|
||||
Log.debug("Loaded user configuration: ", inputrcUrl);
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
input.close();
|
||||
} catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
if (inputrcUrl.getProtocol().equals("file")) {
|
||||
File file = new File(inputrcUrl.getPath());
|
||||
if (file.exists()) {
|
||||
Log.warn("Unable to read user configuration: ", inputrcUrl, e);
|
||||
}
|
||||
} else {
|
||||
Log.warn("Unable to read user configuration: ", inputrcUrl, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadKeys(InputStream input, String appName) throws IOException {
|
||||
BufferedReader reader = new BufferedReader( new java.io.InputStreamReader( input ) );
|
||||
String line;
|
||||
boolean parsing = true;
|
||||
List<Boolean> ifsStack = new ArrayList<Boolean>();
|
||||
while ( (line = reader.readLine()) != null ) {
|
||||
try {
|
||||
line = line.trim();
|
||||
if (line.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
if (line.charAt(0) == '#') {
|
||||
continue;
|
||||
}
|
||||
int i = 0;
|
||||
if (line.charAt(i) == '$') {
|
||||
String cmd;
|
||||
String args;
|
||||
for (++i; i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t'); i++);
|
||||
int s = i;
|
||||
for (; i < line.length() && (line.charAt(i) != ' ' && line.charAt(i) != '\t'); i++);
|
||||
cmd = line.substring(s, i);
|
||||
for (; i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t'); i++);
|
||||
s = i;
|
||||
for (; i < line.length() && (line.charAt(i) != ' ' && line.charAt(i) != '\t'); i++);
|
||||
args = line.substring(s, i);
|
||||
if ("if".equalsIgnoreCase(cmd)) {
|
||||
ifsStack.add( parsing );
|
||||
if (!parsing) {
|
||||
continue;
|
||||
}
|
||||
if (args.startsWith("term=")) {
|
||||
// TODO
|
||||
} else if (args.startsWith("mode=")) {
|
||||
if (args.equalsIgnoreCase("mode=vi")) {
|
||||
parsing = isViEditMode();
|
||||
} else if (args.equals("mode=emacs")) {
|
||||
parsing = !isViEditMode();
|
||||
} else {
|
||||
parsing = false;
|
||||
}
|
||||
} else {
|
||||
parsing = args.equalsIgnoreCase(appName);
|
||||
}
|
||||
} else if ("else".equalsIgnoreCase(cmd)) {
|
||||
if (ifsStack.isEmpty()) {
|
||||
throw new IllegalArgumentException("$else found without matching $if");
|
||||
}
|
||||
boolean invert = true;
|
||||
for (boolean b : ifsStack) {
|
||||
if (!b) {
|
||||
invert = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (invert) {
|
||||
parsing = !parsing;
|
||||
}
|
||||
} else if ("endif".equalsIgnoreCase(cmd)) {
|
||||
if (ifsStack.isEmpty()) {
|
||||
throw new IllegalArgumentException("endif found without matching $if");
|
||||
}
|
||||
parsing = ifsStack.remove( ifsStack.size() - 1 );
|
||||
} else if ("include".equalsIgnoreCase(cmd)) {
|
||||
// TODO
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!parsing) {
|
||||
continue;
|
||||
}
|
||||
boolean equivalency;
|
||||
String keySeq = "";
|
||||
if (line.charAt(i++) == '"') {
|
||||
boolean esc = false;
|
||||
for (;; i++) {
|
||||
if (i >= line.length()) {
|
||||
throw new IllegalArgumentException("Missing closing quote on line '" + line + "'");
|
||||
}
|
||||
if (esc) {
|
||||
esc = false;
|
||||
} else if (line.charAt(i) == '\\') {
|
||||
esc = true;
|
||||
} else if (line.charAt(i) == '"') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (; i < line.length() && line.charAt(i) != ':'
|
||||
&& line.charAt(i) != ' ' && line.charAt(i) != '\t'
|
||||
; i++);
|
||||
keySeq = line.substring(0, i);
|
||||
equivalency = (i + 1 < line.length() && line.charAt(i) == ':' && line.charAt(i + 1) == '=');
|
||||
i++;
|
||||
if (equivalency) {
|
||||
i++;
|
||||
}
|
||||
if (keySeq.equalsIgnoreCase("set")) {
|
||||
String key;
|
||||
String val;
|
||||
for (; i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t'); i++);
|
||||
int s = i;
|
||||
for (; i < line.length() && (line.charAt(i) != ' ' && line.charAt(i) != '\t'); i++);
|
||||
key = line.substring( s, i );
|
||||
for (; i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t'); i++);
|
||||
s = i;
|
||||
for (; i < line.length() && (line.charAt(i) != ' ' && line.charAt(i) != '\t'); i++);
|
||||
val = line.substring( s, i );
|
||||
setVar( key, val );
|
||||
} else {
|
||||
for (; i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t'); i++);
|
||||
int start = i;
|
||||
if (i < line.length() && (line.charAt(i) == '\'' || line.charAt(i) == '\"')) {
|
||||
char delim = line.charAt(i++);
|
||||
boolean esc = false;
|
||||
for (;; i++) {
|
||||
if (i >= line.length()) {
|
||||
break;
|
||||
}
|
||||
if (esc) {
|
||||
esc = false;
|
||||
} else if (line.charAt(i) == '\\') {
|
||||
esc = true;
|
||||
} else if (line.charAt(i) == delim) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (; i < line.length() && line.charAt(i) != ' ' && line.charAt(i) != '\t'; i++);
|
||||
String val = line.substring(Math.min(start, line.length()), Math.min(i, line.length()));
|
||||
if (keySeq.charAt(0) == '"') {
|
||||
keySeq = translateQuoted(keySeq);
|
||||
} else {
|
||||
// Bind key name
|
||||
String keyName = keySeq.lastIndexOf('-') > 0 ? keySeq.substring( keySeq.lastIndexOf('-') + 1 ) : keySeq;
|
||||
char key = getKeyFromName(keyName);
|
||||
keyName = keySeq.toLowerCase();
|
||||
keySeq = "";
|
||||
if (keyName.contains("meta-") || keyName.contains("m-")) {
|
||||
keySeq += "\u001b";
|
||||
}
|
||||
if (keyName.contains("control-") || keyName.contains("c-") || keyName.contains("ctrl-")) {
|
||||
key = (char)(Character.toUpperCase( key ) & 0x1f);
|
||||
}
|
||||
keySeq += key;
|
||||
}
|
||||
if (val.length() > 0 && (val.charAt(0) == '\'' || val.charAt(0) == '\"')) {
|
||||
keys.bind( keySeq, translateQuoted(val) );
|
||||
} else {
|
||||
String operationName = val.replace('-', '_').toUpperCase();
|
||||
try {
|
||||
keys.bind(keySeq, Operation.valueOf(operationName));
|
||||
} catch(IllegalArgumentException e) {
|
||||
Log.info("Unable to bind key for unsupported operation: ", val);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.warn("Unable to parse user configuration: ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String translateQuoted(String keySeq) {
|
||||
int i;
|
||||
String str = keySeq.substring( 1, keySeq.length() - 1 );
|
||||
keySeq = "";
|
||||
for (i = 0; i < str.length(); i++) {
|
||||
char c = str.charAt(i);
|
||||
if (c == '\\') {
|
||||
boolean ctrl = str.regionMatches(i, "\\C-", 0, 3)|| str.regionMatches(i, "\\M-\\C-", 0, 6);
|
||||
boolean meta = str.regionMatches(i, "\\M-", 0, 3)|| str.regionMatches(i, "\\C-\\M-", 0, 6);
|
||||
i += (meta ? 3 : 0) + (ctrl ? 3 : 0) + (!meta && !ctrl ? 1 : 0);
|
||||
if (i >= str.length()) {
|
||||
break;
|
||||
}
|
||||
c = str.charAt(i);
|
||||
if (meta) {
|
||||
keySeq += "\u001b";
|
||||
}
|
||||
if (ctrl) {
|
||||
c = c == '?' ? 0x7f : (char)(Character.toUpperCase( c ) & 0x1f);
|
||||
}
|
||||
if (!meta && !ctrl) {
|
||||
switch (c) {
|
||||
case 'a': c = 0x07; break;
|
||||
case 'b': c = '\b'; break;
|
||||
case 'd': c = 0x7f; break;
|
||||
case 'e': c = 0x1b; break;
|
||||
case 'f': c = '\f'; break;
|
||||
case 'n': c = '\n'; break;
|
||||
case 'r': c = '\r'; break;
|
||||
case 't': c = '\t'; break;
|
||||
case 'v': c = 0x0b; break;
|
||||
case '\\': c = '\\'; break;
|
||||
case '0': case '1': case '2': case '3':
|
||||
case '4': case '5': case '6': case '7':
|
||||
c = 0;
|
||||
for (int j = 0; j < 3; j++, i++) {
|
||||
if (i >= str.length()) {
|
||||
break;
|
||||
}
|
||||
int k = Character.digit(str.charAt(i), 8);
|
||||
if (k < 0) {
|
||||
break;
|
||||
}
|
||||
c = (char)(c * 8 + k);
|
||||
}
|
||||
c &= 0xFF;
|
||||
break;
|
||||
case 'x':
|
||||
i++;
|
||||
c = 0;
|
||||
for (int j = 0; j < 2; j++, i++) {
|
||||
if (i >= str.length()) {
|
||||
break;
|
||||
}
|
||||
int k = Character.digit(str.charAt(i), 16);
|
||||
if (k < 0) {
|
||||
break;
|
||||
}
|
||||
c = (char)(c * 16 + k);
|
||||
}
|
||||
c &= 0xFF;
|
||||
break;
|
||||
case 'u':
|
||||
i++;
|
||||
c = 0;
|
||||
for (int j = 0; j < 4; j++, i++) {
|
||||
if (i >= str.length()) {
|
||||
break;
|
||||
}
|
||||
int k = Character.digit(str.charAt(i), 16);
|
||||
if (k < 0) {
|
||||
break;
|
||||
}
|
||||
c = (char)(c * 16 + k);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
keySeq += c;
|
||||
} else {
|
||||
keySeq += c;
|
||||
}
|
||||
}
|
||||
return keySeq;
|
||||
}
|
||||
|
||||
private char getKeyFromName(String name) {
|
||||
if ("DEL".equalsIgnoreCase(name) || "Rubout".equalsIgnoreCase(name)) {
|
||||
return 0x7f;
|
||||
} else if ("ESC".equalsIgnoreCase(name) || "Escape".equalsIgnoreCase(name)) {
|
||||
return '\033';
|
||||
} else if ("LFD".equalsIgnoreCase(name) || "NewLine".equalsIgnoreCase(name)) {
|
||||
return '\n';
|
||||
} else if ("RET".equalsIgnoreCase(name) || "Return".equalsIgnoreCase(name)) {
|
||||
return '\r';
|
||||
} else if ("SPC".equalsIgnoreCase(name) || "Space".equalsIgnoreCase(name)) {
|
||||
return ' ';
|
||||
} else if ("Tab".equalsIgnoreCase(name)) {
|
||||
return '\t';
|
||||
} else {
|
||||
return name.charAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void setVar(String key, String val) {
|
||||
if ("keymap".equalsIgnoreCase(key)) {
|
||||
if (keyMaps.containsKey(val)) {
|
||||
keys = keyMaps.get(val);
|
||||
}
|
||||
} else if ("editing-mode".equals(key)) {
|
||||
if ("vi".equalsIgnoreCase(val)) {
|
||||
keys = keyMaps.get(KeyMap.VI_INSERT);
|
||||
} else if ("emacs".equalsIgnoreCase(key)) {
|
||||
keys = keyMaps.get(KeyMap.EMACS);
|
||||
}
|
||||
} else if ("blink-matching-paren".equals(key)) {
|
||||
if ("on".equalsIgnoreCase(val)) {
|
||||
keys.setBlinkMatchingParen(true);
|
||||
} else if ("off".equalsIgnoreCase(val)) {
|
||||
keys.setBlinkMatchingParen(false);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Technically variables should be defined as a functor class
|
||||
* so that validation on the variable value can be done at parse
|
||||
* time. This is a stop-gap.
|
||||
*/
|
||||
variables.put(key, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of a variable that was set in the .inputrc file
|
||||
* during processing
|
||||
* @param var The variable name
|
||||
* @return The variable value.
|
||||
*/
|
||||
public String getVariable(String var) {
|
||||
return variables.get (var);
|
||||
}
|
||||
}
|
4006
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/ConsoleReader.java
Normal file
4006
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/ConsoleReader.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.console;
|
||||
|
||||
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* A holder for a {@link StringBuilder} that also contains the current cursor position.
|
||||
*
|
||||
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.0
|
||||
*/
|
||||
public class CursorBuffer
|
||||
{
|
||||
private boolean overTyping = false;
|
||||
|
||||
public int cursor = 0;
|
||||
|
||||
public final StringBuilder buffer = new StringBuilder();
|
||||
|
||||
public CursorBuffer copy () {
|
||||
CursorBuffer that = new CursorBuffer();
|
||||
that.overTyping = this.overTyping;
|
||||
that.cursor = this.cursor;
|
||||
that.buffer.append (this.toString());
|
||||
|
||||
return that;
|
||||
}
|
||||
|
||||
public boolean isOverTyping() {
|
||||
return overTyping;
|
||||
}
|
||||
|
||||
public void setOverTyping(final boolean b) {
|
||||
overTyping = b;
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return buffer.length();
|
||||
}
|
||||
|
||||
public char nextChar() {
|
||||
if (cursor == buffer.length()) {
|
||||
return 0;
|
||||
} else {
|
||||
return buffer.charAt(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
public char current() {
|
||||
if (cursor <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return buffer.charAt(cursor - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the specific character into the buffer, setting the cursor position
|
||||
* ahead one. The text may overwrite or insert based on the current setting
|
||||
* of {@link #isOverTyping}.
|
||||
*
|
||||
* @param c the character to insert
|
||||
*/
|
||||
public void write(final char c) {
|
||||
buffer.insert(cursor++, c);
|
||||
if (isOverTyping() && cursor < buffer.length()) {
|
||||
buffer.deleteCharAt(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the specified chars into the buffer, setting the cursor to the end of the insertion point.
|
||||
*/
|
||||
public void write(final CharSequence str) {
|
||||
checkNotNull(str);
|
||||
|
||||
if (buffer.length() == 0) {
|
||||
buffer.append(str);
|
||||
}
|
||||
else {
|
||||
buffer.insert(cursor, str);
|
||||
}
|
||||
|
||||
cursor += str.length();
|
||||
|
||||
if (isOverTyping() && cursor < buffer.length()) {
|
||||
buffer.delete(cursor, (cursor + str.length()));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean clear() {
|
||||
if (buffer.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer.delete(0, buffer.length());
|
||||
cursor = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
public String upToCursor() {
|
||||
if (cursor <= 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return buffer.substring(0, cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,578 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.console;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The KeyMap class contains all bindings from keys to operations.
|
||||
*
|
||||
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
|
||||
* @since 2.6
|
||||
*/
|
||||
public class KeyMap {
|
||||
|
||||
public static final String VI_MOVE = "vi-move";
|
||||
public static final String VI_INSERT = "vi-insert";
|
||||
public static final String EMACS = "emacs";
|
||||
public static final String EMACS_STANDARD = "emacs-standard";
|
||||
public static final String EMACS_CTLX = "emacs-ctlx";
|
||||
public static final String EMACS_META = "emacs-meta";
|
||||
|
||||
private static final int KEYMAP_LENGTH = 256;
|
||||
|
||||
private static final Object NULL_FUNCTION = new Object();
|
||||
|
||||
private Object[] mapping = new Object[KEYMAP_LENGTH];
|
||||
private Object anotherKey = null;
|
||||
private String name;
|
||||
private boolean isViKeyMap;
|
||||
|
||||
public KeyMap(String name, boolean isViKeyMap) {
|
||||
this(name, new Object[KEYMAP_LENGTH], isViKeyMap);
|
||||
}
|
||||
|
||||
protected KeyMap(String name, Object[] mapping, boolean isViKeyMap) {
|
||||
this.mapping = mapping;
|
||||
this.name = name;
|
||||
this.isViKeyMap = isViKeyMap;
|
||||
}
|
||||
|
||||
public boolean isViKeyMap() {
|
||||
return isViKeyMap;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Object getAnotherKey() {
|
||||
return anotherKey;
|
||||
}
|
||||
|
||||
public void from(KeyMap other) {
|
||||
this.mapping = other.mapping;
|
||||
this.anotherKey = other.anotherKey;
|
||||
}
|
||||
|
||||
public Object getBound( CharSequence keySeq ) {
|
||||
if (keySeq != null && keySeq.length() > 0) {
|
||||
KeyMap map = this;
|
||||
for (int i = 0; i < keySeq.length(); i++) {
|
||||
char c = keySeq.charAt(i);
|
||||
if (c > 255) {
|
||||
return Operation.SELF_INSERT;
|
||||
}
|
||||
if (map.mapping[c] instanceof KeyMap) {
|
||||
if (i == keySeq.length() - 1) {
|
||||
return map.mapping[c];
|
||||
} else {
|
||||
map = (KeyMap) map.mapping[c];
|
||||
}
|
||||
} else {
|
||||
return map.mapping[c];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void bindIfNotBound( CharSequence keySeq, Object function ) {
|
||||
|
||||
bind (this, keySeq, function, true);
|
||||
}
|
||||
|
||||
public void bind( CharSequence keySeq, Object function ) {
|
||||
|
||||
bind (this, keySeq, function, false);
|
||||
}
|
||||
|
||||
private static void bind( KeyMap map, CharSequence keySeq, Object function ) {
|
||||
|
||||
bind (map, keySeq, function, false);
|
||||
}
|
||||
|
||||
private static void bind( KeyMap map, CharSequence keySeq, Object function,
|
||||
boolean onlyIfNotBound ) {
|
||||
|
||||
if (keySeq != null && keySeq.length() > 0) {
|
||||
for (int i = 0; i < keySeq.length(); i++) {
|
||||
char c = keySeq.charAt(i);
|
||||
if (c >= map.mapping.length) {
|
||||
return;
|
||||
}
|
||||
if (i < keySeq.length() - 1) {
|
||||
if (!(map.mapping[c] instanceof KeyMap)) {
|
||||
KeyMap m = new KeyMap("anonymous", false);
|
||||
if (map.mapping[c] != Operation.DO_LOWERCASE_VERSION) {
|
||||
m.anotherKey = map.mapping[c];
|
||||
}
|
||||
map.mapping[c] = m;
|
||||
}
|
||||
map = (KeyMap) map.mapping[c];
|
||||
} else {
|
||||
if (function == null) {
|
||||
function = NULL_FUNCTION;
|
||||
}
|
||||
if (map.mapping[c] instanceof KeyMap) {
|
||||
map.anotherKey = function;
|
||||
} else {
|
||||
Object op = map.mapping[c];
|
||||
if (onlyIfNotBound == false
|
||||
|| op == null
|
||||
|| op == Operation.DO_LOWERCASE_VERSION
|
||||
|| op == Operation.VI_MOVEMENT_MODE ) {
|
||||
|
||||
}
|
||||
|
||||
map.mapping[c] = function;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setBlinkMatchingParen(boolean on) {
|
||||
if (on) {
|
||||
bind( "}", Operation.INSERT_CLOSE_CURLY );
|
||||
bind( ")", Operation.INSERT_CLOSE_PAREN );
|
||||
bind( "]", Operation.INSERT_CLOSE_SQUARE );
|
||||
}
|
||||
}
|
||||
|
||||
private static void bindArrowKeys(KeyMap map) {
|
||||
|
||||
// MS-DOS
|
||||
bind( map, "\033[0A", Operation.PREVIOUS_HISTORY );
|
||||
bind( map, "\033[0B", Operation.BACKWARD_CHAR );
|
||||
bind( map, "\033[0C", Operation.FORWARD_CHAR );
|
||||
bind( map, "\033[0D", Operation.NEXT_HISTORY );
|
||||
|
||||
// Windows
|
||||
bind( map, "\340\000", Operation.KILL_WHOLE_LINE );
|
||||
bind( map, "\340\107", Operation.BEGINNING_OF_LINE );
|
||||
bind( map, "\340\110", Operation.PREVIOUS_HISTORY );
|
||||
bind( map, "\340\111", Operation.BEGINNING_OF_HISTORY );
|
||||
bind( map, "\340\113", Operation.BACKWARD_CHAR );
|
||||
bind( map, "\340\115", Operation.FORWARD_CHAR );
|
||||
bind( map, "\340\117", Operation.END_OF_LINE );
|
||||
bind( map, "\340\120", Operation.NEXT_HISTORY );
|
||||
bind( map, "\340\121", Operation.END_OF_HISTORY );
|
||||
bind( map, "\340\122", Operation.OVERWRITE_MODE );
|
||||
bind( map, "\340\123", Operation.DELETE_CHAR );
|
||||
|
||||
bind( map, "\000\107", Operation.BEGINNING_OF_LINE );
|
||||
bind( map, "\000\110", Operation.PREVIOUS_HISTORY );
|
||||
bind( map, "\000\111", Operation.BEGINNING_OF_HISTORY );
|
||||
bind( map, "\000\110", Operation.PREVIOUS_HISTORY );
|
||||
bind( map, "\000\113", Operation.BACKWARD_CHAR );
|
||||
bind( map, "\000\115", Operation.FORWARD_CHAR );
|
||||
bind( map, "\000\117", Operation.END_OF_LINE );
|
||||
bind( map, "\000\120", Operation.NEXT_HISTORY );
|
||||
bind( map, "\000\121", Operation.END_OF_HISTORY );
|
||||
bind( map, "\000\122", Operation.OVERWRITE_MODE );
|
||||
bind( map, "\000\123", Operation.DELETE_CHAR );
|
||||
|
||||
bind( map, "\033[A", Operation.PREVIOUS_HISTORY );
|
||||
bind( map, "\033[B", Operation.NEXT_HISTORY );
|
||||
bind( map, "\033[C", Operation.FORWARD_CHAR );
|
||||
bind( map, "\033[D", Operation.BACKWARD_CHAR );
|
||||
bind( map, "\033[H", Operation.BEGINNING_OF_LINE );
|
||||
bind( map, "\033[F", Operation.END_OF_LINE );
|
||||
|
||||
bind( map, "\033OA", Operation.PREVIOUS_HISTORY );
|
||||
bind( map, "\033OB", Operation.NEXT_HISTORY );
|
||||
bind( map, "\033OC", Operation.FORWARD_CHAR );
|
||||
bind( map, "\033OD", Operation.BACKWARD_CHAR );
|
||||
bind( map, "\033OH", Operation.BEGINNING_OF_LINE );
|
||||
bind( map, "\033OF", Operation.END_OF_LINE );
|
||||
|
||||
bind( map, "\033[1~", Operation.BEGINNING_OF_LINE);
|
||||
bind( map, "\033[4~", Operation.END_OF_LINE);
|
||||
bind( map, "\033[3~", Operation.DELETE_CHAR);
|
||||
|
||||
// MINGW32
|
||||
bind( map, "\0340H", Operation.PREVIOUS_HISTORY );
|
||||
bind( map, "\0340P", Operation.NEXT_HISTORY );
|
||||
bind( map, "\0340M", Operation.FORWARD_CHAR );
|
||||
bind( map, "\0340K", Operation.BACKWARD_CHAR );
|
||||
}
|
||||
|
||||
// public boolean isConvertMetaCharsToAscii() {
|
||||
// return convertMetaCharsToAscii;
|
||||
// }
|
||||
|
||||
// public void setConvertMetaCharsToAscii(boolean convertMetaCharsToAscii) {
|
||||
// this.convertMetaCharsToAscii = convertMetaCharsToAscii;
|
||||
// }
|
||||
|
||||
public static boolean isMeta( char c ) {
|
||||
return c > 0x7f && c <= 0xff;
|
||||
}
|
||||
|
||||
public static char unMeta( char c ) {
|
||||
return (char) (c & 0x7F);
|
||||
}
|
||||
|
||||
public static char meta( char c ) {
|
||||
return (char) (c | 0x80);
|
||||
}
|
||||
|
||||
public static Map<String, KeyMap> keyMaps() {
|
||||
Map<String, KeyMap> keyMaps = new HashMap<String, KeyMap>();
|
||||
|
||||
KeyMap emacs = emacs();
|
||||
bindArrowKeys(emacs);
|
||||
keyMaps.put(EMACS, emacs);
|
||||
keyMaps.put(EMACS_STANDARD, emacs);
|
||||
keyMaps.put(EMACS_CTLX, (KeyMap) emacs.getBound("\u0018"));
|
||||
keyMaps.put(EMACS_META, (KeyMap) emacs.getBound("\u001b"));
|
||||
|
||||
KeyMap viMov = viMovement();
|
||||
bindArrowKeys(viMov);
|
||||
keyMaps.put(VI_MOVE, viMov);
|
||||
keyMaps.put("vi-command", viMov);
|
||||
|
||||
KeyMap viIns = viInsertion();
|
||||
bindArrowKeys(viIns);
|
||||
keyMaps.put(VI_INSERT, viIns);
|
||||
keyMaps.put("vi", viIns);
|
||||
|
||||
return keyMaps;
|
||||
}
|
||||
|
||||
public static KeyMap emacs() {
|
||||
Object[] map = new Object[KEYMAP_LENGTH];
|
||||
Object[] ctrl = new Object[] {
|
||||
// Control keys.
|
||||
Operation.SET_MARK, /* Control-@ */
|
||||
Operation.BEGINNING_OF_LINE, /* Control-A */
|
||||
Operation.BACKWARD_CHAR, /* Control-B */
|
||||
Operation.INTERRUPT, /* Control-C */
|
||||
Operation.EXIT_OR_DELETE_CHAR, /* Control-D */
|
||||
Operation.END_OF_LINE, /* Control-E */
|
||||
Operation.FORWARD_CHAR, /* Control-F */
|
||||
Operation.ABORT, /* Control-G */
|
||||
Operation.BACKWARD_DELETE_CHAR, /* Control-H */
|
||||
Operation.COMPLETE, /* Control-I */
|
||||
Operation.ACCEPT_LINE, /* Control-J */
|
||||
Operation.KILL_LINE, /* Control-K */
|
||||
Operation.CLEAR_SCREEN, /* Control-L */
|
||||
Operation.ACCEPT_LINE, /* Control-M */
|
||||
Operation.NEXT_HISTORY, /* Control-N */
|
||||
null, /* Control-O */
|
||||
Operation.PREVIOUS_HISTORY, /* Control-P */
|
||||
Operation.QUOTED_INSERT, /* Control-Q */
|
||||
Operation.REVERSE_SEARCH_HISTORY, /* Control-R */
|
||||
Operation.FORWARD_SEARCH_HISTORY, /* Control-S */
|
||||
Operation.TRANSPOSE_CHARS, /* Control-T */
|
||||
Operation.UNIX_LINE_DISCARD, /* Control-U */
|
||||
Operation.QUOTED_INSERT, /* Control-V */
|
||||
Operation.UNIX_WORD_RUBOUT, /* Control-W */
|
||||
emacsCtrlX(), /* Control-X */
|
||||
Operation.YANK, /* Control-Y */
|
||||
null, /* Control-Z */
|
||||
emacsMeta(), /* Control-[ */
|
||||
null, /* Control-\ */
|
||||
Operation.CHARACTER_SEARCH, /* Control-] */
|
||||
null, /* Control-^ */
|
||||
Operation.UNDO, /* Control-_ */
|
||||
};
|
||||
System.arraycopy( ctrl, 0, map, 0, ctrl.length );
|
||||
for (int i = 32; i < 256; i++) {
|
||||
map[i] = Operation.SELF_INSERT;
|
||||
}
|
||||
map[DELETE] = Operation.BACKWARD_DELETE_CHAR;
|
||||
return new KeyMap(EMACS, map, false);
|
||||
}
|
||||
|
||||
public static final char CTRL_D = (char) 4;
|
||||
public static final char CTRL_G = (char) 7;
|
||||
public static final char CTRL_H = (char) 8;
|
||||
public static final char CTRL_I = (char) 9;
|
||||
public static final char CTRL_J = (char) 10;
|
||||
public static final char CTRL_M = (char) 13;
|
||||
public static final char CTRL_R = (char) 18;
|
||||
public static final char CTRL_S = (char) 19;
|
||||
public static final char CTRL_U = (char) 21;
|
||||
public static final char CTRL_X = (char) 24;
|
||||
public static final char CTRL_Y = (char) 25;
|
||||
public static final char ESCAPE = (char) 27; /* Ctrl-[ */
|
||||
public static final char CTRL_OB = (char) 27; /* Ctrl-[ */
|
||||
public static final char CTRL_CB = (char) 29; /* Ctrl-] */
|
||||
|
||||
public static final int DELETE = (char) 127;
|
||||
|
||||
public static KeyMap emacsCtrlX() {
|
||||
Object[] map = new Object[KEYMAP_LENGTH];
|
||||
map[CTRL_G] = Operation.ABORT;
|
||||
map[CTRL_R] = Operation.RE_READ_INIT_FILE;
|
||||
map[CTRL_U] = Operation.UNDO;
|
||||
map[CTRL_X] = Operation.EXCHANGE_POINT_AND_MARK;
|
||||
map['('] = Operation.START_KBD_MACRO;
|
||||
map[')'] = Operation.END_KBD_MACRO;
|
||||
for (int i = 'A'; i <= 'Z'; i++) {
|
||||
map[i] = Operation.DO_LOWERCASE_VERSION;
|
||||
}
|
||||
map['e'] = Operation.CALL_LAST_KBD_MACRO;
|
||||
map[DELETE] = Operation.KILL_LINE;
|
||||
return new KeyMap(EMACS_CTLX, map, false);
|
||||
}
|
||||
|
||||
public static KeyMap emacsMeta() {
|
||||
Object[] map = new Object[KEYMAP_LENGTH];
|
||||
map[CTRL_G] = Operation.ABORT;
|
||||
map[CTRL_H] = Operation.BACKWARD_KILL_WORD;
|
||||
map[CTRL_I] = Operation.TAB_INSERT;
|
||||
map[CTRL_J] = Operation.VI_EDITING_MODE;
|
||||
map[CTRL_M] = Operation.VI_EDITING_MODE;
|
||||
map[CTRL_R] = Operation.REVERT_LINE;
|
||||
map[CTRL_Y] = Operation.YANK_NTH_ARG;
|
||||
map[CTRL_OB] = Operation.COMPLETE;
|
||||
map[CTRL_CB] = Operation.CHARACTER_SEARCH_BACKWARD;
|
||||
map[' '] = Operation.SET_MARK;
|
||||
map['#'] = Operation.INSERT_COMMENT;
|
||||
map['&'] = Operation.TILDE_EXPAND;
|
||||
map['*'] = Operation.INSERT_COMPLETIONS;
|
||||
map['-'] = Operation.DIGIT_ARGUMENT;
|
||||
map['.'] = Operation.YANK_LAST_ARG;
|
||||
map['<'] = Operation.BEGINNING_OF_HISTORY;
|
||||
map['='] = Operation.POSSIBLE_COMPLETIONS;
|
||||
map['>'] = Operation.END_OF_HISTORY;
|
||||
map['?'] = Operation.POSSIBLE_COMPLETIONS;
|
||||
for (int i = 'A'; i <= 'Z'; i++) {
|
||||
map[i] = Operation.DO_LOWERCASE_VERSION;
|
||||
}
|
||||
map['\\'] = Operation.DELETE_HORIZONTAL_SPACE;
|
||||
map['_'] = Operation.YANK_LAST_ARG;
|
||||
map['b'] = Operation.BACKWARD_WORD;
|
||||
map['c'] = Operation.CAPITALIZE_WORD;
|
||||
map['d'] = Operation.KILL_WORD;
|
||||
map['f'] = Operation.FORWARD_WORD;
|
||||
map['l'] = Operation.DOWNCASE_WORD;
|
||||
map['p'] = Operation.NON_INCREMENTAL_REVERSE_SEARCH_HISTORY;
|
||||
map['r'] = Operation.REVERT_LINE;
|
||||
map['t'] = Operation.TRANSPOSE_WORDS;
|
||||
map['u'] = Operation.UPCASE_WORD;
|
||||
map['y'] = Operation.YANK_POP;
|
||||
map['~'] = Operation.TILDE_EXPAND;
|
||||
map[DELETE] = Operation.BACKWARD_KILL_WORD;
|
||||
return new KeyMap(EMACS_META, map, false);
|
||||
}
|
||||
|
||||
public static KeyMap viInsertion() {
|
||||
Object[] map = new Object[KEYMAP_LENGTH];
|
||||
Object[] ctrl = new Object[] {
|
||||
// Control keys.
|
||||
null, /* Control-@ */
|
||||
Operation.SELF_INSERT, /* Control-A */
|
||||
Operation.SELF_INSERT, /* Control-B */
|
||||
Operation.SELF_INSERT, /* Control-C */
|
||||
Operation.VI_EOF_MAYBE, /* Control-D */
|
||||
Operation.SELF_INSERT, /* Control-E */
|
||||
Operation.SELF_INSERT, /* Control-F */
|
||||
Operation.SELF_INSERT, /* Control-G */
|
||||
Operation.BACKWARD_DELETE_CHAR, /* Control-H */
|
||||
Operation.COMPLETE, /* Control-I */
|
||||
Operation.ACCEPT_LINE, /* Control-J */
|
||||
Operation.SELF_INSERT, /* Control-K */
|
||||
Operation.SELF_INSERT, /* Control-L */
|
||||
Operation.ACCEPT_LINE, /* Control-M */
|
||||
Operation.MENU_COMPLETE, /* Control-N */
|
||||
Operation.SELF_INSERT, /* Control-O */
|
||||
Operation.MENU_COMPLETE_BACKWARD, /* Control-P */
|
||||
Operation.SELF_INSERT, /* Control-Q */
|
||||
Operation.REVERSE_SEARCH_HISTORY, /* Control-R */
|
||||
Operation.FORWARD_SEARCH_HISTORY, /* Control-S */
|
||||
Operation.TRANSPOSE_CHARS, /* Control-T */
|
||||
Operation.UNIX_LINE_DISCARD, /* Control-U */
|
||||
Operation.QUOTED_INSERT, /* Control-V */
|
||||
Operation.UNIX_WORD_RUBOUT, /* Control-W */
|
||||
Operation.SELF_INSERT, /* Control-X */
|
||||
Operation.YANK, /* Control-Y */
|
||||
Operation.SELF_INSERT, /* Control-Z */
|
||||
Operation.VI_MOVEMENT_MODE, /* Control-[ */
|
||||
Operation.SELF_INSERT, /* Control-\ */
|
||||
Operation.SELF_INSERT, /* Control-] */
|
||||
Operation.SELF_INSERT, /* Control-^ */
|
||||
Operation.UNDO, /* Control-_ */
|
||||
};
|
||||
System.arraycopy( ctrl, 0, map, 0, ctrl.length );
|
||||
for (int i = 32; i < 256; i++) {
|
||||
map[i] = Operation.SELF_INSERT;
|
||||
}
|
||||
map[DELETE] = Operation.BACKWARD_DELETE_CHAR;
|
||||
return new KeyMap(VI_INSERT, map, false);
|
||||
}
|
||||
|
||||
public static KeyMap viMovement() {
|
||||
Object[] map = new Object[KEYMAP_LENGTH];
|
||||
Object[] low = new Object[] {
|
||||
// Control keys.
|
||||
null, /* Control-@ */
|
||||
null, /* Control-A */
|
||||
null, /* Control-B */
|
||||
Operation.INTERRUPT, /* Control-C */
|
||||
/*
|
||||
* ^D is supposed to move down half a screen. In bash
|
||||
* appears to be ignored.
|
||||
*/
|
||||
Operation.VI_EOF_MAYBE, /* Control-D */
|
||||
Operation.EMACS_EDITING_MODE, /* Control-E */
|
||||
null, /* Control-F */
|
||||
Operation.ABORT, /* Control-G */
|
||||
Operation.BACKWARD_CHAR, /* Control-H */
|
||||
null, /* Control-I */
|
||||
Operation.VI_MOVE_ACCEPT_LINE, /* Control-J */
|
||||
Operation.KILL_LINE, /* Control-K */
|
||||
Operation.CLEAR_SCREEN, /* Control-L */
|
||||
Operation.VI_MOVE_ACCEPT_LINE, /* Control-M */
|
||||
Operation.VI_NEXT_HISTORY, /* Control-N */
|
||||
null, /* Control-O */
|
||||
Operation.VI_PREVIOUS_HISTORY, /* Control-P */
|
||||
/*
|
||||
* My testing with readline is the ^Q is ignored.
|
||||
* Maybe this should be null?
|
||||
*/
|
||||
Operation.QUOTED_INSERT, /* Control-Q */
|
||||
|
||||
/*
|
||||
* TODO - Very broken. While in forward/reverse
|
||||
* history search the VI keyset should go out the
|
||||
* window and we need to enter a very simple keymap.
|
||||
*/
|
||||
Operation.REVERSE_SEARCH_HISTORY, /* Control-R */
|
||||
/* TODO */
|
||||
Operation.FORWARD_SEARCH_HISTORY, /* Control-S */
|
||||
Operation.TRANSPOSE_CHARS, /* Control-T */
|
||||
Operation.UNIX_LINE_DISCARD, /* Control-U */
|
||||
/* TODO */
|
||||
Operation.QUOTED_INSERT, /* Control-V */
|
||||
Operation.UNIX_WORD_RUBOUT, /* Control-W */
|
||||
null, /* Control-X */
|
||||
/* TODO */
|
||||
Operation.YANK, /* Control-Y */
|
||||
null, /* Control-Z */
|
||||
emacsMeta(), /* Control-[ */
|
||||
null, /* Control-\ */
|
||||
/* TODO */
|
||||
Operation.CHARACTER_SEARCH, /* Control-] */
|
||||
null, /* Control-^ */
|
||||
/* TODO */
|
||||
Operation.UNDO, /* Control-_ */
|
||||
Operation.FORWARD_CHAR, /* SPACE */
|
||||
null, /* ! */
|
||||
null, /* " */
|
||||
Operation.VI_INSERT_COMMENT, /* # */
|
||||
Operation.END_OF_LINE, /* $ */
|
||||
Operation.VI_MATCH, /* % */
|
||||
Operation.VI_TILDE_EXPAND, /* & */
|
||||
null, /* ' */
|
||||
null, /* ( */
|
||||
null, /* ) */
|
||||
/* TODO */
|
||||
Operation.VI_COMPLETE, /* * */
|
||||
Operation.VI_NEXT_HISTORY, /* + */
|
||||
Operation.VI_CHAR_SEARCH, /* , */
|
||||
Operation.VI_PREVIOUS_HISTORY, /* - */
|
||||
/* TODO */
|
||||
Operation.VI_REDO, /* . */
|
||||
Operation.VI_SEARCH, /* / */
|
||||
Operation.VI_BEGNNING_OF_LINE_OR_ARG_DIGIT, /* 0 */
|
||||
Operation.VI_ARG_DIGIT, /* 1 */
|
||||
Operation.VI_ARG_DIGIT, /* 2 */
|
||||
Operation.VI_ARG_DIGIT, /* 3 */
|
||||
Operation.VI_ARG_DIGIT, /* 4 */
|
||||
Operation.VI_ARG_DIGIT, /* 5 */
|
||||
Operation.VI_ARG_DIGIT, /* 6 */
|
||||
Operation.VI_ARG_DIGIT, /* 7 */
|
||||
Operation.VI_ARG_DIGIT, /* 8 */
|
||||
Operation.VI_ARG_DIGIT, /* 9 */
|
||||
null, /* : */
|
||||
Operation.VI_CHAR_SEARCH, /* ; */
|
||||
null, /* < */
|
||||
Operation.VI_COMPLETE, /* = */
|
||||
null, /* > */
|
||||
Operation.VI_SEARCH, /* ? */
|
||||
null, /* @ */
|
||||
Operation.VI_APPEND_EOL, /* A */
|
||||
Operation.VI_PREV_WORD, /* B */
|
||||
Operation.VI_CHANGE_TO_EOL, /* C */
|
||||
Operation.VI_DELETE_TO_EOL, /* D */
|
||||
Operation.VI_END_WORD, /* E */
|
||||
Operation.VI_CHAR_SEARCH, /* F */
|
||||
/* I need to read up on what this does */
|
||||
Operation.VI_FETCH_HISTORY, /* G */
|
||||
null, /* H */
|
||||
Operation.VI_INSERT_BEG, /* I */
|
||||
null, /* J */
|
||||
null, /* K */
|
||||
null, /* L */
|
||||
null, /* M */
|
||||
Operation.VI_SEARCH_AGAIN, /* N */
|
||||
null, /* O */
|
||||
Operation.VI_PUT, /* P */
|
||||
null, /* Q */
|
||||
/* TODO */
|
||||
Operation.VI_REPLACE, /* R */
|
||||
Operation.VI_KILL_WHOLE_LINE, /* S */
|
||||
Operation.VI_CHAR_SEARCH, /* T */
|
||||
/* TODO */
|
||||
Operation.REVERT_LINE, /* U */
|
||||
null, /* V */
|
||||
Operation.VI_NEXT_WORD, /* W */
|
||||
Operation.VI_RUBOUT, /* X */
|
||||
Operation.VI_YANK_TO, /* Y */
|
||||
null, /* Z */
|
||||
null, /* [ */
|
||||
Operation.VI_COMPLETE, /* \ */
|
||||
null, /* ] */
|
||||
Operation.VI_FIRST_PRINT, /* ^ */
|
||||
Operation.VI_YANK_ARG, /* _ */
|
||||
Operation.VI_GOTO_MARK, /* ` */
|
||||
Operation.VI_APPEND_MODE, /* a */
|
||||
Operation.VI_PREV_WORD, /* b */
|
||||
Operation.VI_CHANGE_TO, /* c */
|
||||
Operation.VI_DELETE_TO, /* d */
|
||||
Operation.VI_END_WORD, /* e */
|
||||
Operation.VI_CHAR_SEARCH, /* f */
|
||||
null, /* g */
|
||||
Operation.BACKWARD_CHAR, /* h */
|
||||
Operation.VI_INSERTION_MODE, /* i */
|
||||
Operation.NEXT_HISTORY, /* j */
|
||||
Operation.PREVIOUS_HISTORY, /* k */
|
||||
Operation.FORWARD_CHAR, /* l */
|
||||
Operation.VI_SET_MARK, /* m */
|
||||
Operation.VI_SEARCH_AGAIN, /* n */
|
||||
null, /* o */
|
||||
Operation.VI_PUT, /* p */
|
||||
null, /* q */
|
||||
Operation.VI_CHANGE_CHAR, /* r */
|
||||
Operation.VI_SUBST, /* s */
|
||||
Operation.VI_CHAR_SEARCH, /* t */
|
||||
Operation.UNDO, /* u */
|
||||
null, /* v */
|
||||
Operation.VI_NEXT_WORD, /* w */
|
||||
Operation.VI_DELETE, /* x */
|
||||
Operation.VI_YANK_TO, /* y */
|
||||
null, /* z */
|
||||
null, /* { */
|
||||
Operation.VI_COLUMN, /* | */
|
||||
null, /* } */
|
||||
Operation.VI_CHANGE_CASE, /* ~ */
|
||||
Operation.VI_DELETE /* DEL */
|
||||
};
|
||||
System.arraycopy( low, 0, map, 0, low.length );
|
||||
for (int i = 128; i < 256; i++) {
|
||||
map[i] = null;
|
||||
}
|
||||
return new KeyMap(VI_MOVE, map, false);
|
||||
}
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2013, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.console;
|
||||
|
||||
/**
|
||||
* The kill ring class keeps killed text in a fixed size ring. In this
|
||||
* class we also keep record of whether or not the last command was a
|
||||
* kill or a yank. Depending on this, the class may behave
|
||||
* different. For instance, two consecutive kill-word commands fill
|
||||
* the same slot such that the next yank will return the two
|
||||
* previously killed words instead that only the last one. Likewise
|
||||
* yank pop requires that the previous command was either a yank or a
|
||||
* yank-pop.
|
||||
*/
|
||||
public final class KillRing {
|
||||
|
||||
/**
|
||||
* Default size is 60, like in emacs.
|
||||
*/
|
||||
private static final int DEFAULT_SIZE = 60;
|
||||
|
||||
private final String[] slots;
|
||||
private int head = 0;
|
||||
private boolean lastKill = false;
|
||||
private boolean lastYank = false;
|
||||
|
||||
/**
|
||||
* Creates a new kill ring of the given size.
|
||||
*/
|
||||
public KillRing(int size) {
|
||||
slots = new String[size];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new kill ring of the default size. {@see DEFAULT_SIZE}.
|
||||
*/
|
||||
public KillRing() {
|
||||
this(DEFAULT_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the last-yank state.
|
||||
*/
|
||||
public void resetLastYank() {
|
||||
lastYank = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the last-kill state.
|
||||
*/
|
||||
public void resetLastKill() {
|
||||
lastKill = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the last command was a yank.
|
||||
*/
|
||||
public boolean lastYank() {
|
||||
return lastYank;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the string to the kill-ring. Also sets lastYank to false
|
||||
* and lastKill to true.
|
||||
*/
|
||||
public void add(String str) {
|
||||
lastYank = false;
|
||||
|
||||
if (lastKill) {
|
||||
if (slots[head] != null) {
|
||||
slots[head] += str;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
lastKill = true;
|
||||
next();
|
||||
slots[head] = str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the string to the kill-ring product of killing
|
||||
* backwards. If the previous command was a kill text one then
|
||||
* adds the text at the beginning of the previous kill to avoid
|
||||
* that two consecutive backwards kills followed by a yank leaves
|
||||
* things reversed.
|
||||
*/
|
||||
public void addBackwards(String str) {
|
||||
lastYank = false;
|
||||
|
||||
if (lastKill) {
|
||||
if (slots[head] != null) {
|
||||
slots[head] = str + slots[head];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
lastKill = true;
|
||||
next();
|
||||
slots[head] = str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Yanks a previously killed text. Returns {@code null} if the
|
||||
* ring is empty.
|
||||
*/
|
||||
public String yank() {
|
||||
lastKill = false;
|
||||
lastYank = true;
|
||||
return slots[head];
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the pointer to the current slot back and returns the text
|
||||
* in that position. If the previous command was not yank returns
|
||||
* null.
|
||||
*/
|
||||
public String yankPop() {
|
||||
lastKill = false;
|
||||
if (lastYank) {
|
||||
prev();
|
||||
return slots[head];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the pointer to the current slot forward. If the end of
|
||||
* the slots is reached then points back to the beginning.
|
||||
*/
|
||||
private void next() {
|
||||
if (head == 0 && slots[0] == null) {
|
||||
return;
|
||||
}
|
||||
head++;
|
||||
if (head == slots.length) {
|
||||
head = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the pointer to the current slot backwards. If the
|
||||
* beginning of the slots is reached then traverses the slot
|
||||
* backwards until one with not null content is found.
|
||||
*/
|
||||
private void prev() {
|
||||
head--;
|
||||
if (head == -1) {
|
||||
int x = (slots.length - 1);
|
||||
for (; x >= 0; x--) {
|
||||
if (slots[x] != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
head = x;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.console;
|
||||
|
||||
/**
|
||||
* List of all operations.
|
||||
*
|
||||
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
|
||||
* @since 2.6
|
||||
*/
|
||||
public enum Operation {
|
||||
|
||||
ABORT,
|
||||
ACCEPT_LINE,
|
||||
ARROW_KEY_PREFIX,
|
||||
BACKWARD_BYTE,
|
||||
BACKWARD_CHAR,
|
||||
BACKWARD_DELETE_CHAR,
|
||||
BACKWARD_KILL_LINE,
|
||||
BACKWARD_KILL_WORD,
|
||||
BACKWARD_WORD,
|
||||
BEGINNING_OF_HISTORY,
|
||||
BEGINNING_OF_LINE,
|
||||
CALL_LAST_KBD_MACRO,
|
||||
CAPITALIZE_WORD,
|
||||
CHARACTER_SEARCH,
|
||||
CHARACTER_SEARCH_BACKWARD,
|
||||
CLEAR_SCREEN,
|
||||
COMPLETE,
|
||||
COPY_BACKWARD_WORD,
|
||||
COPY_FORWARD_WORD,
|
||||
COPY_REGION_AS_KILL,
|
||||
DELETE_CHAR,
|
||||
DELETE_CHAR_OR_LIST,
|
||||
DELETE_HORIZONTAL_SPACE,
|
||||
DIGIT_ARGUMENT,
|
||||
DO_LOWERCASE_VERSION,
|
||||
DOWNCASE_WORD,
|
||||
DUMP_FUNCTIONS,
|
||||
DUMP_MACROS,
|
||||
DUMP_VARIABLES,
|
||||
EMACS_EDITING_MODE,
|
||||
END_KBD_MACRO,
|
||||
END_OF_HISTORY,
|
||||
END_OF_LINE,
|
||||
EXCHANGE_POINT_AND_MARK,
|
||||
EXIT_OR_DELETE_CHAR,
|
||||
FORWARD_BACKWARD_DELETE_CHAR,
|
||||
FORWARD_BYTE,
|
||||
FORWARD_CHAR,
|
||||
FORWARD_SEARCH_HISTORY,
|
||||
FORWARD_WORD,
|
||||
HISTORY_SEARCH_BACKWARD,
|
||||
HISTORY_SEARCH_FORWARD,
|
||||
INSERT_CLOSE_CURLY,
|
||||
INSERT_CLOSE_PAREN,
|
||||
INSERT_CLOSE_SQUARE,
|
||||
INSERT_COMMENT,
|
||||
INSERT_COMPLETIONS,
|
||||
INTERRUPT,
|
||||
KILL_WHOLE_LINE,
|
||||
KILL_LINE,
|
||||
KILL_REGION,
|
||||
KILL_WORD,
|
||||
MENU_COMPLETE,
|
||||
MENU_COMPLETE_BACKWARD,
|
||||
NEXT_HISTORY,
|
||||
NON_INCREMENTAL_FORWARD_SEARCH_HISTORY,
|
||||
NON_INCREMENTAL_REVERSE_SEARCH_HISTORY,
|
||||
NON_INCREMENTAL_FORWARD_SEARCH_HISTORY_AGAIN,
|
||||
NON_INCREMENTAL_REVERSE_SEARCH_HISTORY_AGAIN,
|
||||
OLD_MENU_COMPLETE,
|
||||
OVERWRITE_MODE,
|
||||
PASTE_FROM_CLIPBOARD,
|
||||
POSSIBLE_COMPLETIONS,
|
||||
PREVIOUS_HISTORY,
|
||||
QUOTED_INSERT,
|
||||
RE_READ_INIT_FILE,
|
||||
REDRAW_CURRENT_LINE,
|
||||
REVERSE_SEARCH_HISTORY,
|
||||
REVERT_LINE,
|
||||
SELF_INSERT,
|
||||
SET_MARK,
|
||||
SKIP_CSI_SEQUENCE,
|
||||
START_KBD_MACRO,
|
||||
TAB_INSERT,
|
||||
TILDE_EXPAND,
|
||||
TRANSPOSE_CHARS,
|
||||
TRANSPOSE_WORDS,
|
||||
TTY_STATUS,
|
||||
UNDO,
|
||||
UNIVERSAL_ARGUMENT,
|
||||
UNIX_FILENAME_RUBOUT,
|
||||
UNIX_LINE_DISCARD,
|
||||
UNIX_WORD_RUBOUT,
|
||||
UPCASE_WORD,
|
||||
YANK,
|
||||
YANK_LAST_ARG,
|
||||
YANK_NTH_ARG,
|
||||
YANK_POP,
|
||||
VI_APPEND_EOL,
|
||||
VI_APPEND_MODE,
|
||||
VI_ARG_DIGIT,
|
||||
VI_BACK_TO_INDENT,
|
||||
VI_BACKWARD_BIGWORD,
|
||||
VI_BACKWARD_WORD,
|
||||
VI_BWORD,
|
||||
VI_CHANGE_CASE,
|
||||
VI_CHANGE_CHAR,
|
||||
VI_CHANGE_TO,
|
||||
VI_CHANGE_TO_EOL,
|
||||
VI_CHAR_SEARCH,
|
||||
VI_COLUMN,
|
||||
VI_COMPLETE,
|
||||
VI_DELETE,
|
||||
VI_DELETE_TO,
|
||||
VI_DELETE_TO_EOL,
|
||||
VI_EDITING_MODE,
|
||||
VI_END_BIGWORD,
|
||||
VI_END_WORD,
|
||||
VI_EOF_MAYBE,
|
||||
VI_EWORD,
|
||||
VI_FWORD,
|
||||
VI_FETCH_HISTORY,
|
||||
VI_FIRST_PRINT,
|
||||
VI_FORWARD_BIGWORD,
|
||||
VI_FORWARD_WORD,
|
||||
VI_GOTO_MARK,
|
||||
VI_INSERT_BEG,
|
||||
VI_INSERTION_MODE,
|
||||
VI_KILL_WHOLE_LINE,
|
||||
VI_MATCH,
|
||||
VI_MOVEMENT_MODE,
|
||||
VI_NEXT_WORD,
|
||||
VI_OVERSTRIKE,
|
||||
VI_OVERSTRIKE_DELETE,
|
||||
VI_PREV_WORD,
|
||||
VI_PUT,
|
||||
VI_REDO,
|
||||
VI_REPLACE,
|
||||
VI_RUBOUT,
|
||||
VI_SEARCH,
|
||||
VI_SEARCH_AGAIN,
|
||||
VI_SET_MARK,
|
||||
VI_SUBST,
|
||||
VI_TILDE_EXPAND,
|
||||
VI_YANK_ARG,
|
||||
VI_YANK_TO,
|
||||
VI_MOVE_ACCEPT_LINE,
|
||||
VI_NEXT_HISTORY,
|
||||
VI_PREVIOUS_HISTORY,
|
||||
VI_INSERT_COMMENT,
|
||||
VI_BEGNNING_OF_LINE_OR_ARG_DIGIT,
|
||||
}
|
36
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/UserInterruptException.java
Normal file
36
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/UserInterruptException.java
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.console;
|
||||
|
||||
/**
|
||||
* This exception is thrown by {@link ConsoleReader#readLine} when
|
||||
* user interrupt handling is enabled and the user types the
|
||||
* interrupt character (ctrl-C). The partially entered line is
|
||||
* available via the {@link #getPartialLine()} method.
|
||||
*/
|
||||
public class UserInterruptException
|
||||
extends RuntimeException
|
||||
{
|
||||
private static final long serialVersionUID = 6172232572140736750L;
|
||||
|
||||
private final String partialLine;
|
||||
|
||||
public UserInterruptException(String partialLine)
|
||||
{
|
||||
this.partialLine = partialLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the partially entered line when ctrl-C was pressed
|
||||
*/
|
||||
public String getPartialLine()
|
||||
{
|
||||
return partialLine;
|
||||
}
|
||||
}
|
124
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/AggregateCompleter.java
Normal file
124
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/AggregateCompleter.java
Normal file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.console.completer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Completer which contains multiple completers and aggregates them together.
|
||||
*
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
public class AggregateCompleter
|
||||
implements Completer
|
||||
{
|
||||
private final List<Completer> completers = new ArrayList<Completer>();
|
||||
|
||||
public AggregateCompleter() {
|
||||
// empty
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an AggregateCompleter with the given collection of completers.
|
||||
* The completers will be used in the iteration order of the collection.
|
||||
*
|
||||
* @param completers the collection of completers
|
||||
*/
|
||||
public AggregateCompleter(final Collection<Completer> completers) {
|
||||
checkNotNull(completers);
|
||||
this.completers.addAll(completers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an AggregateCompleter with the given completers.
|
||||
* The completers will be used in the order given.
|
||||
*
|
||||
* @param completers the completers
|
||||
*/
|
||||
public AggregateCompleter(final Completer... completers) {
|
||||
this(Arrays.asList(completers));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the collection of completers currently being aggregated.
|
||||
*
|
||||
* @return the aggregated completers
|
||||
*/
|
||||
public Collection<Completer> getCompleters() {
|
||||
return completers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a completion operation across all aggregated completers.
|
||||
*
|
||||
* @see Completer#complete(String, int, java.util.List)
|
||||
* @return the highest completion return value from all completers
|
||||
*/
|
||||
public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
|
||||
// buffer could be null
|
||||
checkNotNull(candidates);
|
||||
|
||||
List<Completion> completions = new ArrayList<Completion>(completers.size());
|
||||
|
||||
// Run each completer, saving its completion results
|
||||
int max = -1;
|
||||
for (Completer completer : completers) {
|
||||
Completion completion = new Completion(candidates);
|
||||
completion.complete(completer, buffer, cursor);
|
||||
|
||||
// Compute the max cursor position
|
||||
max = Math.max(max, completion.cursor);
|
||||
|
||||
completions.add(completion);
|
||||
}
|
||||
|
||||
// Append candidates from completions which have the same cursor position as max
|
||||
for (Completion completion : completions) {
|
||||
if (completion.cursor == max) {
|
||||
candidates.addAll(completion.candidates);
|
||||
}
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a string representing the aggregated completers
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "{" +
|
||||
"completers=" + completers +
|
||||
'}';
|
||||
}
|
||||
|
||||
private class Completion
|
||||
{
|
||||
public final List<CharSequence> candidates;
|
||||
|
||||
public int cursor;
|
||||
|
||||
public Completion(final List<CharSequence> candidates) {
|
||||
checkNotNull(candidates);
|
||||
this.candidates = new LinkedList<CharSequence>(candidates);
|
||||
}
|
||||
|
||||
public void complete(final Completer completer, final String buffer, final int cursor) {
|
||||
checkNotNull(completer);
|
||||
this.cursor = completer.complete(buffer, cursor, candidates);
|
||||
}
|
||||
}
|
||||
}
|
461
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/ArgumentCompleter.java
Normal file
461
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/ArgumentCompleter.java
Normal file
@ -0,0 +1,461 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.console.completer;
|
||||
|
||||
import jdk.internal.jline.internal.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* A {@link Completer} implementation that invokes a child completer using the appropriate <i>separator</i> argument.
|
||||
* This can be used instead of the individual completers having to know about argument parsing semantics.
|
||||
*
|
||||
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
public class ArgumentCompleter
|
||||
implements Completer
|
||||
{
|
||||
private final ArgumentDelimiter delimiter;
|
||||
|
||||
private final List<Completer> completers = new ArrayList<Completer>();
|
||||
|
||||
private boolean strict = true;
|
||||
|
||||
/**
|
||||
* Create a new completer with the specified argument delimiter.
|
||||
*
|
||||
* @param delimiter The delimiter for parsing arguments
|
||||
* @param completers The embedded completers
|
||||
*/
|
||||
public ArgumentCompleter(final ArgumentDelimiter delimiter, final Collection<Completer> completers) {
|
||||
this.delimiter = checkNotNull(delimiter);
|
||||
checkNotNull(completers);
|
||||
this.completers.addAll(completers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new completer with the specified argument delimiter.
|
||||
*
|
||||
* @param delimiter The delimiter for parsing arguments
|
||||
* @param completers The embedded completers
|
||||
*/
|
||||
public ArgumentCompleter(final ArgumentDelimiter delimiter, final Completer... completers) {
|
||||
this(delimiter, Arrays.asList(completers));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new completer with the default {@link WhitespaceArgumentDelimiter}.
|
||||
*
|
||||
* @param completers The embedded completers
|
||||
*/
|
||||
public ArgumentCompleter(final Completer... completers) {
|
||||
this(new WhitespaceArgumentDelimiter(), completers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new completer with the default {@link WhitespaceArgumentDelimiter}.
|
||||
*
|
||||
* @param completers The embedded completers
|
||||
*/
|
||||
public ArgumentCompleter(final List<Completer> completers) {
|
||||
this(new WhitespaceArgumentDelimiter(), completers);
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, a completion at argument index N will only succeed
|
||||
* if all the completions from 0-(N-1) also succeed.
|
||||
*/
|
||||
public void setStrict(final boolean strict) {
|
||||
this.strict = strict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a completion at argument index N will success
|
||||
* if all the completions from arguments 0-(N-1) also succeed.
|
||||
*
|
||||
* @return True if strict.
|
||||
* @since 2.3
|
||||
*/
|
||||
public boolean isStrict() {
|
||||
return this.strict;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3
|
||||
*/
|
||||
public ArgumentDelimiter getDelimiter() {
|
||||
return delimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3
|
||||
*/
|
||||
public List<Completer> getCompleters() {
|
||||
return completers;
|
||||
}
|
||||
|
||||
public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
|
||||
// buffer can be null
|
||||
checkNotNull(candidates);
|
||||
|
||||
ArgumentDelimiter delim = getDelimiter();
|
||||
ArgumentList list = delim.delimit(buffer, cursor);
|
||||
int argpos = list.getArgumentPosition();
|
||||
int argIndex = list.getCursorArgumentIndex();
|
||||
|
||||
if (argIndex < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
List<Completer> completers = getCompleters();
|
||||
Completer completer;
|
||||
|
||||
// if we are beyond the end of the completers, just use the last one
|
||||
if (argIndex >= completers.size()) {
|
||||
completer = completers.get(completers.size() - 1);
|
||||
}
|
||||
else {
|
||||
completer = completers.get(argIndex);
|
||||
}
|
||||
|
||||
// ensure that all the previous completers are successful before allowing this completer to pass (only if strict).
|
||||
for (int i = 0; isStrict() && (i < argIndex); i++) {
|
||||
Completer sub = completers.get(i >= completers.size() ? (completers.size() - 1) : i);
|
||||
String[] args = list.getArguments();
|
||||
String arg = (args == null || i >= args.length) ? "" : args[i];
|
||||
|
||||
List<CharSequence> subCandidates = new LinkedList<CharSequence>();
|
||||
|
||||
if (sub.complete(arg, arg.length(), subCandidates) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (subCandidates.size() == 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int ret = completer.complete(list.getCursorArgument(), argpos, candidates);
|
||||
|
||||
if (ret == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int pos = ret + list.getBufferPosition() - argpos;
|
||||
|
||||
// Special case: when completing in the middle of a line, and the area under the cursor is a delimiter,
|
||||
// then trim any delimiters from the candidates, since we do not need to have an extra delimiter.
|
||||
//
|
||||
// E.g., if we have a completion for "foo", and we enter "f bar" into the buffer, and move to after the "f"
|
||||
// and hit TAB, we want "foo bar" instead of "foo bar".
|
||||
|
||||
if ((cursor != buffer.length()) && delim.isDelimiter(buffer, cursor)) {
|
||||
for (int i = 0; i < candidates.size(); i++) {
|
||||
CharSequence val = candidates.get(i);
|
||||
|
||||
while (val.length() > 0 && delim.isDelimiter(val, val.length() - 1)) {
|
||||
val = val.subSequence(0, val.length() - 1);
|
||||
}
|
||||
|
||||
candidates.set(i, val);
|
||||
}
|
||||
}
|
||||
|
||||
Log.trace("Completing ", buffer, " (pos=", cursor, ") with: ", candidates, ": offset=", pos);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link ArgumentCompleter.ArgumentDelimiter} allows custom breaking up of a {@link String} into individual
|
||||
* arguments in order to dispatch the arguments to the nested {@link Completer}.
|
||||
*
|
||||
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||
*/
|
||||
public static interface ArgumentDelimiter
|
||||
{
|
||||
/**
|
||||
* Break the specified buffer into individual tokens that can be completed on their own.
|
||||
*
|
||||
* @param buffer The buffer to split
|
||||
* @param pos The current position of the cursor in the buffer
|
||||
* @return The tokens
|
||||
*/
|
||||
ArgumentList delimit(CharSequence buffer, int pos);
|
||||
|
||||
/**
|
||||
* Returns true if the specified character is a whitespace parameter.
|
||||
*
|
||||
* @param buffer The complete command buffer
|
||||
* @param pos The index of the character in the buffer
|
||||
* @return True if the character should be a delimiter
|
||||
*/
|
||||
boolean isDelimiter(CharSequence buffer, int pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract implementation of a delimiter that uses the {@link #isDelimiter} method to determine if a particular
|
||||
* character should be used as a delimiter.
|
||||
*
|
||||
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||
*/
|
||||
public abstract static class AbstractArgumentDelimiter
|
||||
implements ArgumentDelimiter
|
||||
{
|
||||
private char[] quoteChars = {'\'', '"'};
|
||||
|
||||
private char[] escapeChars = {'\\'};
|
||||
|
||||
public void setQuoteChars(final char[] chars) {
|
||||
this.quoteChars = chars;
|
||||
}
|
||||
|
||||
public char[] getQuoteChars() {
|
||||
return this.quoteChars;
|
||||
}
|
||||
|
||||
public void setEscapeChars(final char[] chars) {
|
||||
this.escapeChars = chars;
|
||||
}
|
||||
|
||||
public char[] getEscapeChars() {
|
||||
return this.escapeChars;
|
||||
}
|
||||
|
||||
public ArgumentList delimit(final CharSequence buffer, final int cursor) {
|
||||
List<String> args = new LinkedList<String>();
|
||||
StringBuilder arg = new StringBuilder();
|
||||
int argpos = -1;
|
||||
int bindex = -1;
|
||||
int quoteStart = -1;
|
||||
|
||||
for (int i = 0; (buffer != null) && (i < buffer.length()); i++) {
|
||||
// once we reach the cursor, set the
|
||||
// position of the selected index
|
||||
if (i == cursor) {
|
||||
bindex = args.size();
|
||||
// the position in the current argument is just the
|
||||
// length of the current argument
|
||||
argpos = arg.length();
|
||||
}
|
||||
|
||||
if (quoteStart < 0 && isQuoteChar(buffer, i)) {
|
||||
// Start a quote block
|
||||
quoteStart = i;
|
||||
} else if (quoteStart >= 0) {
|
||||
// In a quote block
|
||||
if (buffer.charAt(quoteStart) == buffer.charAt(i) && !isEscaped(buffer, i)) {
|
||||
// End the block; arg could be empty, but that's fine
|
||||
args.add(arg.toString());
|
||||
arg.setLength(0);
|
||||
quoteStart = -1;
|
||||
} else if (!isEscapeChar(buffer, i)) {
|
||||
// Take the next character
|
||||
arg.append(buffer.charAt(i));
|
||||
}
|
||||
} else {
|
||||
// Not in a quote block
|
||||
if (isDelimiter(buffer, i)) {
|
||||
if (arg.length() > 0) {
|
||||
args.add(arg.toString());
|
||||
arg.setLength(0); // reset the arg
|
||||
}
|
||||
} else if (!isEscapeChar(buffer, i)) {
|
||||
arg.append(buffer.charAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor == buffer.length()) {
|
||||
bindex = args.size();
|
||||
// the position in the current argument is just the
|
||||
// length of the current argument
|
||||
argpos = arg.length();
|
||||
}
|
||||
if (arg.length() > 0) {
|
||||
args.add(arg.toString());
|
||||
}
|
||||
|
||||
return new ArgumentList(args.toArray(new String[args.size()]), bindex, argpos, cursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified character is a whitespace parameter. Check to ensure that the character is not
|
||||
* escaped by any of {@link #getQuoteChars}, and is not escaped by ant of the {@link #getEscapeChars}, and
|
||||
* returns true from {@link #isDelimiterChar}.
|
||||
*
|
||||
* @param buffer The complete command buffer
|
||||
* @param pos The index of the character in the buffer
|
||||
* @return True if the character should be a delimiter
|
||||
*/
|
||||
public boolean isDelimiter(final CharSequence buffer, final int pos) {
|
||||
return !isQuoted(buffer, pos) && !isEscaped(buffer, pos) && isDelimiterChar(buffer, pos);
|
||||
}
|
||||
|
||||
public boolean isQuoted(final CharSequence buffer, final int pos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isQuoteChar(final CharSequence buffer, final int pos) {
|
||||
if (pos < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; (quoteChars != null) && (i < quoteChars.length); i++) {
|
||||
if (buffer.charAt(pos) == quoteChars[i]) {
|
||||
return !isEscaped(buffer, pos);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this character is a valid escape char (i.e. one that has not been escaped)
|
||||
*
|
||||
* @param buffer
|
||||
* @param pos
|
||||
* @return
|
||||
*/
|
||||
public boolean isEscapeChar(final CharSequence buffer, final int pos) {
|
||||
if (pos < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; (escapeChars != null) && (i < escapeChars.length); i++) {
|
||||
if (buffer.charAt(pos) == escapeChars[i]) {
|
||||
return !isEscaped(buffer, pos); // escape escape
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a character is escaped (i.e. if the previous character is an escape)
|
||||
*
|
||||
* @param buffer
|
||||
* the buffer to check in
|
||||
* @param pos
|
||||
* the position of the character to check
|
||||
* @return true if the character at the specified position in the given buffer is an escape character and the character immediately preceding it is not an
|
||||
* escape character.
|
||||
*/
|
||||
public boolean isEscaped(final CharSequence buffer, final int pos) {
|
||||
if (pos <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isEscapeChar(buffer, pos - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the character at the specified position if a delimiter. This method will only be called if
|
||||
* the character is not enclosed in any of the {@link #getQuoteChars}, and is not escaped by ant of the
|
||||
* {@link #getEscapeChars}. To perform escaping manually, override {@link #isDelimiter} instead.
|
||||
*/
|
||||
public abstract boolean isDelimiterChar(CharSequence buffer, int pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ArgumentCompleter.ArgumentDelimiter} implementation that counts all whitespace (as reported by
|
||||
* {@link Character#isWhitespace}) as being a delimiter.
|
||||
*
|
||||
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||
*/
|
||||
public static class WhitespaceArgumentDelimiter
|
||||
extends AbstractArgumentDelimiter
|
||||
{
|
||||
/**
|
||||
* The character is a delimiter if it is whitespace, and the
|
||||
* preceding character is not an escape character.
|
||||
*/
|
||||
@Override
|
||||
public boolean isDelimiterChar(final CharSequence buffer, final int pos) {
|
||||
return Character.isWhitespace(buffer.charAt(pos));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of a delimited buffer.
|
||||
*
|
||||
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||
*/
|
||||
public static class ArgumentList
|
||||
{
|
||||
private String[] arguments;
|
||||
|
||||
private int cursorArgumentIndex;
|
||||
|
||||
private int argumentPosition;
|
||||
|
||||
private int bufferPosition;
|
||||
|
||||
/**
|
||||
* @param arguments The array of tokens
|
||||
* @param cursorArgumentIndex The token index of the cursor
|
||||
* @param argumentPosition The position of the cursor in the current token
|
||||
* @param bufferPosition The position of the cursor in the whole buffer
|
||||
*/
|
||||
public ArgumentList(final String[] arguments, final int cursorArgumentIndex, final int argumentPosition, final int bufferPosition) {
|
||||
this.arguments = checkNotNull(arguments);
|
||||
this.cursorArgumentIndex = cursorArgumentIndex;
|
||||
this.argumentPosition = argumentPosition;
|
||||
this.bufferPosition = bufferPosition;
|
||||
}
|
||||
|
||||
public void setCursorArgumentIndex(final int i) {
|
||||
this.cursorArgumentIndex = i;
|
||||
}
|
||||
|
||||
public int getCursorArgumentIndex() {
|
||||
return this.cursorArgumentIndex;
|
||||
}
|
||||
|
||||
public String getCursorArgument() {
|
||||
if ((cursorArgumentIndex < 0) || (cursorArgumentIndex >= arguments.length)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return arguments[cursorArgumentIndex];
|
||||
}
|
||||
|
||||
public void setArgumentPosition(final int pos) {
|
||||
this.argumentPosition = pos;
|
||||
}
|
||||
|
||||
public int getArgumentPosition() {
|
||||
return this.argumentPosition;
|
||||
}
|
||||
|
||||
public void setArguments(final String[] arguments) {
|
||||
this.arguments = arguments;
|
||||
}
|
||||
|
||||
public String[] getArguments() {
|
||||
return this.arguments;
|
||||
}
|
||||
|
||||
public void setBufferPosition(final int pos) {
|
||||
this.bufferPosition = pos;
|
||||
}
|
||||
|
||||
public int getBufferPosition() {
|
||||
return this.bufferPosition;
|
||||
}
|
||||
}
|
||||
}
|
194
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/CandidateListCompletionHandler.java
Normal file
194
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/CandidateListCompletionHandler.java
Normal file
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.console.completer;
|
||||
|
||||
import jdk.internal.jline.console.ConsoleReader;
|
||||
import jdk.internal.jline.console.CursorBuffer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A {@link CompletionHandler} that deals with multiple distinct completions
|
||||
* by outputting the complete list of possibilities to the console. This
|
||||
* mimics the behavior of the
|
||||
* <a href="http://www.gnu.org/directory/readline.html">readline</a> library.
|
||||
*
|
||||
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
public class CandidateListCompletionHandler
|
||||
implements CompletionHandler
|
||||
{
|
||||
// TODO: handle quotes and escaped quotes && enable automatic escaping of whitespace
|
||||
|
||||
public boolean complete(final ConsoleReader reader, final List<CharSequence> candidates, final int pos) throws
|
||||
IOException
|
||||
{
|
||||
CursorBuffer buf = reader.getCursorBuffer();
|
||||
|
||||
// if there is only one completion, then fill in the buffer
|
||||
if (candidates.size() == 1) {
|
||||
CharSequence value = candidates.get(0);
|
||||
|
||||
// fail if the only candidate is the same as the current buffer
|
||||
if (value.equals(buf.toString())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
setBuffer(reader, value, pos);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (candidates.size() > 1) {
|
||||
String value = getUnambiguousCompletions(candidates);
|
||||
setBuffer(reader, value, pos);
|
||||
}
|
||||
|
||||
printCandidates(reader, candidates);
|
||||
|
||||
// redraw the current console buffer
|
||||
reader.drawLine();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void setBuffer(final ConsoleReader reader, final CharSequence value, final int offset) throws
|
||||
IOException
|
||||
{
|
||||
while ((reader.getCursorBuffer().cursor > offset) && reader.backspace()) {
|
||||
// empty
|
||||
}
|
||||
|
||||
reader.putString(value);
|
||||
reader.setCursorPosition(offset + value.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Print out the candidates. If the size of the candidates is greater than the
|
||||
* {@link ConsoleReader#getAutoprintThreshold}, they prompt with a warning.
|
||||
*
|
||||
* @param candidates the list of candidates to print
|
||||
*/
|
||||
public static void printCandidates(final ConsoleReader reader, Collection<CharSequence> candidates) throws
|
||||
IOException
|
||||
{
|
||||
Set<CharSequence> distinct = new HashSet<CharSequence>(candidates);
|
||||
|
||||
if (distinct.size() > reader.getAutoprintThreshold()) {
|
||||
//noinspection StringConcatenation
|
||||
reader.print(Messages.DISPLAY_CANDIDATES.format(candidates.size()));
|
||||
reader.flush();
|
||||
|
||||
int c;
|
||||
|
||||
String noOpt = Messages.DISPLAY_CANDIDATES_NO.format();
|
||||
String yesOpt = Messages.DISPLAY_CANDIDATES_YES.format();
|
||||
char[] allowed = {yesOpt.charAt(0), noOpt.charAt(0)};
|
||||
|
||||
while ((c = reader.readCharacter(allowed)) != -1) {
|
||||
String tmp = new String(new char[]{(char) c});
|
||||
|
||||
if (noOpt.startsWith(tmp)) {
|
||||
reader.println();
|
||||
return;
|
||||
}
|
||||
else if (yesOpt.startsWith(tmp)) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
reader.beep();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copy the values and make them distinct, without otherwise affecting the ordering. Only do it if the sizes differ.
|
||||
if (distinct.size() != candidates.size()) {
|
||||
Collection<CharSequence> copy = new ArrayList<CharSequence>();
|
||||
|
||||
for (CharSequence next : candidates) {
|
||||
if (!copy.contains(next)) {
|
||||
copy.add(next);
|
||||
}
|
||||
}
|
||||
|
||||
candidates = copy;
|
||||
}
|
||||
|
||||
reader.println();
|
||||
reader.printColumns(candidates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a root that matches all the {@link String} elements of the specified {@link List},
|
||||
* or null if there are no commonalities. For example, if the list contains
|
||||
* <i>foobar</i>, <i>foobaz</i>, <i>foobuz</i>, the method will return <i>foob</i>.
|
||||
*/
|
||||
private String getUnambiguousCompletions(final List<CharSequence> candidates) {
|
||||
if (candidates == null || candidates.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// convert to an array for speed
|
||||
String[] strings = candidates.toArray(new String[candidates.size()]);
|
||||
|
||||
String first = strings[0];
|
||||
StringBuilder candidate = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < first.length(); i++) {
|
||||
if (startsWith(first.substring(0, i + 1), strings)) {
|
||||
candidate.append(first.charAt(i));
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return candidate.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true is all the elements of <i>candidates</i> start with <i>starts</i>
|
||||
*/
|
||||
private boolean startsWith(final String starts, final String[] candidates) {
|
||||
for (String candidate : candidates) {
|
||||
if (!candidate.startsWith(starts)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static enum Messages
|
||||
{
|
||||
DISPLAY_CANDIDATES,
|
||||
DISPLAY_CANDIDATES_YES,
|
||||
DISPLAY_CANDIDATES_NO,;
|
||||
|
||||
private static final
|
||||
ResourceBundle
|
||||
bundle =
|
||||
ResourceBundle.getBundle(CandidateListCompletionHandler.class.getName(), Locale.getDefault());
|
||||
|
||||
public String format(final Object... args) {
|
||||
if (bundle == null)
|
||||
return "";
|
||||
else
|
||||
return String.format(bundle.getString(name()), args);
|
||||
}
|
||||
}
|
||||
}
|
13
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/CandidateListCompletionHandler.properties
Normal file
13
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/CandidateListCompletionHandler.properties
Normal file
@ -0,0 +1,13 @@
|
||||
#
|
||||
# Copyright (c) 2002-2012, the original author or authors.
|
||||
#
|
||||
# This software is distributable under the BSD license. See the terms of the
|
||||
# BSD license in the documentation provided with this software.
|
||||
#
|
||||
# http://www.opensource.org/licenses/bsd-license.php
|
||||
#
|
||||
|
||||
DISPLAY_CANDIDATES=Display all %d possibilities? (y or n)
|
||||
DISPLAY_CANDIDATES_YES=y
|
||||
DISPLAY_CANDIDATES_NO=n
|
||||
DISPLAY_MORE=--More--
|
38
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/Completer.java
Normal file
38
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/Completer.java
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.console.completer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A completer is the mechanism by which tab-completion candidates will be resolved.
|
||||
*
|
||||
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
public interface Completer
|
||||
{
|
||||
//
|
||||
// FIXME: Check if we can use CharSequece for buffer?
|
||||
//
|
||||
|
||||
/**
|
||||
* Populates <i>candidates</i> with a list of possible completions for the <i>buffer</i>.
|
||||
*
|
||||
* The <i>candidates</i> list will not be sorted before being displayed to the user: thus, the
|
||||
* complete method should sort the {@link List} before returning.
|
||||
*
|
||||
* @param buffer The buffer
|
||||
* @param cursor The current position of the cursor in the <i>buffer</i>
|
||||
* @param candidates The {@link List} of candidates to populate
|
||||
* @return The index of the <i>buffer</i> for which the completion will be relative
|
||||
*/
|
||||
int complete(String buffer, int cursor, List<CharSequence> candidates);
|
||||
}
|
26
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/CompletionHandler.java
Normal file
26
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/CompletionHandler.java
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.console.completer;
|
||||
|
||||
import jdk.internal.jline.console.ConsoleReader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Handler for dealing with candidates for tab-completion.
|
||||
*
|
||||
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
public interface CompletionHandler
|
||||
{
|
||||
boolean complete(ConsoleReader reader, List<CharSequence> candidates, int position) throws IOException;
|
||||
}
|
29
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/EnumCompleter.java
Normal file
29
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/EnumCompleter.java
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.console.completer;
|
||||
|
||||
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* {@link Completer} for {@link Enum} names.
|
||||
*
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
public class EnumCompleter
|
||||
extends StringsCompleter
|
||||
{
|
||||
public EnumCompleter(Class<? extends Enum<?>> source) {
|
||||
checkNotNull(source);
|
||||
|
||||
for (Enum<?> n : source.getEnumConstants()) {
|
||||
this.getStrings().add(n.name().toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
133
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/FileNameCompleter.java
Normal file
133
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/FileNameCompleter.java
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.console.completer;
|
||||
|
||||
import jdk.internal.jline.internal.Configuration;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* A file name completer takes the buffer and issues a list of
|
||||
* potential completions.
|
||||
* <p/>
|
||||
* This completer tries to behave as similar as possible to
|
||||
* <i>bash</i>'s file name completion (using GNU readline)
|
||||
* with the following exceptions:
|
||||
* <p/>
|
||||
* <ul>
|
||||
* <li>Candidates that are directories will end with "/"</li>
|
||||
* <li>Wildcard regular expressions are not evaluated or replaced</li>
|
||||
* <li>The "~" character can be used to represent the user's home,
|
||||
* but it cannot complete to other users' homes, since java does
|
||||
* not provide any way of determining that easily</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
public class FileNameCompleter
|
||||
implements Completer
|
||||
{
|
||||
// TODO: Handle files with spaces in them
|
||||
|
||||
private static final boolean OS_IS_WINDOWS;
|
||||
|
||||
static {
|
||||
String os = Configuration.getOsName();
|
||||
OS_IS_WINDOWS = os.contains("windows");
|
||||
}
|
||||
|
||||
public int complete(String buffer, final int cursor, final List<CharSequence> candidates) {
|
||||
// buffer can be null
|
||||
checkNotNull(candidates);
|
||||
|
||||
if (buffer == null) {
|
||||
buffer = "";
|
||||
}
|
||||
|
||||
if (OS_IS_WINDOWS) {
|
||||
buffer = buffer.replace('/', '\\');
|
||||
}
|
||||
|
||||
String translated = buffer;
|
||||
|
||||
File homeDir = getUserHome();
|
||||
|
||||
// Special character: ~ maps to the user's home directory
|
||||
if (translated.startsWith("~" + separator())) {
|
||||
translated = homeDir.getPath() + translated.substring(1);
|
||||
}
|
||||
else if (translated.startsWith("~")) {
|
||||
translated = homeDir.getParentFile().getAbsolutePath();
|
||||
}
|
||||
else if (!(new File(translated).isAbsolute())) {
|
||||
String cwd = getUserDir().getAbsolutePath();
|
||||
translated = cwd + separator() + translated;
|
||||
}
|
||||
|
||||
File file = new File(translated);
|
||||
final File dir;
|
||||
|
||||
if (translated.endsWith(separator())) {
|
||||
dir = file;
|
||||
}
|
||||
else {
|
||||
dir = file.getParentFile();
|
||||
}
|
||||
|
||||
File[] entries = dir == null ? new File[0] : dir.listFiles();
|
||||
|
||||
return matchFiles(buffer, translated, entries, candidates);
|
||||
}
|
||||
|
||||
protected String separator() {
|
||||
return File.separator;
|
||||
}
|
||||
|
||||
protected File getUserHome() {
|
||||
return Configuration.getUserHome();
|
||||
}
|
||||
|
||||
protected File getUserDir() {
|
||||
return new File(".");
|
||||
}
|
||||
|
||||
protected int matchFiles(final String buffer, final String translated, final File[] files, final List<CharSequence> candidates) {
|
||||
if (files == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int matches = 0;
|
||||
|
||||
// first pass: just count the matches
|
||||
for (File file : files) {
|
||||
if (file.getAbsolutePath().startsWith(translated)) {
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
for (File file : files) {
|
||||
if (file.getAbsolutePath().startsWith(translated)) {
|
||||
CharSequence name = file.getName() + (matches == 1 && file.isDirectory() ? separator() : " ");
|
||||
candidates.add(render(file, name).toString());
|
||||
}
|
||||
}
|
||||
|
||||
final int index = buffer.lastIndexOf(separator());
|
||||
|
||||
return index + separator().length();
|
||||
}
|
||||
|
||||
protected CharSequence render(final File file, final CharSequence name) {
|
||||
return name;
|
||||
}
|
||||
}
|
28
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/NullCompleter.java
Normal file
28
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/NullCompleter.java
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.console.completer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Null completer.
|
||||
*
|
||||
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
public final class NullCompleter
|
||||
implements Completer
|
||||
{
|
||||
public static final NullCompleter INSTANCE = new NullCompleter();
|
||||
|
||||
public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
|
||||
return -1;
|
||||
}
|
||||
}
|
70
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/StringsCompleter.java
Normal file
70
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/StringsCompleter.java
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.console.completer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Completer for a set of strings.
|
||||
*
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
public class StringsCompleter
|
||||
implements Completer
|
||||
{
|
||||
private final SortedSet<String> strings = new TreeSet<String>();
|
||||
|
||||
public StringsCompleter() {
|
||||
// empty
|
||||
}
|
||||
|
||||
public StringsCompleter(final Collection<String> strings) {
|
||||
checkNotNull(strings);
|
||||
getStrings().addAll(strings);
|
||||
}
|
||||
|
||||
public StringsCompleter(final String... strings) {
|
||||
this(Arrays.asList(strings));
|
||||
}
|
||||
|
||||
public Collection<String> getStrings() {
|
||||
return strings;
|
||||
}
|
||||
|
||||
public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
|
||||
// buffer could be null
|
||||
checkNotNull(candidates);
|
||||
|
||||
if (buffer == null) {
|
||||
candidates.addAll(strings);
|
||||
}
|
||||
else {
|
||||
for (String match : strings.tailSet(buffer)) {
|
||||
if (!match.startsWith(buffer)) {
|
||||
break;
|
||||
}
|
||||
|
||||
candidates.add(match);
|
||||
}
|
||||
}
|
||||
|
||||
if (candidates.size() == 1) {
|
||||
candidates.set(0, candidates.get(0) + " ");
|
||||
}
|
||||
|
||||
return candidates.isEmpty() ? -1 : 0;
|
||||
}
|
||||
}
|
14
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/package-info.java
Normal file
14
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/package-info.java
Normal file
@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
/**
|
||||
* Console completer support.
|
||||
*
|
||||
* @since 2.3
|
||||
*/
|
||||
package jdk.internal.jline.console.completer;
|
106
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/history/FileHistory.java
Normal file
106
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/history/FileHistory.java
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.console.history;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.Flushable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintStream;
|
||||
import java.io.Reader;
|
||||
|
||||
import jdk.internal.jline.internal.Log;
|
||||
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* {@link History} using a file for persistent backing.
|
||||
* <p/>
|
||||
* Implementers should install shutdown hook to call {@link FileHistory#flush}
|
||||
* to save history to disk.
|
||||
*
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.0
|
||||
*/
|
||||
public class FileHistory
|
||||
extends MemoryHistory
|
||||
implements PersistentHistory, Flushable
|
||||
{
|
||||
private final File file;
|
||||
|
||||
public FileHistory(final File file) throws IOException {
|
||||
this.file = checkNotNull(file);
|
||||
load(file);
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public void load(final File file) throws IOException {
|
||||
checkNotNull(file);
|
||||
if (file.exists()) {
|
||||
Log.trace("Loading history from: ", file);
|
||||
load(new FileReader(file));
|
||||
}
|
||||
}
|
||||
|
||||
public void load(final InputStream input) throws IOException {
|
||||
checkNotNull(input);
|
||||
load(new InputStreamReader(input));
|
||||
}
|
||||
|
||||
public void load(final Reader reader) throws IOException {
|
||||
checkNotNull(reader);
|
||||
BufferedReader input = new BufferedReader(reader);
|
||||
|
||||
String item;
|
||||
while ((item = input.readLine()) != null) {
|
||||
internalAdd(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
Log.trace("Flushing history");
|
||||
|
||||
if (!file.exists()) {
|
||||
File dir = file.getParentFile();
|
||||
if (!dir.exists() && !dir.mkdirs()) {
|
||||
Log.warn("Failed to create directory: ", dir);
|
||||
}
|
||||
if (!file.createNewFile()) {
|
||||
Log.warn("Failed to create file: ", file);
|
||||
}
|
||||
}
|
||||
|
||||
PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream(file)));
|
||||
try {
|
||||
for (Entry entry : this) {
|
||||
out.println(entry.value());
|
||||
}
|
||||
}
|
||||
finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void purge() throws IOException {
|
||||
Log.trace("Purging history");
|
||||
|
||||
clear();
|
||||
|
||||
if (!file.delete()) {
|
||||
Log.warn("Failed to delete history file: ", file);
|
||||
}
|
||||
}
|
||||
}
|
106
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/history/History.java
Normal file
106
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/history/History.java
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.console.history;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.ListIterator;
|
||||
|
||||
/**
|
||||
* Console history.
|
||||
*
|
||||
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
public interface History
|
||||
extends Iterable<History.Entry>
|
||||
{
|
||||
int size();
|
||||
|
||||
boolean isEmpty();
|
||||
|
||||
int index();
|
||||
|
||||
void clear();
|
||||
|
||||
CharSequence get(int index);
|
||||
|
||||
void add(CharSequence line);
|
||||
|
||||
/**
|
||||
* Set the history item at the given index to the given CharSequence.
|
||||
*
|
||||
* @param index the index of the history offset
|
||||
* @param item the new item
|
||||
* @since 2.7
|
||||
*/
|
||||
void set(int index, CharSequence item);
|
||||
|
||||
/**
|
||||
* Remove the history element at the given index.
|
||||
*
|
||||
* @param i the index of the element to remove
|
||||
* @return the removed element
|
||||
* @since 2.7
|
||||
*/
|
||||
CharSequence remove(int i);
|
||||
|
||||
/**
|
||||
* Remove the first element from history.
|
||||
*
|
||||
* @return the removed element
|
||||
* @since 2.7
|
||||
*/
|
||||
CharSequence removeFirst();
|
||||
|
||||
/**
|
||||
* Remove the last element from history
|
||||
*
|
||||
* @return the removed element
|
||||
* @since 2.7
|
||||
*/
|
||||
CharSequence removeLast();
|
||||
|
||||
void replace(CharSequence item);
|
||||
|
||||
//
|
||||
// Entries
|
||||
//
|
||||
|
||||
interface Entry
|
||||
{
|
||||
int index();
|
||||
|
||||
CharSequence value();
|
||||
}
|
||||
|
||||
ListIterator<Entry> entries(int index);
|
||||
|
||||
ListIterator<Entry> entries();
|
||||
|
||||
Iterator<Entry> iterator();
|
||||
|
||||
//
|
||||
// Navigation
|
||||
//
|
||||
|
||||
CharSequence current();
|
||||
|
||||
boolean previous();
|
||||
|
||||
boolean next();
|
||||
|
||||
boolean moveToFirst();
|
||||
|
||||
boolean moveToLast();
|
||||
|
||||
boolean moveTo(int index);
|
||||
|
||||
void moveToEnd();
|
||||
}
|
348
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/history/MemoryHistory.java
Normal file
348
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/history/MemoryHistory.java
Normal file
@ -0,0 +1,348 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.console.history;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.ListIterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Non-persistent {@link History}.
|
||||
*
|
||||
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
public class MemoryHistory
|
||||
implements History
|
||||
{
|
||||
public static final int DEFAULT_MAX_SIZE = 500;
|
||||
|
||||
private final LinkedList<CharSequence> items = new LinkedList<CharSequence>();
|
||||
|
||||
private int maxSize = DEFAULT_MAX_SIZE;
|
||||
|
||||
private boolean ignoreDuplicates = true;
|
||||
|
||||
private boolean autoTrim = false;
|
||||
|
||||
// NOTE: These are all ideas from looking at the Bash man page:
|
||||
|
||||
// TODO: Add ignore space? (lines starting with a space are ignored)
|
||||
|
||||
// TODO: Add ignore patterns?
|
||||
|
||||
// TODO: Add history timestamp?
|
||||
|
||||
// TODO: Add erase dups?
|
||||
|
||||
private int offset = 0;
|
||||
|
||||
private int index = 0;
|
||||
|
||||
public void setMaxSize(final int maxSize) {
|
||||
this.maxSize = maxSize;
|
||||
maybeResize();
|
||||
}
|
||||
|
||||
public int getMaxSize() {
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
public boolean isIgnoreDuplicates() {
|
||||
return ignoreDuplicates;
|
||||
}
|
||||
|
||||
public void setIgnoreDuplicates(final boolean flag) {
|
||||
this.ignoreDuplicates = flag;
|
||||
}
|
||||
|
||||
public boolean isAutoTrim() {
|
||||
return autoTrim;
|
||||
}
|
||||
|
||||
public void setAutoTrim(final boolean flag) {
|
||||
this.autoTrim = flag;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return items.size();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return items.isEmpty();
|
||||
}
|
||||
|
||||
public int index() {
|
||||
return offset + index;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
items.clear();
|
||||
offset = 0;
|
||||
index = 0;
|
||||
}
|
||||
|
||||
public CharSequence get(final int index) {
|
||||
return items.get(index - offset);
|
||||
}
|
||||
|
||||
public void set(int index, CharSequence item) {
|
||||
items.set(index - offset, item);
|
||||
}
|
||||
|
||||
public void add(CharSequence item) {
|
||||
checkNotNull(item);
|
||||
|
||||
if (isAutoTrim()) {
|
||||
item = String.valueOf(item).trim();
|
||||
}
|
||||
|
||||
if (isIgnoreDuplicates()) {
|
||||
if (!items.isEmpty() && item.equals(items.getLast())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
internalAdd(item);
|
||||
}
|
||||
|
||||
public CharSequence remove(int i) {
|
||||
return items.remove(i);
|
||||
}
|
||||
|
||||
public CharSequence removeFirst() {
|
||||
return items.removeFirst();
|
||||
}
|
||||
|
||||
public CharSequence removeLast() {
|
||||
return items.removeLast();
|
||||
}
|
||||
|
||||
protected void internalAdd(CharSequence item) {
|
||||
items.add(item);
|
||||
|
||||
maybeResize();
|
||||
}
|
||||
|
||||
public void replace(final CharSequence item) {
|
||||
items.removeLast();
|
||||
add(item);
|
||||
}
|
||||
|
||||
private void maybeResize() {
|
||||
while (size() > getMaxSize()) {
|
||||
items.removeFirst();
|
||||
offset++;
|
||||
}
|
||||
|
||||
index = size();
|
||||
}
|
||||
|
||||
public ListIterator<Entry> entries(final int index) {
|
||||
return new EntriesIterator(index - offset);
|
||||
}
|
||||
|
||||
public ListIterator<Entry> entries() {
|
||||
return entries(offset);
|
||||
}
|
||||
|
||||
public Iterator<Entry> iterator() {
|
||||
return entries();
|
||||
}
|
||||
|
||||
private static class EntryImpl
|
||||
implements Entry
|
||||
{
|
||||
private final int index;
|
||||
|
||||
private final CharSequence value;
|
||||
|
||||
public EntryImpl(int index, CharSequence value) {
|
||||
this.index = index;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int index() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public CharSequence value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%d: %s", index, value);
|
||||
}
|
||||
}
|
||||
|
||||
private class EntriesIterator
|
||||
implements ListIterator<Entry>
|
||||
{
|
||||
private final ListIterator<CharSequence> source;
|
||||
|
||||
private EntriesIterator(final int index) {
|
||||
source = items.listIterator(index);
|
||||
}
|
||||
|
||||
public Entry next() {
|
||||
if (!source.hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
return new EntryImpl(offset + source.nextIndex(), source.next());
|
||||
}
|
||||
|
||||
public Entry previous() {
|
||||
if (!source.hasPrevious()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
return new EntryImpl(offset + source.previousIndex(), source.previous());
|
||||
}
|
||||
|
||||
public int nextIndex() {
|
||||
return offset + source.nextIndex();
|
||||
}
|
||||
|
||||
public int previousIndex() {
|
||||
return offset + source.previousIndex();
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return source.hasNext();
|
||||
}
|
||||
|
||||
public boolean hasPrevious() {
|
||||
return source.hasPrevious();
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void set(final Entry entry) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void add(final Entry entry) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Navigation
|
||||
//
|
||||
|
||||
/**
|
||||
* This moves the history to the last entry. This entry is one position
|
||||
* before the moveToEnd() position.
|
||||
*
|
||||
* @return Returns false if there were no history entries or the history
|
||||
* index was already at the last entry.
|
||||
*/
|
||||
public boolean moveToLast() {
|
||||
int lastEntry = size() - 1;
|
||||
if (lastEntry >= 0 && lastEntry != index) {
|
||||
index = size() - 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to the specified index in the history
|
||||
* @param index
|
||||
* @return
|
||||
*/
|
||||
public boolean moveTo(int index) {
|
||||
index -= offset;
|
||||
if (index >= 0 && index < size() ) {
|
||||
this.index = index;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the history index to the first entry.
|
||||
*
|
||||
* @return Return false if there are no entries in the history or if the
|
||||
* history is already at the beginning.
|
||||
*/
|
||||
public boolean moveToFirst() {
|
||||
if (size() > 0 && index != 0) {
|
||||
index = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to the end of the history buffer. This will be a blank entry, after
|
||||
* all of the other entries.
|
||||
*/
|
||||
public void moveToEnd() {
|
||||
index = size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the content of the current buffer.
|
||||
*/
|
||||
public CharSequence current() {
|
||||
if (index >= size()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return items.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the pointer to the previous element in the buffer.
|
||||
*
|
||||
* @return true if we successfully went to the previous element
|
||||
*/
|
||||
public boolean previous() {
|
||||
if (index <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
index--;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the pointer to the next element in the buffer.
|
||||
*
|
||||
* @return true if we successfully went to the next element
|
||||
*/
|
||||
public boolean next() {
|
||||
if (index >= size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
index++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Entry e : this) {
|
||||
sb.append(e.toString() + "\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
35
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/history/PersistentHistory.java
Normal file
35
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/history/PersistentHistory.java
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.console.history;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Persistent {@link History}.
|
||||
*
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
public interface PersistentHistory
|
||||
extends History
|
||||
{
|
||||
/**
|
||||
* Flush all items to persistent storage.
|
||||
*
|
||||
* @throws IOException Flush failed
|
||||
*/
|
||||
void flush() throws IOException;
|
||||
|
||||
/**
|
||||
* Purge persistent storage and {@link #clear}.
|
||||
*
|
||||
* @throws IOException Purge failed
|
||||
*/
|
||||
void purge() throws IOException;
|
||||
}
|
14
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/history/package-info.java
Normal file
14
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/history/package-info.java
Normal file
@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
/**
|
||||
* Console history support.
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
package jdk.internal.jline.console.history;
|
123
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/internal/ConsoleReaderInputStream.java
Normal file
123
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/internal/ConsoleReaderInputStream.java
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.console.internal;
|
||||
|
||||
import jdk.internal.jline.console.ConsoleReader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.SequenceInputStream;
|
||||
import java.util.Enumeration;
|
||||
|
||||
// FIXME: Clean up API and move to jline.console.runner package
|
||||
|
||||
/**
|
||||
* An {@link InputStream} implementation that wraps a {@link ConsoleReader}.
|
||||
* It is useful for setting up the {@link System#in} for a generic console.
|
||||
*
|
||||
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||
* @since 2.7
|
||||
*/
|
||||
class ConsoleReaderInputStream
|
||||
extends SequenceInputStream
|
||||
{
|
||||
private static InputStream systemIn = System.in;
|
||||
|
||||
public static void setIn() throws IOException {
|
||||
setIn(new ConsoleReader());
|
||||
}
|
||||
|
||||
public static void setIn(final ConsoleReader reader) {
|
||||
System.setIn(new ConsoleReaderInputStream(reader));
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the original {@link System#in} input stream.
|
||||
*/
|
||||
public static void restoreIn() {
|
||||
System.setIn(systemIn);
|
||||
}
|
||||
|
||||
public ConsoleReaderInputStream(final ConsoleReader reader) {
|
||||
super(new ConsoleEnumeration(reader));
|
||||
}
|
||||
|
||||
private static class ConsoleEnumeration
|
||||
implements Enumeration<InputStream>
|
||||
{
|
||||
private final ConsoleReader reader;
|
||||
private ConsoleLineInputStream next = null;
|
||||
private ConsoleLineInputStream prev = null;
|
||||
|
||||
public ConsoleEnumeration(final ConsoleReader reader) {
|
||||
this.reader = reader;
|
||||
}
|
||||
|
||||
public InputStream nextElement() {
|
||||
if (next != null) {
|
||||
InputStream n = next;
|
||||
prev = next;
|
||||
next = null;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
return new ConsoleLineInputStream(reader);
|
||||
}
|
||||
|
||||
public boolean hasMoreElements() {
|
||||
// the last line was null
|
||||
if ((prev != null) && (prev.wasNull == true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (next == null) {
|
||||
next = (ConsoleLineInputStream) nextElement();
|
||||
}
|
||||
|
||||
return next != null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ConsoleLineInputStream
|
||||
extends InputStream
|
||||
{
|
||||
private final ConsoleReader reader;
|
||||
private String line = null;
|
||||
private int index = 0;
|
||||
private boolean eol = false;
|
||||
protected boolean wasNull = false;
|
||||
|
||||
public ConsoleLineInputStream(final ConsoleReader reader) {
|
||||
this.reader = reader;
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
if (eol) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (line == null) {
|
||||
line = reader.readLine();
|
||||
}
|
||||
|
||||
if (line == null) {
|
||||
wasNull = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (index >= line.length()) {
|
||||
eol = true;
|
||||
return '\n'; // lines are ended with a newline
|
||||
}
|
||||
|
||||
return line.charAt(index++);
|
||||
}
|
||||
}
|
||||
}
|
93
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/internal/ConsoleRunner.java
Normal file
93
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/internal/ConsoleRunner.java
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.console.internal;
|
||||
|
||||
import jdk.internal.jline.console.ConsoleReader;
|
||||
import jdk.internal.jline.console.completer.ArgumentCompleter;
|
||||
import jdk.internal.jline.console.completer.Completer;
|
||||
import jdk.internal.jline.console.history.FileHistory;
|
||||
import jdk.internal.jline.internal.Configuration;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
// FIXME: Clean up API and move to jline.console.runner package
|
||||
|
||||
/**
|
||||
* A pass-through application that sets the system input stream to a
|
||||
* {@link ConsoleReader} and invokes the specified main method.
|
||||
*
|
||||
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||
* @since 2.7
|
||||
*/
|
||||
public class ConsoleRunner
|
||||
{
|
||||
public static final String property = "jline.history";
|
||||
|
||||
// FIXME: This is really ugly... re-write this
|
||||
|
||||
public static void main(final String[] args) throws Exception {
|
||||
List<String> argList = new ArrayList<String>(Arrays.asList(args));
|
||||
if (argList.size() == 0) {
|
||||
usage();
|
||||
return;
|
||||
}
|
||||
|
||||
String historyFileName = System.getProperty(ConsoleRunner.property, null);
|
||||
|
||||
String mainClass = argList.remove(0);
|
||||
ConsoleReader reader = new ConsoleReader();
|
||||
|
||||
if (historyFileName != null) {
|
||||
reader.setHistory(new FileHistory(new File(Configuration.getUserHome(),
|
||||
String.format(".jline-%s.%s.history", mainClass, historyFileName))));
|
||||
}
|
||||
else {
|
||||
reader.setHistory(new FileHistory(new File(Configuration.getUserHome(),
|
||||
String.format(".jline-%s.history", mainClass))));
|
||||
}
|
||||
|
||||
String completors = System.getProperty(ConsoleRunner.class.getName() + ".completers", "");
|
||||
List<Completer> completorList = new ArrayList<Completer>();
|
||||
|
||||
for (StringTokenizer tok = new StringTokenizer(completors, ","); tok.hasMoreTokens();) {
|
||||
Object obj = Class.forName(tok.nextToken()).newInstance();
|
||||
completorList.add((Completer) obj);
|
||||
}
|
||||
|
||||
if (completorList.size() > 0) {
|
||||
reader.addCompleter(new ArgumentCompleter(completorList));
|
||||
}
|
||||
|
||||
ConsoleReaderInputStream.setIn(reader);
|
||||
|
||||
try {
|
||||
Class<?> type = Class.forName(mainClass);
|
||||
Method method = type.getMethod("main", String[].class);
|
||||
method.invoke(null);
|
||||
}
|
||||
finally {
|
||||
// just in case this main method is called from another program
|
||||
ConsoleReaderInputStream.restoreIn();
|
||||
}
|
||||
}
|
||||
|
||||
private static void usage() {
|
||||
System.out.println("Usage: \n java " + "[-Djline.history='name'] "
|
||||
+ ConsoleRunner.class.getName()
|
||||
+ " <target class name> [args]"
|
||||
+ "\n\nThe -Djline.history option will avoid history"
|
||||
+ "\nmangling when running ConsoleRunner on the same application."
|
||||
+ "\n\nargs will be passed directly to the target class name.");
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
/**
|
||||
* Console support.
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
package jdk.internal.jline.console;
|
239
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/internal/Configuration.java
Normal file
239
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/internal/Configuration.java
Normal file
@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.internal;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Provides access to configuration values.
|
||||
*
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
|
||||
* @since 2.4
|
||||
*/
|
||||
public class Configuration
|
||||
{
|
||||
/**
|
||||
* System property which can point to a file or URL containing configuration properties to load.
|
||||
*
|
||||
* @since 2.7
|
||||
*/
|
||||
public static final String JLINE_CONFIGURATION = "jline.configuration";
|
||||
|
||||
/**
|
||||
* Default configuration file name loaded from user's home directory.
|
||||
*/
|
||||
public static final String JLINE_RC = ".jline.rc";
|
||||
|
||||
private static volatile Properties properties;
|
||||
|
||||
private static Properties initProperties() {
|
||||
URL url = determineUrl();
|
||||
Properties props = new Properties();
|
||||
try {
|
||||
loadProperties(url, props);
|
||||
}
|
||||
catch (IOException e) {
|
||||
// debug here instead of warn, as this can happen normally if default jline.rc file is missing
|
||||
Log.debug("Unable to read configuration from: ", url, e);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
private static void loadProperties(final URL url, final Properties props) throws IOException {
|
||||
Log.debug("Loading properties from: ", url);
|
||||
InputStream input = url.openStream();
|
||||
try {
|
||||
props.load(new BufferedInputStream(input));
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
input.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
if (Log.DEBUG) {
|
||||
Log.debug("Loaded properties:");
|
||||
for (Map.Entry<Object,Object> entry : props.entrySet()) {
|
||||
Log.debug(" ", entry.getKey(), "=", entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static URL determineUrl() {
|
||||
// See if user has customized the configuration location via sysprop
|
||||
String tmp = System.getProperty(JLINE_CONFIGURATION);
|
||||
if (tmp != null) {
|
||||
return Urls.create(tmp);
|
||||
}
|
||||
else {
|
||||
// Otherwise try the default
|
||||
File file = new File(getUserHome(), JLINE_RC);
|
||||
return Urls.create(file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.7
|
||||
*/
|
||||
public static void reset() {
|
||||
Log.debug("Resetting");
|
||||
properties = null;
|
||||
|
||||
// force new properties to load
|
||||
getProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.7
|
||||
*/
|
||||
public static Properties getProperties() {
|
||||
// Not sure its worth to guard this with any synchronization, volatile field probably sufficient
|
||||
if (properties == null) {
|
||||
properties = initProperties();
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
public static String getString(final String name, final String defaultValue) {
|
||||
checkNotNull(name);
|
||||
|
||||
String value;
|
||||
|
||||
// Check sysprops first, it always wins
|
||||
value = System.getProperty(name);
|
||||
|
||||
if (value == null) {
|
||||
// Next try userprops
|
||||
value = getProperties().getProperty(name);
|
||||
|
||||
if (value == null) {
|
||||
// else use the default
|
||||
value = defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static String getString(final String name) {
|
||||
return getString(name, null);
|
||||
}
|
||||
|
||||
public static boolean getBoolean(final String name, final boolean defaultValue) {
|
||||
String value = getString(name);
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return value.length() == 0
|
||||
|| value.equalsIgnoreCase("1")
|
||||
|| value.equalsIgnoreCase("on")
|
||||
|| value.equalsIgnoreCase("true");
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.6
|
||||
*/
|
||||
public static int getInteger(final String name, final int defaultValue) {
|
||||
String str = getString(name);
|
||||
if (str == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return Integer.parseInt(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.6
|
||||
*/
|
||||
public static long getLong(final String name, final long defaultValue) {
|
||||
String str = getString(name);
|
||||
if (str == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return Long.parseLong(str);
|
||||
}
|
||||
|
||||
//
|
||||
// System property helpers
|
||||
//
|
||||
|
||||
/**
|
||||
* @since 2.7
|
||||
*/
|
||||
public static String getLineSeparator() {
|
||||
return System.getProperty("line.separator");
|
||||
}
|
||||
|
||||
public static File getUserHome() {
|
||||
return new File(System.getProperty("user.home"));
|
||||
}
|
||||
|
||||
public static String getOsName() {
|
||||
return System.getProperty("os.name").toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.7
|
||||
*/
|
||||
public static boolean isWindows() {
|
||||
return getOsName().startsWith("windows");
|
||||
}
|
||||
|
||||
// FIXME: Sort out use of property access of file.encoding in InputStreamReader, consolidate should configuration access here
|
||||
|
||||
public static String getFileEncoding() {
|
||||
return System.getProperty("file.encoding");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default encoding. Will first look at the LC_CTYPE environment variable, then the input.encoding
|
||||
* system property, then the default charset according to the JVM.
|
||||
*
|
||||
* @return The default encoding to use when none is specified.
|
||||
*/
|
||||
public static String getEncoding() {
|
||||
// LC_CTYPE is usually in the form en_US.UTF-8
|
||||
String envEncoding = extractEncodingFromCtype(System.getenv("LC_CTYPE"));
|
||||
if (envEncoding != null) {
|
||||
return envEncoding;
|
||||
}
|
||||
return System.getProperty("input.encoding", Charset.defaultCharset().name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the LC_CTYPE value to extract the encoding according to the POSIX standard, which says that the LC_CTYPE
|
||||
* environment variable may be of the format <code>[language[_territory][.codeset][@modifier]]</code>
|
||||
*
|
||||
* @param ctype The ctype to parse, may be null
|
||||
* @return The encoding, if one was present, otherwise null
|
||||
*/
|
||||
static String extractEncodingFromCtype(String ctype) {
|
||||
if (ctype != null && ctype.indexOf('.') > 0) {
|
||||
String encodingAndModifier = ctype.substring(ctype.indexOf('.') + 1);
|
||||
if (encodingAndModifier.indexOf('@') > 0) {
|
||||
return encodingAndModifier.substring(0, encodingAndModifier.indexOf('@'));
|
||||
} else {
|
||||
return encodingAndModifier;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
338
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/internal/InputStreamReader.java
Normal file
338
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/internal/InputStreamReader.java
Normal file
@ -0,0 +1,338 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.nio.charset.MalformedInputException;
|
||||
import java.nio.charset.UnmappableCharacterException;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* NOTE for JLine: the default InputStreamReader that comes from the JRE
|
||||
* usually read more bytes than needed from the input stream, which
|
||||
* is not usable in a character per character model used in the console.
|
||||
* We thus use the harmony code which only reads the minimal number of bytes,
|
||||
* with a modification to ensure we can read larger characters (UTF-16 has
|
||||
* up to 4 bytes, and UTF-32, rare as it is, may have up to 8).
|
||||
*/
|
||||
/**
|
||||
* A class for turning a byte stream into a character stream. Data read from the
|
||||
* source input stream is converted into characters by either a default or a
|
||||
* provided character converter. The default encoding is taken from the
|
||||
* "file.encoding" system property. {@code InputStreamReader} contains a buffer
|
||||
* of bytes read from the source stream and converts these into characters as
|
||||
* needed. The buffer size is 8K.
|
||||
*
|
||||
* @see OutputStreamWriter
|
||||
*/
|
||||
public class InputStreamReader extends Reader {
|
||||
private InputStream in;
|
||||
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
private boolean endOfInput = false;
|
||||
|
||||
String encoding;
|
||||
|
||||
CharsetDecoder decoder;
|
||||
|
||||
ByteBuffer bytes = ByteBuffer.allocate(BUFFER_SIZE);
|
||||
|
||||
/**
|
||||
* Constructs a new {@code InputStreamReader} on the {@link InputStream}
|
||||
* {@code in}. This constructor sets the character converter to the encoding
|
||||
* specified in the "file.encoding" property and falls back to ISO 8859_1
|
||||
* (ISO-Latin-1) if the property doesn't exist.
|
||||
*
|
||||
* @param in
|
||||
* the input stream from which to read characters.
|
||||
*/
|
||||
public InputStreamReader(InputStream in) {
|
||||
super(in);
|
||||
this.in = in;
|
||||
// FIXME: This should probably use Configuration.getFileEncoding()
|
||||
encoding = System.getProperty("file.encoding", "ISO8859_1"); //$NON-NLS-1$//$NON-NLS-2$
|
||||
decoder = Charset.forName(encoding).newDecoder().onMalformedInput(
|
||||
CodingErrorAction.REPLACE).onUnmappableCharacter(
|
||||
CodingErrorAction.REPLACE);
|
||||
bytes.limit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new InputStreamReader on the InputStream {@code in}. The
|
||||
* character converter that is used to decode bytes into characters is
|
||||
* identified by name by {@code enc}. If the encoding cannot be found, an
|
||||
* UnsupportedEncodingException error is thrown.
|
||||
*
|
||||
* @param in
|
||||
* the InputStream from which to read characters.
|
||||
* @param enc
|
||||
* identifies the character converter to use.
|
||||
* @throws NullPointerException
|
||||
* if {@code enc} is {@code null}.
|
||||
* @throws UnsupportedEncodingException
|
||||
* if the encoding specified by {@code enc} cannot be found.
|
||||
*/
|
||||
public InputStreamReader(InputStream in, final String enc)
|
||||
throws UnsupportedEncodingException {
|
||||
super(in);
|
||||
if (enc == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
this.in = in;
|
||||
try {
|
||||
decoder = Charset.forName(enc).newDecoder().onMalformedInput(
|
||||
CodingErrorAction.REPLACE).onUnmappableCharacter(
|
||||
CodingErrorAction.REPLACE);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw (UnsupportedEncodingException)
|
||||
new UnsupportedEncodingException(enc).initCause(e);
|
||||
}
|
||||
bytes.limit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new InputStreamReader on the InputStream {@code in} and
|
||||
* CharsetDecoder {@code dec}.
|
||||
*
|
||||
* @param in
|
||||
* the source InputStream from which to read characters.
|
||||
* @param dec
|
||||
* the CharsetDecoder used by the character conversion.
|
||||
*/
|
||||
public InputStreamReader(InputStream in, CharsetDecoder dec) {
|
||||
super(in);
|
||||
dec.averageCharsPerByte();
|
||||
this.in = in;
|
||||
decoder = dec;
|
||||
bytes.limit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new InputStreamReader on the InputStream {@code in} and
|
||||
* Charset {@code charset}.
|
||||
*
|
||||
* @param in
|
||||
* the source InputStream from which to read characters.
|
||||
* @param charset
|
||||
* the Charset that defines the character converter
|
||||
*/
|
||||
public InputStreamReader(InputStream in, Charset charset) {
|
||||
super(in);
|
||||
this.in = in;
|
||||
decoder = charset.newDecoder().onMalformedInput(
|
||||
CodingErrorAction.REPLACE).onUnmappableCharacter(
|
||||
CodingErrorAction.REPLACE);
|
||||
bytes.limit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes this reader. This implementation closes the source InputStream and
|
||||
* releases all local storage.
|
||||
*
|
||||
* @throws IOException
|
||||
* if an error occurs attempting to close this reader.
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
synchronized (lock) {
|
||||
decoder = null;
|
||||
if (in != null) {
|
||||
in.close();
|
||||
in = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the encoding used to convert bytes into characters.
|
||||
* The value {@code null} is returned if this reader has been closed.
|
||||
*
|
||||
* @return the name of the character converter or {@code null} if this
|
||||
* reader is closed.
|
||||
*/
|
||||
public String getEncoding() {
|
||||
if (!isOpen()) {
|
||||
return null;
|
||||
}
|
||||
return encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a single character from this reader and returns it as an integer
|
||||
* with the two higher-order bytes set to 0. Returns -1 if the end of the
|
||||
* reader has been reached. The byte value is either obtained from
|
||||
* converting bytes in this reader's buffer or by first filling the buffer
|
||||
* from the source InputStream and then reading from the buffer.
|
||||
*
|
||||
* @return the character read or -1 if the end of the reader has been
|
||||
* reached.
|
||||
* @throws IOException
|
||||
* if this reader is closed or some other I/O error occurs.
|
||||
*/
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
synchronized (lock) {
|
||||
if (!isOpen()) {
|
||||
throw new IOException("InputStreamReader is closed.");
|
||||
}
|
||||
|
||||
char buf[] = new char[4];
|
||||
return read(buf, 0, 4) != -1 ? Character.codePointAt(buf, 0) : -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads at most {@code length} characters from this reader and stores them
|
||||
* at position {@code offset} in the character array {@code buf}. Returns
|
||||
* the number of characters actually read or -1 if the end of the reader has
|
||||
* been reached. The bytes are either obtained from converting bytes in this
|
||||
* reader's buffer or by first filling the buffer from the source
|
||||
* InputStream and then reading from the buffer.
|
||||
*
|
||||
* @param buf
|
||||
* the array to store the characters read.
|
||||
* @param offset
|
||||
* the initial position in {@code buf} to store the characters
|
||||
* read from this reader.
|
||||
* @param length
|
||||
* the maximum number of characters to read.
|
||||
* @return the number of characters read or -1 if the end of the reader has
|
||||
* been reached.
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if {@code offset < 0} or {@code length < 0}, or if
|
||||
* {@code offset + length} is greater than the length of
|
||||
* {@code buf}.
|
||||
* @throws IOException
|
||||
* if this reader is closed or some other I/O error occurs.
|
||||
*/
|
||||
@Override
|
||||
public int read(char[] buf, int offset, int length) throws IOException {
|
||||
synchronized (lock) {
|
||||
if (!isOpen()) {
|
||||
throw new IOException("InputStreamReader is closed.");
|
||||
}
|
||||
if (offset < 0 || offset > buf.length - length || length < 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
if (length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
CharBuffer out = CharBuffer.wrap(buf, offset, length);
|
||||
CoderResult result = CoderResult.UNDERFLOW;
|
||||
|
||||
// bytes.remaining() indicates number of bytes in buffer
|
||||
// when 1-st time entered, it'll be equal to zero
|
||||
boolean needInput = !bytes.hasRemaining();
|
||||
|
||||
while (out.hasRemaining()) {
|
||||
// fill the buffer if needed
|
||||
if (needInput) {
|
||||
try {
|
||||
if ((in.available() == 0)
|
||||
&& (out.position() > offset)) {
|
||||
// we could return the result without blocking read
|
||||
break;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// available didn't work so just try the read
|
||||
}
|
||||
|
||||
int to_read = bytes.capacity() - bytes.limit();
|
||||
int off = bytes.arrayOffset() + bytes.limit();
|
||||
int was_red = in.read(bytes.array(), off, to_read);
|
||||
|
||||
if (was_red == -1) {
|
||||
endOfInput = true;
|
||||
break;
|
||||
} else if (was_red == 0) {
|
||||
break;
|
||||
}
|
||||
bytes.limit(bytes.limit() + was_red);
|
||||
needInput = false;
|
||||
}
|
||||
|
||||
// decode bytes
|
||||
result = decoder.decode(bytes, out, false);
|
||||
|
||||
if (result.isUnderflow()) {
|
||||
// compact the buffer if no space left
|
||||
if (bytes.limit() == bytes.capacity()) {
|
||||
bytes.compact();
|
||||
bytes.limit(bytes.position());
|
||||
bytes.position(0);
|
||||
}
|
||||
needInput = true;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result == CoderResult.UNDERFLOW && endOfInput) {
|
||||
result = decoder.decode(bytes, out, true);
|
||||
decoder.flush(out);
|
||||
decoder.reset();
|
||||
}
|
||||
if (result.isMalformed()) {
|
||||
throw new MalformedInputException(result.length());
|
||||
} else if (result.isUnmappable()) {
|
||||
throw new UnmappableCharacterException(result.length());
|
||||
}
|
||||
|
||||
return out.position() - offset == 0 ? -1 : out.position() - offset;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Answer a boolean indicating whether or not this InputStreamReader is
|
||||
* open.
|
||||
*/
|
||||
private boolean isOpen() {
|
||||
return in != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this reader is ready to be read without blocking. If
|
||||
* the result is {@code true}, the next {@code read()} will not block. If
|
||||
* the result is {@code false} then this reader may or may not block when
|
||||
* {@code read()} is called. This implementation returns {@code true} if
|
||||
* there are bytes available in the buffer or the source stream has bytes
|
||||
* available.
|
||||
*
|
||||
* @return {@code true} if the receiver will not block when {@code read()}
|
||||
* is called, {@code false} if unknown or blocking will occur.
|
||||
* @throws IOException
|
||||
* if this reader is closed or some other I/O error occurs.
|
||||
*/
|
||||
@Override
|
||||
public boolean ready() throws IOException {
|
||||
synchronized (lock) {
|
||||
if (in == null) {
|
||||
throw new IOException("InputStreamReader is closed.");
|
||||
}
|
||||
try {
|
||||
return bytes.hasRemaining() || in.available() > 0;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.internal;
|
||||
|
||||
import java.io.PrintStream;
|
||||
|
||||
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Internal logger.
|
||||
*
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.0
|
||||
*/
|
||||
public final class Log
|
||||
{
|
||||
///CLOVER:OFF
|
||||
|
||||
public static enum Level
|
||||
{
|
||||
TRACE,
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARN,
|
||||
ERROR
|
||||
}
|
||||
|
||||
@SuppressWarnings({"StringConcatenation"})
|
||||
public static final boolean TRACE = Boolean.getBoolean(Log.class.getName() + ".trace");
|
||||
|
||||
@SuppressWarnings({"StringConcatenation"})
|
||||
public static final boolean DEBUG = TRACE || Boolean.getBoolean(Log.class.getName() + ".debug");
|
||||
|
||||
private static PrintStream output = System.err;
|
||||
|
||||
public static PrintStream getOutput() {
|
||||
return output;
|
||||
}
|
||||
|
||||
public static void setOutput(final PrintStream out) {
|
||||
output = checkNotNull(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to support rendering messages.
|
||||
*/
|
||||
@TestAccessible
|
||||
static void render(final PrintStream out, final Object message) {
|
||||
if (message.getClass().isArray()) {
|
||||
Object[] array = (Object[]) message;
|
||||
|
||||
out.print("[");
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
out.print(array[i]);
|
||||
if (i + 1 < array.length) {
|
||||
out.print(",");
|
||||
}
|
||||
}
|
||||
out.print("]");
|
||||
}
|
||||
else {
|
||||
out.print(message);
|
||||
}
|
||||
}
|
||||
|
||||
@TestAccessible
|
||||
static void log(final Level level, final Object... messages) {
|
||||
//noinspection SynchronizeOnNonFinalField
|
||||
synchronized (output) {
|
||||
output.format("[%s] ", level);
|
||||
|
||||
for (int i=0; i<messages.length; i++) {
|
||||
// Special handling for the last message if its a throwable, render its stack on the next line
|
||||
if (i + 1 == messages.length && messages[i] instanceof Throwable) {
|
||||
output.println();
|
||||
((Throwable)messages[i]).printStackTrace(output);
|
||||
}
|
||||
else {
|
||||
render(output, messages[i]);
|
||||
}
|
||||
}
|
||||
|
||||
output.println();
|
||||
output.flush();
|
||||
}
|
||||
}
|
||||
|
||||
public static void trace(final Object... messages) {
|
||||
if (TRACE) {
|
||||
log(Level.TRACE, messages);
|
||||
}
|
||||
}
|
||||
|
||||
public static void debug(final Object... messages) {
|
||||
if (TRACE || DEBUG) {
|
||||
log(Level.DEBUG, messages);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.7
|
||||
*/
|
||||
public static void info(final Object... messages) {
|
||||
log(Level.INFO, messages);
|
||||
}
|
||||
|
||||
public static void warn(final Object... messages) {
|
||||
log(Level.WARN, messages);
|
||||
}
|
||||
|
||||
public static void error(final Object... messages) {
|
||||
log(Level.ERROR, messages);
|
||||
}
|
||||
}
|
314
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/internal/NonBlockingInputStream.java
Normal file
314
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/internal/NonBlockingInputStream.java
Normal file
@ -0,0 +1,314 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* This class wraps a regular input stream and allows it to appear as if it
|
||||
* is non-blocking; that is, reads can be performed against it that timeout
|
||||
* if no data is seen for a period of time. This effect is achieved by having
|
||||
* a separate thread perform all non-blocking read requests and then
|
||||
* waiting on the thread to complete.
|
||||
*
|
||||
* <p>VERY IMPORTANT NOTES
|
||||
* <ul>
|
||||
* <li> This class is not thread safe. It expects at most one reader.
|
||||
* <li> The {@link #shutdown()} method must be called in order to shut down
|
||||
* the thread that handles blocking I/O.
|
||||
* </ul>
|
||||
* @since 2.7
|
||||
* @author Scott C. Gray <scottgray1@gmail.com>
|
||||
*/
|
||||
public class NonBlockingInputStream
|
||||
extends InputStream
|
||||
implements Runnable
|
||||
{
|
||||
private InputStream in; // The actual input stream
|
||||
private int ch = -2; // Recently read character
|
||||
|
||||
private boolean threadIsReading = false;
|
||||
private boolean isShutdown = false;
|
||||
private IOException exception = null;
|
||||
private boolean nonBlockingEnabled;
|
||||
|
||||
/**
|
||||
* Creates a <code>NonBlockingInputStream</code> out of a normal blocking
|
||||
* stream. Note that this call also spawn a separate thread to perform the
|
||||
* blocking I/O on behalf of the thread that is using this class. The
|
||||
* {@link #shutdown()} method must be called in order to shut this thread down.
|
||||
* @param in The input stream to wrap
|
||||
* @param isNonBlockingEnabled If true, then the non-blocking methods
|
||||
* {@link #read(long)} and {@link #peek(long)} will be available and,
|
||||
* more importantly, the thread will be started to provide support for the
|
||||
* feature. If false, then this class acts as a clean-passthru for the
|
||||
* underlying I/O stream and provides very little overhead.
|
||||
*/
|
||||
public NonBlockingInputStream (InputStream in, boolean isNonBlockingEnabled) {
|
||||
this.in = in;
|
||||
this.nonBlockingEnabled = isNonBlockingEnabled;
|
||||
|
||||
if (isNonBlockingEnabled) {
|
||||
Thread t = new Thread(this);
|
||||
t.setName("NonBlockingInputStreamThread");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down the thread that is handling blocking I/O. Note that if the
|
||||
* thread is currently blocked waiting for I/O it will not actually
|
||||
* shut down until the I/O is received. Shutting down the I/O thread
|
||||
* does not prevent this class from being used, but causes the
|
||||
* non-blocking methods to fail if called and causes {@link #isNonBlockingEnabled()}
|
||||
* to return false.
|
||||
*/
|
||||
public synchronized void shutdown() {
|
||||
if (!isShutdown && nonBlockingEnabled) {
|
||||
isShutdown = true;
|
||||
notify();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-blocking is considered enabled if the feature is enabled and the
|
||||
* I/O thread has not been shut down.
|
||||
* @return true if non-blocking mode is enabled.
|
||||
*/
|
||||
public boolean isNonBlockingEnabled() {
|
||||
return nonBlockingEnabled && !isShutdown;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
/*
|
||||
* The underlying input stream is closed first. This means that if the
|
||||
* I/O thread was blocked waiting on input, it will be woken for us.
|
||||
*/
|
||||
in.close();
|
||||
shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (nonBlockingEnabled)
|
||||
return read(0L, false);
|
||||
return in.read ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Peeks to see if there is a byte waiting in the input stream without
|
||||
* actually consuming the byte.
|
||||
*
|
||||
* @param timeout The amount of time to wait, 0 == forever
|
||||
* @return -1 on eof, -2 if the timeout expired with no available input
|
||||
* or the character that was read (without consuming it).
|
||||
* @throws IOException
|
||||
*/
|
||||
public int peek(long timeout) throws IOException {
|
||||
if (!nonBlockingEnabled || isShutdown) {
|
||||
throw new UnsupportedOperationException ("peek() "
|
||||
+ "cannot be called as non-blocking operation is disabled");
|
||||
}
|
||||
return read(timeout, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to read a character from the input stream for a specific
|
||||
* period of time.
|
||||
* @param timeout The amount of time to wait for the character
|
||||
* @return The character read, -1 if EOF is reached, or -2 if the
|
||||
* read timed out.
|
||||
* @throws IOException
|
||||
*/
|
||||
public int read(long timeout) throws IOException {
|
||||
if (!nonBlockingEnabled || isShutdown) {
|
||||
throw new UnsupportedOperationException ("read() with timeout "
|
||||
+ "cannot be called as non-blocking operation is disabled");
|
||||
}
|
||||
return read(timeout, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to read a character from the input stream for a specific
|
||||
* period of time.
|
||||
* @param timeout The amount of time to wait for the character
|
||||
* @return The character read, -1 if EOF is reached, or -2 if the
|
||||
* read timed out.
|
||||
* @throws IOException
|
||||
*/
|
||||
private synchronized int read(long timeout, boolean isPeek) throws IOException {
|
||||
/*
|
||||
* If the thread hit an IOException, we report it.
|
||||
*/
|
||||
if (exception != null) {
|
||||
assert ch == -2;
|
||||
IOException toBeThrown = exception;
|
||||
if (!isPeek)
|
||||
exception = null;
|
||||
throw toBeThrown;
|
||||
}
|
||||
|
||||
/*
|
||||
* If there was a pending character from the thread, then
|
||||
* we send it. If the timeout is 0L or the thread was shut down
|
||||
* then do a local read.
|
||||
*/
|
||||
if (ch >= -1) {
|
||||
assert exception == null;
|
||||
}
|
||||
else if ((timeout == 0L || isShutdown) && !threadIsReading) {
|
||||
ch = in.read();
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* If the thread isn't reading already, then ask it to do so.
|
||||
*/
|
||||
if (!threadIsReading) {
|
||||
threadIsReading = true;
|
||||
notify();
|
||||
}
|
||||
|
||||
boolean isInfinite = (timeout <= 0L);
|
||||
|
||||
/*
|
||||
* So the thread is currently doing the reading for us. So
|
||||
* now we play the waiting game.
|
||||
*/
|
||||
while (isInfinite || timeout > 0L) {
|
||||
long start = System.currentTimeMillis ();
|
||||
|
||||
try {
|
||||
wait(timeout);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
/* IGNORED */
|
||||
}
|
||||
|
||||
if (exception != null) {
|
||||
assert ch == -2;
|
||||
|
||||
IOException toBeThrown = exception;
|
||||
if (!isPeek)
|
||||
exception = null;
|
||||
throw toBeThrown;
|
||||
}
|
||||
|
||||
if (ch >= -1) {
|
||||
assert exception == null;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isInfinite) {
|
||||
timeout -= System.currentTimeMillis() - start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ch is the character that was just read. Either we set it because
|
||||
* a local read was performed or the read thread set it (or failed to
|
||||
* change it). We will return it's value, but if this was a peek
|
||||
* operation, then we leave it in place.
|
||||
*/
|
||||
int ret = ch;
|
||||
if (!isPeek) {
|
||||
ch = -2;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* This version of read() is very specific to jline's purposes, it
|
||||
* will always always return a single byte at a time, rather than filling
|
||||
* the entire buffer.
|
||||
*/
|
||||
@Override
|
||||
public int read (byte[] b, int off, int len) throws IOException {
|
||||
if (b == null) {
|
||||
throw new NullPointerException();
|
||||
} else if (off < 0 || len < 0 || len > b.length - off) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
} else if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int c;
|
||||
if (nonBlockingEnabled)
|
||||
c = this.read(0L);
|
||||
else
|
||||
c = in.read();
|
||||
|
||||
if (c == -1) {
|
||||
return -1;
|
||||
}
|
||||
b[off] = (byte)c;
|
||||
return 1;
|
||||
}
|
||||
|
||||
//@Override
|
||||
public void run () {
|
||||
Log.debug("NonBlockingInputStream start");
|
||||
boolean needToShutdown = false;
|
||||
boolean needToRead = false;
|
||||
|
||||
while (!needToShutdown) {
|
||||
|
||||
/*
|
||||
* Synchronize to grab variables accessed by both this thread
|
||||
* and the accessing thread.
|
||||
*/
|
||||
synchronized (this) {
|
||||
needToShutdown = this.isShutdown;
|
||||
needToRead = this.threadIsReading;
|
||||
|
||||
try {
|
||||
/*
|
||||
* Nothing to do? Then wait.
|
||||
*/
|
||||
if (!needToShutdown && !needToRead) {
|
||||
wait(0);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
/* IGNORED */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We're not shutting down, but we need to read. This cannot
|
||||
* happen while we are holding the lock (which we aren't now).
|
||||
*/
|
||||
if (!needToShutdown && needToRead) {
|
||||
int charRead = -2;
|
||||
IOException failure = null;
|
||||
try {
|
||||
charRead = in.read();
|
||||
}
|
||||
catch (IOException e) {
|
||||
failure = e;
|
||||
}
|
||||
|
||||
/*
|
||||
* Re-grab the lock to update the state.
|
||||
*/
|
||||
synchronized (this) {
|
||||
exception = failure;
|
||||
ch = charRead;
|
||||
threadIsReading = false;
|
||||
notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.debug("NonBlockingInputStream shutdown");
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.internal;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Marker for reference which can be a null value.
|
||||
*
|
||||
* @since 2.7
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
|
||||
public @interface Nullable
|
||||
{
|
||||
String value() default "";
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.internal;
|
||||
|
||||
// Some bits lifted from Guava's ( http://code.google.com/p/guava-libraries/ ) Preconditions.
|
||||
|
||||
/**
|
||||
* Preconditions.
|
||||
*
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.7
|
||||
*/
|
||||
public class Preconditions
|
||||
{
|
||||
public static <T> T checkNotNull(final T reference) {
|
||||
if (reference == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
return reference;
|
||||
}
|
||||
}
|
128
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/internal/ShutdownHooks.java
Normal file
128
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/internal/ShutdownHooks.java
Normal file
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Manages the JLine shutdown-hook thread and tasks to execute on shutdown.
|
||||
*
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @since 2.7
|
||||
*/
|
||||
public class ShutdownHooks
|
||||
{
|
||||
public static final String JLINE_SHUTDOWNHOOK = "jline.shutdownhook";
|
||||
|
||||
private static final boolean enabled = Configuration.getBoolean(JLINE_SHUTDOWNHOOK, true);
|
||||
|
||||
private static final List<Task> tasks = new ArrayList<Task>();
|
||||
|
||||
private static Thread hook;
|
||||
|
||||
public static synchronized <T extends Task> T add(final T task) {
|
||||
checkNotNull(task);
|
||||
|
||||
// If not enabled ignore
|
||||
if (!enabled) {
|
||||
Log.debug("Shutdown-hook is disabled; not installing: ", task);
|
||||
return task;
|
||||
}
|
||||
|
||||
// Install the hook thread if needed
|
||||
if (hook == null) {
|
||||
hook = addHook(new Thread("JLine Shutdown Hook")
|
||||
{
|
||||
@Override
|
||||
public void run() {
|
||||
runTasks();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Track the task
|
||||
Log.debug("Adding shutdown-hook task: ", task);
|
||||
tasks.add(task);
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
private static synchronized void runTasks() {
|
||||
Log.debug("Running all shutdown-hook tasks");
|
||||
|
||||
// Iterate through copy of tasks list
|
||||
for (Task task : tasks.toArray(new Task[tasks.size()])) {
|
||||
Log.debug("Running task: ", task);
|
||||
try {
|
||||
task.run();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Log.warn("Task failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
tasks.clear();
|
||||
}
|
||||
|
||||
private static Thread addHook(final Thread thread) {
|
||||
Log.debug("Registering shutdown-hook: ", thread);
|
||||
try {
|
||||
Runtime.getRuntime().addShutdownHook(thread);
|
||||
}
|
||||
catch (AbstractMethodError e) {
|
||||
// JDK 1.3+ only method. Bummer.
|
||||
Log.debug("Failed to register shutdown-hook", e);
|
||||
}
|
||||
return thread;
|
||||
}
|
||||
|
||||
public static synchronized void remove(final Task task) {
|
||||
checkNotNull(task);
|
||||
|
||||
// ignore if not enabled or hook never installed
|
||||
if (!enabled || hook == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Drop the task
|
||||
tasks.remove(task);
|
||||
|
||||
// If there are no more tasks, then remove the hook thread
|
||||
if (tasks.isEmpty()) {
|
||||
removeHook(hook);
|
||||
hook = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void removeHook(final Thread thread) {
|
||||
Log.debug("Removing shutdown-hook: ", thread);
|
||||
|
||||
try {
|
||||
Runtime.getRuntime().removeShutdownHook(thread);
|
||||
}
|
||||
catch (AbstractMethodError e) {
|
||||
// JDK 1.3+ only method. Bummer.
|
||||
Log.debug("Failed to remove shutdown-hook", e);
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
// The VM is shutting down, not a big deal; ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Essentially a {@link Runnable} which allows running to throw an exception.
|
||||
*/
|
||||
public static interface Task
|
||||
{
|
||||
void run() throws Exception;
|
||||
}
|
||||
}
|
236
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/internal/TerminalLineSettings.java
Normal file
236
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/internal/TerminalLineSettings.java
Normal file
@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.internal;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Provides access to terminal line settings via <tt>stty</tt>.
|
||||
*
|
||||
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||
* @author <a href="mailto:dwkemp@gmail.com">Dale Kemp</a>
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @author <a href="mailto:jbonofre@apache.org">Jean-Baptiste Onofr\u00E9</a>
|
||||
* @since 2.0
|
||||
*/
|
||||
public final class TerminalLineSettings
|
||||
{
|
||||
public static final String JLINE_STTY = "jline.stty";
|
||||
|
||||
public static final String DEFAULT_STTY = "stty";
|
||||
|
||||
public static final String JLINE_SH = "jline.sh";
|
||||
|
||||
public static final String DEFAULT_SH = "sh";
|
||||
|
||||
private String sttyCommand;
|
||||
|
||||
private String shCommand;
|
||||
|
||||
private String config;
|
||||
private String initialConfig;
|
||||
|
||||
private long configLastFetched;
|
||||
|
||||
public TerminalLineSettings() throws IOException, InterruptedException {
|
||||
sttyCommand = Configuration.getString(JLINE_STTY, DEFAULT_STTY);
|
||||
shCommand = Configuration.getString(JLINE_SH, DEFAULT_SH);
|
||||
initialConfig = get("-g").trim();
|
||||
config = get("-a");
|
||||
configLastFetched = System.currentTimeMillis();
|
||||
|
||||
Log.debug("Config: ", config);
|
||||
|
||||
// sanity check
|
||||
if (config.length() == 0) {
|
||||
throw new IOException(MessageFormat.format("Unrecognized stty code: {0}", config));
|
||||
}
|
||||
}
|
||||
|
||||
public String getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public void restore() throws IOException, InterruptedException {
|
||||
set(initialConfig);
|
||||
}
|
||||
|
||||
public String get(final String args) throws IOException, InterruptedException {
|
||||
return stty(args);
|
||||
}
|
||||
|
||||
public void set(final String args) throws IOException, InterruptedException {
|
||||
stty(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Get the value of a stty property, including the management of a cache.
|
||||
* </p>
|
||||
*
|
||||
* @param name the stty property.
|
||||
* @return the stty property value.
|
||||
*/
|
||||
public int getProperty(String name) {
|
||||
checkNotNull(name);
|
||||
long currentTime = System.currentTimeMillis();
|
||||
try {
|
||||
// tty properties are cached so we don't have to worry too much about getting term width/height
|
||||
if (config == null || currentTime - configLastFetched > 1000) {
|
||||
config = get("-a");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e instanceof InterruptedException) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
Log.debug("Failed to query stty ", name, "\n", e);
|
||||
if (config == null) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// always update the last fetched time and try to parse the output
|
||||
if (currentTime - configLastFetched > 1000) {
|
||||
configLastFetched = currentTime;
|
||||
}
|
||||
|
||||
return getProperty(name, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Parses a stty output (provided by stty -a) and return the value of a given property.
|
||||
* </p>
|
||||
*
|
||||
* @param name property name.
|
||||
* @param stty string resulting of stty -a execution.
|
||||
* @return value of the given property.
|
||||
*/
|
||||
protected static int getProperty(String name, String stty) {
|
||||
// try the first kind of regex
|
||||
Pattern pattern = Pattern.compile(name + "\\s+=\\s+(.*?)[;\\n\\r]");
|
||||
Matcher matcher = pattern.matcher(stty);
|
||||
if (!matcher.find()) {
|
||||
// try a second kind of regex
|
||||
pattern = Pattern.compile(name + "\\s+([^;]*)[;\\n\\r]");
|
||||
matcher = pattern.matcher(stty);
|
||||
if (!matcher.find()) {
|
||||
// try a second try of regex
|
||||
pattern = Pattern.compile("(\\S*)\\s+" + name);
|
||||
matcher = pattern.matcher(stty);
|
||||
if (!matcher.find()) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return parseControlChar(matcher.group(1));
|
||||
}
|
||||
|
||||
private static int parseControlChar(String str) {
|
||||
// under
|
||||
if ("<undef>".equals(str)) {
|
||||
return -1;
|
||||
}
|
||||
// octal
|
||||
if (str.charAt(0) == '0') {
|
||||
return Integer.parseInt(str, 8);
|
||||
}
|
||||
// decimal
|
||||
if (str.charAt(0) >= '1' && str.charAt(0) <= '9') {
|
||||
return Integer.parseInt(str, 10);
|
||||
}
|
||||
// control char
|
||||
if (str.charAt(0) == '^') {
|
||||
if (str.charAt(1) == '?') {
|
||||
return 127;
|
||||
} else {
|
||||
return str.charAt(1) - 64;
|
||||
}
|
||||
} else if (str.charAt(0) == 'M' && str.charAt(1) == '-') {
|
||||
if (str.charAt(2) == '^') {
|
||||
if (str.charAt(3) == '?') {
|
||||
return 127 + 128;
|
||||
} else {
|
||||
return str.charAt(3) - 64 + 128;
|
||||
}
|
||||
} else {
|
||||
return str.charAt(2) + 128;
|
||||
}
|
||||
} else {
|
||||
return str.charAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
private String stty(final String args) throws IOException, InterruptedException {
|
||||
checkNotNull(args);
|
||||
return exec(String.format("%s %s < /dev/tty", sttyCommand, args));
|
||||
}
|
||||
|
||||
private String exec(final String cmd) throws IOException, InterruptedException {
|
||||
checkNotNull(cmd);
|
||||
return exec(shCommand, "-c", cmd);
|
||||
}
|
||||
|
||||
private String exec(final String... cmd) throws IOException, InterruptedException {
|
||||
checkNotNull(cmd);
|
||||
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
|
||||
Log.trace("Running: ", cmd);
|
||||
|
||||
Process p = Runtime.getRuntime().exec(cmd);
|
||||
|
||||
InputStream in = null;
|
||||
InputStream err = null;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
int c;
|
||||
in = p.getInputStream();
|
||||
while ((c = in.read()) != -1) {
|
||||
bout.write(c);
|
||||
}
|
||||
err = p.getErrorStream();
|
||||
while ((c = err.read()) != -1) {
|
||||
bout.write(c);
|
||||
}
|
||||
out = p.getOutputStream();
|
||||
p.waitFor();
|
||||
}
|
||||
finally {
|
||||
close(in, out, err);
|
||||
}
|
||||
|
||||
String result = bout.toString();
|
||||
|
||||
Log.trace("Result: ", result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void close(final Closeable... closeables) {
|
||||
for (Closeable c : closeables) {
|
||||
try {
|
||||
c.close();
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
33
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/internal/TestAccessible.java
Normal file
33
jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/internal/TestAccessible.java
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.internal;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.CONSTRUCTOR;
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.ElementType.PARAMETER;
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* Marker annotation for members which are exposed for testing access.
|
||||
*
|
||||
* @since 2.7
|
||||
*/
|
||||
@Retention(RUNTIME)
|
||||
@Target({TYPE, CONSTRUCTOR, METHOD, FIELD, PARAMETER})
|
||||
@Documented
|
||||
public @interface TestAccessible
|
||||
{
|
||||
// empty
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package jdk.internal.jline.internal;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* URL helpers.
|
||||
*
|
||||
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
|
||||
* @since 2.7
|
||||
*/
|
||||
public class Urls
|
||||
{
|
||||
public static URL create(final String input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new URL(input);
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
return create(new File(input));
|
||||
}
|
||||
}
|
||||
|
||||
public static URL create(final File file) {
|
||||
try {
|
||||
return file != null ? file.toURI().toURL() : null;
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
/**
|
||||
* Internal support.
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
package jdk.internal.jline.internal;
|
@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
/**
|
||||
* JLine 2.
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
package jdk.internal.jline;
|
127
jdk/src/jdk.internal.le/windows/native/lible/WindowsTerminal.cpp
Normal file
127
jdk/src/jdk.internal.le/windows/native/lible/WindowsTerminal.cpp
Normal file
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include "jni.h"
|
||||
#include "jni_util.h"
|
||||
#include "jvm.h"
|
||||
#include "jdk_internal_jline_WindowsTerminal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <Wincon.h>
|
||||
#include <Winuser.h>
|
||||
|
||||
static jclass recordClass;
|
||||
static jmethodID recordConstructor;
|
||||
|
||||
JNIEXPORT void JNICALL Java_jdk_internal_jline_WindowsTerminal_initIDs
|
||||
(JNIEnv *env, jclass) {
|
||||
jclass cls = env->FindClass("jdk/internal/jline/WindowsTerminal$KEY_EVENT_RECORD");
|
||||
CHECK_NULL(cls);
|
||||
recordClass = (jclass) env->NewGlobalRef(cls);
|
||||
CHECK_NULL(recordClass);
|
||||
recordConstructor = env->GetMethodID(cls, "<init>", "(ZCIII)V");
|
||||
CHECK_NULL(recordConstructor);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL Java_jdk_internal_jline_WindowsTerminal_getConsoleMode
|
||||
(JNIEnv *, jobject) {
|
||||
HANDLE hStdIn;
|
||||
if ((hStdIn = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE) {
|
||||
return -1;
|
||||
}
|
||||
DWORD fdwMode;
|
||||
if (! GetConsoleMode(hStdIn, &fdwMode)) {
|
||||
return -1;
|
||||
}
|
||||
return fdwMode;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_jdk_internal_jline_WindowsTerminal_setConsoleMode
|
||||
(JNIEnv *, jobject, jint mode) {
|
||||
HANDLE hStdIn;
|
||||
if ((hStdIn = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE) {
|
||||
return ;
|
||||
}
|
||||
DWORD fdwMode = mode;
|
||||
SetConsoleMode(hStdIn, fdwMode);
|
||||
}
|
||||
|
||||
JNIEXPORT jobject JNICALL Java_jdk_internal_jline_WindowsTerminal_readKeyEvent
|
||||
(JNIEnv *env, jobject) {
|
||||
HANDLE hStdIn;
|
||||
if ((hStdIn = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE) {
|
||||
return NULL;
|
||||
}
|
||||
INPUT_RECORD record;
|
||||
DWORD n;
|
||||
while (TRUE) {
|
||||
if (ReadConsoleInput(hStdIn, &record, 1, &n) == 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (record.EventType == KEY_EVENT) {
|
||||
jclass clazz = env->FindClass("jdk/internal/jline/WindowsTerminal$KEY_EVENT_RECORD");
|
||||
jmethodID constr = env->GetMethodID(clazz, "<init>", "(ZCIII)V");
|
||||
return env->NewObject(recordClass,
|
||||
recordConstructor,
|
||||
record.Event.KeyEvent.bKeyDown,
|
||||
record.Event.KeyEvent.uChar.UnicodeChar,
|
||||
record.Event.KeyEvent.dwControlKeyState,
|
||||
record.Event.KeyEvent.wVirtualKeyCode,
|
||||
record.Event.KeyEvent.wRepeatCount);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL Java_jdk_internal_jline_WindowsTerminal_getConsoleOutputCodepage
|
||||
(JNIEnv *, jobject) {
|
||||
return GetConsoleCP();
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL Java_jdk_internal_jline_WindowsTerminal_getWindowsTerminalWidth
|
||||
(JNIEnv *, jobject) {
|
||||
HANDLE hStdIn;
|
||||
if ((hStdIn = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE) {
|
||||
return -1;
|
||||
}
|
||||
CONSOLE_SCREEN_BUFFER_INFO info;
|
||||
if (! GetConsoleScreenBufferInfo(hStdIn, &info)) {
|
||||
return -1;
|
||||
}
|
||||
return info.dwSize.X;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL Java_jdk_internal_jline_WindowsTerminal_getWindowsTerminalHeight
|
||||
(JNIEnv *, jobject) {
|
||||
HANDLE hStdIn;
|
||||
if ((hStdIn = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE) {
|
||||
return -1;
|
||||
}
|
||||
CONSOLE_SCREEN_BUFFER_INFO info;
|
||||
if (! GetConsoleScreenBufferInfo(hStdIn, &info)) {
|
||||
return -1;
|
||||
}
|
||||
return info.dwSize.Y;
|
||||
}
|
@ -243,6 +243,7 @@ jdk_other = \
|
||||
javax/xml \
|
||||
-javax/xml/crypto \
|
||||
jdk/asm \
|
||||
jdk/internal/jline \
|
||||
com/sun/jndi \
|
||||
com/sun/corba \
|
||||
lib/testlibrary \
|
||||
|
59
jdk/test/jdk/internal/jline/KeyConversionTest.java
Normal file
59
jdk/test/jdk/internal/jline/KeyConversionTest.java
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8080679
|
||||
* @summary Verify the conversion from key events to escape sequences works properly.
|
||||
* @requires os.family == "windows"
|
||||
*/
|
||||
|
||||
import jdk.internal.jline.WindowsTerminal;
|
||||
import jdk.internal.jline.WindowsTerminal.KEY_EVENT_RECORD;
|
||||
|
||||
public class KeyConversionTest {
|
||||
public static void main(String... args) throws Exception {
|
||||
new KeyConversionTest().run();
|
||||
}
|
||||
|
||||
void run() throws Exception {
|
||||
checkKeyConversion(new KEY_EVENT_RECORD(true, '\0', 256, 37, 1), "\033[D"); //LEFT
|
||||
checkKeyConversion(new KEY_EVENT_RECORD(true, '\0', 264, 37, 1), "\033[1;5D"); //Ctrl-LEFT
|
||||
checkKeyConversion(new KEY_EVENT_RECORD(true, '\0', 258, 37, 1), "\033[1;3D"); //Alt-LEFT
|
||||
checkKeyConversion(new KEY_EVENT_RECORD(true, '\0', 256, 46, 1), "\033[3~"); //delete
|
||||
checkKeyConversion(new KEY_EVENT_RECORD(true, '\0', 264, 46, 1), "\033[3;5~"); //Ctrl-delete
|
||||
checkKeyConversion(new KEY_EVENT_RECORD(true, '\0', 258, 46, 1), "\033[3;3~"); //Alt-delete
|
||||
checkKeyConversion(new KEY_EVENT_RECORD(true, '\0', 272, 46, 1), "\033[3;2~"); //Shift-delete
|
||||
checkKeyConversion(new KEY_EVENT_RECORD(true, '\0', 280, 46, 1), "\033[3;6~"); //Ctrl-Shift-delete
|
||||
checkKeyConversion(new KEY_EVENT_RECORD(true, '\0', 274, 46, 1), "\033[3;4~"); //Alt-Shift-delete
|
||||
checkKeyConversion(new KEY_EVENT_RECORD(true, '\0', 282, 46, 1), "\033[3;8~"); //Ctrl-Alt-Shift-delete
|
||||
}
|
||||
|
||||
void checkKeyConversion(KEY_EVENT_RECORD event, String expected) {
|
||||
String actual = WindowsTerminal.convertKeys(event);
|
||||
|
||||
if (!expected.equals(actual)) {
|
||||
throw new AssertionError("Expected: " + expected + "; actual: " + actual);
|
||||
}
|
||||
}
|
||||
}
|
56
jdk/test/jdk/internal/jline/console/StripAnsiTest.java
Normal file
56
jdk/test/jdk/internal/jline/console/StripAnsiTest.java
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8080679
|
||||
* @summary Verify ConsoleReader.stripAnsi strips escape sequences from its input correctly.
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import jdk.internal.jline.console.ConsoleReader;
|
||||
|
||||
public class StripAnsiTest {
|
||||
public static void main(String... args) throws Exception {
|
||||
new StripAnsiTest().run();
|
||||
}
|
||||
|
||||
void run() throws Exception {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(new byte[0]);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ConsoleReader reader = new ConsoleReader(in, out);
|
||||
|
||||
String withAnsi = "0\033[s1\033[2J2\033[37;4m3";
|
||||
String expected = "0123";
|
||||
|
||||
Method stripAnsi = ConsoleReader.class.getDeclaredMethod("stripAnsi", String.class);
|
||||
stripAnsi.setAccessible(true);
|
||||
String actual = (String) stripAnsi.invoke(reader, withAnsi);
|
||||
|
||||
if (!expected.equals(actual)) {
|
||||
throw new IllegalStateException("Did not correctly strip escape sequences: " + actual);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user