summaryrefslogtreecommitdiff
path: root/src/src/main/java/jline/ConsoleReader.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/src/main/java/jline/ConsoleReader.java')
-rw-r--r--src/src/main/java/jline/ConsoleReader.java1823
1 files changed, 1823 insertions, 0 deletions
diff --git a/src/src/main/java/jline/ConsoleReader.java b/src/src/main/java/jline/ConsoleReader.java
new file mode 100644
index 0000000..18339d4
--- /dev/null
+++ b/src/src/main/java/jline/ConsoleReader.java
@@ -0,0 +1,1823 @@
+/*
+ * 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 KEYMAP_NAMES;
+
+
+ static {
+ Map names = new TreeMap();
+
+ 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));
+ names.put("ABORT", new Short(ABORT));
+
+ KEYMAP_NAMES = new TreeMap(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 completors = new LinkedList();
+ private Character echoCharacter = null;
+ private Map triggeredActions = new HashMap();
+
+ private StringBuffer searchTerm = null;
+ private String previousSearchTerm = "";
+ private int searchIndex = -1;
+
+ /**
+ * Adding a triggered Action allows to give another course 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 getTerminal().getTerminalWidth();
+ }
+
+ /**
+ * Query the terminal to find the current width;
+ *
+ * @see Terminal#getTerminalHeight
+ *
+ * @return the height of the current terminal.
+ */
+ public int getTermheight() {
+ return getTerminal().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 getStrippedAnsiLength(prompt) + buf.cursor;
+ }
+
+ /**
+ * Strips ANSI escape sequences starting with CSI and ending with char in range 64-126
+ * @param ansiString String possibly containing ANSI codes, may be null
+ * @return length after stripping ANSI codes
+ */
+ int getStrippedAnsiLength(String ansiString) {
+ if (ansiString == null) return 0;
+ boolean inAnsi = false;
+ int strippedLength = 0;
+ char[] chars = ansiString.toCharArray();
+ for (int i = 0; i < chars.length; i++) {
+ char c = chars[i];
+ if (!inAnsi && c == 27 && i < chars.length - 1 && chars[i+1] == '[') {
+ i++; // skip '['
+ inAnsi = true;
+ } else if (inAnsi) {
+ if (64 <= c && c <= 126) {
+ inAnsi = false;
+ }
+ } else {
+ strippedLength++;
+ }
+ }
+ return strippedLength;
+ }
+
+ 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);
+ }
+
+ final int NORMAL = 1;
+ final int SEARCH = 2;
+ int state = NORMAL;
+
+ boolean success = true;
+
+ while (true) {
+ // Read next key and look up the command binding.
+ int[] next = readBinding();
+
+ if (next == null) {
+ return null;
+ }
+
+ int c = next[0];
+ int code = next[1];
+
+ if (c == -1) {
+ return null;
+ }
+
+ // Search mode.
+ //
+ // Note that we have to do this first, because if there is a command
+ // not linked to a search command, we leave the search mode and fall
+ // through to the normal state.
+ if (state == SEARCH) {
+ switch (code) {
+ // This doesn't work right now, it seems CTRL-G is not passed
+ // down correctly. :(
+ case ABORT:
+ state = NORMAL;
+ break;
+
+ case SEARCH_PREV:
+ if (searchTerm.length() == 0) {
+ searchTerm.append(previousSearchTerm);
+ }
+
+ if (searchIndex == -1) {
+ searchIndex = history.searchBackwards(searchTerm.toString());
+ } else {
+ searchIndex = history.searchBackwards(searchTerm.toString(), searchIndex);
+ }
+ break;
+
+ case DELETE_PREV_CHAR:
+ if (searchTerm.length() > 0) {
+ searchTerm.deleteCharAt(searchTerm.length() - 1);
+ searchIndex = history.searchBackwards(searchTerm.toString());
+ }
+ break;
+
+ case UNKNOWN:
+ searchTerm.appendCodePoint(c);
+ searchIndex = history.searchBackwards(searchTerm.toString());
+ break;
+
+ default:
+ // Set buffer and cursor position to the found string.
+ if (searchIndex != -1) {
+ history.setCurrentIndex(searchIndex);
+ setBuffer(history.current());
+ buf.cursor = history.current().indexOf(searchTerm.toString());
+ }
+ state = NORMAL;
+ break;
+ }
+
+ // if we're still in search mode, print the search status
+ if (state == SEARCH) {
+ if (searchTerm.length() == 0) {
+ printSearchStatus("", "");
+ } else {
+ if (searchIndex == -1) {
+ beep();
+ } else {
+ printSearchStatus(searchTerm.toString(), history.getHistory(searchIndex));
+ }
+ }
+ }
+ // otherwise, restore the line
+ else {
+ restoreLine();
+ }
+ }
+
+ if (state == NORMAL) {
+ switch (code) {
+ case EXIT: // ctrl-d
+
+ if (buf.buffer.length() == 0) {
+ return null;
+ }
+ else {
+ success = deleteCurrentCharacter();
+ }
+ 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 ABORT:
+ 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 SEARCH_PREV: // CTRL-R
+ if (searchTerm != null) {
+ previousSearchTerm = searchTerm.toString();
+ }
+ searchTerm = new StringBuffer(buf.buffer);
+ state = SEARCH;
+ if (searchTerm.length() > 0) {
+ searchIndex = history.searchBackwards(searchTerm.toString());
+ if (searchIndex == -1) {
+ beep();
+ }
+ printSearchStatus(searchTerm.toString(),
+ searchIndex > -1 ? history.getHistory(searchIndex) : "");
+ } else {
+ searchIndex = -1;
+ printSearchStatus("", "");
+ }
+ 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
+ printANSISequence("2J");
+
+ // then send the ANSI code to go to position 1,1
+ printANSISequence("1;1H");
+
+ 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 candidates = new LinkedList();
+ 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 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.cursor - sameIndex;
+ if (diff < 0) { // we can't backspace here so try from the end of the buffer
+ moveToEnd();
+ diff = buf.buffer.length() - sameIndex;
+ }
+
+ backspace(diff); // go back for the differences
+ killLine(); // clear to the end of the line
+ buf.buffer.setLength(sameIndex); // the new length
+ putString(buffer.substring(sameIndex)); // append the differences
+ }
+
+ /**
+ * 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 - 1); // 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);
+ if (buf.cursor == buf.length() && clear == 0) {
+ return;
+ }
+ char[] chars = buf.buffer.substring(buf.cursor).toCharArray();
+ if (mask != null) {
+ Arrays.fill(chars, mask.charValue());
+ }
+
+ printCharacters(chars);
+ clearAhead(clear);
+ if (terminal.isANSISupported()) {
+ if (chars.length > 0) {
+ // don't ask, it seems to work
+ back(Math.max(chars.length - 1, 1));
+ }
+ } else {
+ 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;
+ }
+
+ if (terminal.isANSISupported()) {
+ printANSISequence("J");
+ 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 {
+ if (num == 0) return;
+ if (terminal.isANSISupported()) {
+ int width = getTermwidth();
+ int cursor = getCursorPosition();
+ // debug("back: " + cursor + " + " + num + " on " + width);
+ int currRow = (cursor + num) / width;
+ int newRow = cursor / width;
+ int newCol = cursor % width + 1;
+ // debug(" old row: " + currRow + " new row: " + newRow);
+ if (newRow < currRow) {
+ printANSISequence((currRow - newRow) + "A");
+ }
+ printANSISequence(newCol + "G");
+ flushConsole();
+ return;
+ }
+ 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;
+ int termwidth = getTermwidth();
+ int lines = getCursorPosition() / termwidth;
+ count = moveCursor(-1 * num) * -1;
+ // debug ("Deleting from " + buf.cursor + " for " + count);
+ buf.buffer.delete(buf.cursor, buf.cursor + count);
+ if (getCursorPosition() / termwidth != lines) {
+ if (terminal.isANSISupported()) {
+ // debug("doing backspace redraw: " + getCursorPosition() + " on " + termwidth + ": " + lines);
+ printANSISequence("J");
+ flushConsole();
+ }
+ }
+ 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 {
+ return moveCursor(buf.length() - buf.cursor) > 0;
+ }
+
+ /**
+ * Delete the character at the current position and redraw the remainder of
+ * the buffer.
+ */
+ private final boolean deleteCurrentCharacter() throws IOException {
+ if (buf.length() == 0 || buf.cursor == buf.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 num
+ * if less than 0, move abs(<i>num</i>) to the left,
+ * otherwise move <i>num</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.
+ *
+ * @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;
+
+ if (terminal.isANSISupported()) {
+ if (where < 0) {
+ back(Math.abs(where));
+ } else {
+ int width = getTermwidth();
+ int cursor = getCursorPosition();
+ int oldLine = (cursor - where) / width;
+ int newLine = cursor / width;
+ if (newLine > oldLine) {
+ printANSISequence((newLine - oldLine) + "B");
+ }
+ printANSISequence(1 +(cursor % width) + "G");
+ }
+ flushConsole();
+ return;
+ }
+
+ 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);
+ }
+
+ private void printANSISequence(String sequence) throws IOException {
+ printCharacter(27);
+ printCharacter('[');
+ printString(sequence);
+ flushConsole();
+ }
+
+ /*
+ private int currentCol, currentRow;
+
+ private void getCurrentPosition() {
+ // check for ByteArrayInputStream to disable for unit tests
+ if (terminal.isANSISupported() && !(in instanceof ByteArrayInputStream)) {
+ try {
+ printANSISequence("[6n");
+ flushConsole();
+ StringBuffer b = new StringBuffer(8);
+ // position is sent as <ESC>[{ROW};{COLUMN}R
+ int r;
+ while((r = in.read()) > -1 && r != 'R') {
+ if (r != 27 && r != '[') {
+ b.append((char) r);
+ }
+ }
+ String[] pos = b.toString().split(";");
+ currentRow = Integer.parseInt(pos[0]);
+ currentCol = Integer.parseInt(pos[1]);
+ } catch (Exception x) {
+ // no luck
+ currentRow = currentCol = -1;
+ }
+ }
+ }
+ */
+
+ /**
+ * 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;
+ }
+
+ public void printSearchStatus(String searchTerm, String match) throws IOException {
+ int i = match.indexOf(searchTerm);
+ printString("\r(reverse-i-search) `" + searchTerm + "': " + match + "\u001b[K");
+ // FIXME: our ANSI using back() does not work here
+ printCharacters(BACKSPACE, match.length() - i);
+ flushConsole();
+ }
+
+ public void restoreLine() throws IOException {
+ printString("\u001b[2K"); // ansi/vt100 for clear whole line
+ redrawLine();
+ flushConsole();
+ }
+}