diff --git a/builtins/src/main/java/org/jline/builtins/ScreenTerminal.java b/builtins/src/main/java/org/jline/builtins/ScreenTerminal.java index a6f6c89bd..08044a7b7 100644 --- a/builtins/src/main/java/org/jline/builtins/ScreenTerminal.java +++ b/builtins/src/main/java/org/jline/builtins/ScreenTerminal.java @@ -23,17 +23,14 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - */ - -/** + * * Based on http://antony.lesuisse.org/software/ajaxterm/ * Public Domain License - */ - -/** + * * See http://www.ecma-international.org/publications/standards/Ecma-048.htm * and http://vt100.net/docs/vt510-rm/ */ + import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; diff --git a/demo/src/main/java/org/apache/felix/gogo/jline/Posix.java b/demo/src/main/java/org/apache/felix/gogo/jline/Posix.java index a557f2c1b..d14807a67 100644 --- a/demo/src/main/java/org/apache/felix/gogo/jline/Posix.java +++ b/demo/src/main/java/org/apache/felix/gogo/jline/Posix.java @@ -1865,6 +1865,7 @@ public static class SortComparator implements Comparator { private char separator; private List sortKeys; + @SuppressWarnings("this-escape") public SortComparator( boolean caseInsensitive, boolean reverse, diff --git a/jansi-core/src/main/java/org/jline/jansi/Ansi.java b/jansi-core/src/main/java/org/jline/jansi/Ansi.java index 28d92226a..600091381 100644 --- a/jansi-core/src/main/java/org/jline/jansi/Ansi.java +++ b/jansi-core/src/main/java/org/jline/jansi/Ansi.java @@ -900,10 +900,11 @@ public String toString() { return builder.toString(); } - /////////////////////////////////////////////////////////////////// - // Private Helper Methods - /////////////////////////////////////////////////////////////////// - + /* + /////////////////////////////////////////////////////////////////// + // Private Helper Methods + /////////////////////////////////////////////////////////////////// + */ private Ansi appendEscapeSequence(char command) { flushAttributes(); builder.append(FIRST_ESC_CHAR); diff --git a/pom.xml b/pom.xml index 3697bd1d6..4fb9c1e9c 100644 --- a/pom.xml +++ b/pom.xml @@ -562,7 +562,7 @@ - 2.38.0 + 2.39.0 java|javax,org,,\# diff --git a/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java b/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java index cf39a924b..c9a3610b5 100644 --- a/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java +++ b/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java @@ -42,16 +42,8 @@ import org.jline.terminal.Terminal.Signal; import org.jline.terminal.Terminal.SignalHandler; import org.jline.terminal.impl.AbstractWindowsTerminal; -import org.jline.utils.AttributedString; -import org.jline.utils.AttributedStringBuilder; -import org.jline.utils.AttributedStyle; -import org.jline.utils.Curses; -import org.jline.utils.Display; +import org.jline.utils.*; import org.jline.utils.InfoCmp.Capability; -import org.jline.utils.Log; -import org.jline.utils.Status; -import org.jline.utils.StyleResolver; -import org.jline.utils.WCWidth; import static org.jline.keymap.KeyMap.alt; import static org.jline.keymap.KeyMap.ctrl; @@ -1200,26 +1192,31 @@ protected String finish(String str) { } protected synchronized void handleSignal(Signal signal) { - doAutosuggestion = false; - if (signal == Signal.WINCH) { - size.copy(terminal.getBufferSize()); - display.resize(size.getRows(), size.getColumns()); - Status status = Status.getStatus(terminal, false); - if (status != null) { - status.resize(size); - status.reset(); + try { + lock.lock(); + doAutosuggestion = false; + if (signal == Signal.WINCH) { + size.copy(terminal.getBufferSize()); + display.resize(size.getRows(), size.getColumns()); + Status status = Status.getStatus(terminal, false); + if (status != null) { + status.resize(size); + status.reset(); + } + terminal.puts(Capability.carriage_return); + terminal.puts(Capability.clr_eos); + redrawLine(); + redisplay(); + } else if (signal == Signal.CONT) { + terminal.enterRawMode(); + size.copy(terminal.getBufferSize()); + display.resize(size.getRows(), size.getColumns()); + terminal.puts(Capability.keypad_xmit); + redrawLine(); + redisplay(); } - terminal.puts(Capability.carriage_return); - terminal.puts(Capability.clr_eos); - redrawLine(); - redisplay(); - } else if (signal == Signal.CONT) { - terminal.enterRawMode(); - size.copy(terminal.getBufferSize()); - display.resize(size.getRows(), size.getColumns()); - terminal.puts(Capability.keypad_xmit); - redrawLine(); - redisplay(); + } finally { + lock.unlock(); } } @@ -3871,6 +3868,10 @@ public boolean redisplay() { } protected void redisplay(boolean flush) { + redisplay(flush, false); + } + + protected void redisplay(boolean flush, boolean complete) { try { lock.lock(); @@ -3925,7 +3926,7 @@ protected void redisplay(boolean flush) { full = sb.toAttributedString(); } - display.update(Collections.singletonList(full), cursor - smallTerminalOffset, flush); + display.update(Collections.singletonList(full), cursor - smallTerminalOffset, flush, complete); return; } @@ -4007,7 +4008,7 @@ protected void redisplay(boolean flush) { } else { newLinesToDisplay = newLines; } - display.update(newLinesToDisplay, cursorPos, flush); + display.update(newLinesToDisplay, cursorPos, flush, complete); } finally { lock.unlock(); } @@ -4033,7 +4034,9 @@ private String matchPreviousCommand(String buffer) { StringBuilder sb = new StringBuilder(); for (char c : buffer.replace("\\", "\\\\").toCharArray()) { if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}' || c == '^' || c == '*' || c == '$' - || c == '.' || c == '?' || c == '+' || c == '|' || c == '<' || c == '>' || c == '!' || c == '-') { + || c == '.' || c == '?' + || c == '+') // BMGFIXX || c == '|' || c == '<' || c == '>' || c == '!' || c == '-') + { sb.append('\\'); } sb.append(c); @@ -4130,6 +4133,11 @@ private AttributedString getHighlightedBuffer(String buffer) { } private AttributedString expandPromptPattern(String pattern, int padToWidth, String message, int line) { + return expandPromptPattern(pattern, padToWidth, message, line, false); + } + + private AttributedString expandPromptPattern( + String pattern, int padToWidth, String message, int line, boolean currentLine) { ArrayList parts = new ArrayList<>(); boolean isHidden = false; int padPartIndex = -1; @@ -4177,6 +4185,9 @@ private AttributedString expandPromptPattern(String pattern, int padToWidth, Str case 'N': sb.append(getInt(LINE_OFFSET, 0) + line); break decode; + case 'C': + sb.append(getInt(LINE_OFFSET, 0) + line + 1); + break decode; case 'M': if (message != null) sb.append(message); break decode; @@ -4189,6 +4200,13 @@ private AttributedString expandPromptPattern(String pattern, int padToWidth, Str padPos = sb.length(); padPartIndex = parts.size(); break decode; + case '*': + if (currentLine) { + sb.append("*"); + } else { + sb.append(" "); + } + break decode; case '-': case '0': case '1': @@ -4246,6 +4264,7 @@ private AttributedString insertSecondaryPrompts( List lines = strAtt.columnSplitLength(Integer.MAX_VALUE); AttributedStringBuilder sb = new AttributedStringBuilder(); String secondaryPromptPattern = getString(SECONDARY_PROMPT_PATTERN, DEFAULT_SECONDARY_PROMPT_PATTERN); + int secondaryLineNo = secondaryPromptPattern.contains("%*") ? getCurrentLineNo(lines) - 1 : -1; boolean needsMessage = secondaryPromptPattern.contains("%M") && strAtt.length() < getInt(FEATURES_MAX_BUFFER_SIZE, DEFAULT_FEATURES_MAX_BUFFER_SIZE); AttributedStringBuilder buf = new AttributedStringBuilder(); @@ -4272,7 +4291,7 @@ private AttributedString insertSecondaryPrompts( } } missings.add(missing); - prompt = expandPromptPattern(secondaryPromptPattern, 0, missing, line + 1); + prompt = expandPromptPattern(secondaryPromptPattern, 0, missing, line + 1, line == secondaryLineNo); width = Math.max(width, prompt.columnLength()); } buf.setLength(0); @@ -4297,7 +4316,7 @@ private AttributedString insertSecondaryPrompts( missing = missings.get(line); } } - prompt = expandPromptPattern(secondaryPromptPattern, width, missing, line + 1); + prompt = expandPromptPattern(secondaryPromptPattern, width, missing, line + 1, line == secondaryLineNo); } else { prompt = prompts.get(line); } @@ -4310,6 +4329,21 @@ private AttributedString insertSecondaryPrompts( return sb.toAttributedString(); } + private int getCurrentLineNo(List lines) { + int currentLine = -1; + int cursor = buf.cursor(); + int start = 0; + for (int l = 0; l < lines.size(); l++) { + int end = start + lines.get(l).length(); + if (cursor >= start && cursor <= end) { + currentLine = l; + break; + } + start = end + 1; + } + return currentLine; + } + private AttributedString addRightPrompt(AttributedString prompt, AttributedString line) { int width = prompt.columnLength(); boolean endsWithNl = line.length() > 0 && line.charAt(line.length() - 1) == '\n'; @@ -4335,6 +4369,9 @@ private AttributedString addRightPrompt(AttributedString prompt, AttributedStrin // protected boolean insertTab() { + if (bindingReader.peekCharacter(10) != NonBlockingReader.READ_EXPIRED) { + return true; + } return isSet(Option.INSERT_TAB) && getLastBinding().equals("\t") && buf.toString().matches("(^|[\\s\\S]*\n)[\r\n\t ]*"); @@ -4608,8 +4645,8 @@ else if (isSet(Option.RECOGNIZE_EXACT)) { size.copy(terminal.getBufferSize()); } } - - protected static CompletingParsedLine wrap(ParsedLine line) { + // BMG protected static CompletingParsedLine wrap(ParsedLine line) { + protected CompletingParsedLine wrap(ParsedLine line) { if (line instanceof CompletingParsedLine) { return (CompletingParsedLine) line; } else { @@ -5622,6 +5659,9 @@ protected boolean moveHistory(final boolean next, int count) { * @return true if successful, false otherwise */ protected boolean moveHistory(final boolean next) { + if (maskingCallback != null) { + return false; + } if (!buf.toString().equals(history.current())) { modifiedHistory.put(history.index(), buf.toString()); } @@ -5982,6 +6022,11 @@ public boolean beep() { return true; } + public boolean redrawDisplay() { + redisplay(true, true); + return true; + } + // // Helpers // diff --git a/remote-telnet/src/main/java/org/jline/builtins/telnet/Connection.java b/remote-telnet/src/main/java/org/jline/builtins/telnet/Connection.java index ba4dcbf2d..093642d6d 100644 --- a/remote-telnet/src/main/java/org/jline/builtins/telnet/Connection.java +++ b/remote-telnet/src/main/java/org/jline/builtins/telnet/Connection.java @@ -189,7 +189,7 @@ public boolean isActive() { return !dead; } // isClosed - /****** Event handling ****************/ + /* ***** Event handling *************** */ /** * Method that registers a ConnectionListener with the diff --git a/remote-telnet/src/main/java/org/jline/builtins/telnet/TelnetIO.java b/remote-telnet/src/main/java/org/jline/builtins/telnet/TelnetIO.java index f280a65b3..b465e63bb 100644 --- a/remote-telnet/src/main/java/org/jline/builtins/telnet/TelnetIO.java +++ b/remote-telnet/src/main/java/org/jline/builtins/telnet/TelnetIO.java @@ -139,7 +139,7 @@ public class TelnetIO { */ protected static final int AO = 245; - /**** Implementation of OutputStream ****************************************************/ + /* *** Implementation of OutputStream *************************************************** */ /** * Are You There */ @@ -187,9 +187,9 @@ public class TelnetIO { */ protected static final int SEND = 1; - /**** End implementation of OutputStream ***********************************************/ + /* *** End implementation of OutputStream ********************************************** */ - /**** Implementation of InputStream ****************************************************/ + /* *** Implementation of InputStream *************************************************** */ /** * Telnet Option: Logout
* This allows nice goodbye to time-outed or unwanted clients. @@ -206,7 +206,7 @@ public class TelnetIO { protected static final int LM_EDIT = 1; protected static final int LM_TRAPSIG = 2; - /**** Implementation of InputStream ****************************************************/ + /* *** Implementation of InputStream *************************************************** */ /**** * Following methods implement init/request/answer procedures for telnet diff --git a/terminal-jansi/src/main/java/org/jline/terminal/impl/jansi/win/JansiWinSysTerminal.java b/terminal-jansi/src/main/java/org/jline/terminal/impl/jansi/win/JansiWinSysTerminal.java index 8c0ed497b..344272f15 100644 --- a/terminal-jansi/src/main/java/org/jline/terminal/impl/jansi/win/JansiWinSysTerminal.java +++ b/terminal-jansi/src/main/java/org/jline/terminal/impl/jansi/win/JansiWinSysTerminal.java @@ -173,6 +173,7 @@ public static boolean isWindowsSystemStream(SystemStream stream) { inMode, outConsole, outMode); + startWinchMonitor(); } @Override @@ -229,6 +230,28 @@ protected boolean processConsoleInput() throws IOException { return flush; } + private void startWinchMonitor() { + Size startSize = getSize(); + Thread winchMonitor = new Thread(() -> { + Size size = startSize; + boolean proceed = true; + while (proceed) { + try { + Thread.sleep(250); + } catch (InterruptedException ex) { + proceed = false; + } + Size newSize = getSize(); + if (size.getRows() != newSize.getRows() || size.getColumns() != newSize.getColumns()) { + size = newSize; + raise(Signal.WINCH); + } + } + }); + winchMonitor.setDaemon(true); + winchMonitor.start(); + } + private char[] focus = new char[] {'\033', '[', ' '}; private void processFocusEvent(boolean hasFocus) throws IOException { @@ -282,7 +305,7 @@ public Cursor getCursorPosition(IntConsumer discarded) { if (GetConsoleScreenBufferInfo(outConsole, info) == 0) { throw new IOError(new IOException("Could not get the cursor position: " + getLastErrorMessage())); } - return new Cursor(info.cursorPosition.x, info.cursorPosition.y); + return new Cursor(info.cursorPosition.x, info.cursorPosition.y - info.window.top); } public void disableScrolling() { diff --git a/terminal/src/main/java/org/jline/utils/Display.java b/terminal/src/main/java/org/jline/utils/Display.java index c2c17ecb7..80c3deb07 100644 --- a/terminal/src/main/java/org/jline/utils/Display.java +++ b/terminal/src/main/java/org/jline/utils/Display.java @@ -114,6 +114,17 @@ public void update(List newLines, int targetCursorPos) { * @param flush whether the output should be flushed or not */ public void update(List newLines, int targetCursorPos, boolean flush) { + update(newLines, targetCursorPos, flush, false); + } + + /** + * Update the display according to the new lines. + * @param newLines the lines to display + * @param targetCursorPos desired cursor position - see Size.cursorPos. + * @param flush whether the output should be flushed or not + * @param complete whether the display should be fully redrawn + */ + public void update(List newLines, int targetCursorPos, boolean flush, boolean complete) { if (reset) { terminal.puts(Capability.clear_screen); oldLines.clear(); @@ -231,7 +242,10 @@ public void update(List newLines, int targetCursorPos, boolean for (int i = 0; i < diffs.size(); i++) { DiffHelper.Diff diff = diffs.get(i); int width = diff.text.columnLength(); - switch (diff.operation) { + DiffHelper.Operation op = (complete && diff.operation == DiffHelper.Operation.EQUAL + ? DiffHelper.Operation.INSERT + : diff.operation); + switch (op) { case EQUAL: if (!ident) { cursorPos = moveVisualCursorTo(currentPos); @@ -296,6 +310,10 @@ public void update(List newLines, int targetCursorPos, boolean } } lineIndex++; + if (complete) { + moveVisualCursorTo(currentPos); + terminal.puts(Capability.clr_eol); + } boolean newWrap = !newNL && lineIndex < newLines.size(); if (targetCursorPos + 1 == lineIndex * columns1 && (newWrap || !delayLineWrap)) targetCursorPos++; boolean atRight = (cursorPos - curCol) % columns1 == columns; diff --git a/terminal/src/main/java/org/jline/utils/InputStreamReader.java b/terminal/src/main/java/org/jline/utils/InputStreamReader.java index 141bb17e8..5fdc4130c 100644 --- a/terminal/src/main/java/org/jline/utils/InputStreamReader.java +++ b/terminal/src/main/java/org/jline/utils/InputStreamReader.java @@ -22,7 +22,7 @@ import java.nio.charset.MalformedInputException; import java.nio.charset.UnmappableCharacterException; -/** +/* * * NOTE for JLine: the default InputStreamReader that comes from the JRE * usually read more bytes than needed from the input stream, which diff --git a/terminal/src/main/java/org/jline/utils/Log.java b/terminal/src/main/java/org/jline/utils/Log.java index 3c4c783d3..0eb157c92 100644 --- a/terminal/src/main/java/org/jline/utils/Log.java +++ b/terminal/src/main/java/org/jline/utils/Log.java @@ -10,6 +10,9 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.LogRecord; @@ -25,6 +28,19 @@ public final class Log { private static final Logger logger = Logger.getLogger("org.jline"); + public static class Stack extends Exception { + static final long serialVersionUID = -3387516993124229948L; + } + + public static void stack() { + Stack ex = new Stack(); + StackTraceElement[] ste = ex.getStackTrace(); + ex.setStackTrace(Arrays.copyOfRange(ste, 1, ste.length)); + StringWriter sw = new StringWriter(); + ex.printStackTrace(new PrintWriter(sw)); + Log.info(sw.toString()); + } + public static void trace(final Object... messages) { log(Level.FINEST, messages); } diff --git a/terminal/src/main/java/org/jline/utils/Status.java b/terminal/src/main/java/org/jline/utils/Status.java index ca8f0c0c1..5a5ebd76e 100644 --- a/terminal/src/main/java/org/jline/utils/Status.java +++ b/terminal/src/main/java/org/jline/utils/Status.java @@ -64,7 +64,7 @@ private boolean isValid(Size size) { public void close() { terminal.puts(Capability.save_cursor); - terminal.puts(Capability.change_scroll_region, 0, display.rows - 1); + if (display != null) terminal.puts(Capability.change_scroll_region, 0, display.rows - 1); terminal.puts(Capability.restore_cursor); terminal.flush(); } diff --git a/terminal/src/main/java/org/jline/utils/WCWidth.java b/terminal/src/main/java/org/jline/utils/WCWidth.java index c99f390d4..b4f0a1130 100644 --- a/terminal/src/main/java/org/jline/utils/WCWidth.java +++ b/terminal/src/main/java/org/jline/utils/WCWidth.java @@ -52,6 +52,9 @@ public static int wcwidth(int ucs) { /* binary search in table of non-spacing characters */ if (bisearch(ucs, combining, combining.length - 1)) return 0; + /* emojis characters */ + if (ucs >= 0x1f000 && ucs <= 0x1feee) return 2; + /* if we arrive here, ucs is not a combining or C0/C1 control character */ return 1 + ((ucs >= 0x1100 @@ -65,7 +68,6 @@ public static int wcwidth(int ucs) { || /* Vertical forms */ (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ (ucs >= 0xffe0 && ucs <= 0xffe6) - || (ucs >= 0x1f000 && ucs <= 0x1feee) || (ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd))) ? 1