diff options
54 files changed, 7636 insertions, 0 deletions
diff --git a/src/jline/LICENSE.txt b/src/jline/LICENSE.txt new file mode 100644 index 0000000000..1cdc44c211 --- /dev/null +++ b/src/jline/LICENSE.txt @@ -0,0 +1,33 @@ +Copyright (c) 2002-2006, Marc Prud'hommeaux <mwp1@cornell.edu> +All rights reserved. + +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. + diff --git a/src/jline/pom.xml b/src/jline/pom.xml new file mode 100644 index 0000000000..25356ff432 --- /dev/null +++ b/src/jline/pom.xml @@ -0,0 +1,221 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +To build, you need to have Maven 2 installed. + +To compile, run: + + mvn compile + +To run tests, run: + + mvn test + +To run one particular test, e.g. TestSomeTest, run: + + mvn test -Dtest=TestSomeTest + +To build the jars, run: + + mvn package + +To create and upload a release, run: + + mvn deploy + +To build the site and upload it, run: + + mvn site:deploy + +To perform a complete release, run: + + mvn clean compile package site assembly:assembly deploy site:deploy + +To actually upload the artifact to sourceforge, it must be manually ftp'd: + + lftp ftp://upload.sourceforge.net/incoming/ -e "put `ls target/jline-*.zip`" + +To make a bundle and request that ibilio upload it, do: + + mvn source:jar javadoc:jar repository:bundle-create + + scp target/jline-*-bundle.jar shell.sourceforge.net:/home/groups/j/jl/jline/htdocs + + Make a request like at http://jira.codehaus.org/browse/MAVENUPLOAD-1003 + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 + http://maven.apache.org/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + <groupId>jline</groupId> + <artifactId>jline</artifactId> + <packaging>jar</packaging> + <name>JLine</name> + <version>0.9.95-SNAPSHOT</version> + <description>JLine is a java library for reading and editing user input in console applications. It features tab-completion, command history, password masking, customizable keybindings, and pass-through handlers to use to chain to other console applications.</description> + <url>http://jline.sourceforge.net</url> + <issueManagement> + <system>sourceforge</system> + <url>http://sourceforge.net/tracker/?group_id=64033&atid=506056</url> + </issueManagement> + <inceptionYear>2002</inceptionYear> + <mailingLists> + <mailingList> + <name>JLine users</name> + <subscribe>https://lists.sourceforge.net/lists/listinfo/jline-users</subscribe> + <post>jline-users@lists.sourceforge.net</post> + <archive>http://sourceforge.net/mailarchive/forum.php?forum=jline-users</archive> + </mailingList> + </mailingLists> + + <developers> + <developer> + <id>mprudhom</id> + <name>Marc Prud'hommeaux</name> + <email>mwp1@cornell.edu</email> + </developer> + </developers> + <licenses> + <license> + <name>BSD</name> + <url>LICENSE.txt</url> + </license> + </licenses> + <scm> + <connection>scm:cvs:pserver:anonymous@jline.cvs.sourceforge.net:/cvsroot/jline:jline</connection> + <developerConnection>scm:cvs:ext:${maven.username}@jline.cvs.sourceforge.net:/cvsroot/jline:jline</developerConnection> + <url>http://jline.cvs.sourceforge.net/jline</url> + </scm> + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>3.8.1</version> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <!-- + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>jalopy-maven-plugin</artifactId> + <version>1.0-SNAPSHOT</version> + <configuration> + <fileFormat>UNIX</fileFormat> + <convention>codestyle.xml</convention> + </configuration> + <executions> + <execution> + <goals> + <goal>format</goal> + </goals> + </execution> + </executions> + </plugin> + --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <!-- <testFailureIgnore>true</testFailureIgnore> --> + <useFile>false</useFile> + <trimStackTrace>false</trimStackTrace> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <source>1.5</source> + <target>1.5</target> + <showWarnings>true</showWarnings> + <compilerArgument>-Xlint:unchecked</compilerArgument> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-site-plugin</artifactId> + <configuration> + <stagingDirectory>../site-staging</stagingDirectory> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-assembly-plugin</artifactId> + <configuration> + <descriptors> + <descriptor>src/assembly/assembly.xml</descriptor> + </descriptors> + </configuration> + </plugin> + </plugins> + </build> + + <reporting> + <plugins> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>jxr-maven-plugin</artifactId> + <configuration> + <aggregate>true</aggregate> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <configuration> + <aggregate>true</aggregate> + <linksource>true</linksource> + <links> + <link>http://java.sun.com/j2se/1.5.0/docs/api</link> + </links> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-pmd-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-project-info-reports-plugin</artifactId> + <reportSets> + <reportSet> + <reports> + <!-- <report>dependencies</report> --> + <!-- <report>cim</report> --> + <!-- <report>cobertura</report> --> + <report>project-team</report> + <report>mailing-list</report> + <report>issue-tracking</report> + <report>license</report> + <report>scm</report> + </reports> + </reportSet> + </reportSets> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>surefire-report-maven-plugin</artifactId> + </plugin> + </plugins> + </reporting> + <distributionManagement> + <repository> + <id>jline</id> + <url>scp://shell.sourceforge.net/home/groups/j/jl/jline/htdocs/m2repo</url> + </repository> + <snapshotRepository> + <id>jline</id> + <url>scp://shell.sourceforge.net/home/groups/j/jl/jline/htdocs/m2snapshot</url> + </snapshotRepository> + <site> + <id>jline</id> + <name>jline</name> + <url>scpexe://shell.sourceforge.net/home/groups/j/jl/jline/htdocs/</url> + </site> + </distributionManagement> +</project> diff --git a/src/jline/src/assembly/assembly.xml b/src/jline/src/assembly/assembly.xml new file mode 100644 index 0000000000..216c697a53 --- /dev/null +++ b/src/jline/src/assembly/assembly.xml @@ -0,0 +1,55 @@ +<assembly> + <id></id> + <formats> + <format>zip</format> + </formats> + <includeBaseDirectory>true</includeBaseDirectory> + <fileSets> + <fileSet> + <includes> + <include>README*</include> + <include>LICENSE*</include> + <include>NOTICE*</include> + </includes> + </fileSet> + <fileSet> + <directory>target</directory> + <outputDirectory></outputDirectory> + <includes> + <include>*.jar</include> + </includes> + </fileSet> + <fileSet> + <directory>licenses</directory> + <outputDirectory>/lib</outputDirectory> + <includes> + <include>*</include> + </includes> + </fileSet> + <fileSet> + <directory>src/test/java/jline/example</directory> + <outputDirectory>/examples/jline/example</outputDirectory> + </fileSet> + <fileSet> + <directory>src/test/resources/jline/example</directory> + <outputDirectory>/examples/jline/example</outputDirectory> + </fileSet> + <fileSet> + <directory>target/site/apidocs</directory> + <outputDirectory>/apidocs</outputDirectory> + </fileSet> + + <!-- also include sources --> + <fileSet> + <directory>src</directory> + <outputDirectory>/src/src</outputDirectory> + </fileSet> + <fileSet> + <directory></directory> + <outputDirectory>/src</outputDirectory> + <includes> + <include>pom.xml</include> + </includes> + </fileSet> + </fileSets> +</assembly> diff --git a/src/jline/src/main/java/jline/ANSIBuffer.java b/src/jline/src/main/java/jline/ANSIBuffer.java new file mode 100644 index 0000000000..c2e33180bb --- /dev/null +++ b/src/jline/src/main/java/jline/ANSIBuffer.java @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import java.io.*; + +/** + * A buffer that can contain ANSI text. + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public class ANSIBuffer { + private boolean ansiEnabled = true; + private final StringBuffer ansiBuffer = new StringBuffer(); + private final StringBuffer plainBuffer = new StringBuffer(); + + public ANSIBuffer() { + } + + public ANSIBuffer(final String str) { + append(str); + } + + public void setAnsiEnabled(final boolean ansi) { + this.ansiEnabled = ansi; + } + + public boolean getAnsiEnabled() { + return this.ansiEnabled; + } + + public String getAnsiBuffer() { + return ansiBuffer.toString(); + } + + public String getPlainBuffer() { + return plainBuffer.toString(); + } + + public String toString(final boolean ansi) { + return ansi ? getAnsiBuffer() : getPlainBuffer(); + } + + public String toString() { + return toString(ansiEnabled); + } + + public ANSIBuffer append(final String str) { + ansiBuffer.append(str); + plainBuffer.append(str); + + return this; + } + + public ANSIBuffer attrib(final String str, final int code) { + ansiBuffer.append(ANSICodes.attrib(code)).append(str) + .append(ANSICodes.attrib(ANSICodes.OFF)); + plainBuffer.append(str); + + return this; + } + + public ANSIBuffer red(final String str) { + return attrib(str, ANSICodes.FG_RED); + } + + public ANSIBuffer blue(final String str) { + return attrib(str, ANSICodes.FG_BLUE); + } + + public ANSIBuffer green(final String str) { + return attrib(str, ANSICodes.FG_GREEN); + } + + public ANSIBuffer black(final String str) { + return attrib(str, ANSICodes.FG_BLACK); + } + + public ANSIBuffer yellow(final String str) { + return attrib(str, ANSICodes.FG_YELLOW); + } + + public ANSIBuffer magenta(final String str) { + return attrib(str, ANSICodes.FG_MAGENTA); + } + + public ANSIBuffer cyan(final String str) { + return attrib(str, ANSICodes.FG_CYAN); + } + + public ANSIBuffer bold(final String str) { + return attrib(str, ANSICodes.BOLD); + } + + public ANSIBuffer underscore(final String str) { + return attrib(str, ANSICodes.UNDERSCORE); + } + + public ANSIBuffer blink(final String str) { + return attrib(str, ANSICodes.BLINK); + } + + public ANSIBuffer reverse(final String str) { + return attrib(str, ANSICodes.REVERSE); + } + + public static class ANSICodes { + static final int OFF = 0; + static final int BOLD = 1; + static final int UNDERSCORE = 4; + static final int BLINK = 5; + static final int REVERSE = 7; + static final int CONCEALED = 8; + static final int FG_BLACK = 30; + static final int FG_RED = 31; + static final int FG_GREEN = 32; + static final int FG_YELLOW = 33; + static final int FG_BLUE = 34; + static final int FG_MAGENTA = 35; + static final int FG_CYAN = 36; + static final int FG_WHITE = 37; + static final char ESC = 27; + + /** + * Constructor is private since this is a utility class. + */ + private ANSICodes() { + } + + /** + * Sets the screen mode. The mode will be one of the following values: + * <pre> + * mode description + * ---------------------------------------- + * 0 40 x 148 x 25 monochrome (text) + * 1 40 x 148 x 25 color (text) + * 2 80 x 148 x 25 monochrome (text) + * 3 80 x 148 x 25 color (text) + * 4 320 x 148 x 200 4-color (graphics) + * 5 320 x 148 x 200 monochrome (graphics) + * 6 640 x 148 x 200 monochrome (graphics) + * 7 Enables line wrapping + * 13 320 x 148 x 200 color (graphics) + * 14 640 x 148 x 200 color (16-color graphics) + * 15 640 x 148 x 350 monochrome (2-color graphics) + * 16 640 x 148 x 350 color (16-color graphics) + * 17 640 x 148 x 480 monochrome (2-color graphics) + * 18 640 x 148 x 480 color (16-color graphics) + * 19 320 x 148 x 200 color (256-color graphics) + * </pre> + */ + public static String setmode(final int mode) { + return ESC + "[=" + mode + "h"; + } + + /** + * Same as setmode () except for mode = 7, which disables line + * wrapping (useful for writing the right-most column without + * scrolling to the next line). + */ + public static String resetmode(final int mode) { + return ESC + "[=" + mode + "l"; + } + + /** + * Clears the screen and moves the cursor to the home postition. + */ + public static String clrscr() { + return ESC + "[2J"; + } + + /** + * Removes all characters from the current cursor position until + * the end of the line. + */ + public static String clreol() { + return ESC + "[K"; + } + + /** + * Moves the cursor n positions to the left. If n is greater or + * equal to the current cursor column, the cursor is moved to the + * first column. + */ + public static String left(final int n) { + return ESC + "[" + n + "D"; + } + + /** + * Moves the cursor n positions to the right. If n plus the current + * cursor column is greater than the rightmost column, the cursor + * is moved to the rightmost column. + */ + public static String right(final int n) { + return ESC + "[" + n + "C"; + } + + /** + * Moves the cursor n rows up without changing the current column. + * If n is greater than or equal to the current row, the cursor is + * placed in the first row. + */ + public static String up(final int n) { + return ESC + "[" + n + "A"; + } + + /** + * Moves the cursor n rows down. If n plus the current row is greater + * than the bottom row, the cursor is moved to the bottom row. + */ + public static String down(final int n) { + return ESC + "[" + n + "B"; + } + + /* + * Moves the cursor to the given row and column. (1,1) represents + * the upper left corner. The lower right corner of a usual DOS + * screen is (25, 80). + */ + public static String gotoxy(final int row, final int column) { + return ESC + "[" + row + ";" + column + "H"; + } + + /** + * Saves the current cursor position. + */ + public static String save() { + return ESC + "[s"; + } + + /** + * Restores the saved cursor position. + */ + public static String restore() { + return ESC + "[u"; + } + + /** + * Sets the character attribute. It will be + * one of the following character attributes: + * + * <pre> + * Text attributes + * 0 All attributes off + * 1 Bold on + * 4 Underscore (on monochrome display adapter only) + * 5 Blink on + * 7 Reverse video on + * 8 Concealed on + * + * Foreground colors + * 30 Black + * 31 Red + * 32 Green + * 33 Yellow + * 34 Blue + * 35 Magenta + * 36 Cyan + * 37 White + * + * Background colors + * 40 Black + * 41 Red + * 42 Green + * 43 Yellow + * 44 Blue + * 45 Magenta + * 46 Cyan + * 47 White + * </pre> + * + * The attributes remain in effect until the next attribute command + * is sent. + */ + public static String attrib(final int attr) { + return ESC + "[" + attr + "m"; + } + + /** + * Sets the key with the given code to the given value. code must be + * derived from the following table, value must + * be any semicolon-separated + * combination of String (enclosed in double quotes) and numeric values. + * For example, to set F1 to the String "Hello F1", followed by a CRLF + * sequence, one can use: ANSI.setkey ("0;59", "\"Hello F1\";13;10"). + * Heres's the table of key values: + * <pre> + * Key Code SHIFT+code CTRL+code ALT+code + * --------------------------------------------------------------- + * F1 0;59 0;84 0;94 0;104 + * F2 0;60 0;85 0;95 0;105 + * F3 0;61 0;86 0;96 0;106 + * F4 0;62 0;87 0;97 0;107 + * F5 0;63 0;88 0;98 0;108 + * F6 0;64 0;89 0;99 0;109 + * F7 0;65 0;90 0;100 0;110 + * F8 0;66 0;91 0;101 0;111 + * F9 0;67 0;92 0;102 0;112 + * F10 0;68 0;93 0;103 0;113 + * F11 0;133 0;135 0;137 0;139 + * F12 0;134 0;136 0;138 0;140 + * HOME (num keypad) 0;71 55 0;119 -- + * UP ARROW (num keypad) 0;72 56 (0;141) -- + * PAGE UP (num keypad) 0;73 57 0;132 -- + * LEFT ARROW (num keypad) 0;75 52 0;115 -- + * RIGHT ARROW (num keypad) 0;77 54 0;116 -- + * END (num keypad) 0;79 49 0;117 -- + * DOWN ARROW (num keypad) 0;80 50 (0;145) -- + * PAGE DOWN (num keypad) 0;81 51 0;118 -- + * INSERT (num keypad) 0;82 48 (0;146) -- + * DELETE (num keypad) 0;83 46 (0;147) -- + * HOME (224;71) (224;71) (224;119) (224;151) + * UP ARROW (224;72) (224;72) (224;141) (224;152) + * PAGE UP (224;73) (224;73) (224;132) (224;153) + * LEFT ARROW (224;75) (224;75) (224;115) (224;155) + * RIGHT ARROW (224;77) (224;77) (224;116) (224;157) + * END (224;79) (224;79) (224;117) (224;159) + * DOWN ARROW (224;80) (224;80) (224;145) (224;154) + * PAGE DOWN (224;81) (224;81) (224;118) (224;161) + * INSERT (224;82) (224;82) (224;146) (224;162) + * DELETE (224;83) (224;83) (224;147) (224;163) + * PRINT SCREEN -- -- 0;114 -- + * PAUSE/BREAK -- -- 0;0 -- + * BACKSPACE 8 8 127 (0) + * ENTER 13 -- 10 (0 + * TAB 9 0;15 (0;148) (0;165) + * NULL 0;3 -- -- -- + * A 97 65 1 0;30 + * B 98 66 2 0;48 + * C 99 66 3 0;46 + * D 100 68 4 0;32 + * E 101 69 5 0;18 + * F 102 70 6 0;33 + * G 103 71 7 0;34 + * H 104 72 8 0;35 + * I 105 73 9 0;23 + * J 106 74 10 0;36 + * K 107 75 11 0;37 + * L 108 76 12 0;38 + * M 109 77 13 0;50 + * N 110 78 14 0;49 + * O 111 79 15 0;24 + * P 112 80 16 0;25 + * Q 113 81 17 0;16 + * R 114 82 18 0;19 + * S 115 83 19 0;31 + * T 116 84 20 0;20 + * U 117 85 21 0;22 + * V 118 86 22 0;47 + * W 119 87 23 0;17 + * X 120 88 24 0;45 + * Y 121 89 25 0;21 + * Z 122 90 26 0;44 + * 1 49 33 -- 0;120 + * 2 50 64 0 0;121 + * 3 51 35 -- 0;122 + * 4 52 36 -- 0;123 + * 5 53 37 -- 0;124 + * 6 54 94 30 0;125 + * 7 55 38 -- 0;126 + * 8 56 42 -- 0;126 + * 9 57 40 -- 0;127 + * 0 48 41 -- 0;129 + * - 45 95 31 0;130 + * = 61 43 --- 0;131 + * [ 91 123 27 0;26 + * ] 93 125 29 0;27 + * 92 124 28 0;43 + * ; 59 58 -- 0;39 + * ' 39 34 -- 0;40 + * , 44 60 -- 0;51 + * . 46 62 -- 0;52 + * / 47 63 -- 0;53 + * ` 96 126 -- (0;41) + * ENTER (keypad) 13 -- 10 (0;166) + * / (keypad) 47 47 (0;142) (0;74) + * * (keypad) 42 (0;144) (0;78) -- + * - (keypad) 45 45 (0;149) (0;164) + * + (keypad) 43 43 (0;150) (0;55) + * 5 (keypad) (0;76) 53 (0;143) -- + */ + public static String setkey(final String code, final String value) { + return ESC + "[" + code + ";" + value + "p"; + } + } + + public static void main(final String[] args) throws Exception { + // sequence, one can use: ANSI.setkey ("0;59", "\"Hello F1\";13;10"). + BufferedReader reader = + new BufferedReader(new InputStreamReader(System.in)); + System.out.print(ANSICodes.setkey("97", "97;98;99;13") + + ANSICodes.attrib(ANSICodes.OFF)); + System.out.flush(); + + String line; + + while ((line = reader.readLine()) != null) { + System.out.println("GOT: " + line); + } + } +} diff --git a/src/jline/src/main/java/jline/ArgumentCompletor.java b/src/jline/src/main/java/jline/ArgumentCompletor.java new file mode 100644 index 0000000000..2cd572ce2f --- /dev/null +++ b/src/jline/src/main/java/jline/ArgumentCompletor.java @@ -0,0 +1,439 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import java.util.*; + +/** + * A {@link Completor} implementation that invokes a child completor + * using the appropriate <i>separator</i> argument. This + * can be used instead of the individual completors having to + * know about argument parsing semantics. + * <p> + * <strong>Example 1</strong>: Any argument of the command line can + * use file completion. + * <p> + * <pre> + * consoleReader.addCompletor (new ArgumentCompletor ( + * new {@link FileNameCompletor} ())) + * </pre> + * <p> + * <strong>Example 2</strong>: The first argument of the command line + * can be completed with any of "foo", "bar", or "baz", and remaining + * arguments can be completed with a file name. + * <p> + * <pre> + * consoleReader.addCompletor (new ArgumentCompletor ( + * new {@link SimpleCompletor} (new String [] { "foo", "bar", "baz"}))); + * consoleReader.addCompletor (new ArgumentCompletor ( + * new {@link FileNameCompletor} ())); + * </pre> + * + * <p> + * When the argument index is past the last embedded completors, the last + * completors is always used. To disable this behavior, have the last + * completor be a {@link NullCompletor}. For example: + * </p> + * + * <pre> + * consoleReader.addCompletor (new ArgumentCompletor ( + * new {@link SimpleCompletor} (new String [] { "foo", "bar", "baz"}), + * new {@link SimpleCompletor} (new String [] { "xxx", "yyy", "xxx"}), + * new {@link NullCompletor} + * )); + * </pre> + * <p> + * TODO: handle argument quoting and escape characters + * </p> + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public class ArgumentCompletor implements Completor { + final Completor[] completors; + final ArgumentDelimiter delim; + boolean strict = true; + + /** + * Constuctor: create a new completor with the default + * argument separator of " ". + * + * @param completor the embedded completor + */ + public ArgumentCompletor(final Completor completor) { + this(new Completor[] { + completor + }); + } + + /** + * Constuctor: create a new completor with the default + * argument separator of " ". + * + * @param completors the List of completors to use + */ + public ArgumentCompletor(final List<Completor> completors) { + this((Completor[]) completors.toArray(new Completor[completors.size()])); + } + + /** + * Constuctor: create a new completor with the default + * argument separator of " ". + * + * @param completors the embedded argument completors + */ + public ArgumentCompletor(final Completor[] completors) { + this(completors, new WhitespaceArgumentDelimiter()); + } + + /** + * Constuctor: create a new completor with the specified + * argument delimiter. + * + * @param completor the embedded completor + * @param delim the delimiter for parsing arguments + */ + public ArgumentCompletor(final Completor completor, + final ArgumentDelimiter delim) { + this(new Completor[] { + completor + }, delim); + } + + /** + * Constuctor: create a new completor with the specified + * argument delimiter. + * + * @param completors the embedded completors + * @param delim the delimiter for parsing arguments + */ + public ArgumentCompletor(final Completor[] completors, + final ArgumentDelimiter delim) { + this.completors = completors; + this.delim = delim; + } + + /** + * 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 succees + * if all the completions from arguments 0-(N-1) also succeed. + */ + public boolean getStrict() { + return this.strict; + } + + public int complete(final String buffer, final int cursor, + final List<String> candidates) { + ArgumentList list = delim.delimit(buffer, cursor); + int argpos = list.getArgumentPosition(); + int argIndex = list.getCursorArgumentIndex(); + + if (argIndex < 0) { + return -1; + } + + final Completor comp; + + // if we are beyond the end of the completors, just use the last one + if (argIndex >= completors.length) { + comp = completors[completors.length - 1]; + } else { + comp = completors[argIndex]; + } + + // ensure that all the previous completors are successful before + // allowing this completor to pass (only if strict is true). + for (int i = 0; getStrict() && (i < argIndex); i++) { + Completor sub = + completors[(i >= completors.length) ? (completors.length - 1) : i]; + String[] args = list.getArguments(); + String arg = ((args == null) || (i >= args.length)) ? "" : args[i]; + + List<String> subCandidates = new LinkedList<String>(); + + if (sub.complete(arg, arg.length(), subCandidates) == -1) { + return -1; + } + + if (subCandidates.size() == 0) { + return -1; + } + } + + int ret = comp.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++) { + String val = candidates.get(i).toString(); + + while ((val.length() > 0) + && delim.isDelimiter(val, val.length() - 1)) { + val = val.substring(0, val.length() - 1); + } + + candidates.set(i, val); + } + } + + ConsoleReader.debug("Completing " + buffer + "(pos=" + cursor + ") " + + "with: " + candidates + ": offset=" + pos); + + return pos; + } + + /** + * The {@link ArgumentCompletor.ArgumentDelimiter} allows custom + * breaking up of a {@link String} into individual arguments in + * order to dispatch the arguments to the nested {@link Completor}. + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ + public static interface ArgumentDelimiter { + /** + * Break the specified buffer into individual tokens + * that can be completed on their own. + * + * @param buffer the buffer to split + * @param argumentPosition the current position of the + * cursor in the buffer + * @return the tokens + */ + ArgumentList delimit(String buffer, int argumentPosition); + + /** + * 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(String buffer, int pos); + } + + /** + * Abstract implementation of a delimiter that uses the + * {@link #isDelimiter} method to determine if a particular + * character should be used as a delimiter. + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ + public abstract static class AbstractArgumentDelimiter + implements ArgumentDelimiter { + private char[] quoteChars = new char[] { '\'', '"' }; + private char[] escapeChars = new char[] { '\\' }; + + public void setQuoteChars(final char[] quoteChars) { + this.quoteChars = quoteChars; + } + + public char[] getQuoteChars() { + return this.quoteChars; + } + + public void setEscapeChars(final char[] escapeChars) { + this.escapeChars = escapeChars; + } + + public char[] getEscapeChars() { + return this.escapeChars; + } + + public ArgumentList delimit(final String buffer, final int cursor) { + List<String> args = new LinkedList<String>(); + StringBuffer arg = new StringBuffer(); + 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((String[]) 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 String buffer, final int pos) { + if (isQuoted(buffer, pos)) { + return false; + } + + if (isEscaped(buffer, pos)) { + return false; + } + + return isDelimiterChar(buffer, pos); + } + + public boolean isQuoted(final String buffer, final int pos) { + return false; + } + + public boolean isEscaped(final String 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(String buffer, int pos); + } + + /** + * {@link ArgumentCompletor.ArgumentDelimiter} + * implementation that counts all + * whitespace (as reported by {@link Character#isWhitespace}) + * as being a delimiter. + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ + public static class WhitespaceArgumentDelimiter + extends AbstractArgumentDelimiter { + /** + * The character is a delimiter if it is whitespace, and the + * preceeding character is not an escape character. + */ + public boolean isDelimiterChar(String buffer, int pos) { + return Character.isWhitespace(buffer.charAt(pos)); + } + } + + /** + * The result of a delimited buffer. + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ + public static class ArgumentList { + private String[] arguments; + private int cursorArgumentIndex; + private int argumentPosition; + private int bufferPosition; + + /** + * @param arguments the array of tokens + * @param cursorArgumentIndex the token index of the cursor + * @param argumentPosition the position of the cursor in the + * current token + * @param bufferPosition the position of the cursor in + * the whole buffer + */ + public ArgumentList(String[] arguments, int cursorArgumentIndex, + int argumentPosition, int bufferPosition) { + this.arguments = arguments; + this.cursorArgumentIndex = cursorArgumentIndex; + this.argumentPosition = argumentPosition; + this.bufferPosition = bufferPosition; + } + + public void setCursorArgumentIndex(int cursorArgumentIndex) { + this.cursorArgumentIndex = cursorArgumentIndex; + } + + public int getCursorArgumentIndex() { + return this.cursorArgumentIndex; + } + + public String getCursorArgument() { + if ((cursorArgumentIndex < 0) + || (cursorArgumentIndex >= arguments.length)) { + return null; + } + + return arguments[cursorArgumentIndex]; + } + + public void setArgumentPosition(int argumentPosition) { + this.argumentPosition = argumentPosition; + } + + public int getArgumentPosition() { + return this.argumentPosition; + } + + public void setArguments(String[] arguments) { + this.arguments = arguments; + } + + public String[] getArguments() { + return this.arguments; + } + + public void setBufferPosition(int bufferPosition) { + this.bufferPosition = bufferPosition; + } + + public int getBufferPosition() { + return this.bufferPosition; + } + } +} diff --git a/src/jline/src/main/java/jline/CandidateCycleCompletionHandler.java b/src/jline/src/main/java/jline/CandidateCycleCompletionHandler.java new file mode 100644 index 0000000000..a0bf208cdc --- /dev/null +++ b/src/jline/src/main/java/jline/CandidateCycleCompletionHandler.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import java.io.*; +import java.util.*; + +/** + * <p> + * A {@link CompletionHandler} that deals with multiple distinct completions + * by cycling through each one every time tab is pressed. This + * mimics the behavior of the + * <a href="http://packages.qa.debian.org/e/editline.html">editline</a> + * library. + * </p> + * <p><strong>This class is currently a stub; it does nothing</strong></p> + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public class CandidateCycleCompletionHandler implements CompletionHandler { + public boolean complete(final ConsoleReader reader, final List candidates, + final int position) throws IOException { + throw new IllegalStateException("CandidateCycleCompletionHandler unimplemented"); + } +} diff --git a/src/jline/src/main/java/jline/CandidateListCompletionHandler.java b/src/jline/src/main/java/jline/CandidateListCompletionHandler.java new file mode 100644 index 0000000000..17f03d5e68 --- /dev/null +++ b/src/jline/src/main/java/jline/CandidateListCompletionHandler.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import java.io.*; +import java.text.MessageFormat; +import java.util.*; + +/** + * <p> + * A {@link CompletionHandler} that deals with multiple distinct completions + * by outputting the complete list of possibilities to the console. This + * mimics the behavior of the + * <a href="http://www.gnu.org/directory/readline.html">readline</a> + * library. + * </p> + * + * <strong>TODO:</strong> + * <ul> + * <li>handle quotes and escaped quotes</li> + * <li>enable automatic escaping of whitespace</li> + * </ul> + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public class CandidateListCompletionHandler implements CompletionHandler { + private static ResourceBundle loc = ResourceBundle. + getBundle(CandidateListCompletionHandler.class.getName()); + + private boolean eagerNewlines = true; + + public void setAlwaysIncludeNewline(boolean eagerNewlines) { + this.eagerNewlines = eagerNewlines; + } + + public boolean complete(final ConsoleReader reader, final List<String> candidates, + final int pos) throws IOException { + CursorBuffer buf = reader.getCursorBuffer(); + + // if there is only one completion, then fill in the buffer + if (candidates.size() == 1) { + String value = candidates.get(0).toString(); + + // 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); + String bufString = buf.toString(); + setBuffer(reader, value, pos); + } + + if (eagerNewlines) + reader.printNewline(); + printCandidates(reader, candidates, eagerNewlines); + + // redraw the current console buffer + reader.drawLine(); + + return true; + } + + public static void setBuffer(ConsoleReader reader, String value, int offset) + throws IOException { + while ((reader.getCursorBuffer().cursor > offset) + && reader.backspace()) { + ; + } + + reader.putString(value); + reader.setCursorPosition(offset + value.length()); + } + + /** + * Print out the candidates. If the size of the candidates + * is greated than the {@link getAutoprintThreshhold}, + * they prompt with aq warning. + * + * @param candidates the list of candidates to print + */ + public static final void printCandidates(ConsoleReader reader, + Collection<String> candidates, boolean eagerNewlines) + throws IOException { + Set<String> distinct = new HashSet<String>(candidates); + + if (distinct.size() > reader.getAutoprintThreshhold()) { + if (!eagerNewlines) + reader.printNewline(); + reader.printString(MessageFormat.format + (loc.getString("display-candidates"), new Object[] { + new Integer(candidates .size()) + }) + " "); + + reader.flushConsole(); + + int c; + + String noOpt = loc.getString("display-candidates-no"); + String yesOpt = loc.getString("display-candidates-yes"); + + while ((c = reader.readCharacter(new char[] { + yesOpt.charAt(0), noOpt.charAt(0) })) != -1) { + if (noOpt.startsWith + (new String(new char[] { (char) c }))) { + reader.printNewline(); + return; + } else if (yesOpt.startsWith + (new String(new char[] { (char) c }))) { + 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<String> copy = new ArrayList<String>(); + + for (Iterator<String> i = candidates.iterator(); i.hasNext();) { + String next = i.next(); + + if (!(copy.contains(next))) { + copy.add(next); + } + } + + candidates = copy; + } + + reader.printNewline(); + reader.printColumns(candidates); + } + + /** + * Returns a root that matches all the {@link String} elements + * of the specified {@link List}, or null if there are + * no commalities. For example, if the list contains + * <i>foobar</i>, <i>foobaz</i>, <i>foobuz</i>, the + * method will return <i>foob</i>. + */ + private final String getUnambiguousCompletions(final List<String> candidates) { + if ((candidates == null) || (candidates.size() == 0)) { + return null; + } + + // convert to an array for speed + String[] strings = + (String[]) candidates.toArray(new String[candidates.size()]); + + String first = strings[0]; + StringBuffer candidate = new StringBuffer(); + + for (int i = 0; i < first.length(); i++) { + if (startsWith(first.substring(0, i + 1), strings)) { + candidate.append(first.charAt(i)); + } else { + break; + } + } + + return candidate.toString(); + } + + /** + * @return true is all the elements of <i>candidates</i> + * start with <i>starts</i> + */ + private final boolean startsWith(final String starts, + final String[] candidates) { + for (int i = 0; i < candidates.length; i++) { + if (!candidates[i].startsWith(starts)) { + return false; + } + } + + return true; + } +} diff --git a/src/jline/src/main/java/jline/ClassNameCompletor.java b/src/jline/src/main/java/jline/ClassNameCompletor.java new file mode 100644 index 0000000000..5c3ca87dca --- /dev/null +++ b/src/jline/src/main/java/jline/ClassNameCompletor.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import java.io.*; +import java.net.*; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * A Completor implementation that completes java class names. By default, + * it scans the java class path to locate all the classes. + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public class ClassNameCompletor extends SimpleCompletor { + + /** + * Complete candidates using all the classes available in the + * java <em>CLASSPATH</em>. + */ + public ClassNameCompletor() throws IOException { + this(null); + } + + public ClassNameCompletor(final SimpleCompletorFilter filter) + throws IOException { + super(getClassNames(), filter); + setDelimiter("."); + } + + public static String[] getClassNames() throws IOException { + Set<URL> urls = new HashSet<URL>(); + + for (ClassLoader loader = ClassNameCompletor.class + .getClassLoader(); loader != null; + loader = loader.getParent()) { + if (!(loader instanceof URLClassLoader)) { + continue; + } + + urls.addAll(Arrays.asList(((URLClassLoader) loader).getURLs())); + } + + // Now add the URL that holds java.lang.String. This is because + // some JVMs do not report the core classes jar in the list of + // class loaders. + Class[] systemClasses = new Class[] { + String.class, javax.swing.JFrame.class + }; + + for (int i = 0; i < systemClasses.length; i++) { + URL classURL = systemClasses[i].getResource("/" + + systemClasses[i].getName() .replace('.', '/') + ".class"); + + if (classURL != null) { + URLConnection uc = (URLConnection) classURL.openConnection(); + + if (uc instanceof JarURLConnection) { + urls.add(((JarURLConnection) uc).getJarFileURL()); + } + } + } + + Set<String> classes = new HashSet<String>(); + + for (Iterator i = urls.iterator(); i.hasNext();) { + URL url = (URL) i.next(); + File file = new File(url.getFile()); + + if (file.isDirectory()) { + Set<String> files = getClassFiles(file.getAbsolutePath(), + new HashSet<String>(), file, new int[] { 200 }); + classes.addAll(files); + + continue; + } + + if ((file == null) || !file.isFile()) // TODO: handle directories + { + continue; + } + + JarFile jf = new JarFile(file); + + for (Enumeration e = jf.entries(); e.hasMoreElements();) { + JarEntry entry = (JarEntry) e.nextElement(); + + if (entry == null) { + continue; + } + + String name = entry.getName(); + + if (!name.endsWith(".class")) // only use class files + { + continue; + } + + classes.add(name); + } + } + + // now filter classes by changing "/" to "." and trimming the + // trailing ".class" + Set<String> classNames = new TreeSet<String>(); + + for (Iterator<String> i = classes.iterator(); i.hasNext();) { + String name = (String) i.next(); + classNames.add(name.replace('/', '.'). + substring(0, name.length() - 6)); + } + + return (String[]) classNames.toArray(new String[classNames.size()]); + } + + private static Set<String> getClassFiles(String root, Set<String> holder, File directory, + int[] maxDirectories) { + // we have passed the maximum number of directories to scan + if (maxDirectories[0]-- < 0) { + return holder; + } + + File[] files = directory.listFiles(); + + for (int i = 0; (files != null) && (i < files.length); i++) { + String name = files[i].getAbsolutePath(); + + if (!(name.startsWith(root))) { + continue; + } else if (files[i].isDirectory()) { + getClassFiles(root, holder, files[i], maxDirectories); + } else if (files[i].getName().endsWith(".class")) { + holder.add(files[i].getAbsolutePath(). + substring(root.length() + 1)); + } + } + + return holder; + } +} diff --git a/src/jline/src/main/java/jline/CompletionHandler.java b/src/jline/src/main/java/jline/CompletionHandler.java new file mode 100644 index 0000000000..5dffdcbaae --- /dev/null +++ b/src/jline/src/main/java/jline/CompletionHandler.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import java.io.*; +import java.util.*; + +/** + * Handler for dealing with candidates for tab-completion. + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public interface CompletionHandler { + boolean complete(ConsoleReader reader, List<String> candidates, int position) + throws IOException; +} diff --git a/src/jline/src/main/java/jline/Completor.java b/src/jline/src/main/java/jline/Completor.java new file mode 100644 index 0000000000..ed1238a93d --- /dev/null +++ b/src/jline/src/main/java/jline/Completor.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import java.util.*; + +/** + * A Completor is the mechanism by which tab-completion candidates + * will be resolved. + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public interface Completor { + /** + * Populates <i>candidates</i> with a list of possible + * completions for the <i>buffer</i>. The <i>candidates</i> + * list will not be sorted before being displayed to the + * user: thus, the complete method should sort the + * {@link List} before returning. + * + * + * @param buffer the buffer + * @param candidates the {@link List} of candidates to populate + * @return the index of the <i>buffer</i> for which + * the completion will be relative + */ + int complete(String buffer, int cursor, List<String> candidates); +} diff --git a/src/jline/src/main/java/jline/ConsoleOperations.java b/src/jline/src/main/java/jline/ConsoleOperations.java new file mode 100644 index 0000000000..585ed401d9 --- /dev/null +++ b/src/jline/src/main/java/jline/ConsoleOperations.java @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import java.awt.event.KeyEvent; + +/** + * Symbolic constants for Console operations and virtual key bindings. + * @see KeyEvent + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public interface ConsoleOperations { + final String CR = System.getProperty("line.separator"); + final char BACKSPACE = '\b'; + final char RESET_LINE = '\r'; + final char KEYBOARD_BELL = '\07'; + final char CTRL_A = 1; + final char CTRL_B = 2; + final char CTRL_C = 3; + final char CTRL_D = 4; + final char CTRL_E = 5; + final char CTRL_F = 6; + final static char CTRL_K = 11; + final static char CTRL_L = 12; + final char CTRL_N = 14; + final char CTRL_P = 16; + final static char CTRL_OB = 27; + final static char DELETE = 127; + final static char CTRL_QM = 127; + + + /** + * Logical constants for key operations. + */ + + /** + * Unknown operation. + */ + final short UNKNOWN = -99; + + /** + * Operation that moves to the beginning of the buffer. + */ + final short MOVE_TO_BEG = -1; + + /** + * Operation that moves to the end of the buffer. + */ + final short MOVE_TO_END = -3; + + /** + * Operation that moved to the previous character in the buffer. + */ + final short PREV_CHAR = -4; + + /** + * Operation that issues a newline. + */ + final short NEWLINE = -6; + + /** + * Operation that deletes the buffer from the current character to the end. + */ + final short KILL_LINE = -7; + + /** + * Operation that clears the screen. + */ + final short CLEAR_SCREEN = -8; + + /** + * Operation that sets the buffer to the next history item. + */ + final short NEXT_HISTORY = -9; + + /** + * Operation that sets the buffer to the previous history item. + */ + final short PREV_HISTORY = -11; + + /** + * Operation that redisplays the current buffer. + */ + final short REDISPLAY = -13; + + /** + * Operation that deletes the buffer from the cursor to the beginning. + */ + final short KILL_LINE_PREV = -15; + + /** + * Operation that deletes the previous word in the buffer. + */ + final short DELETE_PREV_WORD = -16; + + /** + * Operation that moves to the next character in the buffer. + */ + final short NEXT_CHAR = -19; + + /** + * Operation that moves to the previous character in the buffer. + */ + final short REPEAT_PREV_CHAR = -20; + + /** + * Operation that searches backwards in the command history. + */ + final short SEARCH_PREV = -21; + + /** + * Operation that repeats the character. + */ + final short REPEAT_NEXT_CHAR = -24; + + /** + * Operation that searches forward in the command history. + */ + final short SEARCH_NEXT = -25; + + /** + * Operation that moved to the previous whitespace. + */ + final short PREV_SPACE_WORD = -27; + + /** + * Operation that moved to the end of the current word. + */ + final short TO_END_WORD = -29; + + /** + * Operation that + */ + final short REPEAT_SEARCH_PREV = -34; + + /** + * Operation that + */ + final short PASTE_PREV = -36; + + /** + * Operation that + */ + final short REPLACE_MODE = -37; + + /** + * Operation that + */ + final short SUBSTITUTE_LINE = -38; + + /** + * Operation that + */ + final short TO_PREV_CHAR = -39; + + /** + * Operation that + */ + final short NEXT_SPACE_WORD = -40; + + /** + * Operation that + */ + final short DELETE_PREV_CHAR = -41; + + /** + * Operation that + */ + final short ADD = -42; + + /** + * Operation that + */ + final short PREV_WORD = -43; + + /** + * Operation that + */ + final short CHANGE_META = -44; + + /** + * Operation that + */ + final short DELETE_META = -45; + + /** + * Operation that + */ + final short END_WORD = -46; + + /** + * Operation that toggles insert/overtype + */ + final short INSERT = -48; + + /** + * Operation that + */ + final short REPEAT_SEARCH_NEXT = -49; + + /** + * Operation that + */ + final short PASTE_NEXT = -50; + + /** + * Operation that + */ + final short REPLACE_CHAR = -51; + + /** + * Operation that + */ + final short SUBSTITUTE_CHAR = -52; + + /** + * Operation that + */ + final short TO_NEXT_CHAR = -53; + + /** + * Operation that undoes the previous operation. + */ + final short UNDO = -54; + + /** + * Operation that moved to the next word. + */ + final short NEXT_WORD = -55; + + /** + * Operation that deletes the previous character. + */ + final short DELETE_NEXT_CHAR = -56; + + /** + * Operation that toggles between uppercase and lowercase. + */ + final short CHANGE_CASE = -57; + + /** + * Operation that performs completion operation on the current word. + */ + final short COMPLETE = -58; + + /** + * Operation that exits the command prompt. + */ + final short EXIT = -59; + + /** + * Operation that pastes the contents of the clipboard into the line + */ + final short PASTE = -60; + + /** + * Operation that moves the current History to the beginning. + */ + final static short START_OF_HISTORY = -61; + + /** + * Operation that moves the current History to the end. + */ + final static short END_OF_HISTORY = -62; + + /** + * Operation that clears whatever text is on the current line. + */ + final static short CLEAR_LINE = -63; + +} diff --git a/src/jline/src/main/java/jline/ConsoleReader.java b/src/jline/src/main/java/jline/ConsoleReader.java new file mode 100644 index 0000000000..6c7fff1aed --- /dev/null +++ b/src/jline/src/main/java/jline/ConsoleReader.java @@ -0,0 +1,1624 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import java.awt.*; +import java.awt.datatransfer.*; +import java.awt.event.ActionListener; + +import java.io.*; +import java.util.*; +import java.util.List; + +/** + * 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 Terminal#initializeTerminal} for convenience + * methods for issuing platform-specific setup commands. + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public class ConsoleReader implements ConsoleOperations { + + final static int TAB_WIDTH = 4; + + String prompt; + + private boolean useHistory = true; + + private boolean usePagination = false; + + public static final String CR = System.getProperty("line.separator"); + + private static ResourceBundle loc = ResourceBundle + .getBundle(CandidateListCompletionHandler.class.getName()); + + /** + * Map that contains the operation name to keymay operation mapping. + */ + public static SortedMap<String, Short> KEYMAP_NAMES; + + static { + Map<String, Short> names = new TreeMap<String, Short>(); + + names.put("MOVE_TO_BEG", new Short(MOVE_TO_BEG)); + names.put("MOVE_TO_END", new Short(MOVE_TO_END)); + names.put("PREV_CHAR", new Short(PREV_CHAR)); + names.put("NEWLINE", new Short(NEWLINE)); + names.put("KILL_LINE", new Short(KILL_LINE)); + names.put("PASTE", new Short(PASTE)); + names.put("CLEAR_SCREEN", new Short(CLEAR_SCREEN)); + names.put("NEXT_HISTORY", new Short(NEXT_HISTORY)); + names.put("PREV_HISTORY", new Short(PREV_HISTORY)); + names.put("START_OF_HISTORY", new Short(START_OF_HISTORY)); + names.put("END_OF_HISTORY", new Short(END_OF_HISTORY)); + names.put("REDISPLAY", new Short(REDISPLAY)); + names.put("KILL_LINE_PREV", new Short(KILL_LINE_PREV)); + names.put("DELETE_PREV_WORD", new Short(DELETE_PREV_WORD)); + names.put("NEXT_CHAR", new Short(NEXT_CHAR)); + names.put("REPEAT_PREV_CHAR", new Short(REPEAT_PREV_CHAR)); + names.put("SEARCH_PREV", new Short(SEARCH_PREV)); + names.put("REPEAT_NEXT_CHAR", new Short(REPEAT_NEXT_CHAR)); + names.put("SEARCH_NEXT", new Short(SEARCH_NEXT)); + names.put("PREV_SPACE_WORD", new Short(PREV_SPACE_WORD)); + names.put("TO_END_WORD", new Short(TO_END_WORD)); + names.put("REPEAT_SEARCH_PREV", new Short(REPEAT_SEARCH_PREV)); + names.put("PASTE_PREV", new Short(PASTE_PREV)); + names.put("REPLACE_MODE", new Short(REPLACE_MODE)); + names.put("SUBSTITUTE_LINE", new Short(SUBSTITUTE_LINE)); + names.put("TO_PREV_CHAR", new Short(TO_PREV_CHAR)); + names.put("NEXT_SPACE_WORD", new Short(NEXT_SPACE_WORD)); + names.put("DELETE_PREV_CHAR", new Short(DELETE_PREV_CHAR)); + names.put("ADD", new Short(ADD)); + names.put("PREV_WORD", new Short(PREV_WORD)); + names.put("CHANGE_META", new Short(CHANGE_META)); + names.put("DELETE_META", new Short(DELETE_META)); + names.put("END_WORD", new Short(END_WORD)); + names.put("NEXT_CHAR", new Short(NEXT_CHAR)); + names.put("INSERT", new Short(INSERT)); + names.put("REPEAT_SEARCH_NEXT", new Short(REPEAT_SEARCH_NEXT)); + names.put("PASTE_NEXT", new Short(PASTE_NEXT)); + names.put("REPLACE_CHAR", new Short(REPLACE_CHAR)); + names.put("SUBSTITUTE_CHAR", new Short(SUBSTITUTE_CHAR)); + names.put("TO_NEXT_CHAR", new Short(TO_NEXT_CHAR)); + names.put("UNDO", new Short(UNDO)); + names.put("NEXT_WORD", new Short(NEXT_WORD)); + names.put("DELETE_NEXT_CHAR", new Short(DELETE_NEXT_CHAR)); + names.put("CHANGE_CASE", new Short(CHANGE_CASE)); + names.put("COMPLETE", new Short(COMPLETE)); + names.put("EXIT", new Short(EXIT)); + names.put("CLEAR_LINE", new Short(CLEAR_LINE)); + + KEYMAP_NAMES = new TreeMap<String, Short>(Collections.unmodifiableMap(names)); + } + + /** + * The map for logical operations. + */ + private final short[] keybindings; + + /** + * If true, issue an audible keyboard bell when appropriate. + */ + private boolean bellEnabled = true; + + /** + * The current character mask. + */ + private Character mask = null; + + /** + * The null mask. + */ + private static final Character NULL_MASK = new Character((char) 0); + + /** + * The number of tab-completion candidates above which a warning will be + * prompted before showing all the candidates. + */ + private int autoprintThreshhold = Integer.getInteger( + "jline.completion.threshold", 100).intValue(); // same default as + + // bash + + /** + * The Terminal to use. + */ + private final Terminal terminal; + + private CompletionHandler completionHandler = new CandidateListCompletionHandler(); + + InputStream in; + + final Writer out; + + final CursorBuffer buf = new CursorBuffer(); + + static PrintWriter debugger; + + History history = new History(); + + final List<Completor> completors = new LinkedList<Completor>(); + + private Character echoCharacter = null; + + private Map<Character, ActionListener> triggeredActions = new HashMap<Character, ActionListener>(); + + + /** + * Adding a triggered Action allows to give another curse of action + * if a character passed the preprocessing. + * + * Say you want to close the application if the user enter q. + * addTriggerAction('q', new ActionListener(){ System.exit(0); }); + * would do the trick. + * + * @param c + * @param listener + */ + public void addTriggeredAction(char c, ActionListener listener){ + triggeredActions.put(new Character(c), listener); + } + + /** + * Create a new reader using {@link FileDescriptor#in} for input and + * {@link System#out} for output. {@link FileDescriptor#in} is used because + * it has a better chance of being unbuffered. + */ + public ConsoleReader() throws IOException { + this(new FileInputStream(FileDescriptor.in), + new PrintWriter( + new OutputStreamWriter(System.out, + System.getProperty("jline.WindowsTerminal.output.encoding",System.getProperty("file.encoding"))))); + } + + /** + * Create a new reader using the specified {@link InputStream} for input and + * the specific writer for output, using the default keybindings resource. + */ + public ConsoleReader(final InputStream in, final Writer out) + throws IOException { + this(in, out, null); + } + + public ConsoleReader(final InputStream in, final Writer out, + final InputStream bindings) throws IOException { + this(in, out, bindings, Terminal.getTerminal()); + } + + /** + * Create a new reader. + * + * @param in + * the input + * @param out + * the output + * @param bindings + * the key bindings to use + * @param term + * the terminal to use + */ + public ConsoleReader(InputStream in, Writer out, InputStream bindings, + Terminal term) throws IOException { + this.terminal = term; + setInput(in); + this.out = out; + if (bindings == null) { + try { + String bindingFile = System.getProperty("jline.keybindings", + new File(System.getProperty("user.home", + ".jlinebindings.properties")).getAbsolutePath()); + + if (new File(bindingFile).isFile()) { + bindings = new FileInputStream(new File(bindingFile)); + } + } catch (Exception e) { + // swallow exceptions with option debugging + if (debugger != null) { + e.printStackTrace(debugger); + } + } + } + + if (bindings == null) { + bindings = terminal.getDefaultBindings(); + } + + this.keybindings = new short[Character.MAX_VALUE * 2]; + + Arrays.fill(this.keybindings, UNKNOWN); + + /** + * Loads the key bindings. Bindings file is in the format: + * + * keycode: operation name + */ + if (bindings != null) { + Properties p = new Properties(); + p.load(bindings); + bindings.close(); + + for (Iterator i = p.keySet().iterator(); i.hasNext();) { + String val = (String) i.next(); + + try { + Short code = new Short(val); + String op = (String) p.getProperty(val); + + Short opval = (Short) KEYMAP_NAMES.get(op); + + if (opval != null) { + keybindings[code.shortValue()] = opval.shortValue(); + } + } catch (NumberFormatException nfe) { + consumeException(nfe); + } + } + + // hardwired arrow key bindings + // keybindings[VK_UP] = PREV_HISTORY; + // keybindings[VK_DOWN] = NEXT_HISTORY; + // keybindings[VK_LEFT] = PREV_CHAR; + // keybindings[VK_RIGHT] = NEXT_CHAR; + } + } + + public Terminal getTerminal() { + return this.terminal; + } + + /** + * Set the stream for debugging. Development use only. + */ + public void setDebug(final PrintWriter debugger) { + ConsoleReader.debugger = debugger; + } + + /** + * Set the stream to be used for console input. + */ + public void setInput(final InputStream in) { + this.in = in; + } + + /** + * Returns the stream used for console input. + */ + public InputStream getInput() { + return this.in; + } + + /** + * 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); + } + + /** + * @param bellEnabled + * if true, enable audible keyboard bells if an alert is + * required. + */ + public void setBellEnabled(final boolean bellEnabled) { + this.bellEnabled = bellEnabled; + } + + /** + * @return true is audible keyboard bell is enabled. + */ + public boolean getBellEnabled() { + return this.bellEnabled; + } + + /** + * Query the terminal to find the current width; + * + * @see Terminal#getTerminalWidth + * @return the width of the current terminal. + */ + public int getTermwidth() { + return Terminal.setupTerminal().getTerminalWidth(); + } + + /** + * Query the terminal to find the current width; + * + * @see Terminal#getTerminalHeight + * + * @return the height of the current terminal. + */ + public int getTermheight() { + return Terminal.setupTerminal().getTerminalHeight(); + } + + /** + * @param autoprintThreshhold + * the number of candidates to print without issuing a warning. + */ + public void setAutoprintThreshhold(final int autoprintThreshhold) { + this.autoprintThreshhold = autoprintThreshhold; + } + + /** + * @return the number of candidates to print without issing a warning. + */ + public int getAutoprintThreshhold() { + return this.autoprintThreshhold; + } + + int getKeyForAction(short logicalAction) { + for (int i = 0; i < keybindings.length; i++) { + if (keybindings[i] == logicalAction) { + return i; + } + } + + return -1; + } + + /** + * Clear the echoed characters for the specified character code. + */ + int clearEcho(int c) throws IOException { + // if the terminal is not echoing, then just return... + if (!terminal.getEcho()) { + return 0; + } + + // otherwise, clear + int num = countEchoCharacters((char) c); + back(num); + drawBuffer(num); + + return num; + } + + int countEchoCharacters(char 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. + */ + StringBuffer getPrintableCharacters(char ch) { + StringBuffer sbuff = new StringBuffer(); + + 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; + } + + int getCursorPosition() { + // FIXME: does not handle anything but a line with a prompt + // absolute position + return ((prompt == null) ? 0 : prompt.length()) + buf.cursor; + } + + public String readLine(final String prompt) throws IOException { + return readLine(prompt, null); + } + + /** + * The default prompt that will be issued. + */ + public void setDefaultPrompt(String prompt) { + this.prompt = prompt; + } + + /** + * The default prompt that will be issued. + */ + public String getDefaultPrompt() { + return prompt; + } + + /** + * Read a line from the <i>in</i> {@link InputStream}, and return the line + * (without any trailing newlines). + * + * @param prompt + * the prompt to issue to the console, may be null. + * @return a line that is read from the terminal, or null if there was null + * input (e.g., <i>CTRL-D</i> was pressed). + */ + public String readLine(final String prompt, final Character mask) + throws IOException { + this.mask = mask; + if (prompt != null) + this.prompt = prompt; + + try { + terminal.beforeReadLine(this, this.prompt, mask); + + if ((this.prompt != null) && (this.prompt.length() > 0)) { + out.write(this.prompt); + out.flush(); + } + + // if the terminal is unsupported, just use plain-java reading + if (!terminal.isSupported()) { + return readLine(in); + } + + while (true) { + int[] next = readBinding(); + + if (next == null) { + return null; + } + + int c = next[0]; + int code = next[1]; + + if (c == -1) { + return null; + } + + boolean success = true; + + switch (code) { + case EXIT: // ctrl-d + + if (buf.buffer.length() == 0) { + return null; + } + break; + + case COMPLETE: // tab + success = complete(); + break; + + case MOVE_TO_BEG: + success = setCursorPosition(0); + break; + + case KILL_LINE: // CTRL-K + success = killLine(); + break; + + case CLEAR_SCREEN: // CTRL-L + success = clearScreen(); + break; + + case KILL_LINE_PREV: // CTRL-U + success = resetLine(); + break; + + case NEWLINE: // enter + moveToEnd(); + printNewline(); // output newline + return finishBuffer(); + + case DELETE_PREV_CHAR: // backspace + success = backspace(); + break; + + case DELETE_NEXT_CHAR: // delete + success = deleteCurrentCharacter(); + break; + + case MOVE_TO_END: + success = moveToEnd(); + break; + + case PREV_CHAR: + success = moveCursor(-1) != 0; + break; + + case NEXT_CHAR: + success = moveCursor(1) != 0; + break; + + case NEXT_HISTORY: + success = moveHistory(true); + break; + + case PREV_HISTORY: + success = moveHistory(false); + break; + + case REDISPLAY: + break; + + case PASTE: + success = paste(); + break; + + case DELETE_PREV_WORD: + success = deletePreviousWord(); + break; + + case PREV_WORD: + success = previousWord(); + break; + + case NEXT_WORD: + success = nextWord(); + break; + + case START_OF_HISTORY: + success = history.moveToFirstEntry(); + if (success) + setBuffer(history.current()); + break; + + case END_OF_HISTORY: + success = history.moveToLastEntry(); + if (success) + setBuffer(history.current()); + break; + + case CLEAR_LINE: + moveInternal(-(buf.buffer.length())); + killLine(); + break; + + case INSERT: + buf.setOvertyping(!buf.isOvertyping()); + break; + + case UNKNOWN: + default: + if (c != 0) { // ignore null chars + ActionListener action = (ActionListener) triggeredActions.get(new Character((char)c)); + if (action != null) + action.actionPerformed(null); + else + putChar(c, true); + } else + success = false; + } + + if (!(success)) { + beep(); + } + + flushConsole(); + } + } finally { + terminal.afterReadLine(this, this.prompt, mask); + } + } + + private String readLine(InputStream in) throws IOException { + StringBuffer buf = new StringBuffer(); + + while (true) { + int i = in.read(); + + if ((i == -1) || (i == '\n') || (i == '\r')) { + return buf.toString(); + } + + buf.append((char) i); + } + + // return new BufferedReader (new InputStreamReader (in)).readLine (); + } + + /** + * Reads the console input and returns an array of the form [raw, key + * binding]. + */ + private int[] readBinding() throws IOException { + int c = readVirtualKey(); + + if (c == -1) { + return null; + } + + // extract the appropriate key binding + short code = keybindings[c]; + + if (debugger != null) { + debug(" translated: " + (int) c + ": " + code); + } + + return new int[] { c, code }; + } + + /** + * Move up or down the history tree. + */ + private final 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; + } + + /** + * 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) { + } + } + + 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 = null; + + for (BufferedReader read = new BufferedReader((Reader) content); (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 ufe) { + if (debugger != null) + debug(ufe + ""); + + return false; + } + } + + /** + * 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); + + 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 + printString(((char) 27) + "[2J"); + flushConsole(); + + // then send the ANSI code to go to position 1,1 + printString(((char) 27) + "[1;1H"); + flushConsole(); + + redrawLine(); + + return true; + } + + /** + * Use the completors to modify the buffer with the appropriate completions. + * + * @return true if successful + */ + private final boolean complete() throws IOException { + // debug ("tab for (" + buf + ")"); + if (completors.size() == 0) { + return false; + } + + List<String> candidates = new LinkedList<String>(); + String bufstr = buf.buffer.toString(); + int cursor = buf.cursor; + + int position = -1; + + for (Iterator i = completors.iterator(); i.hasNext();) { + Completor comp = (Completor) i.next(); + + if ((position = comp.complete(bufstr, cursor, candidates)) != -1) { + break; + } + } + + // no candidates? Fail. + if (candidates.size() == 0) { + return false; + } + + return completionHandler.complete(this, candidates, position); + } + + public CursorBuffer getCursorBuffer() { + return buf; + } + + /** + * Output the specified {@link Collection} in proper columns. + * + * @param stuff + * the stuff to print + */ + public void printColumns(final Collection stuff) throws IOException { + if ((stuff == null) || (stuff.size() == 0)) { + return; + } + + int width = getTermwidth(); + int maxwidth = 0; + + for (Iterator i = stuff.iterator(); i.hasNext(); maxwidth = Math.max( + maxwidth, i.next().toString().length())) { + ; + } + + StringBuffer line = new StringBuffer(); + + int showLines; + + if (usePagination) + showLines = getTermheight() - 1; // page limit + else + showLines = Integer.MAX_VALUE; + + for (Iterator i = stuff.iterator(); i.hasNext();) { + String cur = (String) i.next(); + + if ((line.length() + maxwidth) > width) { + printString(line.toString().trim()); + printNewline(); + line.setLength(0); + if (--showLines == 0) { // Overflow + printString(loc.getString("display-more")); + flushConsole(); + int c = readVirtualKey(); + if (c == '\r' || c == '\n') + showLines = 1; // one step forward + else if (c != 'q') + showLines = getTermheight() - 1; // page forward + + back(loc.getString("display-more").length()); + if (c == 'q') + break; // cancel + } + } + + pad(cur, maxwidth + 3, line); + } + + if (line.length() > 0) { + printString(line.toString().trim()); + printNewline(); + line.setLength(0); + } + } + + /** + * Append <i>toPad</i> to the specified <i>appendTo</i>, as well as (<i>toPad.length () - + * len</i>) spaces. + * + * @param toPad + * the {@link String} to pad + * @param len + * the target length + * @param appendTo + * the {@link StringBuffer} to which to append the padded + * {@link String}. + */ + private final void pad(final String toPad, final int len, + final StringBuffer appendTo) { + appendTo.append(toPad); + + for (int i = 0; i < (len - toPad.length()); i++, appendTo.append(' ')) { + ; + } + } + + /** + * Add the specified {@link Completor} to the list of handlers for + * tab-completion. + * + * @param completor + * the {@link Completor} to add + * @return true if it was successfully added + */ + public boolean addCompletor(final Completor completor) { + return completors.add(completor); + } + + /** + * Remove the specified {@link Completor} from the list of handlers for + * tab-completion. + * + * @param completor + * the {@link Completor} to remove + * @return true if it was successfully removed + */ + public boolean removeCompletor(final Completor completor) { + return completors.remove(completor); + } + + /** + * Returns an unmodifiable list of all the completors. + */ + public Collection<Completor> getCompletors() { + return Collections.unmodifiableList(completors); + } + + /** + * Erase the current line. + * + * @return false if we failed (e.g., the buffer was empty) + */ + final boolean resetLine() throws IOException { + if (buf.cursor == 0) { + return false; + } + + backspaceAll(); + + return true; + } + + /** + * Move the cursor position to the specified absolute index. + */ + public final boolean setCursorPosition(final int position) + throws IOException { + 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 final 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.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 + } + + /** + * Clear the line and redraw it. + */ + public final void redrawLine() throws IOException { + printCharacter(RESET_LINE); + flushConsole(); + drawLine(); + } + + /** + * Output put the prompt + the current buffer + */ + public final void drawLine() throws IOException { + if (prompt != null) { + printString(prompt); + } + + printString(buf.buffer.toString()); + + if (buf.length() != buf.cursor) // not at end of line + back(buf.length() - buf.cursor); // sync + } + + /** + * Output a platform-dependant newline. + */ + public final void printNewline() throws IOException { + printString(CR); + flushConsole(); + } + + /** + * Clear the buffer and add its contents to the history. + * + * @return the former contents of the buffer. + */ + final String finishBuffer() { + String str = buf.buffer.toString(); + + // 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 && useHistory) { + history.addToHistory(str); + } else { + mask = null; + } + } + + history.moveToEnd(); + + buf.buffer.setLength(0); + buf.cursor = 0; + + return str; + } + + /** + * Write out the specified string to the buffer and the output stream. + */ + public final void putString(final String str) throws IOException { + buf.write(str); + printString(str); + drawBuffer(); + } + + /** + * Output the specified string to the output stream (but not the buffer). + */ + public final void printString(final String str) throws IOException { + printCharacters(str.toCharArray()); + } + + /** + * Output the specified character, both to the buffer and the output stream. + */ + private final void putChar(final int c, final boolean print) + throws IOException { + buf.write((char) c); + + if (print) { + // no masking... + if (mask == null) { + printCharacter(c); + } + // null mask: don't print anything... + else if (mask.charValue() == 0) { + ; + } + // otherwise print the mask... + else { + printCharacter(mask.charValue()); + } + + 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 final void drawBuffer(final int clear) throws IOException { + // debug ("drawBuffer: " + clear); + char[] chars = buf.buffer.substring(buf.cursor).toCharArray(); + if (mask != null) + Arrays.fill(chars, mask.charValue()); + + printCharacters(chars); + + clearAhead(clear); + back(chars.length); + flushConsole(); + } + + /** + * Redraw the rest of the buffer from the cursor onwards. This is necessary + * for inserting text into the buffer. + */ + private final void drawBuffer() throws IOException { + drawBuffer(0); + } + + /** + * Clear ahead the specified number of characters without moving the cursor. + */ + private final void clearAhead(final int num) throws IOException { + if (num == 0) { + return; + } + + // debug ("clearAhead: " + num); + + // print blank extra characters + printCharacters(' ', num); + + // we need to flush here so a "clever" console + // doesn't just ignore the redundancy of a space followed by + // a backspace. + flushConsole(); + + // reset the visual cursor + back(num); + + flushConsole(); + } + + /** + * Move the visual cursor backwards without modifying the buffer cursor. + */ + private final void back(final int num) throws IOException { + printCharacters(BACKSPACE, num); + flushConsole(); + } + + /** + * Issue an audible keyboard bell, if {@link #getBellEnabled} return true. + */ + public final void beep() throws IOException { + if (!(getBellEnabled())) { + return; + } + + printCharacter(KEYBOARD_BELL); + // need to flush so the console actually beeps + flushConsole(); + } + + /** + * Output the specified character to the output stream without manipulating + * the current buffer. + */ + private final void printCharacter(final int c) throws IOException { + if (c == '\t') { + char cbuf[] = new char[TAB_WIDTH]; + Arrays.fill(cbuf, ' '); + out.write(cbuf); + return; + } + + out.write(c); + } + + /** + * Output the specified characters to the output stream without manipulating + * the current buffer. + */ + private final void printCharacters(final char[] c) throws IOException { + int len = 0; + for (int i = 0; i < c.length; i++) + if (c[i] == '\t') + len += TAB_WIDTH; + else + len++; + + char cbuf[]; + if (len == c.length) + cbuf = c; + else { + cbuf = new char[len]; + int pos = 0; + for (int i = 0; i < c.length; i++){ + if (c[i] == '\t') { + Arrays.fill(cbuf, pos, pos + TAB_WIDTH, ' '); + pos += TAB_WIDTH; + } else { + cbuf[pos] = c[i]; + pos++; + } + } + } + + out.write(cbuf); + } + + private final void printCharacters(final char c, final int num) + throws IOException { + if (num == 1) { + printCharacter(c); + } else { + char[] chars = new char[num]; + Arrays.fill(chars, c); + printCharacters(chars); + } + } + + /** + * 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 immedately. + */ + public final void flushConsole() throws IOException { + out.flush(); + } + + private final int backspaceAll() throws IOException { + return backspace(Integer.MAX_VALUE); + } + + /** + * Issue <em>num</em> backspaces. + * + * @return the number of characters backed up + */ + private final int backspace(final int num) throws IOException { + if (buf.cursor == 0) { + return 0; + } + + int count = 0; + + count = moveCursor(-1 * num) * -1; + // debug ("Deleting from " + buf.cursor + " for " + count); + buf.buffer.delete(buf.cursor, buf.cursor + count); + drawBuffer(count); + + return count; + } + + /** + * Issue a backspace. + * + * @return true if successful + */ + public final boolean backspace() throws IOException { + return backspace(1) == 1; + } + + private final boolean moveToEnd() throws IOException { + if (moveCursor(1) == 0) { + return false; + } + + while (moveCursor(1) != 0) { + ; + } + + return true; + } + + /** + * Delete the character at the current position and redraw the remainder of + * the buffer. + */ + private final boolean deleteCurrentCharacter() throws IOException { + boolean success = buf.buffer.length() > 0; + if (!success) { + return false; + } + + if (buf.cursor == buf.buffer.length()) { + return false; + } + + buf.buffer.deleteCharAt(buf.cursor); + drawBuffer(1); + return true; + } + + private final boolean previousWord() throws IOException { + while (isDelimiter(buf.current()) && (moveCursor(-1) != 0)) { + ; + } + + while (!isDelimiter(buf.current()) && (moveCursor(-1) != 0)) { + ; + } + + return true; + } + + private final boolean nextWord() throws IOException { + while (isDelimiter(buf.current()) && (moveCursor(1) != 0)) { + ; + } + + while (!isDelimiter(buf.current()) && (moveCursor(1) != 0)) { + ; + } + + return true; + } + + private final boolean deletePreviousWord() throws IOException { + while (isDelimiter(buf.current()) && backspace()) { + ; + } + + while (!isDelimiter(buf.current()) && backspace()) { + ; + } + + return true; + } + + /** + * Move the cursor <i>where</i> characters. + * + * @param where + * if less than 0, move abs(<i>where</i>) to the left, + * otherwise move <i>where</i> to the right. + * + * @return the number of spaces we moved + */ + public final 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; + } + + /** + * debug. + * + * @param str + * the message to issue. + */ + public static void debug(final String str) { + if (debugger != null) { + debugger.println(str); + debugger.flush(); + } + } + + /** + * Move the cursor <i>where</i> characters, withough checking the current + * buffer. + * + * @see #where + * + * @param where + * the number of characters to move to the right or left. + */ + private final void moveInternal(final int where) throws IOException { + // debug ("move cursor " + where + " (" + // + buf.cursor + " => " + (buf.cursor + where) + ")"); + buf.cursor += where; + + char c; + + if (where < 0) { + int len = 0; + for (int i = buf.cursor; i < buf.cursor - where; i++){ + if (buf.getBuffer().charAt(i) == '\t') + len += TAB_WIDTH; + else + len++; + } + + char cbuf[] = new char[len]; + Arrays.fill(cbuf, BACKSPACE); + out.write(cbuf); + + return; + } else if (buf.cursor == 0) { + return; + } else if (mask != null) { + c = mask.charValue(); + } else { + printCharacters(buf.buffer.substring(buf.cursor - where, buf.cursor).toCharArray()); + return; + } + + // null character mask: don't output anything + if (NULL_MASK.equals(mask)) { + return; + } + + printCharacters(c, Math.abs(where)); + } + + /** + * Read a character from the console. + * + * @return the character, or -1 if an EOF is received. + */ + public final int readVirtualKey() throws IOException { + int c = terminal.readVirtualKey(in); + + if (debugger != null) { + debug("keystroke: " + c + ""); + } + + // clear any echo characters + clearEcho(c); + + return c; + } + + 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) readVirtualKey()) < 0) + ; + + return c; + } + + + /** + * Issue <em>num</em> deletes. + * + * @return the number of characters backed up + */ + private final int delete (final int num) + throws IOException + { + /* Commented out beacuse of DWA-2949: + if (buf.cursor == 0) + return 0;*/ + + buf.buffer.delete (buf.cursor, buf.cursor + 1); + drawBuffer (1); + + return 1; + } + + public final boolean replace(int num, 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; + } + + /** + * Issue a delete. + * + * @return true if successful + */ + public final boolean delete () + throws IOException + { + return delete (1) == 1; + } + + + public void setHistory(final History history) { + this.history = history; + } + + public History getHistory() { + return this.history; + } + + public void setCompletionHandler(final CompletionHandler completionHandler) { + this.completionHandler = completionHandler; + } + + public CompletionHandler getCompletionHandler() { + return this.completionHandler; + } + + /** + * <p> + * Set the echo character. For example, to have "*" entered when a password + * is typed: + * </p> + * + * <pre> + * myConsoleReader.setEchoCharacter(new Character('*')); + * </pre> + * + * <p> + * Setting the character to + * + * <pre> + * null + * </pre> + * + * will restore normal character echoing. Setting the character to + * + * <pre> + * new Character(0) + * </pre> + * + * will cause nothing to be echoed. + * </p> + * + * @param echoCharacter + * the character to echo to the console in place of the typed + * character. + */ + public void setEchoCharacter(final Character echoCharacter) { + this.echoCharacter = echoCharacter; + } + + /** + * Returns the echo character. + */ + public Character getEchoCharacter() { + return this.echoCharacter; + } + + /** + * No-op for exceptions we want to silently consume. + */ + private void consumeException(final Throwable e) { + } + + /** + * 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(char c) { + return !Character.isLetterOrDigit(c); + } + + /** + * Whether or not to add new commands to the history buffer. + */ + public void setUseHistory(boolean useHistory) { + this.useHistory = useHistory; + } + + /** + * Whether or not to add new commands to the history buffer. + */ + public boolean getUseHistory() { + return useHistory; + } + + /** + * Whether to use pagination when the number of rows of candidates exceeds + * the height of the temrinal. + */ + public void setUsePagination(boolean usePagination) { + this.usePagination = usePagination; + } + + /** + * Whether to use pagination when the number of rows of candidates exceeds + * the height of the temrinal. + */ + public boolean getUsePagination() { + return this.usePagination; + } + +} diff --git a/src/jline/src/main/java/jline/ConsoleReaderInputStream.java b/src/jline/src/main/java/jline/ConsoleReaderInputStream.java new file mode 100644 index 0000000000..d2a14d8e72 --- /dev/null +++ b/src/jline/src/main/java/jline/ConsoleReaderInputStream.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import java.io.*; +import java.util.*; + +/** + * An {@link InputStream} implementation that wraps a {@link ConsoleReader}. + * It is useful for setting up the {@link System#in} for a generic + * console. + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public class ConsoleReaderInputStream extends SequenceInputStream { + private static InputStream systemIn = System.in; + + public static void setIn() throws IOException { + setIn(new ConsoleReader()); + } + + public static void setIn(final ConsoleReader reader) { + System.setIn(new ConsoleReaderInputStream(reader)); + } + + /** + * Restore the original {@link System#in} input stream. + */ + public static void restoreIn() { + System.setIn(systemIn); + } + + public ConsoleReaderInputStream(final ConsoleReader reader) { + super(new ConsoleEnumeration(reader)); + } + + private static class ConsoleEnumeration implements Enumeration<InputStream> { + private final ConsoleReader reader; + private ConsoleLineInputStream next = null; + private ConsoleLineInputStream prev = null; + + public ConsoleEnumeration(final ConsoleReader reader) { + this.reader = reader; + } + + public InputStream nextElement() { + if (next != null) { + InputStream n = next; + prev = next; + next = null; + + return n; + } + + return new ConsoleLineInputStream(reader); + } + + public boolean hasMoreElements() { + // the last line was null + if ((prev != null) && (prev.wasNull == true)) { + return false; + } + + if (next == null) { + next = (ConsoleLineInputStream) nextElement(); + } + + return next != null; + } + } + + private static class ConsoleLineInputStream extends InputStream { + private final ConsoleReader reader; + private String line = null; + private int index = 0; + private boolean eol = false; + protected boolean wasNull = false; + + public ConsoleLineInputStream(final ConsoleReader reader) { + this.reader = reader; + } + + public int read() throws IOException { + if (eol) { + return -1; + } + + if (line == null) { + line = reader.readLine(); + } + + if (line == null) { + wasNull = true; + return -1; + } + + if (index >= line.length()) { + eol = true; + return '\n'; // lines are ended with a newline + } + + return line.charAt(index++); + } + } +} diff --git a/src/jline/src/main/java/jline/ConsoleRunner.java b/src/jline/src/main/java/jline/ConsoleRunner.java new file mode 100644 index 0000000000..dac4b7c88a --- /dev/null +++ b/src/jline/src/main/java/jline/ConsoleRunner.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import java.io.*; +import java.util.*; + +/** + * <p> + * A pass-through application that sets the system input stream to a + * {@link ConsoleReader} and invokes the specified main method. + * </p> + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public class ConsoleRunner { + public static final String property = "jline.history"; + + public static void main(final String[] args) throws Exception { + String historyFileName = null; + + List<String> argList = new ArrayList<String>(Arrays.asList(args)); + + if (argList.size() == 0) { + usage(); + + return; + } + + historyFileName = System.getProperty(ConsoleRunner.property, null); + + // invoke the main() method + String mainClass = (String) argList.remove(0); + + // setup the inpout stream + ConsoleReader reader = new ConsoleReader(); + + if (historyFileName != null) { + reader.setHistory(new History (new File + (System.getProperty("user.home"), + ".jline-" + mainClass + + "." + historyFileName + ".history"))); + } else { + reader.setHistory(new History(new File + (System.getProperty("user.home"), + ".jline-" + mainClass + ".history"))); + } + + String completors = System.getProperty + (ConsoleRunner.class.getName() + ".completors", ""); + List<Completor> completorList = new ArrayList<Completor>(); + + for (StringTokenizer tok = new StringTokenizer(completors, ","); + tok.hasMoreTokens();) { + completorList.add + ((Completor) Class.forName(tok.nextToken()).newInstance()); + } + + if (completorList.size() > 0) { + reader.addCompletor(new ArgumentCompletor(completorList)); + } + + ConsoleReaderInputStream.setIn(reader); + + try { + Class.forName(mainClass). + getMethod("main", new Class[] { String[].class }). + invoke(null, new Object[] { argList.toArray(new String[0]) }); + } finally { + // just in case this main method is called from another program + ConsoleReaderInputStream.restoreIn(); + } + } + + private static void usage() { + System.out.println("Usage: \n java " + "[-Djline.history='name'] " + + ConsoleRunner.class.getName() + + " <target class name> [args]" + + "\n\nThe -Djline.history option will avoid history" + + "\nmangling when running ConsoleRunner on the same application." + + "\n\nargs will be passed directly to the target class name."); + } +} diff --git a/src/jline/src/main/java/jline/CursorBuffer.java b/src/jline/src/main/java/jline/CursorBuffer.java new file mode 100644 index 0000000000..3aec20af51 --- /dev/null +++ b/src/jline/src/main/java/jline/CursorBuffer.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +/** + * A CursorBuffer is a holder for a {@link StringBuffer} that also contains the + * current cursor position. + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public class CursorBuffer { + public int cursor = 0; + + StringBuffer buffer = new StringBuffer(); + + private boolean overtyping = false; + + public int length() { + return buffer.length(); + } + + public char current() { + if (cursor <= 0) { + return 0; + } + + return buffer.charAt(cursor - 1); + } + + public boolean clearBuffer() { + if (buffer.length() == 0) { + return false; + } + + buffer.delete(0, buffer.length()); + cursor = 0; + return true; + } + + /** + * 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 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 {@link String} into the buffer, setting the cursor + * to the end of the insertion point. + * + * @param str + * the String to insert. Must not be null. + */ + public void write(final String 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 String toString() { + return buffer.toString(); + } + + public boolean isOvertyping() { + return overtyping; + } + + public void setOvertyping(boolean b) { + overtyping = b; + } + + public StringBuffer getBuffer() { + return buffer; + } + + public void setBuffer(StringBuffer buffer) { + buffer.setLength(0); + buffer.append(this.buffer.toString()); + + this.buffer = buffer; + } + + +} diff --git a/src/jline/src/main/java/jline/FileNameCompletor.java b/src/jline/src/main/java/jline/FileNameCompletor.java new file mode 100644 index 0000000000..d1d63735d1 --- /dev/null +++ b/src/jline/src/main/java/jline/FileNameCompletor.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import java.io.*; +import java.util.*; + +/** + * A file name completor takes the buffer and issues a list of + * potential completions. + * + * <p> + * This completor tries to behave as similar as possible to + * <i>bash</i>'s file name completion (using GNU readline) + * with the following exceptions: + * + * <ul> + * <li>Candidates that are directories will end with "/"</li> + * <li>Wildcard regular expressions are not evaluated or replaced</li> + * <li>The "~" character can be used to represent the user's home, + * but it cannot complete to other users' homes, since java does + * not provide any way of determining that easily</li> + * </ul> + * + * <p>TODO</p> + * <ul> + * <li>Handle files with spaces in them</li> + * <li>Have an option for file type color highlighting</li> + * </ul> + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public class FileNameCompletor implements Completor { + public int complete(final String buf, final int cursor, + final List<String> candidates) { + String buffer = (buf == null) ? "" : buf; + + String translated = buffer; + + // special character: ~ maps to the user's home directory + if (translated.startsWith("~" + File.separator)) { + translated = System.getProperty("user.home") + + translated.substring(1); + } else if (translated.startsWith("~")) { + translated = new File(System.getProperty("user.home")).getParentFile() + .getAbsolutePath(); + } else if (!(translated.startsWith(File.separator))) { + translated = new File("").getAbsolutePath() + File.separator + + translated; + } + + File f = new File(translated); + + final File dir; + + if (translated.endsWith(File.separator)) { + dir = f; + } else { + dir = f.getParentFile(); + } + + final File[] entries = (dir == null) ? new File[0] : dir.listFiles(); + + try { + return matchFiles(buffer, translated, entries, candidates); + } finally { + // we want to output a sorted list of files + sortFileNames(candidates); + } + } + + protected void sortFileNames(final List<String> fileNames) { + Collections.sort(fileNames); + } + + /** + * Match the specified <i>buffer</i> to the array of <i>entries</i> + * and enter the matches into the list of <i>candidates</i>. This method + * can be overridden in a subclass that wants to do more + * sophisticated file name completion. + * + * @param buffer the untranslated buffer + * @param translated the buffer with common characters replaced + * @param entries the list of files to match + * @param candidates the list of candidates to populate + * + * @return the offset of the match + */ + public int matchFiles(String buffer, String translated, File[] entries, + List<String> candidates) { + if (entries == null) { + return -1; + } + + int matches = 0; + + // first pass: just count the matches + for (int i = 0; i < entries.length; i++) { + if (entries[i].getAbsolutePath().startsWith(translated)) { + matches++; + } + } + + // green - executable + // blue - directory + // red - compressed + // cyan - symlink + for (int i = 0; i < entries.length; i++) { + if (entries[i].getAbsolutePath().startsWith(translated)) { + String name = + entries[i].getName() + + (((matches == 1) && entries[i].isDirectory()) + ? File.separator : " "); + + /* + if (entries [i].isDirectory ()) + { + name = new ANSIBuffer ().blue (name).toString (); + } + */ + candidates.add(name); + } + } + + final int index = buffer.lastIndexOf(File.separator); + + return index + File.separator.length(); + } +} diff --git a/src/jline/src/main/java/jline/History.java b/src/jline/src/main/java/jline/History.java new file mode 100644 index 0000000000..9e96f69146 --- /dev/null +++ b/src/jline/src/main/java/jline/History.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import java.io.*; +import java.util.*; + +/** + * A command history buffer. + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public class History { + private List<String> history = new ArrayList<String>(); + + private PrintWriter output = null; + + private int maxSize = 500; + + private int currentIndex = 0; + + /** + * Construstor: initialize a blank history. + */ + public History() { + } + + /** + * Construstor: initialize History object the the specified {@link File} for + * storage. + */ + public History(final File historyFile) throws IOException { + setHistoryFile(historyFile); + } + + public void setHistoryFile(final File historyFile) throws IOException { + if (historyFile.isFile()) { + load(new FileInputStream(historyFile)); + } + + setOutput(new PrintWriter(new FileWriter(historyFile), true)); + flushBuffer(); + } + + /** + * Load the history buffer from the specified InputStream. + */ + public void load(final InputStream in) throws IOException { + load(new InputStreamReader(in)); + } + + /** + * Load the history buffer from the specified Reader. + */ + public void load(final Reader reader) throws IOException { + BufferedReader breader = new BufferedReader(reader); + List<String> lines = new ArrayList<String>(); + String line; + + while ((line = breader.readLine()) != null) { + lines.add(line); + } + + for (Iterator i = lines.iterator(); i.hasNext();) { + addToHistory((String) i.next()); + } + } + + public int size() { + return history.size(); + } + + /** + * Clear the history buffer + */ + public void clear() { + history.clear(); + currentIndex = 0; + } + + /** + * Add the specified buffer to the end of the history. The pointer is set to + * the end of the history buffer. + */ + public void addToHistory(final String buffer) { + // don't append duplicates to the end of the buffer + if ((history.size() != 0) + && buffer.equals(history.get(history.size() - 1))) { + return; + } + + history.add(buffer); + + while (history.size() > getMaxSize()) { + history.remove(0); + } + + currentIndex = history.size(); + + if (getOutput() != null) { + getOutput().println(buffer); + getOutput().flush(); + } + } + + /** + * Flush the entire history buffer to the output PrintWriter. + */ + public void flushBuffer() throws IOException { + if (getOutput() != null) { + for (Iterator i = history.iterator(); i.hasNext(); getOutput() + .println((String) i.next())) { + ; + } + + getOutput().flush(); + } + } + + /** + * 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 moveToLastEntry() { + int lastEntry = history.size() - 1; + if (lastEntry >= 0 && lastEntry != currentIndex) { + currentIndex = history.size() - 1; + 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() { + currentIndex = history.size(); + } + + /** + * Set the maximum size that the history buffer will store. + */ + public void setMaxSize(final int maxSize) { + this.maxSize = maxSize; + } + + /** + * Get the maximum size that the history buffer will store. + */ + public int getMaxSize() { + return this.maxSize; + } + + /** + * The output to which all history elements will be written (or null of + * history is not saved to a buffer). + */ + public void setOutput(final PrintWriter output) { + this.output = output; + } + + /** + * Returns the PrintWriter that is used to store history elements. + */ + public PrintWriter getOutput() { + return this.output; + } + + /** + * Returns the current history index. + */ + public int getCurrentIndex() { + return this.currentIndex; + } + + /** + * Return the content of the current buffer. + */ + public String current() { + if (currentIndex >= history.size()) { + return ""; + } + + return (String) history.get(currentIndex); + } + + /** + * Move the pointer to the previous element in the buffer. + * + * @return true if we successfully went to the previous element + */ + public boolean previous() { + if (currentIndex <= 0) { + return false; + } + + currentIndex--; + + 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 (currentIndex >= history.size()) { + return false; + } + + currentIndex++; + + return true; + } + + /** + * Returns an immutable list of the history buffer. + */ + public List<String> getHistoryList() { + return Collections.unmodifiableList(history); + } + + /** + * Returns the standard {@link AbstractCollection#toString} representation + * of the history list. + */ + public String toString() { + return history.toString(); + } + + /** + * 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 moveToFirstEntry() { + if (history.size() > 0 && currentIndex != 0) { + currentIndex = 0; + return true; + } + + return false; + } +} diff --git a/src/jline/src/main/java/jline/MultiCompletor.java b/src/jline/src/main/java/jline/MultiCompletor.java new file mode 100644 index 0000000000..2d43a204cc --- /dev/null +++ b/src/jline/src/main/java/jline/MultiCompletor.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import java.util.*; + +/** + * <p> + * A completor that contains multiple embedded completors. This differs + * from the {@link ArgumentCompletor}, in that the nested completors + * are dispatched individually, rather than delimited by arguments. + * </p> + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public class MultiCompletor implements Completor { + Completor[] completors = new Completor[0]; + + /** + * Construct a MultiCompletor with no embedded completors. + */ + public MultiCompletor() { + this(new Completor[0]); + } + + /** + * Construct a MultiCompletor with the specified list of + * {@link Completor} instances. + */ + public MultiCompletor(final List<Completor> completors) { + this((Completor[]) completors.toArray(new Completor[completors.size()])); + } + + /** + * Construct a MultiCompletor with the specified + * {@link Completor} instances. + */ + public MultiCompletor(final Completor[] completors) { + this.completors = completors; + } + + public int complete(final String buffer, final int pos, final List<String> cand) { + int[] positions = new int[completors.length]; + List<List<String>> copies = new ArrayList<List<String>>(); + for (int i = 0; i < completors.length; i++) { + copies.add(null); + } + + for (int i = 0; i < completors.length; i++) { + // clone and save the candidate list + copies.set(i, new LinkedList<String>(cand)); + positions[i] = completors[i].complete(buffer, pos, copies.get(i)); + } + + int maxposition = -1; + + for (int i = 0; i < positions.length; i++) { + maxposition = Math.max(maxposition, positions[i]); + } + + // now we have the max cursor value: build up all the + // candidate lists that have the same cursor value + for (int i = 0; i < copies.size(); i++) { + if (positions[i] == maxposition) { + cand.addAll(copies.get(i)); + } + } + + return maxposition; + } + + public void setCompletors(final Completor[] completors) { + this.completors = completors; + } + + public Completor[] getCompletors() { + return this.completors; + } +} diff --git a/src/jline/src/main/java/jline/NullCompletor.java b/src/jline/src/main/java/jline/NullCompletor.java new file mode 100644 index 0000000000..aa6cdf744e --- /dev/null +++ b/src/jline/src/main/java/jline/NullCompletor.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import java.util.*; + +/** + * <p> + * A completor that does nothing. Useful as the last item in an + * {@link ArgumentCompletor}. + * </p> + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public class NullCompletor implements Completor { + /** + * Returns -1 always, indicating that the the buffer is never + * handled. + */ + public int complete(final String buffer, int cursor, List candidates) { + return -1; + } +} diff --git a/src/jline/src/main/java/jline/SimpleCompletor.java b/src/jline/src/main/java/jline/SimpleCompletor.java new file mode 100644 index 0000000000..3581277409 --- /dev/null +++ b/src/jline/src/main/java/jline/SimpleCompletor.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import java.io.*; +import java.util.*; + +/** + * <p> + * A simple {@link Completor} implementation that handles a pre-defined + * list of completion words. + * </p> + * + * <p> + * Example usage: + * </p> + * <pre> + * myConsoleReader.addCompletor (new SimpleCompletor (new String [] { "now", "yesterday", "tomorrow" })); + * </pre> + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public class SimpleCompletor implements Completor, Cloneable { + /** + * The list of candidates that will be completed. + */ + SortedSet<String> candidates; + + /** + * A delimiter to use to qualify completions. + */ + String delimiter; + final SimpleCompletorFilter filter; + + /** + * Create a new SimpleCompletor with a single possible completion + * values. + */ + public SimpleCompletor(final String candidateString) { + this(new String[] { + candidateString + }); + } + + /** + * Create a new SimpleCompletor with a list of possible completion + * values. + */ + public SimpleCompletor(final String[] candidateStrings) { + this(candidateStrings, null); + } + + public SimpleCompletor(final String[] strings, + final SimpleCompletorFilter filter) { + this.filter = filter; + setCandidateStrings(strings); + } + + /** + * Complete candidates using the contents of the specified Reader. + */ + public SimpleCompletor(final Reader reader) throws IOException { + this(getStrings(reader)); + } + + /** + * Complete candidates using the whitespearated values in + * read from the specified Reader. + */ + public SimpleCompletor(final InputStream in) throws IOException { + this(getStrings(new InputStreamReader(in))); + } + + private static String[] getStrings(final Reader in) + throws IOException { + final Reader reader = + (in instanceof BufferedReader) ? in : new BufferedReader(in); + + List<String> words = new LinkedList<String>(); + String line; + + while ((line = ((BufferedReader) reader).readLine()) != null) { + for (StringTokenizer tok = new StringTokenizer(line); + tok.hasMoreTokens(); words.add(tok.nextToken())) { + ; + } + } + + return (String[]) words.toArray(new String[words.size()]); + } + + public int complete(final String buffer, final int cursor, final List<String> clist) { + String start = (buffer == null) ? "" : buffer; + + SortedSet<String> matches = candidates.tailSet(start); + + for (Iterator i = matches.iterator(); i.hasNext();) { + String can = (String) i.next(); + + if (!(can.startsWith(start))) { + break; + } + + if (delimiter != null) { + int index = can.indexOf(delimiter, cursor); + + if (index != -1) { + can = can.substring(0, index + 1); + } + } + + clist.add(can); + } + + if (clist.size() == 1) { + clist.set(0, ((String) clist.get(0)) + " "); + } + + // the index of the completion is always from the beginning of + // the buffer. + return (clist.size() == 0) ? (-1) : 0; + } + + public void setDelimiter(final String delimiter) { + this.delimiter = delimiter; + } + + public String getDelimiter() { + return this.delimiter; + } + + public void setCandidates(final SortedSet<String> candidates) { + if (filter != null) { + TreeSet<String> filtered = new TreeSet<String>(); + + for (Iterator i = candidates.iterator(); i.hasNext();) { + String element = (String) i.next(); + element = filter.filter(element); + + if (element != null) { + filtered.add(element); + } + } + + this.candidates = filtered; + } else { + this.candidates = candidates; + } + } + + public SortedSet getCandidates() { + return Collections.unmodifiableSortedSet(this.candidates); + } + + public void setCandidateStrings(final String[] strings) { + setCandidates(new TreeSet<String>(Arrays.asList(strings))); + } + + public void addCandidateString(final String candidateString) { + final String string = + (filter == null) ? candidateString : filter.filter(candidateString); + + if (string != null) { + candidates.add(string); + } + } + + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + /** + * Filter for elements in the completor. + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ + public static interface SimpleCompletorFilter { + /** + * Filter the specified String. To not filter it, return the + * same String as the parameter. To exclude it, return null. + */ + public String filter(String element); + } + + public static class NoOpFilter implements SimpleCompletorFilter { + public String filter(final String element) { + return element; + } + } +} diff --git a/src/jline/src/main/java/jline/Terminal.java b/src/jline/src/main/java/jline/Terminal.java new file mode 100644 index 0000000000..53a5aff4d4 --- /dev/null +++ b/src/jline/src/main/java/jline/Terminal.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import java.io.*; + +/** + * Representation of the input terminal for a platform. Handles + * any initialization that the platform may need to perform + * in order to allow the {@link ConsoleReader} to correctly handle + * input. + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public abstract class Terminal implements ConsoleOperations { + private static Terminal term; + + /** + * @see #setupTerminal + */ + public static Terminal getTerminal() { + return setupTerminal(); + } + + /** + * Reset the current terminal to null. + */ + public static void resetTerminal() { + term = null; + } + + /** + * <p>Configure and return the {@link Terminal} instance for the + * current platform. This will initialize any system settings + * that are required for the console to be able to handle + * input correctly, such as setting tabtop, buffered input, and + * character echo.</p> + * + * <p>This class will use the Terminal implementation specified in the + * <em>jline.terminal</em> system property, or, if it is unset, by + * detecting the operating system from the <em>os.name</em> + * system property and instantiating either the + * {@link WindowsTerminalTest} or {@link UnixTerminal}. + * + * @see #initializeTerminal + */ + public static synchronized Terminal setupTerminal() { + if (term != null) { + return term; + } + + final Terminal t; + + String os = System.getProperty("os.name").toLowerCase(); + String termProp = System.getProperty("jline.terminal"); + + if ((termProp != null) && (termProp.length() > 0)) { + try { + t = (Terminal) Class.forName(termProp).newInstance(); + } catch (Exception e) { + throw (IllegalArgumentException) new IllegalArgumentException(e + .toString()).fillInStackTrace(); + } + } else if (os.indexOf("windows") != -1) { + t = new WindowsTerminal(); + } else { + t = new UnixTerminal(); + } + + try { + t.initializeTerminal(); + } catch (Exception e) { + e.printStackTrace(); + + return term = new UnsupportedTerminal(); + } + + return term = t; + } + + /** + * Returns true if the current console supports ANSI + * codes. + */ + public boolean isANSISupported() { + return true; + } + + /** + * Read a single character from the input stream. This might + * enable a terminal implementation to better handle nuances of + * the console. + */ + public int readCharacter(final InputStream in) throws IOException { + return in.read(); + } + + /** + * Reads a virtual key from the console. Typically, this will + * just be the raw character that was entered, but in some cases, + * multiple input keys will need to be translated into a single + * virtual key. + * + * @param in the InputStream to read from + * @return the virtual key (e.g., {@link ConsoleOperations#VK_UP}) + */ + public int readVirtualKey(InputStream in) throws IOException { + return readCharacter(in); + } + + /** + * Initialize any system settings + * that are required for the console to be able to handle + * input correctly, such as setting tabtop, buffered input, and + * character echo. + */ + public abstract void initializeTerminal() throws Exception; + + /** + * Returns the current width of the terminal (in characters) + */ + public abstract int getTerminalWidth(); + + /** + * Returns the current height of the terminal (in lines) + */ + public abstract int getTerminalHeight(); + + /** + * Returns true if this terminal is capable of initializing the + * terminal to use jline. + */ + public abstract boolean isSupported(); + + /** + * Returns true if the terminal will echo all characters type. + */ + public abstract boolean getEcho(); + + /** + * Invokes before the console reads a line with the prompt and mask. + */ + public void beforeReadLine(ConsoleReader reader, String prompt, + Character mask) { + } + + /** + * Invokes after the console reads a line with the prompt and mask. + */ + public void afterReadLine(ConsoleReader reader, String prompt, + Character mask) { + } + + /** + * Returns false if character echoing is disabled. + */ + public abstract boolean isEchoEnabled(); + + + /** + * Enable character echoing. This can be used to re-enable character + * if the ConsoleReader is no longer being used. + */ + public abstract void enableEcho(); + + + /** + * Disable character echoing. This can be used to manually re-enable + * character if the ConsoleReader has been disabled. + */ + public abstract void disableEcho(); + + public InputStream getDefaultBindings() { + return Terminal.class.getResourceAsStream("keybindings.properties"); + } +} diff --git a/src/jline/src/main/java/jline/UnixTerminal.java b/src/jline/src/main/java/jline/UnixTerminal.java new file mode 100644 index 0000000000..e9f76053ff --- /dev/null +++ b/src/jline/src/main/java/jline/UnixTerminal.java @@ -0,0 +1,428 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import java.io.*; +import java.util.*; + +/** + * <p> + * Terminal that is used for unix platforms. Terminal initialization + * is handled by issuing the <em>stty</em> command against the + * <em>/dev/tty</em> file to disable character echoing and enable + * character input. All known unix systems (including + * Linux and Macintosh OS X) support the <em>stty</em>), so this + * implementation should work for an reasonable POSIX system. + * </p> + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + * @author Updates <a href="mailto:dwkemp@gmail.com">Dale Kemp</a> 2005-12-03 + */ +public class UnixTerminal extends Terminal { + public static final short ARROW_START = 27; + public static final short ARROW_PREFIX = 91; + public static final short ARROW_LEFT = 68; + public static final short ARROW_RIGHT = 67; + public static final short ARROW_UP = 65; + public static final short ARROW_DOWN = 66; + public static final short O_PREFIX = 79; + public static final short HOME_CODE = 72; + public static final short END_CODE = 70; + + public static final short DEL_THIRD = 51; + public static final short DEL_SECOND = 126; + + private Map terminfo; + private boolean echoEnabled; + private String ttyConfig; + private boolean backspaceDeleteSwitched = false; + private static String sttyCommand = + System.getProperty("jline.sttyCommand", "stty"); + + + String encoding = System.getProperty("input.encoding", "UTF-8"); + ReplayPrefixOneCharInputStream replayStream = new ReplayPrefixOneCharInputStream(encoding); + InputStreamReader replayReader; + + public UnixTerminal() { + try { + replayReader = new InputStreamReader(replayStream, encoding); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + protected void checkBackspace(){ + String[] ttyConfigSplit = ttyConfig.split(":|="); + + if (ttyConfigSplit.length < 7) + return; + + if (ttyConfigSplit[6] == null) + return; + + backspaceDeleteSwitched = ttyConfigSplit[6].equals("7f"); + } + + /** + * Remove line-buffered input by invoking "stty -icanon min 1" + * against the current terminal. + */ + public void initializeTerminal() throws IOException, InterruptedException { + // save the initial tty configuration + ttyConfig = stty("-g"); + + // sanity check + if ((ttyConfig.length() == 0) + || ((ttyConfig.indexOf("=") == -1) + && (ttyConfig.indexOf(":") == -1))) { + throw new IOException("Unrecognized stty code: " + ttyConfig); + } + + checkBackspace(); + + // set the console to be character-buffered instead of line-buffered + stty("-icanon min 1"); + + // disable character echoing + stty("-echo"); + echoEnabled = false; + + // at exit, restore the original tty configuration (for JDK 1.3+) + try { + Runtime.getRuntime().addShutdownHook(new Thread() { + public void start() { + try { + restoreTerminal(); + } catch (Exception e) { + consumeException(e); + } + } + }); + } catch (AbstractMethodError ame) { + // JDK 1.3+ only method. Bummer. + consumeException(ame); + } + } + + /** + * Restore the original terminal configuration, which can be used when + * shutting down the console reader. The ConsoleReader cannot be + * used after calling this method. + */ + public void restoreTerminal() throws Exception { + if (ttyConfig != null) { + stty(ttyConfig); + ttyConfig = null; + } + resetTerminal(); + } + + + + public int readVirtualKey(InputStream in) throws IOException { + int c = readCharacter(in); + + if (backspaceDeleteSwitched) + if (c == DELETE) + c = '\b'; + else if (c == '\b') + c = DELETE; + + // in Unix terminals, arrow keys are represented by + // a sequence of 3 characters. E.g., the up arrow + // key yields 27, 91, 68 + if (c == ARROW_START) { + //also the escape key is 27 + //thats why we read until we + //have something different than 27 + //this is a bugfix, because otherwise + //pressing escape and than an arrow key + //was an undefined state + while (c == ARROW_START) + c = readCharacter(in); + if (c == ARROW_PREFIX || c == O_PREFIX) { + c = readCharacter(in); + if (c == ARROW_UP) { + return CTRL_P; + } else if (c == ARROW_DOWN) { + return CTRL_N; + } else if (c == ARROW_LEFT) { + return CTRL_B; + } else if (c == ARROW_RIGHT) { + return CTRL_F; + } else if (c == HOME_CODE) { + return CTRL_A; + } else if (c == END_CODE) { + return CTRL_E; + } else if (c == DEL_THIRD) { + c = readCharacter(in); // read 4th + return DELETE; + } + } + } + // handle unicode characters, thanks for a patch from amyi@inf.ed.ac.uk + if (c > 128) { + // handle unicode characters longer than 2 bytes, + // thanks to Marc.Herbert@continuent.com + replayStream.setInput(c, in); +// replayReader = new InputStreamReader(replayStream, encoding); + c = replayReader.read(); + + } + + return c; + } + + /** + * No-op for exceptions we want to silently consume. + */ + private void consumeException(Throwable e) { + } + + public boolean isSupported() { + return true; + } + + public boolean getEcho() { + return false; + } + + /** + * Returns the value of "stty size" width param. + * + * <strong>Note</strong>: this method caches the value from the + * first time it is called in order to increase speed, which means + * that changing to size of the terminal will not be reflected + * in the console. + */ + public int getTerminalWidth() { + int val = -1; + + try { + val = getTerminalProperty("columns"); + } catch (Exception e) { + } + + if (val == -1) { + val = 80; + } + + return val; + } + + /** + * Returns the value of "stty size" height param. + * + * <strong>Note</strong>: this method caches the value from the + * first time it is called in order to increase speed, which means + * that changing to size of the terminal will not be reflected + * in the console. + */ + public int getTerminalHeight() { + int val = -1; + + try { + val = getTerminalProperty("rows"); + } catch (Exception e) { + } + + if (val == -1) { + val = 24; + } + + return val; + } + + private static int getTerminalProperty(String prop) + throws IOException, InterruptedException { + // need to be able handle both output formats: + // speed 9600 baud; 24 rows; 140 columns; + // and: + // speed 38400 baud; rows = 49; columns = 111; ypixels = 0; xpixels = 0; + String props = stty("-a"); + + for (StringTokenizer tok = new StringTokenizer(props, ";\n"); + tok.hasMoreTokens();) { + String str = tok.nextToken().trim(); + + if (str.startsWith(prop)) { + int index = str.lastIndexOf(" "); + + return Integer.parseInt(str.substring(index).trim()); + } else if (str.endsWith(prop)) { + int index = str.indexOf(" "); + + return Integer.parseInt(str.substring(0, index).trim()); + } + } + + return -1; + } + + /** + * Execute the stty command with the specified arguments + * against the current active terminal. + */ + private static String stty(final String args) + throws IOException, InterruptedException { + return exec("stty " + args + " < /dev/tty").trim(); + } + + /** + * Execute the specified command and return the output + * (both stdout and stderr). + */ + private static String exec(final String cmd) + throws IOException, InterruptedException { + return exec(new String[] { + "sh", + "-c", + cmd + }); + } + + /** + * Execute the specified command and return the output + * (both stdout and stderr). + */ + private static String exec(final String[] cmd) + throws IOException, InterruptedException { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + + Process p = Runtime.getRuntime().exec(cmd); + int c; + InputStream in; + + in = p.getInputStream(); + + while ((c = in.read()) != -1) { + bout.write(c); + } + + in = p.getErrorStream(); + + while ((c = in.read()) != -1) { + bout.write(c); + } + + p.waitFor(); + + String result = new String(bout.toByteArray()); + + return result; + } + + /** + * The command to use to set the terminal options. Defaults + * to "stty", or the value of the system property "jline.sttyCommand". + */ + public static void setSttyCommand(String cmd) { + sttyCommand = cmd; + } + + /** + * The command to use to set the terminal options. Defaults + * to "stty", or the value of the system property "jline.sttyCommand". + */ + public static String getSttyCommand() { + return sttyCommand; + } + + public synchronized boolean isEchoEnabled() { + return echoEnabled; + } + + + public synchronized void enableEcho() { + try { + stty("echo"); + echoEnabled = true; + } catch (Exception e) { + consumeException(e); + } + } + + public synchronized void disableEcho() { + try { + stty("-echo"); + echoEnabled = false; + } catch (Exception e) { + consumeException(e); + } + } + + /** + * This is awkward and inefficient, but probably the minimal way to add + * UTF-8 support to JLine + * + * @author <a href="mailto:Marc.Herbert@continuent.com">Marc Herbert</a> + */ + static class ReplayPrefixOneCharInputStream extends InputStream { + byte firstByte; + int byteLength; + InputStream wrappedStream; + int byteRead; + + final String encoding; + + public ReplayPrefixOneCharInputStream(String encoding) { + this.encoding = encoding; + } + + public void setInput(int recorded, InputStream wrapped) throws IOException { + this.byteRead = 0; + this.firstByte = (byte) recorded; + this.wrappedStream = wrapped; + + byteLength = 1; + if (encoding.equalsIgnoreCase("UTF-8")) + setInputUTF8(recorded, wrapped); + else if (encoding.equalsIgnoreCase("UTF-16")) + byteLength = 2; + else if (encoding.equalsIgnoreCase("UTF-32")) + byteLength = 4; + } + + + public void setInputUTF8(int recorded, InputStream wrapped) throws IOException { + // 110yyyyy 10zzzzzz + if ((firstByte & (byte) 0xE0) == (byte) 0xC0) + this.byteLength = 2; + // 1110xxxx 10yyyyyy 10zzzzzz + else if ((firstByte & (byte) 0xF0) == (byte) 0xE0) + this.byteLength = 3; + // 11110www 10xxxxxx 10yyyyyy 10zzzzzz + else if ((firstByte & (byte) 0xF8) == (byte) 0xF0) + this.byteLength = 4; + else + throw new IOException("invalid UTF-8 first byte: " + firstByte); + } + + public int read() throws IOException { + if (available() == 0) + return -1; + + byteRead++; + + if (byteRead == 1) + return firstByte; + + return wrappedStream.read(); + } + + /** + * InputStreamReader is greedy and will try to read bytes in advance. We + * do NOT want this to happen since we use a temporary/"losing bytes" + * InputStreamReader above, that's why we hide the real + * wrappedStream.available() here. + */ + public int available() { + return byteLength - byteRead; + } + } +} diff --git a/src/jline/src/main/java/jline/UnsupportedTerminal.java b/src/jline/src/main/java/jline/UnsupportedTerminal.java new file mode 100644 index 0000000000..2d87a18f6c --- /dev/null +++ b/src/jline/src/main/java/jline/UnsupportedTerminal.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import java.io.IOException; + +/** + * A no-op unsupported terminal. + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public class UnsupportedTerminal extends Terminal { + private Thread maskThread = null; + + public void initializeTerminal() { + // nothing we need to do (or can do) for windows. + } + + public boolean getEcho() { + return true; + } + + + public boolean isEchoEnabled() { + return true; + } + + + public void enableEcho() { + } + + + public void disableEcho() { + } + + + /** + * Always returng 80, since we can't access this info on Windows. + */ + public int getTerminalWidth() { + return 80; + } + + /** + * Always returng 24, since we can't access this info on Windows. + */ + public int getTerminalHeight() { + return 80; + } + + public boolean isSupported() { + return false; + } + + public void beforeReadLine(final ConsoleReader reader, final String prompt, + final Character mask) { + if ((mask != null) && (maskThread == null)) { + final String fullPrompt = "\r" + prompt + + " " + + " " + + " " + + "\r" + prompt; + + maskThread = new Thread("JLine Mask Thread") { + public void run() { + while (!interrupted()) { + try { + reader.out.write(fullPrompt); + reader.out.flush(); + sleep(3); + } catch (IOException ioe) { + return; + } catch (InterruptedException ie) { + return; + } + } + } + }; + + maskThread.setPriority(Thread.MAX_PRIORITY); + maskThread.setDaemon(true); + maskThread.start(); + } + } + + public void afterReadLine(final ConsoleReader reader, final String prompt, + final Character mask) { + if ((maskThread != null) && maskThread.isAlive()) { + maskThread.interrupt(); + } + + maskThread = null; + } +} diff --git a/src/jline/src/main/java/jline/WindowsTerminal.java b/src/jline/src/main/java/jline/WindowsTerminal.java new file mode 100644 index 0000000000..5d603cbcf0 --- /dev/null +++ b/src/jline/src/main/java/jline/WindowsTerminal.java @@ -0,0 +1,513 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import java.io.*; + +import jline.UnixTerminal.ReplayPrefixOneCharInputStream; + +/** + * <p> + * Terminal implementation for Microsoft Windows. Terminal initialization in + * {@link #initializeTerminal} is accomplished by extracting the + * <em>jline_<i>version</i>.dll</em>, saving it to the system temporary + * directoy (determined by the setting of the <em>java.io.tmpdir</em> System + * property), loading the library, and then calling the Win32 APIs <a + * href="http://msdn.microsoft.com/library/default.asp? + * url=/library/en-us/dllproc/base/setconsolemode.asp">SetConsoleMode</a> and + * <a href="http://msdn.microsoft.com/library/default.asp? + * url=/library/en-us/dllproc/base/getconsolemode.asp">GetConsoleMode</a> to + * disable character echoing. + * </p> + * + * <p> + * By default, the {@link #readCharacter} method will attempt to test to see if + * the specified {@link InputStream} is {@link System#in} or a wrapper around + * {@link FileDescriptor#in}, and if so, will bypass the character reading to + * directly invoke the readc() method in the JNI library. This is so the class + * can read special keys (like arrow keys) which are otherwise inaccessible via + * the {@link System#in} stream. Using JNI reading can be bypassed by setting + * the <code>jline.WindowsTerminal.disableDirectConsole</code> system property + * to <code>true</code>. + * </p> + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public class WindowsTerminal extends Terminal { + // constants copied from wincon.h + + /** + * 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. + */ + private static final int 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. + */ + private static final int 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. + */ + private static final int 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. + */ + private static final int 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. + */ + private static final int 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. + */ + private static final int 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. + */ + private static final int ENABLE_WRAP_AT_EOL_OUTPUT = 2; + + /** + * 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. + */ + public static final int SPECIAL_KEY_INDICATOR = 224; + + /** + * On windows terminals, this character indicates that a special key on the + * number pad has been pressed. + */ + public static final int NUMPAD_KEY_INDICATOR = 0; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR, + * this character indicates an left arrow key press. + */ + public static final int LEFT_ARROW_KEY = 75; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR + * this character indicates an + * right arrow key press. + */ + public static final int RIGHT_ARROW_KEY = 77; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR + * this character indicates an up + * arrow key press. + */ + public static final int UP_ARROW_KEY = 72; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR + * this character indicates an + * down arrow key press. + */ + public static final int DOWN_ARROW_KEY = 80; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR + * this character indicates that + * the delete key was pressed. + */ + public static final int DELETE_KEY = 83; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR + * this character indicates that + * the home key was pressed. + */ + public static final int HOME_KEY = 71; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR + * this character indicates that + * the end key was pressed. + */ + public static final char END_KEY = 79; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR + * this character indicates that + * the page up key was pressed. + */ + public static final char PAGE_UP_KEY = 73; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR + * this character indicates that + * the page down key was pressed. + */ + public static final char PAGE_DOWN_KEY = 81; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR + * this character indicates that + * the insert key was pressed. + */ + public static final char INSERT_KEY = 82; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR, + * this character indicates that the escape key was pressed. + */ + public static final char ESCAPE_KEY = 0; + + private Boolean directConsole; + + private boolean echoEnabled; + + String encoding = System.getProperty("jline.WindowsTerminal.input.encoding", System.getProperty("file.encoding")); + ReplayPrefixOneCharInputStream replayStream = new ReplayPrefixOneCharInputStream(encoding); + InputStreamReader replayReader; + + public WindowsTerminal() { + String dir = System.getProperty("jline.WindowsTerminal.directConsole"); + + if ("true".equals(dir)) { + directConsole = Boolean.TRUE; + } else if ("false".equals(dir)) { + directConsole = Boolean.FALSE; + } + + try { + replayReader = new InputStreamReader(replayStream, encoding); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + private native int getConsoleMode(); + + private native void setConsoleMode(final int mode); + + private native int readByte(); + + private native int getWindowsTerminalWidth(); + + private native int getWindowsTerminalHeight(); + + public int readCharacter(final InputStream in) throws IOException { + // if we can detect that we are directly wrapping the system + // input, then bypass the input stream and read directly (which + // allows us to access otherwise unreadable strokes, such as + // the arrow keys) + if (directConsole == Boolean.FALSE) { + return super.readCharacter(in); + } else if ((directConsole == Boolean.TRUE) + || ((in == System.in) || (in instanceof FileInputStream + && (((FileInputStream) in).getFD() == FileDescriptor.in)))) { + return readByte(); + } else { + return super.readCharacter(in); + } + } + + public void initializeTerminal() throws Exception { + loadLibrary("jline"); + + final int originalMode = getConsoleMode(); + + setConsoleMode(originalMode & ~ENABLE_ECHO_INPUT); + + // set the console to raw mode + int newMode = originalMode + & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT + | ENABLE_PROCESSED_INPUT | ENABLE_WINDOW_INPUT); + echoEnabled = false; + setConsoleMode(newMode); + + // at exit, restore the original tty configuration (for JDK 1.3+) + try { + Runtime.getRuntime().addShutdownHook(new Thread() { + public void start() { + // restore the old console mode + setConsoleMode(originalMode); + } + }); + } catch (AbstractMethodError ame) { + // JDK 1.3+ only method. Bummer. + consumeException(ame); + } + } + + private void loadLibrary(final String name) throws IOException { + // store the DLL in the temporary directory for the System + String version = getClass().getPackage().getImplementationVersion(); + + if (version == null) { + version = ""; + } + + version = version.replace('.', '_'); + + File f = new File(System.getProperty("java.io.tmpdir"), name + "_" + + version + ".dll"); + boolean exists = f.isFile(); // check if it already exists + + // extract the embedded jline.dll file from the jar and save + // it to the current directory + int bits = 32; + + // check for 64-bit systems and use to appropriate DLL + if (System.getProperty("os.arch").indexOf("64") != -1) + bits = 64; + + InputStream in = new BufferedInputStream(getClass() + .getResourceAsStream(name + bits + ".dll")); + + try { + OutputStream fout = new BufferedOutputStream( + new FileOutputStream(f)); + byte[] bytes = new byte[1024 * 10]; + + for (int n = 0; n != -1; n = in.read(bytes)) { + fout.write(bytes, 0, n); + } + + fout.close(); + } catch (IOException ioe) { + // We might get an IOException trying to overwrite an existing + // jline.dll file if there is another process using the DLL. + // If this happens, ignore errors. + if (!exists) { + throw ioe; + } + } + + // try to clean up the DLL after the JVM exits + f.deleteOnExit(); + + // now actually load the DLL + System.load(f.getAbsolutePath()); + } + + public int readVirtualKey(InputStream in) throws IOException { + int indicator = readCharacter(in); + + // in Windows terminals, arrow keys are represented by + // a sequence of 2 characters. E.g., the up arrow + // key yields 224, 72 + if (indicator == SPECIAL_KEY_INDICATOR + || indicator == NUMPAD_KEY_INDICATOR) { + int key = readCharacter(in); + + switch (key) { + case UP_ARROW_KEY: + return CTRL_P; // translate UP -> CTRL-P + case LEFT_ARROW_KEY: + return CTRL_B; // translate LEFT -> CTRL-B + case RIGHT_ARROW_KEY: + return CTRL_F; // translate RIGHT -> CTRL-F + case DOWN_ARROW_KEY: + return CTRL_N; // translate DOWN -> CTRL-N + case DELETE_KEY: + return CTRL_QM; // translate DELETE -> CTRL-? + case HOME_KEY: + return CTRL_A; + case END_KEY: + return CTRL_E; + case PAGE_UP_KEY: + return CTRL_K; + case PAGE_DOWN_KEY: + return CTRL_L; + case ESCAPE_KEY: + return CTRL_OB; // translate ESCAPE -> CTRL-[ + case INSERT_KEY: + return CTRL_C; + default: + return 0; + } + } else if (indicator > 128) { + // handle unicode characters longer than 2 bytes, + // thanks to Marc.Herbert@continuent.com + replayStream.setInput(indicator, in); + // replayReader = new InputStreamReader(replayStream, encoding); + indicator = replayReader.read(); + + } + + return indicator; + + } + + public boolean isSupported() { + return true; + } + + /** + * Windows doesn't support ANSI codes by default; disable them. + */ + public boolean isANSISupported() { + return false; + } + + public boolean getEcho() { + return false; + } + + /** + * Unsupported; return the default. + * + * @see Terminal#getTerminalWidth + */ + public int getTerminalWidth() { + return getWindowsTerminalWidth(); + } + + /** + * Unsupported; return the default. + * + * @see Terminal#getTerminalHeight + */ + public int getTerminalHeight() { + return getWindowsTerminalHeight(); + } + + /** + * No-op for exceptions we want to silently consume. + */ + private void consumeException(final Throwable e) { + } + + /** + * Whether or not to allow the use of the JNI console interaction. + */ + public void setDirectConsole(Boolean directConsole) { + this.directConsole = directConsole; + } + + /** + * Whether or not to allow the use of the JNI console interaction. + */ + public Boolean getDirectConsole() { + return this.directConsole; + } + + public synchronized boolean isEchoEnabled() { + return echoEnabled; + } + + public synchronized void enableEcho() { + // Must set these four modes at the same time to make it work fine. + setConsoleMode(getConsoleMode() | ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT + | ENABLE_PROCESSED_INPUT | ENABLE_WINDOW_INPUT); + echoEnabled = true; + } + + public synchronized void disableEcho() { + // Must set these four modes at the same time to make it work fine. + setConsoleMode(getConsoleMode() + & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT + | ENABLE_PROCESSED_INPUT | ENABLE_WINDOW_INPUT)); + echoEnabled = true; + } + + public InputStream getDefaultBindings() { + return getClass().getResourceAsStream("windowsbindings.properties"); + } + + /** + * This is awkward and inefficient, but probably the minimal way to add + * UTF-8 support to JLine + * + * @author <a href="mailto:Marc.Herbert@continuent.com">Marc Herbert</a> + */ + static class ReplayPrefixOneCharInputStream extends InputStream { + byte firstByte; + int byteLength; + InputStream wrappedStream; + int byteRead; + + final String encoding; + + public ReplayPrefixOneCharInputStream(String encoding) { + this.encoding = encoding; + } + + public void setInput(int recorded, InputStream wrapped) throws IOException { + this.byteRead = 0; + this.firstByte = (byte) recorded; + this.wrappedStream = wrapped; + + byteLength = 1; + if (encoding.equalsIgnoreCase("UTF-8")) + setInputUTF8(recorded, wrapped); + else if (encoding.equalsIgnoreCase("UTF-16")) + byteLength = 2; + else if (encoding.equalsIgnoreCase("UTF-32")) + byteLength = 4; + } + + + public void setInputUTF8(int recorded, InputStream wrapped) throws IOException { + // 110yyyyy 10zzzzzz + if ((firstByte & (byte) 0xE0) == (byte) 0xC0) + this.byteLength = 2; + // 1110xxxx 10yyyyyy 10zzzzzz + else if ((firstByte & (byte) 0xF0) == (byte) 0xE0) + this.byteLength = 3; + // 11110www 10xxxxxx 10yyyyyy 10zzzzzz + else if ((firstByte & (byte) 0xF8) == (byte) 0xF0) + this.byteLength = 4; + else + throw new IOException("invalid UTF-8 first byte: " + firstByte); + } + + public int read() throws IOException { + if (available() == 0) + return -1; + + byteRead++; + + if (byteRead == 1) + return firstByte; + + return wrappedStream.read(); + } + + /** + * InputStreamReader is greedy and will try to read bytes in advance. We + * do NOT want this to happen since we use a temporary/"losing bytes" + * InputStreamReader above, that's why we hide the real + * wrappedStream.available() here. + */ + public int available() { + return byteLength - byteRead; + } + } + +} diff --git a/src/jline/src/main/java/jline/package.html b/src/jline/src/main/java/jline/package.html new file mode 100644 index 0000000000..c80743165a --- /dev/null +++ b/src/jline/src/main/java/jline/package.html @@ -0,0 +1,9 @@ +<body> +<p> +The core JLine API. The central class is +<a href="ConsoleReader">jline.ConsoleReader</a>}, which +is a reader for obtaining input from an arbitrary +InputStream (usually <em>System.in</em>). +</p> +</body> + diff --git a/src/jline/src/main/native/Makefile b/src/jline/src/main/native/Makefile new file mode 100644 index 0000000000..c620814825 --- /dev/null +++ b/src/jline/src/main/native/Makefile @@ -0,0 +1,8 @@ + + +#export PATH=${PATH}:/usr/lib/gcc-lib/i686-pc-cygwin/3.3.3 +JDK='/C/Program Files/Java/jdk1.5.0/' + +native: + #gcc -I'C:/Program Files/Java/'*'/include/' -I'C:/Program Files/Java/'*'/include//win32/' -mno-cygwin -Wl,--add-stdcall-alias -shared -o jline.dll jline_WindowsTerminal.c + gcc -L /usr/lib/mingw/ -I${JDK}/include -I${JDK}/include/win32 -mwindows -mno-cygwin -Wl,--add-stdcall-alias -shared -o jline.dll jline_WindowsTerminal.c diff --git a/src/jline/src/main/native/jline_WindowsTerminal.c b/src/jline/src/main/native/jline_WindowsTerminal.c new file mode 100644 index 0000000000..f020cae52b --- /dev/null +++ b/src/jline/src/main/native/jline_WindowsTerminal.c @@ -0,0 +1,57 @@ +#include "jline_WindowsTerminal.h"
+#include <windows.h>
+
+JNIEXPORT jint JNICALL Java_jline_WindowsTerminal_getConsoleMode
+ (JNIEnv *env, jobject ob)
+{
+ DWORD mode;
+ HANDLE hConsole = GetStdHandle (STD_INPUT_HANDLE);
+
+ if (hConsole == INVALID_HANDLE_VALUE)
+ return -1;
+
+ if (!GetConsoleMode (hConsole, &mode))
+ return -1;
+
+ // CloseHandle (hConsole);
+
+ // printf ("JNI get mode=%d\n", mode);
+ return mode;
+}
+
+JNIEXPORT void JNICALL Java_jline_WindowsTerminal_setConsoleMode
+ (JNIEnv *env, jobject ob, jint mode)
+{
+ DWORD m = (DWORD)mode;
+ HANDLE hConsole = GetStdHandle (STD_INPUT_HANDLE);
+
+ if (hConsole == INVALID_HANDLE_VALUE)
+ return;
+
+ // printf ("JNI set mode=%d\n", m);
+ SetConsoleMode (hConsole, m);
+ // CloseHandle (hConsole);
+}
+
+JNIEXPORT jint JNICALL Java_jline_WindowsTerminal_readByte (JNIEnv * env, jclass class)
+{
+ return getch ();
+}
+
+JNIEXPORT jint JNICALL Java_jline_WindowsTerminal_getWindowsTerminalWidth (JNIEnv * env, jclass class)
+{
+ HANDLE inputHandle = GetStdHandle (STD_INPUT_HANDLE);
+ HANDLE outputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
+ PCONSOLE_SCREEN_BUFFER_INFO info = malloc (sizeof (CONSOLE_SCREEN_BUFFER_INFO));
+ GetConsoleScreenBufferInfo (outputHandle, info);
+ return info->srWindow.Right - info->srWindow.Left+1;
+}
+
+JNIEXPORT jint JNICALL Java_jline_WindowsTerminal_getWindowsTerminalHeight (JNIEnv * env, jclass class)
+{
+ HANDLE inputHandle = GetStdHandle (STD_INPUT_HANDLE);
+ HANDLE outputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
+ PCONSOLE_SCREEN_BUFFER_INFO info = malloc (sizeof (CONSOLE_SCREEN_BUFFER_INFO));
+ GetConsoleScreenBufferInfo (outputHandle, info);
+ return info->srWindow.Bottom - info->srWindow.Top+1;
+}
diff --git a/src/jline/src/main/native/jline_WindowsTerminal.h b/src/jline/src/main/native/jline_WindowsTerminal.h new file mode 100644 index 0000000000..5078b939cb --- /dev/null +++ b/src/jline/src/main/native/jline_WindowsTerminal.h @@ -0,0 +1,68 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include <jni.h> +/* Header for class jline_WindowsTerminal */ + +#ifndef _Included_jline_WindowsTerminal +#define _Included_jline_WindowsTerminal +#ifdef __cplusplus +extern "C" { +#endif +/* Inaccessible static: term */ +#undef jline_WindowsTerminal_ENABLE_LINE_INPUT +#define jline_WindowsTerminal_ENABLE_LINE_INPUT 2L +#undef jline_WindowsTerminal_ENABLE_ECHO_INPUT +#define jline_WindowsTerminal_ENABLE_ECHO_INPUT 4L +#undef jline_WindowsTerminal_ENABLE_PROCESSED_INPUT +#define jline_WindowsTerminal_ENABLE_PROCESSED_INPUT 1L +#undef jline_WindowsTerminal_ENABLE_WINDOW_INPUT +#define jline_WindowsTerminal_ENABLE_WINDOW_INPUT 8L +#undef jline_WindowsTerminal_ENABLE_MOUSE_INPUT +#define jline_WindowsTerminal_ENABLE_MOUSE_INPUT 16L +#undef jline_WindowsTerminal_ENABLE_PROCESSED_OUTPUT +#define jline_WindowsTerminal_ENABLE_PROCESSED_OUTPUT 1L +#undef jline_WindowsTerminal_ENABLE_WRAP_AT_EOL_OUTPUT +#define jline_WindowsTerminal_ENABLE_WRAP_AT_EOL_OUTPUT 2L +/* + * Class: jline_WindowsTerminal + * Method: getConsoleMode + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_jline_WindowsTerminal_getConsoleMode + (JNIEnv *, jobject); + +/* + * Class: jline_WindowsTerminal + * Method: setConsoleMode + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_jline_WindowsTerminal_setConsoleMode + (JNIEnv *, jobject, jint); + +/* + * Class: jline_WindowsTerminal + * Method: readByte + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_jline_WindowsTerminal_readByte + (JNIEnv *, jobject); + +/* + * Class: jline_WindowsTerminal + * Method: getWindowsTerminalWidth + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_jline_WindowsTerminal_getWindowsTerminalWidth + (JNIEnv *, jobject); + +/* + * Class: jline_WindowsTerminal + * Method: getWindowsTerminalHeight + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_jline_WindowsTerminal_getWindowsTerminalHeight + (JNIEnv *, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/jline/src/main/resources/jline/CandidateListCompletionHandler.properties b/src/jline/src/main/resources/jline/CandidateListCompletionHandler.properties new file mode 100644 index 0000000000..18ee221cc9 --- /dev/null +++ b/src/jline/src/main/resources/jline/CandidateListCompletionHandler.properties @@ -0,0 +1,5 @@ +display-candidates: Display all {0} possibilities? (y or n) +display-candidates-yes: y +display-candidates-no: n +display-more: --More-- + diff --git a/src/jline/src/main/resources/jline/jline32.dll b/src/jline/src/main/resources/jline/jline32.dll Binary files differnew file mode 100644 index 0000000000..a0d3b117ce --- /dev/null +++ b/src/jline/src/main/resources/jline/jline32.dll diff --git a/src/jline/src/main/resources/jline/jline64.dll b/src/jline/src/main/resources/jline/jline64.dll Binary files differnew file mode 100644 index 0000000000..922d6b01c5 --- /dev/null +++ b/src/jline/src/main/resources/jline/jline64.dll diff --git a/src/jline/src/main/resources/jline/keybindings.properties b/src/jline/src/main/resources/jline/keybindings.properties new file mode 100644 index 0000000000..6f13615d84 --- /dev/null +++ b/src/jline/src/main/resources/jline/keybindings.properties @@ -0,0 +1,62 @@ +# Keybinding mapping for JLine. The format is: +# [key code]: [logical operation] + +# CTRL-B: move to the previous character +2: PREV_CHAR + +# CTRL-G: move to the previous word +7: PREV_WORD + +# CTRL-F: move to the next character +6: NEXT_CHAR + +# CTRL-A: move to the beginning of the line +1: MOVE_TO_BEG + +# CTRL-D: close out the input stream +4: EXIT + +# CTRL-E: move the cursor to the end of the line +5: MOVE_TO_END + +# BACKSPACE, CTRL-H: delete the previous character +# 8 is the ASCII code for backspace and therefor +# deleting the previous character +8: DELETE_PREV_CHAR + +# TAB, CTRL-I: signal that console completion should be attempted +9: COMPLETE + +# CTRL-J, CTRL-M: newline +10: NEWLINE + +# CTRL-K: erase the current line +11: KILL_LINE + +# ENTER: newline +13: NEWLINE + +# CTRL-L: clear screen +12: CLEAR_SCREEN + +# CTRL-N: scroll to the next element in the history buffer +14: NEXT_HISTORY + +# CTRL-P: scroll to the previous element in the history buffer +16: PREV_HISTORY + +# CTRL-R: redraw the current line +18: REDISPLAY + +# CTRL-U: delete all the characters before the cursor position +21: KILL_LINE_PREV + +# CTRL-V: paste the contents of the clipboard (useful for Windows terminal) +22: PASTE + +# CTRL-W: delete the word directly before the cursor +23: DELETE_PREV_WORD + +# DELETE, CTRL-?: delete the previous character +# 127 is the ASCII code for delete +127: DELETE_PREV_CHAR diff --git a/src/jline/src/main/resources/jline/windowsbindings.properties b/src/jline/src/main/resources/jline/windowsbindings.properties new file mode 100644 index 0000000000..51ba4919c9 --- /dev/null +++ b/src/jline/src/main/resources/jline/windowsbindings.properties @@ -0,0 +1,65 @@ +# Keybinding mapping for JLine. The format is: +# [key code]: [logical operation] + +# CTRL-B: move to the previous character +2: PREV_CHAR + +# CTRL-C: toggle overtype mode (frankly, I wasn't sure where to bind this) +3: INSERT + +# CTRL-G: move to the previous word +7: PREV_WORD + +# CTRL-F: move to the next character +6: NEXT_CHAR + +# CTRL-A: move to the beginning of the line +1: MOVE_TO_BEG + +# CTRL-D: close out the input stream +4: EXIT + +# CTRL-E: move the cursor to the end of the line +5: MOVE_TO_END + +# CTRL-H: delete the previous character +8: DELETE_PREV_CHAR + +# TAB, CTRL-I: signal that console completion should be attempted +9: COMPLETE + +# CTRL-J, CTRL-M: newline +10: NEWLINE + +# CTRL-K: Vertical tab - on windows we'll move to the start of the history +11: START_OF_HISTORY + +# ENTER: newline +13: NEWLINE + +# CTRL-L: Form feed - on windows, we'll move to the end of the history +12: END_OF_HISTORY + +# CTRL-N: scroll to the next element in the history buffer +14: NEXT_HISTORY + +# CTRL-P: scroll to the previous element in the history buffer +16: PREV_HISTORY + +# CTRL-R: redraw the current line +18: REDISPLAY + +# CTRL-U: delete all the characters before the cursor position +21: KILL_LINE_PREV + +# CTRL-V: paste the contents of the clipboard (useful for Windows terminal) +22: PASTE + +# CTRL-W: delete the word directly before the cursor +23: DELETE_PREV_WORD + +# CTRL-[: escape - clear the current line. +27: CLEAR_LINE + +# CTRL-?: delete the previous character +127: DELETE_NEXT_CHAR diff --git a/src/jline/src/site/apt/building.apt b/src/jline/src/site/apt/building.apt new file mode 100644 index 0000000000..a09137873a --- /dev/null +++ b/src/jline/src/site/apt/building.apt @@ -0,0 +1,39 @@ + ------ + jline + ------ + +Building JLine + + Building JLine requires an installation of {{{http://maven.apache.org/}Maven 2}}. + + Source code is included in the JLine distribution, or can be checked out from CVS as described {{{source-repository.html}here}}. + +Maven Repository + + If you are using Maven 2, you can add JLine as an automatic dependency by adding the following to your project's pom.xml file: + ++--------------------------------+ + + <repositories> + ... + <repository> + <id>jline</id> + <name>JLine Project Repository</name> + <url>http://jline.sourceforge.net/m2repo</url> + </repository> + </repositories> + <dependencies> + ... + <dependency> + <groupId>jline</groupId> + <artifactId>jline</artifactId> + <version>0.9.9</version> + </dependency> + </dependencies> + ++--------------------------------+ + + + + + diff --git a/src/jline/src/site/apt/downloads.apt b/src/jline/src/site/apt/downloads.apt new file mode 100644 index 0000000000..de90db9595 --- /dev/null +++ b/src/jline/src/site/apt/downloads.apt @@ -0,0 +1,39 @@ + ------ + jline + ------ + +Download JLine + + JLine packages can be downloaded from: + + {{{http://sourceforge.net/project/showfiles.php?group_id=64033}http://sourceforge.net/project/showfiles.php?group_id=64033}} + + +Maven Repository + + If you are using Maven 2, you can add JLine as an automatic dependency by adding the following to your project's pom.xml file: + ++--------------------------------+ + + <repositories> + ... + <repository> + <id>jline</id> + <name>JLine Project Repository</name> + <url>http://jline.sourceforge.net/m2repo</url> + </repository> + </repositories> + <dependencies> + ... + <dependency> + <groupId>jline</groupId> + <artifactId>jline</artifactId> + <version>0.9.9</version> + </dependency> + </dependencies> + ++--------------------------------+ + + + + diff --git a/src/jline/src/site/docbook/index.xml b/src/jline/src/site/docbook/index.xml new file mode 100644 index 0000000000..c68b42f1e9 --- /dev/null +++ b/src/jline/src/site/docbook/index.xml @@ -0,0 +1,492 @@ +<!DOCTYPE book + PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" + "http://www.docbook.org/xml/4.1.2/docbookx.dtd"> + +<book> + <bookinfo> + <title> + JLine + </title> + <copyright> + <year>2002, 2003, 2004, 2005, 2006, 2007</year> + <holder>Marc Prud'hommeaux</holder> + </copyright> + </bookinfo> + <part id="manual"> + <title>JLine Manual</title> + + <chapter id="introduction"> + <title>Introduction</title> + <para> + 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. + </para> + </chapter> + + <chapter id="license"> + <title>License and Terms of Use</title> + <para> + JLine is distributed under the BSD license, meaning that + you are completely free to redistribute, modify, or sell it + with almost no restrictins. + For more information on the BSD license, see + <ulink url="http://www.opensource.org/licenses/bsd-license.php" + >http://www.opensource.org/licenses/bsd-license.php</ulink>. + </para> + <para> + For information on obtaining the software under another + license, contact the copyright holder: + <ulink url="mailto:mwp1@cornell.edu">mwp1@cornell.edu</ulink>. + </para> + </chapter> + + <chapter id="obtaining"> + <title>Obtaining JLine</title> + <para> + JLine is hosted on SourceForge, and is located at + <ulink url="http://jline.sf.net">http://jline.sf.net</ulink>. + The latest release can be downloaded from + <ulink url= + "http://sourceforge.net/project/showfiles.php?group_id=64033">http://sourceforge.net/project/showfiles.php?group_id=64033</ulink>. + API documentation can be found in the + <ulink url="apidocs">apidocs/</ulink> directory. + </para> + </chapter> + + <chapter id="installation"> + <title>Installation</title> + <para> + JLine has no library dependencies, aside from a JVM + of version 1.2 or higher. To install JLine, download the + <filename>jline.jar</filename> file, and either place it in + the system-wide java extensions directory, or + manually add it to your + <computeroutput>CLASSPATH</computeroutput>. + The extensions directory is dependent on your operating + system. Some few examples are: + <itemizedlist> + <listitem><para> + Macintosh OS X: + <filename>/Library/Java/Extensions</filename> or + <filename>/System/Library/Java/Extensions</filename> + </para></listitem> + <listitem><para> + Microsoft Windows: + <filename>JAVAHOME\jre\lib\ext</filename> + (example: + <filename>C:\j2sdk1.4.1_03\jre\lib\ext</filename>) + </para></listitem> + <listitem><para> + UNIX Systems: + <filename>JAVAHOME/jre/lib/ext</filename> + (example: + <filename>/usr/local/java/jre/lib/ext</filename>) + </para></listitem> + </itemizedlist> + </para> + <para> + JLine is not 100% pure Java. On Windows, it relies on a + <filename>.dll</filename> file to initialize the terminal + to be able to accept unbuffered input. However, + no installation is necessary for this: when initialized, + JLine will dynamically extract the DLL to a temporary + directory and load it. For more details, see the + documentation for the <classname>jline.WindowsTerminal</classname> class. + </para> + <para> + On UNIX systems (including Macintosh OS X), JLine will + execute the <filename>stty</filename> command to initialize + the terminal to allow unbuffered input. For more details, + see the documentation for the <classname>jline.UnixTerminal</classname> class. + </para> + <para> + For both Windows and UNIX systems, JLine will fail to + initialize if it is run inside a strict security manager + that does not allow the loading of libraries, writing + to the file system, or executing external programs. However, + for most console applications, this is usually not the case. + </para> + </chapter> + <chapter id="supported_platforms"> + <title>Supported Platforms</title> + <para> + JLine should work on any Windows system, or any + minimally compliant POSIX system (includling Linux and + Macintosh OS X). + </para> + <para> + The platforms on which JLine has been confirmed to work are: + <itemizedlist> + <listitem><para> + Microsoft Windows XP + </para></listitem> + <listitem><para> + RedHat Linux 9.0 + </para></listitem> + <listitem><para> + Debian Linux 3.0 + </para></listitem> + <listitem><para> + Macintosh OS X 10.3 + </para></listitem> + </itemizedlist> + </para> + <para> + Please report successes or failures to the author: + <ulink url="mailto:mwp1@cornell.edu">mwp1@cornell.edu</ulink>. + </para> + </chapter> + <chapter id="features"> + <title>Features</title> + <section id="features_history"> + <title>Command History</title> + <para> + </para> + </section> + <section id="features_completion"> + <title>Tab completion</title> + <para> + </para> + </section> + <section id="features_line_editing"> + <title>Line editing</title> + <para> + </para> + </section> + <section id="features_keybindings"> + <title>Custom Keybindings</title> + <para> + You can create your own keybindings by creating a + <filename>HOME/.jlinebindings.properties"</filename> + file. You can override the location of this file with + the "<computeroutput>jline.keybindings</computeroutput>" + system property. + </para> + <!-- + <para> + The default keybindings are as follows: + <programlisting> + </programlisting> + </para> + --> + </section> + <section id="features_masking"> + <title>Character masking</title> + <para> + </para> + </section> + </chapter> + <chapter id="api"> + <title>API</title> + <para> + This section discusses some common usages of the JLine API. + For in-depth usage of the JLine API, see the + <ulink url="apidocs">apidocs</ulink>. + </para> + <section id="reading_password"> + <title>Reading a password from the console</title> + <para> + A common task that console applications need to do is + read in a password. While it is standard for software + to not echo password strings as they are typed, + the Java core APIs surprisingly do not provide any + means to do this. + </para> + <para> + JLine can read a password with the following code: + <programlisting> +String password = new <classname>jline.ConsoleReader</classname>().readLine(new Character('*')); + </programlisting> + This will replace every character types on the console + with a star character. + </para> + <para> + Alternately, you can have it not echo password + character at all: + <programlisting> +String password = new <classname>jline.ConsoleReader</classname>().readLine(new Character(0)); + </programlisting> + </para> + <para> + The <filename>jline-demo.jar</filename> file contains + a sample application that reads the password. To run + the sample, execute: + <programlisting> +java -cp jline-demo.jar jline.example.PasswordReader "*" + </programlisting> + </para> + </section> + </chapter> + <chapter id="faq"> + <title>Frequently Asked Questions</title> + <section id="faq_unsupported_platform"><title> + Can I disable JLine if it isn't working on my platform? + </title> + <para> + You can disable JLine by setting the System property + "<computeroutput>jline.terminal</computeroutput>" + to + "<classname>jline.UnsupportedTerminal</classname>". For example: + <programlisting> +java -Djline.terminal=jline.UnsupportedTerminal jline.example.Example simple + </programlisting> + </para> + </section> + <section id="faq_custom_keybindings"><title> + How do I customize the key bindings? + </title> + <para> + You can create your own keybindings by creating a + <filename>HOME/.jlinebindings.properties"</filename> + file. You can override the location of this file with + the "<computeroutput>jline.keybindings</computeroutput>" + system property. To examine the format to use, see the + <filename>src/jline/keybindings.properties</filename> + file in the source distribution. + </para> + </section> + <section id="faq_jline_as_default"><title> + Can I use JLine as the default console input stream for + all applications? + </title> + <para> + No, but you can use the <classname>jline.ConsoleRunner</classname> application + to set up the system input stream and continue on + the launch another program. For example, to use JLine + as the input handler for the popular + <ulink url="http://www.beanshell.org">BeanShell</ulink> + console application, you can run: + <programlisting> +java <classname>jline.ConsoleRunner</classname> <ulink url="http://www.beanshell.org/manual/standalonemode.html">bsh.Interpreter</ulink> + </programlisting> + </para> + </section> + <section id="faq_jline_beanshell"><title> + Can I use JLine as the input handler for <ulink url="http://www.beanshell.org">BeanShell</ulink>? + </title> + <para> + Yes. Try running: + <programlisting> +java <classname>jline.ConsoleRunner</classname> <ulink url="http://www.beanshell.org/manual/standalonemode.html">bsh.Interpreter</ulink> + </programlisting> + </para> + </section> + <section id="faq_jline_jdb"><title> + Can I use JLine as the input handler for <ulink url="http://java.sun.com/j2se/1.3/docs/tooldocs/solaris/jdb.html">jdb</ulink> (the java debugger)? + </title> + <para> + Yes. Try running: + <programlisting> +java <classname>jline.ConsoleRunner</classname> com.sun.tools.example.debug.tty.TTY <emphasis>args</emphasis> + </programlisting> + </para> + </section> + <section id="faq_pure_java"><title> + Is JLine <trademark>100% pure Java</trademark>? + </title> + <para> + No: JLine uses a couple small native methods in the Windows + platform. On Unix, it is technically pure java, but relies + on the execution of external (non-java) programs. See the + <link linkend="installation">installation section</link> + for more details. + </para> + </section> + <section id="faq_password"><title> + How do I make it so password characters are no echoed + to the screen? + </title> + <para> + See <link linkend="reading_password"/>. + </para> + </section> + <section id="faq_cursrs"><title> + Is JLine a full-featured curses implementation? + </title> + <para> + No: JLine has no ability to position the cursor on the + console. It might someday evolve into a plausible + Java curses implementation. + </para> + </section> + </chapter> + </part> + + <appendix id="known_bugs"> + <title>Known Bugs</title> + <itemizedlist> + <listitem><para> + Clearing the screen (CTRL-L) doesn't currently work on Windows. + </para></listitem> + </itemizedlist> + </appendix> + + <appendix id="contributors"> + <title>Contributors</title> + <para> + The following people have contributed to improving JLine over the + years: + </para> + <itemizedlist> + <listitem><para> + Marc Prud'hommeaux + </para></listitem> + <listitem><para> + Damian Steer + </para></listitem> + <listitem><para> + Dale Kemp + </para></listitem> + <listitem><para> + Jun Liu + </para></listitem> + <listitem><para> + malcolm@epcc.ed.ac.uk + </para></listitem> + <listitem><para> + Simon Patarin + </para></listitem> + <listitem><para> + Amy Isard + </para></listitem> + <listitem><para> + Ryan Bell + </para></listitem> + <listitem><para> + Marc Herbert + </para></listitem> + <listitem><para> + Christian Salm + </para></listitem> + </itemizedlist> + </appendix> + + <appendix id="todo"> + <title>Future enhancements</title> + <itemizedlist> + <listitem><para> + Add localization for all strings. + </para></listitem> + <listitem><para> + Create a BNFCompletor that can handle any BNF. + </para></listitem> + </itemizedlist> + </appendix> + + <appendix id="changelog"> + <title>Change Log</title> + <itemizedlist> + <title>0.9.93 2007-11-13</title> + <listitem><para> + Fixed backspace handling on Unix/OS X. + </para></listitem> + </itemizedlist> + <itemizedlist> + <title>0.9.92 2007-10-30</title> + <listitem><para> + JLine now works with 64-bit Windows systems. + </para></listitem> + </itemizedlist> + <itemizedlist> + <title>0.9.91 2007-03-11</title> + <listitem><para> + Added ConsoleReader.setUsePagination() method which allows + configuration of pagination when the number of rows of + candidates exceeds the height of the detected terminal, thanks + to a patch by Damian Steer. + </para></listitem> + <listitem><para> + Better support for UTF-8 inputs (issue #1623521). + </para></listitem> + <listitem><para> + Improved list of supported keys on Windows (issue #1649790). + </para></listitem> + </itemizedlist> + <itemizedlist> + <title>0.9.5 2006-03-08</title> + <listitem><para> + Fixed problem with "stty" on Solaris, which doesn't + understand "stty size" to query the terminal size. It now + uses "stty -a", which supposedly outputs a POSIX standard + format. + </para></listitem> + <listitem><para> + Support HOME and END keys, thanks to a patch by + Dale Kemp. + </para></listitem> + </itemizedlist> + <itemizedlist> + <title>0.9.1 2005-01-29</title> + <listitem><para> + Fixed problem with the 0.9.0 distribution that + failed to include the Windows jline.dll in the jline.jar, + rendering it inoperable on Windows. + </para></listitem> + <listitem><para> + Implemented proper interception or arrow keys on Windows, + meaning that history can now be navigated with the UP + and DOWN keys, and line editing can take place with + the LEFT and RIGHT arrow keys. + </para></listitem> + </itemizedlist> + <itemizedlist> + <title>0.9.0 2005-01-23</title> + <listitem><para> + Changed license from GPL to BSD. + </para></listitem> + <listitem><para> + Made "CTRL-L" map to clearing the screen. + </para></listitem> + </itemizedlist> + <itemizedlist> + <title>0.8.1 2003-11-18</title> + <listitem><para> + Fixed accidental dependency on JVM 1.4. + </para></listitem> + </itemizedlist> + <itemizedlist> + <title>0.8.0 2003-11-17</title> + <listitem><para> + Windows support using a native .dll + </para></listitem> + <listitem><para> + A new ClassNameCompletor + </para></listitem> + <listitem><para> + Many doc improvements + </para></listitem> + </itemizedlist> + <itemizedlist> + <title>0.6.0 2003-07-08</title> + <listitem><para> + Many bugfixes + </para></listitem> + <listitem><para> + Better release system + </para></listitem> + <listitem><para> + Automatically set terminal property by + issuing stty on UNIX systems + </para></listitem> + <listitem><para> + Additional tab-completion handlers + </para></listitem> + <listitem><para> + Tested on Debian Linux and Mac OS 10.2 + </para></listitem> + <listitem><para> + Example includes dictionary, filename, and simple completion + </para></listitem> + </itemizedlist> + <itemizedlist> + <title>0.3.0 2002-10-05</title> + <listitem><para> + Initial release + </para></listitem> + </itemizedlist> + </appendix> +</book> diff --git a/src/jline/src/site/fml/faq.fml b/src/jline/src/site/fml/faq.fml new file mode 100644 index 0000000000..b9902b0fa7 --- /dev/null +++ b/src/jline/src/site/fml/faq.fml @@ -0,0 +1,26 @@ + <?xml version="1.0"?> +<faqs title="JLine FAQ"> + <part id="general"> + <faq> + <question>How does JLine work?</question> + <answer> + <p> + On Windows, JLine uses a native .dll (which it automatically + extracts from the jline jar and loads at runtime) to access + native console features. On UNIX systems, JLine will perform + the necessary terminal setup by launching the "stty" command. + </p> + </answer> + </faq> + <faq> + <question>What platforms has JLine been tested on?</question> + <answer> + <p> + Various flavors of Windows (95, 98, NT, XP, 2000), Mac OS X, + and multiple UNIX systems (Linux, Solaris, HPUX). + </p> + </answer> + </faq> + + </part> +</faqs> diff --git a/src/jline/src/site/resources/css/site.css b/src/jline/src/site/resources/css/site.css new file mode 100755 index 0000000000..5d71608c15 --- /dev/null +++ b/src/jline/src/site/resources/css/site.css @@ -0,0 +1,311 @@ +
+body {
+ min-width: 600px;
+ width: 600px;
+ width: auto !important;
+ background-color: #fff;
+ font-family: Verdana, sans;
+}
+
+body,div,span,td,p,h2,h3,h4,h5,h6,a,ul,li {
+ font-family: Verdana, sans;
+ font-size: 11px;
+ color: #5A5A5A;
+ font-style: normal;
+}
+
+a,a:hover,a:visited,a:active {
+ color: #5A5A5A;
+ /* text-decoration: underline; */
+}
+
+/* main layout */
+#banner {
+ color: #FFA500;
+ border: none;
+ margin: 0 0 0 0;
+ background-color: #fff;
+ background-image: url(../images/header.jpg);
+ background-position: right;
+ background-repeat: no-repeat;
+ height: 100px;
+}
+
+#bannerLeft img{
+ margin: 10px 0 0 10px;
+}
+
+#leftColumn {
+ background-color: transparent;
+ position: absolute;
+ top: 140px;
+ left: 20px;
+ width: 180px;
+ margin: 0px;
+ padding: 0px;
+ border: none;
+}
+
+#bodyColumn {
+ margin: 0 0 20px 220px;
+ background-color: #fff;
+ padding: 30px;
+ position: relative;
+ background-image: url(../images/dotted.png);
+ background-repeat: repeat-y;
+}
+
+#footer div.xright {
+ color: #fff;
+ margin-right: 10px;
+}
+
+/* end main layout */
+.deprecated {
+ text-decoration: line-through;
+}
+
+.comment {
+ color: green;
+}
+
+.source pre {
+ font-family: "Andale Mono", monospace;
+ font-size: 11px;
+ background-color: #ddd;
+ width: 100%;
+ color: #5A5A5A;
+ border-width: 0px;
+ padding-top: 6px;
+ padding-left: 3px;
+}
+
+#breadcrumbs {
+ background-color: #FE1100;
+ border: none;
+ height: 15px;
+}
+
+/*
+ workaround for bug in the Doxia docbook renderer that opens
+ anchors (e.g. <a name="index">), but doesn't ever close them
+*/
+#section a,a:hover,a:visited,a:active {
+ text-decoration: none;
+}
+
+
+#breadcrumbs a {
+ color: #fff;
+ margin-left: 20px;
+ text-decoration: none;
+}
+
+h1 {
+ border: none;
+ padding-left: 0;
+ font-weight: bold;
+ text-transform: capitalize;
+ background-color: #7FAABB !important;
+ color: #FFFFFF !important;
+ font-size: 19px !important;
+}
+
+h2 {
+ border: none;
+ padding-left: 0;
+ font-size: 13px;
+ font-weight: bold;
+ text-transform: capitalize;
+ background-color: #7FAABB !important;
+ color: #FFFFFF !important;
+ font-size: 17px !important;
+}
+
+h3 {
+ border: none;
+ font-weight: bolder;
+ padding-left: 0;
+ background-color: #8BBBD1 !important;
+ color: #FFFFFF !important;
+}
+
+#navcolumn {
+ padding: 0;
+ overflow: hidden;
+}
+
+#navcolumn ul {
+ margin: 0px 0 3px 0;
+ background-repeat: repeat-x;
+}
+
+#navcolumn h5 {
+ border: none;
+ background-image: url(../images/dotted.png);
+ background-repeat: repeat-x;
+ padding: 4px 0 3px 20px;
+ font-size: 11px !important;
+ margin-top: -1px;
+}
+
+#navcolumn ul {
+ margin-bottom: 8px;
+}
+
+#navcolumn li {
+ margin: 0px 0 0px 3px;
+ padding: 2px;
+ list-style-position: outside;
+ font-size: 7.5pt !important;
+ padding-left: 16px;
+ padding-left /**/: 2px !important;
+ /* warning, don't reformat, there should be no comment between padding-left and comment, to fix IE5 issues */
+}
+
+#menuDownloadable_docs li {
+ background-image: url(../images/ico_file_pdf.png);
+ padding-top: 3px;
+ padding-bottom: 1px;
+}
+
+#navcolumn strong {
+ color: #000000;
+ font-weight: bold;
+}
+
+#navcolumn strong a {
+ color: #000000;
+ font-weight: bold;
+}
+
+#navcolumn a {
+ padding-left: 14px;
+ text-decoration: underline;
+ padding-bottom: 2px;
+ color: #5a5a5a;
+}
+
+#navcolumn a img {
+ margin-top: 0;
+}
+
+#navcolumn a#poweredBy img {
+ margin: 0 0 15px 20px;
+ width: 90px;
+ height: 30px;
+}
+
+#navcolumn #lastPublished {
+ color: #999;
+ margin: 0 0 0 20px;
+}
+
+#navcolumn a:hover {
+ color: Olive;
+ padding-left: 14px;
+ text-decoration: underline;
+ padding-bottom: 2px;
+}
+
+#breadcrumbs div.xright,#breadcrumbs div.xleft {
+ color: #fff;
+ display: inline;
+ font-size: 7pt !important;
+}
+
+#banner a#projectLogo img {
+ float: left;
+ background-color: #fff !important;
+ margin: 20px 0 0 20px !important;
+}
+
+#navcolumn li {
+ color: #000000;
+}
+
+#navcolumn strong {
+ color: #000000;
+ font-weight: bold;
+ margin-left: 15px;
+}
+
+div.source {
+ background-color: #ddd;
+}
+
+div.source pre,code,td.code {
+ font-size: 8pt !important;
+ font-family: monospace;
+ margin: 0;
+}
+
+td.code {
+ font-size: 10pt !important;
+ font-family: monospace;
+}
+
+div#legend {
+ display: none;
+}
+
+table td.source {
+ border: none !important;
+}
+
+table td,table th {
+ font-size: 8pt !important;
+ font-family: verdana;
+}
+
+table th {
+ font-weight: bold;
+}
+
+.collapsed {
+ background-image: url(../images/collapsed.png) !important;
+}
+
+li.expanded {
+ background-image: url(../images/expanded.png) !important;
+}
+
+/*
+li.expanded ul {
+ margin-top: 5px !important;
+}
+*/
+
+a.externalLink,a.newWindow {
+ padding-right: 9px !important;
+ background-image: none !important; /*ie5*/
+}
+
+a.externalLink /* */ {
+ background-image: url(../images/external.png) !important;
+}
+
+a.newWindow /* */ {
+ background-image: url(../images/newwindow.png) !important;
+}
+
+table {
+ width: 68%; /* fix for ie 5.x */
+}
+
+i {
+ content: "\"/*"
+}
+
+table {
+ width: 100%;
+}
+/* remove banner: comment the following lines for the full layout */ /*
+#banner, #breadcrumbs {
+ display: none !important;
+}
+#leftColumn {
+ position: relative;
+ top: 0;
+}
+*/
diff --git a/src/jline/src/site/resources/images/collapsed.png b/src/jline/src/site/resources/images/collapsed.png Binary files differnew file mode 100755 index 0000000000..a02c1e67c9 --- /dev/null +++ b/src/jline/src/site/resources/images/collapsed.png diff --git a/src/jline/src/site/resources/images/dotted.png b/src/jline/src/site/resources/images/dotted.png Binary files differnew file mode 100755 index 0000000000..8a4d443a4a --- /dev/null +++ b/src/jline/src/site/resources/images/dotted.png diff --git a/src/jline/src/site/resources/images/expanded.png b/src/jline/src/site/resources/images/expanded.png Binary files differnew file mode 100755 index 0000000000..8a19dbf103 --- /dev/null +++ b/src/jline/src/site/resources/images/expanded.png diff --git a/src/jline/src/site/resources/images/external.png b/src/jline/src/site/resources/images/external.png Binary files differnew file mode 100755 index 0000000000..19f28955ca --- /dev/null +++ b/src/jline/src/site/resources/images/external.png diff --git a/src/jline/src/site/resources/images/ico_file_pdf.png b/src/jline/src/site/resources/images/ico_file_pdf.png Binary files differnew file mode 100644 index 0000000000..9ceb00f2dd --- /dev/null +++ b/src/jline/src/site/resources/images/ico_file_pdf.png diff --git a/src/jline/src/site/resources/images/logo.jpg b/src/jline/src/site/resources/images/logo.jpg Binary files differnew file mode 100644 index 0000000000..1f1da5c8f2 --- /dev/null +++ b/src/jline/src/site/resources/images/logo.jpg diff --git a/src/jline/src/site/resources/images/newwindow.png b/src/jline/src/site/resources/images/newwindow.png Binary files differnew file mode 100755 index 0000000000..1374c228af --- /dev/null +++ b/src/jline/src/site/resources/images/newwindow.png diff --git a/src/jline/src/site/site.xml b/src/jline/src/site/site.xml new file mode 100644 index 0000000000..274ec83ee4 --- /dev/null +++ b/src/jline/src/site/site.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<project name="JLine"> + <bannerLeft> + <name>JLine</name> + <src>images/logo.jpg</src> + <href>http://jline.sourceforge.net/</href> + </bannerLeft> + <bannerRight> + <name>SourceForge</name> + <src>http://sourceforge.net/sflogo.php?group_id=64033</src> + <href>http://sourceforge.net/</href> + </bannerRight> + <body> + <menu name="JLine"> + <item name="Manual" href="index.html"/> + <item name="FAQs" href="faq.html"/> + <item name="Download" href="downloads.html"/> + <item name="Building" href="building.html"/> + </menu> + + <menu name="Community"> + <item name="Forums" + href="http://sourceforge.net/forum/?group_id=64033"/> + <item name="Issue Tracker" + href="http://sourceforge.net/tracker/?group_id=64033"/> + <item name="News" + href="http://sourceforge.net/news/?group_id=64033"/> + </menu> + + <menu name="Project"> + <item name="Javadocs" + href="apidocs/index.html"/> + <item name="Browse Source Code" + href="http://jline.cvs.sourceforge.net/jline/"/> + </menu> + + ${reports} + + </body> +</project> diff --git a/src/jline/src/test/java/jline/ConsoleReaderTest.java b/src/jline/src/test/java/jline/ConsoleReaderTest.java new file mode 100644 index 0000000000..4a25783c58 --- /dev/null +++ b/src/jline/src/test/java/jline/ConsoleReaderTest.java @@ -0,0 +1,162 @@ +package jline; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.StringWriter; +import java.io.Writer; + +import junit.framework.TestCase; + +public class ConsoleReaderTest extends TestCase { + + public ConsoleReaderTest(String name) { + super(name); + } + + protected void setUp() throws Exception { + System.setProperty("jline.WindowsTerminal.directConsole", "false"); + } + + public void testDeleteAndBackspaceKeymappings() throws Exception { + // test only works on Windows + if (!(Terminal.getTerminal() instanceof WindowsTerminal)) + return; + + ConsoleReader consoleReader = new ConsoleReader(); + assertNotNull(consoleReader); + assertEquals(127, consoleReader + .getKeyForAction(ConsoleReader.DELETE_NEXT_CHAR)); + assertEquals(8, consoleReader + .getKeyForAction(ConsoleReader.DELETE_PREV_CHAR)); + } + + public void testReadline() throws Exception { + ConsoleReader consoleReader = createConsole("Sample String\r\n" + .getBytes()); + assertNotNull(consoleReader); + String line = consoleReader.readLine(); + assertEquals("Sample String", line); + + } + + public void testDeleteOnWindowsTerminal() throws Exception { + // test only works on Windows + if (!(Terminal.getTerminal() instanceof WindowsTerminal)) + return; + + char[] characters = new char[] { 'S', 's', + WindowsTerminal.SPECIAL_KEY_INDICATOR, + WindowsTerminal.LEFT_ARROW_KEY, + WindowsTerminal.SPECIAL_KEY_INDICATOR, + WindowsTerminal.DELETE_KEY, '\r', 'n' }; + assertWindowsKeyBehavior("S", characters); + } + + public void testNumpadDeleteOnWindowsTerminal() throws Exception { + // test only works on Windows + if (!(Terminal.getTerminal() instanceof WindowsTerminal)) + return; + + char[] characters = new char[] { 'S', 's', + WindowsTerminal.NUMPAD_KEY_INDICATOR, + WindowsTerminal.LEFT_ARROW_KEY, + WindowsTerminal.NUMPAD_KEY_INDICATOR, + WindowsTerminal.DELETE_KEY, '\r', 'n' }; + assertWindowsKeyBehavior("S", characters); + } + + public void testHomeKeyOnWindowsTerminal() throws Exception { + // test only works on Windows + if (!(Terminal.getTerminal() instanceof WindowsTerminal)) + return; + + char[] characters = new char[] { 'S', 's', + WindowsTerminal.SPECIAL_KEY_INDICATOR, + WindowsTerminal.HOME_KEY, 'x', '\r', '\n' }; + assertWindowsKeyBehavior("xSs", characters); + + } + + public void testEndKeyOnWindowsTerminal() throws Exception { + // test only works on Windows + if (!(Terminal.getTerminal() instanceof WindowsTerminal)) + return; + + char[] characters = new char[] { 'S', 's', + WindowsTerminal.SPECIAL_KEY_INDICATOR, + WindowsTerminal.HOME_KEY, 'x', + WindowsTerminal.SPECIAL_KEY_INDICATOR, WindowsTerminal.END_KEY, + 'j', '\r', '\n' }; + assertWindowsKeyBehavior("xSsj", characters); + } + + public void testPageUpOnWindowsTerminal() throws Exception { + // test only works on Windows + if (!(Terminal.getTerminal() instanceof WindowsTerminal)) + return; + + char[] characters = new char[] { WindowsTerminal.SPECIAL_KEY_INDICATOR, + WindowsTerminal.PAGE_UP_KEY, '\r', '\n' }; + assertWindowsKeyBehavior("dir", characters); + } + + public void testPageDownOnWindowsTerminal() throws Exception { + // test only works on Windows + if (!(Terminal.getTerminal() instanceof WindowsTerminal)) + return; + + char[] characters = new char[] { WindowsTerminal.SPECIAL_KEY_INDICATOR, + WindowsTerminal.PAGE_DOWN_KEY, '\r', '\n' }; + assertWindowsKeyBehavior("mkdir monkey", characters); + } + + public void testEscapeOnWindowsTerminal() throws Exception { + // test only works on Windows + if (!(Terminal.getTerminal() instanceof WindowsTerminal)) + return; + + char[] characters = new char[] { 's', 's', 's', + WindowsTerminal.SPECIAL_KEY_INDICATOR, + WindowsTerminal.ESCAPE_KEY, '\r', '\n' }; + assertWindowsKeyBehavior("", characters); + } + + public void testInsertOnWindowsTerminal() throws Exception { + // test only works on Windows + if (!(Terminal.getTerminal() instanceof WindowsTerminal)) + return; + + char[] characters = new char[] { 'o', 'p', 's', + WindowsTerminal.SPECIAL_KEY_INDICATOR, + WindowsTerminal.HOME_KEY, + WindowsTerminal.SPECIAL_KEY_INDICATOR, + WindowsTerminal.INSERT_KEY, 'o', 'o', 'p', 's', '\r', '\n' }; + assertWindowsKeyBehavior("oops", characters); + } + + private void assertWindowsKeyBehavior(String expected, char[] input) + throws Exception { + StringBuffer buffer = new StringBuffer(); + buffer.append(input); + ConsoleReader reader = createConsole(buffer.toString().getBytes()); + assertNotNull(reader); + String line = reader.readLine(); + assertEquals(expected, line); + } + + private ConsoleReader createConsole(byte[] bytes) throws Exception { + InputStream in = new ByteArrayInputStream(bytes); + Writer writer = new StringWriter(); + ConsoleReader reader = new ConsoleReader(in, writer); + reader.setHistory(createSeededHistory()); + return reader; + } + + private History createSeededHistory() { + History history = new History(); + history.addToHistory("dir"); + history.addToHistory("cd c:\\"); + history.addToHistory("mkdir monkey"); + return history; + } +} diff --git a/src/jline/src/test/java/jline/JLineTestCase.java b/src/jline/src/test/java/jline/JLineTestCase.java new file mode 100644 index 0000000000..92e09d485d --- /dev/null +++ b/src/jline/src/test/java/jline/JLineTestCase.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import junit.framework.*; + +import java.io.*; + +public abstract class JLineTestCase extends TestCase { + ConsoleReader console; + + public JLineTestCase(String test) { + super(test); + } + + public void setUp() throws Exception { + super.setUp(); + console = new ConsoleReader(null, new PrintWriter( + new OutputStreamWriter(new ByteArrayOutputStream())), null, + new UnixTerminal()); + } + + public void assertBuffer(String expected, Buffer buffer) throws IOException { + assertBuffer(expected, buffer, true); + } + + public void assertBuffer(String expected, Buffer buffer, 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 + while (console.readLine((String) null) != null) { + ; + } + + assertEquals(expected, console.getCursorBuffer().toString()); + } + + private int getKeyForAction(short logicalAction) { + int action = console.getKeyForAction(logicalAction); + + if (action == -1) { + fail("Keystroke for logical action " + logicalAction + + " was not bound in the console"); + } + + return action; + } + + /** + * TODO: Fix this so tests don't break on windows machines. + * + * @author Ryan + * + */ + class Buffer { + private final ByteArrayOutputStream bout = new ByteArrayOutputStream(); + + public Buffer() { + } + + public Buffer(String str) { + append(str); + } + + public byte[] getBytes() { + return bout.toByteArray(); + } + + public Buffer op(short operation) { + return append(getKeyForAction(operation)); + } + + public Buffer ctrlA() { + return append(getKeyForAction(ConsoleReader.MOVE_TO_BEG)); + } + + public Buffer ctrlU() { + return append(getKeyForAction(ConsoleReader.KILL_LINE_PREV)); + } + + public Buffer tab() { + return append(getKeyForAction(ConsoleReader.COMPLETE)); + } + + public Buffer back() { + return append(getKeyForAction(ConsoleReader.DELETE_PREV_CHAR)); + } + + public Buffer left() { + return append(UnixTerminal.ARROW_START).append( + UnixTerminal.ARROW_PREFIX).append(UnixTerminal.ARROW_LEFT); + } + + public Buffer right() { + return append(UnixTerminal.ARROW_START).append( + UnixTerminal.ARROW_PREFIX).append(UnixTerminal.ARROW_RIGHT); + } + + public Buffer up() { + return append(UnixTerminal.ARROW_START).append( + UnixTerminal.ARROW_PREFIX).append(UnixTerminal.ARROW_UP); + } + + public Buffer down() { + return append(UnixTerminal.ARROW_START).append( + UnixTerminal.ARROW_PREFIX).append(UnixTerminal.ARROW_DOWN); + } + + public Buffer append(String str) { + byte[] bytes = str.getBytes(); + + for (int i = 0; i < bytes.length; i++) { + append(bytes[i]); + } + + return this; + } + + public Buffer append(int i) { + return append((byte) i); + } + + public Buffer append(byte b) { + bout.write(b); + + return this; + } + } +} diff --git a/src/jline/src/test/java/jline/TestCompletion.java b/src/jline/src/test/java/jline/TestCompletion.java new file mode 100644 index 0000000000..a481e2625b --- /dev/null +++ b/src/jline/src/test/java/jline/TestCompletion.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + +import java.util.*; + +/** + * Tests command history. + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public class TestCompletion extends JLineTestCase { + public TestCompletion(String test) { + super(test); + } + + public void testSimpleCompletor() throws Exception { + // clear any current completors + for (Iterator i = console.getCompletors().iterator(); i.hasNext(); + console.removeCompletor((Completor) i.next())) { + ; + } + + console.addCompletor + (new SimpleCompletor(new String[] { "foo", "bar", "baz" })); + + assertBuffer("foo ", new Buffer("f").op(ConsoleReader.COMPLETE)); + // single tab completes to unabbiguous "ba" + assertBuffer("ba", new Buffer("b").op(ConsoleReader.COMPLETE)); + assertBuffer("ba", new Buffer("ba").op(ConsoleReader.COMPLETE)); + assertBuffer("baz ", new Buffer("baz").op(ConsoleReader.COMPLETE)); + } + + public void testArgumentCompletor() throws Exception { + // clear any current completors + for (Iterator i = console.getCompletors().iterator(); i.hasNext(); + console.removeCompletor((Completor) i.next())) { + ; + } + + console.addCompletor(new ArgumentCompletor + (new SimpleCompletor(new String[] { "foo", "bar", "baz" }))); + + assertBuffer("foo foo ", new Buffer("foo f"). + op(ConsoleReader.COMPLETE)); + assertBuffer("foo ba", new Buffer("foo b"). + op(ConsoleReader.COMPLETE)); + assertBuffer("foo ba", new Buffer("foo ba"). + op(ConsoleReader.COMPLETE)); + assertBuffer("foo baz ", new Buffer("foo baz"). + op(ConsoleReader.COMPLETE)); + + // test completion in the mid range + assertBuffer("foo baz", + new Buffer("f baz").left().left().left().left(). + op(ConsoleReader.COMPLETE)); + assertBuffer("ba foo", + new Buffer("b foo").left().left().left().left(). + op(ConsoleReader.COMPLETE)); + assertBuffer("foo ba baz", + new Buffer("foo b baz").left().left().left().left(). + op(ConsoleReader.COMPLETE)); + assertBuffer("foo foo baz", + new Buffer("foo f baz").left().left().left().left(). + op(ConsoleReader.COMPLETE)); + } +} diff --git a/src/jline/src/test/java/jline/TestEditLine.java b/src/jline/src/test/java/jline/TestEditLine.java new file mode 100644 index 0000000000..88b45240ed --- /dev/null +++ b/src/jline/src/test/java/jline/TestEditLine.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + + +/** + * Tests various features of editing lines. + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public class TestEditLine extends JLineTestCase { + public TestEditLine(String test) { + super(test); + } + + public void testDeletePreviousWord() throws Exception { + Buffer b = new Buffer("This is a test"); + + assertBuffer("This is a ", b = b.op(ConsoleReader.DELETE_PREV_WORD)); + assertBuffer("This is ", b = b.op(ConsoleReader.DELETE_PREV_WORD)); + assertBuffer("This ", b = b.op(ConsoleReader.DELETE_PREV_WORD)); + assertBuffer("", b = b.op(ConsoleReader.DELETE_PREV_WORD)); + assertBuffer("", b = b.op(ConsoleReader.DELETE_PREV_WORD)); + assertBuffer("", b = b.op(ConsoleReader.DELETE_PREV_WORD)); + } + + 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(ConsoleReader.PREV_WORD) + .append('X') + .op(ConsoleReader.MOVE_TO_END) + .append('X')); + + assertBuffer("This is Xa testX", + new Buffer("This is a test").op(ConsoleReader.PREV_WORD) + .op(ConsoleReader.PREV_WORD) + .append('X') + .op(ConsoleReader.MOVE_TO_END) + .append('X')); + + assertBuffer("This Xis a testX", + new Buffer("This is a test").op(ConsoleReader.PREV_WORD) + .op(ConsoleReader.PREV_WORD) + .op(ConsoleReader.PREV_WORD) + .append('X') + .op(ConsoleReader.MOVE_TO_END) + .append('X')); + } + + public void testPreviousWord() throws Exception { + assertBuffer("This is a Xtest", + new Buffer("This is a test").op(ConsoleReader.PREV_WORD) + .append('X')); + assertBuffer("This is Xa test", + new Buffer("This is a test").op(ConsoleReader.PREV_WORD) + .op(ConsoleReader.PREV_WORD) + .append('X')); + assertBuffer("This Xis a test", + new Buffer("This is a test").op(ConsoleReader.PREV_WORD) + .op(ConsoleReader.PREV_WORD) + .op(ConsoleReader.PREV_WORD) + .append('X')); + assertBuffer("XThis is a test", + new Buffer("This is a test").op(ConsoleReader.PREV_WORD) + .op(ConsoleReader.PREV_WORD) + .op(ConsoleReader.PREV_WORD) + .op(ConsoleReader.PREV_WORD) + .append('X')); + assertBuffer("XThis is a test", + new Buffer("This is a test").op(ConsoleReader.PREV_WORD) + .op(ConsoleReader.PREV_WORD) + .op(ConsoleReader.PREV_WORD) + .op(ConsoleReader.PREV_WORD) + .op(ConsoleReader.PREV_WORD) + .append('X')); + assertBuffer("XThis is a test", + new Buffer("This is a test").op(ConsoleReader.PREV_WORD) + .op(ConsoleReader.PREV_WORD) + .op(ConsoleReader.PREV_WORD) + .op(ConsoleReader.PREV_WORD) + .op(ConsoleReader.PREV_WORD) + .op(ConsoleReader.PREV_WORD) + .append('X')); + } + + 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')); + } + + 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()); + } + + 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); + } + + 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()); + } + + 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()); + } + + public void testBuffer() throws Exception { + assertBuffer("This is a test", new Buffer("This is a test")); + } +} diff --git a/src/jline/src/test/java/jline/TestHistory.java b/src/jline/src/test/java/jline/TestHistory.java new file mode 100644 index 0000000000..a39afa5c19 --- /dev/null +++ b/src/jline/src/test/java/jline/TestHistory.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline; + + +/** + * Tests command history. + * + * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> + */ +public class TestHistory extends JLineTestCase { + public TestHistory(String test) { + super(test); + } + + public void testSingleHistory() throws Exception { + Buffer b = new Buffer(). + append("test line 1").op(ConsoleReader.NEWLINE). + append("test line 2").op(ConsoleReader.NEWLINE). + append("test line 3").op(ConsoleReader.NEWLINE). + append("test line 4").op(ConsoleReader.NEWLINE). + append("test line 5").op(ConsoleReader.NEWLINE). + append(""); + + assertBuffer("", b); + + assertBuffer("test line 5", b = b.op(ConsoleReader.PREV_HISTORY)); + assertBuffer("test line 4", b = b.op(ConsoleReader.PREV_HISTORY)); + assertBuffer("test line 5", b = b.op(ConsoleReader.NEXT_HISTORY)); + assertBuffer("test line 4", b = b.op(ConsoleReader.PREV_HISTORY)); + assertBuffer("test line 3", b = b.op(ConsoleReader.PREV_HISTORY)); + assertBuffer("test line 2", b = b.op(ConsoleReader.PREV_HISTORY)); + assertBuffer("test line 1", b = b.op(ConsoleReader.PREV_HISTORY)); + + // beginning of history + assertBuffer("test line 1", b = b.op(ConsoleReader.PREV_HISTORY)); + assertBuffer("test line 1", b = b.op(ConsoleReader.PREV_HISTORY)); + assertBuffer("test line 1", b = b.op(ConsoleReader.PREV_HISTORY)); + assertBuffer("test line 1", b = b.op(ConsoleReader.PREV_HISTORY)); + + assertBuffer("test line 2", b = b.op(ConsoleReader.NEXT_HISTORY)); + assertBuffer("test line 3", b = b.op(ConsoleReader.NEXT_HISTORY)); + assertBuffer("test line 4", b = b.op(ConsoleReader.NEXT_HISTORY)); + assertBuffer("test line 5", b = b.op(ConsoleReader.NEXT_HISTORY)); + + // end of history + assertBuffer("", b = b.op(ConsoleReader.NEXT_HISTORY)); + assertBuffer("", b = b.op(ConsoleReader.NEXT_HISTORY)); + assertBuffer("", b = b.op(ConsoleReader.NEXT_HISTORY)); + + assertBuffer("test line 5", b = b.op(ConsoleReader.PREV_HISTORY)); + assertBuffer("test line 4", b = b.op(ConsoleReader.PREV_HISTORY)); + b = b.op(ConsoleReader.MOVE_TO_BEG).append("XXX") + .op(ConsoleReader.NEWLINE); + assertBuffer("XXXtest line 4", b = b.op(ConsoleReader.PREV_HISTORY)); + assertBuffer("test line 5", b = b.op(ConsoleReader.PREV_HISTORY)); + assertBuffer("test line 4", b = b.op(ConsoleReader.PREV_HISTORY)); + assertBuffer("test line 5", b = b.op(ConsoleReader.NEXT_HISTORY)); + assertBuffer("XXXtest line 4", b = b.op(ConsoleReader.NEXT_HISTORY)); + assertBuffer("", b = b.op(ConsoleReader.NEXT_HISTORY)); + + assertBuffer("XXXtest line 4", b = b.op(ConsoleReader.PREV_HISTORY)); + assertBuffer("XXXtest line 4", b = b.op(ConsoleReader.NEWLINE). + op(ConsoleReader.PREV_HISTORY)); + assertBuffer("XXXtest line 4", b = b.op(ConsoleReader.NEWLINE). + op(ConsoleReader.PREV_HISTORY)); + assertBuffer("XXXtest line 4", b = b.op(ConsoleReader.NEWLINE). + op(ConsoleReader.PREV_HISTORY)); + assertBuffer("XXXtest line 4", b = b.op(ConsoleReader.NEWLINE). + op(ConsoleReader.PREV_HISTORY)); + } +} diff --git a/src/jline/src/test/java/jline/example/Example.java b/src/jline/src/test/java/jline/example/Example.java new file mode 100644 index 0000000000..80a8d99e33 --- /dev/null +++ b/src/jline/src/test/java/jline/example/Example.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2002-2006, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline.example; + +import jline.*; + +import java.io.*; +import java.util.*; +import java.util.zip.*; + +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(" dictionary - a completor that comples " + + "english dictionary words"); + 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("\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 { + Character mask = null; + String trigger = null; + + ConsoleReader reader = new ConsoleReader(); + reader.setBellEnabled(false); + reader.setDebug(new PrintWriter(new FileWriter("writer.debug", true))); + + if ((args == null) || (args.length == 0)) { + usage(); + + return; + } + + List<Completor> completors = new LinkedList<Completor>(); + + if (args.length > 0) { + if (args[0].equals("none")) { + } else if (args[0].equals("files")) { + completors.add(new FileNameCompletor()); + } else if (args[0].equals("classes")) { + completors.add(new ClassNameCompletor()); + } else if (args[0].equals("dictionary")) { + completors.add(new SimpleCompletor(new GZIPInputStream( + Example.class.getResourceAsStream("english.gz")))); + } else if (args[0].equals("simple")) { + completors.add(new SimpleCompletor(new String[] { "foo", "bar", + "baz" })); + } else { + usage(); + + return; + } + } + + if (args.length == 3) { + mask = new Character(args[2].charAt(0)); + trigger = args[1]; + } + + reader.addCompletor(new ArgumentCompletor(completors)); + + String line; + PrintWriter out = new PrintWriter(System.out); + + while ((line = reader.readLine("prompt> ")) != null) { + 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; + } + } + } +} diff --git a/src/jline/src/test/java/jline/example/PasswordReader.java b/src/jline/src/test/java/jline/example/PasswordReader.java new file mode 100644 index 0000000000..133185540a --- /dev/null +++ b/src/jline/src/test/java/jline/example/PasswordReader.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ +package jline.example; + +import jline.*; + +import java.io.*; + +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 = null; + do { + line = reader.readLine("enter password> ", mask); + System.out.println("Got password: " + line); + } while(line != null && line.length() > 0); + } +} diff --git a/src/jline/src/test/resources/jline/example/english.gz b/src/jline/src/test/resources/jline/example/english.gz Binary files differnew file mode 100644 index 0000000000..f0a85c08d9 --- /dev/null +++ b/src/jline/src/test/resources/jline/example/english.gz |