From: <ls...@us...> - 2007-08-13 18:27:42
|
Revision: 3396 http://jnode.svn.sourceforge.net/jnode/?rev=3396&view=rev Author: lsantha Date: 2007-08-13 11:27:41 -0700 (Mon, 13 Aug 2007) Log Message: ----------- General input history support for command line applications - by crawley. Modified Paths: -------------- trunk/core/src/driver/org/jnode/driver/console/InputCompleter.java trunk/core/src/driver/org/jnode/driver/console/textscreen/KeyboardInputStream.java trunk/core/src/driver/org/jnode/driver/console/textscreen/Line.java trunk/shell/src/shell/org/jnode/shell/CommandShell.java trunk/shell/src/shell/org/jnode/shell/Shell.java trunk/shell/src/shell/org/jnode/shell/command/HistoryCommand.java Added Paths: ----------- trunk/core/src/driver/org/jnode/driver/console/InputHistory.java Modified: trunk/core/src/driver/org/jnode/driver/console/InputCompleter.java =================================================================== --- trunk/core/src/driver/org/jnode/driver/console/InputCompleter.java 2007-08-13 07:38:53 UTC (rev 3395) +++ trunk/core/src/driver/org/jnode/driver/console/InputCompleter.java 2007-08-13 18:27:41 UTC (rev 3396) @@ -4,5 +4,11 @@ public interface InputCompleter { CompletionInfo complete(String partial); - CommandHistory getCommandHistory(); + /** + * Gets the completer's current InputHistory object. If the completer is modal, + * different histories may be returned at different times. + */ + public InputHistory getInputHistory(); + + } Added: trunk/core/src/driver/org/jnode/driver/console/InputHistory.java =================================================================== --- trunk/core/src/driver/org/jnode/driver/console/InputHistory.java (rev 0) +++ trunk/core/src/driver/org/jnode/driver/console/InputHistory.java 2007-08-13 18:27:41 UTC (rev 3396) @@ -0,0 +1,103 @@ +/* + * $Id: CommandHistory.java 2224 2006-01-01 12:49:03Z epr $ + * + * JNode.org + * Copyright (C) 2003-2006 JNode.org + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; If not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package org.jnode.driver.console; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class is used to manage history lists for command shell and application input. + * <p> + * TODO - support conventional history list (no removal of duplicates), an optional + * upper bound on the history list size, and loading from / saving to a file. + * + * @author Matt Paine + * @author cr...@jn... + */ +public class InputHistory { + + /** Holds the history lines. **/ + private final List<String> history = new ArrayList<String>(); + + /** Constructs an InputHistory object. **/ + public InputHistory() { + } + + /** + * Adds a line to the end of the history list, removing it if is already present + * @param line the input line to be recorded. + */ + public void addLine(String line) { + if (history.contains(line)) { + history.remove(line); + } + history.add(line); + } + + /** + * Returns the current number of history recorded. + * @return the number of lines in the history list. + */ + public int size() { + return history.size(); + } + + /** + * Gets the history line at a given index in the list + * @param index The index (starting at zero) for the history line to be returned. + * @return The history line requested or <code>null</code> if the index is out of range. + */ + public String getLineAt(int index) { + try { + return history.get(index); + } catch (IndexOutOfBoundsException ex) { + return null; + } + } + + /** + * Searches for the most recent command types starting with the specified + * string. + * @param start The string to search for. + * @return The most recent command matching the search string. + */ + public String getLineWithPrefix(String start) { + return getLineAt(findLine(start)); + } + + /** + * Searches the collection for the most recent line starting with the + * specified string. + * @param start the string to search for. + * @return the index number of the specified string (or -1 if not found). + */ + private int findLine(String start) { + for (int x = 0; x < history.size(); x++) { + String line = history.get(x); + if (line != null && line.startsWith(start)) { + return x; + } + } + return -1; + } + +} Modified: trunk/core/src/driver/org/jnode/driver/console/textscreen/KeyboardInputStream.java =================================================================== --- trunk/core/src/driver/org/jnode/driver/console/textscreen/KeyboardInputStream.java 2007-08-13 07:38:53 UTC (rev 3395) +++ trunk/core/src/driver/org/jnode/driver/console/textscreen/KeyboardInputStream.java 2007-08-13 18:27:41 UTC (rev 3396) @@ -227,6 +227,7 @@ if (completion.needNewPrompt()) { currentLine.start(true); } + out.print(currentPrompt); refreshCurrentLine(); } break; @@ -273,7 +274,7 @@ // Previous history item if (completer != null) { if (historyIndex == -1) { - historyIndex = completer.getCommandHistory().size(); + historyIndex = completer.getInputHistory().size(); savedCurrentLine = currentLine.getContent(); } historyIndex--; @@ -287,7 +288,7 @@ if (historyIndex == -1) savedCurrentLine = currentLine.getContent(); - if (historyIndex == completer.getCommandHistory().size() - 1) + if (historyIndex == completer.getInputHistory().size() - 1) historyIndex = -2; historyIndex++; @@ -332,7 +333,7 @@ private void updateCurrentLine() { if (historyIndex > -1) { - currentLine.setContent(completer.getCommandHistory().getCommand(historyIndex)); + currentLine.setContent(completer.getInputHistory().getLineAt(historyIndex)); } else { currentLine.setContent(savedCurrentLine); } Modified: trunk/core/src/driver/org/jnode/driver/console/textscreen/Line.java =================================================================== --- trunk/core/src/driver/org/jnode/driver/console/textscreen/Line.java 2007-08-13 07:38:53 UTC (rev 3395) +++ trunk/core/src/driver/org/jnode/driver/console/textscreen/Line.java 2007-08-13 18:27:41 UTC (rev 3396) @@ -347,6 +347,7 @@ // ensure that the location of the input cursor is included. console.ensureVisible(inputCursorY); } + console.setCursorVisible(true); } catch (Exception e) { // TODO - why ignore these exceptions? Are they due to the console methods // not being thread-safe??? Modified: trunk/shell/src/shell/org/jnode/shell/CommandShell.java =================================================================== --- trunk/shell/src/shell/org/jnode/shell/CommandShell.java 2007-08-13 07:38:53 UTC (rev 3395) +++ trunk/shell/src/shell/org/jnode/shell/CommandShell.java 2007-08-13 18:27:41 UTC (rev 3396) @@ -25,6 +25,7 @@ import gnu.java.security.action.SetPropertyAction; import java.io.File; +import java.io.FilterInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; @@ -42,10 +43,9 @@ import javax.naming.NameNotFoundException; import org.apache.log4j.Logger; -import org.jnode.driver.console.CommandHistory; +import org.jnode.driver.console.InputHistory; import org.jnode.driver.console.CompletionInfo; import org.jnode.driver.console.ConsoleManager; -import org.jnode.driver.console.InputCompleter; import org.jnode.driver.console.TextConsole; import org.jnode.driver.console.ConsoleListener; import org.jnode.driver.console.ConsoleEvent; @@ -66,8 +66,9 @@ /** * @author epr * @author Fabien DUMINY + * @authod crawley */ -public class CommandShell implements Runnable, Shell, ConsoleListener, InputCompleter { +public class CommandShell implements Runnable, Shell, ConsoleListener { public static final String PROMPT_PROPERTY_NAME = "jnode.prompt"; @@ -92,14 +93,27 @@ /** * Contains the archive of commands. * */ - private CommandHistory history = new CommandHistory(); + private InputHistory commandHistory = new InputHistory(); + + /** + * Contains the application input history for the current thread. + */ + private static InheritableThreadLocal<InputHistory> applicationHistory = + new InheritableThreadLocal<InputHistory> (); + + private boolean readingCommand; /** - * Contains the newest command being typed in * + * Contains the last command entered */ - private String newestLine = ""; + private String lastCommandLine = ""; /** + * Contains the last application input line entered + */ + private String lastInputLine = ""; + + /** * Flag to know when to wait (while input is happening). This is (hopefully) * a thread safe implementation. * */ @@ -205,7 +219,7 @@ if (e.startsWith(command)) { final String cmd = e.substring(command.length()); out.println(prompt() + cmd); - processCommand(cmd); + processCommand(cmd, false); } } catch (Throwable ex) { ex.printStackTrace(err); @@ -231,10 +245,10 @@ try { clearEof(); out.print(prompt()); + readingCommand = true; String line = readInputLine().trim(); if (line.length() > 0) { - clearEof(); - processCommand(line); + processCommand(line, true); } if (VmSystem.isShuttingDown()) { @@ -264,63 +278,24 @@ } } - protected void processCommand(String cmdLineStr) { - commandInvoker.invoke(cmdLineStr); + protected void processCommand(String cmdLineStr, boolean interactive) { + clearEof(); + if (interactive) { + readingCommand = false; + // Each interactive command is launched with a fresh history + // for input completion + applicationHistory.set(new InputHistory()); + } + commandInvoker.invoke(cmdLineStr); + if (interactive) { + applicationHistory.set(null); + } } public void invokeCommand(String command) { - processCommand(command); + processCommand(command, false); } - // /** - // * Execute a single command line. - // * - // * @param cmdLineStr - // */ - // protected void processCommand(String cmdLineStr) { - // - // final CommandLine cmdLine = new CommandLine(cmdLineStr); - // if (!cmdLine.hasNext()) - // return; - // String cmdName = cmdLine.next(); - // - // // Add this command to the history. - // if (!cmdLineStr.equals(newestLine)) - // history.addCommand(cmdLineStr); - // - // try { - // Class cmdClass = getCommandClass(cmdName); - // final Method main = cmdClass.getMethod("main", MAIN_ARG_TYPES); - // try { - // main.invoke(null, new Object[] { - // cmdLine.getRemainder().toStringArray()}); - // } catch (InvocationTargetException ex) { - // Throwable tex = ex.getTargetException(); - // if (tex instanceof SyntaxError) { - // Help.getInfo(cmdClass).usage(); - // err.println(tex.getMessage()); - // } else { - // err.println("Exception in command"); - // tex.printStackTrace(err); - // } - // } catch (Exception ex) { - // err.println("Exception in command"); - // ex.printStackTrace(err); - // } catch (Error ex) { - // err.println("Fatal error in command"); - // ex.printStackTrace(err); - // } - // } catch (NoSuchMethodException ex) { - // err.println("Alias class has no main method " + cmdName); - // } catch (ClassNotFoundException ex) { - // err.println("Unknown alias class " + ex.getMessage()); - // } catch (ClassCastException ex) { - // err.println("Invalid command " + cmdName); - // } catch (Exception ex) { - // err.println("I FOUND AN ERROR: " + ex); - // } - // } - protected CommandInfo getCommandClass(String cmd) throws ClassNotFoundException { try { @@ -341,13 +316,25 @@ } /** - * Gets the CommandHistory object associated with this shell. + * Gets the shell's command InputHistory object. */ - public CommandHistory getCommandHistory() { - return history; + public InputHistory getCommandHistory() { + return commandHistory; } /** + * Gets the shell's currently active InputHistory object. + */ + public InputHistory getInputHistory() { + if (readingCommand) { + return commandHistory; + } + else { + return CommandShell.applicationHistory.get(); + } + } + + /** * Gets the expanded prompt */ protected String prompt() { @@ -405,14 +392,23 @@ private CompletionInfo completion; public CompletionInfo complete(String partial) { + if (!readingCommand) { + // dummy completion behavior for application input. + CompletionInfo completion = new CompletionInfo(); + completion.setCompleted(partial); + completion.setNewPrompt(true); + return completion; + } + // workaround to set the currentShell to this shell try { ShellUtils.getShellManager().registerShell(this); } catch (NameNotFoundException ex) { } + // do command completion + String result = null; completion = new CompletionInfo(); - String result = null; try { CommandLine cl = new CommandLine(partial); String cmd = ""; @@ -472,13 +468,73 @@ } public void addCommandToHistory(String cmdLineStr) { - // Add this command to the history. - if (isHistoryEnabled() && !cmdLineStr.equals(newestLine)) - history.addCommand(cmdLineStr); + // Add this command to the command history. + if (isHistoryEnabled() && !cmdLineStr.equals(lastCommandLine)) { + commandHistory.addLine(cmdLineStr); + lastCommandLine = cmdLineStr; + } } + public void addInputToHistory(String inputLine) { + // Add this input to the application input history. + if (isHistoryEnabled() && !inputLine.equals(lastInputLine)) { + InputHistory history = applicationHistory.get(); + if (history != null) { + history.addLine(inputLine); + lastInputLine = inputLine; + } + } + } + public InputStream getInputStream() { - return in; + if (isHistoryEnabled()) { + // Insert a filter on the input stream that adds completed input lines + // to the application input history. + // TODO - revisit for support of muilt-byte character encodings. + return new FilterInputStream(in) { + private StringBuilder line = new StringBuilder(); + + @Override + public int read() throws IOException { + int res = super.read(); + if (res != -1) { + filter((byte) res); + } + return res; + } + + @Override + public int read(byte[] buf, int offset, int len) throws IOException { + int res = super.read(buf, offset, len); + for (int i = 0; i < res; i++) { + filter(buf[offset + i]); + } + return res; + } + + @Override + public int read(byte[] buf) throws IOException { + int res = super.read(buf); + for (int i = 0; i < res; i++) { + filter(buf[i]); + } + return res; + } + + private void filter(byte b) { + if (b == '\n') { + addInputToHistory(line.toString()); + line.setLength(0); + } + else { + line.append((char) b); + } + } + }; + } + else { + return in; + } } public PrintStream getOutputStream() { Modified: trunk/shell/src/shell/org/jnode/shell/Shell.java =================================================================== --- trunk/shell/src/shell/org/jnode/shell/Shell.java 2007-08-13 07:38:53 UTC (rev 3395) +++ trunk/shell/src/shell/org/jnode/shell/Shell.java 2007-08-13 18:27:41 UTC (rev 3396) @@ -22,13 +22,14 @@ package org.jnode.shell; import org.jnode.shell.alias.AliasManager; -import org.jnode.driver.console.CommandHistory; import org.jnode.driver.console.Console; +import org.jnode.driver.console.InputCompleter; +import org.jnode.driver.console.InputHistory; /** * @author epr */ -public interface Shell { +public interface Shell extends InputCompleter { /** * Gets the alias manager of this shell @@ -36,16 +37,17 @@ public AliasManager getAliasManager(); /** - * Gets the CommandHistory object associated with this shell. - */ - public CommandHistory getCommandHistory(); - - /** * Prints a list of choices for command line completion. */ public void list(String[] items); /** + * Gets the shell's command InputHistory object. Unlike getInputHistory, + * this method is not modal. + */ + public InputHistory getCommandHistory(); + + /** * Returns the console where the shell is running. * @return the console */ Modified: trunk/shell/src/shell/org/jnode/shell/command/HistoryCommand.java =================================================================== --- trunk/shell/src/shell/org/jnode/shell/command/HistoryCommand.java 2007-08-13 07:38:53 UTC (rev 3395) +++ trunk/shell/src/shell/org/jnode/shell/command/HistoryCommand.java 2007-08-13 18:27:41 UTC (rev 3396) @@ -23,7 +23,7 @@ import java.io.PrintStream; import javax.naming.NameNotFoundException; -import org.jnode.driver.console.CommandHistory; +import org.jnode.driver.console.InputHistory; import org.jnode.shell.help.*; import org.jnode.shell.help.argument.OptionArgument; import org.jnode.shell.Shell; @@ -70,7 +70,7 @@ private PrintStream out; /** Reference to the CommandHistory to work with. **/ - private CommandHistory history; + private InputHistory history; //********** Constructor **********// @@ -115,7 +115,7 @@ /** List out every command from the history, with each commands index. **/ public void listCommands() { for (int x = 0; x < history.size(); x++) - out.println("" + x + ": " + history.getCommand(x)); + out.println("" + x + ": " + history.getLineAt(x)); out.println(); } @@ -178,10 +178,10 @@ private String parseCommandArg(String arg) { try { int i = Integer.parseInt(arg); - return history.getCommand(i); + return history.getLineAt(i); } catch (NumberFormatException nfex) { - return history.getCommand(arg); + return history.getLineWithPrefix(arg); } } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |