pax_global_header00006660000000000000000000000064121462204440014511gustar00rootroot0000000000000052 comment=a3bf01ecc02b63db0b21f260bba9f82fdfd5b4fb .gitignore000066400000000000000000000000311214622044400130370ustar00rootroot00000000000000*.iml *.ipr *.iws target CHANGELOG.md000066400000000000000000000031441214622044400126700ustar00rootroot00000000000000 ## [Jline 2.9][2_9] [2_9]: https://oss.sonatype.org/content/groups/public/jline/jline/2.9 * Ability to control terminal encoding ## [Jline 2.8][2_8] [2_8]: https://oss.sonatype.org/content/groups/public/jline/jline/2.8 * Backward history searching * Update to jansi 2.9 * Handle EOF / Ctrl-D on unsupported terminals * Distinguish carriage return from newline * Correcting Manifest to make jline work as a bundle in OSGi * Handle TERM=dumb as an UnsupportedTerminal * Add back PasswordReader ## [JLine 2.7][2_7] [2_7]: https://oss.sonatype.org/content/groups/public/jline/jline/2.7 * Updated license headers to be consistent BSD version * Added support for vi keymap. Most major vi features should work. * The following features are NOT yet available. * Undo/redo support is not yet available * Character search (CTRL-]) * Yank via (CTRL-Y) * Quoted insert (CTRL-Y). * The "jline.esc.timeout" configuration option (in your $HOME/.jline.rc) controls the number of millisesconds that jline will wait after seeing an ESC key to see if another character arrives. * The JVM shutdown hook that restores the terminal settings when the JVM exits (jline.shutdownhook) is now turned on by default. LICENSE.txt000066400000000000000000000027771214622044400127150ustar00rootroot00000000000000Copyright (c) 2002-2012, the original author or authors. All rights reserved. http://www.opensource.org/licenses/bsd-license.php Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of JLine nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. README.md000066400000000000000000000031321214622044400123330ustar00rootroot00000000000000 Description ----------- JLine is a Java library for handling console input. It is similar in functionality to [BSD editline](http://www.thrysoee.dk/editline/) and [GNU readline](http://www.gnu.org/s/readline/). People familiar with the readline/editline capabilities for modern shells (such as bash and tcsh) will find most of the command editing features of JLine to be familiar. JLine 2.x is an evolution of [JLine 1.x](https://github.com/jline/jline) which was previously maintained at [SourceForge](http://jline.sourceforge.net/). License ------- JLine is distributed under the [BSD License](http://www.opensource.org/licenses/bsd-license.php), meaning that you are completely free to redistribute, modify, or sell it with almost no restrictions. Documentation ------------- * [wiki](https://github.com/jline/jline2/wiki) Forums ------ * [jline-users](https://groups.google.com/group/jline-users) * [jline-dev](https://groups.google.com/group/jline-dev) Maven Usage ----------- Use the following definition to use JLine in your maven project: jline jline 2.10 Building -------- ### Requirements * Maven 2+ * Java 5+ Check out and build: git clone git://github.com/jline/jline2.git cd jline2 mvn install header.txt000066400000000000000000000003661214622044400130530ustar00rootroot00000000000000Copyright (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.phppom.xml000066400000000000000000000554731214622044400124100ustar00rootroot00000000000000 4.0.0 org.sonatype.oss oss-parent 7 jline jline JLine 2.11 The BSD License http://www.opensource.org/licenses/bsd-license.php repo scm:git:git://github.com/jline/jline2.git scm:git:ssh://git@github.com/jline/jline2.git http://github.com/jline/jline2 github https://github.com/jline/jline2/issues jline-users https://groups.google.com/group/jline-users jline-dev https://groups.google.com/group/jline-dev github-project-site gitsite:git@github.com/jline/jline2.git jdillon Jason Dillon jason@planet57.com Build Master Developer gnodet Guillaume Nodet gnodet@gmail.com Developer mprudhom Marc Prud'hommeaux mprudhom@gmail.com Original Author Jline 1.x Developer Emeritus Scott Gray scottgray1@gmail.com Hiram Chirino hiram@hiramchirino.com UTF-8 UTF-8 yyyyMMddHHmm ${maven.build.timestamp} true 1.11 2.2.1 org.fusesource.jansi jansi ${jansi.version} junit junit 4.10 test org.apache.maven.scm maven-scm-provider-gitexe 1.8.1 org.apache.maven.scm maven-scm-manager-plexus 1.8.1 com.github.stephenc.wagon wagon-gitsite 0.4.1 install ${project.basedir}/src/main/resources false **/* ${project.basedir}/src/main/filtered-resources true **/* ${project.basedir}/src/test/resources false **/* ${project.basedir}/src/test/filtered-resources true **/* org.apache.maven.plugins maven-site-plugin 3.2 org.apache.maven.doxia doxia-module-markdown 1.3 UTF-8 UTF-8 attach-descriptor attach-descriptor org.apache.maven.plugins maven-javadoc-plugin 2.9 1.5 org.apache.maven.plugins maven-surefire-plugin 2.12 ${maven.test.redirectTestOutputToFile} once -ea false ${project.build.directory} **/Abstract*.java **/Test*.java **/*Test.java true org.apache.maven.plugins maven-compiler-plugin 2.5 1.5 1.5 org.apache.maven.plugins maven-source-plugin 2.1.2 attach-sources jar-no-fork org.codehaus.mojo animal-sniffer-maven-plugin 1.9 check org.codehaus.mojo.signature java15 1.0 org.apache.felix maven-bundle-plugin 2.1.0 process-classes manifest jline;jline.console;jline.console.completer;jline.console.history;version:=${project.version};-noimport:=true, =org.fusesource.jansi;version=${jansi.version} javax.swing;resolution:=optional * org.apache.maven.plugins maven-jar-plugin 2.4 ${project.build.outputDirectory}/META-INF/MANIFEST.MF test-jar org.apache.maven.plugins maven-scm-plugin 1.8.1 org.apache.maven.plugins maven-shade-plugin 1.6 shade junit:junit org.fusesource.jansi:jansi META-INF/maven/** *.txt junit/** org/junit/** org/hamcrest/** org/fusesource/hawtjni/runtime/Jni* org/fusesource/hawtjni/runtime/*Flag* org/fusesource/hawtjni/runtime/T32* org/fusesource/hawtjni/runtime/NativeStats* org.apache.maven.plugins maven-site-plugin attach-descriptor attach-descriptor com.mycila.maven-license-plugin maven-license-plugin 1.9.0 true true
${project.basedir}/header.txt
false **/pom.xml **/*.xml **/*.xsd **/*.xjb **/*.properties **/*.ini **/*.java **/*.groovy **/*.scala **/*.aj **/*.js **/*.css **/*.help **/*.proto **/*.sm **/*.bat **/*.vm **/*.md **/target/** **/.*/** SLASHSTAR_STYLE SLASHSTAR_STYLE XML_STYLE DOUBLESLASH_STYLE DOUBLESLASH_STYLE SCRIPT_STYLE SCRIPT_STYLE XML_STYLE
org.apache.maven.plugins maven-project-info-reports-plugin 2.6 issue-tracking mailing-list project-team modules license dependencies dependency-convergence dependency-info dependency-management plugins plugin-management distribution-management org.apache.maven.plugins maven-javadoc-plugin 2.9 com.google.doclava doclava 1.0.5 com.google.doclava.Doclava 1.5 ${sun.boot.class.path} -quiet -federate JDK http://download.oracle.com/javase/6/docs/api/index.html? -federationxml JDK http://doclava.googlecode.com/svn/static/api/openjdk-6.xml -hdf project.name "${project.name}" -d ${project.build.directory}/site/apidocs false default javadoc org.apache.maven.plugins maven-pmd-plugin 2.7.1 1.5 org.apache.maven.plugins maven-jxr-plugin 2.3 true default jxr org.codehaus.mojo findbugs-maven-plugin 2.5.2 org.codehaus.mojo cobertura-maven-plugin 2.5.2 site-stage clean install site:site site:stage retro retro true org.codehaus.mojo retrotranslator-maven-plugin 1.0-alpha-4 translate-project jdk14 true
src/000077500000000000000000000000001214622044400116445ustar00rootroot00000000000000src/main/000077500000000000000000000000001214622044400125705ustar00rootroot00000000000000src/main/java/000077500000000000000000000000001214622044400135115ustar00rootroot00000000000000src/main/java/jline/000077500000000000000000000000001214622044400146125ustar00rootroot00000000000000src/main/java/jline/AnsiWindowsTerminal.java000066400000000000000000000044011214622044400214150ustar00rootroot00000000000000/* * 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 jline; import jline.internal.Configuration; import org.fusesource.jansi.AnsiConsole; import org.fusesource.jansi.AnsiOutputStream; import org.fusesource.jansi.WindowsAnsiOutputStream; import java.io.ByteArrayOutputStream; import java.io.OutputStream; /** * ANSI-supported {@link WindowsTerminal}. * * @since 2.0 */ public class AnsiWindowsTerminal extends WindowsTerminal { private final boolean ansiSupported = detectAnsiSupport(); @Override public OutputStream wrapOutIfNeeded(OutputStream out) { return wrapOutputStream(out); } /** * Returns an ansi output stream handler. We return whatever was * passed if we determine we cannot handle ansi based on Kernel32 calls. * * @return an @{link AltWindowAnsiOutputStream} instance or the passed * stream. */ private static OutputStream wrapOutputStream(final OutputStream stream) { if (Configuration.isWindows()) { // On windows we know the console does not interpret ANSI codes.. try { return new WindowsAnsiOutputStream(stream); } catch (Throwable ignore) { // this happens when JNA is not in the path.. or // this happens when the stdout is being redirected to a file. } // Use the ANSIOutputStream to strip out the ANSI escape sequences. return new AnsiOutputStream(stream); } return stream; } private static boolean detectAnsiSupport() { OutputStream out = AnsiConsole.wrapOutputStream(new ByteArrayOutputStream()); try { out.close(); } catch (Exception e) { // ignore; } return out instanceof WindowsAnsiOutputStream; } public AnsiWindowsTerminal() throws Exception { super(); } @Override public boolean isAnsiSupported() { return ansiSupported; } @Override public boolean hasWeirdWrap() { return false; } } src/main/java/jline/NoInterruptUnixTerminal.java000066400000000000000000000014361214622044400223120ustar00rootroot00000000000000/* * 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 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(); } } src/main/java/jline/Terminal.java000066400000000000000000000031011214622044400172230ustar00rootroot00000000000000/* * 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 jline; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * Representation of the input terminal for a platform. * * @author Marc Prud'hommeaux * @author Jason Dillon * @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); } src/main/java/jline/TerminalFactory.java000066400000000000000000000114521214622044400205630ustar00rootroot00000000000000/* * 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 jline; import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; import jline.internal.Configuration; import jline.internal.Log; import jline.internal.Preconditions; import static jline.internal.Preconditions.checkNotNull; /** * Creates terminal instances. * * @author Jason Dillon * @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 final InheritableThreadLocal holder = new InheritableThreadLocal(); 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() { holder.remove(); } public static synchronized void resetIf(final Terminal t) { if (holder.get() == t) { 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> FLAVORS = new HashMap>(); static { registerFlavor(Flavor.WINDOWS, AnsiWindowsTerminal.class); registerFlavor(Flavor.UNIX, UnixTerminal.class); } public static synchronized Terminal get() { Terminal t = holder.get(); if (t == null) { t = create(); holder.set(t); } return t; } public static Terminal getFlavor(final Flavor flavor) throws Exception { Class type = FLAVORS.get(flavor); if (type != null) { return type.newInstance(); } throw new InternalError(); } public static void registerFlavor(final Flavor flavor, final Class type) { FLAVORS.put(flavor, type); } } src/main/java/jline/TerminalSupport.java000066400000000000000000000051541214622044400206320ustar00rootroot00000000000000/* * 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 jline; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import jline.internal.Log; import jline.internal.ShutdownHooks; import jline.internal.ShutdownHooks.Task; /** * Provides support for {@link Terminal} instances. * * @author Jason Dillon * @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 { // 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); ShutdownHooks.remove(shutdownTask); } 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; } }src/main/java/jline/UnixTerminal.java000066400000000000000000000072131214622044400200770ustar00rootroot00000000000000/* * 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 jline; import jline.internal.Log; import jline.internal.TerminalLineSettings; /** * Terminal that is used for unix platforms. Terminal initialization * is handled by issuing the stty command against the * /dev/tty file to disable character echoing and enable * character input. All known unix systems (including * Linux and Macintosh OS X) support the stty), so this * implementation should work for an reasonable POSIX system. * * @author Marc Prud'hommeaux * @author Dale Kemp * @author Jason Dillon * @author Jean-Baptiste Onofré * @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"); 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 stty columns param. */ @Override public int getWidth() { int w = settings.getProperty("columns"); return w < 1 ? DEFAULT_WIDTH : w; } /** * Returns the value of 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) { 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); } } } src/main/java/jline/UnsupportedTerminal.java000066400000000000000000000012261214622044400215020ustar00rootroot00000000000000/* * 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 jline; /** * An unsupported terminal. * * @author Marc Prud'hommeaux * @author Jason Dillon * @since 2.0 */ public class UnsupportedTerminal extends TerminalSupport { public UnsupportedTerminal() { super(false); setAnsiSupported(false); setEchoEnabled(true); } } src/main/java/jline/WindowsTerminal.java000066400000000000000000000213261214622044400206070ustar00rootroot00000000000000/* * 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 jline; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import jline.internal.Configuration; import jline.internal.Log; import org.fusesource.jansi.internal.WindowsSupport; import static jline.WindowsTerminal.ConsoleMode.ENABLE_ECHO_INPUT; import static jline.WindowsTerminal.ConsoleMode.ENABLE_LINE_INPUT; import static jline.WindowsTerminal.ConsoleMode.ENABLE_PROCESSED_INPUT; import static jline.WindowsTerminal.ConsoleMode.ENABLE_WINDOW_INPUT; import static jline.internal.Preconditions.checkNotNull; /** * Terminal implementation for Microsoft Windows. Terminal initialization in * {@link #init} is accomplished by extracting the * jline_version.dll, saving it to the system temporary * directoy (determined by the setting of the java.io.tmpdir System * property), loading the library, and then calling the Win32 APIs SetConsoleMode and * GetConsoleMode to * disable character echoing. *

*

* 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 jline.WindowsTerminal.directConsole system property * to false. *

* * @author Marc Prud'hommeaux * @author Jason Dillon * @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)); // // 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() { @Override public int read() throws IOException { return readByte(); } }; } 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; } // // Native Bits // private int getConsoleMode() { return WindowsSupport.getConsoleMode(); } private void setConsoleMode(int mode) { WindowsSupport.setConsoleMode(mode); } private int readByte() { return WindowsSupport.readByte(); } private int getWindowsTerminalWidth() { return WindowsSupport.getWindowsTerminalWidth(); } private int getWindowsTerminalHeight() { return WindowsSupport.getWindowsTerminalHeight(); } /** * Console mode *

* Constants copied wincon.h. */ 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; } } } src/main/java/jline/console/000077500000000000000000000000001214622044400162545ustar00rootroot00000000000000src/main/java/jline/console/ConsoleKeys.java000066400000000000000000000373311214622044400213640ustar00rootroot00000000000000/* * 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 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 jline.internal.Log; /** * @author Ståle W. Pedersen */ public class ConsoleKeys { private KeyMap keys; private Map keyMaps; private Map variables = new HashMap(); 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 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 ifsStack = new ArrayList(); 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); } } src/main/java/jline/console/ConsoleReader.java000066400000000000000000003705421214622044400216570ustar00rootroot00000000000000/* * 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 jline.console; import java.awt.*; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.event.ActionListener; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.Writer; import java.net.URL; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.ResourceBundle; import java.util.Stack; import jline.Terminal; import jline.TerminalFactory; import jline.UnixTerminal; import jline.console.completer.CandidateListCompletionHandler; import jline.console.completer.Completer; import jline.console.completer.CompletionHandler; import jline.console.history.History; import jline.console.history.MemoryHistory; import jline.internal.Configuration; import jline.internal.InputStreamReader; import jline.internal.Log; import jline.internal.NonBlockingInputStream; import jline.internal.Nullable; import jline.internal.Urls; import org.fusesource.jansi.AnsiOutputStream; import static jline.internal.Preconditions.checkNotNull; /** * A reader for console applications. It supports custom tab-completion, * saveable command history, and command line editing. On some platforms, * platform-specific commands will need to be issued before the reader will * function properly. See {@link jline.Terminal#init} for convenience * methods for issuing platform-specific setup commands. * * @author Marc Prud'hommeaux * @author Jason Dillon * @author Guillaume Nodet */ public class ConsoleReader { public static final String JLINE_NOBELL = "jline.nobell"; public static final String JLINE_ESC_TIMEOUT = "jline.esc.timeout"; public static final String JLINE_INPUTRC = "jline.inputrc"; public static final String INPUT_RC = ".inputrc"; public static final String DEFAULT_INPUT_RC = "/etc/inputrc"; public static final char BACKSPACE = '\b'; public static final char RESET_LINE = '\r'; public static final char KEYBOARD_BELL = '\07'; public static final char NULL_MASK = 0; public static final int TAB_WIDTH = 4; private static final ResourceBundle resources = ResourceBundle.getBundle(CandidateListCompletionHandler.class.getName()); private final Terminal terminal; private final Writer out; private final CursorBuffer buf = new CursorBuffer(); private String prompt; private int promptLen; private boolean expandEvents = true; private boolean bellEnabled = !Configuration.getBoolean(JLINE_NOBELL, true); private boolean handleUserInterrupt = false; private Character mask; private Character echoCharacter; private StringBuffer searchTerm = null; private String previousSearchTerm = ""; private int searchIndex = -1; private int parenBlinkTimeout = 500; /* * The reader and the nonBlockingInput go hand-in-hand. The reader wraps * the nonBlockingInput, but we have to retain a handle to it so that * we can shut down its blocking read thread when we go away. */ private NonBlockingInputStream in; private long escapeTimeout; private Reader reader; /* * TODO: Please read the comments about this in setInput(), but this needs * to be done away with. */ private boolean isUnitTestInput; /** * Last character searched for with a vi character search */ private char charSearchChar = 0; // Character to search for private char charSearchLastInvokeChar = 0; // Most recent invocation key private char charSearchFirstInvokeChar = 0;// First character that invoked /** * The vi yank buffer */ private String yankBuffer = ""; private String encoding; private boolean recording; private String macro = ""; private String appName; private URL inputrcUrl; private ConsoleKeys consoleKeys; private String commentBegin = null; private boolean skipLF = false; /** * Set to true if the reader should attempt to detect copy-n-paste. The * effect of this that an attempt is made to detect if tab is quickly * followed by another character, then it is assumed that the tab was * a literal tab as part of a copy-and-paste operation and is inserted as * such. */ private boolean copyPasteDetection = false; /* * Current internal state of the line reader */ private State state = State.NORMAL; /** * Possible states in which the current readline operation may be in. */ private static enum State { /** * The user is just typing away */ NORMAL, /** * In the middle of a emacs seach */ SEARCH, FORWARD_SEARCH, /** * VI "yank-to" operation ("y" during move mode) */ VI_YANK_TO, /** * VI "delete-to" operation ("d" during move mode) */ VI_DELETE_TO, /** * VI "change-to" operation ("c" during move mode) */ VI_CHANGE_TO } public ConsoleReader() throws IOException { this(null, new FileInputStream(FileDescriptor.in), System.out, null); } public ConsoleReader(final InputStream in, final OutputStream out) throws IOException { this(null, in, out, null); } public ConsoleReader(final InputStream in, final OutputStream out, final Terminal term) throws IOException { this(null, in, out, term); } public ConsoleReader(final @Nullable String appName, final InputStream in, final OutputStream out, final @Nullable Terminal term) throws IOException { this(appName, in, out, term, null); } public ConsoleReader(final @Nullable String appName, final InputStream in, final OutputStream out, final @Nullable Terminal term, final @Nullable String encoding) throws IOException { this.appName = appName != null ? appName : "JLine"; this.encoding = encoding != null ? encoding : Configuration.getEncoding(); this.terminal = term != null ? term : TerminalFactory.get(); this.out = new OutputStreamWriter(terminal.wrapOutIfNeeded(out), this.encoding); setInput( in ); this.inputrcUrl = getInputRc(); consoleKeys = new ConsoleKeys(appName, inputrcUrl); } private URL getInputRc() throws IOException { String path = Configuration.getString(JLINE_INPUTRC); if (path == null) { File f = new File(Configuration.getUserHome(), INPUT_RC); if (!f.exists()) { f = new File(DEFAULT_INPUT_RC); } return f.toURI().toURL(); } else { return Urls.create(path); } } public KeyMap getKeys() { return consoleKeys.getKeys(); } void setInput(final InputStream in) throws IOException { this.escapeTimeout = Configuration.getLong(JLINE_ESC_TIMEOUT, 100); /* * This is gross and here is how to fix it. In getCurrentPosition() * and getCurrentAnsiRow(), the logic is disabled when running unit * tests and the fact that it is a unit test is determined by knowing * if the original input stream was a ByteArrayInputStream. So, this * is our test to do this. What SHOULD happen is that the unit * tests should pass in a terminal that is appropriately configured * such that whatever behavior they expect to happen (or not happen) * happens (or doesn't). * * So, TODO, get rid of this and fix the unit tests. */ this.isUnitTestInput = in instanceof ByteArrayInputStream; boolean nonBlockingEnabled = escapeTimeout > 0L && terminal.isSupported() && in != null; /* * If we had a non-blocking thread already going, then shut it down * and start a new one. */ if (this.in != null) { this.in.shutdown(); } final InputStream wrapped = terminal.wrapInIfNeeded( in ); this.in = new NonBlockingInputStream(wrapped, nonBlockingEnabled); this.reader = new InputStreamReader( this.in, encoding ); } /** * Shuts the console reader down. This method should be called when you * have completed using the reader as it shuts down and cleans up resources * that would otherwise be "leaked". */ public void shutdown() { if (in != null) { in.shutdown(); } } /** * Shuts down the ConsoleReader if the JVM attempts to clean it up. */ @Override protected void finalize() throws Throwable { try { shutdown(); } finally { super.finalize(); } } public InputStream getInput() { return in; } public Writer getOutput() { return out; } public Terminal getTerminal() { return terminal; } public CursorBuffer getCursorBuffer() { return buf; } public void setExpandEvents(final boolean expand) { this.expandEvents = expand; } public boolean getExpandEvents() { return expandEvents; } /** * Enables or disables copy and paste detection. The effect of enabling this * this setting is that when a tab is received immediately followed by another * character, the tab will not be treated as a completion, but as a tab literal. * @param onoff true if detection is enabled */ public void setCopyPasteDetection(final boolean onoff) { copyPasteDetection = onoff; } /** * @return true if copy and paste detection is enabled. */ public boolean isCopyPasteDetectionEnabled() { return copyPasteDetection; } /** * Set whether the console bell is enabled. * * @param enabled true if enabled; false otherwise * @since 2.7 */ public void setBellEnabled(boolean enabled) { this.bellEnabled = enabled; } /** * Get whether the console bell is enabled * * @return true if enabled; false otherwise * @since 2.7 */ public boolean getBellEnabled() { return bellEnabled; } /** * Set whether user interrupts (ctrl-C) are handled by having JLine * throw {@link UserInterruptException} from {@link #readLine}. * Otherwise, the JVM will handle {@code SIGINT} as normal, which * usually causes it to exit. The default is {@code false}. * * @since 2.10 */ public void setHandleUserInterrupt(boolean enabled) { this.handleUserInterrupt = enabled; } /** * Get whether user interrupt handling is enabled * * @return true if enabled; false otherwise * @since 2.10 */ public boolean getHandleUserInterrupt() { return handleUserInterrupt; } /** * Sets the string that will be used to start a comment when the * insert-comment key is struck. * @param commentBegin The begin comment string. * @since 2.7 */ public void setCommentBegin(String commentBegin) { this.commentBegin = commentBegin; } /** * @return the string that will be used to start a comment when the * insert-comment key is struck. * @since 2.7 */ public String getCommentBegin() { String str = commentBegin; if (str == null) { str = consoleKeys.getVariable("comment-begin"); if (str == null) { str = "#"; } } return str; } public void setPrompt(final String prompt) { this.prompt = prompt; this.promptLen = ((prompt == null) ? 0 : stripAnsi(lastLine(prompt)).length()); } public String getPrompt() { return prompt; } /** * Set the echo character. For example, to have "*" entered when a password is typed: *

*

     * myConsoleReader.setEchoCharacter(new Character('*'));
     * 
*

* Setting the character to *

*

     * null
     * 
*

* will restore normal character echoing. Setting the character to *

*

     * new Character(0)
     * 
*

* will cause nothing to be echoed. * * @param c the character to echo to the console in place of the typed character. */ public void setEchoCharacter(final Character c) { this.echoCharacter = c; } /** * Returns the echo character. */ public Character getEchoCharacter() { return echoCharacter; } /** * Erase the current line. * * @return false if we failed (e.g., the buffer was empty) */ protected final boolean resetLine() throws IOException { if (buf.cursor == 0) { return false; } backspaceAll(); return true; } int getCursorPosition() { // FIXME: does not handle anything but a line with a prompt absolute position return promptLen + buf.cursor; } /** * Returns the text after the last '\n'. * prompt is returned if no '\n' characters are present. * null is returned if prompt is null. */ private String lastLine(String str) { if (str == null) return ""; int last = str.lastIndexOf("\n"); if (last >= 0) { return str.substring(last + 1, str.length()); } return str; } private String stripAnsi(String str) { if (str == null) return ""; try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); AnsiOutputStream aos = new AnsiOutputStream(baos); aos.write(str.getBytes()); aos.flush(); return baos.toString(); } catch (IOException e) { return str; } } /** * Move the cursor position to the specified absolute index. */ public final boolean setCursorPosition(final int position) throws IOException { if (position == buf.cursor) { return true; } return moveCursor(position - buf.cursor) != 0; } /** * Set the current buffer's content to the specified {@link String}. The * visual console will be modified to show the current buffer. * * @param buffer the new contents of the buffer. */ private void setBuffer(final String buffer) throws IOException { // don't bother modifying it if it is unchanged if (buffer.equals(buf.buffer.toString())) { return; } // obtain the difference between the current buffer and the new one int sameIndex = 0; for (int i = 0, l1 = buffer.length(), l2 = buf.buffer.length(); (i < l1) && (i < l2); i++) { if (buffer.charAt(i) == buf.buffer.charAt(i)) { sameIndex++; } else { break; } } int diff = buf.cursor - sameIndex; if (diff < 0) { // we can't backspace here so try from the end of the buffer moveToEnd(); diff = buf.buffer.length() - sameIndex; } backspace(diff); // go back for the differences killLine(); // clear to the end of the line buf.buffer.setLength(sameIndex); // the new length putString(buffer.substring(sameIndex)); // append the differences } private void setBuffer(final CharSequence buffer) throws IOException { setBuffer(String.valueOf(buffer)); } /** * Output put the prompt + the current buffer */ public final void drawLine() throws IOException { String prompt = getPrompt(); if (prompt != null) { print(prompt); } print(buf.buffer.toString()); if (buf.length() != buf.cursor) { // not at end of line back(buf.length() - buf.cursor - 1); } // force drawBuffer to check for weird wrap (after clear screen) drawBuffer(); } /** * Clear the line and redraw it. */ public final void redrawLine() throws IOException { print(RESET_LINE); // flush(); drawLine(); } /** * Clear the buffer and add its contents to the history. * * @return the former contents of the buffer. */ final String finishBuffer() throws IOException { // FIXME: Package protected because used by tests String str = buf.buffer.toString(); String historyLine = str; if (expandEvents) { str = expandEvents(str); // all post-expansion occurrences of '!' must have been escaped, so re-add escape to each historyLine = str.replace("!", "\\!"); // only leading '^' results in expansion, so only re-add escape for that case historyLine = historyLine.replaceAll("^\\^", "\\\\^"); } // we only add it to the history if the buffer is not empty // and if mask is null, since having a mask typically means // the string was a password. We clear the mask after this call if (str.length() > 0) { if (mask == null && isHistoryEnabled()) { history.add(historyLine); } else { mask = null; } } history.moveToEnd(); buf.buffer.setLength(0); buf.cursor = 0; return str; } /** * Expand event designator such as !!, !#, !3, etc... * See http://www.gnu.org/software/bash/manual/html_node/Event-Designators.html */ protected String expandEvents(String str) throws IOException { StringBuilder sb = new StringBuilder(); for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); switch (c) { case '\\': // any '\!' should be considered an expansion escape, so skip expansion and strip the escape character // a leading '\^' should be considered an expansion escape, so skip expansion and strip the escape character // otherwise, add the escape if (i + 1 < str.length()) { char nextChar = str.charAt(i+1); if (nextChar == '!' || (nextChar == '^' && i == 0)) { c = nextChar; i++; } } sb.append(c); break; case '!': if (i + 1 < str.length()) { c = str.charAt(++i); boolean neg = false; String rep = null; int i1, idx; switch (c) { case '!': if (history.size() == 0) { throw new IllegalArgumentException("!!: event not found"); } rep = history.get(history.index() - 1).toString(); break; case '#': sb.append(sb.toString()); break; case '?': i1 = str.indexOf('?', i + 1); if (i1 < 0) { i1 = str.length(); } String sc = str.substring(i + 1, i1); i = i1; idx = searchBackwards(sc); if (idx < 0) { throw new IllegalArgumentException("!?" + sc + ": event not found"); } else { rep = history.get(idx).toString(); } break; case ' ': case '\t': sb.append('!'); sb.append(c); break; case '-': neg = true; i++; // fall through case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': i1 = i; for (; i < str.length(); i++) { c = str.charAt(i); if (c < '0' || c > '9') { break; } } idx = 0; try { idx = Integer.parseInt(str.substring(i1, i)); } catch (NumberFormatException e) { throw new IllegalArgumentException((neg ? "!-" : "!") + str.substring(i1, i) + ": event not found"); } if (neg) { if (idx > 0 && idx <= history.size()) { rep = (history.get(history.index() - idx)).toString(); } else { throw new IllegalArgumentException((neg ? "!-" : "!") + str.substring(i1, i) + ": event not found"); } } else { if (idx > history.index() - history.size() && idx <= history.index()) { rep = (history.get(idx - 1)).toString(); } else { throw new IllegalArgumentException((neg ? "!-" : "!") + str.substring(i1, i) + ": event not found"); } } break; default: String ss = str.substring(i); i = str.length(); idx = searchBackwards(ss, history.index(), true); if (idx < 0) { throw new IllegalArgumentException("!" + ss + ": event not found"); } else { rep = history.get(idx).toString(); } break; } if (rep != null) { sb.append(rep); } } else { sb.append(c); } break; case '^': if (i == 0) { int i1 = str.indexOf('^', i + 1); int i2 = str.indexOf('^', i1 + 1); if (i2 < 0) { i2 = str.length(); } if (i1 > 0 && i2 > 0) { String s1 = str.substring(i + 1, i1); String s2 = str.substring(i1 + 1, i2); String s = history.get(history.index() - 1).toString().replace(s1, s2); sb.append(s); i = i2 + 1; break; } } sb.append(c); break; default: sb.append(c); break; } } String result = sb.toString(); if (!str.equals(result)) { print(result); println(); flush(); } return result; } /** * Write out the specified string to the buffer and the output stream. */ public final void putString(final CharSequence str) throws IOException { buf.write(str); if (mask == null) { // no masking print(str); } else if (mask == NULL_MASK) { // don't print anything } else { print(mask, str.length()); } drawBuffer(); } /** * Redraw the rest of the buffer from the cursor onwards. This is necessary * for inserting text into the buffer. * * @param clear the number of characters to clear after the end of the buffer */ private void drawBuffer(final int clear) throws IOException { // debug ("drawBuffer: " + clear); if (buf.cursor == buf.length() && clear == 0) { } else { char[] chars = buf.buffer.substring(buf.cursor).toCharArray(); if (mask != null) { Arrays.fill(chars, mask); } if (terminal.hasWeirdWrap()) { // need to determine if wrapping will occur: int width = terminal.getWidth(); int pos = getCursorPosition(); for (int i = 0; i < chars.length; i++) { print(chars[i]); if ((pos + i + 1) % width == 0) { print(32); // move cursor to next line by printing dummy space print(13); // CR / not newline. } } } else { print(chars); } clearAhead(clear, chars.length); if (terminal.isAnsiSupported()) { if (chars.length > 0) { back(chars.length); } } else { back(chars.length); } } if (terminal.hasWeirdWrap()) { int width = terminal.getWidth(); // best guess on whether the cursor is in that weird location... // Need to do this without calling ansi cursor location methods // otherwise it breaks paste of wrapped lines in xterm. if (getCursorPosition() > 0 && (getCursorPosition() % width == 0) && buf.cursor == buf.length() && clear == 0) { // the following workaround is reverse-engineered from looking // at what bash sent to the terminal in the same situation print(32); // move cursor to next line by printing dummy space print(13); // CR / not newline. } } } /** * Redraw the rest of the buffer from the cursor onwards. This is necessary * for inserting text into the buffer. */ private void drawBuffer() throws IOException { drawBuffer(0); } /** * Clear ahead the specified number of characters without moving the cursor. * * @param num the number of characters to clear * @param delta the difference between the internal cursor and the screen * cursor - if > 0, assume some stuff was printed and weird wrap has to be * checked */ private void clearAhead(final int num, int delta) throws IOException { if (num == 0) { return; } if (terminal.isAnsiSupported()) { int width = terminal.getWidth(); int screenCursorCol = getCursorPosition() + delta; // clear current line printAnsiSequence("K"); // if cursor+num wraps, then we need to clear the line(s) below too int curCol = screenCursorCol % width; int endCol = (screenCursorCol + num - 1) % width; int lines = num / width; if (endCol < curCol) lines++; for (int i = 0; i < lines; i++) { printAnsiSequence("B"); printAnsiSequence("2K"); } for (int i = 0; i < lines; i++) { printAnsiSequence("A"); } return; } // print blank extra characters print(' ', num); // we need to flush here so a "clever" console doesn't just ignore the redundancy // of a space followed by a backspace. // flush(); // reset the visual cursor back(num); // flush(); } /** * Move the visual cursor backwards without modifying the buffer cursor. */ protected void back(final int num) throws IOException { if (num == 0) return; if (terminal.isAnsiSupported()) { int width = getTerminal().getWidth(); int cursor = getCursorPosition(); int realCursor = cursor + num; int realCol = realCursor % width; int newCol = cursor % width; int moveup = num / width; int delta = realCol - newCol; if (delta < 0) moveup++; if (moveup > 0) { printAnsiSequence(moveup + "A"); } printAnsiSequence((1 + newCol) + "G"); return; } print(BACKSPACE, num); // flush(); } /** * Flush the console output stream. This is important for printout out single characters (like a backspace or * keyboard) that we want the console to handle immediately. */ public void flush() throws IOException { out.flush(); } private int backspaceAll() throws IOException { return backspace(Integer.MAX_VALUE); } /** * Issue num backspaces. * * @return the number of characters backed up */ private int backspace(final int num) throws IOException { if (buf.cursor == 0) { return 0; } int count = 0; int termwidth = getTerminal().getWidth(); int lines = getCursorPosition() / termwidth; count = moveCursor(-1 * num) * -1; buf.buffer.delete(buf.cursor, buf.cursor + count); if (getCursorPosition() / termwidth != lines) { if (terminal.isAnsiSupported()) { // debug("doing backspace redraw: " + getCursorPosition() + " on " + termwidth + ": " + lines); printAnsiSequence("K"); // if cursor+num wraps, then we need to clear the line(s) below too // last char printed is one pos less than cursor so we subtract // one /* // TODO: fixme (does not work - test with reverse search with wrapping line and CTRL-E) int endCol = (getCursorPosition() + num - 1) % termwidth; int curCol = getCursorPosition() % termwidth; if (endCol < curCol) lines++; for (int i = 1; i < lines; i++) { printAnsiSequence("B"); printAnsiSequence("2K"); } for (int i = 1; i < lines; i++) { printAnsiSequence("A"); } return count; */ } } drawBuffer(count); return count; } /** * Issue a backspace. * * @return true if successful */ public boolean backspace() throws IOException { return backspace(1) == 1; } protected boolean moveToEnd() throws IOException { if (buf.cursor == buf.length()) { return true; } return moveCursor(buf.length() - buf.cursor) > 0; } /** * Delete the character at the current position and redraw the remainder of the buffer. */ private boolean deleteCurrentCharacter() throws IOException { if (buf.length() == 0 || buf.cursor == buf.length()) { return false; } buf.buffer.deleteCharAt(buf.cursor); drawBuffer(1); return true; } /** * This method is calling while doing a delete-to ("d"), change-to ("c"), * or yank-to ("y") and it filters out only those movement operations * that are allowable during those operations. Any operation that isn't * allow drops you back into movement mode. * * @param op The incoming operation to remap * @return The remaped operation */ private Operation viDeleteChangeYankToRemap (Operation op) { switch (op) { case VI_EOF_MAYBE: case ABORT: case BACKWARD_CHAR: case FORWARD_CHAR: case END_OF_LINE: case VI_MATCH: case VI_BEGNNING_OF_LINE_OR_ARG_DIGIT: case VI_ARG_DIGIT: case VI_PREV_WORD: case VI_END_WORD: case VI_CHAR_SEARCH: case VI_NEXT_WORD: case VI_FIRST_PRINT: case VI_GOTO_MARK: case VI_COLUMN: case VI_DELETE_TO: case VI_YANK_TO: case VI_CHANGE_TO: return op; default: return Operation.VI_MOVEMENT_MODE; } } /** * Deletes the previous character from the cursor position * @param count number of times to do it. * @return true if it was done. * @throws IOException */ private boolean viRubout(int count) throws IOException { boolean ok = true; for (int i = 0; ok && i < count; i++) { ok = backspace(); } return ok; } /** * Deletes the character you are sitting on and sucks the rest of * the line in from the right. * @param count Number of times to perform the operation. * @return true if its works, false if it didn't * @throws IOException */ private boolean viDelete(int count) throws IOException { boolean ok = true; for (int i = 0; ok && i < count; i++) { ok = deleteCurrentCharacter(); } return ok; } /** * Switches the case of the current character from upper to lower * or lower to upper as necessary and advances the cursor one * position to the right. * @param count The number of times to repeat * @return true if it completed successfully, false if not all * case changes could be completed. * @throws IOException */ private boolean viChangeCase(int count) throws IOException { boolean ok = true; for (int i = 0; ok && i < count; i++) { ok = buf.cursor < buf.buffer.length (); if (ok) { char ch = buf.buffer.charAt(buf.cursor); if (Character.isUpperCase(ch)) { ch = Character.toLowerCase(ch); } else if (Character.isLowerCase(ch)) { ch = Character.toUpperCase(ch); } buf.buffer.setCharAt(buf.cursor, ch); drawBuffer(1); moveCursor(1); } } return ok; } /** * Implements the vi change character command (in move-mode "r" * followed by the character to change to). * @param count Number of times to perform the action * @param c The character to change to * @return Whether or not there were problems encountered * @throws IOException */ private boolean viChangeChar(int count, int c) throws IOException { // EOF, ESC, or CTRL-C aborts. if (c < 0 || c == '\033' || c == '\003') { return true; } boolean ok = true; for (int i = 0; ok && i < count; i++) { ok = buf.cursor < buf.buffer.length (); if (ok) { buf.buffer.setCharAt(buf.cursor, (char) c); drawBuffer(1); if (i < (count-1)) { moveCursor(1); } } } return ok; } /** * This is a close facsimile of the actual vi previous word logic. In * actual vi words are determined by boundaries of identity characterse. * This logic is a bit more simple and simply looks at white space or * digits or characters. It should be revised at some point. * * @param count number of iterations * @return true if the move was successful, false otherwise * @throws IOException */ private boolean viPreviousWord(int count) throws IOException { boolean ok = true; if (buf.cursor == 0) { return false; } int pos = buf.cursor - 1; for (int i = 0; pos > 0 && i < count; i++) { // If we are on white space, then move back. while (pos > 0 && isWhitespace(buf.buffer.charAt(pos))) { --pos; } while (pos > 0 && !isDelimiter(buf.buffer.charAt(pos-1))) { --pos; } if (pos > 0 && i < (count-1)) { --pos; } } setCursorPosition(pos); return ok; } /** * Performs the vi "delete-to" action, deleting characters between a given * span of the input line. * @param startPos The start position * @param endPos The end position. * @return true if it succeeded, false otherwise * @throws IOException */ private boolean viDeleteTo(int startPos, int endPos) throws IOException { if (startPos == endPos) { return true; } if (endPos < startPos) { int tmp = endPos; endPos = startPos; startPos = tmp; } setCursorPosition(startPos); buf.cursor = startPos; buf.buffer.delete(startPos, endPos); drawBuffer(endPos - startPos); return true; } /** * Implement the "vi" yank-to operation. This operation allows you * to yank the contents of the current line based upon a move operation, * for exaple "yw" yanks the current word, "3yw" yanks 3 words, etc. * * @param startPos The starting position from which to yank * @param endPos The ending position to which to yank * @return true if the yank succeeded * @throws IOException */ private boolean viYankTo(int startPos, int endPos) throws IOException { int cursorPos = startPos; if (endPos < startPos) { int tmp = endPos; endPos = startPos; startPos = tmp; } if (startPos == endPos) { yankBuffer = ""; return true; } yankBuffer = buf.buffer.substring(startPos, endPos); /* * It was a movement command that moved the cursor to find the * end position, so put the cursor back where it started. */ setCursorPosition(cursorPos); return true; } /** * Pasts the yank buffer to the right of the current cursor position * and moves the cursor to the end of the pasted region. * * @param count Number of times to perform the operation. * @return true if it worked, false otherwise * @throws IOException */ private boolean viPut(int count) throws IOException { if (yankBuffer.length () == 0) { return true; } if (buf.cursor < buf.buffer.length ()) { moveCursor(1); } for (int i = 0; i < count; i++) { putString(yankBuffer); } moveCursor(-1); return true; } /** * Searches forward of the current position for a character and moves * the cursor onto it. * @param count Number of times to repeat the process. * @param ch The character to search for * @return true if the char was found, false otherwise * @throws IOException */ private boolean viCharSearch(int count, int invokeChar, int ch) throws IOException { if (ch < 0 || invokeChar < 0) { return false; } char searchChar = (char)ch; boolean isForward; boolean stopBefore; /* * The character stuff turns out to be hairy. Here is how it works: * f - search forward for ch * F - search backward for ch * t - search forward for ch, but stop just before the match * T - search backward for ch, but stop just after the match * ; - After [fFtT;], repeat the last search, after ',' reverse it * , - After [fFtT;], reverse the last search, after ',' repeat it */ if (invokeChar == ';' || invokeChar == ',') { // No recent search done? Then bail if (charSearchChar == 0) { return false; } // Reverse direction if switching between ',' and ';' if (charSearchLastInvokeChar == ';' || charSearchLastInvokeChar == ',') { if (charSearchLastInvokeChar != invokeChar) { charSearchFirstInvokeChar = switchCase(charSearchFirstInvokeChar); } } else { if (invokeChar == ',') { charSearchFirstInvokeChar = switchCase(charSearchFirstInvokeChar); } } searchChar = charSearchChar; } else { charSearchChar = searchChar; charSearchFirstInvokeChar = (char) invokeChar; } charSearchLastInvokeChar = (char)invokeChar; isForward = Character.isLowerCase(charSearchFirstInvokeChar); stopBefore = (Character.toLowerCase(charSearchFirstInvokeChar) == 't'); boolean ok = false; if (isForward) { while (count-- > 0) { int pos = buf.cursor + 1; while (pos < buf.buffer.length()) { if (buf.buffer.charAt(pos) == (char) searchChar) { setCursorPosition(pos); ok = true; break; } ++pos; } } if (ok) { if (stopBefore) moveCursor(-1); /* * When in yank-to, move-to, del-to state we actually want to * go to the character after the one we landed on to make sure * that the character we ended up on is included in the * operation */ if (isInViMoveOperationState()) { moveCursor(1); } } } else { while (count-- > 0) { int pos = buf.cursor - 1; while (pos >= 0) { if (buf.buffer.charAt(pos) == (char) searchChar) { setCursorPosition(pos); ok = true; break; } --pos; } } if (ok && stopBefore) moveCursor(1); } return ok; } private char switchCase(char ch) { if (Character.isUpperCase(ch)) { return Character.toLowerCase(ch); } return Character.toUpperCase(ch); } /** * @return true if line reader is in the middle of doing a change-to * delete-to or yank-to. */ private final boolean isInViMoveOperationState() { return state == State.VI_CHANGE_TO || state == State.VI_DELETE_TO || state == State.VI_YANK_TO; } /** * This is a close facsimile of the actual vi next word logic. * As with viPreviousWord() this probably needs to be improved * at some point. * * @param count number of iterations * @return true if the move was successful, false otherwise * @throws IOException */ private boolean viNextWord(int count) throws IOException { int pos = buf.cursor; int end = buf.buffer.length(); for (int i = 0; pos < end && i < count; i++) { // Skip over letter/digits while (pos < end && !isDelimiter(buf.buffer.charAt(pos))) { ++pos; } /* * Don't you love special cases? During delete-to and yank-to * operations the word movement is normal. However, during a * change-to, the trailing spaces behind the last word are * left in tact. */ if (i < (count-1) || !(state == State.VI_CHANGE_TO)) { while (pos < end && isDelimiter(buf.buffer.charAt(pos))) { ++pos; } } } setCursorPosition(pos); return true; } /** * Implements a close facsimile of the vi end-of-word movement. * If the character is on white space, it takes you to the end * of the next word. If it is on the last character of a word * it takes you to the next of the next word. Any other character * of a word, takes you to the end of the current word. * * @param count Number of times to repeat the action * @return true if it worked. * @throws IOException */ private boolean viEndWord(int count) throws IOException { int pos = buf.cursor; int end = buf.buffer.length(); for (int i = 0; pos < end && i < count; i++) { if (pos < (end-1) && !isDelimiter(buf.buffer.charAt(pos)) && isDelimiter(buf.buffer.charAt (pos+1))) { ++pos; } // If we are on white space, then move back. while (pos < end && isDelimiter(buf.buffer.charAt(pos))) { ++pos; } while (pos < (end-1) && !isDelimiter(buf.buffer.charAt(pos+1))) { ++pos; } } setCursorPosition(pos); return true; } private boolean previousWord() throws IOException { while (isDelimiter(buf.current()) && (moveCursor(-1) != 0)) { // nothing } while (!isDelimiter(buf.current()) && (moveCursor(-1) != 0)) { // nothing } return true; } private boolean nextWord() throws IOException { while (isDelimiter(buf.nextChar()) && (moveCursor(1) != 0)) { // nothing } while (!isDelimiter(buf.nextChar()) && (moveCursor(1) != 0)) { // nothing } return true; } /** * Deletes to the beginning of the word that the cursor is sitting on. * If the cursor is on white-space, it deletes that and to the beginning * of the word before it. If the user is not on a word or whitespace * it deletes up to the end of the previous word. * * @param count Number of times to perform the operation * @return true if it worked, false if you tried to delete too many words * @throws IOException */ private boolean unixWordRubout(int count) throws IOException { for (; count > 0; --count) { if (buf.cursor == 0) return false; while (isWhitespace(buf.current()) && backspace()) { // nothing } while (!isWhitespace(buf.current()) && backspace()) { // nothing } } return true; } private String insertComment(boolean isViMode) throws IOException { String comment = this.getCommentBegin (); setCursorPosition(0); putString(comment); if (isViMode) { consoleKeys.setKeyMap(KeyMap.VI_INSERT); } return accept(); } /** * Similar to putString() but allows the string to be repeated a specific * number of times, allowing easy support of vi digit arguments to a given * command. The string is placed as the current cursor position. * * @param count The count of times to insert the string. * @param str The string to insert * @return true if the operation is a success, false otherwise * @throws IOException */ private boolean insert(int count, final CharSequence str) throws IOException { for (int i = 0; i < count; i++) { buf.write(str); if (mask == null) { // no masking print(str); } else if (mask == NULL_MASK) { // don't print anything } else { print(mask, str.length()); } } drawBuffer(); return true; } /** * Implements vi search ("/" or "?"). * @throws IOException */ private int viSearch(char searchChar) throws IOException { boolean isForward = (searchChar == '/'); /* * This is a little gross, I'm sure there is a more appropriate way * of saving and restoring state. */ CursorBuffer origBuffer = buf.copy(); // Clear the contents of the current line and setCursorPosition (0); killLine(); // Our new "prompt" is the character that got us into search mode. putString(Character.toString(searchChar)); flush(); boolean isAborted = false; boolean isComplete = false; /* * Readline doesn't seem to do any special character map handling * here, so I think we are safe. */ int ch = -1; while (!isAborted && !isComplete && (ch = readCharacter()) != -1) { switch (ch) { case '\033': // ESC /* * The ESC behavior doesn't appear to be readline behavior, * but it is a little tweak of my own. I like it. */ isAborted = true; break; case '\010': // Backspace case '\177': // Delete backspace(); /* * Backspacing through the "prompt" aborts the search. */ if (buf.cursor == 0) { isAborted = true; } break; case '\012': // NL case '\015': // CR isComplete = true; break; default: putString(Character.toString((char) ch)); } flush(); } // If we aborted, then put ourself at the end of the original buffer. if (ch == -1 || isAborted) { setCursorPosition(0); killLine(); putString(origBuffer.buffer); setCursorPosition(origBuffer.cursor); return -1; } /* * The first character of the buffer was the search character itself * so we discard it. */ String searchTerm = buf.buffer.substring(1); int idx = -1; /* * The semantics of the history thing is gross when you want to * explicitly iterate over entries (without an iterator) as size() * returns the actual number of entries in the list but get() * doesn't work the way you think. */ int end = history.index(); int start = (end <= history.size()) ? 0 : end - history.size(); if (isForward) { for (int i = start; i < end; i++) { if (history.get(i).toString().contains(searchTerm)) { idx = i; break; } } } else { for (int i = end-1; i >= start; i--) { if (history.get(i).toString().contains(searchTerm)) { idx = i; break; } } } /* * No match? Then restore what we were working on, but make sure * the cursor is at the beginning of the line. */ if (idx == -1) { setCursorPosition(0); killLine(); putString(origBuffer.buffer); setCursorPosition(0); return -1; } /* * Show the match. */ setCursorPosition(0); killLine(); putString(history.get(idx)); setCursorPosition(0); flush(); /* * While searching really only the "n" and "N" keys are interpreted * as movement, any other key is treated as if you are editing the * line with it, so we return it back up to the caller for interpretation. */ isComplete = false; while (!isComplete && (ch = readCharacter()) != -1) { boolean forward = isForward; switch (ch) { case 'p': case 'P': forward = !isForward; // Fallthru case 'n': case 'N': boolean isMatch = false; if (forward) { for (int i = idx+1; !isMatch && i < end; i++) { if (history.get(i).toString().contains(searchTerm)) { idx = i; isMatch = true; } } } else { for (int i = idx - 1; !isMatch && i >= start; i--) { if (history.get(i).toString().contains(searchTerm)) { idx = i; isMatch = true; } } } if (isMatch) { setCursorPosition(0); killLine(); putString(history.get(idx)); setCursorPosition(0); } break; default: isComplete = true; } flush(); } /* * Complete? */ return ch; } public void setParenBlinkTimeout(int timeout) { parenBlinkTimeout = timeout; } private void insertClose(String s) throws IOException { putString(s); int closePosition = buf.cursor; moveCursor(-1); viMatch(); if (in.isNonBlockingEnabled()) { in.peek(parenBlinkTimeout); } setCursorPosition(closePosition); } /** * Implements vi style bracket matching ("%" command). The matching * bracket for the current bracket type that you are sitting on is matched. * The logic works like so: * @return true if it worked, false if the cursor was not on a bracket * character or if there was no matching bracket. * @throws IOException */ private boolean viMatch() throws IOException { int pos = buf.cursor; if (pos == buf.length()) { return false; } int type = getBracketType(buf.buffer.charAt (pos)); int move = (type < 0) ? -1 : 1; int count = 1; if (type == 0) return false; while (count > 0) { pos += move; // Fell off the start or end. if (pos < 0 || pos >= buf.buffer.length ()) { return false; } int curType = getBracketType(buf.buffer.charAt (pos)); if (curType == type) { ++count; } else if (curType == -type) { --count; } } /* * Slight adjustment for delete-to, yank-to, change-to to ensure * that the matching paren is consumed */ if (move > 0 && isInViMoveOperationState()) ++pos; setCursorPosition(pos); return true; } /** * Given a character determines what type of bracket it is (paren, * square, curly, or none). * @param ch The character to check * @return 1 is square, 2 curly, 3 parent, or zero for none. The value * will be negated if it is the closing form of the bracket. */ private int getBracketType (char ch) { switch (ch) { case '[': return 1; case ']': return -1; case '{': return 2; case '}': return -2; case '(': return 3; case ')': return -3; default: return 0; } } private boolean deletePreviousWord() throws IOException { while (isDelimiter(buf.current()) && backspace()) { // nothing } while (!isDelimiter(buf.current()) && backspace()) { // nothing } return true; } private boolean deleteNextWord() throws IOException { while (isDelimiter(buf.nextChar()) && delete()) { } while (!isDelimiter(buf.nextChar()) && delete()) { // nothing } return true; } private boolean capitalizeWord() throws IOException { boolean first = true; int i = 1; char c; while (buf.cursor + i - 1< buf.length() && !isDelimiter((c = buf.buffer.charAt(buf.cursor + i - 1)))) { buf.buffer.setCharAt(buf.cursor + i - 1, first ? Character.toUpperCase(c) : Character.toLowerCase(c)); first = false; i++; } drawBuffer(); moveCursor(i - 1); return true; } private boolean upCaseWord() throws IOException { int i = 1; char c; while (buf.cursor + i - 1 < buf.length() && !isDelimiter((c = buf.buffer.charAt(buf.cursor + i - 1)))) { buf.buffer.setCharAt(buf.cursor + i - 1, Character.toUpperCase(c)); i++; } drawBuffer(); moveCursor(i - 1); return true; } private boolean downCaseWord() throws IOException { int i = 1; char c; while (buf.cursor + i - 1 < buf.length() && !isDelimiter((c = buf.buffer.charAt(buf.cursor + i - 1)))) { buf.buffer.setCharAt(buf.cursor + i - 1, Character.toLowerCase(c)); i++; } drawBuffer(); moveCursor(i - 1); return true; } /** * Performs character transpose. The character prior to the cursor and the * character under the cursor are swapped and the cursor is advanced one * character unless you are already at the end of the line. * * @param count The number of times to perform the transpose * @return true if the operation succeeded, false otherwise (e.g. transpose * cannot happen at the beginning of the line). * @throws IOException */ private boolean transposeChars(int count) throws IOException { for (; count > 0; --count) { if (buf.cursor == 0 || buf.cursor == buf.buffer.length()) { return false; } int first = buf.cursor-1; int second = buf.cursor; char tmp = buf.buffer.charAt (first); buf.buffer.setCharAt(first, buf.buffer.charAt(second)); buf.buffer.setCharAt(second, tmp); // This could be done more efficiently by only re-drawing at the end. moveInternal(-1); drawBuffer(); moveInternal(2); } return true; } public boolean isKeyMap(String name) { // Current keymap. KeyMap map = consoleKeys.getKeys(); KeyMap mapByName = consoleKeys.getKeyMaps().get(name); if (mapByName == null) return false; /* * This may not be safe to do, but there doesn't appear to be a * clean way to find this information out. */ return map == mapByName; } /** * The equivalent of hitting <RET>. The line is considered * complete and is returned. * * @return The completed line of text. * @throws IOException */ public String accept() throws IOException { moveToEnd(); println(); // output newline flush(); return finishBuffer(); } private void abort() throws IOException { beep(); buf.clear(); println(); redrawLine(); } /** * Move the cursor where characters. * * @param num If less than 0, move abs(where) to the left, otherwise move where to the right. * @return The number of spaces we moved */ public int moveCursor(final int num) throws IOException { int where = num; if ((buf.cursor == 0) && (where <= 0)) { return 0; } if ((buf.cursor == buf.buffer.length()) && (where >= 0)) { return 0; } if ((buf.cursor + where) < 0) { where = -buf.cursor; } else if ((buf.cursor + where) > buf.buffer.length()) { where = buf.buffer.length() - buf.cursor; } moveInternal(where); return where; } /** * Move the cursor where characters, without checking the current buffer. * * @param where the number of characters to move to the right or left. */ private void moveInternal(final int where) throws IOException { // debug ("move cursor " + where + " (" // + buf.cursor + " => " + (buf.cursor + where) + ")"); buf.cursor += where; if (terminal.isAnsiSupported()) { if (where < 0) { back(Math.abs(where)); } else { int width = getTerminal().getWidth(); int cursor = getCursorPosition(); int oldLine = (cursor - where) / width; int newLine = cursor / width; if (newLine > oldLine) { printAnsiSequence((newLine - oldLine) + "B"); } printAnsiSequence(1 +(cursor % width) + "G"); } // flush(); return; } char c; if (where < 0) { int len = 0; for (int i = buf.cursor; i < buf.cursor - where; i++) { if (buf.buffer.charAt(i) == '\t') { len += TAB_WIDTH; } else { len++; } } char chars[] = new char[len]; Arrays.fill(chars, BACKSPACE); out.write(chars); return; } else if (buf.cursor == 0) { return; } else if (mask != null) { c = mask; } else { print(buf.buffer.substring(buf.cursor - where, buf.cursor).toCharArray()); return; } // null character mask: don't output anything if (mask == NULL_MASK) { return; } print(c, Math.abs(where)); } // FIXME: replace() is not used public final boolean replace(final int num, final String replacement) { buf.buffer.replace(buf.cursor - num, buf.cursor, replacement); try { moveCursor(-num); drawBuffer(Math.max(0, num - replacement.length())); moveCursor(replacement.length()); } catch (IOException e) { e.printStackTrace(); return false; } return true; } /** * Read a character from the console. * * @return the character, or -1 if an EOF is received. */ public final int readCharacter() throws IOException { int c = reader.read(); if (c >= 0) { Log.trace("Keystroke: ", c); // clear any echo characters if (terminal.isSupported()) { clearEcho(c); } } return c; } /** * Clear the echoed characters for the specified character code. */ private int clearEcho(final int c) throws IOException { // if the terminal is not echoing, then ignore if (!terminal.isEchoEnabled()) { return 0; } // otherwise, clear int num = countEchoCharacters(c); back(num); drawBuffer(num); return num; } private int countEchoCharacters(final int c) { // tabs as special: we need to determine the number of spaces // to cancel based on what out current cursor position is if (c == 9) { int tabStop = 8; // will this ever be different? int position = getCursorPosition(); return tabStop - (position % tabStop); } return getPrintableCharacters(c).length(); } /** * Return the number of characters that will be printed when the specified * character is echoed to the screen * * Adapted from cat by Torbjorn Granlund, as repeated in stty by David MacKenzie. */ private StringBuilder getPrintableCharacters(final int ch) { StringBuilder sbuff = new StringBuilder(); if (ch >= 32) { if (ch < 127) { sbuff.append(ch); } else if (ch == 127) { sbuff.append('^'); sbuff.append('?'); } else { sbuff.append('M'); sbuff.append('-'); if (ch >= (128 + 32)) { if (ch < (128 + 127)) { sbuff.append((char) (ch - 128)); } else { sbuff.append('^'); sbuff.append('?'); } } else { sbuff.append('^'); sbuff.append((char) (ch - 128 + 64)); } } } else { sbuff.append('^'); sbuff.append((char) (ch + 64)); } return sbuff; } public final int readCharacter(final char... allowed) throws IOException { // if we restrict to a limited set and the current character is not in the set, then try again. char c; Arrays.sort(allowed); // always need to sort before binarySearch while (Arrays.binarySearch(allowed, c = (char) readCharacter()) < 0) { // nothing } return c; } // // Key Bindings // public static final String JLINE_COMPLETION_THRESHOLD = "jline.completion.threshold"; // // Line Reading // /** * Read the next line and return the contents of the buffer. */ public String readLine() throws IOException { return readLine((String) null); } /** * Read the next line with the specified character mask. If null, then * characters will be echoed. If 0, then no characters will be echoed. */ public String readLine(final Character mask) throws IOException { return readLine(null, mask); } public String readLine(final String prompt) throws IOException { return readLine(prompt, null); } /** * Sets the current keymap by name. Supported keymaps are "emacs", * "vi-insert", "vi-move". * @param name The name of the keymap to switch to * @return true if the keymap was set, or false if the keymap is * not recognized. */ public boolean setKeyMap(String name) { return consoleKeys.setKeyMap(name); } /** * Returns the name of the current key mapping. * @return the name of the key mapping. This will be the canonical name * of the current mode of the key map and may not reflect the name that * was used with {@link #setKeyMap(String)}. */ public String getKeyMap() { return consoleKeys.getKeys().getName(); } /** * Read a line from the in {@link InputStream}, and return the line * (without any trailing newlines). * * @param prompt The prompt to issue to the console, may be null. * @return A line that is read from the terminal, or null if there was null input (e.g., CTRL-D * was pressed). */ public String readLine(String prompt, final Character mask) throws IOException { // prompt may be null // mask may be null /* * This is the accumulator for VI-mode repeat count. That is, while in * move mode, if you type 30x it will delete 30 characters. This is * where the "30" is accumulated until the command is struck. */ int repeatCount = 0; // FIXME: This blows, each call to readLine will reset the console's state which doesn't seem very nice. this.mask = mask; if (prompt != null) { setPrompt(prompt); } else { prompt = getPrompt(); } try { if (!terminal.isSupported()) { beforeReadLine(prompt, mask); } if (prompt != null && prompt.length() > 0) { out.write(prompt); out.flush(); } // if the terminal is unsupported, just use plain-java reading if (!terminal.isSupported()) { return readLineSimple(); } if (handleUserInterrupt && (terminal instanceof UnixTerminal)) { ((UnixTerminal) terminal).disableInterruptCharacter(); } String originalPrompt = this.prompt; state = State.NORMAL; boolean success = true; StringBuilder sb = new StringBuilder(); Stack pushBackChar = new Stack(); while (true) { int c = pushBackChar.isEmpty() ? readCharacter() : pushBackChar.pop (); if (c == -1) { return null; } sb.appendCodePoint(c); if (recording) { macro += new String(new int[]{c}, 0, 1); } Object o = getKeys().getBound( sb ); if (o == Operation.DO_LOWERCASE_VERSION) { sb.setLength( sb.length() - 1); sb.append( Character.toLowerCase( (char) c )); o = getKeys().getBound( sb ); } /* * A KeyMap indicates that the key that was struck has a * number of keys that can follow it as indicated in the * map. This is used primarily for Emacs style ESC-META-x * lookups. Since more keys must follow, go back to waiting * for the next key. */ if ( o instanceof KeyMap ) { /* * The ESC key (#27) is special in that it is ambiguous until * you know what is coming next. The ESC could be a literal * escape, like the user entering vi-move mode, or it could * be part of a terminal control sequence. The following * logic attempts to disambiguate things in the same * fashion as regular vi or readline. * * When ESC is encountered and there is no other pending * character in the pushback queue, then attempt to peek * into the input stream (if the feature is enabled) for * 150ms. If nothing else is coming, then assume it is * not a terminal control sequence, but a raw escape. */ if (c == 27 && pushBackChar.isEmpty() && in.isNonBlockingEnabled() && in.peek(escapeTimeout) == -2) { o = ((KeyMap) o).getAnotherKey(); if (o == null || o instanceof KeyMap) { continue; } sb.setLength(0); } else { continue; } } /* * If we didn't find a binding for the key and there is * more than one character accumulated then start checking * the largest span of characters from the beginning to * see if there is a binding for them. * * For example if our buffer has ESC,CTRL-M,C the getBound() * called previously indicated that there is no binding for * this sequence, so this then checks ESC,CTRL-M, and failing * that, just ESC. Each keystroke that is pealed off the end * during these tests is stuffed onto the pushback buffer so * they won't be lost. * * If there is no binding found, then we go back to waiting for * input. */ while ( o == null && sb.length() > 0 ) { c = sb.charAt( sb.length() - 1 ); sb.setLength( sb.length() - 1 ); Object o2 = getKeys().getBound( sb ); if ( o2 instanceof KeyMap ) { o = ((KeyMap) o2).getAnotherKey(); if ( o == null ) { continue; } else { pushBackChar.push( (char) c ); } } } if ( o == null ) { continue; } Log.trace("Binding: ", o); // Handle macros if (o instanceof String) { String macro = (String) o; for (int i = 0; i < macro.length(); i++) { pushBackChar.push(macro.charAt(macro.length() - 1 - i)); } sb.setLength( 0 ); continue; } // Handle custom callbacks if (o instanceof ActionListener) { ((ActionListener) o).actionPerformed(null); sb.setLength( 0 ); continue; } // Search mode. // // Note that we have to do this first, because if there is a command // not linked to a search command, we leave the search mode and fall // through to the normal state. if (state == State.SEARCH || state == State.FORWARD_SEARCH) { int cursorDest = -1; switch ( ((Operation) o )) { case ABORT: state = State.NORMAL; buf.clear(); buf.buffer.append(searchTerm); break; case REVERSE_SEARCH_HISTORY: case HISTORY_SEARCH_BACKWARD: state = State.SEARCH; if (searchTerm.length() == 0) { searchTerm.append(previousSearchTerm); } if (searchIndex > 0) { searchIndex = searchBackwards(searchTerm.toString(), searchIndex); } break; case FORWARD_SEARCH_HISTORY: case HISTORY_SEARCH_FORWARD: state = State.FORWARD_SEARCH; if (searchTerm.length() == 0) { searchTerm.append(previousSearchTerm); } if (searchIndex > -1 && searchIndex < history.size() - 1) { searchIndex = searchForwards(searchTerm.toString(), searchIndex); } break; case BACKWARD_DELETE_CHAR: if (searchTerm.length() > 0) { searchTerm.deleteCharAt(searchTerm.length() - 1); if (state == State.SEARCH) { searchIndex = searchBackwards(searchTerm.toString()); } else { searchIndex = searchForwards(searchTerm.toString()); } } break; case SELF_INSERT: searchTerm.appendCodePoint(c); if (state == State.SEARCH) { searchIndex = searchBackwards(searchTerm.toString()); } else { searchIndex = searchForwards(searchTerm.toString()); } break; default: // Set buffer and cursor position to the found string. if (searchIndex != -1) { history.moveTo(searchIndex); // set cursor position to the found string cursorDest = history.current().toString().indexOf(searchTerm.toString()); } state = State.NORMAL; break; } // if we're still in search mode, print the search status if (state == State.SEARCH || state == State.FORWARD_SEARCH) { if (searchTerm.length() == 0) { if (state == State.SEARCH) { printSearchStatus("", ""); } else { printForwardSearchStatus("", ""); } searchIndex = -1; } else { if (searchIndex == -1) { beep(); printSearchStatus(searchTerm.toString(), ""); } else if (state == State.SEARCH) { printSearchStatus(searchTerm.toString(), history.get(searchIndex).toString()); } else { printForwardSearchStatus(searchTerm.toString(), history.get(searchIndex).toString()); } } } // otherwise, restore the line else { restoreLine(originalPrompt, cursorDest); } } if (state != State.SEARCH && state != State.FORWARD_SEARCH) { /* * If this is still false at the end of the switch, then * we reset our repeatCount to 0. */ boolean isArgDigit = false; /* * Every command that can be repeated a specified number * of times, needs to know how many times to repeat, so * we figure that out here. */ int count = (repeatCount == 0) ? 1 : repeatCount; /* * Default success to true. You only need to explicitly * set it if something goes wrong. */ success = true; if (o instanceof Operation) { Operation op = (Operation)o; /* * Current location of the cursor (prior to the operation). * These are used by vi *-to operation (e.g. delete-to) * so we know where we came from. */ int cursorStart = buf.cursor; State origState = state; /* * If we are on a "vi" movement based operation, then we * need to restrict the sets of inputs pretty heavily. */ if (state == State.VI_CHANGE_TO || state == State.VI_YANK_TO || state == State.VI_DELETE_TO) { op = viDeleteChangeYankToRemap(op); } switch ( op ) { case COMPLETE: // tab // There is an annoyance with tab completion in that // sometimes the user is actually pasting input in that // has physical tabs in it. This attempts to look at how // quickly a character follows the tab, if the character // follows *immediately*, we assume it is a tab literal. boolean isTabLiteral = false; if (copyPasteDetection && c == 9 && (!pushBackChar.isEmpty() || (in.isNonBlockingEnabled() && in.peek(escapeTimeout) != -2))) { isTabLiteral = true; } if (! isTabLiteral) { success = complete(); } else { putString(sb); } break; case POSSIBLE_COMPLETIONS: printCompletionCandidates(); break; case BEGINNING_OF_LINE: success = setCursorPosition(0); break; case KILL_LINE: // CTRL-K success = killLine(); break; case KILL_WHOLE_LINE: success = setCursorPosition(0) && killLine(); break; case CLEAR_SCREEN: // CTRL-L success = clearScreen(); break; case OVERWRITE_MODE: buf.setOverTyping(!buf.isOverTyping()); break; case SELF_INSERT: putString(sb); break; case ACCEPT_LINE: return accept(); case ABORT: if (searchTerm == null) { abort(); } break; case INTERRUPT: if (handleUserInterrupt) { println(); flush(); String partialLine = buf.buffer.toString(); buf.clear(); throw new UserInterruptException(partialLine); } break; /* * VI_MOVE_ACCEPT_LINE is the result of an ENTER * while in move mode. This is the same as a normal * ACCEPT_LINE, except that we need to enter * insert mode as well. */ case VI_MOVE_ACCEPT_LINE: consoleKeys.setKeyMap(KeyMap.VI_INSERT); return accept(); case BACKWARD_WORD: success = previousWord(); break; case FORWARD_WORD: success = nextWord(); break; case PREVIOUS_HISTORY: success = moveHistory(false); break; /* * According to bash/readline move through history * in "vi" mode will move the cursor to the * start of the line. If there is no previous * history, then the cursor doesn't move. */ case VI_PREVIOUS_HISTORY: success = moveHistory(false, count) && setCursorPosition(0); break; case NEXT_HISTORY: success = moveHistory(true); break; /* * According to bash/readline move through history * in "vi" mode will move the cursor to the * start of the line. If there is no next history, * then the cursor doesn't move. */ case VI_NEXT_HISTORY: success = moveHistory(true, count) && setCursorPosition(0); break; case BACKWARD_DELETE_CHAR: // backspace success = backspace(); break; case EXIT_OR_DELETE_CHAR: if (buf.buffer.length() == 0) { return null; } success = deleteCurrentCharacter(); break; case DELETE_CHAR: // delete success = deleteCurrentCharacter(); break; case BACKWARD_CHAR: success = moveCursor(-(count)) != 0; break; case FORWARD_CHAR: success = moveCursor(count) != 0; break; case UNIX_LINE_DISCARD: success = resetLine(); break; case UNIX_WORD_RUBOUT: success = unixWordRubout(count); break; case BACKWARD_KILL_WORD: success = deletePreviousWord(); break; case KILL_WORD: success = deleteNextWord(); break; case BEGINNING_OF_HISTORY: success = history.moveToFirst(); if (success) { setBuffer(history.current()); } break; case END_OF_HISTORY: success = history.moveToLast(); if (success) { setBuffer(history.current()); } break; case REVERSE_SEARCH_HISTORY: case HISTORY_SEARCH_BACKWARD: if (searchTerm != null) { previousSearchTerm = searchTerm.toString(); } searchTerm = new StringBuffer(buf.buffer); state = State.SEARCH; if (searchTerm.length() > 0) { searchIndex = searchBackwards(searchTerm.toString()); if (searchIndex == -1) { beep(); } printSearchStatus(searchTerm.toString(), searchIndex > -1 ? history.get(searchIndex).toString() : ""); } else { searchIndex = -1; printSearchStatus("", ""); } break; case FORWARD_SEARCH_HISTORY: case HISTORY_SEARCH_FORWARD: if (searchTerm != null) { previousSearchTerm = searchTerm.toString(); } searchTerm = new StringBuffer(buf.buffer); state = State.FORWARD_SEARCH; if (searchTerm.length() > 0) { searchIndex = searchForwards(searchTerm.toString()); if (searchIndex == -1) { beep(); } printForwardSearchStatus(searchTerm.toString(), searchIndex > -1 ? history.get(searchIndex).toString() : ""); } else { searchIndex = -1; printForwardSearchStatus("", ""); } break; case CAPITALIZE_WORD: success = capitalizeWord(); break; case UPCASE_WORD: success = upCaseWord(); break; case DOWNCASE_WORD: success = downCaseWord(); break; case END_OF_LINE: success = moveToEnd(); break; case TAB_INSERT: putString( "\t" ); break; case RE_READ_INIT_FILE: consoleKeys.loadKeys(appName, inputrcUrl); break; case START_KBD_MACRO: recording = true; break; case END_KBD_MACRO: recording = false; macro = macro.substring(0, macro.length() - sb.length()); break; case CALL_LAST_KBD_MACRO: for (int i = 0; i < macro.length(); i++) { pushBackChar.push(macro.charAt(macro.length() - 1 - i)); } sb.setLength( 0 ); break; case VI_EDITING_MODE: consoleKeys.setKeyMap(KeyMap.VI_INSERT); break; case VI_MOVEMENT_MODE: /* * If we are re-entering move mode from an * aborted yank-to, delete-to, change-to then * don't move the cursor back. The cursor is * only move on an expclit entry to movement * mode. */ if (state == state.NORMAL) { moveCursor(-1); } consoleKeys.setKeyMap(KeyMap.VI_MOVE); break; case VI_INSERTION_MODE: consoleKeys.setKeyMap(KeyMap.VI_INSERT); break; case VI_APPEND_MODE: moveCursor(1); consoleKeys.setKeyMap(KeyMap.VI_INSERT); break; case VI_APPEND_EOL: success = moveToEnd(); consoleKeys.setKeyMap(KeyMap.VI_INSERT); break; /* * Handler for CTRL-D. Attempts to follow readline * behavior. If the line is empty, then it is an EOF * otherwise it is as if the user hit enter. */ case VI_EOF_MAYBE: if (buf.buffer.length() == 0) { return null; } return accept(); case TRANSPOSE_CHARS: success = transposeChars(count); break; case INSERT_COMMENT: return insertComment (false); case INSERT_CLOSE_CURLY: insertClose("}"); break; case INSERT_CLOSE_PAREN: insertClose(")"); break; case INSERT_CLOSE_SQUARE: insertClose("]"); break; case VI_INSERT_COMMENT: return insertComment (true); case VI_MATCH: success = viMatch (); break; case VI_SEARCH: int lastChar = viSearch(sb.charAt (0)); if (lastChar != -1) { pushBackChar.push((char)lastChar); } break; case VI_ARG_DIGIT: repeatCount = (repeatCount * 10) + sb.charAt(0) - '0'; isArgDigit = true; break; case VI_BEGNNING_OF_LINE_OR_ARG_DIGIT: if (repeatCount > 0) { repeatCount = (repeatCount * 10) + sb.charAt(0) - '0'; isArgDigit = true; } else { success = setCursorPosition(0); } break; case VI_PREV_WORD: success = viPreviousWord(count); break; case VI_NEXT_WORD: success = viNextWord(count); break; case VI_END_WORD: success = viEndWord(count); break; case VI_INSERT_BEG: success = setCursorPosition(0); consoleKeys.setKeyMap(KeyMap.VI_INSERT); break; case VI_RUBOUT: success = viRubout(count); break; case VI_DELETE: success = viDelete(count); break; case VI_DELETE_TO: /* * This is a weird special case. In vi * "dd" deletes the current line. So if we * get a delete-to, followed by a delete-to, * we delete the line. */ if (state == State.VI_DELETE_TO) { success = setCursorPosition(0) && killLine(); state = origState = State.NORMAL; } else { state = State.VI_DELETE_TO; } break; case VI_YANK_TO: // Similar to delete-to, a "yy" yanks the whole line. if (state == State.VI_YANK_TO) { yankBuffer = buf.buffer.toString(); state = origState = State.NORMAL; } else { state = State.VI_YANK_TO; } break; case VI_CHANGE_TO: if (state == State.VI_CHANGE_TO) { success = setCursorPosition(0) && killLine(); state = origState = State.NORMAL; consoleKeys.setKeyMap(KeyMap.VI_INSERT); } else { state = State.VI_CHANGE_TO; } break; case VI_PUT: success = viPut(count); break; case VI_CHAR_SEARCH: { // ';' and ',' don't need another character. They indicate repeat next or repeat prev. int searchChar = (c != ';' && c != ',') ? (pushBackChar.isEmpty() ? readCharacter() : pushBackChar.pop ()) : 0; success = viCharSearch(count, c, searchChar); } break; case VI_CHANGE_CASE: success = viChangeCase(count); break; case VI_CHANGE_CHAR: success = viChangeChar(count, pushBackChar.isEmpty() ? readCharacter() : pushBackChar.pop()); break; case EMACS_EDITING_MODE: consoleKeys.setKeyMap(KeyMap.EMACS); break; default: break; } /* * If we were in a yank-to, delete-to, move-to * when this operation started, then fall back to */ if (origState != State.NORMAL) { if (origState == State.VI_DELETE_TO) { success = viDeleteTo(cursorStart, buf.cursor); } else if (origState == State.VI_CHANGE_TO) { success = viDeleteTo(cursorStart, buf.cursor); consoleKeys.setKeyMap(KeyMap.VI_INSERT); } else if (origState == State.VI_YANK_TO) { success = viYankTo(cursorStart, buf.cursor); } state = State.NORMAL; } /* * Another subtly. The check for the NORMAL state is * to ensure that we do not clear out the repeat * count when in delete-to, yank-to, or move-to modes. */ if (state == State.NORMAL && !isArgDigit) { /* * If the operation performed wasn't a vi argument * digit, then clear out the current repeatCount; */ repeatCount = 0; } if (state != State.SEARCH && state != State.FORWARD_SEARCH) { previousSearchTerm = ""; searchTerm = null; searchIndex = -1; } } } if (!success) { beep(); } sb.setLength( 0 ); flush(); } } finally { if (!terminal.isSupported()) { afterReadLine(); } if (handleUserInterrupt && (terminal instanceof UnixTerminal)) { ((UnixTerminal) terminal).enableInterruptCharacter(); } } } /** * Read a line for unsupported terminals. */ private String readLineSimple() throws IOException { StringBuilder buff = new StringBuilder(); if (skipLF) { skipLF = false; int i = readCharacter(); if (i == -1 || i == '\r') { return buff.toString(); } else if (i == '\n') { // ignore } else { buff.append((char) i); } } while (true) { int i = readCharacter(); if (i == -1 && buff.length() == 0) { return null; } if (i == -1 || i == '\n') { return buff.toString(); } else if (i == '\r') { skipLF = true; return buff.toString(); } else { buff.append((char) i); } } } // // Completion // private final List completers = new LinkedList(); private CompletionHandler completionHandler = new CandidateListCompletionHandler(); /** * Add the specified {@link jline.console.completer.Completer} to the list of handlers for tab-completion. * * @param completer the {@link jline.console.completer.Completer} to add * @return true if it was successfully added */ public boolean addCompleter(final Completer completer) { return completers.add(completer); } /** * Remove the specified {@link jline.console.completer.Completer} from the list of handlers for tab-completion. * * @param completer The {@link Completer} to remove * @return True if it was successfully removed */ public boolean removeCompleter(final Completer completer) { return completers.remove(completer); } /** * Returns an unmodifiable list of all the completers. */ public Collection getCompleters() { return Collections.unmodifiableList(completers); } public void setCompletionHandler(final CompletionHandler handler) { this.completionHandler = checkNotNull(handler); } public CompletionHandler getCompletionHandler() { return this.completionHandler; } /** * Use the completers to modify the buffer with the appropriate completions. * * @return true if successful */ protected boolean complete() throws IOException { // debug ("tab for (" + buf + ")"); if (completers.size() == 0) { return false; } List candidates = new LinkedList(); String bufstr = buf.buffer.toString(); int cursor = buf.cursor; int position = -1; for (Completer comp : completers) { if ((position = comp.complete(bufstr, cursor, candidates)) != -1) { break; } } return candidates.size() != 0 && getCompletionHandler().complete(this, candidates, position); } protected void printCompletionCandidates() throws IOException { // debug ("tab for (" + buf + ")"); if (completers.size() == 0) { return; } List candidates = new LinkedList(); String bufstr = buf.buffer.toString(); int cursor = buf.cursor; for (Completer comp : completers) { if (comp.complete(bufstr, cursor, candidates) != -1) { break; } } CandidateListCompletionHandler.printCandidates(this, candidates); drawLine(); } /** * The number of tab-completion candidates above which a warning will be * prompted before showing all the candidates. */ private int autoprintThreshold = Configuration.getInteger(JLINE_COMPLETION_THRESHOLD, 100); // same default as bash /** * @param threshold the number of candidates to print without issuing a warning. */ public void setAutoprintThreshold(final int threshold) { this.autoprintThreshold = threshold; } /** * @return the number of candidates to print without issuing a warning. */ public int getAutoprintThreshold() { return autoprintThreshold; } private boolean paginationEnabled; /** * Whether to use pagination when the number of rows of candidates exceeds the height of the terminal. */ public void setPaginationEnabled(final boolean enabled) { this.paginationEnabled = enabled; } /** * Whether to use pagination when the number of rows of candidates exceeds the height of the terminal. */ public boolean isPaginationEnabled() { return paginationEnabled; } // // History // private History history = new MemoryHistory(); public void setHistory(final History history) { this.history = history; } public History getHistory() { return history; } private boolean historyEnabled = true; /** * Whether or not to add new commands to the history buffer. */ public void setHistoryEnabled(final boolean enabled) { this.historyEnabled = enabled; } /** * Whether or not to add new commands to the history buffer. */ public boolean isHistoryEnabled() { return historyEnabled; } /** * Used in "vi" mode for argumented history move, to move a specific * number of history entries forward or back. * * @param next If true, move forward * @param count The number of entries to move * @return true if the move was successful * @throws IOException */ private boolean moveHistory(final boolean next, int count) throws IOException { boolean ok = true; for (int i = 0; i < count && (ok = moveHistory(next)); i++) { /* empty */ } return ok; } /** * Move up or down the history tree. */ private boolean moveHistory(final boolean next) throws IOException { if (next && !history.next()) { return false; } else if (!next && !history.previous()) { return false; } setBuffer(history.current()); return true; } // // Printing // public static final String CR = Configuration.getLineSeparator(); /** * Output the specified character to the output stream without manipulating the current buffer. */ private void print(final int c) throws IOException { if (c == '\t') { char chars[] = new char[TAB_WIDTH]; Arrays.fill(chars, ' '); out.write(chars); return; } out.write(c); } /** * Output the specified characters to the output stream without manipulating the current buffer. */ private void print(final char... buff) throws IOException { int len = 0; for (char c : buff) { if (c == '\t') { len += TAB_WIDTH; } else { len++; } } char chars[]; if (len == buff.length) { chars = buff; } else { chars = new char[len]; int pos = 0; for (char c : buff) { if (c == '\t') { Arrays.fill(chars, pos, pos + TAB_WIDTH, ' '); pos += TAB_WIDTH; } else { chars[pos] = c; pos++; } } } out.write(chars); } private void print(final char c, final int num) throws IOException { if (num == 1) { print(c); } else { char[] chars = new char[num]; Arrays.fill(chars, c); print(chars); } } /** * Output the specified string to the output stream (but not the buffer). */ public final void print(final CharSequence s) throws IOException { print(checkNotNull(s).toString().toCharArray()); } public final void println(final CharSequence s) throws IOException { print(checkNotNull(s).toString().toCharArray()); println(); } /** * Output a platform-dependant newline. */ public final void println() throws IOException { print(CR); // flush(); } // // Actions // /** * Issue a delete. * * @return true if successful */ public final boolean delete() throws IOException { return delete(1) == 1; } // FIXME: delete(int) only used by above + the return is always 1 and num is ignored /** * Issue num deletes. * * @return the number of characters backed up */ private int delete(final int num) throws IOException { // TODO: Try to use jansi for this /* Commented out because of DWA-2949: if (buf.cursor == 0) { return 0; } */ buf.buffer.delete(buf.cursor, buf.cursor + 1); drawBuffer(1); return 1; } /** * Kill the buffer ahead of the current cursor position. * * @return true if successful */ public boolean killLine() throws IOException { int cp = buf.cursor; int len = buf.buffer.length(); if (cp >= len) { return false; } int num = buf.buffer.length() - cp; clearAhead(num, 0); for (int i = 0; i < num; i++) { buf.buffer.deleteCharAt(len - i - 1); } return true; } /** * Clear the screen by issuing the ANSI "clear screen" code. */ public boolean clearScreen() throws IOException { if (!terminal.isAnsiSupported()) { return false; } // send the ANSI code to clear the screen printAnsiSequence("2J"); // then send the ANSI code to go to position 1,1 printAnsiSequence("1;1H"); redrawLine(); return true; } /** * Issue an audible keyboard bell. */ public void beep() throws IOException { if (bellEnabled) { print(KEYBOARD_BELL); // need to flush so the console actually beeps flush(); } } /** * Paste the contents of the clipboard into the console buffer * * @return true if clipboard contents pasted */ public boolean paste() throws IOException { Clipboard clipboard; try { // May throw ugly exception on system without X clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); } catch (Exception e) { return false; } if (clipboard == null) { return false; } Transferable transferable = clipboard.getContents(null); if (transferable == null) { return false; } try { Object content = transferable.getTransferData(DataFlavor.plainTextFlavor); // This fix was suggested in bug #1060649 at // http://sourceforge.net/tracker/index.php?func=detail&aid=1060649&group_id=64033&atid=506056 // to get around the deprecated DataFlavor.plainTextFlavor, but it // raises a UnsupportedFlavorException on Mac OS X if (content == null) { try { content = new DataFlavor().getReaderForText(transferable); } catch (Exception e) { // ignore } } if (content == null) { return false; } String value; if (content instanceof Reader) { // TODO: we might want instead connect to the input stream // so we can interpret individual lines value = ""; String line; BufferedReader read = new BufferedReader((Reader) content); while ((line = read.readLine()) != null) { if (value.length() > 0) { value += "\n"; } value += line; } } else { value = content.toString(); } if (value == null) { return true; } putString(value); return true; } catch (UnsupportedFlavorException e) { Log.error("Paste failed: ", e); return false; } } // // Triggered Actions // private final Map triggeredActions = new HashMap(); /** * Adding a triggered Action allows to give another curse of action if a character passed the pre-processing. *

* Say you want to close the application if the user enter q. * addTriggerAction('q', new ActionListener(){ System.exit(0); }); would do the trick. */ public void addTriggeredAction(final char c, final ActionListener listener) { triggeredActions.put(c, listener); } // // Formatted Output // /** * Output the specified {@link Collection} in proper columns. */ public void printColumns(final Collection items) throws IOException { if (items == null || items.isEmpty()) { return; } int width = getTerminal().getWidth(); int height = getTerminal().getHeight(); int maxWidth = 0; for (CharSequence item : items) { maxWidth = Math.max(maxWidth, item.length()); } maxWidth = maxWidth + 3; Log.debug("Max width: ", maxWidth); int showLines; if (isPaginationEnabled()) { showLines = height - 1; // page limit } else { showLines = Integer.MAX_VALUE; } StringBuilder buff = new StringBuilder(); for (CharSequence item : items) { if ((buff.length() + maxWidth) > width) { println(buff); buff.setLength(0); if (--showLines == 0) { // Overflow print(resources.getString("DISPLAY_MORE")); flush(); int c = readCharacter(); if (c == '\r' || c == '\n') { // one step forward showLines = 1; } else if (c != 'q') { // page forward showLines = height - 1; } back(resources.getString("DISPLAY_MORE").length()); if (c == 'q') { // cancel break; } } } // NOTE: toString() is important here due to AnsiString being retarded buff.append(item.toString()); for (int i = 0; i < (maxWidth - item.length()); i++) { buff.append(' '); } } if (buff.length() > 0) { println(buff); } } // // Non-supported Terminal Support // private Thread maskThread; private void beforeReadLine(final String prompt, final Character mask) { if (mask != null && maskThread == null) { final String fullPrompt = "\r" + prompt + " " + " " + " " + "\r" + prompt; maskThread = new Thread() { public void run() { while (!interrupted()) { try { Writer out = getOutput(); out.write(fullPrompt); out.flush(); sleep(3); } catch (IOException e) { return; } catch (InterruptedException e) { return; } } } }; maskThread.setPriority(Thread.MAX_PRIORITY); maskThread.setDaemon(true); maskThread.start(); } } private void afterReadLine() { if (maskThread != null && maskThread.isAlive()) { maskThread.interrupt(); } maskThread = null; } /** * Erases the current line with the existing prompt, then redraws the line * with the provided prompt and buffer * @param prompt * the new prompt * @param buffer * the buffer to be drawn * @param cursorDest * where you want the cursor set when the line has been drawn. * -1 for end of line. * */ public void resetPromptLine(String prompt, String buffer, int cursorDest) throws IOException { // move cursor to end of line moveToEnd(); // backspace all text, including prompt buf.buffer.append(this.prompt); int promptLength = 0; if (this.prompt != null) { promptLength = this.prompt.length(); } buf.cursor += promptLength; setPrompt(""); backspaceAll(); setPrompt(prompt); redrawLine(); setBuffer(buffer); // move cursor to destination (-1 will move to end of line) if (cursorDest < 0) cursorDest = buffer.length(); setCursorPosition(cursorDest); flush(); } public void printSearchStatus(String searchTerm, String match) throws IOException { printSearchStatus(searchTerm, match, "(reverse-i-search)`"); } public void printForwardSearchStatus(String searchTerm, String match) throws IOException { printSearchStatus(searchTerm, match, "(i-search)`"); } private void printSearchStatus(String searchTerm, String match, String searchLabel) throws IOException { String prompt = searchLabel + searchTerm + "': "; int cursorDest = match.indexOf(searchTerm); resetPromptLine(prompt, match, cursorDest); } public void restoreLine(String originalPrompt, int cursorDest) throws IOException { // TODO move cursor to matched string String prompt = lastLine(originalPrompt); String buffer = buf.buffer.toString(); resetPromptLine(prompt, buffer, cursorDest); } // // History search // /** * Search backward in history from a given position. * * @param searchTerm substring to search for. * @param startIndex the index from which on to search * @return index where this substring has been found, or -1 else. */ public int searchBackwards(String searchTerm, int startIndex) { return searchBackwards(searchTerm, startIndex, false); } /** * Search backwards in history from the current position. * * @param searchTerm substring to search for. * @return index where the substring has been found, or -1 else. */ public int searchBackwards(String searchTerm) { return searchBackwards(searchTerm, history.index()); } public int searchBackwards(String searchTerm, int startIndex, boolean startsWith) { ListIterator it = history.entries(startIndex); while (it.hasPrevious()) { History.Entry e = it.previous(); if (startsWith) { if (e.value().toString().startsWith(searchTerm)) { return e.index(); } } else { if (e.value().toString().contains(searchTerm)) { return e.index(); } } } return -1; } /** * Search forward in history from a given position. * * @param searchTerm substring to search for. * @param startIndex the index from which on to search * @return index where this substring has been found, or -1 else. */ public int searchForwards(String searchTerm, int startIndex) { return searchForwards(searchTerm, startIndex, false); } /** * Search forwards in history from the current position. * * @param searchTerm substring to search for. * @return index where the substring has been found, or -1 else. */ public int searchForwards(String searchTerm) { return searchForwards(searchTerm, history.index()); } public int searchForwards(String searchTerm, int startIndex, boolean startsWith) { ListIterator it = history.entries(startIndex); if (searchIndex != -1 && it.hasNext()) { it.next(); } while (it.hasNext()) { History.Entry e = it.next(); if (startsWith) { if (e.value().toString().startsWith(searchTerm)) { return e.index(); } } else { if (e.value().toString().contains(searchTerm)) { return e.index(); } } } return -1; } // // Helpers // /** * Checks to see if the specified character is a delimiter. We consider a * character a delimiter if it is anything but a letter or digit. * * @param c The character to test * @return True if it is a delimiter */ private boolean isDelimiter(final char c) { return !Character.isLetterOrDigit(c); } /** * Checks to see if a character is a whitespace character. Currently * this delegates to {@link Character#isWhitespace(char)}, however * eventually it should be hooked up so that the definition of whitespace * can be configured, as readline does. * * @param c The character to check * @return true if the character is a whitespace */ private boolean isWhitespace(final char c) { return Character.isWhitespace (c); } private void printAnsiSequence(String sequence) throws IOException { print(27); print('['); print(sequence); flush(); // helps with step debugging } } src/main/java/jline/console/CursorBuffer.java000066400000000000000000000053071214622044400215330ustar00rootroot00000000000000/* * 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 jline.console; import static jline.internal.Preconditions.checkNotNull; /** * A holder for a {@link StringBuilder} that also contains the current cursor position. * * @author Marc Prud'hommeaux * @author Jason Dillon * @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; } @Override public String toString() { return buffer.toString(); } } src/main/java/jline/console/KeyMap.java000066400000000000000000000665721214622044400203250ustar00rootroot00000000000000/* * 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 jline.console; import java.util.HashMap; import java.util.Map; /** * The KeyMap class contains all bindings from keys to operations. * * @author Guillaume Nodet * @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[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 keyMaps() { Map keyMaps = new HashMap(); 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, /* C */ Operation.VI_DELETE_TO, /* 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_SUBST, /* 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); } } src/main/java/jline/console/Operation.java000066400000000000000000000064441214622044400210670ustar00rootroot00000000000000/* * 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 jline.console; /** * List of all operations. * * @author Guillaume Nodet * @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_CHAR_SEARCH, VI_COLUMN, VI_COMPLETE, VI_DELETE, VI_DELETE_TO, 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_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, } src/main/java/jline/console/UserInterruptException.java000066400000000000000000000016301214622044400236310ustar00rootroot00000000000000/* * 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 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 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; } } src/main/java/jline/console/completer/000077500000000000000000000000001214622044400202465ustar00rootroot00000000000000src/main/java/jline/console/completer/AggregateCompleter.java000066400000000000000000000072051214622044400246560ustar00rootroot00000000000000/* * 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 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 jline.internal.Preconditions.checkNotNull; /** * Completer which contains multiple completers and aggregates them together. * * @author Jason Dillon * @since 2.3 */ public class AggregateCompleter implements Completer { private final List completers = new ArrayList(); 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 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 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 candidates) { // buffer could be null checkNotNull(candidates); List completions = new ArrayList(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 candidates; public int cursor; public Completion(final List candidates) { checkNotNull(candidates); this.candidates = new LinkedList(candidates); } public void complete(final Completer completer, final String buffer, final int cursor) { checkNotNull(completer); this.cursor = completer.complete(buffer, cursor, candidates); } } } src/main/java/jline/console/completer/ArgumentCompleter.java000066400000000000000000000324601214622044400245530ustar00rootroot00000000000000/* * 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 jline.console.completer; import 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 jline.internal.Preconditions.checkNotNull; /** * A {@link Completer} implementation that invokes a child completer using the appropriate separator argument. * This can be used instead of the individual completers having to know about argument parsing semantics. * * @author Marc Prud'hommeaux * @author Jason Dillon * @since 2.3 */ public class ArgumentCompleter implements Completer { private final ArgumentDelimiter delimiter; private final List completers = new ArrayList(); 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 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 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 getCompleters() { return completers; } public int complete(final String buffer, final int cursor, final List 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 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 subCandidates = new LinkedList(); 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 Marc Prud'hommeaux */ 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 Marc Prud'hommeaux */ public abstract static class AbstractArgumentDelimiter implements ArgumentDelimiter { // TODO: handle argument quoting and escape characters 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 args = new LinkedList(); StringBuilder arg = new StringBuilder(); int argpos = -1; int bindex = -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 ((i == buffer.length()) || isDelimiter(buffer, i)) { if (arg.length() > 0) { args.add(arg.toString()); arg.setLength(0); // reset the arg } } else { arg.append(buffer.charAt(i)); } } 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 isEscaped(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 - 1); // escape escape } } return false; } /** * 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 Marc Prud'hommeaux */ 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 Marc Prud'hommeaux */ 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; } } } src/main/java/jline/console/completer/CandidateListCompletionHandler.java000066400000000000000000000140471214622044400271570ustar00rootroot00000000000000/* * 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 jline.console.completer; import jline.console.ConsoleReader; import 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 * readline library. * * @author Marc Prud'hommeaux * @author Jason Dillon * @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 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 candidates) throws IOException { Set distinct = new HashSet(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 copy = new ArrayList(); 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 * foobar, foobaz, foobuz, the method will return foob. */ private String getUnambiguousCompletions(final List 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 candidates start with starts */ 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); } } } src/main/java/jline/console/completer/Completer.java000066400000000000000000000024721214622044400230500ustar00rootroot00000000000000/* * 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 jline.console.completer; import java.util.List; /** * A completer is the mechanism by which tab-completion candidates will be resolved. * * @author Marc Prud'hommeaux * @author Jason Dillon * @since 2.3 */ public interface Completer { // // FIXME: Check if we can use CharSequece for buffer? // /** * Populates candidates with a list of possible completions for the buffer. * * The candidates 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 buffer * @param candidates The {@link List} of candidates to populate * @return The index of the buffer for which the completion will be relative */ int complete(String buffer, int cursor, List candidates); } src/main/java/jline/console/completer/CompletionHandler.java000066400000000000000000000013641214622044400245240ustar00rootroot00000000000000/* * 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 jline.console.completer; import jline.console.ConsoleReader; import java.io.IOException; import java.util.List; /** * Handler for dealing with candidates for tab-completion. * * @author Marc Prud'hommeaux * @author Jason Dillon * @since 2.3 */ public interface CompletionHandler { boolean complete(ConsoleReader reader, List candidates, int position) throws IOException; } src/main/java/jline/console/completer/EnumCompleter.java000066400000000000000000000014041214622044400236670ustar00rootroot00000000000000/* * 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 jline.console.completer; import static jline.internal.Preconditions.checkNotNull; /** * {@link Completer} for {@link Enum} names. * * @author Jason Dillon * @since 2.3 */ public class EnumCompleter extends StringsCompleter { public EnumCompleter(Class source) { checkNotNull(source); for (Enum n : source.getEnumConstants()) { this.getStrings().add(n.name().toLowerCase()); } } }src/main/java/jline/console/completer/FileNameCompleter.java000066400000000000000000000074661214622044400244610ustar00rootroot00000000000000/* * 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 jline.console.completer; import jline.internal.Configuration; import java.io.File; import java.util.List; import static jline.internal.Preconditions.checkNotNull; /** * A file name completer takes the buffer and issues a list of * potential completions. *

* This completer tries to behave as similar as possible to * bash's file name completion (using GNU readline) * with the following exceptions: *

*

    *
  • Candidates that are directories will end with "/"
  • *
  • Wildcard regular expressions are not evaluated or replaced
  • *
  • 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
  • *
* * @author Marc Prud'hommeaux * @author Jason Dillon * @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 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 (!(translated.startsWith(separator()))) { 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 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; } } src/main/java/jline/console/completer/NullCompleter.java000066400000000000000000000013731214622044400237020ustar00rootroot00000000000000/* * 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 jline.console.completer; import java.util.List; /** * Null completer. * * @author Marc Prud'hommeaux * @author Jason Dillon * @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 candidates) { return -1; } }src/main/java/jline/console/completer/StringsCompleter.java000066400000000000000000000033351214622044400244210ustar00rootroot00000000000000/* * 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 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 jline.internal.Preconditions.checkNotNull; /** * Completer for a set of strings. * * @author Jason Dillon * @since 2.3 */ public class StringsCompleter implements Completer { private final SortedSet strings = new TreeSet(); public StringsCompleter() { // empty } public StringsCompleter(final Collection strings) { checkNotNull(strings); getStrings().addAll(strings); } public StringsCompleter(final String... strings) { this(Arrays.asList(strings)); } public Collection getStrings() { return strings; } public int complete(final String buffer, final int cursor, final List 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; } }src/main/java/jline/console/completer/package-info.java000066400000000000000000000005451214622044400234410ustar00rootroot00000000000000/* * 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 jline.console.completer;src/main/java/jline/console/history/000077500000000000000000000000001214622044400177555ustar00rootroot00000000000000src/main/java/jline/console/history/FileHistory.java000066400000000000000000000054131214622044400230640ustar00rootroot00000000000000/* * 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 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 jline.internal.Log; import static jline.internal.Preconditions.checkNotNull; /** * {@link History} using a file for persistent backing. *

* Implementers should install shutdown hook to call {@link FileHistory#flush} * to save history to disk. * * @author Jason Dillon * @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); } } }src/main/java/jline/console/history/History.java000066400000000000000000000037221214622044400222650ustar00rootroot00000000000000/* * 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 jline.console.history; import java.util.Iterator; import java.util.ListIterator; /** * Console history. * * @author Marc Prud'hommeaux * @author Jason Dillon * @since 2.3 */ public interface History extends Iterable { 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 entries(int index); ListIterator entries(); Iterator iterator(); // // Navigation // CharSequence current(); boolean previous(); boolean next(); boolean moveToFirst(); boolean moveToLast(); boolean moveTo(int index); void moveToEnd(); } src/main/java/jline/console/history/MemoryHistory.java000066400000000000000000000172731214622044400234640ustar00rootroot00000000000000/* * 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 jline.console.history; import java.util.Iterator; import java.util.LinkedList; import java.util.ListIterator; import java.util.NoSuchElementException; import static jline.internal.Preconditions.checkNotNull; /** * Non-persistent {@link History}. * * @author Marc Prud'hommeaux * @author Jason Dillon * @since 2.3 */ public class MemoryHistory implements History { public static final int DEFAULT_MAX_SIZE = 500; private final LinkedList items = new LinkedList(); 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 entries(final int index) { return new EntriesIterator(index - offset); } public ListIterator entries() { return entries(offset); } public Iterator 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 { private final ListIterator 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(); } } src/main/java/jline/console/history/PersistentHistory.java000066400000000000000000000014521214622044400243440ustar00rootroot00000000000000/* * 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 jline.console.history; import java.io.IOException; /** * Persistent {@link History}. * * @author Jason Dillon * @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; }src/main/java/jline/console/history/package-info.java000066400000000000000000000005411214622044400231440ustar00rootroot00000000000000/* * 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 jline.console.history;src/main/java/jline/console/internal/000077500000000000000000000000001214622044400200705ustar00rootroot00000000000000src/main/java/jline/console/internal/ConsoleReaderInputStream.java000066400000000000000000000063021214622044400256550ustar00rootroot00000000000000/* * 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 jline.console.internal; import 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 Marc Prud'hommeaux * @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 { private final ConsoleReader reader; private ConsoleLineInputStream next = null; private ConsoleLineInputStream prev = null; public ConsoleEnumeration(final ConsoleReader reader) { this.reader = reader; } public Object 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++); } } }src/main/java/jline/console/internal/ConsoleRunner.java000066400000000000000000000062571214622044400235410ustar00rootroot00000000000000/* * 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 jline.console.internal; import jline.console.ConsoleReader; import jline.console.completer.ArgumentCompleter; import jline.console.completer.Completer; import jline.console.history.FileHistory; import 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 Marc Prud'hommeaux * @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 argList = 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 completorList = new ArrayList(); 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", new Class[]{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() + " [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."); } }src/main/java/jline/console/package-info.java000066400000000000000000000005211214622044400214410ustar00rootroot00000000000000/* * 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 jline.console;src/main/java/jline/internal/000077500000000000000000000000001214622044400164265ustar00rootroot00000000000000src/main/java/jline/internal/Configuration.java000066400000000000000000000134301214622044400221010ustar00rootroot00000000000000/* * 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 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 jline.internal.Preconditions.checkNotNull; /** * Provides access to configuration values. * * @author Jason Dillon * @author Guillaume Nodet * @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 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"); } public static String getEncoding() { // LC_CTYPE is usually in the form en_US.UTF-8 String ctype = System.getenv("LC_CTYPE"); if (ctype != null && ctype.indexOf('.') > 0) { return ctype.substring(ctype.indexOf('.') + 1); } return System.getProperty("input.encoding", Charset.defaultCharset().name()); } }src/main/java/jline/internal/InputStreamReader.java000066400000000000000000000302461214622044400226740ustar00rootroot00000000000000/* * 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 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; } } } }src/main/java/jline/internal/Log.java000066400000000000000000000060561214622044400200210ustar00rootroot00000000000000/* * 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 jline.internal; import java.io.PrintStream; import static jline.internal.Preconditions.checkNotNull; /** * Internal logger. * * @author Jason Dillon * @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; iVERY IMPORTANT NOTES *

    *
  • This class is not thread safe. It expects at most one reader. *
  • The {@link #shutdown()} method must be called in order to shut down * the thread that handles blocking I/O. *
* @since 2.7 * @author Scott C. Gray */ 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 NonBlockingInputStream 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"); } } src/main/java/jline/internal/Nullable.java000066400000000000000000000011431214622044400210260ustar00rootroot00000000000000/* * 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 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 ""; } src/main/java/jline/internal/Preconditions.java000066400000000000000000000013021214622044400221050ustar00rootroot00000000000000/* * 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 jline.internal; // Some bits lifted from Guava's ( http://code.google.com/p/guava-libraries/ ) Preconditions. /** * Preconditions. * * @author Jason Dillon * @since 2.7 */ public class Preconditions { public static T checkNotNull(final T reference) { if (reference == null) { throw new NullPointerException(); } return reference; } }src/main/java/jline/internal/ShutdownHooks.java000066400000000000000000000066571214622044400221260ustar00rootroot00000000000000/* * 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 jline.internal; import java.util.ArrayList; import java.util.List; import static jline.internal.Preconditions.checkNotNull; /** * Manages the JLine shutdown-hook thread and tasks to execute on shutdown. * * @author Jason Dillon * @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 tasks = new ArrayList(); private static Thread hook; public static synchronized 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; } }src/main/java/jline/internal/TerminalLineSettings.java000066400000000000000000000150001214622044400233710ustar00rootroot00000000000000/* * 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 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 jline.internal.Preconditions.checkNotNull; /** * Provides access to terminal line settings via stty. * * @author Marc Prud'hommeaux * @author Dale Kemp * @author Jason Dillon * @author Jean-Baptiste Onofré * @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 long configLastFetched; public TerminalLineSettings() throws IOException, InterruptedException { sttyCommand = Configuration.getString(JLINE_STTY, DEFAULT_STTY); shCommand = Configuration.getString(JLINE_SH, DEFAULT_SH); 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("sane"); } public String get(final String args) throws IOException, InterruptedException { return stty(args); } public void set(final String args) throws IOException, InterruptedException { stty(args); } /** *

* Get the value of a stty property, including the management of a cache. *

* * @param name the stty property. * @return the stty property value. */ public int getProperty(String name) { checkNotNull(name); try { // tty properties are cached so we don't have to worry too much about getting term widht/height if (config == null || System.currentTimeMillis() - configLastFetched > 1000 ) { config = get("-a"); configLastFetched = System.currentTimeMillis(); } return this.getProperty(name, config); } catch (Exception e) { Log.warn("Failed to query stty ", name, e); return -1; } } /** *

* Parses a stty output (provided by stty -a) and return the value of a given property. *

* * @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 ("".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 } } } }src/main/java/jline/internal/TestAccessible.java000066400000000000000000000017301214622044400221670ustar00rootroot00000000000000/* * 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 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 }src/main/java/jline/internal/Urls.java000066400000000000000000000020761214622044400202230ustar00rootroot00000000000000/* * 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 jline.internal; import java.io.File; import java.net.MalformedURLException; import java.net.URL; /** * URL helpers. * * @author Jason Dillon * @author Guillaume Nodet * @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); } } }src/main/java/jline/internal/package-info.java000066400000000000000000000005231214622044400216150ustar00rootroot00000000000000/* * 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 jline.internal;src/main/java/jline/package-info.java000066400000000000000000000005011214622044400177750ustar00rootroot00000000000000/* * 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 jline;src/main/resources/000077500000000000000000000000001214622044400146025ustar00rootroot00000000000000src/main/resources/jline/000077500000000000000000000000001214622044400157035ustar00rootroot00000000000000src/main/resources/jline/console/000077500000000000000000000000001214622044400173455ustar00rootroot00000000000000src/main/resources/jline/console/completer/000077500000000000000000000000001214622044400213375ustar00rootroot00000000000000src/main/resources/jline/console/completer/CandidateListCompletionHandler.properties000066400000000000000000000006071214622044400315200ustar00rootroot00000000000000# # 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-- src/site/000077500000000000000000000000001214622044400126105ustar00rootroot00000000000000src/site/markdown/000077500000000000000000000000001214622044400144325ustar00rootroot00000000000000src/site/markdown/index.md000066400000000000000000000010761214622044400160670ustar00rootroot00000000000000 # Jline v2 JLine is a Java library for handling console input. It is similar in functionality to BSD editline and GNU readline. People familiar with the readline/editline capabilities for modern shells (such as bash and tcsh) will find most of the command editing features of JLine to be familiar.src/site/resources/000077500000000000000000000000001214622044400146225ustar00rootroot00000000000000src/site/resources/images/000077500000000000000000000000001214622044400160675ustar00rootroot00000000000000src/site/resources/images/jline.jpg000066400000000000000000000100311214622044400176650ustar00rootroot00000000000000ÿØÿàJFIFHHÿÛC   %# , #&')*)-0-(0%()(ÿÛC   (((((((((((((((((((((((((((((((((((((((((((((((((((ÿÀQé"ÿÄÿÄD !‘1AQSq"24a±RrsÁÃ#67t‚¡²áB³ðC£ÂÑÒÿÄÿÄ=!1¡AQabdq¢±áâ45‘Ñ"2r‚²#$Á3’ðÿÚ ?©$•±‹¸€_N?ê7ŠIr‘yuNÞÆ‰ÿ-%ÒªœOpÇ›{sú+ž’ãHÊ­Û7:ú3o¨‘¯8rr+W¤)üFñGHSøâ¢¤-;óÐÛè™p¬x=ÊÕé ¼QÒþ#x¨©ßž†ßDp¬x=ÊÕé ¼QÒþ#x¨©ßž†ßDp¬x=ÊÕé ¼QÒþ#x¨©ßž†ßDp¬x=ÊÕé ¼QÒþ#x¨©ßž†ßDp¬x=ÊÕé ¼QÒþ#x¨©ßž†ßDp¬x=ÊÕé ¼QÒþ#x¨©ßž†ßDp¬x=ÊÕé ¼QÒþ#x¨©ßž†ßDp¬x=ÊÕé ¼QÒþ#x¨©ßž†ßDp¬x=ÊÕé ¼QÒþ#x¨©ßž†ßDp¬x=ÊÕé ¼QÒþ#x¨©ßž†ßDp¬x=ÊÕé ¼QÒþ#x¨©ßž†ßDp¬x=ÊÕé ¼QÒþ#x¨©ßž†ßDp¬x=ÊÛŠvJ.Dz]'ö#ŽM]†ÇO#‰l &îüzéË]œÐW>š=ÊGG{Ø‘ôH~QþÙ‚yMùi4œ¼£ý³ò›òÒiV1/‰ww]$þSîþNB…X…û2†^Äs][ÄR)ÚâÐÖmÚJgRlaæ0d©œŸ¼)¬xL®h$€©•9mG †61η€;’• ·S±¹#a1TJ½×ù¥ÆiÀê°ãFç2IËu6ÿŠ×6,@ƒ} V•´u¥Í-- ¤_@:¾ËV…×e=›â8Ü-š¦©à;±žˆcÅýJ¢{ý²· "KipKä˺@lÈœGpû¥WÙ ´ÐºHªdôþi_U1S,:Úþmź›Ôlz‰UDúknàÙA.\ØšAn»ýÂÄ„,óÍZÊ:¹Ú— ïêh÷¨ñDé]˜ÁršVVÃEžwY¡gBìp}™ãµÑ6I¦,¾û5‚ßÕkóNR­ËmÕcݤXß­K›š&ºÖ UÐÖÔ xÃ:®¶ä¹ä! ³!B„!B„!B„!BœœŸ}Iþñß4øH~O¾¤ÿxïš|+¤_v/Ÿë>"OÔ|Ò#”¶`žS~ZM'/(ÿlÁ<¦ü´šUœKâ]Ýä^É?”Ãû¿“„!AV%Ðä|z\ ‰ìu ‘Ú^ÞÍýªªÀ+]‡Å0·¤.£2t–Øöü¬ös!~NIÿHVL%åÐN¢¹>[@È«Úæ g4Ûs¥}í/›ɸ•e#´T2=1ºÞ©q ¿ÂêN¨žZ™ß5D¯–g›¹ïq.qï$ªonÓ³üGÎ1ÿqª`P±wÕ­â·ÝX2&G$–Ò]kó4mL’æššâ×Á5¬‚S t½¸€°T¼ ¡e{#¥¦ÍÎ6Î.$÷q©^¹6 šª¬BvArò.÷{ÓS+lyÕlŽlNGÊNÿHîà—Óa¯¡äØ+6/•”øt¦´½ã_ÿ`•Ìš7ºÌ‘Ž=ÁÀ¯^öÆÝOsZÞòlŸöÉ©YƒHÜ=¬Ž¤Zò:·®KÙ;ª±=x‹Œi°¨|ƒ„ÒÀþT±¹sôÆGGýËØ6ú-ÊM»¬–lš9=Iï'¾Õí”á&—@…··rOm)?+×FsO5ôß°ŽÅª¯ 03ti¸Sp<¬n#8¦™™®:¬n72äW„€.M‚ÅU8‚0CKäqÒÆ·Üdm™ÕãÚ*±bâü3©­ø-4”/©Ó¨r©øÞQÁ„Ú23žx¾ç‰q¢Õ,gù‚È"à‚=ɹWC’ðLa¸=NúC^æ²ìcPqÿ–í[lGfXf!Hé°ë1ÖÜX¥œ-r}ÈHÛ–SÂæÊrÖ»H#“”\iØ‘È[,ÁƒÔàx‹é*ÚC†ö»±ÁkR‡±Ñ¸µÚ¼SÔGSfˆÝ®Ò rr}õ'ûÇ|Óá!ù>ú“ýã¾ið®Q~Aظ-gÄIúšDröÌÊoËI¤ååí˜'”ß–“J³‰|K»¼‚ëÙ'ò˜wòrüïªoÒG$Óv²1{y¯Öè*#`tôòÄ×jcì+,SW5õuL‘î.7ïºjg쳇·*â˜úªw¿pß¹¤¦;ÕŠ÷üVUA–uf¶Á£s½­ÇkÛ_.Å/Kê´ß˜U†ÌMòõ?Ù Pp/sÑw9í|Uc³hN,t…ër=«N]8ØÀ:CÙZݹ~ïñ8¿Ýj˜•%·ÚÆA’ŸˆÕQ4q´y_ø©µBÅçŸuaÈv‘‡¸ž7Ÿ&­ÞHý­ þoÁW8_±EöB‘ò0Õ›¨@ì¿Ì*ç ö8þÈMpï†o™T¬¬ù´ß·øµ#ùGûf å7å¤ÒurŽ…æL`†™XO¼é#ûJJ¤˜ÿ%ÝÞAtL’ á1[¥üŠË.â/ÂñŠj˜Éxk‡x;—ÆSʲf¬YÒú|ÄCK;¯ÚGüì^WÐ,nJ"ugÑ'-±Ò>E!:ÈQ*q¨1k)ZßøÚë`Ø=ÇWÕWZ¯é¸\2ž²Ð—\¢ÿfèŒoö=wSƒõBáùEƒú5Cüc±éÕwÿ±sìùœ©!¨*E[ Kzãp?ÕWä<@b42XohR$æÐ¿ÊʦÙ#HË”÷ú¡B ÜÜ9Óü»kETNInŸªØmG›Éx•e#´T5¬u¯¤¹Á·ø^é ’sÎ+‡ã mmlõ0LlDÏ/-=àž¤äÛ—îÿó‹ýÖ©Ž7ϺùÆüÂÅ\¯mdmF¥zÀé —ª‘íÃ;O†‚6«;«´1Ê?Ô.•œ¢¡hÀ¨%·¥ô Ûû‹ÿ¥ßd2NOª Ê/ön‡øÆÿcÔêï‡b¯dïÌàíI¬ƒ‚tÖkˆH5Gó¼þ ¯Âpøè¨MÍìS¾Å¤˜üöÔlB¦RîY£@Ër/ûžìJs&¼ãô¶YN™ó W~ÖÖSJé[U;æ ²ÚuÚ÷ßÖ›:£¨¤Â™c‰u»R›h™«6a¢¶™õÅ”¦W:šÐFG6NárÛ›uoî\ØÚ6i‰»±rÖýÌ_ü¨-¬§§{šÖIÓÿ®¬rà8¦)O²ÎÇ1£ð鵆ŽFói¿"`ò„¢¦Ž–‚¥šDæbÁnÖ–’¨ &¶xÞ`Äó‘ËŠÖ>¥Ñ‚¨†ß¸Ö%U²‰f.ݺÕÓ'¨E@ÈœðífàÜiú“ýã¾iðüŸ}Iþñß4øV˜¿ ì\j³â$ýGÍ+¶Ï”ª3 hê)¥ÐêPÿCEõêÓÛ}Þ¯õI'å u²Š+‹õêÿ ¾’&È,ðX:>ŸÂo¢J(%q{ÛryÊcI”8"$³F¡ffübúÒŸc˜-vmS4ê7µº“fºµT‰à¹¶ ޵’*hâõ’Ëe!­ £RS,®•æGk&üžIPݖгÂÆ0:á­30Ê6QS2& ‹/×dYdÑ`°ù#³žnyÒ“lÙ_Çæ‚hjí;NŠqîãÖâëù¤™~PÇ[!kh®;õ…^Ë ${AXz>ŸÂoJ%q{ÛryÏÝ7¥Ê,F’! Y£PÍoÙ v_« Å…v Û;¨ nP”ìæâk{‚ò*xâõËe%Œ hkE€J§žJ‰ ²›¸ë+ŸÍù~ŸÃßOS^Ó¼ Ø÷¥»d•FòIÍ_ÕÔlŸ–F°ci7#JË*f†6<†ž M¾‹ŒÊy>—¢Ñ 6îK9voTÜÅ-Dóš—Ë3¥sù½äÜî¹Oû,|Ë5_Hº^Cœ4KÔ5sBÇǬ ó?¿ü8 Ш#‹ê‹-FÐrôY‹–’f“I¤uµÃ¨…Ô€¼"âÅzsC…Ž¥ª9Ä‘›¤*I³¬Xb §suB.ð-p ¢²VpÌ&\,ZÐìÒÄ]¨°_ÉfkCE€ZáŒØÅ‚•]ˆTb j]œ@·ÑÝeÎçÌcÙr®Ä·nç{nûM³¼M˜Ì1È5CõiµÕH[qb¢Å«V%‡ÁÞ$pÒ`Äê©à},O³¬hÓ²ÿEø2Õ¢Ã"ˆíh E´ì¶3.êk–È× #pÒá}öø‘ñ]sC…ˆºØö´µÚŠ Ï‚F˳¸*mÊGÂsrè4ØÞ°¨œ?WÑX×eö)"Õ _Éf °° ÌQ6&æ0h[k+f­”Í9»€òry¿(RcÑ;OeÂàé¶;DÚ®rA¨ºûӦȲ÷š/u HðÜËèäSþ|Ùœ±¹“aŽhc§šõžûßðKÓ”qÞsOз_¯WøUô°²Ag´‡£éü&ðQd¡‚G¹ºO9N)²¥‰°Ã%š5 7ý„®ØÞ\©ÁéÜjXZçãñM›/˜¡dBÌh %”  3Þ^â÷k+Ô! +Ê„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„/ÿÙsrc/site/site.xml000066400000000000000000000031511214622044400142760ustar00rootroot00000000000000 jline http://jline.github.com/jline2/images/jline.jpg http://jline.github.com org.apache.maven.skins maven-fluido-skin 1.3.0 false true jline/jline2 right darkblue src/test/000077500000000000000000000000001214622044400126235ustar00rootroot00000000000000src/test/java/000077500000000000000000000000001214622044400135445ustar00rootroot00000000000000src/test/java/jline/000077500000000000000000000000001214622044400146455ustar00rootroot00000000000000src/test/java/jline/TerminalFactoryTest.java000066400000000000000000000027721214622044400214630ustar00rootroot00000000000000/* * 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 jline; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; /** * Tests for the {@link TerminalFactory}. */ public class TerminalFactoryTest { @Before public void setUp() throws Exception { TerminalFactory.reset(); } @After public void tearDown() throws Exception { TerminalFactory.reset(); } @Test public void testConfigureNone() { try { TerminalFactory.configure(TerminalFactory.NONE); Terminal t = TerminalFactory.get(); assertNotNull(t); assertEquals(UnsupportedTerminal.class.getName(), t.getClass().getName()); } finally { TerminalFactory.configure(TerminalFactory.AUTO); } } @Test public void testConfigureUnsupportedTerminal() { try { TerminalFactory.configure(UnsupportedTerminal.class.getName()); Terminal t = TerminalFactory.get(); assertNotNull(t); assertEquals(UnsupportedTerminal.class.getName(), t.getClass().getName()); } finally { TerminalFactory.configure(TerminalFactory.AUTO); } } }src/test/java/jline/console/000077500000000000000000000000001214622044400163075ustar00rootroot00000000000000src/test/java/jline/console/ConsoleReaderTest.java000066400000000000000000000556661214622044400225610ustar00rootroot00000000000000/* * 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 jline.console; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.util.HashMap; import java.util.Map; import jline.TerminalFactory; import jline.WindowsTerminal; import jline.console.history.History; import jline.console.history.MemoryHistory; import jline.internal.Configuration; import org.junit.After; import org.junit.Before; import org.junit.Test; import static jline.console.ConsoleReaderTest.WindowsKey.*; import static org.junit.Assert.*; /** * Tests for the {@link ConsoleReader}. */ public class ConsoleReaderTest { private ByteArrayOutputStream output; @Before public void setUp() throws Exception { TerminalFactory.configure(TerminalFactory.AUTO); TerminalFactory.reset(); System.setProperty(Configuration.JLINE_CONFIGURATION, "/no-such-file"); System.setProperty(WindowsTerminal.DIRECT_CONSOLE, "false"); System.setProperty(ConsoleReader.JLINE_INPUTRC, "/no/such/file"); Configuration.reset(); } @After public void tearDown() throws Exception { TerminalFactory.get().restore(); TerminalFactory.reset(); } private void assertWindowsKeyBehavior(String expected, char[] input) throws Exception { StringBuilder buffer = new StringBuilder(); buffer.append(input); ConsoleReader reader = createConsole(buffer.toString()); assertNotNull(reader); String line = reader.readLine(); assertEquals(expected, line); } private ConsoleReader createConsole() throws Exception { return createConsole(""); } private ConsoleReader createConsole(String chars) throws Exception { return createConsole(chars.getBytes(Configuration.getEncoding())); } private ConsoleReader createConsole(byte[] bytes) throws Exception { return createConsole(null, bytes); } private ConsoleReader createConsole(String appName, byte[] bytes) throws Exception { InputStream in = new ByteArrayInputStream(bytes); output = new ByteArrayOutputStream(); ConsoleReader reader = new ConsoleReader(appName, in, output, null); reader.setHistory(createSeededHistory()); return reader; } private History createSeededHistory() { History history = new MemoryHistory(); history.add("dir"); history.add("cd c:\\"); history.add("mkdir monkey"); return history; } @Test public void testReadline() throws Exception { ConsoleReader consoleReader = createConsole("Sample String\r\n"); assertNotNull(consoleReader); String line = consoleReader.readLine(); assertEquals("Sample String", line); } @Test public void testReadlineWithUnicode() throws Exception { System.setProperty("input.encoding", "UTF-8"); ConsoleReader consoleReader = createConsole("\u6771\u00E9\u00E8\r\n"); assertNotNull(consoleReader); String line = consoleReader.readLine(); assertEquals("\u6771\u00E9\u00E8", line); } @Test public void testReadlineWithMask() throws Exception { ConsoleReader consoleReader = createConsole("Sample String\r\n"); assertNotNull(consoleReader); String line = consoleReader.readLine('*'); assertEquals("Sample String", line); assertEquals("*************", output.toString().trim()); } @Test public void testDeleteOnWindowsTerminal() throws Exception { // test only works on Windows if (!(TerminalFactory.get() instanceof WindowsTerminal)) { return; } char[] characters = new char[]{ 'S', 's', (char) SPECIAL_KEY_INDICATOR.code, (char) LEFT_ARROW_KEY.code, (char) SPECIAL_KEY_INDICATOR.code, (char) DELETE_KEY.code, '\r', 'n' }; assertWindowsKeyBehavior("S", characters); } @Test public void testNumpadDeleteOnWindowsTerminal() throws Exception { // test only works on Windows if (!(TerminalFactory.get() instanceof WindowsTerminal)) { return; } char[] characters = new char[]{ 'S', 's', (char) NUMPAD_KEY_INDICATOR.code, (char) LEFT_ARROW_KEY.code, (char) NUMPAD_KEY_INDICATOR.code, (char) DELETE_KEY.code, '\r', 'n' }; assertWindowsKeyBehavior("S", characters); } @Test public void testHomeKeyOnWindowsTerminal() throws Exception { // test only works on Windows if (!(TerminalFactory.get() instanceof WindowsTerminal)) { return; } char[] characters = new char[]{ 'S', 's', (char) SPECIAL_KEY_INDICATOR.code, (char) HOME_KEY.code, 'x', '\r', '\n' }; assertWindowsKeyBehavior("xSs", characters); } @Test public void testEndKeyOnWindowsTerminal() throws Exception { // test only works on Windows if (!(TerminalFactory.get() instanceof WindowsTerminal)) { return; } char[] characters = new char[]{ 'S', 's', (char) SPECIAL_KEY_INDICATOR.code, (char) HOME_KEY.code, 'x', (char) SPECIAL_KEY_INDICATOR.code, (char) END_KEY.code, 'j', '\r', '\n' }; assertWindowsKeyBehavior("xSsj", characters); } @Test public void testPageUpOnWindowsTerminal() throws Exception { // test only works on Windows if (!(TerminalFactory.get() instanceof WindowsTerminal)) { return; } char[] characters = new char[]{ (char) SPECIAL_KEY_INDICATOR.code, (char) PAGE_UP_KEY.code, '\r', '\n' }; assertWindowsKeyBehavior("dir", characters); } @Test public void testPageDownOnWindowsTerminal() throws Exception { // test only works on Windows if (!(TerminalFactory.get() instanceof WindowsTerminal)) { return; } char[] characters = new char[]{ (char) SPECIAL_KEY_INDICATOR.code, (char) PAGE_DOWN_KEY.code, '\r', '\n' }; assertWindowsKeyBehavior("mkdir monkey", characters); } @Test public void testEscapeOnWindowsTerminal() throws Exception { // test only works on Windows if (!(TerminalFactory.get() instanceof WindowsTerminal)) { return; } char[] characters = new char[]{ 's', 's', 's', (char) SPECIAL_KEY_INDICATOR.code, (char) ESCAPE_KEY.code, '\r', '\n' }; assertWindowsKeyBehavior("", characters); } @Test public void testInsertOnWindowsTerminal() throws Exception { // test only works on Windows if (!(TerminalFactory.get() instanceof WindowsTerminal)) { return; } char[] characters = new char[]{ 'o', 'p', 's', (char) SPECIAL_KEY_INDICATOR.code, (char) HOME_KEY.code, (char) SPECIAL_KEY_INDICATOR.code, (char) INSERT_KEY.code, 'o', 'o', 'p', 's', '\r', '\n' }; assertWindowsKeyBehavior("oops", characters); } @Test public void testExpansion() throws Exception { ConsoleReader reader = createConsole(); MemoryHistory history = new MemoryHistory(); history.setMaxSize(3); history.add("foo"); history.add("dir"); history.add("cd c:\\"); history.add("mkdir monkey"); reader.setHistory(history); assertEquals("echo a!", reader.expandEvents("echo a!")); assertEquals("mkdir monkey ; echo a!", reader.expandEvents("!! ; echo a!")); assertEquals("echo ! a", reader.expandEvents("echo ! a")); assertEquals("echo !\ta", reader.expandEvents("echo !\ta")); assertEquals("mkdir barey", reader.expandEvents("^monk^bar^")); assertEquals("mkdir barey", reader.expandEvents("^monk^bar")); assertEquals("a^monk^bar", reader.expandEvents("a^monk^bar")); assertEquals("mkdir monkey", reader.expandEvents("!!")); assertEquals("echo echo a", reader.expandEvents("echo !#a")); assertEquals("mkdir monkey", reader.expandEvents("!mk")); try { reader.expandEvents("!mz"); fail("expected IllegalArgumentException"); } catch (IllegalArgumentException e) { assertEquals("!mz: event not found", e.getMessage()); } assertEquals("mkdir monkey", reader.expandEvents("!?mo")); assertEquals("mkdir monkey", reader.expandEvents("!?mo?")); assertEquals("mkdir monkey", reader.expandEvents("!-1")); assertEquals("cd c:\\", reader.expandEvents("!-2")); assertEquals("cd c:\\", reader.expandEvents("!3")); assertEquals("mkdir monkey", reader.expandEvents("!4")); try { reader.expandEvents("!20"); fail("expected IllegalArgumentException"); } catch (IllegalArgumentException e) { assertEquals("!20: event not found", e.getMessage()); } try { reader.expandEvents("!-20"); fail("expected IllegalArgumentException"); } catch (IllegalArgumentException e) { assertEquals("!-20: event not found", e.getMessage()); } } @Test public void testNumericExpansions() throws Exception { ConsoleReader reader = createConsole(); MemoryHistory history = new MemoryHistory(); history.setMaxSize(3); // Seed history with three entries: // 1 history1 // 2 history2 // 3 history3 history.add("history1"); history.add("history2"); history.add("history3"); reader.setHistory(history); // Validate !n assertExpansionIllegalArgumentException(reader, "!0"); assertEquals("history1", reader.expandEvents("!1")); assertEquals("history2", reader.expandEvents("!2")); assertEquals("history3", reader.expandEvents("!3")); assertExpansionIllegalArgumentException(reader, "!4"); // Validate !-n assertExpansionIllegalArgumentException(reader, "!-0"); assertEquals("history3", reader.expandEvents("!-1")); assertEquals("history2", reader.expandEvents("!-2")); assertEquals("history1", reader.expandEvents("!-3")); assertExpansionIllegalArgumentException(reader, "!-4"); // Validate !! assertEquals("history3", reader.expandEvents("!!")); // Add two new entries. Because maxSize=3, history is: // 3 history3 // 4 history4 // 5 history5 history.add("history4"); history.add("history5"); // Validate !n assertExpansionIllegalArgumentException(reader, "!0"); assertExpansionIllegalArgumentException(reader, "!1"); assertExpansionIllegalArgumentException(reader, "!2"); assertEquals("history3", reader.expandEvents("!3")); assertEquals("history4", reader.expandEvents("!4")); assertEquals("history5", reader.expandEvents("!5")); assertExpansionIllegalArgumentException(reader, "!6"); // Validate !-n assertExpansionIllegalArgumentException(reader, "!-0"); assertEquals("history5", reader.expandEvents("!-1")); assertEquals("history4", reader.expandEvents("!-2")); assertEquals("history3", reader.expandEvents("!-3")); assertExpansionIllegalArgumentException(reader, "!-4"); // Validate !! assertEquals("history5", reader.expandEvents("!!")); } /** * Validates that an 'event not found' IllegalArgumentException is thrown * for the expansion event. */ protected void assertExpansionIllegalArgumentException(ConsoleReader reader, String event) throws Exception { try { reader.expandEvents(event); fail("Expected IllegalArgumentException for " + event); } catch (IllegalArgumentException e) { assertEquals(event + ": event not found", e.getMessage()); } } @Test public void testStoringHistory() throws Exception { ConsoleReader reader = createConsole("foo ! bar\r\n"); MemoryHistory history = new MemoryHistory(); reader.setHistory(history); reader.setExpandEvents(true); String line = reader.readLine(); assertEquals("foo ! bar", line); history.previous(); assertEquals("foo \\! bar", history.current()); reader = createConsole("cd c:\\docs\r\n"); history = new MemoryHistory(); reader.setHistory(history); reader.setExpandEvents(true); line = reader.readLine(); assertEquals("cd c:\\docs", line); history.previous(); assertEquals("cd c:\\docs", history.current()); } @Test public void testExpansionAndHistoryWithEscapes() throws Exception { /* * Tests the results of the ConsoleReader.readLine() call and the line * stored in history. For each input, it tests the with-expansion and * without-expansion case. */ ConsoleReader reader = null; // \! (escaped expansion v1) reader = createConsole("echo ab\\!ef", true, "cd"); assertReadLine("echo ab!ef", reader); assertHistory("echo ab\\!ef", reader); reader = createConsole("echo ab\\!ef", false, "cd"); assertReadLine("echo ab\\!ef", reader); assertHistory("echo ab\\!ef", reader); // \!\! (escaped expansion v2) reader = createConsole("echo ab\\!\\!ef", true, "cd"); assertReadLine("echo ab!!ef", reader); assertHistory("echo ab\\!\\!ef", reader); reader = createConsole("echo ab\\!\\!ef", false, "cd"); assertReadLine("echo ab\\!\\!ef", reader); assertHistory("echo ab\\!\\!ef", reader); // !! (expansion) reader = createConsole("echo ab!!ef", true, "cd"); assertReadLine("echo abcdef", reader); assertHistory("echo abcdef", reader); reader = createConsole("echo ab!!ef", false, "cd"); assertReadLine("echo ab!!ef", reader); assertHistory("echo ab!!ef", reader); // \G (backslash no expansion) reader = createConsole("echo abc\\Gdef", true, "cd"); assertReadLine("echo abc\\Gdef", reader); assertHistory("echo abc\\Gdef", reader); reader = createConsole("echo abc\\Gdef", false, "cd"); assertReadLine("echo abc\\Gdef", reader); assertHistory("echo abc\\Gdef", reader); // \^ (escaped expansion) reader = createConsole("\\^abc^def", true, "echo abc"); assertReadLine("^abc^def", reader); assertHistory("\\^abc^def", reader); reader = createConsole("\\^abc^def", false, "echo abc"); assertReadLine("\\^abc^def", reader); assertHistory("\\^abc^def", reader); // ^^ (expansion) reader = createConsole("^abc^def", true, "echo abc"); assertReadLine("echo def", reader); assertHistory("echo def", reader); reader = createConsole("^abc^def", false, "echo abc"); assertReadLine("^abc^def", reader); assertHistory("^abc^def", reader); } private ConsoleReader createConsole(String input, boolean expandEvents, String... historyItems) throws Exception { ConsoleReader consoleReader = createConsole(input + "\r\n"); MemoryHistory history = new MemoryHistory(); if (historyItems != null) { for (String historyItem : historyItems) { history.add(historyItem); } } consoleReader.setHistory(history); consoleReader.setExpandEvents(expandEvents); return consoleReader; } private void assertReadLine(String expected, ConsoleReader consoleReader) throws Exception { assertEquals(expected, consoleReader.readLine()); } private void assertHistory(String expected, ConsoleReader consoleReader) { History history = consoleReader.getHistory(); history.previous(); assertEquals(expected, history.current()); } @Test public void testStoringHistoryWithExpandEventsOff() throws Exception { ConsoleReader reader = createConsole("foo ! bar\r\n"); MemoryHistory history = new MemoryHistory(); reader.setHistory(history); reader.setExpandEvents(false); String line = reader.readLine(); assertEquals("foo ! bar", line); history.previous(); assertEquals("foo ! bar", history.current()); } @Test public void testMacro() throws Exception { ConsoleReader consoleReader = createConsole("\u0018(foo\u0018)\u0018e\r\n"); assertNotNull(consoleReader); String line = consoleReader.readLine(); assertEquals("foofoo", line); } @Test public void testInput() throws Exception { System.setProperty(ConsoleReader.JLINE_INPUTRC, getClass().getResource("/jline/internal/config1").toExternalForm()); try { ConsoleReader consoleReader = createConsole("\u0018(foo\u0018)\u0018e\r\n"); assertNotNull(consoleReader); assertEquals(Operation.UNIVERSAL_ARGUMENT, consoleReader.getKeys().getBound("" + ((char)('U' - 'A' + 1)))); assertEquals("Function Key \u2671", consoleReader.getKeys().getBound("\u001b[11~")); assertEquals(null, consoleReader.getKeys().getBound(((char)('X' - 'A' + 1)) + "q")); consoleReader = createConsole("bash", new byte[0]); assertNotNull(consoleReader); assertEquals("\u001bb\"\u001bf\"", consoleReader.getKeys().getBound(((char)('X' - 'A' + 1)) + "q")); } finally { System.clearProperty(ConsoleReader.JLINE_INPUTRC); } } @Test public void testInput2() throws Exception { System.setProperty(ConsoleReader.JLINE_INPUTRC, getClass().getResource("/jline/internal/config2").toExternalForm()); try { ConsoleReader consoleReader = createConsole("Bash", new byte[0]); assertNotNull(consoleReader); assertNotNull(consoleReader.getKeys().getBound("\u001b" + ((char)('V' - 'A' + 1)))); } finally { System.clearProperty(ConsoleReader.JLINE_INPUTRC); } } @Test public void testInputBadConfig() throws Exception { System.setProperty(ConsoleReader.JLINE_INPUTRC, getClass().getResource("/jline/internal/config-bad").toExternalForm()); try { ConsoleReader consoleReader = createConsole("Bash", new byte[0]); assertNotNull(consoleReader); assertEquals("\u001bb\"\u001bf\"", consoleReader.getKeys().getBound(((char)('X' - 'A' + 1)) + "q")); } finally { System.clearProperty(ConsoleReader.JLINE_INPUTRC); } } @Test public void testBell() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ConsoleReader consoleReader = new ConsoleReader(System.in, baos); assertFalse("default bell should be disabled", consoleReader.getBellEnabled()); consoleReader.beep(); assertEquals("out should not have received bell", 0, baos.toByteArray().length); consoleReader.setBellEnabled(true); assertTrue("bell should have been enabled", consoleReader.getBellEnabled()); consoleReader.beep(); assertEquals("out should have received bell", 1, baos.toByteArray().length); assertEquals("out should have received bell", ConsoleReader.KEYBOARD_BELL, baos.toByteArray()[0]); } /** * Windows keys. *

* Constants copied wincon.h. */ public static enum WindowsKey { /** * On windows terminals, this character indicates that a 'special' key has * been pressed. This means that a key such as an arrow key, or delete, or * home, etc. will be indicated by the next character. */ SPECIAL_KEY_INDICATOR(224), /** * On windows terminals, this character indicates that a special key on the * number pad has been pressed. */ NUMPAD_KEY_INDICATOR(0), /** * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR, * this character indicates an left arrow key press. */ LEFT_ARROW_KEY(75), /** * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR * this character indicates an * right arrow key press. */ RIGHT_ARROW_KEY(77), /** * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR * this character indicates an up * arrow key press. */ UP_ARROW_KEY(72), /** * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR * this character indicates an * down arrow key press. */ DOWN_ARROW_KEY(80), /** * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR * this character indicates that * the delete key was pressed. */ DELETE_KEY(83), /** * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR * this character indicates that * the home key was pressed. */ HOME_KEY(71), /** * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR * this character indicates that * the end key was pressed. */ END_KEY(79), /** * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR * this character indicates that * the page up key was pressed. */ PAGE_UP_KEY(73), /** * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR * this character indicates that * the page down key was pressed. */ PAGE_DOWN_KEY(81), /** * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR * this character indicates that * the insert key was pressed. */ INSERT_KEY(82), /** * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR, * this character indicates that the escape key was pressed. */ ESCAPE_KEY(0),; public final int code; WindowsKey(final int code) { this.code = code; } private static final Map codes; static { Map map = new HashMap(); for (WindowsKey key : WindowsKey.values()) { map.put(key.code, key); } codes = map; } public static WindowsKey valueOf(final int code) { return codes.get(code); } } } src/test/java/jline/console/ConsoleReaderTestSupport.java000066400000000000000000000156731214622044400241500ustar00rootroot00000000000000/* * 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 jline.console; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import jline.TerminalSupport; import org.junit.Before; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Provides support for console reader tests. */ public abstract class ConsoleReaderTestSupport { protected ConsoleReader console; protected ByteArrayOutputStream consoleOutputStream; @Before public void setUp() throws Exception { consoleOutputStream = new ByteArrayOutputStream(); console = new ConsoleReader(null, consoleOutputStream, new TerminalSupport(true) { }); console.setKeyMap(KeyMap.EMACS); } protected void assertConsoleOutputContains(Character c) { String output = consoleOutputStream.toString(); assertTrue(output.contains(c.toString())); } protected void assertBeeped() { assertConsoleOutputContains(ConsoleReader.KEYBOARD_BELL); } protected void assertBuffer(final String expected, final Buffer buffer) throws IOException { assertBuffer(expected, buffer, true); } protected void assertBuffer(final String expected, final Buffer buffer, final boolean clear) throws IOException { // clear current buffer, if any if (clear) { console.finishBuffer(); console.getHistory().clear(); } console.setInput(new ByteArrayInputStream(buffer.getBytes())); // run it through the reader String line; while ((line = console.readLine((String) null)) != null) { //System.err.println("Read line: " + line); } assertEquals(expected, console.getCursorBuffer().toString()); } protected void assertPosition(int pos, final Buffer buffer, final boolean clear) throws IOException { // clear current buffer, if any if (clear) { console.finishBuffer(); console.getHistory().clear(); } console.setInput(new ByteArrayInputStream(buffer.getBytes())); // run it through the reader String line; while ((line = console.readLine((String) null)) != null) { //System.err.println("Read line: " + line); } assertEquals(pos, console.getCursorPosition ()); } /** * This is used to check the contents of the last completed * line of input in the input buffer. * * @param expected The expected contents of the line. * @param buffer The buffer * @param clear If true, the current buffer of the console * is cleared. * @throws IOException */ protected void assertLine(final String expected, final Buffer buffer, final boolean clear) throws IOException { // clear current buffer, if any if (clear) { console.finishBuffer(); console.getHistory().clear(); } console.setInput(new ByteArrayInputStream(buffer.getBytes())); String line; String prevLine = null; while ((line = console.readLine((String) null)) != null) { prevLine = line; } assertEquals(expected, prevLine); } private String getKeyForAction(final Operation key) { switch (key) { case BACKWARD_WORD: return "\u001Bb"; case BEGINNING_OF_LINE: return "\033[H"; case END_OF_LINE: return "\u0005"; case UNIX_WORD_RUBOUT: return "\u0017"; case ACCEPT_LINE: return "\n"; case PREVIOUS_HISTORY: return "\033[A"; case NEXT_HISTORY: return "\033[B"; case BACKWARD_CHAR: return "\u0002"; case COMPLETE: return "\011"; case BACKWARD_DELETE_CHAR: return "\010"; case VI_EOF_MAYBE: return "\004"; } throw new IllegalArgumentException(key.toString()); } protected class Buffer { private final ByteArrayOutputStream out = new ByteArrayOutputStream(); public Buffer() { // nothing } public Buffer(final String str) { append(str); } public byte[] getBytes() { return out.toByteArray(); } public Buffer op(final Operation op) { return append(getKeyForAction(op)); } public Buffer ctrlA() { return append("\001"); } /** * Generate a CTRL-X sequence where 'X' is the control character * you wish to generate. * @param let The letter of the control character. Valid values are * 'A' through 'Z'. * @return The modified buffer. */ public Buffer ctrl(char let) { if (let < 'A' || let > 'Z') throw new RuntimeException("Cannot generate CTRL code for " + "char '" + let + "' (" + ((int)let) + ")"); int ch = (((int)let) - 'A') + 1; return append((char)ch); } public Buffer enter() { return ctrl('J'); } public Buffer CR() { return ctrl('M'); } public Buffer ctrlU() { return append("\025"); } public Buffer tab() { return op(Operation.COMPLETE); } public Buffer escape() { return append("\033"); } public Buffer back() { return op(Operation.BACKWARD_DELETE_CHAR); } public Buffer back(int n) { for (int i = 0; i < n; i++) op(Operation.BACKWARD_DELETE_CHAR); return this; } public Buffer left() { return append("\033[D"); } public Buffer left(int n) { for (int i = 0; i < n; i++) append("\033[D"); return this; } public Buffer right() { return append("\033[C"); } public Buffer right(int n) { for (int i = 0; i < n; i++) append("\033[C"); return this; } public Buffer up() { return append(getKeyForAction(Operation.PREVIOUS_HISTORY)); } public Buffer down() { return append("\033[B"); } public Buffer append(final String str) { for (byte b : str.getBytes()) { append(b); } return this; } public Buffer append(final int i) { out.write((byte) i); return this; } } } src/test/java/jline/console/EditLineTest.java000066400000000000000000000147571214622044400215250ustar00rootroot00000000000000/* * 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 jline.console; import org.junit.Test; import static jline.console.Operation.BACKWARD_WORD; import static jline.console.Operation.*; /** * Tests various features of editing lines. * * @author Marc Prud'hommeaux */ public class EditLineTest extends ConsoleReaderTestSupport { @Test public void testDeletePreviousWord() throws Exception { Buffer b = new Buffer("This is a test"); assertBuffer("This is a ", b = b.op(UNIX_WORD_RUBOUT)); assertBuffer("This is ", b = b.op(UNIX_WORD_RUBOUT)); assertBuffer("This ", b = b.op(UNIX_WORD_RUBOUT)); assertBuffer("", b = b.op(UNIX_WORD_RUBOUT)); assertBuffer("", b = b.op(UNIX_WORD_RUBOUT)); assertBuffer("", b = b.op(UNIX_WORD_RUBOUT)); } @Test public void testMoveToEnd() throws Exception { Buffer b = new Buffer("This is a test"); assertBuffer("This is a XtestX", new Buffer("This is a test").op(BACKWARD_WORD) .append('X') .op(END_OF_LINE) .append('X')); assertBuffer("This is Xa testX", new Buffer("This is a test").op(BACKWARD_WORD) .op(BACKWARD_WORD) .append('X') .op(END_OF_LINE) .append('X')); assertBuffer("This Xis a testX", new Buffer("This is a test").op(BACKWARD_WORD) .op(BACKWARD_WORD) .op(BACKWARD_WORD) .append('X') .op(END_OF_LINE) .append('X')); } @Test public void testPreviousWord() throws Exception { assertBuffer("This is a Xtest", new Buffer("This is a test").op(BACKWARD_WORD) .append('X')); assertBuffer("This is Xa test", new Buffer("This is a test").op(BACKWARD_WORD) .op(BACKWARD_WORD) .append('X')); assertBuffer("This Xis a test", new Buffer("This is a test").op(BACKWARD_WORD) .op(BACKWARD_WORD) .op(BACKWARD_WORD) .append('X')); assertBuffer("XThis is a test", new Buffer("This is a test").op(BACKWARD_WORD) .op(BACKWARD_WORD) .op(BACKWARD_WORD) .op(BACKWARD_WORD) .append('X')); assertBuffer("XThis is a test", new Buffer("This is a test").op(BACKWARD_WORD) .op(BACKWARD_WORD) .op(BACKWARD_WORD) .op(BACKWARD_WORD) .op(BACKWARD_WORD) .append('X')); assertBuffer("XThis is a test", new Buffer("This is a test").op(BACKWARD_WORD) .op(BACKWARD_WORD) .op(BACKWARD_WORD) .op(BACKWARD_WORD) .op(BACKWARD_WORD) .op(BACKWARD_WORD) .append('X')); } @Test public void testLineStart() throws Exception { assertBuffer("XThis is a test", new Buffer("This is a test").ctrlA().append('X')); assertBuffer("TXhis is a test", new Buffer("This is a test").ctrlA().right().append('X')); } @Test public void testClearLine() throws Exception { assertBuffer("", new Buffer("This is a test").ctrlU()); assertBuffer("t", new Buffer("This is a test").left().ctrlU()); assertBuffer("st", new Buffer("This is a test").left().left().ctrlU()); } @Test public void testRight() throws Exception { Buffer b = new Buffer("This is a test"); b = b.left().right().back(); assertBuffer("This is a tes", b); b = b.left().left().left().right().left().back(); assertBuffer("This is ates", b); b.append('X'); assertBuffer("This is aXtes", b); } @Test public void testLeft() throws Exception { Buffer b = new Buffer("This is a test"); b = b.left().left().left(); assertBuffer("This is a est", b = b.back()); assertBuffer("This is aest", b = b.back()); assertBuffer("This is est", b = b.back()); assertBuffer("This isest", b = b.back()); assertBuffer("This iest", b = b.back()); assertBuffer("This est", b = b.back()); assertBuffer("Thisest", b = b.back()); assertBuffer("Thiest", b = b.back()); assertBuffer("Thest", b = b.back()); assertBuffer("Test", b = b.back()); assertBuffer("est", b = b.back()); assertBuffer("est", b = b.back()); assertBuffer("est", b = b.back()); assertBuffer("est", b = b.back()); assertBuffer("est", b = b.back()); } @Test public void testBackspace() throws Exception { Buffer b = new Buffer("This is a test"); assertBuffer("This is a tes", b = b.back()); assertBuffer("This is a te", b = b.back()); assertBuffer("This is a t", b = b.back()); assertBuffer("This is a ", b = b.back()); assertBuffer("This is a", b = b.back()); assertBuffer("This is ", b = b.back()); assertBuffer("This is", b = b.back()); assertBuffer("This i", b = b.back()); assertBuffer("This ", b = b.back()); assertBuffer("This", b = b.back()); assertBuffer("Thi", b = b.back()); assertBuffer("Th", b = b.back()); assertBuffer("T", b = b.back()); assertBuffer("", b = b.back()); assertBuffer("", b = b.back()); assertBuffer("", b = b.back()); assertBuffer("", b = b.back()); assertBuffer("", b = b.back()); } @Test public void testBuffer() throws Exception { assertBuffer("This is a test", new Buffer("This is a test")); } @Test public void testAbortPartialBuffer() throws Exception { console.setBellEnabled(true); assertBuffer("", new Buffer("This is a test").ctrl('G')); assertConsoleOutputContains('\n'); assertBeeped(); consoleOutputStream.reset(); assertBuffer("", new Buffer("This is a test").op(BACKWARD_WORD) .op(BACKWARD_WORD) .ctrl('G')); assertConsoleOutputContains('\n'); assertBeeped(); } } src/test/java/jline/console/HistorySearchTest.java000066400000000000000000000120041214622044400225760ustar00rootroot00000000000000package jline.console; import jline.console.history.MemoryHistory; import org.junit.Before; import org.junit.Test; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class HistorySearchTest { private ConsoleReader reader; private ByteArrayOutputStream output; @Before public void setUp() throws Exception { InputStream in = new ByteArrayInputStream(new byte[]{}); output = new ByteArrayOutputStream(); reader = new ConsoleReader("test console reader", in, output, null); } private MemoryHistory setupHistory() { MemoryHistory history = new MemoryHistory(); history.setMaxSize(10); history.add("foo"); history.add("fiddle"); history.add("faddle"); reader.setHistory(history); return history; } @Test public void testReverseHistorySearch() throws Exception { MemoryHistory history = setupHistory(); String readLineResult; reader.setInput(new ByteArrayInputStream(new byte[]{KeyMap.CTRL_R, 'f', '\n'})); readLineResult = reader.readLine(); assertEquals("faddle", readLineResult); assertEquals(3, history.size()); reader.setInput(new ByteArrayInputStream(new byte[]{ KeyMap.CTRL_R, 'f', KeyMap.CTRL_R, KeyMap.CTRL_R, KeyMap.CTRL_R, KeyMap.CTRL_R, KeyMap.CTRL_R, '\n' })); readLineResult = reader.readLine(); assertEquals("foo", readLineResult); assertEquals(4, history.size()); reader.setInput(new ByteArrayInputStream(new byte[]{KeyMap.CTRL_R, 'f', KeyMap.CTRL_R, KeyMap.CTRL_R, '\n'})); readLineResult = reader.readLine(); assertEquals("fiddle", readLineResult); assertEquals(5, history.size()); } @Test public void testForwardHistorySearch() throws Exception { MemoryHistory history = setupHistory(); String readLineResult; reader.setInput(new ByteArrayInputStream(new byte[]{ KeyMap.CTRL_R, 'f', KeyMap.CTRL_R, KeyMap.CTRL_R, KeyMap.CTRL_S, '\n' })); readLineResult = reader.readLine(); assertEquals("fiddle", readLineResult); assertEquals(4, history.size()); reader.setInput(new ByteArrayInputStream(new byte[]{ KeyMap.CTRL_R, 'f', KeyMap.CTRL_R, KeyMap.CTRL_R, KeyMap.CTRL_R, KeyMap.CTRL_S, KeyMap.CTRL_S, '\n' })); readLineResult = reader.readLine(); assertEquals("faddle", readLineResult); assertEquals(5, history.size()); reader.setInput(new ByteArrayInputStream(new byte[]{ KeyMap.CTRL_R, 'f', KeyMap.CTRL_R, KeyMap.CTRL_R, KeyMap.CTRL_R, KeyMap.CTRL_R, KeyMap.CTRL_S, '\n' })); readLineResult = reader.readLine(); assertEquals("fiddle", readLineResult); assertEquals(6, history.size()); } @Test public void testSearchHistoryAfterHittingEnd() throws Exception { MemoryHistory history = setupHistory(); String readLineResult; reader.setInput(new ByteArrayInputStream(new byte[]{ KeyMap.CTRL_R, 'f', KeyMap.CTRL_R, KeyMap.CTRL_R, KeyMap.CTRL_R, KeyMap.CTRL_S, '\n' })); readLineResult = reader.readLine(); assertEquals("fiddle", readLineResult); assertEquals(4, history.size()); } @Test public void testSearchHistoryWithNoMatches() throws Exception { MemoryHistory history = setupHistory(); String readLineResult; reader.setInput(new ByteArrayInputStream(new byte[]{ 'x', KeyMap.CTRL_S, KeyMap.CTRL_S, '\n' })); readLineResult = reader.readLine(); assertEquals("", readLineResult); assertEquals(3, history.size()); } @Test public void testAbortingSearchRetainsCurrentBufferAndPrintsDetails() throws Exception { MemoryHistory history = setupHistory(); String readLineResult; reader.setInput(new ByteArrayInputStream(new byte[]{ 'f', KeyMap.CTRL_R, 'f', KeyMap.CTRL_G })); readLineResult = reader.readLine(); assertEquals(null, readLineResult); assertTrue(output.toString().contains("(reverse-i-search)`ff':")); assertEquals("ff", reader.getCursorBuffer().toString()); assertEquals(3, history.size()); } @Test public void testAbortingAfterSearchingPreviousLinesGivesBlank() throws Exception { MemoryHistory history = setupHistory(); String readLineResult; reader.setInput(new ByteArrayInputStream(new byte[]{ 'f', KeyMap.CTRL_R, 'f', '\n', 'f', 'o', 'o', KeyMap.CTRL_G })); readLineResult = reader.readLine(); assertEquals("", readLineResult); readLineResult = reader.readLine(); assertEquals(null, readLineResult); assertEquals("", reader.getCursorBuffer().toString()); assertEquals(3, history.size()); } } src/test/java/jline/console/ViMoveModeTest.java000066400000000000000000001144351214622044400220340ustar00rootroot00000000000000/* * 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 jline.console; import static jline.console.Operation.*; import jline.console.history.History; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertTrue; /** * Unit tests for the greatest keymap binding in the world! Vi!!!! * These tests are primarily intended to test "move-mode" in VI, but * as a necessary by-product they use quite a bit of insert mode as well. */ public class ViMoveModeTest extends ConsoleReaderTestSupport { /** * For all tests we will start out in insert/edit mode. */ @Before public void setUp() throws Exception { super.setUp(); } @Test public void testMoveLeft() throws Exception { /* * There are various keys that will move you left. */ testMoveLeft("\033[D"); /* Left arrow */ testMoveLeft("h"); /* h key */ testMoveLeft("\010"); /* CTRL-H */ } public void testMoveLeft(String left) throws Exception { /* * Move left */ console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("0123456789")) .escape() .append(left) .append(left) .append(left) .append("iX") .enter(); assertLine("012345X6789", b, true); /* * Move left - use digit arguments. */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("0123456789")) .escape() .append('3') .append(left) .append("iX") .enter(); assertLine("012345X6789", b, true); /* * Move left - use multi-digit arguments. */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("0123456789ABCDEFHIJLMNOPQRSTUVWXYZ")) .escape() .append("13") .append(left) .append("iX") .enter(); assertLine("0123456789ABCDEFHIJLXMNOPQRSTUVWXYZ", b, true); /* * Delete move left. */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("0123456789ABCDEFHIJLMNOPQRSTUVWXYZ")) .escape() .append("13d") .append(left) .enter(); assertLine("0123456789ABCDEFHIJLZ", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("0123456789ABCDEFHIJLMNOPQRSTUVWXYZ")) .escape() .append("d") .append(left) .append("d") .append(left) .enter(); assertLine("0123456789ABCDEFHIJLMNOPQRSTUVWZ", b, true); /* * Change move left */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("0123456789ABCDEFHIJLMNOPQRSTUVWXYZ")) .escape() .append("13c") .append(left) .append("_HI") .enter(); assertLine("0123456789ABCDEFHIJL_HIZ", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("word")) .escape() .append("c") .append(left) .append("X") .enter(); assertLine("woXd", b, true); /* * Yank left */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("word")) .escape() .append("3y") .append(left) .append("p") .enter(); assertLine("wordwor", b, true); } @Test public void testMoveRight() throws Exception { testMoveRight("\033[C"); /* right arrow */ testMoveRight("l"); /* "l" key */ testMoveRight(" "); /* space */ } public void testMoveRight(String right) throws Exception { /* * Move right */ console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("0123456789")) .escape() .append('0') // beginning of line .append(right) .append(right) .append(right) .append("iX") .enter(); assertLine("012X3456789", b, true); /* * Move right use digit arguments. */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("0123456789ABCDEFHIJK")) .escape() .append("012") .append(right) .append("iX") .enter(); assertLine("0123456789ABXCDEFHIJK", b, true); /* * Delete move right */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("a bunch of words")) .escape() .append("05d") .append(right) .enter(); assertLine("ch of words", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("a bunch of words")) .escape() .append("0d") .append(right) .append("d") .append(right) .enter(); assertLine("bunch of words", b, true); /* * Change move right */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("a bunch of words")) .escape() .append("010c") .append(right) .append("XXX") .enter(); assertLine("XXX words", b, true); /* * Yank move right */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("a bunch of words")) .escape() .append("010y") .append(right) .append("$p") .enter(); assertLine("a bunch of wordsa bunch of", b, true); } @Test public void testCtrlD() throws Exception { /* * According to bash behavior hitting ^D anywhere in a non-empty * line is just like hitting enter. First, test at the end of the line. * The escape() puts us in move mode. */ console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("abc")).escape().op(VI_EOF_MAYBE); assertLine("abc", b, true); /* * Since VI_EOF_MAYBE is acceptable in both move mode and insert * mode, make sure we are testing the right now. */ assertTrue(console.isKeyMap(KeyMap.VI_MOVE)); /* * Next, the middle of the line. */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("abc")).left().left().escape().op(VI_EOF_MAYBE); assertLine("abc", b, true); assertTrue(console.isKeyMap(KeyMap.VI_MOVE)); /* * Beginning of the line. */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("abc")).left().left().left().escape().op(VI_EOF_MAYBE); assertLine("abc", b, true); assertTrue(console.isKeyMap(KeyMap.VI_MOVE)); /* * Now, check the behavior of an empty buffer. This should cause * a null to be returned. I'll try it in two different ways. */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("abc")).back().back().back().escape().op(VI_EOF_MAYBE); assertLine(null, b, true); assertTrue(console.isKeyMap(KeyMap.VI_MOVE)); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("")).escape().op(VI_EOF_MAYBE); assertLine(null, b, true); assertTrue(console.isKeyMap(KeyMap.VI_MOVE)); } @Test public void testCtrlJ() throws Exception { /* * ENTER is CTRL-J. */ testEnter('J'); } @Test public void testCtrlK() throws Exception { /* * Ctrl-K should delete to end-of-line */ console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("This is a test")) .escape() .left().left().left().left() .ctrl('K') .enter(); assertLine("This is a", b, true); b = (new Buffer("hello")) .escape() .ctrl('K') .enter(); assertLine("hell", b, true); } @Test public void testCtrlL() throws Exception { /* * CTRL-L clears the screen. I can't test much but to make sure * that the cursor is where it is supposed to be. * * IMPORTANT NOTE: The CTRL-K is commented out below. Technically * it is a bug. What is supposed to happen is that the escape() * backs the cursor up one, so the CTRL-K is supposed to delete * the "o". With the CTRL-L involved, it doesn't. I suspect that * it has to do with the parsing of the stream of escape's coming * in and I'm not entirely sure it is easy to fix. Since this is * really an edge case I'm commenting it out, but I'm leaving this * comment here because it may be a sign of something lurking down the * road. */ console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("hello")) .escape() .ctrl ('L') // .ctrl ('K') .enter (); assertLine("hello", b, true); } @Test public void testCtrlM() throws Exception { testEnter('M'); } @Test public void testCtrlP_CtrlN() throws Exception { console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("line1")).enter() .append("line2").enter() .append("li") .escape() .ctrl('P') .ctrl('P') .enter(); assertLine("line1", b, false); console.getHistory ().clear (); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("line1")).enter() .append("line2").enter() .append("li") .escape() .ctrl('P') .ctrl('P') .ctrl('N') .enter(); assertLine("line2", b, false); /* * One last test. Make sure that when we move through history * that the cursor is moved to the front of the line. */ console.getHistory ().clear (); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aline")).enter() .append("bline").enter() .append("cli") .escape() .ctrl('P') .ctrl('P') .ctrl('N') .append ("iX") .enter(); assertLine("Xbline", b, false); } @Test public void testCtrlT() throws Exception { /* * Transpose every character exactly. */ console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("abcdef")) .escape() // Move mode .append('0') // Beginning of line .right() // Right one .ctrl('T') // Transpose .ctrl('T') .ctrl('T') .ctrl('T') .ctrl('T') .enter(); assertLine("bcdefa", b, false); /* * Cannot transpose the first character or the last character */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("abcdef")) .escape() // Move mode .append('0') // Beginning of line .ctrl('T') .ctrl('T') .append('$') // End of line .ctrl('T') .ctrl('T') .enter(); assertLine("abcdef", b, false); } @Test public void testCtrlU() throws Exception { /* * CTRL-U is "line discard", it deletes everything prior to the * current cursor position. */ console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("all work and no play")) .escape() // Move mode .left(3) // Left to the "p" in play .ctrl('U') // Line discard .enter(); assertLine("play", b, false); /* * Nothing happens at the beginning of the line */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("donkey punch")) .escape() // Move mode .append('0') // Beginning of the line .ctrl('U') // Line discard .enter(); assertLine("donkey punch", b, false); /* * End of the line leaves an empty buffer */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("rabid hamster")) .escape() // Move mode .right() // End of line .ctrl('U') // Line discard .enter(); assertLine("", b, false); } @Test public void testCtrlW() throws Exception { /* * CTRL-W is word rubout. It deletes to the beginning of the word * you are currently sitting in, or if you are one a break character * it deletes up to the beginning of the previous word. */ console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("oily rancid badgers")) .escape() .ctrl('W') .ctrl('W') .enter(); assertLine("oily s", b, false); /* * Test behavior with non-word characters. */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("pasty bulimic rats !!!!!")) .escape() .ctrl('W') .ctrl('W') .enter(); assertLine("pasty bulimic !", b, false); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("pasty bulimic rats !!!!!")) .escape() .append("2") .ctrl('W') .enter(); assertLine("pasty bulimic !", b, false); } @Test public void testInsertComment() throws Exception { /* * The # key causes a comment to get inserted. */ console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("putrified whales")) .escape() .append ("#"); assertLine("#putrified whales", b, false); assertTrue(console.isKeyMap(KeyMap.VI_INSERT)); } @Test public void testEndOfLine() throws Exception { /* * The $ key causes the cursor to move to the end of the line */ console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("chicken sushimi")) .escape() .left(10) .append("$a is tasty!") .enter(); assertLine("chicken sushimi is tasty!", b, false); assertTrue(console.isKeyMap(KeyMap.VI_INSERT)); /* * Delete to EOL */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("chicken sushimi")) .escape() .append("0lld$") .enter(); assertLine("ch", b, false); /* * Change to EOL */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("chicken sushimi")) .escape() .append("0llc$opsticks") .enter(); assertLine("chopsticks", b, false); /* * Yank to EOL */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("chicken sushimi")) .escape() .append("0lly$$p") .enter(); assertLine("chicken sushimiicken sushimi", b, false); } @Test public void testMatch() throws Exception { /* * The % character matches brackets (square, parens, or curly). * First, test close paren w/nesting */ console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("ab((cdef[[))")) .escape() // Move us back one character (on last close) .append("%aX") // Find match, add an X after it .enter(); assertLine("ab(X(cdef[[))", b, false); /* * Open paren, w/nesting */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("ab((cdef[[))")) .escape() // Move us back one character (on last close) .append('0') // Beginning of line .right(2) // Right to first open paren .append("%aX") // Match closing, add an X after it .enter(); assertLine("ab((cdef[[))X", b, false); /* * No match leaves the cursor in place */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("abcd))")) .escape() .append("%aX") .enter(); assertLine("abcd))X", b, false); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("(abcd(d")) .escape() .append("0%aX") // Beginning of line, match, append X .enter(); assertLine("(Xabcd(d", b, false); /* * Delete match */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("ab(def)hij")) .escape() .append("0lld%") .enter(); assertLine("abhij", b, false); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("ab(def)")) .escape() .append("0lld%") .enter(); assertLine("ab", b, false); /* * Yank match */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("ab(def)hij")) .escape() .append("0lly%$p") .enter(); assertLine("ab(def)hij(def)", b, false); /* * Change match */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("ab(def)hij")) .escape() .append("0llc%X") .enter(); assertLine("abXhij", b, false); } @Test public void testSearch() throws Exception { /* * Tests the "/" forward search */ History history = console.getHistory(); history.clear(); history.add("aaadef"); history.add("bbbdef"); history.add("cccdef"); /* * An aborted search should leave you exactly on the * character you were on of the original term. First, I will * test aborting by deleting back over the search expression. */ console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("I like frogs")) .escape() .left(4) // Cursor is on the "f" .append("/def") // Start a search .back(4) // Delete everything (aborts search) .append("ibig ") // Insert mode, type "big " .enter(); // Done assertLine("I like big frogs", b, false); /* * Next, hit escape to abort. This technically isn't readline * behavior, but I added it because I find it useful. */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("I like frogs")) .escape() .left(4) // Cursor is on the "f" .append("/def") // Start a search .escape() // Abort the search .append("ibig ") // Insert mode, type "big " .enter(); // Done assertLine("I like big frogs", b, false); /* * Test a failed history match. This is like an abort, but it * should leave the cursor at the start of the line. */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("I like frogs")) .escape() .left(4) // Cursor is on the "f" .append("/III") // Search (no match) .enter() // Kick it off. .append("iX") // With cursor at start, insert an X .enter(); assertLine("XI like frogs", b, false); /* * Test a valid search. */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("I like frogs")) .escape() .left(4) // Cursor is on the "f" .append("/def") // Search (no match) .enter() // Kick it off. .append("nNiX") // Move forward two, insert an X. Note I use // use "n" and "N" to move. .enter(); assertLine("Xcccdef", b, false); /* * The previous test messed with history. */ history.clear(); history.add("aaadef"); history.add("bbbdef"); history.add("cccdef"); /* * Search backwards */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("I like frogs")) .escape() .left(4) // Cursor is on the "f" .append("?def") // Search (no match) .enter() // Kick it off. .append("nNiX") // Move forward two, insert an X. Note I use // use "n" and "N" to move. .enter(); assertLine("Xaaadef", b, false); /* * Test bug fix: use CR to terminate seach instead of newline */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("abc")) .enter() .append("def") .enter() .append("hij") .enter() .escape() .append("/bc") .CR() .append("iX") .enter(); assertLine("Xabc", b, false); } @Test public void testWordRight() throws Exception { console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("buttery frog necks")) .escape() .append("0ww") // Beginning of line, nxt word, nxt word .ctrl('U') // Kill to beginning of line .enter(); // Kick it off. assertLine("necks", b, false); b = (new Buffer("buttery frog foo")) .escape() .left(5) .append('w') .ctrl('K') // Kill to end of line .enter(); // Kick it off. assertLine("buttery frog ", b, false); b = (new Buffer("a big batch of buttery frog livers")) .escape() .append("05w") // Beg of line, 5 words right .ctrl('U') // Kill to beginning of line .enter(); // Kick it off. assertLine("frog livers", b, false); /* * Delete word right */ b = (new Buffer("a big batch of buttery frog livers")) .escape() .append("05dw") .enter(); assertLine("frog livers", b, false); b = (new Buffer("another big batch of buttery frog livers")) .escape() .append("0ldw") .enter(); assertLine("abig batch of buttery frog livers", b, false); /* * Yank word right */ b = (new Buffer("big brown pickles")) .escape() .append("02yw$p") .enter(); assertLine("big brown picklesbig brown ", b, false); /* * Change word right */ b = (new Buffer("big brown pickles")) .escape() .append("0wcwgreen") .enter(); assertLine("big green pickles", b, false); b = (new Buffer("big brown pickles")) .escape() .append("02cwlittle bitty") .enter(); assertLine("little bitty pickles", b, false); } @Test public void testWordLeft() throws Exception { console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("lucious lark liquid ")) .escape() .append("bb") // Beginning of line, prv word, prv word .ctrl('K') // Kill to end of line .enter(); // Kick it off. assertLine("lucious ", b, false); b = (new Buffer("lucious lark liquid")) .escape() .left(2) .append('b') .ctrl('U') .enter(); assertLine("liquid", b, false); b = (new Buffer("lively lolling lark liquid")) .escape() .append("3b") .ctrl('K') // Kill to beginning of line .enter(); assertLine("lively ", b, false); } @Test public void testEndWord() throws Exception { console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("putrid pidgen porridge")) .escape() .append("0e") .ctrl('K') // Kill to end of line .enter(); // Kick it off. assertLine("putri", b, false); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer(" putrid pidgen porridge")) .escape() .append("0e") .ctrl('K') // Kill to end of line .enter(); // Kick it off. assertLine(" putri", b, false); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("putrid pidgen porridge and mash")) .escape() .append("05l") // Beg of line, 5 right .append("3e") // 3 end-of-word .ctrl('U') // Kill to beg of line .enter(); // Kick it off. assertLine("d mash", b, false); } @Test public void testInsertBeginningOfLine() throws Exception { console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("dessicated dog droppings")) .escape() .append("Itasty ") .enter(); assertLine("tasty dessicated dog droppings", b, false); } @Test public void testRubout() throws Exception { console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("gross animal stuff")) .escape() .left() .append("XXX") .enter(); assertLine("gross animal ff", b, false); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("gross animal stuff")) .escape() .left() .append("50X") .enter(); assertLine("ff", b, false); } @Test public void testDelete() throws Exception { console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("thing to delete")) .escape() .append("bbxxx") .enter(); assertLine("thing delete", b, false); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("thing to delete")) .escape() .append("bb99x") .enter(); assertLine("thing ", b, false); } @Test public void testChangeCase() throws Exception { console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("big.LITTLE")) .escape() .append("0~~~~~~~~~~") .enter(); assertLine("BIG.little", b, false); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("big.LITTLE")) .escape() .append("020~") .enter(); assertLine("BIG.little", b, false); } @Test public void testChangeChar() throws Exception { console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("abcdefhij")) .escape() .append("0rXiY") .enter(); assertLine("YXbcdefhij", b, false); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("abcdefhij")) .escape() .append("04rXiY") .enter(); assertLine("XXXYXefhij", b, false); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("abcdefhij")) .escape() .append("099rZ") .enter(); assertLine("ZZZZZZZZZ", b, false); /* * Aborted replace. */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("abcdefhij")) .escape() .append("0r") .escape() .append("iX") .enter(); assertLine("Xabcdefhij", b, false); } @Test public void testCharSearch_f() throws Exception { /* * f = search forward for character */ console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("aaaafaaaafaaaafaaaaf")) .escape() .append("03ffiX") // start, find the third f, insert X .enter(); assertLine("aaaafaaaafaaaaXfaaaaf", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaafaaaafaaaafaaaaf")) .escape() .append("0ffffffiX") // start, find the third f, insert X .enter(); assertLine("aaaafaaaafaaaaXfaaaaf", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaafaaaafaaaafaaaaf")) .escape() .append("0ff;;iX") // start, find f, repeat fwd, repeat fwd .enter(); assertLine("aaaafaaaafaaaaXfaaaaf", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaafaaaafaaaafaaaaf")) .escape() .append("0ff;,iX") // start, find f, repeat fwd, repeat back .enter(); assertLine("aaaaXfaaaafaaaafaaaaf", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaaXaaaaXaaaaXaaaaX")) .escape() .append("0fX3;iY") // start, find X, repeat fwd x 3, ins Y .enter(); assertLine("aaaaXaaaaXaaaaXaaaaYX", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaafaaaafaaaafaaaaf")) .escape() .append("03dff") // start, delete to third f .enter(); assertLine("aaaaf", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaaXaaaaXaaaaXaaaaX")) .escape() .append("0fX2d;") // start, find X, 2 x delete repeat last search .enter(); assertLine("aaaaaaaaX", b, true); } @Test public void testCharSearch_F() throws Exception { /* * f = search forward for character */ console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("aaaafaaaafaaaafaaaaf")) .escape() .append("3FfiX") // go 3 f's back, insert X .enter(); assertLine("aaaaXfaaaafaaaafaaaaf", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaafaaaafaaaafaaaaf")) .escape() .append("FfFfFfiX") // start, find the third f back, insert X .enter(); assertLine("aaaaXfaaaafaaaafaaaaf", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaafaaaafaaaafaaaaf")) .escape() .append("Ff;iX") // start, find f, repeat fwd, repeat fwd .enter(); assertLine("aaaafaaaaXfaaaafaaaaf", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaafaaaafaaaafaaaaf")) .escape() .append("Ff;,iX") // start, rev find f, repeat, reverse .enter(); assertLine("aaaafaaaafaaaaXfaaaaf", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaaXaaaaXaaaaXaaaaX")) .escape() .append("FX2;iY") // start, rev find X, repeat x 2, ins Y .enter(); assertLine("aaaaYXaaaaXaaaaXaaaaX", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaafaaaafaaaafaaaaf")) .escape() .append("3dFf") // start, delete back to third f .enter(); assertLine("aaaaf", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaaXaaaaXaaaaXaaaaX")) .escape() .append("FX2d;") // start, find X, 2 x delete repeat last search .enter(); assertLine("aaaaXaaaaX", b, true); } @Test public void testCharSearch_t() throws Exception { /* * r = search forward for character, stopping before */ console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("aaaafaaaafaaaafaaaaf")) .escape() .append("03tfiX") .enter(); assertLine("aaaafaaaafaaaXafaaaaf", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaafaaaafaaaafaaaaf")) .escape() .append("0tftftfiX") .enter(); assertLine("aaaXafaaaafaaaafaaaaf", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaafaaaafaaaafaaaaf")) .escape() .append("0tf;;iX") .enter(); assertLine("aaaXafaaaafaaaafaaaaf", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaafaaaafaaaafaaaaf")) .escape() .append("02tf;,iX") .enter(); assertLine("aaaafXaaaafaaaafaaaaf", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaaXaaaaXaaaaXaaaaX")) .escape() .append("0tX3;iY") .enter(); assertLine("aaaaXaaaaXaaaYaXaaaaX", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaafaaaafaaaafaaaaf")) .escape() .append("03dtf") .enter(); assertLine("faaaaf", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaaXaaaaXaaaaXaaaaX")) .escape() .append("0tX2d;") .enter(); assertLine("aaaXaaaaXaaaaX", b, true); } @Test public void testCharSearch_T() throws Exception { /* * r = search backward for character, stopping after */ console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("aaaafaaaafaaaafaaaaf")) .escape() .append("3TfiX") .enter(); assertLine("aaaafXaaaafaaaafaaaaf", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaafaaaafaaaafaaaaf")) .escape() .append("TfTfTfiX") .enter(); assertLine("aaaafaaaafaaaafXaaaaf", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaafaaaafaaaafaaaaf")) .escape() .append("Tf;;iX") .enter(); assertLine("aaaafaaaafaaaafXaaaaf", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaafaaaafaaaafaaaaf")) .escape() .append("2Tf;,iX") .enter(); assertLine("aaaafaaaafaaaXafaaaaf", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaaXaaaaXaaaaXaaaaX")) .escape() .append("TX3;iY") .enter(); assertLine("aaaaXYaaaaXaaaaXaaaaX", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaafaaaafaaaafaaaaf")) .escape() .append("3dTf") .enter(); assertLine("aaaaff", b, true); console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("aaaaXaaaaXaaaaXaaaaX")) .escape() .append("TX2d;") .enter(); assertLine("aaaaXaaaaXaaaaX", b, true); } @Test public void test_dd() throws Exception { /* * This tests "dd" or delete-to + delete-to, which should kill the * current line. */ console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("abcdef")) .escape() .append("dd") .enter(); assertLine("", b, true); /* * I found a bug here dd didn't work at position 0. This tests the fix. */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("abcdef")) .escape() .append("0dd") .enter(); assertLine("", b, true); } @Test public void test_yy() throws Exception { /* * This tests "yy" or yank-to + yank-to, which should yank the whole line */ console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("abcdef")) .escape() .append("yyp") .enter(); assertLine("abcdefabcdef", b, true); } @Test public void test_cc() throws Exception { /* * This tests "cc" or change-to + change-to, which changes the whole line */ console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("abcdef")) .escape() .append("ccsuck") .enter(); assertLine("suck", b, true); } /** * Used to test various forms of hitting "enter" (return). This can be * CTRL-J or CTRL-M...maybe others. * * @param enterChar The escape character that acts as enter. * @throws Exception */ private void testEnter(char enterChar) throws Exception { /* * I want to test to make sure that I am re-entering insert mode * when enter is hit. */ console.setKeyMap(KeyMap.VI_INSERT); Buffer b = (new Buffer("abc")).escape().ctrl(enterChar); assertLine("abc", b, true); assertTrue(console.isKeyMap(KeyMap.VI_INSERT)); /* * This sort of tests the same thing by actually enter * characters after the first enter. */ console.setKeyMap(KeyMap.VI_INSERT); b = (new Buffer("abc")).escape().ctrl(enterChar) .append("def").enter(); assertLine("def", b, true); assertTrue(console.isKeyMap(KeyMap.VI_INSERT)); } /* * TODO - Test arrow key bindings */ } src/test/java/jline/console/completer/000077500000000000000000000000001214622044400203015ustar00rootroot00000000000000src/test/java/jline/console/completer/ArgumentCompleterTest.java000066400000000000000000000027361214622044400254510ustar00rootroot00000000000000/* * 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 jline.console.completer; import jline.console.ConsoleReaderTestSupport; import jline.console.completer.ArgumentCompleter; import jline.console.completer.StringsCompleter; import org.junit.Test; /** * Tests for {@link jline.console.completer.ArgumentCompleter}. * * @author Marc Prud'hommeaux */ public class ArgumentCompleterTest extends ConsoleReaderTestSupport { @Test public void test1() throws Exception { console.addCompleter(new ArgumentCompleter(new StringsCompleter("foo", "bar", "baz"))); assertBuffer("foo foo ", new Buffer("foo f").tab()); assertBuffer("foo ba", new Buffer("foo b").tab()); assertBuffer("foo ba", new Buffer("foo ba").tab()); assertBuffer("foo baz ", new Buffer("foo baz").tab()); // test completion in the mid range assertBuffer("foo baz", new Buffer("f baz").left().left().left().left().tab()); assertBuffer("ba foo", new Buffer("b foo").left().left().left().left().tab()); assertBuffer("foo ba baz", new Buffer("foo b baz").left().left().left().left().tab()); assertBuffer("foo foo baz", new Buffer("foo f baz").left().left().left().left().tab()); } }src/test/java/jline/console/completer/NullCompleterTest.java000066400000000000000000000015511214622044400245730ustar00rootroot00000000000000/* * 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 jline.console.completer; import jline.console.ConsoleReaderTestSupport; import jline.console.completer.NullCompleter; import org.junit.Test; /** * Tests for {@link NullCompleter}. * * @author Jason Dillon */ public class NullCompleterTest extends ConsoleReaderTestSupport { @Test public void test1() throws Exception { console.addCompleter(NullCompleter.INSTANCE); assertBuffer("f", new Buffer("f").tab()); assertBuffer("ba", new Buffer("ba").tab()); assertBuffer("baz", new Buffer("baz").tab()); } }src/test/java/jline/console/completer/StringsCompleterTest.java000066400000000000000000000020141214622044400253050ustar00rootroot00000000000000/* * 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 jline.console.completer; import jline.console.ConsoleReaderTestSupport; import jline.console.completer.StringsCompleter; import org.junit.Test; /** * Tests for {@link jline.console.completer.StringsCompleter}. * * @author Marc Prud'hommeaux */ public class StringsCompleterTest extends ConsoleReaderTestSupport { @Test public void test1() throws Exception { console.addCompleter(new StringsCompleter("foo", "bar", "baz")); assertBuffer("foo ", new Buffer("f").tab()); // single tab completes to unambiguous "ba" assertBuffer("ba", new Buffer("b").tab()); assertBuffer("ba", new Buffer("ba").tab()); assertBuffer("baz ", new Buffer("baz").tab()); } }src/test/java/jline/console/history/000077500000000000000000000000001214622044400200105ustar00rootroot00000000000000src/test/java/jline/console/history/HistoryTest.java000066400000000000000000000062041214622044400231560ustar00rootroot00000000000000/* * 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 jline.console.history; import jline.console.ConsoleReaderTestSupport; import org.junit.Test; import static jline.console.Operation.*; /** * Tests command history. * * @author Marc Prud'hommeaux */ public class HistoryTest extends ConsoleReaderTestSupport { @Test public void testSingleHistory() throws Exception { Buffer b = new Buffer(). append("test line 1").op(ACCEPT_LINE). append("test line 2").op(ACCEPT_LINE). append("test line 3").op(ACCEPT_LINE). append("test line 4").op(ACCEPT_LINE). append("test line 5").op(ACCEPT_LINE). append(""); assertBuffer("", b); assertBuffer("test line 5", b = b.op(PREVIOUS_HISTORY)); assertBuffer("test line 5", b = b.op(BACKWARD_CHAR)); assertBuffer("test line 4", b = b.op(PREVIOUS_HISTORY)); assertBuffer("test line 5", b = b.op(NEXT_HISTORY)); assertBuffer("test line 4", b = b.op(PREVIOUS_HISTORY)); assertBuffer("test line 3", b = b.op(PREVIOUS_HISTORY)); assertBuffer("test line 2", b = b.op(PREVIOUS_HISTORY)); assertBuffer("test line 1", b = b.op(PREVIOUS_HISTORY)); // beginning of history assertBuffer("test line 1", b = b.op(PREVIOUS_HISTORY)); assertBuffer("test line 1", b = b.op(PREVIOUS_HISTORY)); assertBuffer("test line 1", b = b.op(PREVIOUS_HISTORY)); assertBuffer("test line 1", b = b.op(PREVIOUS_HISTORY)); assertBuffer("test line 2", b = b.op(NEXT_HISTORY)); assertBuffer("test line 3", b = b.op(NEXT_HISTORY)); assertBuffer("test line 4", b = b.op(NEXT_HISTORY)); assertBuffer("test line 5", b = b.op(NEXT_HISTORY)); // end of history assertBuffer("", b = b.op(NEXT_HISTORY)); assertBuffer("", b = b.op(NEXT_HISTORY)); assertBuffer("", b = b.op(NEXT_HISTORY)); assertBuffer("test line 5", b = b.op(PREVIOUS_HISTORY)); assertBuffer("test line 4", b = b.op(PREVIOUS_HISTORY)); b = b.op(BEGINNING_OF_LINE).append("XXX").op(ACCEPT_LINE); assertBuffer("XXXtest line 4", b = b.op(PREVIOUS_HISTORY)); assertBuffer("test line 5", b = b.op(PREVIOUS_HISTORY)); assertBuffer("test line 4", b = b.op(PREVIOUS_HISTORY)); assertBuffer("test line 5", b = b.op(NEXT_HISTORY)); assertBuffer("XXXtest line 4", b = b.op(NEXT_HISTORY)); assertBuffer("", b = b.op(NEXT_HISTORY)); assertBuffer("XXXtest line 4", b = b.op(PREVIOUS_HISTORY)); assertBuffer("XXXtest line 4", b = b.op(ACCEPT_LINE).op(PREVIOUS_HISTORY)); assertBuffer("XXXtest line 4", b = b.op(ACCEPT_LINE).op(PREVIOUS_HISTORY)); assertBuffer("XXXtest line 4", b = b.op(ACCEPT_LINE).op(PREVIOUS_HISTORY)); assertBuffer("XXXtest line 4", b = b.op(ACCEPT_LINE).op(PREVIOUS_HISTORY)); } } src/test/java/jline/console/history/MemoryHistoryTest.java000066400000000000000000000056771214622044400243640ustar00rootroot00000000000000/* * 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 jline.console.history; import org.junit.After; import org.junit.Before; import org.junit.Test; import static junit.framework.Assert.*; /** * Tests for {@link MemoryHistory}. * * @author Marc Prud'hommeaux */ public class MemoryHistoryTest { private MemoryHistory history; @Before public void setUp() { history = new MemoryHistory(); } @After public void tearDown() { history = null; } @Test public void testAdd() { assertEquals(0, history.size()); history.add("test"); assertEquals(1, history.size()); assertEquals("test", history.get(0)); assertEquals(1, history.index()); } private void assertHistoryContains(final int offset, final String... items) { assertEquals(items.length, history.size()); int i=0; for (History.Entry entry : history) { assertEquals(offset + i, entry.index()); assertEquals(items[i++], entry.value()); } } @Test public void testOffset() { history.setMaxSize(5); assertEquals(0, history.size()); assertEquals(0, history.index()); history.add("a"); history.add("b"); history.add("c"); history.add("d"); history.add("e"); assertEquals(5, history.size()); assertEquals(5, history.index()); assertHistoryContains(0, "a", "b", "c", "d", "e"); history.add("f"); assertEquals(5, history.size()); assertEquals(6, history.index()); assertHistoryContains(1, "b", "c", "d", "e", "f"); assertEquals("f", history.get(5)); } @Test public void testReplace() { assertEquals(0, history.size()); history.add("a"); history.add("b"); history.replace("c"); assertHistoryContains(0, "a", "c"); } @Test public void testSet() { history.add("a"); history.add("b"); history.add("c"); history.set(1, "d"); assertHistoryContains(0, "a", "d", "c"); } @Test public void testRemove() { history.add("a"); history.add("b"); history.add("c"); history.remove(1); assertHistoryContains(0, "a", "c"); } @Test public void testRemoveFirst() { history.add("a"); history.add("b"); history.add("c"); history.removeFirst(); assertHistoryContains(0, "b", "c"); } @Test public void testRemoveLast() { history.add("a"); history.add("b"); history.add("c"); history.removeLast(); assertHistoryContains(0, "a", "b"); } } src/test/java/jline/example/000077500000000000000000000000001214622044400163005ustar00rootroot00000000000000src/test/java/jline/example/Example.java000066400000000000000000000075661214622044400205540ustar00rootroot00000000000000/* * 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 jline.example; import java.io.IOException; import java.io.PrintWriter; import java.util.LinkedList; import java.util.List; import jline.console.ConsoleReader; import jline.console.completer.Completer; import jline.console.completer.FileNameCompleter; import jline.console.completer.StringsCompleter; public class Example { public static void usage() { System.out.println("Usage: java " + Example.class.getName() + " [none/simple/files/dictionary [trigger mask]]"); System.out.println(" none - no completors"); System.out.println(" simple - a simple completor that comples " + "\"foo\", \"bar\", and \"baz\""); System.out .println(" files - a completor that comples " + "file names"); System.out.println(" classes - a completor that comples " + "java class names"); System.out .println(" trigger - a special word which causes it to assume " + "the next line is a password"); System.out.println(" mask - is the character to print in place of " + "the actual password character"); System.out.println(" color - colored prompt and feedback"); System.out.println("\n E.g - java Example simple su '*'\n" + "will use the simple compleator with 'su' triggering\n" + "the use of '*' as a password mask."); } public static void main(String[] args) throws IOException { try { Character mask = null; String trigger = null; boolean color = false; ConsoleReader reader = new ConsoleReader(); reader.setPrompt("prompt> "); if ((args == null) || (args.length == 0)) { usage(); return; } List completors = new LinkedList(); if (args.length > 0) { if (args[0].equals("none")) { } else if (args[0].equals("files")) { completors.add(new FileNameCompleter()); } else if (args[0].equals("simple")) { completors.add(new StringsCompleter("foo", "bar", "baz")); } else if (args[0].equals("color")) { color = true; reader.setPrompt("\u001B[1mfoo\u001B[0m@bar\u001B[32m@baz\u001B[0m> "); } else { usage(); return; } } if (args.length == 3) { mask = args[2].charAt(0); trigger = args[1]; } for (Completer c : completors) { reader.addCompleter(c); } String line; PrintWriter out = new PrintWriter(reader.getOutput()); while ((line = reader.readLine()) != null) { if (color){ out.println("\u001B[33m======>\u001B[0m\"" + line + "\""); } else { out.println("======>\"" + line + "\""); } out.flush(); // If we input the special word then we will mask // the next line. if ((trigger != null) && (line.compareTo(trigger) == 0)) { line = reader.readLine("password> ", mask); } if (line.equalsIgnoreCase("quit") || line.equalsIgnoreCase("exit")) { break; } } } catch (Throwable t) { t.printStackTrace(); } } } src/test/java/jline/example/PasswordReader.java000066400000000000000000000017521214622044400220750ustar00rootroot00000000000000/* * 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 jline.example; import jline.console.ConsoleReader; import java.io.IOException; public class PasswordReader { public static void usage() { System.out.println("Usage: java " + PasswordReader.class.getName() + " [mask]"); } public static void main(String[] args) throws IOException { ConsoleReader reader = new ConsoleReader(); Character mask = (args.length == 0) ? new Character((char) 0) : new Character(args[0].charAt(0)); String line; do { line = reader.readLine("Enter password> ", mask); System.out.println("Got password: " + line); } while (line != null && line.length() > 0); } } src/test/java/jline/internal/000077500000000000000000000000001214622044400164615ustar00rootroot00000000000000src/test/java/jline/internal/ConfigurationTest.java000066400000000000000000000035631214622044400230020ustar00rootroot00000000000000/* * 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 jline.internal; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; /** * Tests for {@link Configuration}. */ public class ConfigurationTest { @Test public void initFromSystemProperty() { System.setProperty(Configuration.JLINE_CONFIGURATION, getClass().getResource("jlinerc1").toExternalForm()); Configuration.reset(); String value = Configuration.getString("a"); assertEquals("b", value); } @Test public void getBooleanFromSystemProperty() { System.setProperty("test", "false"); boolean value = Configuration.getBoolean("test", true); assertEquals(false, value); } @Test public void getIntegerFromSystemProperty() { System.setProperty("test", "1234"); int value = Configuration.getInteger("test", 5678); assertEquals(1234, value); } @Test public void getIntegerUsingDefault() { System.getProperties().remove("test"); int value = Configuration.getInteger("test", 1234); assertEquals(1234, value); } @Test public void resetReconfigures() { System.setProperty(Configuration.JLINE_CONFIGURATION, getClass().getResource("jlinerc1").toExternalForm()); Configuration.reset(); String value1 = Configuration.getString("a"); assertEquals("b", value1); System.setProperty(Configuration.JLINE_CONFIGURATION, getClass().getResource("jlinerc2").toExternalForm()); Configuration.reset(); String value2 = Configuration.getString("c"); assertEquals("d", value2); } }src/test/java/jline/internal/LogTest.java000066400000000000000000000030661214622044400207120ustar00rootroot00000000000000/* * 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 jline.internal; import jline.internal.Log.Level; import org.junit.Before; import org.junit.Test; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import static org.junit.Assert.assertEquals; /** * Tests for {@link Log}. */ public class LogTest { private ByteArrayOutputStream buff; private PrintStream out; @Before public void setUp() throws Exception { buff = new ByteArrayOutputStream(); out = new PrintStream(buff); Log.setOutput(out); } @Test public void renderArray() { Log.render(out, new Object[]{"a", 1, "2", "b"}); assertEquals("[a,1,2,b]", buff.toString()); } @Test public void renderArrayWithThrowable() { Log.render(out, new Object[]{"a", 1, "2", "b", new Throwable("TEST")}); assertEquals("[a,1,2,b,java.lang.Throwable: TEST]", buff.toString()); } @Test public void renderThrowable() { Log.render(out, new Throwable("TEST")); System.out.println(buff); } @Test public void logSimple() { Log.log(Level.DEBUG, "a", 1, "2", "b"); System.out.println(buff); } @Test public void logWithThrowable() { Log.log(Level.DEBUG, "a", 1, "2", "b", new Throwable("TEST")); System.out.println(buff); } }src/test/java/jline/internal/TerminalLineSettingsTest.java000066400000000000000000000200141214622044400242650ustar00rootroot00000000000000/* * 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 jline.internal; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; /** * Tests for the {@link TerminalLineSettings}. * * @author Jean-Baptiste Onofré */ public class TerminalLineSettingsTest { private TerminalLineSettings settings; private final String linuxSttySample = "speed 38400 baud; rows 85; columns 244; line = 0;\n" + "intr = ^C; quit = ^\\; erase = ^?; kill = ^U; eof = ^D; eol = M-^?; eol2 = M-^?; swtch = M-^?; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;\n" + "-parenb -parodd cs8 hupcl -cstopb cread -clocal -crtscts\n" + "-ignbrk brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc ixany imaxbel iutf8\n" + "opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0\n" + "isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke"; private final String solarisSttySample = "speed 38400 baud; \n" + "rows = 85; columns = 244; ypixels = 0; xpixels = 0;\n" + "csdata ?\n" + "eucw 1:0:0:0, scrw 1:0:0:0\n" + "intr = ^c; quit = ^\\; erase = ^?; kill = ^u;\n" + "eof = ^d; eol = -^?; eol2 = -^?; swtch = ;\n" + "start = ^q; stop = ^s; susp = ^z; dsusp = ^y;\n" + "rprnt = ^r; flush = ^o; werase = ^w; lnext = ^v;\n" + "-parenb -parodd cs8 -cstopb -hupcl cread -clocal -loblk -crtscts -crtsxoff -parext \n" + "-ignbrk brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl -iuclc \n" + "ixon ixany -ixoff imaxbel \n" + "isig icanon -xcase echo echoe echok -echonl -noflsh \n" + "-tostop echoctl -echoprt echoke -defecho -flusho -pendin iexten \n" + "opost -olcuc onlcr -ocrnl -onocr -onlret -ofill -ofdel tab3"; private final String aixSttySample = "speed 38400 baud; 85 rows; 244 columns;\n" + "eucw 1:1:0:0, scrw 1:1:0:0:\n" + "intr = ^C; quit = ^\\; erase = ^?; kill = ^U; eof = ^D; eol = \n" + "eol2 = ; start = ^Q; stop = ^S; susp = ^Z; dsusp = ^Y; reprint = ^R\n" + "discard = ^O; werase = ^W; lnext = ^V\n" + "-parenb -parodd cs8 -cstopb -hupcl cread -clocal -parext \n" + "-ignbrk brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl -iuclc \n" + "ixon ixany -ixoff imaxbel \n" + "isig icanon -xcase echo echoe echok -echonl -noflsh \n" + "-tostop echoctl -echoprt echoke -flusho -pending iexten \n" + "opost -olcuc onlcr -ocrnl -onocr -onlret -ofill -ofdel tab3"; private final String macOsSttySample = "speed 9600 baud; 47 rows; 155 columns;\n" + "lflags: icanon isig iexten echo echoe -echok echoke -echonl echoctl\n" + "-echoprt -altwerase -noflsh -tostop -flusho pendin -nokerninfo\n" + "-extproc\n" + "iflags: -istrip icrnl -inlcr -igncr ixon -ixoff ixany imaxbel iutf8\n" + "-ignbrk brkint -inpck -ignpar -parmrk\n" + "oflags: opost onlcr -oxtabs -onocr -onlret\n" + "cflags: cread cs8 -parenb -parodd hupcl -clocal -cstopb -crtscts -dsrflow\n" + "-dtrflow -mdmbuf\n" + "cchars: discard = ^O; dsusp = ^Y; eof = ^D; eol = ;\n" + "eol2 = ; erase = ^?; intr = ^C; kill = ^U; lnext = ^V;\n" + "min = 1; quit = ^\\; reprint = ^R; start = ^Q; status = ^T;\n" + "stop = ^S; susp = ^Z; time = 0; werase = ^W;"; private final String netBsdSttySample = "speed 38400 baud; 85 rows; 244 columns;\n" + "lflags: icanon isig iexten echo echoe echok echoke -echonl echoctl\n" + " -echoprt -altwerase -noflsh -tostop -flusho pendin -nokerninfo\n" + " -extproc\n" + "iflags: -istrip icrnl -inlcr -igncr ixon -ixoff ixany imaxbel -ignbrk\n" + " brkint -inpck -ignpar -parmrk\n" + "oflags: opost onlcr -ocrnl oxtabs onocr onlret\n" + "cflags: cread cs8 -parenb -parodd hupcl -clocal -cstopb -crtscts -mdmbuf\n" + " -cdtrcts\n" + "cchars: discard = ^O; dsusp = ^Y; eof = ^D; eol = ;\n" + " eol2 = ; erase = ^?; intr = ^C; kill = ^U; lnext = ^V;\n" + " min = 1; quit = ^\\; reprint = ^R; start = ^Q; status = ^T;\n" + " stop = ^S; susp = ^Z; time = 0; werase = ^W;"; private final String freeBsdSttySample = "speed 9600 baud; 32 rows; 199 columns;\n" + "lflags: icanon isig iexten echo echoe echok echoke -echonl echoctl\n" + " -echoprt -altwerase -noflsh -tostop -flusho -pendin -nokerninfo\n" + " -extproc\n" + "iflags: -istrip icrnl -inlcr -igncr ixon -ixoff ixany imaxbel -ignbrk\n" + " brkint -inpck -ignpar -parmrk\n" + "oflags: opost onlcr -ocrnl tab0 -onocr -onlret\n" + "cflags: cread cs8 -parenb -parodd hupcl -clocal -cstopb -crtscts -dsrflow\n" + " -dtrflow -mdmbuf\n" + "cchars: discard = ^O; dsusp = ^Y; eof = ^D; eol = ;\n" + " eol2 = ; erase = ^?; erase2 = ^H; intr = ^C; kill = ^U;\n" + " lnext = ^V; min = 1; quit = ^\\; reprint = ^R; start = ^Q;\n" + " status = ^T; stop = ^S; susp = ^Z; time = 0; werase = ^W;"; @Before public void setUp() throws Exception { } @Test public void testGetConfig() throws Exception { if (!Configuration.getOsName().contains("win")) { TerminalLineSettings settings = new TerminalLineSettings(); String config = settings.getConfig(); System.out.println(config); } } @Test public void testLinuxSttyParsing() { assertEquals(0x7f, TerminalLineSettings.getProperty("erase", linuxSttySample)); assertEquals(244, TerminalLineSettings.getProperty("columns", linuxSttySample)); assertEquals(85, TerminalLineSettings.getProperty("rows", linuxSttySample)); } @Test public void testSolarisSttyParsing() { assertEquals(0x7f, TerminalLineSettings.getProperty("erase", solarisSttySample)); assertEquals(244, TerminalLineSettings.getProperty("columns", solarisSttySample)); assertEquals(85, TerminalLineSettings.getProperty("rows", solarisSttySample)); } @Test public void testAixSttyParsing() { assertEquals(0x7f, TerminalLineSettings.getProperty("erase", aixSttySample)); assertEquals(244, TerminalLineSettings.getProperty("columns", aixSttySample)); assertEquals(85, TerminalLineSettings.getProperty("rows", aixSttySample)); } @Test public void testMacOsSttyParsing() { assertEquals(0x7f, TerminalLineSettings.getProperty("erase", macOsSttySample)); assertEquals(155, TerminalLineSettings.getProperty("columns", macOsSttySample)); assertEquals(47, TerminalLineSettings.getProperty("rows", macOsSttySample)); } @Test public void testNetBsdSttyParsing() { assertEquals(0x7f, TerminalLineSettings.getProperty("erase", netBsdSttySample)); assertEquals(244, TerminalLineSettings.getProperty("columns", netBsdSttySample)); assertEquals(85, TerminalLineSettings.getProperty("rows", netBsdSttySample)); } @Test public void testFreeBsdSttyParsing() { assertEquals(0x7f, TerminalLineSettings.getProperty("erase", freeBsdSttySample)); assertEquals(199, TerminalLineSettings.getProperty("columns", freeBsdSttySample)); assertEquals(32, TerminalLineSettings.getProperty("rows", freeBsdSttySample)); } }src/test/java/jline/readline/000077500000000000000000000000001214622044400164305ustar00rootroot00000000000000src/test/java/jline/readline/KeyMapTest.java000066400000000000000000000021321214622044400213170ustar00rootroot00000000000000/* * 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 jline.readline; import jline.console.KeyMap; import jline.console.Operation; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class KeyMapTest { @Test public void testBound() throws Exception { KeyMap map = KeyMap.emacs(); assertEquals( Operation.COMPLETE, map.getBound("\u001B" + KeyMap.CTRL_OB) ); assertEquals( Operation.BACKWARD_WORD, map.getBound(KeyMap.ESCAPE + "b") ); map.bindIfNotBound("\033[0A", Operation.PREVIOUS_HISTORY); assertEquals( Operation.PREVIOUS_HISTORY, map.getBound("\033[0A") ); map.bind( "\033[0AB", Operation.NEXT_HISTORY ); assertTrue( map.getBound("\033[0A") instanceof KeyMap ); assertEquals( Operation.NEXT_HISTORY , map.getBound("\033[0AB") ); } } src/test/resources/000077500000000000000000000000001214622044400146355ustar00rootroot00000000000000src/test/resources/jline/000077500000000000000000000000001214622044400157365ustar00rootroot00000000000000src/test/resources/jline/emtpy-config000066400000000000000000000000001214622044400202500ustar00rootroot00000000000000src/test/resources/jline/internal/000077500000000000000000000000001214622044400175525ustar00rootroot00000000000000src/test/resources/jline/internal/config-bad000066400000000000000000000001601214622044400214630ustar00rootroot00000000000000# Don't bomb on unsupported operations "\C-fake": some-fake-operation "\C-bad: nothing "\C-xq": "\eb\"\ef\"" src/test/resources/jline/internal/config1000066400000000000000000000003701214622044400210230ustar00rootroot00000000000000Control-u: universal-argument Meta-Rubout: backward-kill-word Control-o: "> output" "\C-u": universal-argument "\C-x\C-r": re-read-init-file "\e[11~": "Function Key \u2671" $if Bash # Quote the current or previous word "\C-xq": "\eb\"\ef\"" $endif src/test/resources/jline/internal/config2000066400000000000000000000045641214622044400210350ustar00rootroot00000000000000 # This file controls the behaviour of line input editing for # programs that use the Gnu Readline library. Existing programs # include FTP, Bash, and Gdb. # # You can re-read the inputrc file with C-x C-r. # Lines beginning with '#' are comments. # # First, include any systemwide bindings and variable assignments from # /etc/Inputrc $include /etc/Inputrc # # Set various bindings for emacs mode. set editing-mode emacs $if mode=emacs Meta-Control-h: backward-kill-word Text after the function name is ignored # # Arrow keys in keypad mode # #"\M-OD": backward-char #"\M-OC": forward-char #"\M-OA": previous-history #"\M-OB": next-history # # Arrow keys in ANSI mode # "\M-[D": backward-char "\M-[C": forward-char "\M-[A": previous-history "\M-[B": next-history # # Arrow keys in 8 bit keypad mode # #"\M-\C-OD": backward-char #"\M-\C-OC": forward-char #"\M-\C-OA": previous-history #"\M-\C-OB": next-history # # Arrow keys in 8 bit ANSI mode # #"\M-\C-[D": backward-char #"\M-\C-[C": forward-char #"\M-\C-[A": previous-history #"\M-\C-[B": next-history C-q: quoted-insert $endif # An old-style binding. This happens to be the default. TAB: complete # Macros that are convenient for shell interaction $if Bash # edit the path "\C-xp": "PATH=${PATH}\e\C-e\C-a\ef\C-f" # prepare to type a quoted word -- insert open and close double quotes # and move to just after the open quote "\C-x\"": "\"\"\C-b" # insert a backslash (testing backslash escapes in sequences and macros) "\C-x\\": "\\" # Quote the current or previous word "\C-xq": "\eb\"\ef\"" # Add a binding to refresh the line, which is unbound "\C-xr": redraw-current-line # Edit variable on current line. "\M-\C-v": "\C-a\C-k$\C-y\M-\C-e\C-a\C-y=" $endif # use a visible bell if one is available set bell-style visible # don't strip characters to 7 bits when reading set input-meta on # allow iso-latin1 characters to be inserted rather than converted to # prefix-meta sequences set convert-meta off # display characters with the eighth bit set directly rather than # as meta-prefixed characters set output-meta on # if there are more than 150 possible completions for a word, ask the # user if he wants to see all of them set completion-query-items 150 # For FTP $if Ftp "\C-xg": "get \M-?" "\C-xt": "put \M-?" "\M-.": yank-last-arg $endifsrc/test/resources/jline/internal/jlinerc1000066400000000000000000000000031214622044400211750ustar00rootroot00000000000000a=bsrc/test/resources/jline/internal/jlinerc2000066400000000000000000000000031214622044400211760ustar00rootroot00000000000000c=d