Skip to content
Permalink
Browse files
Extract the readBinding method so that it can be publicly reused
Fixes #152
  • Loading branch information
gnodet committed Jul 24, 2015
1 parent 3b0c474 commit 54673e36c516e2bdfbacc11035f5942fcaa043a0
Showing with 151 additions and 128 deletions.
  1. +151 −128 src/main/java/jline/console/ConsoleReader.java
@@ -95,6 +95,9 @@
private static final ResourceBundle
resources = ResourceBundle.getBundle(CandidateListCompletionHandler.class.getName());

private static final int ESCAPE = 27;
private static final int READ_EXPIRED = -2;

private final Terminal2 terminal;

private final Writer out;
@@ -127,6 +130,10 @@

private int parenBlinkTimeout = 500;

// Reading buffers
private final StringBuilder opBuffer = new StringBuilder();
private final Stack<Character> pushBackChar = new Stack<Character>();

/*
* The reader and the nonBlockingInput go hand-in-hand. The reader wraps
* the nonBlockingInput, but we have to retain a handle to it so that
@@ -2133,7 +2140,7 @@ public int readCharacter(boolean checkForAltKeyCombo) throws IOException {
if (terminal.isSupported()) {
clearEcho(c);
}
if (c == 27 && checkForAltKeyCombo && in.peek(escapeTimeout) >= 32) {
if (c == ESCAPE && checkForAltKeyCombo && in.peek(escapeTimeout) >= 32) {
/* When ESC is encountered and there is a pending
* character in the pushback queue, then it seems to be
* an Alt-[key] combination. Is this true, cross-platform?
@@ -2186,6 +2193,131 @@ public int readCharacter(boolean checkForAltKeyCombo, final char... allowed) thr
return c;
}

/**
* Read from the input stream and decode an operation from the key map.
*/
public Object readBinding(KeyMap keys) throws IOException {
Object o;
do {
int c = pushBackChar.isEmpty() ? readCharacter() : pushBackChar.pop();
if (c == -1) {
return null;
}
opBuffer.appendCodePoint(c);

if (recording) {
macro += new String(Character.toChars(c));
}

if (quotedInsert) {
o = Operation.SELF_INSERT;
quotedInsert = false;
} else {
o = keys.getBound(opBuffer);
}

/*
* The kill ring keeps record of whether or not the
* previous command was a yank or a kill. We reset
* that state here if needed.
*/
if (!recording && !(o instanceof KeyMap)) {
if (o != Operation.YANK_POP && o != Operation.YANK) {
killRing.resetLastYank();
}
if (o != Operation.KILL_LINE && o != Operation.KILL_WHOLE_LINE
&& o != Operation.BACKWARD_KILL_WORD && o != Operation.KILL_WORD
&& o != Operation.UNIX_LINE_DISCARD && o != Operation.UNIX_WORD_RUBOUT) {
killRing.resetLastKill();
}
}

if (o == Operation.DO_LOWERCASE_VERSION) {
opBuffer.setLength(opBuffer.length() - 1);
opBuffer.append(Character.toLowerCase((char) c));
o = keys.getBound(opBuffer);
}

/*
* A KeyMap indicates that the key that was struck has a
* number of keys that can follow it as indicated in the
* map. This is used primarily for Emacs style ESC-META-x
* lookups. Since more keys must follow, go back to waiting
* for the next key.
*/
if (o instanceof KeyMap) {
/*
* The ESC key (#27) is special in that it is ambiguous until
* you know what is coming next. The ESC could be a literal
* escape, like the user entering vi-move mode, or it could
* be part of a terminal control sequence. The following
* logic attempts to disambiguate things in the same
* fashion as regular vi or readline.
*
* When ESC is encountered and there is no other pending
* character in the pushback queue, then attempt to peek
* into the input stream (if the feature is enabled) for
* 150ms. If nothing else is coming, then assume it is
* not a terminal control sequence, but a raw escape.
*/
if (c == ESCAPE
&& pushBackChar.isEmpty()
&& in.isNonBlockingEnabled()
&& in.peek(escapeTimeout) == READ_EXPIRED) {
Object otherKey = ((KeyMap) o).getAnotherKey();
if (otherKey == null) {
// Tne next line is in case a binding was set up inside this secondary
// KeyMap (like EMACS_META). For example, a binding could be put
// there for an ActionListener for the ESC key. This way, the var 'o' won't
// be null and the code can proceed to let the ActionListener be
// handled, below.
otherKey = ((KeyMap) o).getBound(Character.toString((char) c));
}
o = otherKey;
if (o == null || o instanceof KeyMap) {
continue;
}
opBuffer.setLength(0);
} else {
continue;
}
}

/*
* If we didn't find a binding for the key and there is
* more than one character accumulated then start checking
* the largest span of characters from the beginning to
* see if there is a binding for them.
*
* For example if our buffer has ESC,CTRL-M,C the getBound()
* called previously indicated that there is no binding for
* this sequence, so this then checks ESC,CTRL-M, and failing
* that, just ESC. Each keystroke that is pealed off the end
* during these tests is stuffed onto the pushback buffer so
* they won't be lost.
*
* If there is no binding found, then we go back to waiting for
* input.
*/
while (o == null && opBuffer.length() > 0) {
c = opBuffer.charAt(opBuffer.length() - 1);
opBuffer.setLength(opBuffer.length() - 1);
Object o2 = keys.getBound(opBuffer);
if (o2 instanceof KeyMap) {
o = ((KeyMap) o2).getAnotherKey();
if (o == null) {
continue;
} else {
pushBackChar.push((char) c);
}
}
}

} while (o == null || o instanceof KeyMap);

return o;
}

//
// Key Bindings
//
@@ -2309,126 +2441,17 @@ public String readLine(String prompt, final Character mask, String buffer) throw

boolean success = true;

StringBuilder sb = new StringBuilder();
Stack<Character> pushBackChar = new Stack<Character>();
opBuffer.setLength(0);
pushBackChar.clear();
while (true) {
int c = pushBackChar.isEmpty() ? readCharacter() : pushBackChar.pop ();
if (c == -1) {
return null;
}
sb.appendCodePoint(c);

if (recording) {
macro += new String(Character.toChars(c));
}

Object o;
if (quotedInsert) {
o = Operation.SELF_INSERT;
quotedInsert = false;
} else {
o = getKeys().getBound(sb);
}
/*
* The kill ring keeps record of whether or not the
* previous command was a yank or a kill. We reset
* that state here if needed.
*/
if (!recording && !(o instanceof KeyMap)) {
if (o != Operation.YANK_POP && o != Operation.YANK) {
killRing.resetLastYank();
}
if (o != Operation.KILL_LINE && o != Operation.KILL_WHOLE_LINE
&& o != Operation.BACKWARD_KILL_WORD && o != Operation.KILL_WORD
&& o != Operation.UNIX_LINE_DISCARD && o != Operation.UNIX_WORD_RUBOUT) {
killRing.resetLastKill();
}
}

if (o == Operation.DO_LOWERCASE_VERSION) {
sb.setLength( sb.length() - 1);
sb.append( Character.toLowerCase( (char) c ));
o = getKeys().getBound( sb );
}

/*
* A KeyMap indicates that the key that was struck has a
* number of keys that can follow it as indicated in the
* map. This is used primarily for Emacs style ESC-META-x
* lookups. Since more keys must follow, go back to waiting
* for the next key.
*/
if ( o instanceof KeyMap ) {
/*
* The ESC key (#27) is special in that it is ambiguous until
* you know what is coming next. The ESC could be a literal
* escape, like the user entering vi-move mode, or it could
* be part of a terminal control sequence. The following
* logic attempts to disambiguate things in the same
* fashion as regular vi or readline.
*
* When ESC is encountered and there is no other pending
* character in the pushback queue, then attempt to peek
* into the input stream (if the feature is enabled) for
* 150ms. If nothing else is coming, then assume it is
* not a terminal control sequence, but a raw escape.
*/
if (c == 27
&& pushBackChar.isEmpty()
&& in.isNonBlockingEnabled()
&& in.peek(escapeTimeout) == -2) {
Object otherKey = ((KeyMap) o).getAnotherKey();
if (otherKey == null) {
// Tne next line is in case a binding was set up inside this secondary
// KeyMap (like EMACS_META). For example, a binding could be put
// there for an ActionListener for the ESC key. This way, the var 'o' won't
// be null and the code can proceed to let the ActionListener be
// handled, below.
otherKey = ((KeyMap) o).getBound(Character.toString((char) c));
}
o = otherKey;
if (o == null || o instanceof KeyMap) {
continue;
}
sb.setLength(0);
}
else {
continue;
}
}

/*
* If we didn't find a binding for the key and there is
* more than one character accumulated then start checking
* the largest span of characters from the beginning to
* see if there is a binding for them.
*
* For example if our buffer has ESC,CTRL-M,C the getBound()
* called previously indicated that there is no binding for
* this sequence, so this then checks ESC,CTRL-M, and failing
* that, just ESC. Each keystroke that is pealed off the end
* during these tests is stuffed onto the pushback buffer so
* they won't be lost.
*
* If there is no binding found, then we go back to waiting for
* input.
*/
while ( o == null && sb.length() > 0 ) {
c = sb.charAt( sb.length() - 1 );
sb.setLength( sb.length() - 1 );
Object o2 = getKeys().getBound( sb );
if ( o2 instanceof KeyMap ) {
o = ((KeyMap) o2).getAnotherKey();
if ( o == null ) {
continue;
} else {
pushBackChar.push( (char) c );
}
}
Object o = readBinding(getKeys());
if (o == null) {
return null;
}

if ( o == null ) {
continue;
int c = 0;
if (opBuffer.length() > 0) {
c = opBuffer.codePointBefore(opBuffer.length());
}
Log.trace("Binding: ", o);

@@ -2439,14 +2462,14 @@ public String readLine(String prompt, final Character mask, String buffer) throw
for (int i = 0; i < macro.length(); i++) {
pushBackChar.push(macro.charAt(macro.length() - 1 - i));
}
sb.setLength( 0 );
opBuffer.setLength(0);
continue;
}

// Handle custom callbacks
if (o instanceof ActionListener) {
((ActionListener) o).actionPerformed(null);
sb.setLength( 0 );
opBuffer.setLength(0);
continue;
}

@@ -2611,7 +2634,7 @@ public String readLine(String prompt, final Character mask, String buffer) throw
success = complete();
}
else {
putString(sb);
putString(opBuffer);
}
break;

@@ -2649,7 +2672,7 @@ public String readLine(String prompt, final Character mask, String buffer) throw
break;

case SELF_INSERT:
putString(sb);
putString(opBuffer);
break;

case ACCEPT_LINE:
@@ -2883,14 +2906,14 @@ public String readLine(String prompt, final Character mask, String buffer) throw

case END_KBD_MACRO:
recording = false;
macro = macro.substring(0, macro.length() - sb.length());
macro = macro.substring(0, macro.length() - opBuffer.length());
break;

case CALL_LAST_KBD_MACRO:
for (int i = 0; i < macro.length(); i++) {
pushBackChar.push(macro.charAt(macro.length() - 1 - i));
}
sb.setLength( 0 );
opBuffer.setLength(0);
break;

case VI_EDITING_MODE:
@@ -2963,20 +2986,20 @@ public String readLine(String prompt, final Character mask, String buffer) throw
break;

case VI_SEARCH:
int lastChar = viSearch(sb.charAt (0));
int lastChar = viSearch(opBuffer.charAt(0));
if (lastChar != -1) {
pushBackChar.push((char)lastChar);
}
break;

case VI_ARG_DIGIT:
repeatCount = (repeatCount * 10) + sb.charAt(0) - '0';
repeatCount = (repeatCount * 10) + opBuffer.charAt(0) - '0';
isArgDigit = true;
break;

case VI_BEGNNING_OF_LINE_OR_ARG_DIGIT:
if (repeatCount > 0) {
repeatCount = (repeatCount * 10) + sb.charAt(0) - '0';
repeatCount = (repeatCount * 10) + opBuffer.charAt(0) - '0';
isArgDigit = true;
}
else {
@@ -3154,7 +3177,7 @@ else if (origState == State.VI_YANK_TO) {
if (!success) {
beep();
}
sb.setLength( 0 );
opBuffer.setLength(0);

flush();
}

0 comments on commit 54673e3

Please sign in to comment.