--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/LineReaderImpl.java Mon Nov 04 14:26:18 2019 +0800
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/LineReaderImpl.java Mon Nov 04 09:40:35 2019 +0100
@@ -1,10 +1,10 @@
/*
- * Copyright (c) 2002-2018, the original author or authors.
+ * Copyright (c) 2002-2019, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
- * http://www.opensource.org/licenses/bsd-license.php
+ * https://opensource.org/licenses/BSD-3-Clause
*/
package jdk.internal.org.jline.reader.impl;
@@ -17,10 +17,13 @@
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
import java.util.function.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import jdk.internal.org.jline.keymap.BindingReader;
@@ -32,6 +35,7 @@
import jdk.internal.org.jline.terminal.Attributes.ControlChar;
import jdk.internal.org.jline.terminal.Terminal.Signal;
import jdk.internal.org.jline.terminal.Terminal.SignalHandler;
+import jdk.internal.org.jline.terminal.impl.AbstractWindowsTerminal;
import jdk.internal.org.jline.utils.AttributedString;
import jdk.internal.org.jline.utils.AttributedStringBuilder;
import jdk.internal.org.jline.utils.AttributedStyle;
@@ -159,8 +163,8 @@
protected final Size size = new Size();
- protected AttributedString prompt;
- protected AttributedString rightPrompt;
+ protected AttributedString prompt = AttributedString.EMPTY;
+ protected AttributedString rightPrompt = AttributedString.EMPTY;
protected MaskingCallback maskingCallback;
@@ -210,6 +214,10 @@
protected UndoTree<Buffer> undo = new UndoTree<>(this::setBuffer);
protected boolean isUndo;
+ /**
+ * State lock
+ */
+ protected final ReentrantLock lock = new ReentrantLock();
/*
* Current internal state of the line reader
*/
@@ -239,6 +247,11 @@
protected int smallTerminalOffset = 0;
+ /*
+ * accept-and-infer-next-history, accept-and-hold & accept-line-and-down-history
+ */
+ protected boolean nextCommandFromHistory = false;
+ protected int nextHistoryId = -1;
public LineReaderImpl(Terminal terminal) throws IOException {
@@ -266,6 +279,7 @@
builtinWidgets = builtinWidgets();
widgets = new HashMap<>(builtinWidgets);
bindingReader = new BindingReader(terminal.reader());
+ doDisplay();
}
public Terminal getTerminal() {
@@ -467,8 +481,7 @@
SignalHandler previousWinchHandler = null;
SignalHandler previousContHandler = null;
Attributes originalAttributes = null;
- boolean dumb = Terminal.TYPE_DUMB.equals(terminal.getType())
- || Terminal.TYPE_DUMB_COLOR.equals(terminal.getType());
+ boolean dumb = isTerminalDumb();
try {
this.maskingCallback = maskingCallback;
@@ -495,6 +508,17 @@
if (buffer != null) {
buf.write(buffer);
}
+ if (nextCommandFromHistory && nextHistoryId > 0) {
+ if (history.size() > nextHistoryId) {
+ history.moveTo(nextHistoryId);
+ } else {
+ history.moveTo(history.last());
+ }
+ buf.write(history.current());
+ } else {
+ nextHistoryId = -1;
+ }
+ nextCommandFromHistory = false;
undo.clear();
parsedLine = null;
keyMap = MAIN;
@@ -503,7 +527,9 @@
history.attach(this);
}
- synchronized (this) {
+ try {
+ lock.lock();
+
this.reading = true;
previousIntrHandler = terminal.handle(Signal.INT, signal -> readLineThread.interrupt());
@@ -511,18 +537,7 @@
previousContHandler = terminal.handle(Signal.CONT, this::handleSignal);
originalAttributes = terminal.enterRawMode();
- // Cache terminal size for the duration of the call to readLine()
- // It will eventually be updated with WINCH signals
- size.copy(terminal.getSize());
-
- display = new Display(terminal, false);
- if (size.getRows() == 0 || size.getColumns() == 0) {
- display.resize(1, Integer.MAX_VALUE);
- } else {
- display.resize(size.getRows(), size.getColumns());
- }
- if (isSet(Option.DELAY_LINE_WRAP))
- display.setDelayLineWrap(true);
+ doDisplay();
// Move into application mode
if (!dumb) {
@@ -547,6 +562,8 @@
// Draw initial prompt
redrawLine();
redisplay();
+ } finally {
+ lock.unlock();
}
while (true) {
@@ -578,7 +595,8 @@
regionActive = RegionType.NONE;
}
- synchronized (this) {
+ try {
+ lock.lock();
// Get executable widget
Buffer copy = buf.copy();
Widget w = getWidget(o);
@@ -610,6 +628,8 @@
if (!dumb) {
redisplay();
}
+ } finally {
+ lock.unlock();
}
}
} catch (IOError e) {
@@ -620,7 +640,9 @@
}
}
finally {
- synchronized (this) {
+ try {
+ lock.lock();
+
this.reading = false;
cleanup();
@@ -636,26 +658,54 @@
if (previousContHandler != null) {
terminal.handle(Signal.CONT, previousContHandler);
}
+ } finally {
+ lock.unlock();
}
startedReading.set(false);
}
}
+ private boolean isTerminalDumb(){
+ return Terminal.TYPE_DUMB.equals(terminal.getType())
+ || Terminal.TYPE_DUMB_COLOR.equals(terminal.getType());
+ }
+
+ private void doDisplay(){
+ // Cache terminal size for the duration of the call to readLine()
+ // It will eventually be updated with WINCH signals
+ size.copy(terminal.getBufferSize());
+
+ display = new Display(terminal, false);
+ if (size.getRows() == 0 || size.getColumns() == 0) {
+ display.resize(1, Integer.MAX_VALUE);
+ } else {
+ display.resize(size.getRows(), size.getColumns());
+ }
+ if (isSet(Option.DELAY_LINE_WRAP))
+ display.setDelayLineWrap(true);
+ }
+
@Override
- public synchronized void printAbove(String str) {
- boolean reading = this.reading;
- if (reading) {
- display.update(Collections.emptyList(), 0);
- }
- if (str.endsWith("\n")) {
- terminal.writer().print(str);
- } else {
- terminal.writer().println(str);
- }
- if (reading) {
- redisplay(false);
- }
- terminal.flush();
+ public void printAbove(String str) {
+ try {
+ lock.lock();
+
+ boolean reading = this.reading;
+ if (reading) {
+ display.update(Collections.emptyList(), 0);
+ }
+ if (str.endsWith("\n") || str.endsWith("\n\033[m") || str.endsWith("\n\033[0m")) {
+ terminal.writer().print(str);
+ } else {
+ terminal.writer().println(str);
+ }
+ if (reading) {
+ redisplay(false);
+ }
+ terminal.flush();
+ } finally {
+ lock.unlock();
+ }
}
@Override
@@ -664,8 +714,13 @@
}
@Override
- public synchronized boolean isReading() {
- return reading;
+ public boolean isReading() {
+ try {
+ lock.lock();
+ return reading;
+ } finally {
+ lock.unlock();
+ }
}
/* Make sure we position the cursor on column 0 */
@@ -700,27 +755,32 @@
sb.append(" ");
sb.append(KeyMap.key(terminal, Capability.carriage_return));
}
- print(sb.toAnsi(terminal));
+ sb.print(terminal);
return true;
}
@Override
- public synchronized void callWidget(String name) {
- if (!reading) {
- throw new IllegalStateException("Widgets can only be called during a `readLine` call");
- }
+ public void callWidget(String name) {
try {
- Widget w;
- if (name.startsWith(".")) {
- w = builtinWidgets.get(name.substring(1));
- } else {
- w = widgets.get(name);
- }
- if (w != null) {
- w.apply();
- }
- } catch (Throwable t) {
- Log.debug("Error executing widget '", name, "'", t);
+ lock.lock();
+ if (!reading) {
+ throw new IllegalStateException("Widgets can only be called during a `readLine` call");
+ }
+ try {
+ Widget w;
+ if (name.startsWith(".")) {
+ w = builtinWidgets.get(name.substring(1));
+ } else {
+ w = widgets.get(name);
+ }
+ if (w != null) {
+ w.apply();
+ }
+ } catch (Throwable t) {
+ Log.debug("Error executing widget '", name, "'", t);
+ }
+ } finally {
+ lock.unlock();
}
}
@@ -760,13 +820,35 @@
* @return the character, or -1 if an EOF is received.
*/
public int readCharacter() {
- return bindingReader.readCharacter();
+ if (lock.isHeldByCurrentThread()) {
+ try {
+ lock.unlock();
+ return bindingReader.readCharacter();
+ } finally {
+ lock.lock();
+ }
+ } else {
+ return bindingReader.readCharacter();
+ }
}
public int peekCharacter(long timeout) {
return bindingReader.peekCharacter(timeout);
}
+ protected <T> T doReadBinding(KeyMap<T> keys, KeyMap<T> local) {
+ if (lock.isHeldByCurrentThread()) {
+ try {
+ lock.unlock();
+ return bindingReader.readBinding(keys, local);
+ } finally {
+ lock.lock();
+ }
+ } else {
+ return bindingReader.readBinding(keys, local);
+ }
+ }
+
/**
* Read from the input stream and decode an operation from the key map.
*
@@ -783,7 +865,7 @@
}
public Binding readBinding(KeyMap<Binding> keys, KeyMap<Binding> local) {
- Binding o = bindingReader.readBinding(keys, local);
+ Binding o = doReadBinding(keys, local);
/*
* The kill ring keeps record of whether or not the
* previous command was a yank or a kill. We reset
@@ -926,7 +1008,7 @@
if (ch != '\n') {
sb.append(ch);
}
- } else if (ch == '\\') {
+ } else if (parser.isEscapeChar(ch)) {
escaped = true;
} else {
sb.append(ch);
@@ -948,13 +1030,18 @@
protected void handleSignal(Signal signal) {
if (signal == Signal.WINCH) {
- size.copy(terminal.getSize());
+ Status status = Status.getStatus(terminal, false);
+ if (status != null) {
+ status.hardReset();
+ }
+ size.copy(terminal.getBufferSize());
display.resize(size.getRows(), size.getColumns());
+ redrawLine();
redisplay();
}
else if (signal == Signal.CONT) {
terminal.enterRawMode();
- size.copy(terminal.getSize());
+ size.copy(terminal.getBufferSize());
display.resize(size.getRows(), size.getColumns());
terminal.puts(Capability.keypad_xmit);
redrawLine();
@@ -1903,7 +1990,7 @@
while (true) {
post = () -> new AttributedString(searchPrompt + searchBuffer.toString() + "_");
redisplay();
- Binding b = bindingReader.readBinding(keyMap);
+ Binding b = doReadBinding(keyMap, null);
if (b instanceof Reference) {
String func = ((Reference) b).name();
switch (func) {
@@ -2300,7 +2387,7 @@
} else {
viMoveMode = mode;
mark = -1;
- Binding b = bindingReader.readBinding(getKeys(), keyMaps.get(VIOPP));
+ Binding b = doReadBinding(getKeys(), keyMaps.get(VIOPP));
if (b == null || new Reference(SEND_BREAK).equals(b)) {
viMoveMode = ViMoveMode.NORMAL;
mark = oldMark;
@@ -2710,6 +2797,42 @@
return acceptLine();
}
+ protected boolean acceptAndHold() {
+ nextCommandFromHistory = false;
+ acceptLine();
+ if (!buf.toString().isEmpty()) {
+ nextHistoryId = Integer.MAX_VALUE;
+ nextCommandFromHistory = true;
+ }
+ return nextCommandFromHistory;
+ }
+
+ protected boolean acceptLineAndDownHistory() {
+ nextCommandFromHistory = false;
+ acceptLine();
+ if (nextHistoryId < 0) {
+ nextHistoryId = history.index();
+ }
+ if (history.size() > nextHistoryId + 1) {
+ nextHistoryId++;
+ nextCommandFromHistory = true;
+ }
+ return nextCommandFromHistory;
+ }
+
+ protected boolean acceptAndInferNextHistory() {
+ nextCommandFromHistory = false;
+ acceptLine();
+ if (!buf.toString().isEmpty()) {
+ nextHistoryId = searchBackwards(buf.toString(), history.last());
+ if (nextHistoryId >= 0 && history.size() > nextHistoryId + 1) {
+ nextHistoryId++;
+ nextCommandFromHistory = true;
+ }
+ }
+ return nextCommandFromHistory;
+ }
+
protected boolean acceptLine() {
parsedLine = null;
if (!isSet(Option.DISABLE_EVENT_EXPANSION)) {
@@ -3343,255 +3466,317 @@
protected Map<String, Widget> builtinWidgets() {
Map<String, Widget> widgets = new HashMap<>();
- widgets.put(ACCEPT_LINE, this::acceptLine);
- widgets.put(ARGUMENT_BASE, this::argumentBase);
- widgets.put(BACKWARD_CHAR, this::backwardChar);
- widgets.put(BACKWARD_DELETE_CHAR, this::backwardDeleteChar);
- widgets.put(BACKWARD_DELETE_WORD, this::backwardDeleteWord);
- widgets.put(BACKWARD_KILL_LINE, this::backwardKillLine);
- widgets.put(BACKWARD_KILL_WORD, this::backwardKillWord);
- widgets.put(BACKWARD_WORD, this::backwardWord);
- widgets.put(BEEP, this::beep);
- widgets.put(BEGINNING_OF_BUFFER_OR_HISTORY, this::beginningOfBufferOrHistory);
- widgets.put(BEGINNING_OF_HISTORY, this::beginningOfHistory);
- widgets.put(BEGINNING_OF_LINE, this::beginningOfLine);
- widgets.put(BEGINNING_OF_LINE_HIST, this::beginningOfLineHist);
- widgets.put(CAPITALIZE_WORD, this::capitalizeWord);
- widgets.put(CLEAR, this::clear);
- widgets.put(CLEAR_SCREEN, this::clearScreen);
- widgets.put(COMPLETE_PREFIX, this::completePrefix);
- widgets.put(COMPLETE_WORD, this::completeWord);
- widgets.put(COPY_PREV_WORD, this::copyPrevWord);
- widgets.put(COPY_REGION_AS_KILL, this::copyRegionAsKill);
- widgets.put(DELETE_CHAR, this::deleteChar);
- widgets.put(DELETE_CHAR_OR_LIST, this::deleteCharOrList);
- widgets.put(DELETE_WORD, this::deleteWord);
- widgets.put(DIGIT_ARGUMENT, this::digitArgument);
- widgets.put(DO_LOWERCASE_VERSION, this::doLowercaseVersion);
- widgets.put(DOWN_CASE_WORD, this::downCaseWord);
- widgets.put(DOWN_LINE, this::downLine);
- widgets.put(DOWN_LINE_OR_HISTORY, this::downLineOrHistory);
- widgets.put(DOWN_LINE_OR_SEARCH, this::downLineOrSearch);
- widgets.put(DOWN_HISTORY, this::downHistory);
- widgets.put(EMACS_EDITING_MODE, this::emacsEditingMode);
- widgets.put(EMACS_BACKWARD_WORD, this::emacsBackwardWord);
- widgets.put(EMACS_FORWARD_WORD, this::emacsForwardWord);
- widgets.put(END_OF_BUFFER_OR_HISTORY, this::endOfBufferOrHistory);
- widgets.put(END_OF_HISTORY, this::endOfHistory);
- widgets.put(END_OF_LINE, this::endOfLine);
- widgets.put(END_OF_LINE_HIST, this::endOfLineHist);
- widgets.put(EXCHANGE_POINT_AND_MARK, this::exchangePointAndMark);
- widgets.put(EXPAND_HISTORY, this::expandHistory);
- widgets.put(EXPAND_OR_COMPLETE, this::expandOrComplete);
- widgets.put(EXPAND_OR_COMPLETE_PREFIX, this::expandOrCompletePrefix);
- widgets.put(EXPAND_WORD, this::expandWord);
- widgets.put(FRESH_LINE, this::freshLine);
- widgets.put(FORWARD_CHAR, this::forwardChar);
- widgets.put(FORWARD_WORD, this::forwardWord);
- widgets.put(HISTORY_INCREMENTAL_SEARCH_BACKWARD, this::historyIncrementalSearchBackward);
- widgets.put(HISTORY_INCREMENTAL_SEARCH_FORWARD, this::historyIncrementalSearchForward);
- widgets.put(HISTORY_SEARCH_BACKWARD, this::historySearchBackward);
- widgets.put(HISTORY_SEARCH_FORWARD, this::historySearchForward);
- widgets.put(INSERT_CLOSE_CURLY, this::insertCloseCurly);
- widgets.put(INSERT_CLOSE_PAREN, this::insertCloseParen);
- widgets.put(INSERT_CLOSE_SQUARE, this::insertCloseSquare);
- widgets.put(INSERT_COMMENT, this::insertComment);
- widgets.put(KILL_BUFFER, this::killBuffer);
- widgets.put(KILL_LINE, this::killLine);
- widgets.put(KILL_REGION, this::killRegion);
- widgets.put(KILL_WHOLE_LINE, this::killWholeLine);
- widgets.put(KILL_WORD, this::killWord);
- widgets.put(LIST_CHOICES, this::listChoices);
- widgets.put(MENU_COMPLETE, this::menuComplete);
- widgets.put(MENU_EXPAND_OR_COMPLETE, this::menuExpandOrComplete);
- widgets.put(NEG_ARGUMENT, this::negArgument);
- widgets.put(OVERWRITE_MODE, this::overwriteMode);
-// widgets.put(QUIT, this::quit);
- widgets.put(QUOTED_INSERT, this::quotedInsert);
- widgets.put(REDISPLAY, this::redisplay);
- widgets.put(REDRAW_LINE, this::redrawLine);
- widgets.put(REDO, this::redo);
- widgets.put(SELF_INSERT, this::selfInsert);
- widgets.put(SELF_INSERT_UNMETA, this::selfInsertUnmeta);
- widgets.put(SEND_BREAK, this::sendBreak);
- widgets.put(SET_MARK_COMMAND, this::setMarkCommand);
- widgets.put(TRANSPOSE_CHARS, this::transposeChars);
- widgets.put(TRANSPOSE_WORDS, this::transposeWords);
- widgets.put(UNDEFINED_KEY, this::undefinedKey);
- widgets.put(UNIVERSAL_ARGUMENT, this::universalArgument);
- widgets.put(UNDO, this::undo);
- widgets.put(UP_CASE_WORD, this::upCaseWord);
- widgets.put(UP_HISTORY, this::upHistory);
- widgets.put(UP_LINE, this::upLine);
- widgets.put(UP_LINE_OR_HISTORY, this::upLineOrHistory);
- widgets.put(UP_LINE_OR_SEARCH, this::upLineOrSearch);
- widgets.put(VI_ADD_EOL, this::viAddEol);
- widgets.put(VI_ADD_NEXT, this::viAddNext);
- widgets.put(VI_BACKWARD_CHAR, this::viBackwardChar);
- widgets.put(VI_BACKWARD_DELETE_CHAR, this::viBackwardDeleteChar);
- widgets.put(VI_BACKWARD_BLANK_WORD, this::viBackwardBlankWord);
- widgets.put(VI_BACKWARD_BLANK_WORD_END, this::viBackwardBlankWordEnd);
- widgets.put(VI_BACKWARD_KILL_WORD, this::viBackwardKillWord);
- widgets.put(VI_BACKWARD_WORD, this::viBackwardWord);
- widgets.put(VI_BACKWARD_WORD_END, this::viBackwardWordEnd);
- widgets.put(VI_BEGINNING_OF_LINE, this::viBeginningOfLine);
- widgets.put(VI_CMD_MODE, this::viCmdMode);
- widgets.put(VI_DIGIT_OR_BEGINNING_OF_LINE, this::viDigitOrBeginningOfLine);
- widgets.put(VI_DOWN_LINE_OR_HISTORY, this::viDownLineOrHistory);
- widgets.put(VI_CHANGE, this::viChange);
- widgets.put(VI_CHANGE_EOL, this::viChangeEol);
- widgets.put(VI_CHANGE_WHOLE_LINE, this::viChangeWholeLine);
- widgets.put(VI_DELETE_CHAR, this::viDeleteChar);
- widgets.put(VI_DELETE, this::viDelete);
- widgets.put(VI_END_OF_LINE, this::viEndOfLine);
- widgets.put(VI_KILL_EOL, this::viKillEol);
- widgets.put(VI_FIRST_NON_BLANK, this::viFirstNonBlank);
- widgets.put(VI_FIND_NEXT_CHAR, this::viFindNextChar);
- widgets.put(VI_FIND_NEXT_CHAR_SKIP, this::viFindNextCharSkip);
- widgets.put(VI_FIND_PREV_CHAR, this::viFindPrevChar);
- widgets.put(VI_FIND_PREV_CHAR_SKIP, this::viFindPrevCharSkip);
- widgets.put(VI_FORWARD_BLANK_WORD, this::viForwardBlankWord);
- widgets.put(VI_FORWARD_BLANK_WORD_END, this::viForwardBlankWordEnd);
- widgets.put(VI_FORWARD_CHAR, this::viForwardChar);
- widgets.put(VI_FORWARD_WORD, this::viForwardWord);
- widgets.put(VI_FORWARD_WORD, this::viForwardWord);
- widgets.put(VI_FORWARD_WORD_END, this::viForwardWordEnd);
- widgets.put(VI_HISTORY_SEARCH_BACKWARD, this::viHistorySearchBackward);
- widgets.put(VI_HISTORY_SEARCH_FORWARD, this::viHistorySearchForward);
- widgets.put(VI_INSERT, this::viInsert);
- widgets.put(VI_INSERT_BOL, this::viInsertBol);
- widgets.put(VI_INSERT_COMMENT, this::viInsertComment);
- widgets.put(VI_JOIN, this::viJoin);
- widgets.put(VI_KILL_LINE, this::viKillWholeLine);
- widgets.put(VI_MATCH_BRACKET, this::viMatchBracket);
- widgets.put(VI_OPEN_LINE_ABOVE, this::viOpenLineAbove);
- widgets.put(VI_OPEN_LINE_BELOW, this::viOpenLineBelow);
- widgets.put(VI_PUT_AFTER, this::viPutAfter);
- widgets.put(VI_PUT_BEFORE, this::viPutBefore);
- widgets.put(VI_REPEAT_FIND, this::viRepeatFind);
- widgets.put(VI_REPEAT_SEARCH, this::viRepeatSearch);
- widgets.put(VI_REPLACE_CHARS, this::viReplaceChars);
- widgets.put(VI_REV_REPEAT_FIND, this::viRevRepeatFind);
- widgets.put(VI_REV_REPEAT_SEARCH, this::viRevRepeatSearch);
- widgets.put(VI_SWAP_CASE, this::viSwapCase);
- widgets.put(VI_UP_LINE_OR_HISTORY, this::viUpLineOrHistory);
- widgets.put(VI_YANK, this::viYankTo);
- widgets.put(VI_YANK_WHOLE_LINE, this::viYankWholeLine);
- widgets.put(VISUAL_LINE_MODE, this::visualLineMode);
- widgets.put(VISUAL_MODE, this::visualMode);
- widgets.put(WHAT_CURSOR_POSITION, this::whatCursorPosition);
- widgets.put(YANK, this::yank);
- widgets.put(YANK_POP, this::yankPop);
- widgets.put(MOUSE, this::mouse);
- widgets.put(BEGIN_PASTE, this::beginPaste);
- widgets.put(FOCUS_IN, this::focusIn);
- widgets.put(FOCUS_OUT, this::focusOut);
+ addBuiltinWidget(widgets, ACCEPT_AND_INFER_NEXT_HISTORY, this::acceptAndInferNextHistory);
+ addBuiltinWidget(widgets, ACCEPT_AND_HOLD, this::acceptAndHold);
+ addBuiltinWidget(widgets, ACCEPT_LINE, this::acceptLine);
+ addBuiltinWidget(widgets, ACCEPT_LINE_AND_DOWN_HISTORY, this::acceptLineAndDownHistory);
+ addBuiltinWidget(widgets, ARGUMENT_BASE, this::argumentBase);
+ addBuiltinWidget(widgets, BACKWARD_CHAR, this::backwardChar);
+ addBuiltinWidget(widgets, BACKWARD_DELETE_CHAR, this::backwardDeleteChar);
+ addBuiltinWidget(widgets, BACKWARD_DELETE_WORD, this::backwardDeleteWord);
+ addBuiltinWidget(widgets, BACKWARD_KILL_LINE, this::backwardKillLine);
+ addBuiltinWidget(widgets, BACKWARD_KILL_WORD, this::backwardKillWord);
+ addBuiltinWidget(widgets, BACKWARD_WORD, this::backwardWord);
+ addBuiltinWidget(widgets, BEEP, this::beep);
+ addBuiltinWidget(widgets, BEGINNING_OF_BUFFER_OR_HISTORY, this::beginningOfBufferOrHistory);
+ addBuiltinWidget(widgets, BEGINNING_OF_HISTORY, this::beginningOfHistory);
+ addBuiltinWidget(widgets, BEGINNING_OF_LINE, this::beginningOfLine);
+ addBuiltinWidget(widgets, BEGINNING_OF_LINE_HIST, this::beginningOfLineHist);
+ addBuiltinWidget(widgets, CAPITALIZE_WORD, this::capitalizeWord);
+ addBuiltinWidget(widgets, CLEAR, this::clear);
+ addBuiltinWidget(widgets, CLEAR_SCREEN, this::clearScreen);
+ addBuiltinWidget(widgets, COMPLETE_PREFIX, this::completePrefix);
+ addBuiltinWidget(widgets, COMPLETE_WORD, this::completeWord);
+ addBuiltinWidget(widgets, COPY_PREV_WORD, this::copyPrevWord);
+ addBuiltinWidget(widgets, COPY_REGION_AS_KILL, this::copyRegionAsKill);
+ addBuiltinWidget(widgets, DELETE_CHAR, this::deleteChar);
+ addBuiltinWidget(widgets, DELETE_CHAR_OR_LIST, this::deleteCharOrList);
+ addBuiltinWidget(widgets, DELETE_WORD, this::deleteWord);
+ addBuiltinWidget(widgets, DIGIT_ARGUMENT, this::digitArgument);
+ addBuiltinWidget(widgets, DO_LOWERCASE_VERSION, this::doLowercaseVersion);
+ addBuiltinWidget(widgets, DOWN_CASE_WORD, this::downCaseWord);
+ addBuiltinWidget(widgets, DOWN_LINE, this::downLine);
+ addBuiltinWidget(widgets, DOWN_LINE_OR_HISTORY, this::downLineOrHistory);
+ addBuiltinWidget(widgets, DOWN_LINE_OR_SEARCH, this::downLineOrSearch);
+ addBuiltinWidget(widgets, DOWN_HISTORY, this::downHistory);
+ addBuiltinWidget(widgets, EMACS_EDITING_MODE, this::emacsEditingMode);
+ addBuiltinWidget(widgets, EMACS_BACKWARD_WORD, this::emacsBackwardWord);
+ addBuiltinWidget(widgets, EMACS_FORWARD_WORD, this::emacsForwardWord);
+ addBuiltinWidget(widgets, END_OF_BUFFER_OR_HISTORY, this::endOfBufferOrHistory);
+ addBuiltinWidget(widgets, END_OF_HISTORY, this::endOfHistory);
+ addBuiltinWidget(widgets, END_OF_LINE, this::endOfLine);
+ addBuiltinWidget(widgets, END_OF_LINE_HIST, this::endOfLineHist);
+ addBuiltinWidget(widgets, EXCHANGE_POINT_AND_MARK, this::exchangePointAndMark);
+ addBuiltinWidget(widgets, EXPAND_HISTORY, this::expandHistory);
+ addBuiltinWidget(widgets, EXPAND_OR_COMPLETE, this::expandOrComplete);
+ addBuiltinWidget(widgets, EXPAND_OR_COMPLETE_PREFIX, this::expandOrCompletePrefix);
+ addBuiltinWidget(widgets, EXPAND_WORD, this::expandWord);
+ addBuiltinWidget(widgets, FRESH_LINE, this::freshLine);
+ addBuiltinWidget(widgets, FORWARD_CHAR, this::forwardChar);
+ addBuiltinWidget(widgets, FORWARD_WORD, this::forwardWord);
+ addBuiltinWidget(widgets, HISTORY_INCREMENTAL_SEARCH_BACKWARD, this::historyIncrementalSearchBackward);
+ addBuiltinWidget(widgets, HISTORY_INCREMENTAL_SEARCH_FORWARD, this::historyIncrementalSearchForward);
+ addBuiltinWidget(widgets, HISTORY_SEARCH_BACKWARD, this::historySearchBackward);
+ addBuiltinWidget(widgets, HISTORY_SEARCH_FORWARD, this::historySearchForward);
+ addBuiltinWidget(widgets, INSERT_CLOSE_CURLY, this::insertCloseCurly);
+ addBuiltinWidget(widgets, INSERT_CLOSE_PAREN, this::insertCloseParen);
+ addBuiltinWidget(widgets, INSERT_CLOSE_SQUARE, this::insertCloseSquare);
+ addBuiltinWidget(widgets, INSERT_COMMENT, this::insertComment);
+ addBuiltinWidget(widgets, KILL_BUFFER, this::killBuffer);
+ addBuiltinWidget(widgets, KILL_LINE, this::killLine);
+ addBuiltinWidget(widgets, KILL_REGION, this::killRegion);
+ addBuiltinWidget(widgets, KILL_WHOLE_LINE, this::killWholeLine);
+ addBuiltinWidget(widgets, KILL_WORD, this::killWord);
+ addBuiltinWidget(widgets, LIST_CHOICES, this::listChoices);
+ addBuiltinWidget(widgets, MENU_COMPLETE, this::menuComplete);
+ addBuiltinWidget(widgets, MENU_EXPAND_OR_COMPLETE, this::menuExpandOrComplete);
+ addBuiltinWidget(widgets, NEG_ARGUMENT, this::negArgument);
+ addBuiltinWidget(widgets, OVERWRITE_MODE, this::overwriteMode);
+// addBuiltinWidget(widgets, QUIT, this::quit);
+ addBuiltinWidget(widgets, QUOTED_INSERT, this::quotedInsert);
+ addBuiltinWidget(widgets, REDISPLAY, this::redisplay);
+ addBuiltinWidget(widgets, REDRAW_LINE, this::redrawLine);
+ addBuiltinWidget(widgets, REDO, this::redo);
+ addBuiltinWidget(widgets, SELF_INSERT, this::selfInsert);
+ addBuiltinWidget(widgets, SELF_INSERT_UNMETA, this::selfInsertUnmeta);
+ addBuiltinWidget(widgets, SEND_BREAK, this::sendBreak);
+ addBuiltinWidget(widgets, SET_MARK_COMMAND, this::setMarkCommand);
+ addBuiltinWidget(widgets, TRANSPOSE_CHARS, this::transposeChars);
+ addBuiltinWidget(widgets, TRANSPOSE_WORDS, this::transposeWords);
+ addBuiltinWidget(widgets, UNDEFINED_KEY, this::undefinedKey);
+ addBuiltinWidget(widgets, UNIVERSAL_ARGUMENT, this::universalArgument);
+ addBuiltinWidget(widgets, UNDO, this::undo);
+ addBuiltinWidget(widgets, UP_CASE_WORD, this::upCaseWord);
+ addBuiltinWidget(widgets, UP_HISTORY, this::upHistory);
+ addBuiltinWidget(widgets, UP_LINE, this::upLine);
+ addBuiltinWidget(widgets, UP_LINE_OR_HISTORY, this::upLineOrHistory);
+ addBuiltinWidget(widgets, UP_LINE_OR_SEARCH, this::upLineOrSearch);
+ addBuiltinWidget(widgets, VI_ADD_EOL, this::viAddEol);
+ addBuiltinWidget(widgets, VI_ADD_NEXT, this::viAddNext);
+ addBuiltinWidget(widgets, VI_BACKWARD_CHAR, this::viBackwardChar);
+ addBuiltinWidget(widgets, VI_BACKWARD_DELETE_CHAR, this::viBackwardDeleteChar);
+ addBuiltinWidget(widgets, VI_BACKWARD_BLANK_WORD, this::viBackwardBlankWord);
+ addBuiltinWidget(widgets, VI_BACKWARD_BLANK_WORD_END, this::viBackwardBlankWordEnd);
+ addBuiltinWidget(widgets, VI_BACKWARD_KILL_WORD, this::viBackwardKillWord);
+ addBuiltinWidget(widgets, VI_BACKWARD_WORD, this::viBackwardWord);
+ addBuiltinWidget(widgets, VI_BACKWARD_WORD_END, this::viBackwardWordEnd);
+ addBuiltinWidget(widgets, VI_BEGINNING_OF_LINE, this::viBeginningOfLine);
+ addBuiltinWidget(widgets, VI_CMD_MODE, this::viCmdMode);
+ addBuiltinWidget(widgets, VI_DIGIT_OR_BEGINNING_OF_LINE, this::viDigitOrBeginningOfLine);
+ addBuiltinWidget(widgets, VI_DOWN_LINE_OR_HISTORY, this::viDownLineOrHistory);
+ addBuiltinWidget(widgets, VI_CHANGE, this::viChange);
+ addBuiltinWidget(widgets, VI_CHANGE_EOL, this::viChangeEol);
+ addBuiltinWidget(widgets, VI_CHANGE_WHOLE_LINE, this::viChangeWholeLine);
+ addBuiltinWidget(widgets, VI_DELETE_CHAR, this::viDeleteChar);
+ addBuiltinWidget(widgets, VI_DELETE, this::viDelete);
+ addBuiltinWidget(widgets, VI_END_OF_LINE, this::viEndOfLine);
+ addBuiltinWidget(widgets, VI_KILL_EOL, this::viKillEol);
+ addBuiltinWidget(widgets, VI_FIRST_NON_BLANK, this::viFirstNonBlank);
+ addBuiltinWidget(widgets, VI_FIND_NEXT_CHAR, this::viFindNextChar);
+ addBuiltinWidget(widgets, VI_FIND_NEXT_CHAR_SKIP, this::viFindNextCharSkip);
+ addBuiltinWidget(widgets, VI_FIND_PREV_CHAR, this::viFindPrevChar);
+ addBuiltinWidget(widgets, VI_FIND_PREV_CHAR_SKIP, this::viFindPrevCharSkip);
+ addBuiltinWidget(widgets, VI_FORWARD_BLANK_WORD, this::viForwardBlankWord);
+ addBuiltinWidget(widgets, VI_FORWARD_BLANK_WORD_END, this::viForwardBlankWordEnd);
+ addBuiltinWidget(widgets, VI_FORWARD_CHAR, this::viForwardChar);
+ addBuiltinWidget(widgets, VI_FORWARD_WORD, this::viForwardWord);
+ addBuiltinWidget(widgets, VI_FORWARD_WORD, this::viForwardWord);
+ addBuiltinWidget(widgets, VI_FORWARD_WORD_END, this::viForwardWordEnd);
+ addBuiltinWidget(widgets, VI_HISTORY_SEARCH_BACKWARD, this::viHistorySearchBackward);
+ addBuiltinWidget(widgets, VI_HISTORY_SEARCH_FORWARD, this::viHistorySearchForward);
+ addBuiltinWidget(widgets, VI_INSERT, this::viInsert);
+ addBuiltinWidget(widgets, VI_INSERT_BOL, this::viInsertBol);
+ addBuiltinWidget(widgets, VI_INSERT_COMMENT, this::viInsertComment);
+ addBuiltinWidget(widgets, VI_JOIN, this::viJoin);
+ addBuiltinWidget(widgets, VI_KILL_LINE, this::viKillWholeLine);
+ addBuiltinWidget(widgets, VI_MATCH_BRACKET, this::viMatchBracket);
+ addBuiltinWidget(widgets, VI_OPEN_LINE_ABOVE, this::viOpenLineAbove);
+ addBuiltinWidget(widgets, VI_OPEN_LINE_BELOW, this::viOpenLineBelow);
+ addBuiltinWidget(widgets, VI_PUT_AFTER, this::viPutAfter);
+ addBuiltinWidget(widgets, VI_PUT_BEFORE, this::viPutBefore);
+ addBuiltinWidget(widgets, VI_REPEAT_FIND, this::viRepeatFind);
+ addBuiltinWidget(widgets, VI_REPEAT_SEARCH, this::viRepeatSearch);
+ addBuiltinWidget(widgets, VI_REPLACE_CHARS, this::viReplaceChars);
+ addBuiltinWidget(widgets, VI_REV_REPEAT_FIND, this::viRevRepeatFind);
+ addBuiltinWidget(widgets, VI_REV_REPEAT_SEARCH, this::viRevRepeatSearch);
+ addBuiltinWidget(widgets, VI_SWAP_CASE, this::viSwapCase);
+ addBuiltinWidget(widgets, VI_UP_LINE_OR_HISTORY, this::viUpLineOrHistory);
+ addBuiltinWidget(widgets, VI_YANK, this::viYankTo);
+ addBuiltinWidget(widgets, VI_YANK_WHOLE_LINE, this::viYankWholeLine);
+ addBuiltinWidget(widgets, VISUAL_LINE_MODE, this::visualLineMode);
+ addBuiltinWidget(widgets, VISUAL_MODE, this::visualMode);
+ addBuiltinWidget(widgets, WHAT_CURSOR_POSITION, this::whatCursorPosition);
+ addBuiltinWidget(widgets, YANK, this::yank);
+ addBuiltinWidget(widgets, YANK_POP, this::yankPop);
+ addBuiltinWidget(widgets, MOUSE, this::mouse);
+ addBuiltinWidget(widgets, BEGIN_PASTE, this::beginPaste);
+ addBuiltinWidget(widgets, FOCUS_IN, this::focusIn);
+ addBuiltinWidget(widgets, FOCUS_OUT, this::focusOut);
return widgets;
}
+ private void addBuiltinWidget(Map<String, Widget> widgets, String name, Widget widget) {
+ widgets.put(name, namedWidget(name, widget));
+ }
+
+ private Widget namedWidget(String name, Widget widget) {
+ return new Widget() {
+ @Override
+ public String toString() {
+ return name;
+ }
+ @Override
+ public boolean apply() {
+ return widget.apply();
+ }
+ };
+ }
+
public boolean redisplay() {
redisplay(true);
return true;
}
- protected synchronized void redisplay(boolean flush) {
- if (skipRedisplay) {
- skipRedisplay = false;
- return;
- }
-
- Status status = Status.getStatus(terminal, false);
- if (status != null) {
- status.redraw();
- }
-
- if (size.getRows() > 0 && size.getRows() < MIN_ROWS) {
- AttributedStringBuilder sb = new AttributedStringBuilder().tabs(TAB_WIDTH);
-
- sb.append(prompt);
- concat(getHighlightedBuffer(buf.toString()).columnSplitLength(Integer.MAX_VALUE), sb);
- AttributedString full = sb.toAttributedString();
-
- sb.setLength(0);
- sb.append(prompt);
- String line = buf.upToCursor();
- if(maskingCallback != null) {
- line = maskingCallback.display(line);
- }
-
- concat(new AttributedString(line).columnSplitLength(Integer.MAX_VALUE), sb);
- AttributedString toCursor = sb.toAttributedString();
-
- int w = WCWidth.wcwidth('\u2026');
- int width = size.getColumns();
- int cursor = toCursor.columnLength();
- int inc = width /2 + 1;
- while (cursor <= smallTerminalOffset + w) {
- smallTerminalOffset -= inc;
- }
- while (cursor >= smallTerminalOffset + width - w) {
- smallTerminalOffset += inc;
- }
- if (smallTerminalOffset > 0) {
- sb.setLength(0);
- sb.append("\u2026");
- sb.append(full.columnSubSequence(smallTerminalOffset + w, Integer.MAX_VALUE));
- full = sb.toAttributedString();
- }
- int length = full.columnLength();
- if (length >= smallTerminalOffset + width) {
+ protected void redisplay(boolean flush) {
+ try {
+ lock.lock();
+
+ if (skipRedisplay) {
+ skipRedisplay = false;
+ return;
+ }
+
+ Status status = Status.getStatus(terminal, false);
+ if (status != null) {
+ status.redraw();
+ }
+
+ if (size.getRows() > 0 && size.getRows() < MIN_ROWS) {
+ AttributedStringBuilder sb = new AttributedStringBuilder().tabs(TAB_WIDTH);
+
+ sb.append(prompt);
+ concat(getHighlightedBuffer(buf.toString()).columnSplitLength(Integer.MAX_VALUE), sb);
+ AttributedString full = sb.toAttributedString();
+
sb.setLength(0);
- sb.append(full.columnSubSequence(0, width - w));
- sb.append("\u2026");
- full = sb.toAttributedString();
- }
-
- display.update(Collections.singletonList(full), cursor - smallTerminalOffset, flush);
- return;
- }
-
- List<AttributedString> secondaryPrompts = new ArrayList<>();
- AttributedString full = getDisplayedBufferWithPrompts(secondaryPrompts);
-
- List<AttributedString> newLines;
- if (size.getColumns() <= 0) {
- newLines = new ArrayList<>();
- newLines.add(full);
- } else {
- newLines = full.columnSplitLength(size.getColumns(), true, display.delayLineWrap());
- }
-
- List<AttributedString> rightPromptLines;
- if (rightPrompt.length() == 0 || size.getColumns() <= 0) {
- rightPromptLines = new ArrayList<>();
- } else {
- rightPromptLines = rightPrompt.columnSplitLength(size.getColumns());
- }
- while (newLines.size() < rightPromptLines.size()) {
- newLines.add(new AttributedString(""));
- }
- for (int i = 0; i < rightPromptLines.size(); i++) {
- AttributedString line = rightPromptLines.get(i);
- newLines.set(i, addRightPrompt(line, newLines.get(i)));
- }
-
- int cursorPos = -1;
- if (size.getColumns() > 0) {
- AttributedStringBuilder sb = new AttributedStringBuilder().tabs(TAB_WIDTH);
- sb.append(prompt);
- String buffer = buf.upToCursor();
- if (maskingCallback != null) {
- buffer = maskingCallback.display(buffer);
- }
- sb.append(insertSecondaryPrompts(new AttributedString(buffer), secondaryPrompts, false));
- List<AttributedString> promptLines = sb.columnSplitLength(size.getColumns(), false, display.delayLineWrap());
- if (!promptLines.isEmpty()) {
- cursorPos = size.cursorPos(promptLines.size() - 1,
- promptLines.get(promptLines.size() - 1).columnLength());
- }
- }
-
- display.update(newLines, cursorPos, flush);
+ sb.append(prompt);
+ String line = buf.upToCursor();
+ if (maskingCallback != null) {
+ line = maskingCallback.display(line);
+ }
+
+ concat(new AttributedString(line).columnSplitLength(Integer.MAX_VALUE), sb);
+ AttributedString toCursor = sb.toAttributedString();
+
+ int w = WCWidth.wcwidth('\u2026');
+ int width = size.getColumns();
+ int cursor = toCursor.columnLength();
+ int inc = width / 2 + 1;
+ while (cursor <= smallTerminalOffset + w) {
+ smallTerminalOffset -= inc;
+ }
+ while (cursor >= smallTerminalOffset + width - w) {
+ smallTerminalOffset += inc;
+ }
+ if (smallTerminalOffset > 0) {
+ sb.setLength(0);
+ sb.append("\u2026");
+ sb.append(full.columnSubSequence(smallTerminalOffset + w, Integer.MAX_VALUE));
+ full = sb.toAttributedString();
+ }
+ int length = full.columnLength();
+ if (length >= smallTerminalOffset + width) {
+ sb.setLength(0);
+ sb.append(full.columnSubSequence(0, width - w));
+ sb.append("\u2026");
+ full = sb.toAttributedString();
+ }
+
+ display.update(Collections.singletonList(full), cursor - smallTerminalOffset, flush);
+ return;
+ }
+
+ List<AttributedString> secondaryPrompts = new ArrayList<>();
+ AttributedString full = getDisplayedBufferWithPrompts(secondaryPrompts);
+
+ List<AttributedString> newLines;
+ if (size.getColumns() <= 0) {
+ newLines = new ArrayList<>();
+ newLines.add(full);
+ } else {
+ newLines = full.columnSplitLength(size.getColumns(), true, display.delayLineWrap());
+ }
+
+ List<AttributedString> rightPromptLines;
+ if (rightPrompt.length() == 0 || size.getColumns() <= 0) {
+ rightPromptLines = new ArrayList<>();
+ } else {
+ rightPromptLines = rightPrompt.columnSplitLength(size.getColumns());
+ }
+ while (newLines.size() < rightPromptLines.size()) {
+ newLines.add(new AttributedString(""));
+ }
+ for (int i = 0; i < rightPromptLines.size(); i++) {
+ AttributedString line = rightPromptLines.get(i);
+ newLines.set(i, addRightPrompt(line, newLines.get(i)));
+ }
+
+ int cursorPos = -1;
+ int cursorNewLinesId = -1;
+ int cursorColPos = -1;
+ if (size.getColumns() > 0) {
+ AttributedStringBuilder sb = new AttributedStringBuilder().tabs(TAB_WIDTH);
+ sb.append(prompt);
+ String buffer = buf.upToCursor();
+ if (maskingCallback != null) {
+ buffer = maskingCallback.display(buffer);
+ }
+ sb.append(insertSecondaryPrompts(new AttributedString(buffer), secondaryPrompts, false));
+ List<AttributedString> promptLines = sb.columnSplitLength(size.getColumns(), false, display.delayLineWrap());
+ if (!promptLines.isEmpty()) {
+ cursorNewLinesId = promptLines.size() - 1;
+ cursorColPos = promptLines.get(promptLines.size() - 1).columnLength();
+ cursorPos = size.cursorPos(cursorNewLinesId, cursorColPos);
+ }
+ }
+
+ List<AttributedString> newLinesToDisplay = new ArrayList<>();
+ int displaySize = size.getRows() - (status != null ? status.size() : 0);
+ if (newLines.size() > displaySize && !isTerminalDumb()) {
+ StringBuilder sb = new StringBuilder(">....");
+ // blanks are needed when displaying command completion candidate list
+ for (int i = sb.toString().length(); i < size.getColumns(); i++) {
+ sb.append(" ");
+ }
+ AttributedString partialCommandInfo = new AttributedString(sb.toString());
+ int lineId = newLines.size() - displaySize + 1;
+ int endId = displaySize;
+ int startId = 1;
+ if (lineId > cursorNewLinesId) {
+ lineId = cursorNewLinesId;
+ endId = displaySize - 1;
+ startId = 0;
+ } else {
+ newLinesToDisplay.add(partialCommandInfo);
+ }
+ int cursorRowPos = 0;
+ for (int i = startId; i < endId; i++) {
+ if (cursorNewLinesId == lineId) {
+ cursorRowPos = i;
+ }
+ newLinesToDisplay.add(newLines.get(lineId++));
+ }
+ if (startId == 0) {
+ newLinesToDisplay.add(partialCommandInfo);
+ }
+ cursorPos = size.cursorPos(cursorRowPos, cursorColPos);
+ } else {
+ newLinesToDisplay = newLines;
+ }
+ display.update(newLinesToDisplay, cursorPos, flush);
+ } finally {
+ lock.unlock();
+ }
}
private void concat(List<AttributedString> lines, AttributedStringBuilder sb) {
@@ -3656,26 +3841,26 @@
decode: while (true) {
ch = pattern.charAt(i++);
switch (ch) {
- case '{':
- case '}':
- String str = sb.toString();
- AttributedString astr;
- if (!isHidden) {
- astr = AttributedString.fromAnsi(str);
- cols += astr.columnLength();
- } else {
- astr = new AttributedString(str, AttributedStyle.HIDDEN);
- }
- if (padPartIndex == parts.size()) {
- padPartString = sb;
- if (i < plen) {
- sb = new StringBuilder();
- }
- } else {
- sb.setLength(0);
- }
- parts.add(astr);
- isHidden = ch == '{';
+ case '{':
+ case '}':
+ String str = sb.toString();
+ AttributedString astr;
+ if (!isHidden) {
+ astr = AttributedString.fromAnsi(str);
+ cols += astr.columnLength();
+ } else {
+ astr = new AttributedString(str, AttributedStyle.HIDDEN);
+ }
+ if (padPartIndex == parts.size()) {
+ padPartString = sb;
+ if (i < plen) {
+ sb = new StringBuilder();
+ }
+ } else {
+ sb.setLength(0);
+ }
+ parts.add(astr);
+ isHidden = ch == '{';
break decode;
case '%':
sb.append(ch);
@@ -4048,113 +4233,117 @@
if (matching.isEmpty()) {
return false;
}
-
- // If we only need to display the list, do it now
- if (lst == CompletionType.List) {
+ size.copy(terminal.getSize());
+ try {
+ // If we only need to display the list, do it now
+ if (lst == CompletionType.List) {
+ List<Candidate> possible = matching.entrySet().stream()
+ .flatMap(e -> e.getValue().stream())
+ .collect(Collectors.toList());
+ doList(possible, line.word(), false, line::escape);
+ return !possible.isEmpty();
+ }
+
+ // Check if there's a single possible match
+ Candidate completion = null;
+ // If there's a single possible completion
+ if (matching.size() == 1) {
+ completion = matching.values().stream().flatMap(Collection::stream)
+ .findFirst().orElse(null);
+ }
+ // Or if RECOGNIZE_EXACT is set, try to find an exact match
+ else if (isSet(Option.RECOGNIZE_EXACT)) {
+ completion = matching.values().stream().flatMap(Collection::stream)
+ .filter(Candidate::complete)
+ .filter(c -> exact.test(c.value()))
+ .findFirst().orElse(null);
+ }
+ // Complete and exit
+ if (completion != null && !completion.value().isEmpty()) {
+ if (prefix) {
+ buf.backspace(line.rawWordCursor());
+ } else {
+ buf.move(line.rawWordLength() - line.rawWordCursor());
+ buf.backspace(line.rawWordLength());
+ }
+ buf.write(line.escape(completion.value(), completion.complete()));
+ if (completion.complete()) {
+ if (buf.currChar() != ' ') {
+ buf.write(" ");
+ } else {
+ buf.move(1);
+ }
+ }
+ if (completion.suffix() != null) {
+ redisplay();
+ Binding op = readBinding(getKeys());
+ if (op != null) {
+ String chars = getString(REMOVE_SUFFIX_CHARS, DEFAULT_REMOVE_SUFFIX_CHARS);
+ String ref = op instanceof Reference ? ((Reference) op).name() : null;
+ if (SELF_INSERT.equals(ref) && chars.indexOf(getLastBinding().charAt(0)) >= 0
+ || ACCEPT_LINE.equals(ref)) {
+ buf.backspace(completion.suffix().length());
+ if (getLastBinding().charAt(0) != ' ') {
+ buf.write(' ');
+ }
+ }
+ pushBackBinding(true);
+ }
+ }
+ return true;
+ }
+
List<Candidate> possible = matching.entrySet().stream()
.flatMap(e -> e.getValue().stream())
.collect(Collectors.toList());
- doList(possible, line.word(), false, line::escape);
- return !possible.isEmpty();
- }
-
- // Check if there's a single possible match
- Candidate completion = null;
- // If there's a single possible completion
- if (matching.size() == 1) {
- completion = matching.values().stream().flatMap(Collection::stream)
- .findFirst().orElse(null);
- }
- // Or if RECOGNIZE_EXACT is set, try to find an exact match
- else if (isSet(Option.RECOGNIZE_EXACT)) {
- completion = matching.values().stream().flatMap(Collection::stream)
- .filter(Candidate::complete)
- .filter(c -> exact.test(c.value()))
- .findFirst().orElse(null);
- }
- // Complete and exit
- if (completion != null && !completion.value().isEmpty()) {
+
+ if (useMenu) {
+ buf.move(line.word().length() - line.wordCursor());
+ buf.backspace(line.word().length());
+ doMenu(possible, line.word(), line::escape);
+ return true;
+ }
+
+ // Find current word and move to end
+ String current;
if (prefix) {
- buf.backspace(line.rawWordCursor());
+ current = line.word().substring(0, line.wordCursor());
} else {
+ current = line.word();
buf.move(line.rawWordLength() - line.rawWordCursor());
+ }
+ // Now, we need to find the unambiguous completion
+ // TODO: need to find common suffix
+ String commonPrefix = null;
+ for (String key : matching.keySet()) {
+ commonPrefix = commonPrefix == null ? key : getCommonStart(commonPrefix, key, caseInsensitive);
+ }
+ boolean hasUnambiguous = commonPrefix.startsWith(current) && !commonPrefix.equals(current);
+
+ if (hasUnambiguous) {
buf.backspace(line.rawWordLength());
- }
- buf.write(line.escape(completion.value(), completion.complete()));
- if (completion.complete()) {
- if (buf.currChar() != ' ') {
- buf.write(" ");
- } else {
- buf.move(1);
+ buf.write(line.escape(commonPrefix, false));
+ current = commonPrefix;
+ if ((!isSet(Option.AUTO_LIST) && isSet(Option.AUTO_MENU))
+ || (isSet(Option.AUTO_LIST) && isSet(Option.LIST_AMBIGUOUS))) {
+ if (!nextBindingIsComplete()) {
+ return true;
+ }
}
}
- if (completion.suffix() != null) {
- redisplay();
- Binding op = readBinding(getKeys());
- if (op != null) {
- String chars = getString(REMOVE_SUFFIX_CHARS, DEFAULT_REMOVE_SUFFIX_CHARS);
- String ref = op instanceof Reference ? ((Reference) op).name() : null;
- if (SELF_INSERT.equals(ref) && chars.indexOf(getLastBinding().charAt(0)) >= 0
- || ACCEPT_LINE.equals(ref)) {
- buf.backspace(completion.suffix().length());
- if (getLastBinding().charAt(0) != ' ') {
- buf.write(' ');
- }
- }
- pushBackBinding(true);
+ if (isSet(Option.AUTO_LIST)) {
+ if (!doList(possible, current, true, line::escape)) {
+ return true;
}
}
+ if (isSet(Option.AUTO_MENU)) {
+ buf.backspace(current.length());
+ doMenu(possible, line.word(), line::escape);
+ }
return true;
- }
-
- List<Candidate> possible = matching.entrySet().stream()
- .flatMap(e -> e.getValue().stream())
- .collect(Collectors.toList());
-
- if (useMenu) {
- buf.move(line.word().length() - line.wordCursor());
- buf.backspace(line.word().length());
- doMenu(possible, line.word(), line::escape);
- return true;
- }
-
- // Find current word and move to end
- String current;
- if (prefix) {
- current = line.word().substring(0, line.wordCursor());
- } else {
- current = line.word();
- buf.move(line.rawWordLength() - line.rawWordCursor());
- }
- // Now, we need to find the unambiguous completion
- // TODO: need to find common suffix
- String commonPrefix = null;
- for (String key : matching.keySet()) {
- commonPrefix = commonPrefix == null ? key : getCommonStart(commonPrefix, key, caseInsensitive);
- }
- boolean hasUnambiguous = commonPrefix.startsWith(current) && !commonPrefix.equals(current);
-
- if (hasUnambiguous) {
- buf.backspace(line.rawWordLength());
- buf.write(line.escape(commonPrefix, false));
- current = commonPrefix;
- if ((!isSet(Option.AUTO_LIST) && isSet(Option.AUTO_MENU))
- || (isSet(Option.AUTO_LIST) && isSet(Option.LIST_AMBIGUOUS))) {
- if (!nextBindingIsComplete()) {
- return true;
- }
- }
- }
- if (isSet(Option.AUTO_LIST)) {
- if (!doList(possible, current, true, line::escape)) {
- return true;
- }
- }
- if (isSet(Option.AUTO_MENU)) {
- buf.backspace(current.length());
- doMenu(possible, line.word(), line::escape);
- }
- return true;
+ } finally {
+ size.copy(terminal.getBufferSize());
+ }
}
private CompletingParsedLine wrap(ParsedLine line) {
@@ -4534,7 +4723,7 @@
if (listMax > 0 && possible.size() >= listMax
|| lines >= size.getRows() - promptLines) {
// prompt
- post = () -> new AttributedString(getAppName() + ": do you wish to see to see all " + possible.size()
+ post = () -> new AttributedString(getAppName() + ": do you wish to see all " + possible.size()
+ " possibilities (" + lines + " lines)?");
redisplay(true);
int c = readCharacter();
@@ -4586,7 +4775,7 @@
}
redisplay();
// TODO: use a different keyMap ?
- Binding b = bindingReader.readBinding(getKeys());
+ Binding b = doReadBinding(getKeys(), null);
if (b instanceof Reference) {
String name = ((Reference) b).name();
if (BACKWARD_DELETE_CHAR.equals(name) || VI_BACKWARD_DELETE_CHAR.equals(name)) {
@@ -4731,7 +4920,7 @@
@SuppressWarnings("unchecked")
protected void toColumns(Object items, int width, int maxWidth, AttributedStringBuilder sb, Candidate selection, String completed, boolean rowsFirst, int[] out) {
- if (maxWidth <= 0) {
+ if (maxWidth <= 0 || width <= 0) {
return;
}
// This is a group
@@ -4985,7 +5174,9 @@
while (end < buf.length() && buf.atChar(end) != '\n') {
end++;
}
- end++;
+ if (end < buf.length()) {
+ end++;
+ }
}
}
String killed = buf.substring(start, end);
@@ -5188,7 +5379,7 @@
keyMap.bind(END_PASTE, BRACKETED_PASTE_END);
StringBuilder sb = new StringBuilder();
while (true) {
- Object b = bindingReader.readBinding(keyMap);
+ Object b = doReadBinding(keyMap, null);
if (b == END_PASTE) {
break;
}
@@ -5227,6 +5418,11 @@
*/
public boolean clearScreen() {
if (terminal.puts(Capability.clear_screen)) {
+ // ConEMU extended fonts support
+ if (AbstractWindowsTerminal.TYPE_WINDOWS_CONEMU.equals(terminal.getType())
+ && !Boolean.getBoolean("org.jline.terminal.conemu.disable-activate")) {
+ terminal.writer().write("\u001b[9999E");
+ }
Status status = Status.getStatus(terminal, false);
if (status != null) {
status.reset();
@@ -5358,6 +5554,7 @@
public KeyMap<Binding> emacs() {
KeyMap<Binding> emacs = new KeyMap<>();
+ bindKeys(emacs);
bind(emacs, SET_MARK_COMMAND, ctrl('@'));
bind(emacs, BEGINNING_OF_LINE, ctrl('A'));
bind(emacs, BACKWARD_CHAR, ctrl('B'));
@@ -5372,6 +5569,7 @@
bind(emacs, CLEAR_SCREEN, ctrl('L'));
bind(emacs, ACCEPT_LINE, ctrl('M'));
bind(emacs, DOWN_LINE_OR_HISTORY, ctrl('N'));
+ bind(emacs, ACCEPT_LINE_AND_DOWN_HISTORY, ctrl('O'));
bind(emacs, UP_LINE_OR_HISTORY, ctrl('P'));
bind(emacs, HISTORY_INCREMENTAL_SEARCH_BACKWARD, ctrl('R'));
bind(emacs, HISTORY_INCREMENTAL_SEARCH_FORWARD, ctrl('S'));
@@ -5415,6 +5613,7 @@
bind(emacs, END_OF_HISTORY, alt('>'));
bind(emacs, LIST_CHOICES, alt('?'));
bind(emacs, DO_LOWERCASE_VERSION, range("^[A-^[Z"));
+ bind(emacs, ACCEPT_AND_HOLD, alt('a'));
bind(emacs, BACKWARD_WORD, alt('b'));
bind(emacs, CAPITALIZE_WORD, alt('c'));
bind(emacs, KILL_WORD, alt('d'));
@@ -5439,6 +5638,7 @@
public KeyMap<Binding> viInsertion() {
KeyMap<Binding> viins = new KeyMap<>();
+ bindKeys(viins);
bind(viins, SELF_INSERT, range("^@-^_"));
bind(viins, LIST_CHOICES, ctrl('D'));
bind(viins, SEND_BREAK, ctrl('G'));
@@ -5638,6 +5838,14 @@
return KeyMap.key(terminal, capability);
}
+ private void bindKeys(KeyMap<Binding> emacs) {
+ Widget beep = namedWidget("beep", this::beep);
+ Stream.of(Capability.values())
+ .filter(c -> c.name().startsWith("key_"))
+ .map(this::key)
+ .forEach(k -> bind(emacs, beep, k));
+ }
+
private void bindArrowKeys(KeyMap<Binding> map) {
bind(map, UP_LINE_OR_SEARCH, key(Capability.key_up));
bind(map, DOWN_LINE_OR_SEARCH, key(Capability.key_down));