diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/keymap/BindingReader.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/keymap/BindingReader.java
index 74d3a46ecf2a7..ef795842ba3b2 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/keymap/BindingReader.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/keymap/BindingReader.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -18,18 +18,39 @@
import jdk.internal.org.jline.utils.NonBlockingReader;
/**
- * The BindingReader will transform incoming chars into
- * key bindings
- *
- * @author Guillaume Nodet
+ * The BindingReader transforms incoming characters into key bindings.
+ *
+ * This class reads characters from a {@link NonBlockingReader} and maps them to
+ * bindings defined in a {@link KeyMap}. It handles multi-character key sequences,
+ * such as escape sequences for function keys, by reading ahead when necessary.
+ *
+ * Key features include:
+ *
+ * Support for timeout-based disambiguation of key sequences
+ * Buffering of input to handle multi-character sequences
+ * Support for running macros (predefined sequences of keystrokes)
+ * Access to the current input buffer and last binding read
+ *
+ *
+ * This class is used by the {@link org.jline.reader.LineReader} to read key bindings
+ * for line editing operations.
*/
public class BindingReader {
+ /** The non-blocking reader used to read input characters */
protected final NonBlockingReader reader;
+ /** Buffer for storing characters during key sequence processing */
protected final StringBuilder opBuffer = new StringBuilder();
+ /** Stack for pushing back characters that need to be re-read */
protected final Deque pushBackChar = new ArrayDeque<>();
+ /** The last key binding that was matched */
protected String lastBinding;
+ /**
+ * Creates a new BindingReader that reads from the specified NonBlockingReader.
+ *
+ * @param reader the NonBlockingReader to read characters from
+ */
public BindingReader(NonBlockingReader reader) {
this.reader = reader;
}
@@ -41,26 +62,55 @@ public BindingReader(NonBlockingReader reader) {
* binding can be found. Characters that can't possibly be matched to
* any binding will be send with the {@link KeyMap#getNomatch()} binding.
* Unicode (>= 128) characters will be matched to {@link KeyMap#getUnicode()}.
+ *
* If the current key sequence is ambiguous, i.e. the sequence is bound but
* it's also a prefix to other sequences, then the {@link KeyMap#getAmbiguousTimeout()}
* timeout will be used to wait for another incoming character.
- * If a character comes, the disambiguation will be done. If the timeout elapses
+ * If a character comes, the disambiguation will be done. If the timeout elapses
* and no character came in, or if the timeout is <= 0, the current bound operation
* will be returned.
+ *
+ * This method blocks until a complete key binding is read or the end of the
+ * stream is reached. If a binding is found in the KeyMap, it is returned.
+ * Otherwise, if the Unicode fallback is set in the KeyMap and a Unicode
+ * character is read, the Unicode fallback binding is returned.
*
- * @param keys the KeyMap to use for decoding the input stream
- * @param the type of bindings to be read
- * @return the decoded binding or null if the end of
- * stream has been reached
+ * @param the type of bindings in the KeyMap
+ * @param keys the KeyMap to use for mapping input to bindings
+ * @return the binding for the read key sequence, or null if the end of stream is reached
*/
public T readBinding(KeyMap keys) {
return readBinding(keys, null, true);
}
+ /**
+ * Reads a key binding from the input stream using the specified KeyMaps.
+ *
+ * This method works like {@link #readBinding(KeyMap)}, but it first checks
+ * the local KeyMap for a binding before falling back to the main KeyMap.
+ *
+ * @param the type of bindings in the KeyMaps
+ * @param keys the main KeyMap to use for mapping input to bindings
+ * @param local the local KeyMap to check first for bindings
+ * @return the binding for the read key sequence, or null if the end of stream is reached
+ */
public T readBinding(KeyMap keys, KeyMap local) {
return readBinding(keys, local, true);
}
+ /**
+ * Reads a key binding from the input stream using the specified KeyMaps.
+ *
+ * This method works like {@link #readBinding(KeyMap, KeyMap)}, but it allows
+ * specifying whether to block waiting for input or return immediately if no
+ * input is available.
+ *
+ * @param the type of bindings in the KeyMaps
+ * @param keys the main KeyMap to use for mapping input to bindings
+ * @param local the local KeyMap to check first for bindings, or null if none
+ * @param block whether to block waiting for input
+ * @return the binding for the read key sequence, or null if no input is available or the end of stream is reached
+ */
public T readBinding(KeyMap keys, KeyMap local, boolean block) {
lastBinding = null;
T o = null;
@@ -116,6 +166,17 @@ public T readBinding(KeyMap keys, KeyMap local, boolean block) {
return null;
}
+ /**
+ * Reads characters from the input until a specific sequence is encountered.
+ *
+ * This method reads characters one by one and accumulates them in a buffer
+ * until the specified terminating sequence is found.
+ *
+ *
+ * @param sequence the terminating sequence to look for
+ * @return the string read up to but not including the terminating sequence,
+ * or null if the end of the stream is reached before the sequence is found
+ */
public String readStringUntil(String sequence) {
StringBuilder sb = new StringBuilder();
if (!pushBackChar.isEmpty()) {
@@ -144,9 +205,15 @@ public String readStringUntil(String sequence) {
}
/**
- * Read a codepoint from the terminal.
+ * Reads a single character (Unicode code point) from the input stream.
+ *
+ * This method blocks until a character is available or the end of the stream
+ * is reached. It properly handles surrogate pairs for Unicode characters
+ * outside the BMP (Basic Multilingual Plane).
*
- * @return the character, or -1 if an EOF is received.
+ * @return the character read, or -1 if the end of the stream is reached
+ * @throws EndOfFileException if the stream is closed while reading
+ * @throws IOError if an I/O error occurs
*/
public int readCharacter() {
if (!pushBackChar.isEmpty()) {
@@ -170,6 +237,17 @@ public int readCharacter() {
}
}
+ /**
+ * Reads a single character (Unicode code point) from the input stream with buffering.
+ *
+ * This method attempts to read multiple characters at once for efficiency,
+ * storing them in an internal buffer. It properly handles surrogate pairs for
+ * Unicode characters outside the BMP (Basic Multilingual Plane).
+ *
+ * @return the character read, or -1 if the end of the stream is reached
+ * @throws EndOfFileException if the stream is closed while reading
+ * @throws IOError if an I/O error occurs
+ */
public int readCharacterBuffered() {
try {
if (pushBackChar.isEmpty()) {
@@ -211,6 +289,17 @@ public int readCharacterBuffered() {
}
}
+ /**
+ * Peeks at the next character in the input stream without consuming it.
+ *
+ * This method waits up to the specified timeout for a character to become
+ * available. If a character is available, it is returned but not consumed.
+ * If no character is available within the timeout, -2 is returned.
+ *
+ * @param timeout the maximum time to wait in milliseconds
+ * @return the next character, -1 if the end of the stream is reached, or -2 if the timeout expires
+ * @throws IOError if an I/O error occurs
+ */
public int peekCharacter(long timeout) {
if (!pushBackChar.isEmpty()) {
return pushBackChar.peek();
@@ -222,14 +311,41 @@ public int peekCharacter(long timeout) {
}
}
+ /**
+ * Runs a macro by pushing its characters into the input buffer.
+ *
+ * This method allows simulating keystrokes by pushing the characters of the
+ * macro string into the input buffer. These characters will be read before
+ * any actual input from the underlying reader.
+ *
+ * @param macro the string of characters to push into the input buffer
+ */
public void runMacro(String macro) {
macro.codePoints().forEachOrdered(pushBackChar::addLast);
}
+ /**
+ * Returns the current contents of the operation buffer.
+ *
+ * The operation buffer contains the characters of the current key sequence
+ * being processed. This is useful for debugging or displaying the current
+ * input state.
+ *
+ * @return the current operation buffer as a string
+ */
public String getCurrentBuffer() {
return opBuffer.toString();
}
+ /**
+ * Returns the last key binding that was successfully read.
+ *
+ * This method returns the string representation of the last key sequence
+ * that was successfully mapped to a binding. This can be useful for displaying
+ * the binding to the user or for implementing key sequence chaining.
+ *
+ * @return the last key binding, or null if no binding has been read yet
+ */
public String getLastBinding() {
return lastBinding;
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/keymap/KeyMap.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/keymap/KeyMap.java
index a8a90ea5e0dcf..9960c7a9e7e5b 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/keymap/KeyMap.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/keymap/KeyMap.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -19,14 +19,42 @@
import jdk.internal.org.jline.utils.InfoCmp.Capability;
/**
- * The KeyMap class contains all bindings from keys to operations.
+ * The KeyMap class maps keyboard input sequences to operations or actions.
+ *
+ * KeyMap is a core component of JLine's input handling system, providing the ability
+ * to bind specific key sequences (like Ctrl+A, Alt+F, or multi-key sequences) to
+ * arbitrary operations represented by objects of type T.
+ *
+ * Key features include:
+ *
+ * Support for single-key and multi-key sequences
+ * Special handling for Unicode characters not explicitly bound
+ * Timeout handling for ambiguous bindings (e.g., Escape key vs. Alt combinations)
+ * Utility methods for creating common key sequences (Ctrl, Alt, etc.)
+ *
+ *
+ * This class is used extensively by the {@link org.jline.reader.LineReader} to implement
+ * customizable key bindings for line editing operations.
*
- * @author Guillaume Nodet
+ * @param the type of objects to which key sequences are bound
* @since 2.6
*/
public class KeyMap {
+ /**
+ * The size of the direct mapping array for ASCII characters.
+ * Characters with code points below this value are mapped directly.
+ */
public static final int KEYMAP_LENGTH = 128;
+
+ /**
+ * Default timeout in milliseconds for ambiguous bindings.
+ *
+ * This is used when a prefix of a multi-character binding is also bound to an action.
+ * For example, if both Escape and Escape+[A are bound, the reader will wait this
+ * amount of time after receiving Escape to determine if it's a standalone Escape
+ * or the beginning of the Escape+[A sequence.
+ */
public static final long DEFAULT_AMBIGUOUS_TIMEOUT = 1000L;
private Object[] mapping = new Object[KEYMAP_LENGTH];
@@ -35,6 +63,23 @@ public class KeyMap {
private T nomatch;
private long ambiguousTimeout = DEFAULT_AMBIGUOUS_TIMEOUT;
+ /**
+ * Creates a new KeyMap.
+ */
+ public KeyMap() {
+ // Default constructor
+ }
+
+ /**
+ * Converts a key sequence to a displayable string representation.
+ *
+ * This method formats control characters, escape sequences, and other special
+ * characters in a readable way, similar to how they might be represented in
+ * configuration files.
+ *
+ * @param key the key sequence to display
+ * @return a readable string representation of the key sequence
+ */
public static String display(String key) {
StringBuilder sb = new StringBuilder();
sb.append("\"");
@@ -57,6 +102,15 @@ public static String display(String key) {
return sb.toString();
}
+ /**
+ * Translates a string containing special escape sequences into the actual key sequence.
+ *
+ * This method handles escape sequences like ^A (Ctrl+A), \e (Escape), \C-a (Ctrl+A),
+ * \M-a (Alt+A), etc., converting them to the actual character sequences they represent.
+ *
+ * @param str the string with escape sequences to translate
+ * @return the translated key sequence
+ */
public static String translate(String str) {
int i;
if (!str.isEmpty()) {
@@ -185,6 +239,15 @@ public static String translate(String str) {
return keySeq.toString();
}
+ /**
+ * Generates a collection of key sequences from a range specification.
+ *
+ * This method takes a range specification like "a-z" or "\C-a-\C-z" and
+ * returns a collection of all key sequences in that range.
+ *
+ * @param range the range specification
+ * @return a collection of key sequences in the specified range, or null if the range is invalid
+ */
public static Collection range(String range) {
String[] keys = range.split("-");
if (keys.length != 2) {
@@ -216,30 +279,83 @@ public static Collection range(String range) {
return seqs;
}
+ /**
+ * Returns the escape character as a string.
+ *
+ * @return the escape character ("\033")
+ */
public static String esc() {
return "\033";
}
+ /**
+ * Creates an Alt+key sequence for a single character.
+ *
+ * This is equivalent to pressing the Alt key and a character key simultaneously.
+ * Internally, this is represented as the escape character followed by the character.
+ *
+ * @param c the character to combine with Alt
+ * @return the Alt+character key sequence
+ */
public static String alt(char c) {
return "\033" + c;
}
+ /**
+ * Creates an Alt+key sequence for a string.
+ *
+ * This is equivalent to pressing the Alt key and typing a sequence of characters.
+ * Internally, this is represented as the escape character followed by the string.
+ *
+ * @param c the string to combine with Alt
+ * @return the Alt+string key sequence
+ */
public static String alt(String c) {
return "\033" + c;
}
+ /**
+ * Returns the delete character as a string.
+ *
+ * @return the delete character ("\177")
+ */
public static String del() {
return "\177";
}
+ /**
+ * Creates a Ctrl+key sequence for a character.
+ *
+ * This is equivalent to pressing the Ctrl key and a character key simultaneously.
+ * For example, Ctrl+A is represented as the character with code point 1.
+ *
+ * @param key the character to combine with Ctrl
+ * @return the Ctrl+key sequence
+ */
public static String ctrl(char key) {
return key == '?' ? del() : Character.toString((char) (Character.toUpperCase(key) & 0x1f));
}
+ /**
+ * Returns the escape sequence for a terminal capability.
+ *
+ * This method retrieves the escape sequence for special keys like arrow keys,
+ * function keys, etc., based on the terminal's capabilities.
+ *
+ * @param terminal the terminal to query
+ * @param capability the capability to retrieve
+ * @return the escape sequence for the specified capability
+ */
public static String key(Terminal terminal, Capability capability) {
return Curses.tputs(terminal.getStringCapability(capability));
}
+ /**
+ * Comparator for sorting key sequences.
+ *
+ * This comparator sorts key sequences first by length, then lexicographically.
+ * It's useful for ensuring that longer key sequences are checked before their prefixes.
+ */
public static final Comparator KEYSEQ_COMPARATOR = (s1, s2) -> {
int len1 = s1.length();
int len2 = s2.length();
@@ -261,34 +377,92 @@ public static String key(Terminal terminal, Capability capability) {
// Methods
//
+ /**
+ * Gets the binding for Unicode characters that don't have explicit bindings.
+ *
+ * This is used as a fallback for characters that don't have specific bindings
+ * in the keymap. Typically, this might be set to a function that inserts the
+ * character into the line buffer.
+ *
+ * @return the binding for Unicode characters
+ */
public T getUnicode() {
return unicode;
}
+ /**
+ * Sets the binding for Unicode characters that don't have explicit bindings.
+ *
+ * @param unicode the binding for Unicode characters
+ */
public void setUnicode(T unicode) {
this.unicode = unicode;
}
+ /**
+ * Gets the binding for input sequences that don't match any known binding.
+ *
+ * This is used as a fallback when an input sequence doesn't match any binding
+ * in the keymap.
+ *
+ * @return the binding for non-matching sequences
+ */
public T getNomatch() {
return nomatch;
}
+ /**
+ * Sets the binding for input sequences that don't match any known binding.
+ *
+ * @param nomatch the binding for non-matching sequences
+ */
public void setNomatch(T nomatch) {
this.nomatch = nomatch;
}
+ /**
+ * Gets the timeout for ambiguous key bindings in milliseconds.
+ *
+ * This timeout is used when a prefix of a multi-character binding is also bound
+ * to an action. The reader will wait this amount of time after receiving the prefix
+ * to determine if more characters are coming.
+ *
+ * @return the ambiguous binding timeout in milliseconds
+ */
public long getAmbiguousTimeout() {
return ambiguousTimeout;
}
+ /**
+ * Sets the timeout for ambiguous key bindings in milliseconds.
+ *
+ * @param ambiguousTimeout the ambiguous binding timeout in milliseconds
+ */
public void setAmbiguousTimeout(long ambiguousTimeout) {
this.ambiguousTimeout = ambiguousTimeout;
}
+ /**
+ * Gets the binding for the "another key" action.
+ *
+ * This binding is returned when a key sequence doesn't match any binding
+ * but is a prefix of a longer binding. It's typically used to implement
+ * incremental search or other features that need to process each character
+ * as it's typed.
+ *
+ * @return the "another key" binding
+ */
public T getAnotherKey() {
return anotherKey;
}
+ /**
+ * Returns a map of all bound key sequences and their associated bindings.
+ *
+ * The map is sorted by key sequence using the {@link #KEYSEQ_COMPARATOR}.
+ *
+ * @return a map of bound key sequences to their bindings
+ */
public Map getBoundKeys() {
Map bound = new TreeMap<>(KEYSEQ_COMPARATOR);
doGetBoundKeys(this, "", bound);
@@ -309,6 +483,22 @@ private static void doGetBoundKeys(KeyMap keyMap, String prefix, Map
+ * This method returns the binding for the given key sequence, if one exists.
+ * It also provides information about how much of the key sequence was consumed
+ * in the {@code remaining} parameter:
+ *
+ * If remaining[0] is -1, the entire sequence was consumed
+ * If remaining[0] is positive, that many characters at the end were not consumed
+ * If remaining[0] is 0, the sequence was consumed but no binding was found
+ *
+ *
+ * @param keySeq the key sequence to look up
+ * @param remaining array of length at least 1 to receive information about remaining characters
+ * @return the binding for the key sequence, or null if no binding was found
+ */
@SuppressWarnings("unchecked")
public T getBound(CharSequence keySeq, int[] remaining) {
remaining[0] = -1;
@@ -334,30 +524,75 @@ public T getBound(CharSequence keySeq, int[] remaining) {
}
}
+ /**
+ * Gets the binding for a key sequence.
+ *
+ * This method returns the binding for the given key sequence only if the entire
+ * sequence is consumed. If the sequence is a prefix of a longer binding or doesn't
+ * match any binding, null is returned.
+ *
+ * @param keySeq the key sequence to look up
+ * @return the binding for the key sequence, or null if no binding was found or the sequence is a prefix
+ */
public T getBound(CharSequence keySeq) {
int[] remaining = new int[1];
T res = getBound(keySeq, remaining);
return remaining[0] <= 0 ? res : null;
}
+ /**
+ * Binds a function to a key sequence only if the key sequence is not already bound.
+ *
+ * This method is useful for setting up default bindings that should not override
+ * user-defined bindings.
+ *
+ * @param function the function to bind
+ * @param keySeq the key sequence to bind to
+ */
public void bindIfNotBound(T function, CharSequence keySeq) {
if (function != null && keySeq != null) {
bind(this, keySeq, function, true);
}
}
+ /**
+ * Binds a function to multiple key sequences.
+ *
+ * This is a convenience method that calls {@link #bind(Object, CharSequence)}
+ * for each key sequence.
+ *
+ * @param function the function to bind
+ * @param keySeqs the key sequences to bind to
+ */
public void bind(T function, CharSequence... keySeqs) {
for (CharSequence keySeq : keySeqs) {
bind(function, keySeq);
}
}
+ /**
+ * Binds a function to multiple key sequences.
+ *
+ * This is a convenience method that calls {@link #bind(Object, CharSequence)}
+ * for each key sequence in the iterable.
+ *
+ * @param function the function to bind
+ * @param keySeqs the key sequences to bind to
+ */
public void bind(T function, Iterable extends CharSequence> keySeqs) {
for (CharSequence keySeq : keySeqs) {
bind(function, keySeq);
}
}
+ /**
+ * Binds a function to a key sequence.
+ *
+ * If the function is null, the key sequence is unbound.
+ *
+ * @param function the function to bind, or null to unbind
+ * @param keySeq the key sequence to bind to
+ */
public void bind(T function, CharSequence keySeq) {
if (keySeq != null) {
if (function == null) {
@@ -374,6 +609,13 @@ public void unbind(CharSequence... keySeqs) {
}
}
+ /**
+ * Unbinds a key sequence.
+ *
+ * This removes any binding associated with the given key sequence.
+ *
+ * @param keySeq the key sequence to unbind
+ */
public void unbind(CharSequence keySeq) {
if (keySeq != null) {
unbind(this, keySeq);
@@ -386,7 +628,7 @@ private static T unbind(KeyMap map, CharSequence keySeq) {
if (keySeq != null && keySeq.length() > 0) {
for (int i = 0; i < keySeq.length() - 1; i++) {
char c = keySeq.charAt(i);
- if (c > map.mapping.length) {
+ if (c >= map.mapping.length) {
return null;
}
if (!(map.mapping[c] instanceof KeyMap)) {
@@ -396,7 +638,7 @@ private static T unbind(KeyMap map, CharSequence keySeq) {
map = (KeyMap) map.mapping[c];
}
char c = keySeq.charAt(keySeq.length() - 1);
- if (c > map.mapping.length) {
+ if (c >= map.mapping.length) {
return null;
}
if (map.mapping[c] instanceof KeyMap) {
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/keymap/package-info.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/keymap/package-info.java
new file mode 100644
index 0000000000000..639158693faa1
--- /dev/null
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/keymap/package-info.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2002-2025, 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.
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+/**
+ * JLine 3 Keymap Package - Components for handling keyboard input and key bindings.
+ *
+ * This package provides the fundamental classes for mapping keyboard input sequences
+ * to actions in interactive terminal applications. It enables the creation of
+ * customizable key bindings similar to those found in editors like Emacs and Vi.
+ *
+ * Key components in this package include:
+ *
+ * {@link org.jline.keymap.KeyMap} - Maps key sequences to actions or commands
+ * {@link org.jline.keymap.BindingReader} - Reads input and translates it to bound actions
+ *
+ *
+ * The keymap system supports:
+ *
+ * Multi-character key sequences (e.g., Escape followed by other keys)
+ * Special keys like function keys, arrow keys, etc.
+ * Control key combinations
+ * Alt/Meta key combinations
+ * Unicode character input
+ *
+ *
+ * This package is used extensively by the {@link org.jline.reader.LineReader} to implement
+ * customizable editing capabilities.
+ *
+ * @since 3.0
+ */
+package jdk.internal.org.jline.keymap;
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Binding.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Binding.java
index 15e7a49a64318..bdcee49a0cf70 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Binding.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Binding.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -9,13 +9,28 @@
package jdk.internal.org.jline.reader;
/**
- * Marker interface for objects bound to key sequences.
+ * Marker interface for objects that can be bound to key sequences in a KeyMap.
+ *
+ * The Binding interface serves as a common type for different kinds of actions
+ * that can be triggered by key sequences in the line editor. JLine supports
+ * three main types of bindings:
+ *
+ * {@link Widget} - Executes a specific editing function
+ * {@link Macro} - Executes a sequence of keystrokes
+ * {@link Reference} - References another widget by name
+ *
+ *
+ * Key bindings are managed through KeyMaps, which map key sequences to Binding
+ * objects. When a user presses a key sequence, the LineReader looks up the
+ * corresponding Binding in the current KeyMap and executes it.
+ *
+ * This interface doesn't define any methods; it's used purely as a marker
+ * to identify objects that can be bound to key sequences.
*
* @see Macro
* @see Reference
* @see Widget
* @see org.jline.keymap.KeyMap
- *
- * @author Guillaume Nodet
+ * @see LineReader#getKeyMaps()
*/
public interface Binding {}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Buffer.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Buffer.java
index 8d65891d9d900..5f207b2ad7337 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Buffer.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Buffer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2017, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -8,70 +8,223 @@
*/
package jdk.internal.org.jline.reader;
+/**
+ * Represents the editable text buffer in the LineReader.
+ *
+ * The Buffer interface provides methods for manipulating the text that the user
+ * is currently editing in the LineReader. It supports operations such as cursor
+ * movement, text insertion and deletion, and content retrieval.
+ *
+ * The buffer maintains a current cursor position that indicates where text will
+ * be inserted or deleted. Many of the methods in this interface operate relative
+ * to this cursor position.
+ *
+ * The default implementation is {@link org.jline.reader.impl.BufferImpl}.
+ *
+ * @see LineReader#getBuffer()
+ * @see org.jline.reader.impl.BufferImpl
+ */
public interface Buffer {
/*
* Read access
*/
+ /**
+ * Returns the current cursor position in the buffer.
+ *
+ * @return the current cursor position (0-based index)
+ */
int cursor();
+ /**
+ * Returns the character at the specified position in the buffer.
+ *
+ * @param i the position to check
+ * @return the character at the specified position, or -1 if the position is invalid
+ */
int atChar(int i);
+ /**
+ * Returns the length of the buffer.
+ *
+ * @return the number of characters in the buffer
+ */
int length();
+ /**
+ * Returns the character at the current cursor position.
+ *
+ * @return the character at the cursor position, or -1 if the cursor is at the end of the buffer
+ */
int currChar();
+ /**
+ * Returns the character before the current cursor position.
+ *
+ * @return the character before the cursor position, or -1 if the cursor is at the beginning of the buffer
+ */
int prevChar();
+ /**
+ * Returns the character after the current cursor position.
+ *
+ * @return the character after the cursor position, or -1 if the cursor is at the end of the buffer
+ */
int nextChar();
/*
* Movement
*/
+ /**
+ * Moves the cursor to the specified position.
+ *
+ * @param position the position to move the cursor to
+ * @return true if the cursor was moved, false if the position was invalid
+ */
boolean cursor(int position);
+ /**
+ * Moves the cursor by the specified number of characters.
+ * Positive values move right, negative values move left.
+ *
+ * @param num the number of characters to move
+ * @return the number of positions actually moved
+ */
int move(int num);
+ /**
+ * Moves the cursor up one line while maintaining the same column position if possible.
+ * This is used for multi-line editing.
+ *
+ * @return true if the cursor was moved, false if it was already at the first line
+ */
boolean up();
+ /**
+ * Moves the cursor down one line while maintaining the same column position if possible.
+ * This is used for multi-line editing.
+ *
+ * @return true if the cursor was moved, false if it was already at the last line
+ */
boolean down();
+ /**
+ * Moves the cursor by the specified number of columns and rows.
+ * This is used for multi-line editing.
+ *
+ * @param dx the number of columns to move (positive for right, negative for left)
+ * @param dy the number of rows to move (positive for down, negative for up)
+ * @return true if the cursor was moved, false otherwise
+ */
boolean moveXY(int dx, int dy);
/*
* Modification
*/
+ /**
+ * Clears the buffer content.
+ *
+ * @return true if the buffer was modified
+ */
boolean clear();
+ /**
+ * Replaces the character at the current cursor position.
+ *
+ * @param c the character to set at the current position
+ * @return true if the buffer was modified
+ */
boolean currChar(int c);
+ /**
+ * Writes a character at the current cursor position and advances the cursor.
+ *
+ * @param c the character to write
+ */
void write(int c);
+ /**
+ * Writes a character at the current cursor position and advances the cursor.
+ *
+ * @param c the character to write
+ * @param overTyping if true, overwrites the character at the current position
+ */
void write(int c, boolean overTyping);
+ /**
+ * Writes a string at the current cursor position and advances the cursor.
+ *
+ * @param str the string to write
+ */
void write(CharSequence str);
+ /**
+ * Writes a string at the current cursor position and advances the cursor.
+ *
+ * @param str the string to write
+ * @param overTyping if true, overwrites characters at the current position
+ */
void write(CharSequence str, boolean overTyping);
+ /**
+ * Deletes the character before the cursor position.
+ *
+ * @return true if the buffer was modified
+ */
boolean backspace();
+ /**
+ * Deletes multiple characters before the cursor position.
+ *
+ * @param num the number of characters to delete
+ * @return the number of characters actually deleted
+ */
int backspace(int num);
+ /**
+ * Deletes the character at the cursor position.
+ *
+ * @return true if the buffer was modified
+ */
boolean delete();
+ /**
+ * Deletes multiple characters starting at the cursor position.
+ *
+ * @param num the number of characters to delete
+ * @return the number of characters actually deleted
+ */
int delete(int num);
/*
* String
*/
+ /**
+ * Returns a substring of the buffer from the specified start position to the end.
+ *
+ * @param start the start index, inclusive
+ * @return the substring
+ */
String substring(int start);
+ /**
+ * Returns a substring of the buffer from the specified start position to the specified end position.
+ *
+ * @param start the start index, inclusive
+ * @param end the end index, exclusive
+ * @return the substring
+ */
String substring(int start, int end);
+ /**
+ * Returns a substring of the buffer from the beginning to the current cursor position.
+ *
+ * @return the substring
+ */
String upToCursor();
String toString();
@@ -80,8 +233,18 @@ public interface Buffer {
* Copy
*/
+ /**
+ * Creates a copy of this buffer.
+ *
+ * @return a new buffer with the same content and cursor position
+ */
Buffer copy();
+ /**
+ * Copies the content and cursor position from another buffer.
+ *
+ * @param buffer the buffer to copy from
+ */
void copyFrom(Buffer buffer);
/**
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Candidate.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Candidate.java
index e28e72ffb2322..eddff0a756857 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Candidate.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Candidate.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2019, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -11,9 +11,30 @@
import java.util.Objects;
/**
- * A completion candidate.
+ * Represents a completion candidate for tab completion.
+ *
+ * A Candidate encapsulates all the information needed to display and apply a
+ * completion suggestion. This includes the actual text to be inserted, how it
+ * should be displayed to the user, grouping information, descriptions, and
+ * other metadata that controls how the candidate behaves when selected.
+ *
+ * Candidates are created by {@link Completer} implementations and passed to the
+ * LineReader, which then filters, sorts, and displays them to the user when
+ * tab completion is requested.
+ *
+ * Each candidate has several properties:
+ *
+ * value - The actual text to be inserted when the candidate is selected
+ * display - How the candidate should be displayed to the user (may include ANSI styling)
+ * group - Optional grouping category for organizing related candidates
+ * description - Optional help text explaining the candidate
+ * suffix - Optional text to append when the candidate is selected
+ * complete - Whether the candidate is a complete word or may be further expanded
+ *
*
- * @author Guillaume Nodet
+ * @see Completer
+ * @see LineReader.Option#AUTO_GROUP
+ * @see LineReader.Option#GROUP
*/
public class Candidate implements Comparable {
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Completer.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Completer.java
index 7a5937460d87c..6f44ceeb2d12b 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Completer.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Completer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -12,26 +12,45 @@
/**
* A completer is the mechanism by which tab-completion candidates will be resolved.
+ *
+ * Completers are used to provide context-sensitive suggestions when the user presses
+ * the tab key while typing a command. They analyze the current input line and generate
+ * a list of possible completions based on the context.
+ *
+ * JLine provides several built-in completers in the {@code org.jline.reader.impl.completer}
+ * package and in the {@code org.jline.builtins.Completers} class.
+ *
+ * Completers can be combined and nested to create sophisticated completion behavior.
+ * They are typically registered with a {@link LineReader} using the
+ * {@link LineReaderBuilder#completer(Completer)} method.
*
- * @author Marc Prud'hommeaux
- * @author Jason Dillon
- * @author Guillaume Nodet
* @since 2.3
+ * @see Candidate
+ * @see LineReaderBuilder#completer(Completer)
*/
public interface Completer {
/**
* Populates candidates with a list of possible completions for the command line .
- *
+ *
* The list of candidates will be sorted and filtered by the LineReader, so that
* the list of candidates displayed to the user will usually be smaller than
- * the list given by the completer. Thus it is not necessary for the completer
- * to do any matching based on the current buffer. On the contrary, in order
+ * the list given by the completer. Thus it is not necessary for the completer
+ * to do any matching based on the current buffer. On the contrary, in order
* for the typo matcher to work, all possible candidates for the word being
* completed should be returned.
+ *
+ * Implementations should add {@link Candidate} objects to the candidates list.
+ * Each candidate can include additional information such as descriptions, groups,
+ * and display attributes that will be used when presenting completion options
+ * to the user.
+ *
+ * This method is called by the LineReader when the user requests completion,
+ * typically by pressing the Tab key.
*
- * @param reader The line reader
- * @param line The parsed command line
- * @param candidates The {@link List} of candidates to populate
+ * @param reader The line reader instance that is requesting completion
+ * @param line The parsed command line containing the current input state
+ * @param candidates The {@link List} of candidates to populate with completion options
+ * @see Candidate
*/
void complete(LineReader reader, ParsedLine line, List candidates);
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/CompletingParsedLine.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/CompletingParsedLine.java
index 93558001af16a..f127db4b6fa56 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/CompletingParsedLine.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/CompletingParsedLine.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -12,14 +12,51 @@
* An extension of {@link ParsedLine} that, being aware of the quoting and escaping rules
* of the {@link org.jline.reader.Parser} that produced it, knows if and how a completion candidate
* should be escaped/quoted.
+ *
+ * This interface adds methods to handle the raw (unprocessed) form of words, including
+ * any quotes and escape characters that may be present in the original input. It also
+ * provides functionality to properly escape completion candidates according to the
+ * parser's syntax rules.
+ *
+ * Implementations of this interface are crucial for proper tab completion in shells
+ * that support complex quoting and escaping mechanisms, ensuring that completed text
+ * is properly formatted according to the shell's syntax.
*
- * @author Eric Bottard
+ * @see ParsedLine
+ * @see Parser
*/
public interface CompletingParsedLine extends ParsedLine {
+ /**
+ * Escapes a completion candidate according to the parser's quoting and escaping rules.
+ *
+ * This method ensures that special characters in the candidate are properly escaped
+ * or quoted according to the syntax rules of the parser, maintaining consistency with
+ * the current input line's quoting style.
+ *
+ * @param candidate the completion candidate that may need escaping
+ * @param complete true if this is a complete word, false if it's a partial completion
+ * @return the properly escaped/quoted candidate ready for insertion
+ */
CharSequence escape(CharSequence candidate, boolean complete);
+ /**
+ * Returns the cursor position within the raw (unprocessed) current word.
+ *
+ * Unlike {@link ParsedLine#wordCursor()}, this method returns the cursor position
+ * in the original word text, including any quotes and escape characters.
+ *
+ * @return the cursor position within the raw current word
+ */
int rawWordCursor();
+ /**
+ * Returns the length of the raw (unprocessed) current word.
+ *
+ * This is the length of the original word text, including any quotes and
+ * escape characters that may have been removed during parsing.
+ *
+ * @return the length of the raw current word
+ */
int rawWordLength();
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/CompletionMatcher.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/CompletionMatcher.java
index 4a2334642cad5..4b9d81e254301 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/CompletionMatcher.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/CompletionMatcher.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2020, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -11,17 +11,46 @@
import java.util.List;
import java.util.Map;
+/**
+ * Interface for matching and filtering completion candidates.
+ *
+ * The CompletionMatcher is responsible for determining which completion candidates
+ * should be presented to the user based on what they've typed so far. It implements
+ * the logic for matching candidates against the input, handling case sensitivity,
+ * typo tolerance, and other matching strategies.
+ *
+ * This interface allows for different matching algorithms to be used, such as:
+ *
+ * Prefix matching - candidates that start with the input text
+ * Substring matching - candidates that contain the input text
+ * Fuzzy matching - candidates that approximately match the input text
+ * Camel case matching - matching based on camel case patterns
+ *
+ *
+ * The default implementation is {@link org.jline.reader.impl.CompletionMatcherImpl}.
+ *
+ * @see org.jline.reader.impl.CompletionMatcherImpl
+ * @see LineReader.Option#COMPLETE_MATCHER_TYPO
+ * @see LineReader.Option#COMPLETE_MATCHER_CAMELCASE
+ */
public interface CompletionMatcher {
/**
- * Compiles completion matcher functions
+ * Initializes the matcher with the current completion context.
+ *
+ * This method is called before any matching operations to set up the matcher
+ * with the current completion context, including the line being completed,
+ * reader options, and other parameters that affect how matching should be performed.
+ *
+ * The matcher uses this information to compile its internal matching functions
+ * that will be used to filter candidates.
*
- * @param options LineReader options
- * @param prefix invoked by complete-prefix or expand-or-complete-prefix widget
- * @param line The parsed line within which completion has been requested
- * @param caseInsensitive if completion is case insensitive or not
- * @param errors number of errors accepted in matching
- * @param originalGroupName value of JLineReader variable original-group-name
+ * @param options LineReader options that may affect matching behavior
+ * @param prefix true if invoked by complete-prefix or expand-or-complete-prefix widget
+ * @param line the parsed line within which completion has been requested
+ * @param caseInsensitive true if completion should be case insensitive
+ * @param errors number of typo errors accepted in matching (for fuzzy matching)
+ * @param originalGroupName value of the LineReader variable original-group-name
*/
void compile(
Map options,
@@ -32,21 +61,37 @@ void compile(
String originalGroupName);
/**
+ * Filters the provided candidates based on the current matching criteria.
+ *
+ * This method applies the matching algorithm to the list of candidates and
+ * returns only those that match the current input according to the configured
+ * matching rules. The returned list may be sorted based on match quality.
*
- * @param candidates list of candidates
- * @return a list of candidates that completion matcher matches
+ * @param candidates the list of candidates to filter
+ * @return a list of candidates that match the current input
*/
List matches(List candidates);
/**
+ * Returns a candidate that exactly matches the current input, if any.
+ *
+ * An exact match typically means the candidate's value is identical to what
+ * the user has typed, possibly ignoring case depending on the matcher configuration.
+ * This is used to determine if the completion should be accepted immediately
+ * without showing a list of options.
*
- * @return a candidate that have exact match, null if no exact match found
+ * @return a candidate that exactly matches the current input, or null if no exact match is found
*/
Candidate exactMatch();
/**
+ * Returns the longest common prefix shared by all matched candidates.
+ *
+ * This is used to implement tab completion behavior where pressing tab will
+ * automatically complete as much of the input as can be unambiguously determined
+ * from the available candidates.
*
- * @return a common prefix of matched candidates
+ * @return the longest common prefix of all matched candidates, or an empty string if none
*/
String getCommonPrefix();
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/EOFError.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/EOFError.java
index 8a4f516174e17..4552904fd1dc2 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/EOFError.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/EOFError.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -8,6 +8,24 @@
*/
package jdk.internal.org.jline.reader;
+/**
+ * Exception thrown when parsing is incomplete due to unexpected end of input.
+ *
+ * EOFError is a specialized type of {@link SyntaxError} that indicates the input
+ * is incomplete rather than invalid. This typically occurs when the user has entered
+ * an incomplete construct, such as an unclosed quote, parenthesis, or bracket.
+ *
+ * This exception provides additional information about what is missing to complete
+ * the input, which can be used by the LineReader to provide appropriate feedback
+ * to the user, such as a continuation prompt that indicates what needs to be closed.
+ *
+ * The name "EOFError" refers to "End Of File Error", indicating that the parser
+ * reached the end of the input before the syntax was complete.
+ *
+ * @see SyntaxError
+ * @see Parser
+ * @see Parser.ParseContext#SECONDARY_PROMPT
+ */
public class EOFError extends SyntaxError {
private static final long serialVersionUID = 1L;
@@ -31,14 +49,38 @@ public EOFError(int line, int column, String message, String missing, int openBr
this.nextClosingBracket = nextClosingBracket;
}
+ /**
+ * Returns the string that is missing to complete the input.
+ *
+ * This is typically a closing delimiter such as a quote, parenthesis, or bracket
+ * that would complete the current syntactic construct.
+ *
+ * @return the missing string, or null if not applicable
+ */
public String getMissing() {
return missing;
}
+ /**
+ * Returns the number of unclosed brackets in the input.
+ *
+ * This count can be used to determine how many closing brackets are needed
+ * to complete the input.
+ *
+ * @return the number of unclosed brackets
+ */
public int getOpenBrackets() {
return openBrackets;
}
+ /**
+ * Returns the next closing bracket that is expected.
+ *
+ * This indicates the specific type of bracket (e.g., ')', ']', or '}') that
+ * is expected next to continue closing the open brackets.
+ *
+ * @return the next expected closing bracket, or null if not applicable
+ */
public String getNextClosingBracket() {
return nextClosingBracket;
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Editor.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Editor.java
index b2fadd3bd5213..625707481aae6 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Editor.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Editor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2019, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -11,10 +11,57 @@
import java.io.IOException;
import java.util.List;
+/**
+ * Interface for launching external editors from within a JLine application.
+ *
+ * The Editor interface provides functionality for opening and editing files in
+ * an external text editor. This allows JLine applications to offer users the
+ * ability to edit content in their preferred editor rather than being limited
+ * to the line editing capabilities of the terminal.
+ *
+ * Typical use cases include:
+ *
+ * Editing configuration files
+ * Writing or modifying scripts
+ * Composing long text content
+ *
+ *
+ * Implementations of this interface handle the details of launching the external
+ * editor process, waiting for it to complete, and potentially reading back the
+ * edited content.
+ *
+ * @see LineReader#editAndAddInBuffer(java.nio.file.Path)
+ */
public interface Editor {
+ /**
+ * Opens the specified files in the external editor.
+ *
+ * This method launches the external editor with the given files as arguments.
+ * The behavior depends on the specific editor implementation and configuration.
+ *
+ * @param files the list of files to open in the editor
+ * @throws IOException if an I/O error occurs while launching the editor
+ */
public void open(List files) throws IOException;
+ /**
+ * Runs the editor process.
+ *
+ * This method starts the editor process and typically waits for it to complete.
+ * The specific behavior depends on the editor implementation.
+ *
+ * @throws IOException if an I/O error occurs while running the editor
+ */
public void run() throws IOException;
+ /**
+ * Sets whether the editor should run in restricted mode.
+ *
+ * In restricted mode, the editor may have limited functionality or access
+ * to certain features or files. This is typically used for security reasons
+ * when the application needs to limit what the user can do in the editor.
+ *
+ * @param restricted true to enable restricted mode, false otherwise
+ */
public void setRestricted(boolean restricted);
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/EndOfFileException.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/EndOfFileException.java
index 1e50a7ea1ae55..c130d4797f3bf 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/EndOfFileException.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/EndOfFileException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2020, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Expander.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Expander.java
index 614c89b3e6100..dec4567422e42 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Expander.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Expander.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -8,9 +8,50 @@
*/
package jdk.internal.org.jline.reader;
+/**
+ * The Expander interface provides functionality for expanding special syntax in command lines.
+ *
+ * Expanders are responsible for processing and expanding various types of expressions
+ * in the input line before it is executed. This includes:
+ *
+ * History expansions (e.g., !! to repeat the last command)
+ * Variable expansions (e.g., $HOME or ${HOME})
+ * Other shell-like expansions
+ *
+ *
+ * The expander is called by the LineReader after the user has accepted a line but before
+ * it is executed or added to the history. This allows the user to see the unexpanded form
+ * while editing, but ensures that the expanded form is what gets executed and stored in history.
+ *
+ * The default implementation is {@link org.jline.reader.impl.DefaultExpander}.
+ *
+ * @see org.jline.reader.impl.DefaultExpander
+ * @see LineReader#getExpander()
+ * @see LineReaderBuilder#expander(Expander)
+ */
public interface Expander {
+ /**
+ * Expands history references in the input line.
+ *
+ * This method processes history designators such as !!, !$, !n, etc., replacing
+ * them with the corresponding entries from the command history.
+ *
+ * @param history the command history to use for expansion
+ * @param line the input line containing history references
+ * @return the line with history references expanded
+ */
String expandHistory(History history, String line);
+ /**
+ * Expands variables in the input word.
+ *
+ * This method processes variable references such as $VAR or ${VAR}, replacing
+ * them with their values. The specific syntax and behavior depends on the
+ * implementation.
+ *
+ * @param word the word containing variable references
+ * @return the word with variables expanded
+ */
String expandVar(String word);
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Highlighter.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Highlighter.java
index dad62551ee062..40892a95c76c6 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Highlighter.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Highlighter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2021, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -12,30 +12,69 @@
import jdk.internal.org.jline.utils.AttributedString;
+/**
+ * The Highlighter interface provides syntax highlighting functionality for the LineReader.
+ *
+ * Highlighters are responsible for applying visual styling to the command line text as
+ * the user types. This can include syntax highlighting for programming languages,
+ * highlighting matching brackets, marking errors, or any other visual cues that help
+ * users understand the structure and validity of their input.
+ *
+ * Implementations convert plain text into {@link AttributedString} instances that contain
+ * both the text and its visual styling information. The LineReader will then render
+ * these styled strings to the terminal with the appropriate colors and text attributes.
+ *
+ * The default implementation is {@link org.jline.reader.impl.DefaultHighlighter}.
+ *
+ * @see org.jline.utils.AttributedString
+ * @see org.jline.reader.impl.DefaultHighlighter
+ * @see LineReaderBuilder#highlighter(Highlighter)
+ */
public interface Highlighter {
/**
- * Highlight buffer
- * @param reader LineReader
- * @param buffer the buffer to be highlighted
- * @return highlighted buffer
+ * Highlights the provided text buffer with appropriate styling.
+ *
+ * This method is called by the LineReader to apply syntax highlighting to the
+ * current input line. It should analyze the buffer content and return an
+ * AttributedString with appropriate styling applied based on the content's
+ * syntax, structure, or other relevant characteristics.
+ *
+ * @param reader The LineReader instance requesting highlighting
+ * @param buffer The text buffer to be highlighted
+ * @return An AttributedString containing the highlighted buffer with styling applied
*/
AttributedString highlight(LineReader reader, String buffer);
/**
- * Refresh highlight configuration
+ * Refreshes the highlighter's configuration.
+ *
+ * This method is called when the highlighter should reload or refresh its
+ * configuration, such as when color schemes change or when syntax rules are updated.
+ * The default implementation does nothing.
+ *
+ * @param reader The LineReader instance associated with this highlighter
*/
default void refresh(LineReader reader) {}
/**
- * Set error pattern to be highlighted
- * @param errorPattern error pattern to be highlighted
+ * Sets a regular expression pattern that identifies errors to be highlighted.
+ *
+ * Text matching this pattern will typically be highlighted with error styling
+ * (often red or with a distinctive background color) to indicate problematic input.
+ *
+ * @param errorPattern A regular expression pattern that matches text to be highlighted as errors
*/
- void setErrorPattern(Pattern errorPattern);
+ default void setErrorPattern(Pattern errorPattern) {}
/**
- * Set error index to be highlighted
- * @param errorIndex error index to be highlighted
+ * Sets a specific character position in the buffer to be highlighted as an error.
+ *
+ * This is typically used to indicate the exact position of a syntax error or
+ * other issue in the input line. The highlighter will apply error styling at
+ * this position.
+ *
+ * @param errorIndex The character index in the buffer to be highlighted as an error
*/
- void setErrorIndex(int errorIndex);
+ default void setErrorIndex(int errorIndex) {}
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/History.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/History.java
index 0cb46a1ee9dfc..27c848bab407f 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/History.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/History.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -15,11 +15,29 @@
import java.util.ListIterator;
/**
- * Console history.
+ * Console command history management interface.
+ *
+ * The History interface provides functionality for storing, retrieving, and navigating
+ * through previously entered commands. It allows users to recall and reuse commands
+ * they've typed before, which is a fundamental feature of interactive command-line
+ * interfaces.
+ *
+ * History implementations typically support:
+ *
+ * Adding new entries as commands are executed
+ * Navigating backward and forward through history
+ * Persisting history to a file for use across sessions
+ * Filtering or ignoring certain commands based on patterns
+ *
+ *
+ * Each history entry contains the command text along with metadata such as the
+ * timestamp when it was executed.
+ *
+ * The default implementation is {@link org.jline.reader.impl.history.DefaultHistory}.
*
- * @author Marc Prud'hommeaux
- * @author Jason Dillon
* @since 2.3
+ * @see LineReader#getHistory()
+ * @see LineReaderBuilder#history(History)
*/
public interface History extends Iterable {
@@ -74,24 +92,61 @@ public interface History extends Iterable {
*/
void purge() throws IOException;
+ /**
+ * Returns the number of items in the history.
+ *
+ * @return the number of history items
+ */
int size();
+ /**
+ * Checks if the history is empty.
+ *
+ * @return true if the history contains no items
+ */
default boolean isEmpty() {
return size() == 0;
}
+ /**
+ * Returns the current index in the history.
+ *
+ * @return the current index
+ */
int index();
+ /**
+ * Returns the index of the first element in the history.
+ *
+ * @return the index of the first history item
+ */
int first();
+ /**
+ * Returns the index of the last element in the history.
+ *
+ * @return the index of the last history item
+ */
int last();
+ /**
+ * Returns the history item at the specified index.
+ *
+ * @param index the index of the history item to retrieve
+ * @return the history item at the specified index
+ */
String get(int index);
default void add(String line) {
add(Instant.now(), line);
}
+ /**
+ * Adds a new item to the history with the specified timestamp.
+ *
+ * @param time the timestamp for the history item
+ * @param line the line to add to the history
+ */
void add(Instant time, String line);
/**
@@ -108,14 +163,41 @@ default boolean isPersistable(Entry entry) {
// Entries
//
+ /**
+ * Represents a single history entry containing a command line and its metadata.
+ *
+ * Each entry in the history has an index position, a timestamp indicating when
+ * it was added, and the actual command line text.
+ */
interface Entry {
+ /**
+ * Returns the index of this entry in the history.
+ *
+ * @return the index position of this entry
+ */
int index();
+ /**
+ * Returns the timestamp when this entry was added to the history.
+ *
+ * @return the timestamp of this entry
+ */
Instant time();
+ /**
+ * Returns the command line text of this entry.
+ *
+ * @return the command line text
+ */
String line();
}
+ /**
+ * Returns a list iterator over the history entries starting at the specified index.
+ *
+ * @param index the index to start iterating from
+ * @return a list iterator over the history entries
+ */
ListIterator iterator(int index);
default ListIterator iterator() {
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/LineReader.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/LineReader.java
index ca1c34ef4eed1..2dd5642794a98 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/LineReader.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/LineReader.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2023, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -417,6 +417,14 @@ public interface LineReader {
*/
String SYSTEM_PROPERTY_PREFIX = "system-property-prefix";
+ /**
+ * Returns the default key maps used by the LineReader.
+ *
+ * These key maps define the standard key bindings for different editing modes
+ * such as Emacs mode, Vi command mode, Vi insert mode, etc.
+ *
+ * @return a map of key map names to key maps
+ */
Map> defaultKeyMaps();
enum Option {
@@ -425,12 +433,14 @@ enum Option {
COMPLETE_MATCHER_CAMELCASE,
/** use type completion matcher */
COMPLETE_MATCHER_TYPO(true),
+ /** disable special handling of magic history expansion commands like "!" and "!!" and "!n" and "!-n" and "!string" and "^string1^string2", as well as [interpret escape characters](https://github.com/jline/jline3/issues/1238) **/
DISABLE_EVENT_EXPANSION,
HISTORY_VERIFY,
HISTORY_IGNORE_SPACE(true),
HISTORY_IGNORE_DUPS(true),
HISTORY_REDUCE_BLANKS(true),
HISTORY_BEEP(true),
+ /** automatically save history when the line reader reads a new line (enabled by default) */
HISTORY_INCREMENTAL(true),
HISTORY_TIMESTAMPED(true),
/** when displaying candidates, group them by {@link Candidate#group()} */
@@ -690,32 +700,151 @@ String readLine(String prompt, String rightPrompt, MaskingCallback maskingCallba
// Chainable setters
//
+ /**
+ * Sets a variable in the LineReader and returns the LineReader for method chaining.
+ *
+ * Variables control various aspects of the LineReader's behavior. See the
+ * various variable constants defined in this interface for available options.
+ *
+ * @param name the variable name
+ * @param value the variable value
+ * @return this LineReader
+ */
LineReader variable(String name, Object value);
+ /**
+ * Sets an option in the LineReader and returns the LineReader for method chaining.
+ *
+ * Options control various aspects of the LineReader's behavior. See the
+ * {@link Option} enum for available options.
+ *
+ * @param option the option to set
+ * @param value the option value
+ * @return this LineReader
+ */
LineReader option(Option option, boolean value);
+ /**
+ * Calls a widget by name.
+ *
+ * Widgets are functions that perform editing operations. This method allows
+ * invoking a widget programmatically rather than through a key binding.
+ *
+ * @param name the name of the widget to call
+ */
void callWidget(String name);
+ /**
+ * Returns a map of all variables set in the LineReader.
+ *
+ * Variables control various aspects of the LineReader's behavior. See the
+ * various variable constants defined in this interface for available options.
+ *
+ * @return a map of variable names to their values
+ */
Map getVariables();
+ /**
+ * Returns the value of a variable.
+ *
+ * Variables control various aspects of the LineReader's behavior. See the
+ * various variable constants defined in this interface for available options.
+ *
+ * @param name the variable name
+ * @return the variable value, or null if the variable is not set
+ */
Object getVariable(String name);
+ /**
+ * Sets a variable in the LineReader.
+ *
+ * Variables control various aspects of the LineReader's behavior. See the
+ * various variable constants defined in this interface for available options.
+ *
+ * @param name the variable name
+ * @param value the variable value
+ */
void setVariable(String name, Object value);
+ /**
+ * Checks if an option is set.
+ *
+ * Options control various aspects of the LineReader's behavior. See the
+ * {@link Option} enum for available options.
+ *
+ * @param option the option to check
+ * @return true if the option is set, false otherwise
+ */
boolean isSet(Option option);
+ /**
+ * Sets an option to true.
+ *
+ * Options control various aspects of the LineReader's behavior. See the
+ * {@link Option} enum for available options.
+ *
+ * @param option the option to set
+ */
void setOpt(Option option);
+ /**
+ * Sets an option to false.
+ *
+ * Options control various aspects of the LineReader's behavior. See the
+ * {@link Option} enum for available options.
+ *
+ * @param option the option to unset
+ */
void unsetOpt(Option option);
+ /**
+ * Returns the terminal associated with this LineReader.
+ *
+ * The terminal is used for input/output operations and provides information
+ * about the terminal capabilities and size.
+ *
+ * @return the terminal
+ */
Terminal getTerminal();
+ /**
+ * Returns a map of all widgets registered with this LineReader.
+ *
+ * Widgets are functions that perform editing operations and can be bound
+ * to key sequences.
+ *
+ * @return a map of widget names to widgets
+ */
Map getWidgets();
+ /**
+ * Returns a map of all built-in widgets provided by the LineReader.
+ *
+ * Built-in widgets implement standard editing operations like cursor movement,
+ * text deletion, history navigation, etc.
+ *
+ * @return a map of built-in widget names to widgets
+ */
Map getBuiltinWidgets();
+ /**
+ * Returns the current line buffer.
+ *
+ * The buffer contains the text that the user is currently editing.
+ * It provides methods for manipulating the text and cursor position.
+ *
+ * @return the current line buffer
+ */
Buffer getBuffer();
+ /**
+ * Returns the application name associated with this LineReader.
+ *
+ * The application name is used for various purposes, such as naming
+ * history files and identifying the application in terminal titles.
+ *
+ * @return the application name
+ */
String getAppName();
/**
@@ -741,50 +870,218 @@ String readLine(String prompt, String rightPrompt, MaskingCallback maskingCallba
*/
MouseEvent readMouseEvent();
+ /**
+ * Returns the history associated with this LineReader.
+ *
+ * The history stores previously entered command lines and provides
+ * methods for navigating, searching, and managing history entries.
+ *
+ * @return the command history
+ */
History getHistory();
+ /**
+ * Returns the parser associated with this LineReader.
+ *
+ * The parser is responsible for breaking command lines into tokens
+ * according to the syntax rules of the shell or application.
+ *
+ * @return the parser
+ */
Parser getParser();
+ /**
+ * Returns the highlighter associated with this LineReader.
+ *
+ * The highlighter is responsible for applying syntax highlighting
+ * to the command line as the user types.
+ *
+ * @return the highlighter
+ */
Highlighter getHighlighter();
+ /**
+ * Returns the expander associated with this LineReader.
+ *
+ * The expander is responsible for expanding special syntax in the command line,
+ * such as history references (e.g., !!, !$) and variables (e.g., $HOME).
+ *
+ * @return the expander
+ */
Expander getExpander();
+ /**
+ * Returns all key maps registered with this LineReader.
+ *
+ * Key maps define the mappings from key sequences to actions for different
+ * editing modes (e.g., Emacs mode, Vi command mode, Vi insert mode).
+ *
+ * @return a map of key map names to key maps
+ */
Map> getKeyMaps();
+ /**
+ * Returns the name of the currently active key map.
+ *
+ * The active key map determines how key presses are interpreted and
+ * which actions they trigger.
+ *
+ * @return the name of the active key map
+ */
String getKeyMap();
+ /**
+ * Sets the active key map by name.
+ *
+ * The active key map determines how key presses are interpreted and
+ * which actions they trigger.
+ *
+ * @param name the name of the key map to activate
+ * @return true if the key map was successfully set, false if the key map does not exist
+ */
boolean setKeyMap(String name);
+ /**
+ * Returns the currently active key map.
+ *
+ * The active key map determines how key presses are interpreted and
+ * which actions they trigger.
+ *
+ * @return the active key map
+ */
KeyMap getKeys();
+ /**
+ * Returns the parsed representation of the current line.
+ *
+ * The parsed line contains the tokenized form of the current input line,
+ * broken down according to the syntax rules of the parser.
+ *
+ * @return the parsed line, or null if the line has not been parsed yet
+ */
ParsedLine getParsedLine();
+ /**
+ * Returns the current search term when in history search mode.
+ *
+ * This is the string that the user is searching for in the command history.
+ *
+ * @return the current search term, or null if not in search mode
+ */
String getSearchTerm();
+ /**
+ * Returns the type of the currently active region selection.
+ *
+ * The region is a selected portion of text in the buffer, similar to
+ * a selection in a text editor.
+ *
+ * @return the type of the active region, or {@link RegionType#NONE} if no region is active
+ */
RegionType getRegionActive();
+ /**
+ * Returns the mark position of the currently active region.
+ *
+ * The mark is one endpoint of the selected region, with the cursor
+ * being the other endpoint.
+ *
+ * @return the position of the mark, or -1 if no region is active
+ */
int getRegionMark();
+ /**
+ * Adds a collection of commands to the input buffer for execution.
+ *
+ * These commands will be executed one by one when the user accepts the current line.
+ * This is useful for implementing features like command scripts or macros.
+ *
+ * @param commands the commands to add to the buffer
+ */
void addCommandsInBuffer(Collection commands);
+ /**
+ * Opens a file in an external editor and adds its contents to the input buffer.
+ *
+ * This method allows the user to edit a file in their preferred text editor
+ * and then have its contents added to the input buffer for execution.
+ *
+ * @param file the file to edit, or null to create a temporary file
+ * @throws Exception if an error occurs while editing the file
+ * @see #editAndAddInBuffer(Path)
+ */
default void editAndAddInBuffer(File file) throws Exception {
editAndAddInBuffer(file != null ? file.toPath() : null);
}
+ /**
+ * Opens a file in an external editor and adds its contents to the input buffer.
+ *
+ * This method allows the user to edit a file in their preferred text editor
+ * and then have its contents added to the input buffer for execution.
+ *
+ * @param file the file to edit, or null to create a temporary file
+ * @throws Exception if an error occurs while editing the file
+ */
void editAndAddInBuffer(Path file) throws Exception;
+ /**
+ * Returns the last key binding that was processed.
+ *
+ * This is the string representation of the last key sequence that
+ * triggered an action.
+ *
+ * @return the last key binding, or null if no binding has been processed
+ */
String getLastBinding();
+ /**
+ * Returns the current tail tip text.
+ *
+ * The tail tip is a hint or suggestion displayed at the end of the current line,
+ * typically showing command syntax or parameter information.
+ *
+ * @return the current tail tip text, or null if no tail tip is set
+ */
String getTailTip();
+ /**
+ * Sets the tail tip text.
+ *
+ * The tail tip is a hint or suggestion displayed at the end of the current line,
+ * typically showing command syntax or parameter information.
+ *
+ * @param tailTip the tail tip text to display, or null to clear the tail tip
+ */
void setTailTip(String tailTip);
+ /**
+ * Sets the type of auto-suggestion to use.
+ *
+ * Auto-suggestions provide inline completion suggestions as the user types,
+ * based on history, completers, or other sources.
+ *
+ * @param type the type of auto-suggestion to use
+ */
void setAutosuggestion(SuggestionType type);
+ /**
+ * Returns the current auto-suggestion type.
+ *
+ * Auto-suggestions provide inline completion suggestions as the user types,
+ * based on history, completers, or other sources.
+ *
+ * @return the current auto-suggestion type
+ */
SuggestionType getAutosuggestion();
/**
- * Clear any internal buffers.
+ * Clears any internal buffers and sensitive data.
+ *
+ * This method is used to ensure that sensitive information, such as passwords
+ * or other confidential data, is removed from memory when it's no longer needed.
+ * It should be called when the LineReader is no longer in use or before reading
+ * sensitive information.
*/
void zeroOut();
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/LineReaderBuilder.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/LineReaderBuilder.java
index 942bba3605d52..d4dd1cc8b9b85 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/LineReaderBuilder.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/LineReaderBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2020, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -20,8 +20,39 @@
import jdk.internal.org.jline.terminal.TerminalBuilder;
import jdk.internal.org.jline.utils.Log;
+/**
+ * A builder for creating and configuring {@link LineReader} instances.
+ *
+ * This builder provides a fluent API for constructing LineReader objects with
+ * various configuration options. It simplifies the process of creating a properly
+ * configured LineReader by providing methods for setting all the necessary components
+ * and options.
+ *
+ * Example usage:
+ *
+ * LineReader reader = LineReaderBuilder.builder()
+ * .terminal(terminal)
+ * .completer(completer)
+ * .parser(parser)
+ * .highlighter(highlighter)
+ * .variable(LineReader.HISTORY_FILE, historyFile)
+ * .option(LineReader.Option.AUTO_LIST, true)
+ * .build();
+ *
+ *
+ * If no terminal is provided, the builder will attempt to create a default terminal
+ * using {@link TerminalBuilder#terminal()}.
+ *
+ * @see LineReader
+ * @see Terminal
+ */
public final class LineReaderBuilder {
+ /**
+ * Creates a new LineReaderBuilder instance.
+ *
+ * @return a new LineReaderBuilder
+ */
public static LineReaderBuilder builder() {
return new LineReaderBuilder();
}
@@ -40,6 +71,14 @@ public static LineReaderBuilder builder() {
private LineReaderBuilder() {}
+ /**
+ * Sets the terminal to be used by the LineReader.
+ *
+ * If not specified, a default terminal will be created when building the LineReader.
+ *
+ * @param terminal the terminal to use
+ * @return this builder
+ */
public LineReaderBuilder terminal(Terminal terminal) {
this.terminal = terminal;
return this;
@@ -72,16 +111,49 @@ public LineReaderBuilder history(History history) {
return this;
}
+ /**
+ * Sets the completer to be used for tab completion.
+ *
+ * The completer provides completion candidates when the user presses the tab key.
+ *
+ * @param completer the completer to use
+ * @return this builder
+ * @see Completer
+ */
public LineReaderBuilder completer(Completer completer) {
this.completer = completer;
return this;
}
+ /**
+ * Sets the highlighter to be used for syntax highlighting.
+ *
+ * The highlighter applies styling to the input text as the user types.
+ *
+ * @param highlighter the highlighter to use
+ * @return this builder
+ * @see Highlighter
+ */
public LineReaderBuilder highlighter(Highlighter highlighter) {
this.highlighter = highlighter;
return this;
}
+ /**
+ * Sets the parser to be used for parsing command lines.
+ *
+ * The parser breaks the input line into tokens according to specific syntax rules.
+ * It is used during tab completion and when accepting a line of input.
+ *
+ * This method will log a warning if the provided parser does not support the
+ * {@link CompletingParsedLine} interface, as this may cause issues with completion
+ * of escaped or quoted words.
+ *
+ * @param parser the parser to use
+ * @return this builder
+ * @see Parser
+ * @see CompletingParsedLine
+ */
public LineReaderBuilder parser(Parser parser) {
if (parser != null) {
try {
@@ -109,6 +181,25 @@ public LineReaderBuilder completionMatcher(CompletionMatcher completionMatcher)
return this;
}
+ /**
+ * Builds and returns a LineReader instance with the configured options.
+ *
+ * This method creates a new LineReader with all the components and options that
+ * have been set on this builder. If no terminal has been provided, a default
+ * terminal will be created.
+ *
+ * The resulting LineReader will have the following components set:
+ *
+ * Terminal - either the provided terminal or a default one
+ * Application name - either the provided name or the terminal name
+ * History - either the provided history or a new DefaultHistory
+ * Completer, Highlighter, Parser, Expander - if provided
+ * Options - any options that were set using option()
+ *
+ *
+ * @return a new LineReader instance
+ * @throws IOError if there is an error creating the default terminal
+ */
public LineReader build() {
Terminal terminal = this.terminal;
if (terminal == null) {
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Macro.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Macro.java
index b00769aff52f5..885d9468ffe38 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Macro.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Macro.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -8,6 +8,28 @@
*/
package jdk.internal.org.jline.reader;
+/**
+ * A macro that executes a sequence of keystrokes when invoked.
+ *
+ * The Macro class is a type of {@link Binding} that represents a sequence of keystrokes
+ * to be executed when a key sequence bound to this macro is pressed. When triggered,
+ * the LineReader will process each keystroke in the macro's sequence as if they were
+ * typed by the user.
+ *
+ * Macros are useful for automating repetitive sequences of editing operations by
+ * binding them to a single key combination. They can include any valid key sequence,
+ * including control characters and escape sequences.
+ *
+ * For example, a macro might be used to:
+ *
+ * Move the cursor to the beginning of the line and insert a specific prefix
+ * Delete a word and replace it with another string
+ * Execute a series of editing commands in sequence
+ *
+ *
+ * @see Binding
+ * @see LineReader#runMacro(String)
+ */
public class Macro implements Binding {
private final String sequence;
@@ -16,6 +38,11 @@ public Macro(String sequence) {
this.sequence = sequence;
}
+ /**
+ * Returns the keystroke sequence that this macro will execute.
+ *
+ * @return the keystroke sequence
+ */
public String getSequence() {
return sequence;
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/MaskingCallback.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/MaskingCallback.java
index 3b624e306f28a..ee720801f2ce3 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/MaskingCallback.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/MaskingCallback.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -9,26 +9,57 @@
package jdk.internal.org.jline.reader;
/**
- * Callback used to mask parts of the line
+ * Callback used to mask parts of the line for sensitive input like passwords.
+ *
+ * The MaskingCallback interface provides methods to transform the input line
+ * both for display purposes and for history storage. This allows applications
+ * to implement custom masking strategies for sensitive information, such as
+ * passwords, API keys, or other confidential data.
+ *
+ * When a MaskingCallback is provided to the LineReader, it will be used to:
+ *
+ * Transform the line before displaying it to the user (e.g., replacing password characters with asterisks)
+ * Transform the line before storing it in the history (e.g., removing sensitive information)
+ *
+ *
+ * A simple implementation is provided in {@link org.jline.reader.impl.SimpleMaskingCallback},
+ * which replaces all characters with a single mask character.
+ *
+ * @see org.jline.reader.impl.SimpleMaskingCallback
+ * @see LineReader#readLine(String, String, MaskingCallback, String)
*/
public interface MaskingCallback {
/**
- * Transforms the line before it is displayed so that
- * some parts can be hidden.
+ * Transforms the line before it is displayed so that sensitive parts can be hidden.
+ *
+ * This method is called by the LineReader whenever the display needs to be updated.
+ * It allows the implementation to replace sensitive information with mask characters
+ * or other visual indicators while preserving the actual input for processing.
+ *
+ * For example, a password masking implementation might replace each character with
+ * an asterisk (*) or hide the input entirely.
*
- * @param line the current line being edited
- * @return the modified line to display
+ * @param line the current line being edited (contains the actual input)
+ * @return the modified line to display (with sensitive parts masked)
*/
String display(String line);
/**
- * Transforms the line before storing in the history.
- * If the return value is empty or null, it will not be saved
- * in the history.
+ * Transforms the line before storing it in the history.
+ *
+ * This method is called by the LineReader when a line is about to be added to
+ * the command history. It allows the implementation to remove or redact sensitive
+ * information before it is persisted.
+ *
+ * If the return value is empty or null, the line will not be saved in the history at all,
+ * which is often appropriate for commands containing passwords or other sensitive data.
+ *
+ * For example, a command like "login --password=secret" might be transformed to
+ * "login --password=****" or simply "login" before being stored in history.
*
- * @param line the line to be added to history
- * @return the modified line
+ * @param line the line to be added to history (contains the actual input)
+ * @return the modified line for history storage, or null/empty to prevent history storage
*/
String history(String line);
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/ParsedLine.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/ParsedLine.java
index 3d87e6b80b2eb..62f7a50f2308f 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/ParsedLine.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/ParsedLine.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -13,12 +13,23 @@
/**
* ParsedLine objects are returned by the {@link Parser}
* during completion or when accepting the line.
- *
+ *
+ * This interface represents a command line that has been tokenized into words
+ * according to the syntax rules of the parser. It provides access to the individual
+ * words, the current word being completed, cursor positions, and the original
+ * unparsed line.
+ *
+ * ParsedLine objects are used extensively during tab completion to determine
+ * what the user is trying to complete and to provide the appropriate context
+ * to {@link Completer} implementations.
+ *
* The instances should implement the {@link CompletingParsedLine}
- * interface so that escape chars and quotes can be correctly handled.
+ * interface so that escape chars and quotes can be correctly handled during
+ * completion.
*
* @see Parser
* @see CompletingParsedLine
+ * @see Completer
*/
public interface ParsedLine {
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Parser.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Parser.java
index 5db53818f67db..87ce83ed4b45f 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Parser.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Parser.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2021, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -11,6 +11,23 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+/**
+ * The Parser interface is responsible for parsing command lines into tokens.
+ *
+ * Parsers analyze input strings and break them into words/tokens according to
+ * specific syntax rules. They handle features such as quoting, escaping special
+ * characters, and comments. The parser is used by the LineReader during tab
+ * completion and when accepting a line of input.
+ *
+ * Implementations should ideally return {@link CompletingParsedLine} objects
+ * to properly support completion with escaped or quoted words.
+ *
+ * The default implementation is {@link org.jline.reader.impl.DefaultParser}.
+ *
+ * @see ParsedLine
+ * @see CompletingParsedLine
+ * @see org.jline.reader.impl.DefaultParser
+ */
public interface Parser {
String REGEX_VARIABLE = "[a-zA-Z_]+[a-zA-Z0-9_-]*";
String REGEX_COMMAND = "[:]?[a-zA-Z]+[a-zA-Z0-9_-]*";
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/PrintAboveWriter.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/PrintAboveWriter.java
index e23988310a593..0547c382e3659 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/PrintAboveWriter.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/PrintAboveWriter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2021, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Reference.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Reference.java
index 2fb1aaa671b0a..ab3335f8dd2f2 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Reference.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Reference.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -9,7 +9,24 @@
package jdk.internal.org.jline.reader;
/**
- * A reference to a {@link Widget}.
+ * A reference to a {@link Widget} by name.
+ *
+ * The Reference class is a type of {@link Binding} that refers to a widget by its name
+ * rather than directly holding the widget implementation. When a key sequence bound to
+ * a Reference is pressed, the LineReader will look up the referenced widget by name
+ * and execute it.
+ *
+ * This indirection allows for more flexible key bindings, as it enables binding keys
+ * to widgets that might be defined or redefined after the key binding is established.
+ * It also allows multiple key sequences to reference the same widget without duplicating
+ * the widget implementation.
+ *
+ * References are particularly useful in configuration files where widgets are referred
+ * to by name rather than by direct object references.
+ *
+ * @see Widget
+ * @see Binding
+ * @see LineReader#callWidget(String)
*/
public class Reference implements Binding {
@@ -19,6 +36,11 @@ public Reference(String name) {
this.name = name;
}
+ /**
+ * Returns the name of the referenced widget.
+ *
+ * @return the widget name
+ */
public String name() {
return name;
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/SyntaxError.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/SyntaxError.java
index eaf3e1d758064..47b52e837f281 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/SyntaxError.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/SyntaxError.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -8,6 +8,24 @@
*/
package jdk.internal.org.jline.reader;
+/**
+ * Exception thrown when a syntax error is encountered during parsing.
+ *
+ * SyntaxError is thrown by the {@link Parser} when it encounters invalid syntax
+ * in the input line. It provides information about the location of the error
+ * (line and column) and a descriptive message about the nature of the error.
+ *
+ * This exception is typically caught by the LineReader, which may then display
+ * an error message to the user or take other appropriate action based on the
+ * parsing context.
+ *
+ * The {@link EOFError} subclass is used specifically for incomplete input errors,
+ * such as unclosed quotes or brackets, which might be completed by additional input.
+ *
+ * @see Parser
+ * @see EOFError
+ * @see Parser.ParseContext
+ */
public class SyntaxError extends RuntimeException {
private static final long serialVersionUID = 1L;
@@ -21,10 +39,20 @@ public SyntaxError(int line, int column, String message) {
this.column = column;
}
+ /**
+ * Returns the column position where the syntax error occurred.
+ *
+ * @return the column position (0-based index)
+ */
public int column() {
return column;
}
+ /**
+ * Returns the line number where the syntax error occurred.
+ *
+ * @return the line number (0-based index)
+ */
public int line() {
return line;
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/UserInterruptException.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/UserInterruptException.java
index e5bc5b3180c64..791c00210920d 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/UserInterruptException.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/UserInterruptException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Widget.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Widget.java
index ae831c5eaadc6..20edccf65e8b7 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Widget.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Widget.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -9,10 +9,38 @@
package jdk.internal.org.jline.reader;
/**
+ * A Widget represents an action that can be bound to a key sequence in the LineReader.
+ *
+ * Widgets are the fundamental building blocks of the line editor's functionality.
+ * Each widget implements a specific editing action or command that can be invoked
+ * by the user through key bindings. Examples include moving the cursor, deleting
+ * characters, searching history, and completing words.
+ *
+ * Widgets can be bound to key sequences using the LineReader's key maps. When the
+ * user presses a key sequence that is bound to a widget, the widget's {@link #apply()}
+ * method is called to perform the associated action.
+ *
+ * JLine provides a set of built-in widgets that implement common editing functions,
+ * and applications can define custom widgets to extend the editor's functionality.
+ *
+ * This interface is designed as a functional interface, making it easy to implement
+ * widgets using lambda expressions.
*
+ * @see LineReader#getWidgets()
+ * @see LineReader#getBuiltinWidgets()
+ * @see LineReader#callWidget(String)
+ * @see Binding
*/
@FunctionalInterface
public interface Widget extends Binding {
+ /**
+ * Executes the action associated with this widget.
+ *
+ * This method is called when the key sequence bound to this widget is pressed.
+ * It should perform the widget's specific editing action or command.
+ *
+ * @return true if the widget was successfully applied, false otherwise
+ */
boolean apply();
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/BufferImpl.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/BufferImpl.java
index a6295dbb487f4..e1fd44d8336e0 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/BufferImpl.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/BufferImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2017, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -14,11 +14,27 @@
import jdk.internal.org.jline.reader.Buffer;
/**
- * A holder for a {@link StringBuilder} that also contains the current cursor position.
+ * Default implementation of the {@link Buffer} interface.
+ *
+ * This class provides a mutable buffer for storing and manipulating the text being
+ * edited in the LineReader. It maintains the text content and the current cursor
+ * position, and provides methods for text insertion, deletion, and cursor movement.
+ *
+ * Key features include:
+ *
+ * Efficient text insertion and deletion with a gap buffer implementation
+ * Support for Unicode characters beyond the Basic Multilingual Plane
+ * Cursor movement in both character and line coordinates
+ * Copy and paste operations
+ * Secure clearing of buffer contents
+ *
+ *
+ * The buffer uses a gap buffer data structure for efficient editing operations,
+ * which provides good performance for the typical editing patterns in a line editor.
*
- * @author Marc Prud'hommeaux
- * @author Jason Dillon
* @since 2.0
+ * @see Buffer
+ * @see org.jline.reader.LineReader#getBuffer()
*/
public class BufferImpl implements Buffer {
private int cursor = 0;
@@ -27,10 +43,18 @@ public class BufferImpl implements Buffer {
private int g0;
private int g1;
+ /**
+ * Creates a new buffer with the default size (64).
+ */
public BufferImpl() {
this(64);
}
+ /**
+ * Creates a new buffer with the specified size.
+ *
+ * @param size the initial size of the buffer
+ */
public BufferImpl(int size) {
buffer = new int[size];
g0 = 0;
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/CompletionMatcherImpl.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/CompletionMatcherImpl.java
index 761ee1815c3d0..a7fcc519ecd58 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/CompletionMatcherImpl.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/CompletionMatcherImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2021, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -20,6 +20,30 @@
import jdk.internal.org.jline.reader.LineReader;
import jdk.internal.org.jline.utils.AttributedString;
+/**
+ * Default implementation of the {@link CompletionMatcher} interface.
+ *
+ * This matcher provides sophisticated algorithms for matching completion candidates
+ * against user input, with support for:
+ *
+ * Prefix matching - candidates that start with the input text
+ * Substring matching - candidates that contain the input text
+ * Camel case matching - matching based on camel case patterns
+ * Fuzzy matching - candidates that approximately match with allowed typos
+ *
+ *
+ * The matcher uses a chain of matching strategies, trying each one in sequence until
+ * matches are found. This allows for a graceful fallback from exact matches to more
+ * approximate matches.
+ *
+ * The behavior of the matcher can be controlled through LineReader options such as
+ * {@link LineReader.Option#COMPLETE_MATCHER_TYPO} and
+ * {@link LineReader.Option#COMPLETE_MATCHER_CAMELCASE}.
+ *
+ * @see CompletionMatcher
+ * @see LineReader.Option#COMPLETE_MATCHER_TYPO
+ * @see LineReader.Option#COMPLETE_MATCHER_CAMELCASE
+ */
public class CompletionMatcherImpl implements CompletionMatcher {
protected Predicate exact;
protected List>, Map>>> matchers;
@@ -104,8 +128,8 @@ protected void defaultMatchers(
// TODO: glob completion
String wd = line.word();
String wdi = caseInsensitive ? wd.toLowerCase() : wd;
- String wp = wdi.substring(0, line.wordCursor());
if (prefix) {
+ String wp = wdi.substring(0, Math.min(line.wordCursor(), wdi.length()));
matchers = new ArrayList<>(Arrays.asList(
simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).startsWith(wp)),
simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).contains(wp))));
@@ -118,7 +142,8 @@ protected void defaultMatchers(
exact = s -> caseInsensitive ? s.equalsIgnoreCase(wd) : s.equals(wd);
} else {
if (LineReader.Option.COMPLETE_IN_WORD.isSet(options)) {
- String ws = wdi.substring(line.wordCursor());
+ String wp = wdi.substring(0, Math.min(line.wordCursor(), wdi.length()));
+ String ws = wdi.substring(Math.min(line.wordCursor(), wdi.length()));
Pattern p1 = Pattern.compile(Pattern.quote(wp) + ".*" + Pattern.quote(ws) + ".*");
Pattern p2 = Pattern.compile(".*" + Pattern.quote(wp) + ".*" + Pattern.quote(ws) + ".*");
matchers = new ArrayList<>(Arrays.asList(
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/DefaultExpander.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/DefaultExpander.java
index e5e7735a38479..f01b7a06240c5 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/DefaultExpander.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/DefaultExpander.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -14,8 +14,34 @@
import jdk.internal.org.jline.reader.History;
import jdk.internal.org.jline.reader.History.Entry;
+/**
+ * Default implementation of the {@link Expander} interface.
+ *
+ * This expander provides functionality for expanding special syntax in command lines,
+ * including:
+ *
+ * History expansions (e.g., !!, !$, !n, etc.)
+ * Variable expansions (e.g., $HOME, ${PATH})
+ *
+ *
+ * The history expansion syntax is similar to that used in Bash and other shells,
+ * allowing users to reference and reuse previous commands or parts of commands.
+ *
+ * The expander is used by the LineReader to process the command line after the user
+ * has accepted it but before it is executed or added to the history.
+ *
+ * @see Expander
+ * @see org.jline.reader.LineReader
+ */
public class DefaultExpander implements Expander {
+ /**
+ * Creates a new DefaultExpander.
+ */
+ public DefaultExpander() {
+ // Default constructor
+ }
+
/**
* Expand event designator such as !!, !#, !3, etc...
* See http://www.gnu.org/software/bash/manual/html_node/Event-Designators.html
@@ -106,7 +132,7 @@ public String expandHistory(History history, String line) {
case '-':
neg = true;
i++;
- // fall through
+ // fall through
case '0':
case '1':
case '2':
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/DefaultHighlighter.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/DefaultHighlighter.java
index 87bf06d007343..ed8fa4c57a1a0 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/DefaultHighlighter.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/DefaultHighlighter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2021, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -18,10 +18,38 @@
import jdk.internal.org.jline.utils.AttributedStyle;
import jdk.internal.org.jline.utils.WCWidth;
+/**
+ * Default implementation of the {@link Highlighter} interface.
+ *
+ * This highlighter provides basic syntax highlighting capabilities for the LineReader,
+ * including:
+ *
+ * Highlighting of search matches
+ * Highlighting of errors based on patterns or indices
+ * Highlighting of selected regions
+ *
+ *
+ * The highlighting is applied using {@link AttributedStyle} to change the appearance
+ * of text in the terminal, such as colors, bold, underline, etc.
+ *
+ * Applications can customize the highlighting behavior by extending this class
+ * and overriding the {@link #highlight(LineReader, String)} method.
+ *
+ * @see Highlighter
+ * @see AttributedStyle
+ * @see org.jline.reader.LineReader
+ */
public class DefaultHighlighter implements Highlighter {
protected Pattern errorPattern;
protected int errorIndex = -1;
+ /**
+ * Creates a new DefaultHighlighter.
+ */
+ public DefaultHighlighter() {
+ // Default constructor
+ }
+
@Override
public void setErrorPattern(Pattern errorPattern) {
this.errorPattern = errorPattern;
@@ -63,37 +91,49 @@ public AttributedString highlight(LineReader reader, String buffer) {
negativeEnd++;
}
}
+ // Convert code point indices to char indices
+ negativeStart =
+ buffer.offsetByCodePoints(0, Math.min(negativeStart, buffer.codePointCount(0, buffer.length())));
+ negativeEnd =
+ buffer.offsetByCodePoints(0, Math.min(negativeEnd, buffer.codePointCount(0, buffer.length())));
+ }
+
+ // Convert errorIndex from code point index to char index
+ int charErrorIndex = -1;
+ if (errorIndex >= 0 && errorIndex < buffer.codePointCount(0, buffer.length())) {
+ charErrorIndex = buffer.offsetByCodePoints(0, errorIndex);
}
AttributedStringBuilder sb = new AttributedStringBuilder();
commandStyle(reader, sb, true);
- for (int i = 0; i < buffer.length(); i++) {
+ for (int i = 0; i < buffer.length(); ) {
if (i == underlineStart) {
sb.style(AttributedStyle::underline);
}
if (i == negativeStart) {
sb.style(AttributedStyle::inverse);
}
- if (i == errorIndex) {
+ if (i == charErrorIndex) {
sb.style(AttributedStyle::inverse);
}
- char c = buffer.charAt(i);
- if (first && Character.isSpaceChar(c)) {
+ int cp = buffer.codePointAt(i);
+ int charCount = Character.charCount(cp);
+ if (first && Character.isSpaceChar(cp)) {
first = false;
commandStyle(reader, sb, false);
}
- if (c == '\t' || c == '\n') {
- sb.append(c);
- } else if (c < 32) {
+ if (cp == '\t' || cp == '\n') {
+ sb.append((char) cp);
+ } else if (cp < 32) {
sb.style(AttributedStyle::inverseNeg)
.append('^')
- .append((char) (c + '@'))
+ .append((char) (cp + '@'))
.style(AttributedStyle::inverseNeg);
} else {
- int w = WCWidth.wcwidth(c);
- if (w > 0) {
- sb.append(c);
+ int w = WCWidth.wcwidth(cp);
+ if (w >= 0) {
+ sb.append(buffer, i, i + charCount);
}
}
if (i == underlineEnd) {
@@ -102,9 +142,10 @@ public AttributedString highlight(LineReader reader, String buffer) {
if (i == negativeEnd) {
sb.style(AttributedStyle::inverseOff);
}
- if (i == errorIndex) {
+ if (i == charErrorIndex) {
sb.style(AttributedStyle::inverseOff);
}
+ i += charCount;
}
if (errorPattern != null) {
sb.styleMatches(errorPattern, AttributedStyle.INVERSE);
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/DefaultParser.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/DefaultParser.java
index 26cdf6abc51af..a8031530b048e 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/DefaultParser.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/DefaultParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2020, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -18,8 +18,34 @@
import jdk.internal.org.jline.reader.ParsedLine;
import jdk.internal.org.jline.reader.Parser;
+/**
+ * Default implementation of the {@link Parser} interface.
+ *
+ * This parser provides a flexible implementation for parsing command lines into tokens,
+ * with support for:
+ *
+ * Quoted strings with customizable quote characters
+ * Escaped characters with customizable escape characters
+ * Bracket matching with customizable bracket pairs
+ * Line and block comments with customizable delimiters
+ * Command and variable name validation with customizable regex patterns
+ *
+ *
+ * The parser is highly configurable through its chainable setter methods, allowing
+ * applications to customize its behavior to match their specific syntax requirements.
+ *
+ * The parser also implements the {@link CompletingParsedLine} interface, which provides
+ * additional methods for handling completion with proper escaping of special characters.
+ *
+ * @see Parser
+ * @see CompletingParsedLine
+ * @see org.jline.reader.LineReader
+ */
public class DefaultParser implements Parser {
+ /**
+ * Enumeration of bracket types that can be used for EOF detection on unclosed brackets.
+ */
public enum Bracket {
ROUND, // ()
CURLY, // {}
@@ -27,6 +53,9 @@ public enum Bracket {
ANGLE // <>
}
+ /**
+ * Class representing block comment delimiters.
+ */
public static class BlockCommentDelims {
private final String start;
private final String end;
@@ -68,55 +97,122 @@ public String getEnd() {
private String regexCommand = "[:]?[a-zA-Z]+[a-zA-Z0-9_-]*";
private int commandGroup = 4;
+ /**
+ * Creates a new DefaultParser.
+ */
+ public DefaultParser() {
+ // Default constructor
+ }
+
//
// Chainable setters
//
+ /**
+ * Sets the line comment delimiters.
+ *
+ * @param lineCommentDelims the line comment delimiters
+ * @return this parser instance
+ */
public DefaultParser lineCommentDelims(final String[] lineCommentDelims) {
this.lineCommentDelims = lineCommentDelims;
return this;
}
+ /**
+ * Sets the block comment delimiters.
+ *
+ * @param blockCommentDelims the block comment delimiters
+ * @return this parser instance
+ */
public DefaultParser blockCommentDelims(final BlockCommentDelims blockCommentDelims) {
this.blockCommentDelims = blockCommentDelims;
return this;
}
+ /**
+ * Sets the quote characters.
+ *
+ * @param chars the quote characters
+ * @return this parser instance
+ */
public DefaultParser quoteChars(final char[] chars) {
this.quoteChars = chars;
return this;
}
+ /**
+ * Sets the escape characters.
+ *
+ * @param chars the escape characters
+ * @return this parser instance
+ */
public DefaultParser escapeChars(final char[] chars) {
this.escapeChars = chars;
return this;
}
+ /**
+ * Sets whether EOF should be returned on unclosed quotes.
+ *
+ * @param eofOnUnclosedQuote true if EOF should be returned on unclosed quotes
+ * @return this parser instance
+ */
public DefaultParser eofOnUnclosedQuote(boolean eofOnUnclosedQuote) {
this.eofOnUnclosedQuote = eofOnUnclosedQuote;
return this;
}
+ /**
+ * Sets the bracket types that should trigger EOF on unclosed brackets.
+ *
+ * @param brackets the bracket types
+ * @return this parser instance
+ */
public DefaultParser eofOnUnclosedBracket(Bracket... brackets) {
setEofOnUnclosedBracket(brackets);
return this;
}
+ /**
+ * Sets whether EOF should be returned on escaped newlines.
+ *
+ * @param eofOnEscapedNewLine true if EOF should be returned on escaped newlines
+ * @return this parser instance
+ */
public DefaultParser eofOnEscapedNewLine(boolean eofOnEscapedNewLine) {
this.eofOnEscapedNewLine = eofOnEscapedNewLine;
return this;
}
+ /**
+ * Sets the regular expression for identifying variables.
+ *
+ * @param regexVariable the regular expression for variables
+ * @return this parser instance
+ */
public DefaultParser regexVariable(String regexVariable) {
this.regexVariable = regexVariable;
return this;
}
+ /**
+ * Sets the regular expression for identifying commands.
+ *
+ * @param regexCommand the regular expression for commands
+ * @return this parser instance
+ */
public DefaultParser regexCommand(String regexCommand) {
this.regexCommand = regexCommand;
return this;
}
+ /**
+ * Sets the command group for the regular expression.
+ *
+ * @param commandGroup the command group
+ * @return this parser instance
+ */
public DefaultParser commandGroup(int commandGroup) {
this.commandGroup = commandGroup;
return this;
@@ -126,54 +222,119 @@ public DefaultParser commandGroup(int commandGroup) {
// Java bean getters and setters
//
+ /**
+ * Sets the quote characters.
+ *
+ * @param chars the quote characters
+ */
public void setQuoteChars(final char[] chars) {
this.quoteChars = chars;
}
+ /**
+ * Gets the quote characters.
+ *
+ * @return the quote characters
+ */
public char[] getQuoteChars() {
return this.quoteChars;
}
+ /**
+ * Sets the escape characters.
+ *
+ * @param chars the escape characters
+ */
public void setEscapeChars(final char[] chars) {
this.escapeChars = chars;
}
+ /**
+ * Gets the escape characters.
+ *
+ * @return the escape characters
+ */
public char[] getEscapeChars() {
return this.escapeChars;
}
+ /**
+ * Sets the line comment delimiters.
+ *
+ * @param lineCommentDelims the line comment delimiters
+ */
public void setLineCommentDelims(String[] lineCommentDelims) {
this.lineCommentDelims = lineCommentDelims;
}
+ /**
+ * Gets the line comment delimiters.
+ *
+ * @return the line comment delimiters
+ */
public String[] getLineCommentDelims() {
return this.lineCommentDelims;
}
+ /**
+ * Sets the block comment delimiters.
+ *
+ * @param blockCommentDelims the block comment delimiters
+ */
public void setBlockCommentDelims(BlockCommentDelims blockCommentDelims) {
this.blockCommentDelims = blockCommentDelims;
}
+ /**
+ * Gets the block comment delimiters.
+ *
+ * @return the block comment delimiters
+ */
public BlockCommentDelims getBlockCommentDelims() {
return blockCommentDelims;
}
+ /**
+ * Sets whether EOF should be returned on unclosed quotes.
+ *
+ * @param eofOnUnclosedQuote true if EOF should be returned on unclosed quotes
+ */
public void setEofOnUnclosedQuote(boolean eofOnUnclosedQuote) {
this.eofOnUnclosedQuote = eofOnUnclosedQuote;
}
+ /**
+ * Checks if EOF should be returned on unclosed quotes.
+ *
+ * @return true if EOF should be returned on unclosed quotes
+ */
public boolean isEofOnUnclosedQuote() {
return eofOnUnclosedQuote;
}
+ /**
+ * Sets whether EOF should be returned on escaped newlines.
+ *
+ * @param eofOnEscapedNewLine true if EOF should be returned on escaped newlines
+ */
public void setEofOnEscapedNewLine(boolean eofOnEscapedNewLine) {
this.eofOnEscapedNewLine = eofOnEscapedNewLine;
}
+ /**
+ * Checks if EOF should be returned on escaped newlines.
+ *
+ * @return true if EOF should be returned on escaped newlines
+ */
public boolean isEofOnEscapedNewLine() {
return eofOnEscapedNewLine;
}
+ /**
+ * Sets the bracket types that should trigger EOF on unclosed brackets.
+ *
+ * @param brackets the bracket types
+ */
public void setEofOnUnclosedBracket(Bracket... brackets) {
if (brackets == null) {
openingBrackets = null;
@@ -207,14 +368,29 @@ public void setEofOnUnclosedBracket(Bracket... brackets) {
}
}
+ /**
+ * Sets the regular expression for identifying variables.
+ *
+ * @param regexVariable the regular expression for variables
+ */
public void setRegexVariable(String regexVariable) {
this.regexVariable = regexVariable;
}
+ /**
+ * Sets the regular expression for identifying commands.
+ *
+ * @param regexCommand the regular expression for commands
+ */
public void setRegexCommand(String regexCommand) {
this.regexCommand = regexCommand;
}
+ /**
+ * Sets the command group for the regular expression.
+ *
+ * @param commandGroup the command group
+ */
public void setCommandGroup(int commandGroup) {
this.commandGroup = commandGroup;
}
@@ -275,6 +451,7 @@ public ParsedLine parse(final String line, final int cursor, ParseContext contex
int rawWordStart = 0;
BracketChecker bracketChecker = new BracketChecker(cursor);
boolean quotedWord = false;
+ boolean wordStarted = false;
boolean lineCommented = false;
boolean blockCommented = false;
boolean blockCommentInRightOrder = true;
@@ -295,6 +472,7 @@ public ParsedLine parse(final String line, final int cursor, ParseContext contex
if (quoteStart < 0 && isQuoteChar(line, i) && !lineCommented && !blockCommented) {
// Start a quote block
quoteStart = i;
+ wordStarted = true;
if (current.length() == 0) {
quotedWord = true;
if (context == ParseContext.SPLIT_LINE) {
@@ -323,9 +501,17 @@ public ParsedLine parse(final String line, final int cursor, ParseContext contex
}
} else {
// Delimiter
- rawWordLength = handleDelimiterAndGetRawWordLength(
- current, words, rawWordStart, rawWordCursor, rawWordLength, i);
+ if (wordStarted && current.length() == 0) {
+ words.add(current.toString());
+ if (rawWordCursor >= 0 && rawWordLength < 0) {
+ rawWordLength = i - rawWordStart;
+ }
+ } else {
+ rawWordLength = handleDelimiterAndGetRawWordLength(
+ current, words, rawWordStart, rawWordCursor, rawWordLength, i);
+ }
rawWordStart = i + 1;
+ wordStarted = false;
}
} else {
if (quoteStart < 0 && !blockCommented && (lineCommented || isLineCommentStarted(line, i))) {
@@ -344,6 +530,7 @@ public ParsedLine parse(final String line, final int cursor, ParseContext contex
current, words, rawWordStart, rawWordCursor, rawWordLength, i);
i += blockCommentStart == null ? 0 : blockCommentStart.length() - 1;
rawWordStart = i + 1;
+ wordStarted = false;
}
} else if (quoteStart < 0 && !lineCommented && isCommentDelim(line, i, blockCommentEnd)) {
current.append(line.charAt(i));
@@ -359,7 +546,7 @@ public ParsedLine parse(final String line, final int cursor, ParseContext contex
}
}
- if (current.length() > 0 || cursor == line.length()) {
+ if (current.length() > 0 || wordStarted || (cursor == line.length() && context == ParseContext.COMPLETE)) {
words.add(current.toString());
if (rawWordCursor >= 0 && rawWordLength < 0) {
rawWordLength = line.length() - rawWordStart;
@@ -367,6 +554,9 @@ public ParsedLine parse(final String line, final int cursor, ParseContext contex
}
if (cursor == line.length()) {
+ if (words.isEmpty()) {
+ words.add("");
+ }
wordIndex = words.size() - 1;
wordCursor = words.get(words.size() - 1).length();
rawWordCursor = cursor - rawWordStart;
@@ -655,8 +845,6 @@ private int bracketId(final char[] brackets, final CharSequence buffer, final in
/**
* The result of a delimited buffer.
- *
- * @author Marc Prud'hommeaux
*/
public class ArgumentList implements ParsedLine, CompletingParsedLine {
private final String line;
@@ -675,24 +863,6 @@ public class ArgumentList implements ParsedLine, CompletingParsedLine {
private final int rawWordLength;
- @Deprecated
- public ArgumentList(
- final String line,
- final List words,
- final int wordIndex,
- final int wordCursor,
- final int cursor) {
- this(
- line,
- words,
- wordIndex,
- wordCursor,
- cursor,
- null,
- wordCursor,
- words.get(wordIndex).length());
- }
-
/**
*
* @param line the command line being edited
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/InputRC.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/InputRC.java
index 0c3aee057a46d..f01224fb22543 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/InputRC.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/InputRC.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2023, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -24,20 +24,72 @@
import jdk.internal.org.jline.terminal.Terminal;
import jdk.internal.org.jline.utils.Log;
+/**
+ * Handles inputrc configuration files for JLine.
+ *
+ * This class provides functionality for parsing and applying inputrc configuration
+ * files, which are used to customize key bindings and other behavior of the
+ * LineReader. The format is compatible with GNU Readline's inputrc files.
+ *
+ * Key features include:
+ *
+ * Binding keys to functions or macros
+ * Setting variables that control LineReader behavior
+ * Conditional configuration based on terminal type
+ *
+ *
+ * The configuration is typically loaded from a file specified by the
+ * {@link org.jline.reader.LineReader#INPUT_RC_FILE_NAME} variable, but can also
+ * be loaded from any input stream.
+ *
+ * @see org.jline.reader.LineReader#INPUT_RC_FILE_NAME
+ * @see org.jline.reader.LineReader#getKeyMaps()
+ */
public final class InputRC {
+ /**
+ * Configures a LineReader from an inputrc file at the specified URL.
+ *
+ * This method opens the URL and passes the resulting input stream to
+ * {@link #configure(LineReader, InputStream)}.
+ *
+ * @param reader the LineReader to configure
+ * @param url the URL of the inputrc file
+ * @throws IOException if an I/O error occurs while reading the configuration
+ */
public static void configure(LineReader reader, URL url) throws IOException {
try (InputStream is = url.openStream()) {
configure(reader, is);
}
}
+ /**
+ * Configures a LineReader from an inputrc file.
+ *
+ * This method reads configuration commands from the provided input stream
+ * and passes them to {@link #configure(LineReader, Reader)}.
+ *
+ * @param reader the LineReader to configure
+ * @param is the input stream containing the configuration
+ * @throws IOException if an I/O error occurs while reading the configuration
+ */
public static void configure(LineReader reader, InputStream is) throws IOException {
try (InputStreamReader r = new InputStreamReader(is)) {
configure(reader, r);
}
}
+ /**
+ * Configures a LineReader from an inputrc file.
+ *
+ * This method reads configuration commands from the provided reader
+ * and applies them to the LineReader. The format of the input is compatible
+ * with GNU Readline's inputrc files.
+ *
+ * @param reader the LineReader to configure
+ * @param r the reader containing the configuration
+ * @throws IOException if an I/O error occurs while reading the configuration
+ */
public static void configure(LineReader reader, Reader r) throws IOException {
BufferedReader br;
if (r instanceof BufferedReader) {
@@ -372,6 +424,16 @@ private static char getKeyFromName(String name) {
}
}
+ /**
+ * Sets a variable in the LineReader.
+ *
+ * This method handles special cases for certain variables, such as the keymap,
+ * and options that can be turned on or off.
+ *
+ * @param reader the LineReader to modify
+ * @param key the name of the variable to set
+ * @param val the value to set
+ */
static void setVar(LineReader reader, String key, String val) {
if (LineReader.KEYMAP.equalsIgnoreCase(key)) {
reader.setKeyMap(val);
@@ -379,7 +441,7 @@ static void setVar(LineReader reader, String key, String val) {
}
for (LineReader.Option option : LineReader.Option.values()) {
- if (option.name().toLowerCase(Locale.ENGLISH).replace('_', '-').equals(val)) {
+ if (option.name().toLowerCase(Locale.ENGLISH).replace('_', '-').equals(key)) {
if ("on".equalsIgnoreCase(val)) {
reader.setOpt(option);
} else if ("off".equalsIgnoreCase(val)) {
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/KillRing.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/KillRing.java
index 2510fd9e32035..44e1ff4cadc07 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/KillRing.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/KillRing.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -132,7 +132,10 @@ public String yankPop() {
lastKill = false;
if (lastYank) {
prev();
- return slots[head];
+ if (head >= 0) {
+ return slots[head];
+ }
+ head = 0; // restore valid state
}
return null;
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/LineReaderImpl.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/LineReaderImpl.java
index 302254ab61fa8..62f4abead1374 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/LineReaderImpl.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/LineReaderImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2023, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -41,6 +41,7 @@
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.terminal.impl.MouseSupport;
import jdk.internal.org.jline.utils.AttributedString;
import jdk.internal.org.jline.utils.AttributedStringBuilder;
import jdk.internal.org.jline.utils.AttributedStyle;
@@ -61,22 +62,37 @@
import static jdk.internal.org.jline.terminal.TerminalBuilder.PROP_DISABLE_ALTERNATE_CHARSET;
/**
- * A reader for terminal applications. It supports custom tab-completion,
- * saveable command history, and command line editing.
+ * The default implementation of the {@link LineReader} interface.
+ *
+ * This class provides a comprehensive implementation of line editing capabilities
+ * for interactive terminal applications, including:
+ *
+ * Command history navigation and management
+ * Tab completion with customizable completion strategies
+ * Syntax highlighting
+ * Emacs and Vi editing modes
+ * Key binding customization
+ * Cut and paste with kill ring
+ * Undo/redo functionality
+ * Search through history
+ * Multi-line editing
+ * Character masking for password input
+ *
+ *
+ * This implementation is highly configurable through options and variables that
+ * control various aspects of its behavior. It also provides a rich set of widgets
+ * (editing functions) that can be bound to key sequences.
+ *
+ * Most applications should not create instances of this class directly, but instead
+ * use the {@link LineReaderBuilder} to create properly configured instances.
*
- * @author Marc Prud'hommeaux
- * @author Jason Dillon
- * @author Guillaume Nodet
+ * @see LineReader
+ * @see LineReaderBuilder
*/
@SuppressWarnings("StatementWithEmptyBody")
public class LineReaderImpl implements LineReader, Flushable {
public static final char NULL_MASK = 0;
-
- /**
- * @deprecated use {@link #DEFAULT_TAB_WIDTH} and {@link #getTabWidth()}
- */
- @Deprecated
- public static final int TAB_WIDTH = 4;
+ private static final boolean DISABLE_MASKING_THREAD = Boolean.getBoolean("test.disabled.masking.thread");
public static final int DEFAULT_TAB_WIDTH = 4;
@@ -185,6 +201,7 @@ protected enum BellType {
//
protected final Map options = new HashMap<>();
+ protected Thread maskThread = null;
protected final Buffer buf = new BufferImpl();
protected String tailTip = "";
@@ -204,6 +221,7 @@ protected enum BellType {
protected boolean searchFailing;
protected boolean searchBackward;
protected int searchIndex = -1;
+ protected Deque searchIndexStack = null; // Stack to track search depth
protected boolean doAutosuggestion;
// Reading buffers
@@ -242,7 +260,9 @@ protected enum BellType {
protected KillRing killRing = new KillRing();
+ @SuppressWarnings("java:S1845") // field name intentionally similar to inherited UNDO constant
protected UndoTree undo;
+
protected boolean isUndo;
/**
@@ -271,9 +291,11 @@ protected enum BellType {
protected boolean skipRedisplay;
protected Display display;
+ protected int lastStatusSize;
protected boolean overTyping = false;
+ @SuppressWarnings("java:S1845") // field name intentionally similar to inherited KEYMAP constant
protected String keyMap;
protected int smallTerminalOffset = 0;
@@ -292,6 +314,7 @@ protected enum BellType {
protected String alternateIn;
protected String alternateOut;
+ protected int currentLine;
public LineReaderImpl(Terminal terminal) throws IOException {
this(terminal, terminal.getName(), null);
@@ -409,7 +432,7 @@ public void runMacro(String macro) {
@Override
public MouseEvent readMouseEvent() {
- return terminal.readMouseEvent(bindingReader::readCharacter);
+ return terminal.readMouseEvent(bindingReader::readCharacter, bindingReader.getLastBinding());
}
/**
@@ -666,7 +689,16 @@ public String readLine(String prompt, String rightPrompt, MaskingCallback maskin
terminal.puts(Capability.keypad_xmit);
if (isSet(Option.AUTO_FRESH_LINE)) callWidget(FRESH_LINE);
if (isSet(Option.MOUSE)) terminal.trackMouse(Terminal.MouseTracking.Normal);
- if (isSet(Option.BRACKETED_PASTE)) terminal.writer().write(BRACKETED_PASTE_ON);
+
+ if (isSet(Option.BRACKETED_PASTE)) {
+ terminal.writer().write(BRACKETED_PASTE_ON);
+ } else if (options.containsKey(Option.BRACKETED_PASTE)) {
+ // Explicitly disabled: ensure terminal bracketed paste is off
+ terminal.writer().write(BRACKETED_PASTE_OFF);
+ }
+ } else if (isTerminalDumb() && maskingCallback != null) {
+ // Setup masking thread for dumb terminals when reading a password
+ setupMaskThread(this.prompt.toAnsi());
}
callWidget(CALLBACK_INIT);
@@ -810,6 +842,46 @@ private void doDisplay() {
if (isSet(Option.DELAY_LINE_WRAP)) display.setDelayLineWrap(true);
}
+ /**
+ * Setup the masking thread for dumb terminals.
+ * This is similar to the approach used in JLine 1.
+ *
+ * For dumb terminals, we can't process characters one by one, so we use a thread
+ * that continuously overwrites the input line to hide what the user is typing.
+ */
+ private void setupMaskThread(final String prompt) {
+ if (isTerminalDumb() && maskThread == null && !DISABLE_MASKING_THREAD) {
+ // Create a prompt that will overwrite the current line and redisplay the prompt
+ final String fullPrompt = "\r" + prompt + " \r" + prompt;
+ maskThread = new Thread("JLine Mask Thread") {
+ public void run() {
+ while (!Thread.interrupted()) {
+ try {
+ terminal.writer().write(fullPrompt);
+ terminal.writer().flush();
+ sleep(3);
+ } catch (InterruptedException ie) {
+ return;
+ }
+ }
+ }
+ };
+ maskThread.setPriority(Thread.MAX_PRIORITY);
+ maskThread.setDaemon(true);
+ maskThread.start();
+ }
+ }
+
+ /**
+ * Stops the masking thread for dumb terminals.
+ */
+ private void stopMaskThread() {
+ if (maskThread != null && maskThread.isAlive()) {
+ maskThread.interrupt();
+ }
+ maskThread = null;
+ }
+
@Override
public void printAbove(String str) {
try {
@@ -1201,17 +1273,24 @@ 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();
+ // Check if the character grid size actually changed.
+ // Pixel-level resizing can trigger SIGWINCH without changing
+ // the number of rows/columns.
+ Size newSize = terminal.getBufferSize();
+ if (newSize.getRows() != size.getRows() || newSize.getColumns() != size.getColumns()) {
+ // Recreate the display to reset cursor tracking. After a resize,
+ // the terminal has reflowed content and the old cursor position
+ // is no longer valid.
+ doDisplay();
+ Status status = Status.getStatus(terminal, false);
+ if (status != null) {
+ status.resize(size);
+ }
+ // Position cursor at column 0 to match the new display's
+ // cursor assumption (cursorPos = 0).
+ terminal.puts(Capability.carriage_return);
+ redisplay();
}
- terminal.puts(Capability.carriage_return);
- terminal.puts(Capability.clr_eos);
- redrawLine();
- redisplay();
} else if (signal == Signal.CONT) {
terminal.enterRawMode();
size.copy(terminal.getBufferSize());
@@ -1362,7 +1441,7 @@ protected boolean viForwardChar() {
return false;
}
while (count-- > 0 && buf.cursor() < lim) {
- buf.move(1);
+ moveForwardOne();
}
return true;
}
@@ -1376,7 +1455,7 @@ protected boolean viBackwardChar() {
return false;
}
while (count-- > 0 && buf.cursor() > 0) {
- buf.move(-1);
+ moveBackwardOne();
if (buf.currChar() == '\n') {
buf.move(1);
break;
@@ -2357,15 +2436,35 @@ protected boolean transposeChars() {
while (buf.cursor() >= lend) {
buf.move(-1);
}
- int c = buf.currChar();
- buf.currChar(buf.prevChar());
- buf.move(-1);
- buf.currChar(c);
- buf.move(neg ? 0 : 2);
+ if (terminal.getGraphemeClusterMode()) {
+ // Swap two grapheme clusters: the one before and the one at cursor
+ int curPos = buf.cursor();
+ int prevLen = graphemeClusterCodePointCountBefore(buf, curPos);
+ int prevStart = curPos - prevLen;
+ int curLen = graphemeClusterCodePointCount(buf, curPos);
+ // Extract both clusters
+ String prev = buf.substring(prevStart, curPos);
+ String curr = buf.substring(curPos, curPos + curLen);
+ // Delete both and reinsert swapped
+ buf.cursor(prevStart);
+ buf.delete(prevLen + curLen);
+ buf.write(curr);
+ buf.write(prev);
+ if (neg) {
+ buf.cursor(prevStart);
+ }
+ } else {
+ int c = buf.currChar();
+ buf.currChar(buf.prevChar());
+ buf.move(-1);
+ buf.currChar(c);
+ buf.move(neg ? 0 : 2);
+ }
}
return true;
}
+ @SuppressWarnings("java:S1845") // widget method intentionally matches its constant name
protected boolean undo() {
isUndo = true;
if (undo.canUndo()) {
@@ -2396,13 +2495,155 @@ protected boolean sendBreak() {
}
protected boolean backwardChar() {
+ if (terminal.getGraphemeClusterMode()) {
+ int moved = 0;
+ for (int i = 0; i < count && buf.cursor() > 0; i++) {
+ moveBackwardOne();
+ moved++;
+ }
+ return moved != 0;
+ }
return buf.move(-count) != 0;
}
protected boolean forwardChar() {
+ if (terminal.getGraphemeClusterMode()) {
+ int moved = 0;
+ for (int i = 0; i < count && buf.cursor() < buf.length(); i++) {
+ moveForwardOne();
+ moved++;
+ }
+ return moved != 0;
+ }
return buf.move(count) != 0;
}
+ /**
+ * Returns the number of code points in the grapheme cluster starting at
+ * position {@code pos} in the buffer.
+ */
+ private static int graphemeClusterCodePointCount(Buffer buf, int pos) {
+ int len = buf.length();
+ if (pos >= len) return 0;
+ int cp = buf.atChar(pos);
+ int cur = pos + 1;
+ // Regional indicator pairs (flags)
+ if (WCWidth.isRegionalIndicator(cp) && cur < len && WCWidth.isRegionalIndicator(buf.atChar(cur))) {
+ return 2;
+ }
+ // Consume grapheme cluster extensions
+ while (cur < len) {
+ int ncp = buf.atChar(cur);
+ if (ncp == 0x200D && cur + 1 < len) {
+ // ZWJ + next code point
+ cur += 2;
+ } else if (WCWidth.wcwidth(ncp) == 0 && ncp >= 0x20) {
+ cur++;
+ } else {
+ break;
+ }
+ }
+ return cur - pos;
+ }
+
+ /**
+ * Returns the number of code points in the grapheme cluster ending just
+ * before position {@code pos} in the buffer (for backward movement).
+ */
+ private static int graphemeClusterCodePointCountBefore(Buffer buf, int pos) {
+ if (pos <= 0) return 0;
+ // Walk backwards: a grapheme cluster in the buffer is structured as
+ // BASE [extending | ZWJ BASE]*
+ // We scan left from pos-1, consuming extending chars and ZWJ+BASE pairs.
+ int cur = pos - 1;
+ // Skip trailing extending characters (variation selectors, skin tones, combining marks)
+ while (cur > 0
+ && WCWidth.wcwidth(buf.atChar(cur)) == 0
+ && buf.atChar(cur) >= 0x20
+ && buf.atChar(cur) != 0x200D) {
+ cur--;
+ }
+ // Now cur points at a base character or a ZWJ.
+ // Scan backwards through ZWJ+BASE pairs
+ while (cur >= 2) {
+ int prev = buf.atChar(cur - 1);
+ if (prev == 0x200D) {
+ // ZWJ before the current base โ skip over ZWJ and the preceding base
+ cur -= 2;
+ // Also skip extending chars before this base
+ while (cur > 0
+ && WCWidth.wcwidth(buf.atChar(cur)) == 0
+ && buf.atChar(cur) >= 0x20
+ && buf.atChar(cur) != 0x200D) {
+ cur--;
+ }
+ } else {
+ break;
+ }
+ }
+ // Check for regional indicator pair
+ if (cur > 0
+ && WCWidth.isRegionalIndicator(buf.atChar(cur))
+ && WCWidth.isRegionalIndicator(buf.atChar(cur - 1))) {
+ cur--;
+ }
+ return pos - cur;
+ }
+
+ /**
+ * Moves the cursor forward past one grapheme cluster (or one code point
+ * when grapheme cluster mode is off).
+ */
+ private void moveForwardOne() {
+ if (terminal.getGraphemeClusterMode()) {
+ buf.move(graphemeClusterCodePointCount(buf, buf.cursor()));
+ } else {
+ buf.move(1);
+ }
+ }
+
+ /**
+ * Moves the cursor backward past one grapheme cluster (or one code point
+ * when grapheme cluster mode is off).
+ */
+ private void moveBackwardOne() {
+ if (terminal.getGraphemeClusterMode()) {
+ buf.move(-graphemeClusterCodePointCountBefore(buf, buf.cursor()));
+ } else {
+ buf.move(-1);
+ }
+ }
+
+ /**
+ * Deletes one grapheme cluster (or one code point) at the cursor.
+ *
+ * @return true if a character was deleted
+ */
+ private boolean deleteGrapheme() {
+ if (buf.cursor() >= buf.length()) return false;
+ if (terminal.getGraphemeClusterMode()) {
+ buf.delete(graphemeClusterCodePointCount(buf, buf.cursor()));
+ } else {
+ buf.delete();
+ }
+ return true;
+ }
+
+ /**
+ * Deletes one grapheme cluster (or one code point) before the cursor.
+ *
+ * @return true if a character was deleted
+ */
+ private boolean backspaceGrapheme() {
+ if (buf.cursor() <= 0) return false;
+ if (terminal.getGraphemeClusterMode()) {
+ buf.backspace(graphemeClusterCodePointCountBefore(buf, buf.cursor()));
+ } else {
+ buf.backspace();
+ }
+ return true;
+ }
+
protected boolean viDigitOrBeginningOfLine() {
if (repeatCount > 0) {
return digitArgument();
@@ -2603,8 +2844,11 @@ protected void doCleanup(boolean nl) {
}
terminal.puts(Capability.keypad_local);
terminal.trackMouse(Terminal.MouseTracking.Off);
+
if (isSet(Option.BRACKETED_PASTE) && !isTerminalDumb())
terminal.writer().write(BRACKETED_PASTE_OFF);
+ // Stop the masking thread if it was started for dumb terminals
+ stopMaskThread();
flush();
}
history.moveToEnd();
@@ -2651,6 +2895,7 @@ protected boolean doSearchHistory(boolean backward) {
searchTerm = new StringBuffer();
searchBackward = backward;
searchFailing = false;
+ searchIndexStack = new ArrayDeque<>(); // Initialize the stack
post = () -> new AttributedString((searchFailing ? "failing" + " " : "")
+ (searchBackward ? "bck-i-search" : "fwd-i-search")
+ ": " + searchTerm + "_");
@@ -2658,10 +2903,12 @@ protected boolean doSearchHistory(boolean backward) {
redisplay();
try {
while (true) {
- int prevSearchIndex = searchIndex;
Binding operation = readBinding(getKeys(), terminators);
String ref = (operation instanceof Reference) ? ((Reference) operation).name() : "";
boolean next = false;
+ boolean clearedFailingState = false;
+ boolean poppedFromStack = false;
+
switch (ref) {
case SEND_BREAK:
beep();
@@ -2676,12 +2923,33 @@ protected boolean doSearchHistory(boolean backward) {
next = true;
break;
case BACKWARD_DELETE_CHAR:
- if (searchTerm.length() > 0) {
+ // Handle backspace with zsh-like behavior
+ if (searchFailing) {
+ // If last search failed, clear failing state and stay at current position
+ searchFailing = false;
+ clearedFailingState = true;
+ // Don't do anything else - just clear the failing state
+ } else if (!searchIndexStack.isEmpty()) {
+ // Navigate back to previous search depth
+ searchIndex = searchIndexStack.pop();
+ poppedFromStack = true;
+ // Re-search to update the buffer
+ next = false; // Don't search for next, just update display
+ } else if (searchTerm.length() > 0) {
+ // Delete last character of search term
searchTerm.deleteCharAt(searchTerm.length() - 1);
+ // Clear the stack when modifying search term
+ searchIndexStack.clear();
+ } else {
+ // Search term is empty, restore original buffer
+ buf.copyFrom(originalBuffer);
+ searchIndex = -1;
}
break;
case SELF_INSERT:
searchTerm.append(getLastBinding());
+ // Clear the stack when typing new characters
+ searchIndexStack.clear();
break;
default:
// Set buffer and cursor position to the found string.
@@ -2693,69 +2961,111 @@ protected boolean doSearchHistory(boolean backward) {
}
// print the search status
- String pattern = doGetSearchPattern();
- if (pattern.length() == 0) {
- buf.copyFrom(originalBuffer);
- searchFailing = false;
- } else {
- boolean caseInsensitive = isSet(Option.CASE_INSENSITIVE_SEARCH);
- Pattern pat = Pattern.compile(
- pattern,
- caseInsensitive ? Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE : Pattern.UNICODE_CASE);
- Pair pair = null;
- if (searchBackward) {
- boolean nextOnly = next;
- pair = matches(pat, buf.toString(), searchIndex).stream()
- .filter(p -> nextOnly ? p.v < buf.cursor() : p.v <= buf.cursor())
- .max(Comparator.comparing(Pair::getV))
- .orElse(null);
- if (pair == null) {
- pair = StreamSupport.stream(
- Spliterators.spliteratorUnknownSize(
- history.reverseIterator(
- searchIndex < 0 ? history.last() : searchIndex - 1),
- Spliterator.ORDERED),
- false)
- .flatMap(e -> matches(pat, e.line(), e.index()).stream())
+ // Special handling for backspace when navigating back in stack
+ if (poppedFromStack) {
+ // Just update the display with the popped index
+ String pattern = doGetSearchPattern();
+ if (!pattern.isEmpty()) {
+ boolean caseInsensitive = isSet(Option.CASE_INSENSITIVE_SEARCH);
+ Pattern pat = Pattern.compile(
+ pattern,
+ caseInsensitive
+ ? Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE
+ : Pattern.UNICODE_CASE);
+ // Find the match at the current searchIndex
+ Pair pair = null;
+ if (searchIndex >= 0) {
+ pair = matches(pat, history.get(searchIndex), searchIndex).stream()
+ .findFirst()
+ .orElse(null);
+ } else {
+ pair = matches(pat, originalBuffer.toString(), -1).stream()
.findFirst()
.orElse(null);
}
+ if (pair != null) {
+ buf.clear();
+ if (searchIndex >= 0) {
+ buf.write(history.get(searchIndex));
+ } else {
+ buf.write(originalBuffer.toString());
+ }
+ buf.cursor(pair.v);
+ searchFailing = false;
+ }
+ }
+ } else if (!clearedFailingState) {
+ // Normal search logic (skip if we just cleared the failing state)
+ String pattern = doGetSearchPattern();
+ if (pattern.isEmpty()) {
+ buf.copyFrom(originalBuffer);
+ searchFailing = false;
} else {
- boolean nextOnly = next;
- pair = matches(pat, buf.toString(), searchIndex).stream()
- .filter(p -> nextOnly ? p.v > buf.cursor() : p.v >= buf.cursor())
- .min(Comparator.comparing(Pair::getV))
- .orElse(null);
- if (pair == null) {
- pair = StreamSupport.stream(
- Spliterators.spliteratorUnknownSize(
- history.iterator(
- (searchIndex < 0 ? history.last() : searchIndex) + 1),
- Spliterator.ORDERED),
- false)
- .flatMap(e -> matches(pat, e.line(), e.index()).stream())
- .findFirst()
+ boolean caseInsensitive = isSet(Option.CASE_INSENSITIVE_SEARCH);
+ Pattern pat = Pattern.compile(
+ pattern,
+ caseInsensitive
+ ? Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE
+ : Pattern.UNICODE_CASE);
+ Pair pair;
+ if (searchBackward) {
+ boolean nextOnly = next;
+ pair = matches(pat, buf.toString(), searchIndex).stream()
+ .filter(p -> nextOnly ? p.v < buf.cursor() : p.v <= buf.cursor())
+ .max(Comparator.comparing(Pair::getV))
.orElse(null);
- if (pair == null && searchIndex >= 0) {
- pair = matches(pat, originalBuffer.toString(), -1).stream()
- .min(Comparator.comparing(Pair::getV))
+ if (pair == null) {
+ pair = StreamSupport.stream(
+ Spliterators.spliteratorUnknownSize(
+ history.reverseIterator(
+ searchIndex < 0 ? history.last() : searchIndex - 1),
+ Spliterator.ORDERED),
+ false)
+ .flatMap(e -> matches(pat, e.line(), e.index()).stream())
+ .findFirst()
.orElse(null);
}
+ } else {
+ boolean nextOnly = next;
+ pair = matches(pat, buf.toString(), searchIndex).stream()
+ .filter(p -> nextOnly ? p.v > buf.cursor() : p.v >= buf.cursor())
+ .min(Comparator.comparing(Pair::getV))
+ .orElse(null);
+ if (pair == null) {
+ pair = StreamSupport.stream(
+ Spliterators.spliteratorUnknownSize(
+ history.iterator(
+ (searchIndex < 0 ? history.last() : searchIndex) + 1),
+ Spliterator.ORDERED),
+ false)
+ .flatMap(e -> matches(pat, e.line(), e.index()).stream())
+ .findFirst()
+ .orElse(null);
+ if (pair == null && searchIndex >= 0) {
+ pair = matches(pat, originalBuffer.toString(), -1).stream()
+ .min(Comparator.comparing(Pair::getV))
+ .orElse(null);
+ }
+ }
}
- }
- if (pair != null) {
- searchIndex = pair.u;
- buf.clear();
- if (searchIndex >= 0) {
- buf.write(history.get(searchIndex));
+ if (pair != null) {
+ // Push current index to stack when navigating to a new position
+ if (next && searchIndex != -1 && pair.u != searchIndex) {
+ searchIndexStack.push(searchIndex);
+ }
+ searchIndex = pair.u;
+ buf.clear();
+ if (searchIndex >= 0) {
+ buf.write(history.get(searchIndex));
+ } else {
+ buf.write(originalBuffer.toString());
+ }
+ buf.cursor(pair.v);
+ searchFailing = false;
} else {
- buf.write(originalBuffer.toString());
+ searchFailing = true;
+ beep();
}
- buf.cursor(pair.v);
- searchFailing = false;
- } else {
- searchFailing = true;
- beep();
}
}
redisplay();
@@ -2769,6 +3079,7 @@ protected boolean doSearchHistory(boolean backward) {
} finally {
searchTerm = null;
searchIndex = -1;
+ searchIndexStack = null;
post = null;
}
}
@@ -3299,7 +3610,9 @@ protected boolean backwardDeleteChar() {
if (buf.cursor() == 0) {
return false;
}
- buf.backspace(count);
+ for (int i = 0; i < count; i++) {
+ if (!backspaceGrapheme()) break;
+ }
return true;
}
@@ -3350,7 +3663,9 @@ protected boolean deleteChar() {
if (buf.cursor() == buf.length()) {
return false;
}
- buf.delete(count);
+ for (int i = 0; i < count; i++) {
+ if (!deleteGrapheme()) break;
+ }
return true;
}
@@ -3374,9 +3689,7 @@ protected boolean viBackwardDeleteChar() {
*/
protected boolean viDeleteChar() {
for (int i = 0; i < count; i++) {
- if (!buf.delete()) {
- return false;
- }
+ if (!deleteGrapheme()) return false;
}
return true;
}
@@ -3393,7 +3706,7 @@ protected boolean viSwapCase() {
int ch = buf.atChar(buf.cursor());
ch = switchCase(ch);
buf.currChar(ch);
- buf.move(1);
+ moveForwardOne();
} else {
return false;
}
@@ -3414,7 +3727,14 @@ protected boolean viReplaceChars() {
}
for (int i = 0; i < count; i++) {
- if (buf.currChar((char) c)) {
+ if (terminal.getGraphemeClusterMode() && buf.cursor() < buf.length()) {
+ // Delete the entire grapheme cluster, then insert the replacement
+ deleteGrapheme();
+ buf.write(c);
+ if (i == count - 1) {
+ buf.move(-1);
+ }
+ } else if (buf.currChar((char) c)) {
if (i < count - 1) {
buf.move(1);
}
@@ -3694,6 +4014,7 @@ protected boolean editAndExecute() {
return out;
}
+ @SuppressWarnings("java:S1845") // widget method intentionally matches its constant name
protected Map builtinWidgets() {
Map widgets = new HashMap<>();
addBuiltinWidget(widgets, ACCEPT_AND_INFER_NEXT_HISTORY, this::acceptAndInferNextHistory);
@@ -3864,6 +4185,7 @@ public boolean apply() {
};
}
+ @SuppressWarnings("java:S1845") // widget method intentionally matches its constant name
public boolean redisplay() {
redisplay(true);
return true;
@@ -3879,6 +4201,14 @@ protected void redisplay(boolean flush) {
}
Status status = Status.getStatus(terminal, false);
+ int currentStatusSize = status != null ? status.size() : 0;
+ if (currentStatusSize != lastStatusSize) {
+ // Status bar appeared, grew, or shrank (e.g. from a background
+ // thread). Content may have scrolled, invalidating cursor tracking.
+ terminal.puts(Capability.carriage_return);
+ doDisplay();
+ lastStatusSize = currentStatusSize;
+ }
if (status != null) {
status.redraw();
}
@@ -3902,7 +4232,7 @@ protected void redisplay(boolean flush) {
int w = WCWidth.wcwidth('\u2026');
int width = size.getColumns();
- int cursor = toCursor.columnLength();
+ int cursor = toCursor.columnLength(terminal);
int inc = width / 2 + 1;
while (cursor <= smallTerminalOffset + w) {
smallTerminalOffset -= inc;
@@ -3913,13 +4243,13 @@ protected void redisplay(boolean flush) {
if (smallTerminalOffset > 0) {
sb.setLength(0);
sb.append("\u2026");
- sb.append(full.columnSubSequence(smallTerminalOffset + w, Integer.MAX_VALUE));
+ sb.append(full.columnSubSequence(smallTerminalOffset + w, Integer.MAX_VALUE, terminal));
full = sb.toAttributedString();
}
- int length = full.columnLength();
+ int length = full.columnLength(terminal);
if (length >= smallTerminalOffset + width) {
sb.setLength(0);
- sb.append(full.columnSubSequence(0, width - w));
+ sb.append(full.columnSubSequence(0, width - w, terminal));
sb.append("\u2026");
full = sb.toAttributedString();
}
@@ -3936,7 +4266,7 @@ protected void redisplay(boolean flush) {
newLines = new ArrayList<>();
newLines.add(full);
} else {
- newLines = full.columnSplitLength(size.getColumns(), true, display.delayLineWrap());
+ newLines = full.columnSplitLength(size.getColumns(), true, display.delayLineWrap(), terminal);
}
List rightPromptLines;
@@ -3965,10 +4295,10 @@ protected void redisplay(boolean flush) {
}
sb.append(insertSecondaryPrompts(new AttributedString(buffer), secondaryPrompts, false));
List promptLines =
- sb.columnSplitLength(size.getColumns(), false, display.delayLineWrap());
+ sb.columnSplitLength(size.getColumns(), false, display.delayLineWrap(), terminal);
if (!promptLines.isEmpty()) {
cursorNewLinesId = promptLines.size() - 1;
- cursorColPos = promptLines.get(promptLines.size() - 1).columnLength();
+ cursorColPos = promptLines.get(promptLines.size() - 1).columnLength(terminal);
cursorPos = size.cursorPos(cursorNewLinesId, cursorColPos);
}
}
@@ -4128,7 +4458,7 @@ private AttributedString getHighlightedBuffer(String buffer) {
return new AttributedString(buffer);
}
- private AttributedString expandPromptPattern(String pattern, int padToWidth, String message, int line) {
+ AttributedString expandPromptPattern(String pattern, int padToWidth, String message, int line) {
ArrayList parts = new ArrayList<>();
boolean isHidden = false;
int padPartIndex = -1;
@@ -4137,7 +4467,7 @@ private AttributedString expandPromptPattern(String pattern, int padToWidth, Str
// Add "%{" to avoid special case for end of string.
pattern = pattern + "%{";
int plen = pattern.length();
- int padChar = -1;
+ String padString = null;
int padPos = -1;
int cols = 0;
for (int i = 0; i < plen; ) {
@@ -4155,7 +4485,7 @@ private AttributedString expandPromptPattern(String pattern, int padToWidth, Str
AttributedString astr;
if (!isHidden) {
astr = fromAnsi(str);
- cols += astr.columnLength();
+ cols += astr.columnLength(terminal);
} else {
astr = new AttributedString(str, AttributedStyle.HIDDEN);
}
@@ -4176,14 +4506,28 @@ private AttributedString expandPromptPattern(String pattern, int padToWidth, Str
case 'N':
sb.append(getInt(LINE_OFFSET, 0) + line);
break decode;
+ case '*':
+ if (this.currentLine == line) {
+ sb.append("*");
+ } else {
+ sb.append(" ");
+ }
+ break decode;
case 'M':
if (message != null) sb.append(message);
break decode;
case 'P':
if (countSeen && count >= 0) padToWidth = count;
- if (i < plen) {
- padChar = pattern.charAt(i++);
- // FIXME check surrogate
+ if (i < plen && pattern.charAt(i) == '{') {
+ // Multi-character pad string: %P{. } or %P{...}
+ i++; // skip '{'
+ int end = pattern.indexOf('}', i);
+ if (end > i) {
+ padString = pattern.substring(i, end);
+ i = end + 1;
+ }
+ } else if (i < plen) {
+ padString = String.valueOf(pattern.charAt(i++));
}
padPos = sb.length();
padPartIndex = parts.size();
@@ -4221,11 +4565,19 @@ private AttributedString expandPromptPattern(String pattern, int padToWidth, Str
}
} else sb.append(ch);
}
- if (padToWidth > cols && padToWidth > 0) {
- int padCharCols = WCWidth.wcwidth(padChar);
- int padCount = (padToWidth - cols) / padCharCols;
+ if (padToWidth > cols && padToWidth > 0 && padString != null) {
+ int remaining = padToWidth - cols;
sb = padPartString;
- while (--padCount >= 0) sb.insert(padPos, (char) padChar); // FIXME if wide
+ int padIdx = 0;
+ while (remaining > 0) {
+ char pc = padString.charAt(padIdx % padString.length());
+ int w = WCWidth.wcwidth(pc);
+ if (w <= 0) w = 1;
+ if (w > remaining) break;
+ sb.insert(padPos + padIdx, pc);
+ padIdx++;
+ remaining -= w;
+ }
parts.set(padPartIndex, fromAnsi(sb.toString()));
}
return AttributedString.join(null, parts);
@@ -4251,7 +4603,7 @@ private AttributedString insertSecondaryPrompts(
int width = 0;
List missings = new ArrayList<>();
if (computePrompts && secondaryPromptPattern.contains("%P")) {
- width = prompt.columnLength();
+ width = prompt.columnLength(terminal);
if (width > size.getColumns() || prompt.contains('\n')) {
width = new TerminalLine(prompt.toString(), 0, size.getColumns())
.getEndLine()
@@ -4272,11 +4624,24 @@ private AttributedString insertSecondaryPrompts(
}
missings.add(missing);
prompt = expandPromptPattern(secondaryPromptPattern, 0, missing, line + 1);
- width = Math.max(width, prompt.columnLength());
+ width = Math.max(width, prompt.columnLength(terminal));
}
buf.setLength(0);
}
int line = 0;
+ // compute the current line number
+ this.currentLine = -1;
+ int cursor = this.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) {
+ this.currentLine = l;
+ break;
+ }
+ start = end + 1;
+ }
+
while (line < lines.size() - 1) {
sb.append(lines.get(line)).append("\n");
buf.append(lines.get(line)).append("\n");
@@ -4310,10 +4675,10 @@ private AttributedString insertSecondaryPrompts(
}
private AttributedString addRightPrompt(AttributedString prompt, AttributedString line) {
- int width = prompt.columnLength();
+ int width = prompt.columnLength(terminal);
boolean endsWithNl = line.length() > 0 && line.charAt(line.length() - 1) == '\n';
// columnLength counts -1 for the final newline; adjust for that
- int nb = size.getColumns() - width - (line.columnLength() + (endsWithNl ? 1 : 0));
+ int nb = size.getColumns() - width - (line.columnLength(terminal) + (endsWithNl ? 1 : 0));
if (nb >= 3) {
AttributedStringBuilder sb = new AttributedStringBuilder(size.getColumns());
sb.append(line, 0, endsWithNl ? line.length() - 1 : line.length());
@@ -4562,8 +4927,8 @@ else if (isSet(Option.RECOGNIZE_EXACT)) {
}
if (useMenu) {
- buf.move(line.word().length() - line.wordCursor());
- buf.backspace(line.word().length());
+ buf.move(line.rawWordLength() - line.rawWordCursor());
+ buf.backspace(line.rawWordLength());
doMenu(possible, line.word(), line::escape);
return true;
}
@@ -4581,9 +4946,13 @@ else if (isSet(Option.RECOGNIZE_EXACT)) {
String commonPrefix = completionMatcher.getCommonPrefix();
boolean hasUnambiguous = commonPrefix.startsWith(current) && !commonPrefix.equals(current);
+ // Track raw length in buffer for correct backspace when entering menu
+ int rawLen = line.rawWordLength();
if (hasUnambiguous) {
- buf.backspace(line.rawWordLength());
- buf.write(line.escape(commonPrefix, false));
+ buf.backspace(rawLen);
+ CharSequence escaped = line.escape(commonPrefix, false);
+ buf.write(escaped.toString());
+ rawLen = escaped.length();
callWidget(REDISPLAY);
current = commonPrefix;
if ((!isSet(Option.AUTO_LIST) && isSet(Option.AUTO_MENU))
@@ -4599,7 +4968,7 @@ else if (isSet(Option.RECOGNIZE_EXACT)) {
}
}
if (isSet(Option.AUTO_MENU)) {
- buf.backspace(current.length());
+ buf.backspace(rawLen);
doMenu(possible, line.word(), line::escape);
}
return true;
@@ -4734,6 +5103,7 @@ private int promptLines() {
private class MenuSupport implements Supplier {
final List possible;
final BiFunction escaper;
+ final int[] groupOffsets; // start index of each group in possible, final entry = possible.size()
int selection;
int topLine;
String word;
@@ -4751,9 +5121,34 @@ public MenuSupport(
this.word = "";
this.completed = completed;
computePost(original, null, possible, completed);
+ // Compute group boundaries
+ if (isSet(Option.GROUP_PERSIST)) {
+ List offsets = new ArrayList<>();
+ offsets.add(0);
+ for (int i = 1; i < possible.size(); i++) {
+ String prev = possible.get(i - 1).group();
+ String curr = possible.get(i).group();
+ if (!Objects.equals(prev, curr)) {
+ offsets.add(i);
+ }
+ }
+ offsets.add(possible.size());
+ this.groupOffsets = offsets.stream().mapToInt(Integer::intValue).toArray();
+ } else {
+ this.groupOffsets = null;
+ }
next();
}
+ private int groupOf(int sel) {
+ for (int g = 0; g < groupOffsets.length - 1; g++) {
+ if (sel < groupOffsets[g + 1]) {
+ return g;
+ }
+ }
+ return groupOffsets.length - 2;
+ }
+
public Candidate completion() {
return possible.get(selection);
}
@@ -4776,6 +5171,10 @@ public void previous() {
* @param step number of options to move by
*/
private void major(int step) {
+ if (groupOffsets != null) {
+ majorGroupAware(step);
+ return;
+ }
int axis = isSet(Option.LIST_ROWS_FIRST) ? columns : lines;
int sel = selection + step * axis;
if (sel < 0) {
@@ -4800,6 +5199,10 @@ private void major(int step) {
* @param step number of options to move by
*/
private void minor(int step) {
+ if (groupOffsets != null) {
+ minorGroupAware(step);
+ return;
+ }
int axis = isSet(Option.LIST_ROWS_FIRST) ? columns : lines;
int row = selection % axis;
int options = possible.size();
@@ -4812,6 +5215,92 @@ private void minor(int step) {
update();
}
+ /**
+ * Group-aware major axis navigation (left/right for column-major, up/down for row-major).
+ * Wraps within the current group at boundaries.
+ */
+ private void majorGroupAware(int step) {
+ int g = groupOf(selection);
+ int groupStart = groupOffsets[g];
+ int groupSize = groupOffsets[g + 1] - groupStart;
+ int local = selection - groupStart;
+ int groupLines = (groupSize + columns - 1) / columns;
+ int axis = isSet(Option.LIST_ROWS_FIRST) ? columns : groupLines;
+ int sel = local + step * axis;
+ if (sel < 0 || sel >= groupSize) {
+ // Wrap within group
+ if (sel < 0) {
+ int pos = (sel + axis) % axis;
+ int remainders = groupSize % axis;
+ sel = groupSize - remainders + pos;
+ if (sel >= groupSize) {
+ sel -= axis;
+ }
+ } else {
+ sel = sel % axis;
+ }
+ }
+ selection = groupStart + sel;
+ update();
+ }
+
+ /**
+ * Group-aware minor axis navigation (up/down for column-major, left/right for row-major).
+ * Crosses group boundaries: moving past the edge of a group enters the adjacent group.
+ */
+ private void minorGroupAware(int step) {
+ boolean rowsFirst = isSet(Option.LIST_ROWS_FIRST);
+ int g = groupOf(selection);
+ int groupStart = groupOffsets[g];
+ int groupSize = groupOffsets[g + 1] - groupStart;
+ int local = selection - groupStart;
+ int groupLines = (groupSize + columns - 1) / columns;
+ int axis = rowsFirst ? columns : groupLines;
+ // For column-major: col = local / groupLines, row = local % groupLines
+ // For row-major: row = local / columns, col = local % columns
+ int col = rowsFirst ? local % columns : local / groupLines;
+ int row = rowsFirst ? local / columns : local % groupLines;
+ int newRow = row + step;
+ if (newRow >= 0 && newRow < axis) {
+ // Within current group, check bounds
+ int newLocal = rowsFirst ? newRow * columns + col : col * groupLines + newRow;
+ if (newLocal < groupSize) {
+ selection = groupStart + newLocal;
+ update();
+ return;
+ }
+ }
+ // Cross group boundary
+ int numGroups = groupOffsets.length - 1;
+ if (step > 0) {
+ // Moving down/right: go to next group, same column, first row
+ int ng = (g + 1) % numGroups;
+ int ngStart = groupOffsets[ng];
+ int ngSize = groupOffsets[ng + 1] - ngStart;
+ int ngLines = (ngSize + columns - 1) / columns;
+ int newCol = Math.min(col, (rowsFirst ? (ngSize - 1) % columns : (ngSize + ngLines - 1) / ngLines - 1));
+ int newLocal = rowsFirst ? newCol : newCol * ngLines;
+ if (newLocal >= ngSize) {
+ newLocal = rowsFirst ? 0 : 0;
+ }
+ selection = ngStart + newLocal;
+ } else {
+ // Moving up/left: go to previous group, same column, last row
+ int ng = (g + numGroups - 1) % numGroups;
+ int ngStart = groupOffsets[ng];
+ int ngSize = groupOffsets[ng + 1] - ngStart;
+ int ngLines = (ngSize + columns - 1) / columns;
+ int newCol = Math.min(col, (rowsFirst ? (ngSize - 1) % columns : (ngSize + ngLines - 1) / ngLines - 1));
+ int lastRow = rowsFirst ? (ngSize - 1) / columns : Math.min(ngLines - 1, ngSize - newCol * ngLines - 1);
+ int newLocal = rowsFirst ? lastRow * columns + newCol : newCol * ngLines + lastRow;
+ if (newLocal >= ngSize) {
+ newLocal = ngSize - 1;
+ }
+ selection = ngStart + newLocal;
+ }
+ update();
+ }
+
public void up() {
if (isSet(Option.LIST_ROWS_FIRST)) {
major(-1);
@@ -4885,8 +5374,13 @@ private void update() {
} else {
computed = pr.post;
}
- lines = pr.lines;
- columns = (possible.size() + lines - 1) / lines;
+ if (pr.columns > 0) {
+ columns = pr.columns;
+ lines = (possible.size() + columns - 1) / columns;
+ } else {
+ lines = pr.lines;
+ columns = (possible.size() + lines - 1) / lines;
+ }
}
@Override
@@ -4903,7 +5397,6 @@ protected boolean doMenu(
original.sort(getCandidateComparator(caseInsensitive, completed));
mergeCandidates(original);
computePost(original, null, possible, completed);
- // candidate grouping is not supported by MenuSupport
boolean defaultAutoGroup = isSet(Option.AUTO_GROUP);
boolean defaultGroup = isSet(Option.GROUP);
if (!isSet(Option.GROUP_PERSIST)) {
@@ -4980,7 +5473,8 @@ && getLastBinding().charAt(0) != ' '
}
protected boolean clearChoices() {
- return doList(new ArrayList<>(), "", false, null, false);
+ post = null;
+ return false;
}
protected boolean doList(
@@ -5174,11 +5668,17 @@ protected static class PostResult {
final AttributedString post;
final int lines;
final int selectedLine;
+ final int columns;
public PostResult(AttributedString post, int lines, int selectedLine) {
+ this(post, lines, selectedLine, -1);
+ }
+
+ public PostResult(AttributedString post, int lines, int selectedLine, int columns) {
this.post = post;
this.lines = lines;
this.selectedLine = selectedLine;
+ this.columns = columns;
}
}
@@ -5325,7 +5825,7 @@ protected PostResult toColumns(
Function wcwidth,
int width,
boolean rowsFirst) {
- int[] out = new int[2];
+ int[] out = new int[3]; // [0]=lines, [1]=selectedLine, [2]=columns
// TODO: support Option.LIST_PACKED
// Compute column width
int maxWidth = 0;
@@ -5384,7 +5884,7 @@ protected PostResult toColumns(
if (sb.length() > 0 && sb.charAt(sb.length() - 1) == '\n') {
sb.setLength(sb.length() - 1);
}
- return new PostResult(sb.toAttributedString(), out[0], out[1]);
+ return new PostResult(sb.toAttributedString(), out[0], out[1], out[2]);
}
@SuppressWarnings("unchecked")
@@ -5433,6 +5933,9 @@ else if (items instanceof List) {
// Try to minimize the number of columns for the given number of rows
// Prevents eg 9 candiates being split 6/3 instead of 5/4.
final int columns = (candidates.size() + lines - 1) / lines;
+ if (out[2] <= 0) {
+ out[2] = columns;
+ }
IntBinaryOperator index;
if (rowsFirst) {
index = (i, j) -> i * columns + j;
@@ -5452,7 +5955,7 @@ else if (items instanceof List) {
boolean hasRightItem = j < columns - 1 && index.applyAsInt(i, j + 1) < candidates.size();
AttributedString left = fromAnsi(cand.displ());
AttributedString right = fromAnsi(cand.descr());
- int lw = left.columnLength();
+ int lw = left.columnLength(terminal);
int rw = 0;
if (right != null) {
int rem = maxWidth
@@ -5460,17 +5963,17 @@ else if (items instanceof List) {
+ MARGIN_BETWEEN_DISPLAY_AND_DESC
+ DESC_PREFIX.length()
+ DESC_SUFFIX.length());
- rw = right.columnLength();
+ rw = right.columnLength(terminal);
if (rw > rem) {
right = AttributedStringBuilder.append(
- right.columnSubSequence(0, rem - WCWidth.wcwidth('\u2026')), "\u2026");
- rw = right.columnLength();
+ right.columnSubSequence(0, rem - WCWidth.wcwidth('\u2026'), terminal), "\u2026");
+ rw = right.columnLength(terminal);
}
right = AttributedStringBuilder.append(DESC_PREFIX, right, DESC_SUFFIX);
rw += DESC_PREFIX.length() + DESC_SUFFIX.length();
}
if (cand == selection) {
- out[1] = i;
+ out[1] = out[0] + i;
asb.style(getCompletionStyleSelection(doMenuList));
if (left.toString()
.regionMatches(
@@ -5841,6 +6344,7 @@ private boolean doCopyKillRegion(boolean kill) {
return true;
}
+ @SuppressWarnings("java:S1845") // widget method intentionally matches its constant name
public boolean yank() {
String yanked = killRing.yank();
if (yanked == null) {
@@ -5871,6 +6375,7 @@ public boolean yankPop() {
return true;
}
+ @SuppressWarnings("java:S1845") // widget method intentionally matches its constant name
public boolean mouse() {
MouseEvent event = readMouseEvent();
if (event.getType() == MouseEvent.Type.Released && event.getButton() == MouseEvent.Button.Button1) {
@@ -5890,11 +6395,11 @@ public boolean mouse() {
int currentLine = promptLines.size() - 1;
int wantedLine = Math.max(0, Math.min(currentLine + event.getY() - cursor.getY(), secondaryPrompts.size()));
int pl0 = currentLine == 0
- ? prompt.columnLength()
- : secondaryPrompts.get(currentLine - 1).columnLength();
+ ? prompt.columnLength(terminal)
+ : secondaryPrompts.get(currentLine - 1).columnLength(terminal);
int pl1 = wantedLine == 0
- ? prompt.columnLength()
- : secondaryPrompts.get(wantedLine - 1).columnLength();
+ ? prompt.columnLength(terminal)
+ : secondaryPrompts.get(wantedLine - 1).columnLength(terminal);
int adjust = pl1 - pl0;
buf.moveXY(event.getX() - cursor.getX() - adjust, event.getY() - cursor.getY());
}
@@ -5921,6 +6426,7 @@ public boolean focusOut() {
* Clean the used display
* @return true
*/
+ @SuppressWarnings("java:S1845") // widget method intentionally matches its constant name
public boolean clear() {
display.update(Collections.emptyList(), 0);
return true;
@@ -5952,6 +6458,7 @@ public boolean clearScreen() {
* Issue an audible keyboard bell.
* @return true
*/
+ @SuppressWarnings("java:S1845") // widget method intentionally matches its constant name
public boolean beep() {
BellType bell_preference = BellType.AUDIBLE;
switch (getString(BELL_STYLE, DEFAULT_BELL_STYLE).toLowerCase()) {
@@ -6066,6 +6573,7 @@ public Map> defaultKeyMaps() {
return keyMaps;
}
+ @SuppressWarnings("java:S1845") // keymap method intentionally matches its constant name
public KeyMap emacs() {
KeyMap emacs = new KeyMap<>();
bindKeys(emacs);
@@ -6184,6 +6692,7 @@ public KeyMap viInsertion() {
return viins;
}
+ @SuppressWarnings("java:S1845") // keymap method intentionally matches its constant name
public KeyMap viCmd() {
KeyMap vicmd = new KeyMap<>();
bind(vicmd, LIST_CHOICES, ctrl('D'));
@@ -6300,6 +6809,7 @@ public KeyMap viCmd() {
return vicmd;
}
+ @SuppressWarnings("java:S1845") // keymap method intentionally matches its constant name
public KeyMap menu() {
KeyMap menu = new KeyMap<>();
bind(menu, MENU_COMPLETE, "\t");
@@ -6309,6 +6819,7 @@ public KeyMap menu() {
return menu;
}
+ @SuppressWarnings("java:S1845") // keymap method intentionally matches its constant name
public KeyMap safe() {
KeyMap safe = new KeyMap<>();
bind(safe, SELF_INSERT, range("^@-^?"));
@@ -6317,6 +6828,7 @@ public KeyMap safe() {
return safe;
}
+ @SuppressWarnings("java:S1845") // keymap method intentionally matches its constant name
public KeyMap dumb() {
KeyMap dumb = new KeyMap<>();
bind(dumb, SELF_INSERT, range("^@-^?"));
@@ -6325,6 +6837,7 @@ public KeyMap dumb() {
return dumb;
}
+ @SuppressWarnings("java:S1845") // keymap method intentionally matches its constant name
public KeyMap visual() {
KeyMap visual = new KeyMap<>();
bind(visual, UP_LINE, key(Capability.key_up), "k");
@@ -6337,6 +6850,7 @@ public KeyMap visual() {
return visual;
}
+ @SuppressWarnings("java:S1845") // keymap method intentionally matches its constant name
public KeyMap viOpp() {
KeyMap viOpp = new KeyMap<>();
bind(viOpp, UP_LINE, key(Capability.key_up), "k");
@@ -6379,7 +6893,7 @@ private void bindArrowKeys(KeyMap map) {
bind(map, DELETE_CHAR, key(Capability.key_dc));
bind(map, KILL_WHOLE_LINE, key(Capability.key_dl));
bind(map, OVERWRITE_MODE, key(Capability.key_ic));
- bind(map, MOUSE, key(Capability.key_mouse));
+ bind(map, MOUSE, MouseSupport.keys(terminal));
bind(map, BEGIN_PASTE, BRACKETED_PASTE_BEGIN);
bind(map, FOCUS_IN, FOCUS_IN_SEQ);
bind(map, FOCUS_OUT, FOCUS_OUT_SEQ);
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/ReaderUtils.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/ReaderUtils.java
index cdc60dccccfaf..b1b5e8d008555 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/ReaderUtils.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/ReaderUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2020, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -11,19 +11,67 @@
import jdk.internal.org.jline.reader.LineReader;
import jdk.internal.org.jline.utils.Levenshtein;
+/**
+ * Utility methods for LineReader implementations.
+ *
+ * This class provides helper methods for working with LineReader variables and options.
+ * It includes methods for retrieving variables of different types (string, boolean, integer,
+ * long) with default values, checking if options are set, and calculating string distances
+ * for completion matching.
+ *
+ * These utilities are primarily used by the LineReader implementation classes to access
+ * configuration values in a consistent way, with proper type conversion and default handling.
+ *
+ * @see LineReader#getVariable(String)
+ * @see LineReader#isSet(LineReader.Option)
+ */
public class ReaderUtils {
private ReaderUtils() {}
+ /**
+ * Checks if a LineReader option is set.
+ *
+ * This method safely handles null readers by returning false.
+ *
+ * @param reader the LineReader to check, may be null
+ * @param option the option to check
+ * @return true if the reader is not null and the option is set, false otherwise
+ */
public static boolean isSet(LineReader reader, LineReader.Option option) {
return reader != null && reader.isSet(option);
}
+ /**
+ * Gets a string variable from a LineReader with a default value.
+ *
+ * This method safely handles null readers by returning the default value.
+ *
+ * @param reader the LineReader to get the variable from, may be null
+ * @param name the name of the variable to get
+ * @param def the default value to return if the variable is not set or the reader is null
+ * @return the variable value as a string, or the default value
+ */
public static String getString(LineReader reader, String name, String def) {
Object v = reader != null ? reader.getVariable(name) : null;
return v != null ? v.toString() : def;
}
+ /**
+ * Gets a boolean variable from a LineReader with a default value.
+ *
+ * This method safely handles null readers by returning the default value.
+ * String values are converted to boolean according to these rules:
+ *
+ * Empty string, "on", "1", and "true" (case-insensitive) are considered true
+ * All other strings are considered false
+ *
+ *
+ * @param reader the LineReader to get the variable from, may be null
+ * @param name the name of the variable to get
+ * @param def the default value to return if the variable is not set or the reader is null
+ * @return the variable value as a boolean, or the default value
+ */
public static boolean getBoolean(LineReader reader, String name, boolean def) {
Object v = reader != null ? reader.getVariable(name) : null;
if (v instanceof Boolean) {
@@ -35,6 +83,17 @@ public static boolean getBoolean(LineReader reader, String name, boolean def) {
return def;
}
+ /**
+ * Gets an integer variable from a LineReader with a default value.
+ *
+ * This method safely handles null readers by returning the default value.
+ * String values are parsed as integers, with a fallback to 0 if parsing fails.
+ *
+ * @param reader the LineReader to get the variable from, may be null
+ * @param name the name of the variable to get
+ * @param def the default value to return if the variable is not set or the reader is null
+ * @return the variable value as an integer, or the default value
+ */
public static int getInt(LineReader reader, String name, int def) {
int nb = def;
Object v = reader != null ? reader.getVariable(name) : null;
@@ -51,6 +110,17 @@ public static int getInt(LineReader reader, String name, int def) {
return nb;
}
+ /**
+ * Gets a long variable from a LineReader with a default value.
+ *
+ * This method safely handles null readers by returning the default value.
+ * String values are parsed as longs, with a fallback to 0 if parsing fails.
+ *
+ * @param reader the LineReader to get the variable from, may be null
+ * @param name the name of the variable to get
+ * @param def the default value to return if the variable is not set or the reader is null
+ * @return the variable value as a long, or the default value
+ */
public static long getLong(LineReader reader, String name, long def) {
long nb = def;
Object v = reader != null ? reader.getVariable(name) : null;
@@ -67,6 +137,17 @@ public static long getLong(LineReader reader, String name, long def) {
return nb;
}
+ /**
+ * Calculates the edit distance between a word and a candidate string.
+ *
+ * This method is used for fuzzy matching in completion. It uses the Levenshtein
+ * distance algorithm to determine how similar two strings are, with special handling
+ * for candidates that are longer than the word being matched.
+ *
+ * @param word the word to match against
+ * @param cand the candidate string to check
+ * @return the edit distance between the strings (lower values indicate closer matches)
+ */
public static int distance(String word, String cand) {
if (word.length() < cand.length()) {
int d1 = Levenshtein.distance(word, cand.substring(0, Math.min(cand.length(), word.length())));
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/SimpleMaskingCallback.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/SimpleMaskingCallback.java
index 423497f3dc44a..fee5681d5ba24 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/SimpleMaskingCallback.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/SimpleMaskingCallback.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2017, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/UndoTree.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/UndoTree.java
index 3b193f9eb65ea..b985303ec75f0 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/UndoTree.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/UndoTree.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -11,8 +11,22 @@
import java.util.function.Consumer;
/**
- * Simple undo tree.
- * Note that the first added state can't be undone
+ * Provides undo/redo functionality for the LineReader.
+ *
+ * This class implements a simple undo tree that allows tracking and restoring
+ * previous states of an object (typically the line buffer). It maintains a linear
+ * history of states that can be navigated with undo and redo operations.
+ *
+ * Key features:
+ *
+ * Tracks a sequence of states that can be undone and redone
+ * Uses a consumer to apply state changes when undoing or redoing
+ * Maintains the current position in the undo history
+ *
+ *
+ * Note that the first added state (the initial state) cannot be undone.
+ *
+ * @param the type of state object being tracked
*/
public class UndoTree {
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/AggregateCompleter.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/AggregateCompleter.java
index 145823c53cede..b4f0da339fcd2 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/AggregateCompleter.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/AggregateCompleter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -21,7 +21,6 @@
/**
* Completer which contains multiple completers and aggregates them together.
*
- * @author Jason Dillon
* @since 2.3
*/
public class AggregateCompleter implements Completer {
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/ArgumentCompleter.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/ArgumentCompleter.java
index f0efa292252e4..b0e349164c2a1 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/ArgumentCompleter.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/ArgumentCompleter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2019, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -25,8 +25,6 @@
* A {@link Completer} implementation that invokes a child completer using the appropriate separator argument.
* This can be used instead of the individual completers having to know about argument parsing semantics.
*
- * @author Marc Prud'hommeaux
- * @author Jason Dillon
* @since 2.3
*/
public class ArgumentCompleter implements Completer {
@@ -140,10 +138,23 @@ public void complete(LineReader reader, ParsedLine line, final List c
completer.complete(reader, line, candidates);
}
+ /**
+ * A simple implementation of ParsedLine for argument completion.
+ *
+ * This class represents a single word with a cursor position, used for
+ * completing arguments in the ArgumentCompleter.
+ *
+ */
public static class ArgumentLine implements ParsedLine {
private final String word;
private final int cursor;
+ /**
+ * Creates a new ArgumentLine with the specified word and cursor position.
+ *
+ * @param word the word being completed
+ * @param cursor the cursor position within the word
+ */
public ArgumentLine(String word, int cursor) {
this.word = word;
this.cursor = cursor;
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/EnumCompleter.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/EnumCompleter.java
index a701f2e3560bc..4f9e52c3b4620 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/EnumCompleter.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/EnumCompleter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -16,7 +16,6 @@
/**
* {@link Completer} for {@link Enum} names.
*
- * @author Jason Dillon
* @since 2.3
*/
public class EnumCompleter extends StringsCompleter {
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/FileNameCompleter.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/FileNameCompleter.java
deleted file mode 100644
index 32bf6e3493e6a..0000000000000
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/FileNameCompleter.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (c) 2002-2018, the original author(s).
- *
- * This software is distributable under the BSD license. See the terms of the
- * BSD license in the documentation provided with this software.
- *
- * https://opensource.org/licenses/BSD-3-Clause
- */
-package jdk.internal.org.jline.reader.impl.completer;
-
-import java.io.IOException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-
-import jdk.internal.org.jline.reader.Candidate;
-import jdk.internal.org.jline.reader.Completer;
-import jdk.internal.org.jline.reader.LineReader;
-import jdk.internal.org.jline.reader.LineReader.Option;
-import jdk.internal.org.jline.reader.ParsedLine;
-import jdk.internal.org.jline.terminal.Terminal;
-import jdk.internal.org.jline.utils.AttributedStringBuilder;
-import jdk.internal.org.jline.utils.AttributedStyle;
-
-/**
- * A file name completer takes the buffer and issues a list of
- * potential completions.
- *
- * This completer tries to behave as similar as possible to
- * bash 's file name completion (using GNU readline)
- * with the following exceptions:
- *
- * Candidates that are directories will end with "/"
- * Wildcard regular expressions are not evaluated or replaced
- * The "~" character can be used to represent the user's home,
- * but it cannot complete to other users' homes, since java does
- * not provide any way of determining that easily
- *
- *
- * @author Marc Prud'hommeaux
- * @author Jason Dillon
- * @since 2.3
- * @deprecated use org.jline.builtins.Completers$FileNameCompleter instead
- */
-@Deprecated
-public class FileNameCompleter implements Completer {
-
- public void complete(LineReader reader, ParsedLine commandLine, final List candidates) {
- assert commandLine != null;
- assert candidates != null;
-
- String buffer = commandLine.word().substring(0, commandLine.wordCursor());
-
- Path current;
- String curBuf;
- String sep = getUserDir().getFileSystem().getSeparator();
- int lastSep = buffer.lastIndexOf(sep);
- if (lastSep >= 0) {
- curBuf = buffer.substring(0, lastSep + 1);
- if (curBuf.startsWith("~")) {
- if (curBuf.startsWith("~" + sep)) {
- current = getUserHome().resolve(curBuf.substring(2));
- } else {
- current = getUserHome().getParent().resolve(curBuf.substring(1));
- }
- } else {
- current = getUserDir().resolve(curBuf);
- }
- } else {
- curBuf = "";
- current = getUserDir();
- }
- try (DirectoryStream directoryStream = Files.newDirectoryStream(current, this::accept)) {
- directoryStream.forEach(p -> {
- String value = curBuf + p.getFileName().toString();
- if (Files.isDirectory(p)) {
- candidates.add(new Candidate(
- value + (reader.isSet(Option.AUTO_PARAM_SLASH) ? sep : ""),
- getDisplay(reader.getTerminal(), p),
- null,
- null,
- reader.isSet(Option.AUTO_REMOVE_SLASH) ? sep : null,
- null,
- false));
- } else {
- candidates.add(
- new Candidate(value, getDisplay(reader.getTerminal(), p), null, null, null, null, true));
- }
- });
- } catch (IOException e) {
- // Ignore
- }
- }
-
- protected boolean accept(Path path) {
- try {
- return !Files.isHidden(path);
- } catch (IOException e) {
- return false;
- }
- }
-
- protected Path getUserDir() {
- return Paths.get(System.getProperty("user.dir"));
- }
-
- protected Path getUserHome() {
- return Paths.get(System.getProperty("user.home"));
- }
-
- protected String getDisplay(Terminal terminal, Path p) {
- // TODO: use $LS_COLORS for output
- String name = p.getFileName().toString();
- if (Files.isDirectory(p)) {
- AttributedStringBuilder sb = new AttributedStringBuilder();
- sb.styled(AttributedStyle.BOLD.foreground(AttributedStyle.RED), name);
- sb.append("/");
- name = sb.toAnsi(terminal);
- } else if (Files.isSymbolicLink(p)) {
- AttributedStringBuilder sb = new AttributedStringBuilder();
- sb.styled(AttributedStyle.BOLD.foreground(AttributedStyle.RED), name);
- sb.append("@");
- name = sb.toAnsi(terminal);
- }
- return name;
- }
-}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/NullCompleter.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/NullCompleter.java
index 21ce836a32f4e..18133893cd1bb 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/NullCompleter.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/NullCompleter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -18,12 +18,17 @@
/**
* Null completer.
*
- * @author Marc Prud'hommeaux
- * @author Jason Dillon
* @since 2.3
*/
public final class NullCompleter implements Completer {
public static final NullCompleter INSTANCE = new NullCompleter();
+ /**
+ * Creates a new NullCompleter.
+ */
+ public NullCompleter() {
+ // Default constructor
+ }
+
public void complete(LineReader reader, final ParsedLine line, final List candidates) {}
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/StringsCompleter.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/StringsCompleter.java
index c791ef68e72a4..c7fcbf571f5ab 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/StringsCompleter.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/StringsCompleter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2019, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -24,7 +24,6 @@
/**
* Completer for a set of strings.
*
- * @author Jason Dillon
* @since 2.3
*/
public class StringsCompleter implements Completer {
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/SystemCompleter.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/SystemCompleter.java
index 642cf275142a7..5f60756b71efd 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/SystemCompleter.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/SystemCompleter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2020, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -20,8 +20,6 @@
/**
* Completer which contains multiple completers and aggregates them together.
- *
- * @author Matti Rinta-Nikkola
*/
public class SystemCompleter implements Completer {
private Map> completers = new HashMap<>();
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/package-info.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/package-info.java
index 55835c7330be8..56f63047964d7 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/package-info.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author or authors.
+ * Copyright (c) 2002-2025, 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.
@@ -7,8 +7,28 @@
* https://opensource.org/licenses/BSD-3-Clause
*/
/**
- * JLine 3.
+ * JLine 3 Completer Implementations.
+ *
+ * This package provides various implementations of the {@link org.jline.reader.Completer}
+ * interface for different completion scenarios. These completers can be used individually
+ * or combined to create sophisticated tab completion behavior.
+ *
+ * Key completer implementations include:
+ *
+ * {@link org.jline.reader.impl.completer.ArgumentCompleter} - Completes commands based on argument position
+ * {@link org.jline.builtins.Completers.FileNameCompleter} - Completes file and directory names
+ * {@link org.jline.reader.impl.completer.StringsCompleter} - Completes from a predefined set of strings
+ * {@link org.jline.reader.impl.completer.SystemCompleter} - Aggregates multiple completers for different commands
+ * {@link org.jline.reader.impl.completer.AggregateCompleter} - Combines multiple completers
+ * {@link org.jline.reader.impl.completer.NullCompleter} - A no-op completer that provides no completions
+ *
+ *
+ * These completers can be registered with a {@link org.jline.reader.LineReader} using
+ * the {@link org.jline.reader.LineReaderBuilder#completer(org.jline.reader.Completer)}
+ * method.
*
* @since 3.0
+ * @see org.jline.reader.Completer
+ * @see org.jline.reader.LineReaderBuilder#completer(org.jline.reader.Completer)
*/
package jdk.internal.org.jline.reader.impl.completer;
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/history/DefaultHistory.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/history/DefaultHistory.java
index ca275c43c6da5..7d8a481753edf 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/history/DefaultHistory.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/history/DefaultHistory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2021, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -22,15 +22,58 @@
import static jdk.internal.org.jline.reader.impl.ReaderUtils.*;
/**
- * {@link History} using a file for persistent backing.
+ * Default implementation of {@link History} with file-based persistent storage.
*
- * Implementers should install shutdown hook to call {@link DefaultHistory#save}
- * to save history to disk.
- *
+ * This class provides a complete implementation of the History interface with the following features:
+ *
+ * In-memory storage of history entries with configurable size limits
+ * Persistent storage in a text file with configurable location and size limits
+ * Support for timestamped history entries
+ * Filtering of entries based on patterns defined in the {@link LineReader#HISTORY_IGNORE} variable
+ * Options to ignore duplicates, reduce blanks, and ignore commands starting with spaces
+ * Incremental saving of history entries
+ * History navigation (previous/next, first/last, etc.)
+ *
+ *
+ * The history file format is either plain text with one command per line, or if
+ * {@link LineReader.Option#HISTORY_TIMESTAMPED} is set, each line starts with a timestamp
+ * in milliseconds since epoch, followed by a colon and the command text.
+ *
+ * Applications using this class should install a shutdown hook to call {@link DefaultHistory#save}
+ * to ensure history is saved to disk when the application exits.
+ *
+ * Example usage:
+ *
+ * LineReader reader = LineReaderBuilder.builder()
+ * .variable(LineReader.HISTORY_FILE, Paths.get(System.getProperty("user.home"), ".myapp_history"))
+ * .build();
+ * // History is automatically attached to the reader
+ *
+ * // To save history manually:
+ * ((DefaultHistory) reader.getHistory()).save();
+ *
+ *
+ * @see History
+ * @see LineReader#HISTORY_FILE
+ * @see LineReader#HISTORY_SIZE
+ * @see LineReader#HISTORY_FILE_SIZE
+ * @see LineReader.Option#HISTORY_TIMESTAMPED
+ * @see LineReader.Option#HISTORY_IGNORE_SPACE
+ * @see LineReader.Option#HISTORY_IGNORE_DUPS
+ * @see LineReader.Option#HISTORY_REDUCE_BLANKS
*/
public class DefaultHistory implements History {
+ /**
+ * Default maximum number of history entries to keep in memory.
+ * This value is used when the {@link LineReader#HISTORY_SIZE} variable is not set.
+ */
public static final int DEFAULT_HISTORY_SIZE = 500;
+
+ /**
+ * Default maximum number of history entries to keep in the history file.
+ * This value is used when the {@link LineReader#HISTORY_FILE_SIZE} variable is not set.
+ */
public static final int DEFAULT_HISTORY_FILE_SIZE = 10000;
private final LinkedList items = new LinkedList<>();
@@ -41,8 +84,16 @@ public class DefaultHistory implements History {
private int offset = 0;
private int index = 0;
+ /**
+ * Creates a new DefaultHistory instance.
+ */
public DefaultHistory() {}
+ /**
+ * Creates a new DefaultHistory instance and attaches it to the specified LineReader.
+ *
+ * @param reader the LineReader to attach to
+ */
@SuppressWarnings("this-escape")
public DefaultHistory(LineReader reader) {
attach(reader);
@@ -61,6 +112,15 @@ private Path getPath() {
}
}
+ /**
+ * Attaches this history to a LineReader.
+ *
+ * This method associates the history with a LineReader and loads the history
+ * from the file specified by the {@link LineReader#HISTORY_FILE} variable.
+ * If the history is already attached to the given reader, this method does nothing.
+ *
+ * @param reader the LineReader to attach this history to
+ */
@Override
public void attach(LineReader reader) {
if (this.reader != reader) {
@@ -73,6 +133,15 @@ public void attach(LineReader reader) {
}
}
+ /**
+ * Loads history from the file specified by the {@link LineReader#HISTORY_FILE} variable.
+ *
+ * This method clears the current history and loads entries from the history file.
+ * If the file doesn't exist or can't be read, the history will be cleared.
+ * If individual lines in the history file are malformed, they will be skipped.
+ *
+ * @throws IOException if an I/O error occurs while reading the history file
+ */
@Override
public void load() throws IOException {
Path path = getPath();
@@ -80,14 +149,31 @@ public void load() throws IOException {
try {
if (Files.exists(path)) {
Log.trace("Loading history from: ", path);
+ internalClear();
+ boolean hasErrors = false;
+
try (BufferedReader reader = Files.newBufferedReader(path)) {
- internalClear();
- reader.lines().forEach(line -> addHistoryLine(path, line));
- setHistoryFileData(path, new HistoryFileData(items.size(), offset + items.size()));
- maybeResize();
+ List lines = reader.lines().collect(java.util.stream.Collectors.toList());
+ for (String line : lines) {
+ try {
+ addHistoryLine(path, line);
+ } catch (IllegalArgumentException e) {
+ Log.debug("Skipping invalid history line: " + line, e);
+ hasErrors = true;
+ }
+ }
+ }
+
+ setHistoryFileData(path, new HistoryFileData(items.size(), offset + items.size()));
+ maybeResize();
+
+ // If we encountered errors, rewrite the history file with valid entries
+ if (hasErrors) {
+ Log.info("History file contained errors, rewriting with valid entries");
+ write(path, false);
}
}
- } catch (IllegalArgumentException | IOException e) {
+ } catch (IOException e) {
Log.debug("Failed to load history; clearing", e);
internalClear();
throw e;
@@ -95,6 +181,18 @@ public void load() throws IOException {
}
}
+ /**
+ * Reads history entries from the specified file and adds them to the current history.
+ *
+ * Unlike {@link #load()}, this method does not clear the existing history before
+ * adding entries from the file. If the file doesn't exist or can't be read,
+ * the history will be cleared. If individual lines in the history file are malformed,
+ * they will be skipped.
+ *
+ * @param file the file to read history from, or null to use the default history file
+ * @param checkDuplicates whether to check for and skip duplicate entries
+ * @throws IOException if an I/O error occurs while reading the history file
+ */
@Override
public void read(Path file, boolean checkDuplicates) throws IOException {
Path path = file != null ? file : getPath();
@@ -102,13 +200,30 @@ public void read(Path file, boolean checkDuplicates) throws IOException {
try {
if (Files.exists(path)) {
Log.trace("Reading history from: ", path);
+ boolean hasErrors = false;
+
try (BufferedReader reader = Files.newBufferedReader(path)) {
- reader.lines().forEach(line -> addHistoryLine(path, line, checkDuplicates));
- setHistoryFileData(path, new HistoryFileData(items.size(), offset + items.size()));
- maybeResize();
+ List lines = reader.lines().collect(java.util.stream.Collectors.toList());
+ for (String line : lines) {
+ try {
+ addHistoryLine(path, line, checkDuplicates);
+ } catch (IllegalArgumentException e) {
+ Log.debug("Skipping invalid history line: " + line, e);
+ hasErrors = true;
+ }
+ }
+ }
+
+ setHistoryFileData(path, new HistoryFileData(items.size(), offset + items.size()));
+ maybeResize();
+
+ // If we encountered errors, rewrite the history file with valid entries
+ if (hasErrors) {
+ Log.info("History file contained errors, rewriting with valid entries");
+ write(path, false);
}
}
- } catch (IllegalArgumentException | IOException e) {
+ } catch (IOException e) {
Log.debug("Failed to read history; clearing", e);
internalClear();
throw e;
@@ -160,10 +275,23 @@ private int getEntriesInFile(Path path) {
return getHistoryFileData(path).getEntriesInFile();
}
+ /**
+ * Adds a history line to the specified history file.
+ *
+ * @param path the path to the history file
+ * @param line the line to add
+ */
protected void addHistoryLine(Path path, String line) {
addHistoryLine(path, line, false);
}
+ /**
+ * Adds a history line to the specified history file with an option to check for duplicates.
+ *
+ * @param path the path to the history file
+ * @param line the line to add
+ * @param checkDuplicates whether to check for duplicate entries
+ */
protected void addHistoryLine(Path path, String line, boolean checkDuplicates) {
if (reader.isSet(LineReader.Option.HISTORY_TIMESTAMPED)) {
int idx = line.indexOf(':');
@@ -186,6 +314,14 @@ protected void addHistoryLine(Path path, String line, boolean checkDuplicates) {
}
}
+ /**
+ * Clears the history and deletes the history file.
+ *
+ * This method removes all history entries from memory and deletes the history file
+ * if it exists.
+ *
+ * @throws IOException if an I/O error occurs while deleting the history file
+ */
@Override
public void purge() throws IOException {
internalClear();
@@ -196,6 +332,16 @@ public void purge() throws IOException {
}
}
+ /**
+ * Writes the history to the specified file, optionally replacing the existing file.
+ *
+ * If the file exists, it will be deleted and recreated. If incremental is true,
+ * only entries that haven't been saved before will be written.
+ *
+ * @param file the file to write history to, or null to use the default history file
+ * @param incremental whether to write only new entries (true) or all entries (false)
+ * @throws IOException if an I/O error occurs while writing the history file
+ */
@Override
public void write(Path file, boolean incremental) throws IOException {
Path path = file != null ? file : getPath();
@@ -205,11 +351,31 @@ public void write(Path file, boolean incremental) throws IOException {
internalWrite(path, incremental ? getLastLoaded(path) : 0);
}
+ /**
+ * Appends history entries to the specified file.
+ *
+ * Unlike {@link #write(Path, boolean)}, this method does not delete the existing file
+ * before writing. If incremental is true, only entries that haven't been saved before
+ * will be appended.
+ *
+ * @param file the file to append history to, or null to use the default history file
+ * @param incremental whether to append only new entries (true) or all entries (false)
+ * @throws IOException if an I/O error occurs while appending to the history file
+ */
@Override
public void append(Path file, boolean incremental) throws IOException {
internalWrite(file != null ? file : getPath(), incremental ? getLastLoaded(file) : 0);
}
+ /**
+ * Saves the history to the default history file.
+ *
+ * This method appends any new history entries (those that haven't been saved before)
+ * to the history file. It's typically called when the application exits to ensure
+ * that history is preserved.
+ *
+ * @throws IOException if an I/O error occurs while saving the history file
+ */
@Override
public void save() throws IOException {
internalWrite(getPath(), getLastLoaded(getPath()));
@@ -243,21 +409,41 @@ private void internalWrite(Path path, int from) throws IOException {
setLastLoaded(path, items.size());
}
+ /**
+ * Trims the history file to the specified maximum number of entries.
+ *
+ * @param path the path to the history file
+ * @param max the maximum number of entries to keep
+ * @throws IOException if an I/O error occurs
+ */
protected void trimHistory(Path path, int max) throws IOException {
Log.trace("Trimming history path: ", path);
// Load all history entries
LinkedList allItems = new LinkedList<>();
try (BufferedReader historyFileReader = Files.newBufferedReader(path)) {
- historyFileReader.lines().forEach(l -> {
- if (reader.isSet(LineReader.Option.HISTORY_TIMESTAMPED)) {
- int idx = l.indexOf(':');
- Instant time = Instant.ofEpochMilli(Long.parseLong(l.substring(0, idx)));
- String line = unescape(l.substring(idx + 1));
- allItems.add(createEntry(allItems.size(), time, line));
- } else {
- allItems.add(createEntry(allItems.size(), Instant.now(), unescape(l)));
+ List lines = historyFileReader.lines().collect(java.util.stream.Collectors.toList());
+ for (String l : lines) {
+ try {
+ if (reader.isSet(LineReader.Option.HISTORY_TIMESTAMPED)) {
+ int idx = l.indexOf(':');
+ if (idx < 0) {
+ Log.debug("Skipping invalid history line: " + l);
+ continue;
+ }
+ try {
+ Instant time = Instant.ofEpochMilli(Long.parseLong(l.substring(0, idx)));
+ String line = unescape(l.substring(idx + 1));
+ allItems.add(createEntry(allItems.size(), time, line));
+ } catch (DateTimeException | NumberFormatException e) {
+ Log.debug("Skipping invalid history timestamp: " + l);
+ }
+ } else {
+ allItems.add(createEntry(allItems.size(), Instant.now(), unescape(l)));
+ }
+ } catch (Exception e) {
+ Log.debug("Skipping invalid history line: " + l, e);
}
- });
+ }
}
// Remove duplicates
List trimmedItems = doTrimHistory(allItems, max);
@@ -360,6 +546,25 @@ public String get(final int index) {
return items.get(idx).line();
}
+ /**
+ * Adds a new entry to the history.
+ *
+ * This method adds a new entry to the history with the specified timestamp and command text.
+ * The entry may be filtered based on various criteria such as:
+ *
+ * If history is disabled ({@link LineReader#DISABLE_HISTORY} is true)
+ * If the line starts with a space and {@link LineReader.Option#HISTORY_IGNORE_SPACE} is set
+ * If the line is a duplicate of the previous entry and {@link LineReader.Option#HISTORY_IGNORE_DUPS} is set
+ * If the line matches a pattern in {@link LineReader#HISTORY_IGNORE}
+ *
+ *
+ * If {@link LineReader.Option#HISTORY_INCREMENTAL} is set, the history will be saved
+ * to disk after adding the entry.
+ *
+ * @param time the timestamp for the new entry
+ * @param line the command text for the new entry
+ * @throws NullPointerException if time or line is null
+ */
@Override
public void add(Instant time, String line) {
Objects.requireNonNull(time);
@@ -392,6 +597,13 @@ public void add(Instant time, String line) {
}
}
+ /**
+ * Checks if a line matches any of the specified patterns.
+ *
+ * @param patterns the patterns to match against, separated by '|'
+ * @param line the line to check
+ * @return true if the line matches any of the patterns
+ */
protected boolean matchPatterns(String patterns, String line) {
if (patterns == null || patterns.isEmpty()) {
return false;
@@ -413,10 +625,23 @@ protected boolean matchPatterns(String patterns, String line) {
return line.matches(sb.toString());
}
+ /**
+ * Adds a line to the history with the specified timestamp.
+ *
+ * @param time the timestamp for the history entry
+ * @param line the line to add
+ */
protected void internalAdd(Instant time, String line) {
internalAdd(time, line, false);
}
+ /**
+ * Adds a line to the history with the specified timestamp and an option to check for duplicates.
+ *
+ * @param time the timestamp for the history entry
+ * @param line the line to add
+ * @param checkDuplicates whether to check for duplicate entries
+ */
protected void internalAdd(Instant time, String line, boolean checkDuplicates) {
Entry entry = new EntryImpl(offset + items.size(), time, line);
if (checkDuplicates) {
@@ -454,12 +679,24 @@ public void resetIndex() {
index = Math.min(index, items.size());
}
+ /**
+ * Default implementation of the {@link History.Entry} interface.
+ *
+ * This class represents a single history entry with an index, timestamp, and command text.
+ */
protected static class EntryImpl implements Entry {
private final int index;
private final Instant time;
private final String line;
+ /**
+ * Creates a new history entry with the specified index, timestamp, and line.
+ *
+ * @param index the index of the entry in the history
+ * @param time the timestamp of the entry
+ * @param line the content of the entry
+ */
public EntryImpl(int index, Instant time, String line) {
this.index = index;
this.time = time;
@@ -489,11 +726,14 @@ public String toString() {
//
/**
- * This moves the history to the last entry. This entry is one position
- * before the moveToEnd() position.
+ * Moves the history cursor to the last entry.
+ *
+ * This positions the cursor at the most recent history entry, which is one position
+ * before the position set by {@link #moveToEnd()}. This is typically used when
+ * starting to navigate backward through history.
*
- * @return Returns false if there were no history iterator or the history
- * index was already at the last entry.
+ * @return true if the cursor was moved, false if there were no history entries
+ * or the cursor was already at the last entry
*/
public boolean moveToLast() {
int lastEntry = size() - 1;
@@ -506,7 +746,13 @@ public boolean moveToLast() {
}
/**
- * Move to the specified index in the history
+ * Moves the history cursor to the specified index.
+ *
+ * This positions the cursor at the history entry with the given index, if it exists.
+ * The index is absolute, taking into account the offset of the history buffer.
+ *
+ * @param index the absolute index to move to
+ * @return true if the cursor was moved, false if the index was out of range
*/
public boolean moveTo(int index) {
index -= offset;
@@ -518,10 +764,13 @@ public boolean moveTo(int index) {
}
/**
- * Moves the history index to the first entry.
+ * Moves the history cursor to the first entry.
+ *
+ * This positions the cursor at the oldest history entry in the buffer.
+ * This is typically used when starting to navigate forward through history.
*
- * @return Return false if there are no iterator in the history or if the
- * history is already at the beginning.
+ * @return true if the cursor was moved, false if there were no history entries
+ * or the cursor was already at the first entry
*/
public boolean moveToFirst() {
if (size() > 0 && index != 0) {
@@ -532,15 +781,23 @@ public boolean moveToFirst() {
}
/**
- * Move to the end of the history buffer. This will be a blank entry, after
- * all of the other iterator.
+ * Moves the history cursor to the end of the history buffer.
+ *
+ * This positions the cursor after the last history entry, which represents
+ * the current input line (not yet in history). This is the default position
+ * when not navigating through history.
*/
public void moveToEnd() {
index = size();
}
/**
- * Return the content of the current buffer.
+ * Returns the text of the history entry at the current cursor position.
+ *
+ * If the cursor is at the end of the history (after the last entry),
+ * this method returns an empty string.
+ *
+ * @return the text of the current history entry, or an empty string if at the end
*/
public String current() {
if (index >= size()) {
@@ -550,9 +807,13 @@ public String current() {
}
/**
- * Move the pointer to the previous element in the buffer.
+ * Moves the history cursor to the previous (older) entry.
+ *
+ * This is typically called when the user presses the up arrow key to navigate
+ * backward through history. If the cursor is already at the first entry,
+ * this method does nothing and returns false.
*
- * @return true if we successfully went to the previous element
+ * @return true if the cursor was moved, false if already at the first entry
*/
public boolean previous() {
if (index <= 0) {
@@ -563,9 +824,13 @@ public boolean previous() {
}
/**
- * Move the pointer to the next element in the buffer.
+ * Moves the history cursor to the next (newer) entry.
+ *
+ * This is typically called when the user presses the down arrow key to navigate
+ * forward through history. If the cursor is already at the end of history,
+ * this method does nothing and returns false.
*
- * @return true if we successfully went to the next element
+ * @return true if the cursor was moved, false if already at the end of history
*/
public boolean next() {
if (index >= size()) {
@@ -632,6 +897,13 @@ static String unescape(String s) {
return sb.toString();
}
+ /**
+ * Helper class for tracking history file state.
+ *
+ * This class maintains information about history files, including how many entries
+ * have been loaded from the file and how many entries are currently in the file.
+ * This information is used for incremental saving and trimming of history files.
+ */
private static class HistoryFileData {
private int lastLoaded = 0;
private int entriesInFile = 0;
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/history/package-info.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/history/package-info.java
index 4976620e8a469..7e4cff71e7023 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/history/package-info.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/history/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author or authors.
+ * Copyright (c) 2002-2025, 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.
@@ -7,8 +7,27 @@
* https://opensource.org/licenses/BSD-3-Clause
*/
/**
- * JLine 3.
+ * JLine 3 History Implementation Package.
+ *
+ * This package provides implementations of the {@link org.jline.reader.History} interface
+ * for managing command history in interactive applications. Command history allows users
+ * to recall, edit, and reuse previously entered commands.
+ *
+ * Key features of the history implementation include:
+ *
+ * Persistent storage of command history in files
+ * Configurable history size limits for memory and file storage
+ * Support for timestamped history entries
+ * Filtering of history entries based on patterns
+ * Duplicate entry handling (ignore, reduce blanks, etc.)
+ * Navigation through history (previous/next, first/last, etc.)
+ *
+ *
+ * The main implementation class is {@link org.jline.reader.impl.history.DefaultHistory},
+ * which provides a file-backed history implementation with various configuration options.
*
* @since 3.0
+ * @see org.jline.reader.History
+ * @see org.jline.reader.impl.history.DefaultHistory
*/
package jdk.internal.org.jline.reader.impl.history;
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/package-info.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/package-info.java
new file mode 100644
index 0000000000000..0223d44e55550
--- /dev/null
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/package-info.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2002-2025, 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.
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+/**
+ * JLine 3 Reader Implementation Package.
+ *
+ * This package provides the core implementations of the interfaces defined in the
+ * {@link org.jline.reader} package. These implementations form the foundation of
+ * JLine's line editing and command processing capabilities.
+ *
+ * Key implementation classes include:
+ *
+ * {@link org.jline.reader.impl.LineReaderImpl} - The main implementation of the
+ * {@link org.jline.reader.LineReader} interface, providing line editing, history
+ * navigation, completion, and other interactive features
+ * {@link org.jline.reader.impl.DefaultParser} - Implementation of the
+ * {@link org.jline.reader.Parser} interface for tokenizing command lines
+ * {@link org.jline.reader.impl.DefaultHighlighter} - Implementation of the
+ * {@link org.jline.reader.Highlighter} interface for syntax highlighting
+ * {@link org.jline.reader.impl.DefaultExpander} - Implementation of the
+ * {@link org.jline.reader.Expander} interface for expanding history references and variables
+ * {@link org.jline.reader.impl.CompletionMatcherImpl} - Implementation of the
+ * {@link org.jline.reader.CompletionMatcher} interface for matching completion candidates
+ * {@link org.jline.reader.impl.BufferImpl} - Implementation of the
+ * {@link org.jline.reader.Buffer} interface for managing the line buffer
+ *
+ *
+ * This package also contains utility classes that support the main implementations:
+ *
+ * {@link org.jline.reader.impl.ReaderUtils} - Utility methods for LineReader implementations
+ * {@link org.jline.reader.impl.UndoTree} - Provides undo/redo functionality
+ * {@link org.jline.reader.impl.KillRing} - Implements a kill ring for cut/paste operations
+ * {@link org.jline.reader.impl.InputRC} - Handles inputrc configuration files
+ *
+ *
+ * Most users will not need to interact directly with these implementation classes,
+ * but instead should use the {@link org.jline.reader.LineReaderBuilder} to create
+ * properly configured instances.
+ *
+ * @since 3.0
+ * @see org.jline.reader
+ * @see org.jline.reader.LineReaderBuilder
+ */
+package jdk.internal.org.jline.reader.impl;
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/package-info.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/package-info.java
index 156f1f9619f76..88eea7fdf4d77 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/package-info.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author or authors.
+ * Copyright (c) 2002-2025, 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.
@@ -7,7 +7,33 @@
* https://opensource.org/licenses/BSD-3-Clause
*/
/**
- * JLine 3.
+ * JLine 3 Reader Package - Core components for building interactive command-line interfaces.
+ *
+ * This package provides the fundamental interfaces and classes for creating interactive
+ * command-line applications with features such as:
+ *
+ * Line editing with customizable key bindings
+ * Command history navigation
+ * Tab completion with pluggable completion strategies
+ * Customizable syntax highlighting
+ * Password masking
+ * Custom prompt rendering
+ * Command parsing and tokenization
+ *
+ *
+ * The main entry point is the {@link org.jline.reader.LineReader} interface, which can be
+ * instantiated using the {@link org.jline.reader.LineReaderBuilder}. The LineReader provides
+ * methods to read input from the user with various customization options.
+ *
+ * Key components in this package include:
+ *
+ * {@link org.jline.reader.LineReader} - The main interface for reading lines from the console
+ * {@link org.jline.reader.LineReaderBuilder} - Builder for creating LineReader instances
+ * {@link org.jline.reader.Parser} - Interface for parsing command lines into tokens
+ * {@link org.jline.reader.Completer} - Interface for providing tab-completion candidates
+ * {@link org.jline.reader.Highlighter} - Interface for syntax highlighting
+ * {@link org.jline.reader.History} - Interface for command history management
+ *
*
* @since 3.0
*/
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/Attributes.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/Attributes.java
index 04959488cf158..c9ebef9b5d822 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/Attributes.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/Attributes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -13,34 +13,152 @@
import java.util.function.Function;
import java.util.stream.Collectors;
+/**
+ * Encapsulates terminal attributes and settings that control terminal behavior.
+ *
+ *
+ * The Attributes class represents the terminal settings similar to the POSIX termios structure,
+ * providing control over terminal input/output behavior, control characters, and various flags.
+ * These attributes determine how the terminal processes input and output, handles special characters,
+ * and behaves in response to various conditions.
+ *
+ *
+ *
+ * Terminal attributes are organized into several categories:
+ *
+ *
+ * Input Flags - Control input processing (e.g., character mapping, parity checking)
+ * Output Flags - Control output processing (e.g., newline translation)
+ * Control Flags - Control hardware settings (e.g., baud rate, character size)
+ * Local Flags - Control various terminal behaviors (e.g., echo, canonical mode)
+ * Control Characters - Define special characters (e.g., EOF, interrupt, erase)
+ *
+ *
+ *
+ * Attributes objects are typically obtained from a {@link Terminal} using {@link Terminal#getAttributes()},
+ * modified as needed, and then applied back to the terminal using {@link Terminal#setAttributes(Attributes)}.
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ *
+ * // Get current attributes
+ * Attributes attrs = terminal.getAttributes();
+ *
+ * // Modify attributes
+ * attrs.setLocalFlag(LocalFlag.ECHO, false); // Disable echo
+ * attrs.setInputFlag(InputFlag.ICRNL, false); // Disable CR to NL mapping
+ * attrs.setControlChar(ControlChar.VMIN, 1); // Set minimum input to 1 character
+ *
+ * // Apply modified attributes
+ * terminal.setAttributes(attrs);
+ *
+ *
+ * @see Terminal#getAttributes()
+ * @see Terminal#setAttributes(Attributes)
+ */
public class Attributes {
/**
- * Control characters
+ * Control characters used for special terminal functions.
+ *
+ *
+ * Control characters are special characters that trigger specific terminal behaviors
+ * when encountered in the input stream. These characters control various aspects of
+ * terminal operation, such as signaling end-of-file, interrupting processes, or
+ * erasing characters.
+ *
+ *
+ *
+ * The most commonly used control characters include:
+ *
+ *
+ * {@link #VEOF} - End-of-file character (typically Ctrl+D)
+ * {@link #VINTR} - Interrupt character (typically Ctrl+C)
+ * {@link #VQUIT} - Quit character (typically Ctrl+\)
+ * {@link #VERASE} - Erase character (typically Backspace)
+ * {@link #VKILL} - Kill line character (typically Ctrl+U)
+ * {@link #VMIN} - Minimum number of characters for non-canonical read
+ * {@link #VTIME} - Timeout in deciseconds for non-canonical read
+ *
+ *
+ *
+ * Control characters can be accessed and modified using {@link #getControlChar(ControlChar)}
+ * and {@link #setControlChar(ControlChar, int)}.
+ *
+ *
+ * @see #getControlChar(ControlChar)
+ * @see #setControlChar(ControlChar, int)
*/
public enum ControlChar {
+ /** End-of-file character (typically Ctrl+D) */
VEOF,
+ /** End-of-line character */
VEOL,
+ /** Secondary end-of-line character */
VEOL2,
+ /** Erase character (typically Backspace) */
VERASE,
+ /** Word erase character (typically Ctrl+W) */
VWERASE,
+ /** Kill line character (typically Ctrl+U) */
VKILL,
+ /** Reprint line character (typically Ctrl+R) */
VREPRINT,
+ /** Interrupt character (typically Ctrl+C) */
VINTR,
+ /** Quit character (typically Ctrl+\) */
VQUIT,
+ /** Suspend character (typically Ctrl+Z) */
VSUSP,
+ /** Delayed suspend character */
VDSUSP,
+ /** Start output character (typically Ctrl+Q) */
VSTART,
+ /** Stop output character (typically Ctrl+S) */
VSTOP,
+ /** Literal next character (typically Ctrl+V) */
VLNEXT,
+ /** Discard output character (typically Ctrl+O) */
VDISCARD,
+ /** Minimum number of characters for non-canonical read */
VMIN,
+ /** Timeout in deciseconds for non-canonical read */
VTIME,
+ /** Status request character (typically Ctrl+T) */
VSTATUS
}
/**
- * Input flags - software input processing
+ * Input flags that control how terminal input is processed.
+ *
+ *
+ * Input flags determine how the terminal processes input characters before they are
+ * made available to the application. These flags control aspects such as character
+ * mapping, parity checking, and flow control for input.
+ *
+ *
+ *
+ * Common input flags include:
+ *
+ *
+ * {@link #ICRNL} - Map CR to NL on input (convert carriage returns to newlines)
+ * {@link #INLCR} - Map NL to CR on input (convert newlines to carriage returns)
+ * {@link #IGNCR} - Ignore carriage returns on input
+ * {@link #IXON} - Enable XON/XOFF flow control on output
+ * {@link #IXOFF} - Enable XON/XOFF flow control on input
+ *
+ *
+ *
+ * Input flags can be accessed and modified using methods like {@link #getInputFlag(InputFlag)},
+ * {@link #setInputFlag(InputFlag, boolean)}, and {@link #setInputFlags(EnumSet)}.
+ *
+ *
+ * @see #getInputFlag(InputFlag)
+ * @see #setInputFlag(InputFlag, boolean)
+ * @see #getInputFlags()
+ * @see #setInputFlags(EnumSet)
*/
public enum InputFlag {
IGNBRK, /* ignore BREAK condition */
@@ -61,8 +179,34 @@ public enum InputFlag {
INORMEOL /* normalize end-of-line */
}
- /*
- * Output flags - software output processing
+ /**
+ * Output flags that control how terminal output is processed.
+ *
+ *
+ * Output flags determine how the terminal processes output characters before they are
+ * sent to the terminal device. These flags control aspects such as newline translation,
+ * tab expansion, and other output processing features.
+ *
+ *
+ *
+ * Common output flags include:
+ *
+ *
+ * {@link #OPOST} - Enable output processing (required for other output flags to take effect)
+ * {@link #ONLCR} - Map NL to CR-NL on output (convert newlines to carriage return + newline)
+ * {@link #OCRNL} - Map CR to NL on output (convert carriage returns to newlines)
+ * {@link #OXTABS} - Expand tabs to spaces on output
+ *
+ *
+ *
+ * Output flags can be accessed and modified using methods like {@link #getOutputFlag(OutputFlag)},
+ * {@link #setOutputFlag(OutputFlag, boolean)}, and {@link #setOutputFlags(EnumSet)}.
+ *
+ *
+ * @see #getOutputFlag(OutputFlag)
+ * @see #setOutputFlag(OutputFlag, boolean)
+ * @see #getOutputFlags()
+ * @see #setOutputFlags(EnumSet)
*/
public enum OutputFlag {
OPOST, /* enable following output processing */
@@ -82,8 +226,34 @@ public enum OutputFlag {
OFDEL /* fill is DEL, else NUL */
}
- /*
- * Control flags - hardware control of terminal
+ /**
+ * Control flags that manage hardware aspects of the terminal.
+ *
+ *
+ * Control flags determine how the terminal hardware operates. These flags control
+ * aspects such as baud rate, character size, parity, and hardware flow control.
+ *
+ *
+ *
+ * Common control flags include:
+ *
+ *
+ * {@link #CS5}, {@link #CS6}, {@link #CS7}, {@link #CS8} - Character size (5-8 bits)
+ * {@link #CSTOPB} - Use two stop bits instead of one
+ * {@link #PARENB} - Enable parity generation and detection
+ * {@link #PARODD} - Use odd parity instead of even
+ * {@link #CLOCAL} - Ignore modem control lines
+ *
+ *
+ *
+ * Control flags can be accessed and modified using methods like {@link #getControlFlag(ControlFlag)},
+ * {@link #setControlFlag(ControlFlag, boolean)}, and {@link #setControlFlags(EnumSet)}.
+ *
+ *
+ * @see #getControlFlag(ControlFlag)
+ * @see #setControlFlag(ControlFlag, boolean)
+ * @see #getControlFlags()
+ * @see #setControlFlags(EnumSet)
*/
public enum ControlFlag {
CIGNORE, /* ignore control flags */
@@ -104,12 +274,40 @@ public enum ControlFlag {
CCAR_OFLOW /* DCD flow control of output */
}
- /*
- * "Local" flags - dumping ground for other state
+ /**
+ * Local flags that control various terminal behaviors.
+ *
+ *
+ * Local flags control a variety of terminal behaviors that don't fit into the other
+ * flag categories. These include echo control, canonical mode, signal generation,
+ * and special character processing.
+ *
+ *
+ *
+ * Common local flags include:
+ *
+ *
+ * {@link #ECHO} - Echo input characters
+ * {@link #ICANON} - Enable canonical mode (line-by-line input)
+ * {@link #ISIG} - Enable signal generation (INTR, QUIT, SUSP)
+ * {@link #IEXTEN} - Enable extended input processing
+ * {@link #ECHOCTL} - Echo control characters as ^X
+ *
+ *
+ *
+ * Note: Some flags in this category begin with the letter "I" and might appear to
+ * belong in the input flags category, but they are historically part of the local flags.
+ *
*
- * Warning: some flags in this structure begin with
- * the letter "I" and look like they belong in the
- * input flag.
+ *
+ * Local flags can be accessed and modified using methods like {@link #getLocalFlag(LocalFlag)},
+ * {@link #setLocalFlag(LocalFlag, boolean)}, and {@link #setLocalFlags(EnumSet)}.
+ *
+ *
+ * @see #getLocalFlag(LocalFlag)
+ * @see #setLocalFlag(LocalFlag, boolean)
+ * @see #getLocalFlags()
+ * @see #setLocalFlags(EnumSet)
*/
public enum LocalFlag {
ECHOKE, /* visual erase for line kill */
@@ -137,8 +335,29 @@ public enum LocalFlag {
final EnumSet lflag = EnumSet.noneOf(LocalFlag.class);
final EnumMap cchars = new EnumMap<>(ControlChar.class);
+ /**
+ * Creates a new Attributes instance with default settings.
+ *
+ *
+ * This constructor creates an Attributes object with all flags unset and
+ * all control characters undefined. The attributes can be modified using
+ * the various setter methods.
+ *
+ */
public Attributes() {}
+ /**
+ * Creates a new Attributes instance by copying another Attributes object.
+ *
+ *
+ * This constructor creates a new Attributes object with the same settings
+ * as the specified Attributes object. All flags and control characters are
+ * copied from the source object.
+ *
+ *
+ * @param attr the Attributes object to copy
+ * @see #copy(Attributes)
+ */
@SuppressWarnings("this-escape")
public Attributes(Attributes attr) {
copy(attr);
@@ -148,19 +367,70 @@ public Attributes(Attributes attr) {
// Input flags
//
+ /**
+ * Returns the set of input flags currently enabled.
+ *
+ *
+ * This method returns a reference to the internal set of input flags.
+ * Changes to the returned set will directly affect this Attributes object.
+ *
+ *
+ * @return the set of enabled input flags
+ * @see InputFlag
+ * @see #setInputFlags(EnumSet)
+ */
public EnumSet getInputFlags() {
return iflag;
}
+ /**
+ * Sets the input flags to the specified set of flags.
+ *
+ *
+ * This method replaces all current input flags with the specified set.
+ * Any previously enabled flags not in the new set will be disabled.
+ *
+ *
+ * @param flags the set of input flags to enable
+ * @see InputFlag
+ * @see #getInputFlags()
+ */
public void setInputFlags(EnumSet flags) {
iflag.clear();
iflag.addAll(flags);
}
+ /**
+ * Checks if a specific input flag is enabled.
+ *
+ *
+ * This method returns whether the specified input flag is currently enabled
+ * in this Attributes object.
+ *
+ *
+ * @param flag the input flag to check
+ * @return {@code true} if the flag is enabled, {@code false} otherwise
+ * @see InputFlag
+ * @see #setInputFlag(InputFlag, boolean)
+ */
public boolean getInputFlag(InputFlag flag) {
return iflag.contains(flag);
}
+ /**
+ * Sets multiple input flags to the same value.
+ *
+ *
+ * This method enables or disables all the specified input flags based on the
+ * value parameter. If value is true, all flags in the set will be enabled.
+ * If value is false, all flags in the set will be disabled.
+ *
+ *
+ * @param flags the set of input flags to modify
+ * @param value {@code true} to enable the flags, {@code false} to disable them
+ * @see InputFlag
+ * @see #setInputFlag(InputFlag, boolean)
+ */
public void setInputFlags(EnumSet flags, boolean value) {
if (value) {
iflag.addAll(flags);
@@ -169,6 +439,19 @@ public void setInputFlags(EnumSet flags, boolean value) {
}
}
+ /**
+ * Sets a specific input flag to the specified value.
+ *
+ *
+ * This method enables or disables a single input flag based on the value parameter.
+ * If value is true, the flag will be enabled. If value is false, the flag will be disabled.
+ *
+ *
+ * @param flag the input flag to modify
+ * @param value {@code true} to enable the flag, {@code false} to disable it
+ * @see InputFlag
+ * @see #getInputFlag(InputFlag)
+ */
public void setInputFlag(InputFlag flag, boolean value) {
if (value) {
iflag.add(flag);
@@ -280,20 +563,86 @@ public void setLocalFlag(LocalFlag flag, boolean value) {
// Control chars
//
+ /**
+ * Returns the map of control characters and their values.
+ *
+ *
+ * This method returns a reference to the internal map of control characters.
+ * Changes to the returned map will directly affect this Attributes object.
+ *
+ *
+ * @return the map of control characters to their values
+ * @see ControlChar
+ * @see #setControlChars(EnumMap)
+ */
public EnumMap getControlChars() {
return cchars;
}
+ /**
+ * Sets the control characters to the specified map of values.
+ *
+ *
+ * This method replaces all current control character settings with the
+ * specified map. Any previously set control characters not in the new map
+ * will be unset.
+ *
+ *
+ * @param chars the map of control characters to their values
+ * @see ControlChar
+ * @see #getControlChars()
+ */
public void setControlChars(EnumMap chars) {
cchars.clear();
cchars.putAll(chars);
}
+ /**
+ * Returns the value of a specific control character.
+ *
+ *
+ * This method returns the current value of the specified control character,
+ * or -1 if the control character is not defined.
+ *
+ *
+ *
+ * For most control characters, the value represents the ASCII code of the
+ * character. For {@link ControlChar#VMIN} and {@link ControlChar#VTIME},
+ * the values have special meanings related to non-canonical input mode.
+ *
+ *
+ * @param c the control character to retrieve
+ * @return the value of the control character, or -1 if not defined
+ * @see ControlChar
+ * @see #setControlChar(ControlChar, int)
+ */
public int getControlChar(ControlChar c) {
Integer v = cchars.get(c);
return v != null ? v : -1;
}
+ /**
+ * Sets a specific control character to the specified value.
+ *
+ *
+ * This method sets the value of the specified control character.
+ *
+ *
+ *
+ * For most control characters, the value should be the ASCII code of the
+ * character. For {@link ControlChar#VMIN} and {@link ControlChar#VTIME},
+ * the values have special meanings:
+ *
+ *
+ * VMIN - Minimum number of characters for non-canonical read
+ * VTIME - Timeout in deciseconds for non-canonical read
+ *
+ *
+ * @param c the control character to set
+ * @param value the value to set for the control character
+ * @see ControlChar
+ * @see #getControlChar(ControlChar)
+ */
public void setControlChar(ControlChar c, int value) {
cchars.put(c, value);
}
@@ -302,6 +651,17 @@ public void setControlChar(ControlChar c, int value) {
// Miscellaneous methods
//
+ /**
+ * Copies all settings from another Attributes object to this one.
+ *
+ *
+ * This method copies all flags and control characters from the specified
+ * Attributes object to this object. Any previous settings in this object
+ * will be overwritten.
+ *
+ *
+ * @param attributes the Attributes object to copy from
+ */
public void copy(Attributes attributes) {
setControlFlags(attributes.getControlFlags());
setInputFlags(attributes.getInputFlags());
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/Cursor.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/Cursor.java
index 9bc3d902849ed..24ccb5b6b58f5 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/Cursor.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/Cursor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -9,7 +9,45 @@
package jdk.internal.org.jline.terminal;
/**
- * Class holding the cursor position.
+ * Represents the position of the cursor within a terminal.
+ *
+ *
+ * The Cursor class encapsulates the coordinates of the cursor in a terminal, providing
+ * access to its X (column) and Y (row) position. Cursor positions are used for various
+ * terminal operations such as text insertion, deletion, and formatting.
+ *
+ *
+ *
+ * In terminal coordinates:
+ *
+ *
+ * X coordinate - Represents the column position (horizontal), typically 0-based
+ * Y coordinate - Represents the row position (vertical), typically 0-based
+ *
+ *
+ *
+ * Cursor objects are typically obtained from a {@link Terminal} using the
+ * {@link Terminal#getCursorPosition(java.util.function.IntConsumer)} method, which queries
+ * the terminal for its current cursor position. This information can be used to determine
+ * where text will be inserted or to calculate relative positions for cursor movement.
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ *
+ * // Get current cursor position
+ * Cursor cursor = terminal.getCursorPosition(c -> {});
+ * if (cursor != null) {
+ * System.out.println("Cursor position: column=" + cursor.getX() + ", row=" + cursor.getY());
+ * }
+ *
+ *
+ *
+ * Note that not all terminals support cursor position reporting. The
+ * {@link Terminal#getCursorPosition(java.util.function.IntConsumer)} method may return
+ * {@code null} if cursor position reporting is not supported.
+ *
*
* @see Terminal#getCursorPosition(java.util.function.IntConsumer)
*/
@@ -18,19 +56,63 @@ public class Cursor {
private final int x;
private final int y;
+ /**
+ * Creates a new Cursor instance at the specified coordinates.
+ *
+ *
+ * This constructor creates a Cursor object representing a position in the terminal
+ * at the given column (x) and row (y) coordinates. In terminal coordinates, the
+ * origin (0,0) is typically at the top-left corner of the screen.
+ *
+ *
+ * @param x the column position (horizontal coordinate)
+ * @param y the row position (vertical coordinate)
+ */
public Cursor(int x, int y) {
this.x = x;
this.y = y;
}
+ /**
+ * Returns the column position (horizontal coordinate) of this cursor.
+ *
+ *
+ * The X coordinate represents the horizontal position of the cursor in the terminal,
+ * measured in character cells from the left edge of the terminal. The leftmost column
+ * is typically position 0.
+ *
+ *
+ * @return the column position (X coordinate)
+ */
public int getX() {
return x;
}
+ /**
+ * Returns the row position (vertical coordinate) of this cursor.
+ *
+ *
+ * The Y coordinate represents the vertical position of the cursor in the terminal,
+ * measured in character cells from the top edge of the terminal. The topmost row
+ * is typically position 0.
+ *
+ *
+ * @return the row position (Y coordinate)
+ */
public int getY() {
return y;
}
+ /**
+ * Compares this Cursor object with another object for equality.
+ *
+ *
+ * Two Cursor objects are considered equal if they have the same X and Y coordinates.
+ *
+ *
+ * @param o the object to compare with
+ * @return {@code true} if the objects are equal, {@code false} otherwise
+ */
@Override
public boolean equals(Object o) {
if (o instanceof Cursor) {
@@ -41,11 +123,29 @@ public boolean equals(Object o) {
}
}
+ /**
+ * Returns a hash code for this Cursor object.
+ *
+ *
+ * The hash code is computed based on the X and Y coordinates.
+ *
+ *
+ * @return a hash code value for this object
+ */
@Override
public int hashCode() {
return x * 31 + y;
}
+ /**
+ * Returns a string representation of this Cursor object.
+ *
+ *
+ * The string representation includes the X and Y coordinates.
+ *
+ *
+ * @return a string representation of this object
+ */
@Override
public String toString() {
return "Cursor[" + "x=" + x + ", y=" + y + ']';
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/KeyEvent.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/KeyEvent.java
new file mode 100644
index 0000000000000..6a11b067f0f09
--- /dev/null
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/KeyEvent.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) the original author(s).
+ *
+ * This software is distributable under the BSD license. See the terms of the
+ * BSD license in the documentation provided with this software.
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+package jdk.internal.org.jline.terminal;
+
+import java.util.EnumSet;
+
+/**
+ * Represents a keyboard event in a terminal.
+ *
+ *
+ * The KeyEvent class encapsulates information about keyboard actions in a terminal,
+ * including the type of key pressed, any modifier keys that were held, and the
+ * raw sequence that was received from the terminal.
+ *
+ *
+ *
+ * Key events include:
+ *
+ *
+ * Character - A printable character was typed
+ * Arrow - An arrow key was pressed (Up, Down, Left, Right)
+ * Function - A function key was pressed (F1-F12)
+ * Special - A special key was pressed (Enter, Tab, Escape, etc.)
+ * Unknown - An unrecognized key sequence
+ *
+ */
+public class KeyEvent {
+
+ /**
+ * Defines the types of key events that can occur.
+ */
+ public enum Type {
+ /**
+ * A printable character was typed.
+ */
+ Character,
+
+ /**
+ * An arrow key was pressed.
+ */
+ Arrow,
+
+ /**
+ * A function key was pressed (F1-F12).
+ */
+ Function,
+
+ /**
+ * A special key was pressed (Enter, Tab, Escape, etc.).
+ */
+ Special,
+
+ /**
+ * An unrecognized key sequence.
+ */
+ Unknown
+ }
+
+ /**
+ * Defines arrow key directions.
+ */
+ public enum Arrow {
+ Up,
+ Down,
+ Left,
+ Right
+ }
+
+ /**
+ * Defines special keys.
+ */
+ public enum Special {
+ Enter,
+ Tab,
+ Escape,
+ Backspace,
+ Delete,
+ Home,
+ End,
+ PageUp,
+ PageDown,
+ Insert
+ }
+
+ /**
+ * Defines modifier keys that can be held during a key event.
+ */
+ public enum Modifier {
+ /**
+ * The Shift key was held.
+ */
+ Shift,
+
+ /**
+ * The Alt key was held.
+ */
+ Alt,
+
+ /**
+ * The Control key was held.
+ */
+ Control
+ }
+
+ private final Type type;
+ private final char character;
+ private final Arrow arrow;
+ private final Special special;
+ private final int functionKey;
+ private final EnumSet modifiers;
+ private final String rawSequence;
+
+ /**
+ * Creates a character key event.
+ */
+ public KeyEvent(char character, EnumSet modifiers, String rawSequence) {
+ this.type = Type.Character;
+ this.character = character;
+ this.arrow = null;
+ this.special = null;
+ this.functionKey = 0;
+ this.modifiers = modifiers;
+ this.rawSequence = rawSequence;
+ }
+
+ /**
+ * Creates an arrow key event.
+ */
+ public KeyEvent(Arrow arrow, EnumSet modifiers, String rawSequence) {
+ this.type = Type.Arrow;
+ this.character = '\0';
+ this.arrow = arrow;
+ this.special = null;
+ this.functionKey = 0;
+ this.modifiers = modifiers;
+ this.rawSequence = rawSequence;
+ }
+
+ /**
+ * Creates a special key event.
+ */
+ public KeyEvent(Special special, EnumSet modifiers, String rawSequence) {
+ this.type = Type.Special;
+ this.character = '\0';
+ this.arrow = null;
+ this.special = special;
+ this.functionKey = 0;
+ this.modifiers = modifiers;
+ this.rawSequence = rawSequence;
+ }
+
+ /**
+ * Creates a function key event.
+ */
+ public KeyEvent(int functionKey, EnumSet modifiers, String rawSequence) {
+ this.type = Type.Function;
+ this.character = '\0';
+ this.arrow = null;
+ this.special = null;
+ this.functionKey = functionKey;
+ this.modifiers = modifiers;
+ this.rawSequence = rawSequence;
+ }
+
+ /**
+ * Creates an unknown key event.
+ */
+ public KeyEvent(String rawSequence) {
+ this.type = Type.Unknown;
+ this.character = '\0';
+ this.arrow = null;
+ this.special = null;
+ this.functionKey = 0;
+ this.modifiers = EnumSet.noneOf(Modifier.class);
+ this.rawSequence = rawSequence;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public char getCharacter() {
+ return character;
+ }
+
+ public Arrow getArrow() {
+ return arrow;
+ }
+
+ public Special getSpecial() {
+ return special;
+ }
+
+ public int getFunctionKey() {
+ return functionKey;
+ }
+
+ public EnumSet getModifiers() {
+ return modifiers;
+ }
+
+ public String getRawSequence() {
+ return rawSequence;
+ }
+
+ public boolean hasModifier(Modifier modifier) {
+ return modifiers.contains(modifier);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("KeyEvent[type=").append(type);
+ switch (type) {
+ case Character:
+ sb.append(", character='").append(character).append("'");
+ break;
+ case Arrow:
+ sb.append(", arrow=").append(arrow);
+ break;
+ case Special:
+ sb.append(", special=").append(special);
+ break;
+ case Function:
+ sb.append(", function=F").append(functionKey);
+ break;
+ case Unknown:
+ sb.append(", unknown");
+ break;
+ }
+ if (!modifiers.isEmpty()) {
+ sb.append(", modifiers=").append(modifiers);
+ }
+ sb.append(", raw='").append(rawSequence).append("']");
+ return sb.toString();
+ }
+}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/KeyParser.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/KeyParser.java
new file mode 100644
index 0000000000000..ec2167a21377a
--- /dev/null
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/KeyParser.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (c) the original author(s).
+ *
+ * This software is distributable under the BSD license. See the terms of the
+ * BSD license in the documentation provided with this software.
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+package jdk.internal.org.jline.terminal;
+
+import java.util.EnumSet;
+
+/**
+ * Utility class for parsing raw terminal input sequences into KeyEvent objects.
+ */
+public class KeyParser {
+
+ private KeyParser() {}
+
+ /**
+ * Parses a raw input sequence into a KeyEvent.
+ *
+ * @param rawSequence the raw input sequence from the terminal
+ * @return a KeyEvent representing the parsed input
+ */
+ public static KeyEvent parse(String rawSequence) {
+ if (rawSequence == null || rawSequence.isEmpty()) {
+ return new KeyEvent(rawSequence);
+ }
+
+ // Handle escape sequences
+ if (rawSequence.startsWith("\u001b")) {
+ return parseEscapeSequence(rawSequence);
+ }
+
+ // Handle control characters
+ if (rawSequence.length() == 1) {
+ char ch = rawSequence.charAt(0);
+
+ // Control characters (0x00-0x1F)
+ if (ch >= 0 && ch <= 31) {
+ return parseControlCharacter(ch, rawSequence);
+ }
+
+ // Regular printable character
+ if (ch >= 32 && ch <= 126) {
+ return new KeyEvent(ch, EnumSet.noneOf(KeyEvent.Modifier.class), rawSequence);
+ }
+
+ // Extended ASCII or Unicode
+ if (ch > 126) {
+ return new KeyEvent(ch, EnumSet.noneOf(KeyEvent.Modifier.class), rawSequence);
+ }
+ }
+
+ // Multi-character sequence that's not an escape sequence
+ return new KeyEvent(rawSequence);
+ }
+
+ private static KeyEvent parseEscapeSequence(String sequence) {
+ if (sequence.length() < 2) {
+ return new KeyEvent(KeyEvent.Special.Escape, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ }
+
+ // Alt+character sequences (ESC followed by a character)
+ if (sequence.length() == 2) {
+ char ch = sequence.charAt(1);
+ EnumSet modifiers = EnumSet.of(KeyEvent.Modifier.Alt);
+
+ if (ch >= 32 && ch <= 126) {
+ return new KeyEvent(ch, modifiers, sequence);
+ }
+ }
+
+ // ANSI escape sequences
+ if (sequence.startsWith("\u001b[")) {
+ return parseAnsiSequence(sequence);
+ }
+
+ // SS3 escape sequences (ESC O)
+ if (sequence.startsWith("\u001bO")) {
+ return parseSS3Sequence(sequence);
+ }
+
+ // Other escape sequences
+ return new KeyEvent(sequence);
+ }
+
+ private static KeyEvent parseAnsiSequence(String sequence) {
+ // Common ANSI sequences
+ switch (sequence) {
+ // Arrow keys
+ case "\u001b[A":
+ return new KeyEvent(KeyEvent.Arrow.Up, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001b[B":
+ return new KeyEvent(KeyEvent.Arrow.Down, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001b[C":
+ return new KeyEvent(KeyEvent.Arrow.Right, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001b[D":
+ return new KeyEvent(KeyEvent.Arrow.Left, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+
+ // Function keys
+ case "\u001b[11~":
+ case "\u001bOP":
+ return new KeyEvent(1, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001b[12~":
+ case "\u001bOQ":
+ return new KeyEvent(2, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001b[13~":
+ case "\u001bOR":
+ return new KeyEvent(3, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001b[14~":
+ case "\u001bOS":
+ return new KeyEvent(4, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001b[15~":
+ return new KeyEvent(5, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001b[17~":
+ return new KeyEvent(6, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001b[18~":
+ return new KeyEvent(7, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001b[19~":
+ return new KeyEvent(8, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001b[20~":
+ return new KeyEvent(9, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001b[21~":
+ return new KeyEvent(10, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001b[23~":
+ return new KeyEvent(11, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001b[24~":
+ return new KeyEvent(12, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+
+ // Special keys
+ case "\u001b[H":
+ return new KeyEvent(KeyEvent.Special.Home, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001b[F":
+ return new KeyEvent(KeyEvent.Special.End, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001b[2~":
+ return new KeyEvent(KeyEvent.Special.Insert, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001b[3~":
+ return new KeyEvent(KeyEvent.Special.Delete, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001b[5~":
+ return new KeyEvent(KeyEvent.Special.PageUp, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001b[6~":
+ return new KeyEvent(KeyEvent.Special.PageDown, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+
+ // Backtab (Shift+Tab)
+ case "\u001b[Z":
+ return new KeyEvent(KeyEvent.Special.Tab, EnumSet.of(KeyEvent.Modifier.Shift), sequence);
+
+ default:
+ // Try to parse modified keys (with Shift, Alt, Ctrl)
+ return parseModifiedAnsiSequence(sequence);
+ }
+ }
+
+ private static KeyEvent parseSS3Sequence(String sequence) {
+ // SS3 sequences (ESC O)
+ switch (sequence) {
+ // Function keys
+ case "\u001bOP":
+ return new KeyEvent(1, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001bOQ":
+ return new KeyEvent(2, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001bOR":
+ return new KeyEvent(3, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case "\u001bOS":
+ return new KeyEvent(4, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ default:
+ return new KeyEvent(sequence);
+ }
+ }
+
+ private static KeyEvent parseModifiedAnsiSequence(String sequence) {
+ // Pattern: \E[1;modifiers{A,B,C,D} for modified arrow keys
+ // Pattern: \E[{number};modifiers~ for modified special keys
+
+ // Modified arrow keys: \E[1;{mod}{A,B,C,D}
+ if (sequence.matches("\\u001b\\[1;[2-8][ABCD]")) {
+ int modCode = Character.getNumericValue(sequence.charAt(4));
+ char arrowChar = sequence.charAt(5);
+
+ EnumSet modifiers = parseModifierCode(modCode);
+ KeyEvent.Arrow arrow = parseArrowChar(arrowChar);
+
+ if (arrow != null) {
+ return new KeyEvent(arrow, modifiers, sequence);
+ }
+ }
+
+ // Modified function keys: \E[{fn};{mod}~
+ if (sequence.matches("\\u001b\\[[0-9]+;[2-8]~")) {
+ String[] parts = sequence.substring(2, sequence.length() - 1).split(";");
+ if (parts.length == 2) {
+ try {
+ int fnNum = Integer.parseInt(parts[0]);
+ int modCode = Integer.parseInt(parts[1]);
+
+ EnumSet modifiers = parseModifierCode(modCode);
+ int functionKey = mapFunctionKeyNumber(fnNum);
+
+ if (functionKey > 0) {
+ return new KeyEvent(functionKey, modifiers, sequence);
+ }
+ } catch (NumberFormatException e) {
+ // Fall through to unknown
+ }
+ }
+ }
+
+ // Modified special keys: \E[{special};{mod}~
+ if (sequence.matches("\\u001b\\[[2-6];[2-8]~")) {
+ String[] parts = sequence.substring(2, sequence.length() - 1).split(";");
+ if (parts.length == 2) {
+ try {
+ int specialCode = Integer.parseInt(parts[0]);
+ int modCode = Integer.parseInt(parts[1]);
+
+ EnumSet modifiers = parseModifierCode(modCode);
+ KeyEvent.Special special = mapSpecialKeyCode(specialCode);
+
+ if (special != null) {
+ return new KeyEvent(special, modifiers, sequence);
+ }
+ } catch (NumberFormatException e) {
+ // Fall through to unknown
+ }
+ }
+ }
+
+ return new KeyEvent(sequence);
+ }
+
+ private static EnumSet parseModifierCode(int modCode) {
+ EnumSet modifiers = EnumSet.noneOf(KeyEvent.Modifier.class);
+
+ // Modifier codes: 2=Shift, 3=Alt, 4=Shift+Alt, 5=Ctrl, 6=Shift+Ctrl, 7=Alt+Ctrl, 8=Shift+Alt+Ctrl
+ // The encoding is: 1 + (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0)
+ int mod = modCode - 1; // Remove base offset
+
+ if ((mod & 1) != 0) { // Shift bit
+ modifiers.add(KeyEvent.Modifier.Shift);
+ }
+ if ((mod & 2) != 0) { // Alt bit
+ modifiers.add(KeyEvent.Modifier.Alt);
+ }
+ if ((mod & 4) != 0) { // Ctrl bit
+ modifiers.add(KeyEvent.Modifier.Control);
+ }
+
+ return modifiers;
+ }
+
+ private static KeyEvent.Arrow parseArrowChar(char arrowChar) {
+ switch (arrowChar) {
+ case 'A':
+ return KeyEvent.Arrow.Up;
+ case 'B':
+ return KeyEvent.Arrow.Down;
+ case 'C':
+ return KeyEvent.Arrow.Right;
+ case 'D':
+ return KeyEvent.Arrow.Left;
+ default:
+ return null;
+ }
+ }
+
+ private static int mapFunctionKeyNumber(int fnNum) {
+ // Map ANSI function key numbers to F1-F12
+ switch (fnNum) {
+ case 11:
+ return 1; // F1
+ case 12:
+ return 2; // F2
+ case 13:
+ return 3; // F3
+ case 14:
+ return 4; // F4
+ case 15:
+ return 5; // F5
+ case 17:
+ return 6; // F6
+ case 18:
+ return 7; // F7
+ case 19:
+ return 8; // F8
+ case 20:
+ return 9; // F9
+ case 21:
+ return 10; // F10
+ case 23:
+ return 11; // F11
+ case 24:
+ return 12; // F12
+ default:
+ return 0;
+ }
+ }
+
+ private static KeyEvent.Special mapSpecialKeyCode(int specialCode) {
+ switch (specialCode) {
+ case 2:
+ return KeyEvent.Special.Insert;
+ case 3:
+ return KeyEvent.Special.Delete;
+ case 5:
+ return KeyEvent.Special.PageUp;
+ case 6:
+ return KeyEvent.Special.PageDown;
+ default:
+ return null;
+ }
+ }
+
+ private static KeyEvent parseControlCharacter(char ch, String sequence) {
+ switch (ch) {
+ case '\t':
+ return new KeyEvent(KeyEvent.Special.Tab, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case '\r':
+ case '\n':
+ return new KeyEvent(KeyEvent.Special.Enter, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case '\u001b':
+ return new KeyEvent(KeyEvent.Special.Escape, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ case '\b':
+ case '\u007f':
+ return new KeyEvent(KeyEvent.Special.Backspace, EnumSet.noneOf(KeyEvent.Modifier.class), sequence);
+ default:
+ // Other control characters - could be Ctrl+letter combinations
+ if (ch >= 1 && ch <= 26) {
+ // Ctrl+A through Ctrl+Z
+ char letter = (char) ('a' + ch - 1);
+ return new KeyEvent(letter, EnumSet.of(KeyEvent.Modifier.Control), sequence);
+ }
+ return new KeyEvent(sequence);
+ }
+ }
+}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/MouseEvent.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/MouseEvent.java
index 059b43c51ae07..cdde331976001 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/MouseEvent.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/MouseEvent.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -10,28 +10,136 @@
import java.util.EnumSet;
+/**
+ * Represents a mouse event in a terminal that supports mouse tracking.
+ *
+ *
+ * The MouseEvent class encapsulates information about mouse actions in a terminal,
+ * including the type of event (press, release, move, etc.), which button was involved,
+ * any modifier keys that were pressed, and the coordinates where the event occurred.
+ *
+ *
+ *
+ * Mouse events are only available in terminals that support mouse tracking, which can be
+ * enabled using {@link Terminal#trackMouse(Terminal.MouseTracking)}. Once mouse tracking
+ * is enabled, mouse events can be read using {@link Terminal#readMouseEvent()}.
+ *
+ *
+ *
+ * Mouse events include:
+ *
+ *
+ * Pressed - A mouse button was pressed
+ * Released - A mouse button was released
+ * Moved - The mouse was moved without any buttons pressed
+ * Dragged - The mouse was moved with a button pressed
+ * Wheel - The mouse wheel was scrolled
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ *
+ * // Enable mouse tracking
+ * if (terminal.hasMouseSupport()) {
+ * terminal.trackMouse(Terminal.MouseTracking.Normal);
+ *
+ * // Read mouse events
+ * MouseEvent event = terminal.readMouseEvent();
+ * System.out.println("Mouse event: type=" + event.getType() +
+ * ", button=" + event.getButton() +
+ * ", position=" + event.getX() + "," + event.getY());
+ * }
+ *
+ *
+ * @see Terminal#hasMouseSupport()
+ * @see Terminal#trackMouse(Terminal.MouseTracking)
+ * @see Terminal#readMouseEvent()
+ */
public class MouseEvent {
+ /**
+ * Defines the types of mouse events that can occur.
+ */
public enum Type {
+ /**
+ * A mouse button was released.
+ */
Released,
+
+ /**
+ * A mouse button was pressed.
+ */
Pressed,
+
+ /**
+ * The mouse wheel was scrolled.
+ */
Wheel,
+
+ /**
+ * The mouse was moved without any buttons pressed.
+ */
Moved,
+
+ /**
+ * The mouse was moved with a button pressed (drag operation).
+ */
Dragged
}
+ /**
+ * Defines the mouse buttons that can be involved in a mouse event.
+ */
public enum Button {
+ /**
+ * No specific button is involved (used for move events).
+ */
NoButton,
+
+ /**
+ * The primary mouse button (usually the left button).
+ */
Button1,
+
+ /**
+ * The middle mouse button.
+ */
Button2,
+
+ /**
+ * The secondary mouse button (usually the right button).
+ */
Button3,
+
+ /**
+ * The mouse wheel was scrolled upward.
+ */
WheelUp,
+
+ /**
+ * The mouse wheel was scrolled downward.
+ */
WheelDown
}
+ /**
+ * Defines the modifier keys that can be pressed during a mouse event.
+ */
public enum Modifier {
+ /**
+ * The Shift key was pressed during the mouse event.
+ */
Shift,
+
+ /**
+ * The Alt key was pressed during the mouse event.
+ */
Alt,
+
+ /**
+ * The Control key was pressed during the mouse event.
+ */
Control
}
@@ -41,6 +149,15 @@ public enum Modifier {
private final int x;
private final int y;
+ /**
+ * Creates a new MouseEvent with the specified parameters.
+ *
+ * @param type the type of mouse event (press, release, etc.)
+ * @param button the button involved in the event
+ * @param modifiers the modifier keys pressed during the event
+ * @param x the column (horizontal) position of the event
+ * @param y the row (vertical) position of the event
+ */
public MouseEvent(Type type, Button button, EnumSet modifiers, int x, int y) {
this.type = type;
this.button = button;
@@ -49,26 +166,66 @@ public MouseEvent(Type type, Button button, EnumSet modifiers, int x,
this.y = y;
}
+ /**
+ * Returns the type of this mouse event.
+ *
+ * @return the event type (press, release, move, etc.)
+ */
public Type getType() {
return type;
}
+ /**
+ * Returns the button involved in this mouse event.
+ *
+ * @return the mouse button
+ */
public Button getButton() {
return button;
}
+ /**
+ * Returns the set of modifier keys pressed during this mouse event.
+ *
+ * @return the set of modifier keys (Shift, Alt, Control)
+ */
public EnumSet getModifiers() {
return modifiers;
}
+ /**
+ * Returns the column (horizontal) position of this mouse event.
+ *
+ * @return the X coordinate (column)
+ */
public int getX() {
return x;
}
+ /**
+ * Returns the row (vertical) position of this mouse event.
+ *
+ * @return the Y coordinate (row)
+ */
public int getY() {
return y;
}
+ /**
+ * Returns a string representation of this MouseEvent object.
+ *
+ *
+ * The string representation includes all properties of the mouse event:
+ * the event type, button, modifier keys, and coordinates.
+ *
+ *
+ * Example output:
+ *
+ * MouseEvent[type=Pressed, button=Button1, modifiers=[Shift], x=10, y=20]
+ *
+ *
+ * @return a string representation of this object
+ */
@Override
public String toString() {
return "MouseEvent[" + "type="
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/Size.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/Size.java
index fde3bf35959ed..ff691535f09c2 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/Size.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/Size.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -8,13 +8,70 @@
*/
package jdk.internal.org.jline.terminal;
+/**
+ * Represents the dimensions of a terminal in terms of rows and columns.
+ *
+ *
+ * The Size class encapsulates the dimensions of a terminal screen, providing methods to get and set
+ * the number of rows and columns. Terminal dimensions are used for various operations such as
+ * cursor positioning, screen clearing, and text layout calculations.
+ *
+ *
+ *
+ * Terminal dimensions are typically measured in character cells, where:
+ *
+ *
+ * Columns - The number of character cells in each row (width)
+ * Rows - The number of character cells in each column (height)
+ *
+ *
+ *
+ * Size objects are typically obtained from a {@link Terminal} using {@link Terminal#getSize()},
+ * and can be used to adjust display formatting or to set the terminal size using
+ * {@link Terminal#setSize(Size)}.
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ *
+ * // Get current terminal size
+ * Size size = terminal.getSize();
+ * System.out.println("Terminal dimensions: " + size.getColumns() + "x" + size.getRows());
+ *
+ * // Create a new size and set it
+ * Size newSize = new Size(80, 24);
+ * terminal.setSize(newSize);
+ *
+ *
+ * @see Terminal#getSize()
+ * @see Terminal#setSize(Size)
+ */
public class Size {
private int rows;
private int cols;
+ /**
+ * Creates a new Size instance with default dimensions (0 rows and 0 columns).
+ *
+ *
+ * This constructor creates a Size object with zero dimensions. The dimensions
+ * can be set later using {@link #setRows(int)} and {@link #setColumns(int)}.
+ *
+ */
public Size() {}
+ /**
+ * Creates a new Size instance with the specified dimensions.
+ *
+ *
+ * This constructor creates a Size object with the specified number of columns and rows.
+ *
+ *
+ * @param columns the number of columns (width)
+ * @param rows the number of rows (height)
+ */
@SuppressWarnings("this-escape")
public Size(int columns, int rows) {
this();
@@ -22,18 +79,58 @@ public Size(int columns, int rows) {
setRows(rows);
}
+ /**
+ * Returns the number of columns (width) in this terminal size.
+ *
+ *
+ * The number of columns represents the width of the terminal in character cells.
+ *
+ *
+ * @return the number of columns
+ * @see #setColumns(int)
+ */
public int getColumns() {
return cols;
}
+ /**
+ * Sets the number of columns (width) for this terminal size.
+ *
+ *
+ * The number of columns represents the width of the terminal in character cells.
+ *
+ *
+ * @param columns the number of columns to set
+ * @see #getColumns()
+ */
public void setColumns(int columns) {
cols = (short) columns;
}
+ /**
+ * Returns the number of rows (height) in this terminal size.
+ *
+ *
+ * The number of rows represents the height of the terminal in character cells.
+ *
+ *
+ * @return the number of rows
+ * @see #setRows(int)
+ */
public int getRows() {
return rows;
}
+ /**
+ * Sets the number of rows (height) for this terminal size.
+ *
+ *
+ * The number of rows represents the height of the terminal in character cells.
+ *
+ *
+ * @param rows the number of rows to set
+ * @see #getRows()
+ */
public void setRows(int rows) {
this.rows = (short) rows;
}
@@ -53,11 +150,32 @@ public int cursorPos(int row, int col) {
return row * (cols + 1) + col;
}
+ /**
+ * Copies the dimensions from another Size object to this one.
+ *
+ *
+ * This method updates this Size object to have the same dimensions
+ * (rows and columns) as the specified Size object.
+ *
+ *
+ * @param size the Size object to copy dimensions from
+ */
public void copy(Size size) {
setColumns(size.getColumns());
setRows(size.getRows());
}
+ /**
+ * Compares this Size object with another object for equality.
+ *
+ *
+ * Two Size objects are considered equal if they have the same number of
+ * rows and columns.
+ *
+ *
+ * @param o the object to compare with
+ * @return {@code true} if the objects are equal, {@code false} otherwise
+ */
@Override
public boolean equals(Object o) {
if (o instanceof Size) {
@@ -68,11 +186,29 @@ public boolean equals(Object o) {
}
}
+ /**
+ * Returns a hash code for this Size object.
+ *
+ *
+ * The hash code is computed based on the rows and columns values.
+ *
+ *
+ * @return a hash code value for this object
+ */
@Override
public int hashCode() {
return rows * 31 + cols;
}
+ /**
+ * Returns a string representation of this Size object.
+ *
+ *
+ * The string representation includes the number of columns and rows.
+ *
+ *
+ * @return a string representation of this object
+ */
@Override
public String toString() {
return "Size[" + "cols=" + cols + ", rows=" + rows + ']';
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/Terminal.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/Terminal.java
index 054915e133d2a..8d4a4dd8a03b7 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/Terminal.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/Terminal.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -25,84 +25,341 @@
/**
* A terminal representing a virtual terminal on the computer.
*
- * Terminals should be closed by calling the {@link #close()} method
- * in order to restore their original state.
+ * The Terminal interface is the central abstraction in JLine, providing access to the terminal's
+ * capabilities, input/output streams, and control functions. It abstracts the differences between
+ * various terminal types and operating systems, allowing applications to work consistently across
+ * environments.
+ *
+ * Terminal Capabilities
+ * Terminals provide access to their capabilities through the {@link #getStringCapability(Capability)},
+ * {@link #getBooleanCapability(Capability)}, and {@link #getNumericCapability(Capability)} methods.
+ * These capabilities represent the terminal's features and are defined in the terminfo database.
+ *
+ * Input and Output
+ * Terminal input can be read using the {@link #reader()} method, which returns a non-blocking reader.
+ * Output can be written using the {@link #writer()} method, which returns a print writer. For raw access
+ * to the underlying streams, use {@link #input()} and {@link #output()}.
+ *
+ * Terminal Attributes
+ * Terminal attributes control the behavior of the terminal, such as echo mode, canonical mode, etc.
+ * These can be accessed and modified using {@link #getAttributes()} and {@link #setAttributes(Attributes)}.
+ *
+ * Signal Handling
+ * Terminals can handle various signals, such as CTRL+C (INT), CTRL+\ (QUIT), etc. Signal handlers
+ * can be registered using {@link #handle(Signal, SignalHandler)}.
+ *
+ * Signal handling allows terminal applications to respond appropriately to these events,
+ * such as gracefully terminating when the user presses Ctrl+C, or adjusting the display
+ * when the terminal window is resized.
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ *
+ * // Handle interrupt signal (Ctrl+C)
+ * terminal.handle(Signal.INT, signal -> {
+ * terminal.writer().println("\nInterrupted! Press Enter to exit.");
+ * terminal.flush();
+ * });
+ *
+ *
+ * Mouse Support
+ * Some terminals support mouse tracking, which can be enabled using {@link #trackMouse(MouseTracking)}.
+ * Mouse events can then be read using {@link #readMouseEvent()}.
+ *
+ * Lifecycle
+ * Terminals should be closed by calling the {@link #close()} method when they are no longer needed
+ * in order to restore their original state. Failure to close a terminal may leave the terminal in an
+ * inconsistent state.
+ *
+ * Creating Terminals
+ * Terminals are typically created using the {@link TerminalBuilder} class, which provides a fluent API
+ * for configuring and creating terminal instances.
+ *
+ * @see TerminalBuilder
+ * @see Attributes
+ * @see Size
+ * @see Cursor
+ * @see MouseEvent
*/
public interface Terminal extends Closeable, Flushable {
/**
- * Type used for dumb terminals.
+ * Type identifier for dumb terminals with minimal capabilities.
+ *
+ *
+ * A dumb terminal has minimal capabilities and typically does not support
+ * cursor movement, colors, or other advanced features. It's often used as
+ * a fallback when a more capable terminal is not available.
+ *
*/
String TYPE_DUMB = "dumb";
+ /**
+ * Type identifier for dumb terminals with basic color support.
+ *
+ *
+ * A dumb-color terminal has minimal capabilities like a dumb terminal,
+ * but does support basic color output. It still lacks support for cursor
+ * movement and other advanced features.
+ *
+ */
String TYPE_DUMB_COLOR = "dumb-color";
+ /**
+ * Returns the name of this terminal.
+ *
+ *
+ * The terminal name is typically a descriptive identifier that can be used for logging
+ * or debugging purposes. It may reflect the terminal type, connection method, or other
+ * distinguishing characteristics.
+ *
+ *
+ * @return the terminal name
+ */
String getName();
- //
- // Signal support
- //
+ /*
+ * Signal support for terminal applications.
+ *
+ *
+ * JLine provides support for handling terminal signals, which are asynchronous notifications
+ * sent to the application in response to certain events or user actions. Common signals include
+ * interrupt (Ctrl+C), quit (Ctrl+\), suspend (Ctrl+Z), and window size changes.
+ *
+ *
+ *
+ */
/**
- * Types of signals.
+ * Types of signals that can be handled by terminal applications.
+ *
+ *
+ * Signals represent asynchronous notifications that can be sent to the application
+ * in response to certain events or user actions. Each signal type corresponds to a
+ * specific event or key combination:
+ *
+ *
+ *
+ * {@link #INT} - Interrupt signal (typically Ctrl+C)
+ * {@link #QUIT} - Quit signal (typically Ctrl+\)
+ * {@link #TSTP} - Terminal stop signal (typically Ctrl+Z)
+ * {@link #CONT} - Continue signal (sent when resuming after TSTP)
+ * {@link #INFO} - Information signal (typically Ctrl+T on BSD systems)
+ * {@link #WINCH} - Window change signal (sent when terminal size changes)
+ *
+ *
+ *
+ * Note that signal handling behavior may vary across different platforms and terminal
+ * implementations. Some signals may not be available or may behave differently on
+ * certain systems.
+ *
+ *
+ * @see #handle(Signal, SignalHandler)
+ * @see #raise(Signal)
*/
enum Signal {
+ /**
+ * Interrupt signal, typically generated by pressing Ctrl+C.
+ * Used to interrupt or terminate a running process.
+ */
INT,
+
+ /**
+ * Quit signal, typically generated by pressing Ctrl+\.
+ * Often used to force a core dump or immediate termination.
+ * Note: The JVM does not easily allow catching this signal natively.
+ */
QUIT,
+
+ /**
+ * Terminal stop signal, typically generated by pressing Ctrl+Z.
+ * Used to suspend the current process.
+ */
TSTP,
+
+ /**
+ * Continue signal, sent when resuming a process after suspension.
+ * This signal is sent to a process when it's resumed after being stopped by TSTP.
+ */
CONT,
+
+ /**
+ * Information signal, typically generated by pressing Ctrl+T on BSD systems.
+ * Used to request status information from a running process.
+ */
INFO,
+
+ /**
+ * Window change signal, sent when the terminal window size changes.
+ * Applications can handle this signal to adjust their display accordingly.
+ */
WINCH
}
/**
- * The SignalHandler defines the interface used to trap signals and perform specific behaviors.
+ * Interface for handling terminal signals.
+ *
+ *
+ * The SignalHandler interface defines the contract for objects that can respond to
+ * terminal signals. When a signal is raised, the corresponding handler's {@link #handle(Signal)}
+ * method is called with the signal that was raised.
+ *
+ *
+ *
+ * JLine provides two predefined signal handlers:
+ *
+ *
+ * {@link #SIG_DFL} - Default signal handler that uses the JVM's default behavior
+ * {@link #SIG_IGN} - Ignores the signal and performs no special processing
+ *
+ *
+ * Example usage with a custom handler:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ *
+ * // Create a custom signal handler
+ * SignalHandler handler = signal -> {
+ * if (signal == Signal.INT) {
+ * terminal.writer().println("\nInterrupted!");
+ * terminal.flush();
+ * }
+ * };
+ *
+ * // Register the handler for the INT signal
+ * terminal.handle(Signal.INT, handler);
+ *
+ *
* @see Terminal.Signal
* @see Terminal#handle(Signal, SignalHandler)
*/
interface SignalHandler {
/**
- * The {@code SIG_DFL} value can be used to specify that the JVM default behavior
- * should be used to handle this signal.
+ * Default signal handler that uses the JVM's default behavior for the signal.
+ *
+ *
+ * When this handler is registered for a signal, the terminal will use the JVM's
+ * default behavior to handle the signal. For example, the default behavior for
+ * the INT signal (Ctrl+C) is to terminate the JVM.
+ *
+ *
+ * Example usage:
+ *
+ * // Restore default behavior for INT signal
+ * terminal.handle(Signal.INT, SignalHandler.SIG_DFL);
+ *
*/
SignalHandler SIG_DFL = NativeSignalHandler.SIG_DFL;
/**
- * The {@code SIG_IGN} value can be used to ignore this signal and not perform
- * any special processing.
+ * Signal handler that ignores the signal and performs no special processing.
+ *
+ *
+ * When this handler is registered for a signal, the terminal will completely
+ * ignore the signal and continue normal operation. This is useful for preventing
+ * signals like INT (Ctrl+C) from terminating the application.
+ *
+ *
+ * Example usage:
+ *
+ * // Ignore INT signal (Ctrl+C will not terminate the application)
+ * terminal.handle(Signal.INT, SignalHandler.SIG_IGN);
+ *
*/
SignalHandler SIG_IGN = NativeSignalHandler.SIG_IGN;
/**
- * Handle the signal.
- * @param signal the signal
+ * Handles the specified signal.
+ *
+ *
+ * This method is called when a signal is raised and this handler is registered
+ * for that signal. Implementations should perform any necessary actions in response
+ * to the signal.
+ *
+ *
+ *
+ * Note that signal handlers should generally be short-lived and avoid blocking
+ * operations, as they may be called in contexts where blocking could cause
+ * deadlocks or other issues.
+ *
+ *
+ * @param signal the signal that was raised
*/
void handle(Signal signal);
}
/**
* Registers a handler for the given {@link Signal}.
+ *
*
- * Note that the JVM does not easily allow catching the {@link Signal#QUIT} signal, which causes a thread dump
- * to be displayed. This signal is mainly used when connecting through an SSH socket to a virtual terminal.
+ * This method allows the application to specify custom behavior when a particular
+ * signal is raised. The handler's {@link SignalHandler#handle(Signal)} method will
+ * be called whenever the specified signal is raised.
+ *
+ *
+ *
+ * Note that the JVM does not easily allow catching the {@link Signal#QUIT} signal (Ctrl+\),
+ * which typically causes a thread dump to be displayed. This signal handling is mainly
+ * effective when connecting through an SSH socket to a virtual terminal.
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ *
+ * // Handle window resize events
+ * terminal.handle(Signal.WINCH, signal -> {
+ * Size size = terminal.getSize();
+ * terminal.writer().println("\nTerminal resized to " +
+ * size.getColumns() + "x" + size.getRows());
+ * terminal.flush();
+ * });
+ *
+ * // Ignore interrupt signal
+ * terminal.handle(Signal.INT, SignalHandler.SIG_IGN);
+ *
*
* @param signal the signal to register a handler for
- * @param handler the handler
- * @return the previous signal handler
+ * @param handler the handler to be called when the signal is raised
+ * @return the previous signal handler that was registered for this signal
+ * @see Signal
+ * @see SignalHandler
+ * @see #raise(Signal)
*/
SignalHandler handle(Signal signal, SignalHandler handler);
/**
- * Raise the specific signal.
- * This is not method usually called by non system terminals.
- * When accessing a terminal through a SSH or Telnet connection, signals may be
- * conveyed by the protocol and thus need to be raised when reaching the terminal code.
- * The terminals do that automatically when the terminal input stream has a character
- * mapped to {@link Attributes.ControlChar#VINTR}, {@link Attributes.ControlChar#VQUIT},
- * or {@link Attributes.ControlChar#VSUSP}.
+ * Raises the specified signal, triggering any registered handlers.
+ *
+ *
+ * This method manually triggers a signal, causing any registered handler for that
+ * signal to be called. This is typically not a method that application code would
+ * call directly, but is used internally by terminal implementations.
+ *
+ *
+ *
+ * When accessing a terminal through an SSH or Telnet connection, signals may be
+ * conveyed by the protocol and need to be raised when they reach the terminal code.
+ * Terminal implementations automatically raise signals when the input stream receives
+ * characters mapped to special control characters:
+ *
+ *
+ * {@link Attributes.ControlChar#VINTR} (typically Ctrl+C) - Raises {@link Signal#INT}
+ * {@link Attributes.ControlChar#VQUIT} (typically Ctrl+\) - Raises {@link Signal#QUIT}
+ * {@link Attributes.ControlChar#VSUSP} (typically Ctrl+Z) - Raises {@link Signal#TSTP}
+ *
+ *
+ *
+ * In some cases, application code might want to programmatically raise signals to
+ * trigger specific behaviors, such as simulating a window resize event by raising
+ * {@link Signal#WINCH}.
+ *
*
* @param signal the signal to raise
+ * @see Signal
+ * @see #handle(Signal, SignalHandler)
+ * @see Attributes.ControlChar
*/
void raise(Signal signal);
@@ -131,10 +388,47 @@ interface SignalHandler {
* Returns the {@link Charset} that should be used to encode characters
* for {@link #input()} and {@link #output()}.
*
+ * This method returns a general encoding that can be used for both input and output.
+ * For stream-specific encodings, use {@link #inputEncoding()} and {@link #outputEncoding()}.
+ *
* @return The terminal encoding
+ * @see #inputEncoding()
+ * @see #outputEncoding()
*/
Charset encoding();
+ /**
+ * Returns the {@link Charset} that should be used to decode characters
+ * from the terminal input ({@link #input()}).
+ *
+ * This method returns the encoding specifically for terminal input.
+ * If no specific input encoding was configured, it falls back to the
+ * general encoding from {@link #encoding()}.
+ *
+ * @return The terminal input encoding
+ * @see #encoding()
+ */
+ default Charset inputEncoding() {
+ return encoding();
+ }
+
+ /**
+ * Returns the {@link Charset} that should be used to encode characters
+ * for the terminal output ({@link #output()}).
+ *
+ * This method returns the encoding specifically for terminal output.
+ * The encoding used depends on the system stream associated with this terminal:
+ * if the terminal is bound to standard error, it uses the stderr encoding;
+ * otherwise, it uses the stdout encoding. If no specific output encoding
+ * was configured, it falls back to the general encoding from {@link #encoding()}.
+ *
+ * @return The terminal output encoding
+ * @see #encoding()
+ */
+ default Charset outputEncoding() {
+ return encoding();
+ }
+
/**
* Retrieve the input stream for this terminal.
* In some rare cases, there may be a need to access the
@@ -174,10 +468,41 @@ interface SignalHandler {
boolean canPauseResume();
/**
- * Stop reading the input stream.
+ * Temporarily stops reading the input stream.
+ *
+ *
+ * This method pauses the terminal's input processing, which can be useful when
+ * transferring control to a subprocess or when the terminal needs to be in a
+ * specific state for certain operations. While paused, the terminal will not
+ * process input or handle signals that would normally be triggered by special
+ * characters in the input stream.
+ *
+ *
+ *
+ * This method returns immediately without waiting for the terminal to actually
+ * pause. To wait until the terminal has fully paused, use {@link #pause(boolean)}
+ * with a value of {@code true}.
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ *
+ * // Pause terminal input processing before running a subprocess
+ * terminal.pause();
+ *
+ * // Run subprocess that takes control of the terminal
+ * Process process = new ProcessBuilder("vim").inheritIO().start();
+ * process.waitFor();
+ *
+ * // Resume terminal input processing
+ * terminal.resume();
+ *
*
* @see #resume()
+ * @see #pause(boolean)
* @see #paused()
+ * @see #canPauseResume()
*/
void pause();
@@ -190,10 +515,36 @@ interface SignalHandler {
void pause(boolean wait) throws InterruptedException;
/**
- * Resume reading the input stream.
+ * Resumes reading the input stream after it has been paused.
+ *
+ *
+ * This method restarts the terminal's input processing after it has been
+ * temporarily stopped using {@link #pause()} or {@link #pause(boolean)}.
+ * Once resumed, the terminal will continue to process input and handle signals
+ * triggered by special characters in the input stream.
+ *
+ *
+ *
+ * Calling this method when the terminal is not paused has no effect.
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ *
+ * // Pause terminal input processing
+ * terminal.pause();
+ *
+ * // Perform operations while terminal input is paused...
+ *
+ * // Resume terminal input processing
+ * terminal.resume();
+ *
*
* @see #pause()
+ * @see #pause(boolean)
* @see #paused()
+ * @see #canPauseResume()
*/
void resume();
@@ -217,26 +568,176 @@ interface SignalHandler {
// Pty settings
//
+ /**
+ * Puts the terminal into raw mode.
+ *
+ *
+ * In raw mode, input is available character by character, terminal-generated signals are disabled,
+ * and special character processing is disabled. This mode is typically used for full-screen
+ * interactive applications like text editors.
+ *
+ *
+ *
+ * This method modifies the terminal attributes to configure raw mode and returns the
+ * original attributes, which can be used to restore the terminal to its previous state.
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ * Attributes originalAttributes = terminal.enterRawMode();
+ *
+ * // Use terminal in raw mode...
+ *
+ * // Restore original attributes when done
+ * terminal.setAttributes(originalAttributes);
+ *
+ *
+ * @return the original terminal attributes before entering raw mode
+ * @see #setAttributes(Attributes)
+ */
Attributes enterRawMode();
+ /**
+ * Returns whether the terminal is currently echoing input characters.
+ *
+ *
+ * When echo is enabled, characters typed by the user are automatically displayed on the screen.
+ * When echo is disabled, input characters are not displayed, which is useful for password input
+ * or other sensitive information.
+ *
+ *
+ * @return {@code true} if echo is enabled, {@code false} otherwise
+ * @see #echo(boolean)
+ */
boolean echo();
+ /**
+ * Enables or disables echoing of input characters.
+ *
+ *
+ * When echo is enabled, characters typed by the user are automatically displayed on the screen.
+ * When echo is disabled, input characters are not displayed, which is useful for password input
+ * or other sensitive information.
+ *
+ *
+ * Example usage for password input:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ * boolean oldEcho = terminal.echo(false); // Disable echo
+ * String password = readPassword(terminal);
+ * terminal.echo(oldEcho); // Restore previous echo state
+ *
+ *
+ * @param echo {@code true} to enable echo, {@code false} to disable it
+ * @return the previous echo state
+ */
boolean echo(boolean echo);
/**
- * Returns the terminal attributes.
- * The returned object can be safely modified
- * further used in a call to {@link #setAttributes(Attributes)}.
+ * Returns the current terminal attributes.
*
- * @return the terminal attributes.
+ *
+ * Terminal attributes control various aspects of terminal behavior, including:
+ *
+ *
+ * Input processing - How input characters are processed (e.g., character mapping, parity checking)
+ * Output processing - How output characters are processed (e.g., newline translation)
+ * Control settings - Hardware settings like baud rate and character size
+ * Local settings - Terminal behavior settings like echo, canonical mode, and signal generation
+ * Control characters - Special characters like EOF, interrupt, and erase
+ *
+ *
+ *
+ * The returned {@link Attributes} object is a copy of the terminal's current attributes
+ * and can be safely modified without affecting the terminal until it is applied using
+ * {@link #setAttributes(Attributes)}. This allows for making multiple changes to the
+ * attributes before applying them all at once.
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ *
+ * // Get current attributes
+ * Attributes attrs = terminal.getAttributes();
+ *
+ * // Modify attributes
+ * attrs.setLocalFlag(LocalFlag.ECHO, false); // Disable echo
+ * attrs.setInputFlag(InputFlag.ICRNL, false); // Disable CR to NL mapping
+ * attrs.setControlChar(ControlChar.VMIN, 1); // Set minimum input to 1 character
+ * attrs.setControlChar(ControlChar.VTIME, 0); // Set timeout to 0 deciseconds
+ *
+ * // Apply modified attributes
+ * terminal.setAttributes(attrs);
+ *
+ *
+ * @return a copy of the terminal's current attributes
+ * @see #setAttributes(Attributes)
+ * @see Attributes
+ * @see #enterRawMode()
*/
Attributes getAttributes();
/**
- * Set the terminal attributes.
- * The terminal will perform a copy of the given attributes.
+ * Sets the terminal attributes to the specified values.
+ *
+ *
+ * This method applies the specified attributes to the terminal, changing its behavior
+ * according to the settings in the {@link Attributes} object. The terminal makes a copy
+ * of the provided attributes, so further modifications to the {@code attr} object will
+ * not affect the terminal until this method is called again.
+ *
+ *
+ *
+ * Terminal attributes control various aspects of terminal behavior, including input and
+ * output processing, control settings, local settings, and special control characters.
+ * Changing these attributes allows for fine-grained control over how the terminal
+ * processes input and output.
+ *
+ *
+ *
+ * Common attribute modifications include:
+ *
+ *
+ * Disabling echo for password input
+ * Enabling/disabling canonical mode for line-by-line or character-by-character input
+ * Disabling signal generation for custom handling of Ctrl+C and other control sequences
+ * Changing control characters like the interrupt character or end-of-file character
+ *
+ *
+ *
+ * For convenience, the {@link #enterRawMode()} method provides a pre-configured set of
+ * attributes suitable for full-screen interactive applications.
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ *
+ * // Save original attributes for later restoration
+ * Attributes originalAttrs = terminal.getAttributes();
+ *
+ * try {
+ * // Create and configure new attributes
+ * Attributes attrs = new Attributes(originalAttrs);
+ * attrs.setLocalFlag(LocalFlag.ECHO, false); // Disable echo for password input
+ * attrs.setLocalFlag(LocalFlag.ICANON, false); // Disable canonical mode
+ *
+ * // Apply the new attributes
+ * terminal.setAttributes(attrs);
+ *
+ * // Use terminal with modified attributes...
+ * } finally {
+ * // Restore original attributes
+ * terminal.setAttributes(originalAttrs);
+ * }
+ *
*
- * @param attr the new attributes
+ * @param attr the attributes to apply to the terminal
+ * @see #getAttributes()
+ * @see Attributes
+ * @see #enterRawMode()
*/
void setAttributes(Attributes attr);
@@ -247,12 +748,50 @@ interface SignalHandler {
*/
Size getSize();
+ /**
+ * Sets the size of the terminal.
+ *
+ *
+ * This method attempts to resize the terminal to the specified dimensions. Note that
+ * not all terminals support resizing, and the actual size after this operation may
+ * differ from the requested size depending on terminal capabilities and constraints.
+ *
+ *
+ *
+ * For virtual terminals or terminal emulators, this may update the internal size
+ * representation. For physical terminals, this may send appropriate escape sequences
+ * to adjust the viewable area.
+ *
+ *
+ * @param size the new terminal size (columns and rows)
+ * @see #getSize()
+ */
void setSize(Size size);
+ /**
+ * Returns the width (number of columns) of the terminal.
+ *
+ *
+ * This is a convenience method equivalent to {@code getSize().getColumns()}.
+ *
+ *
+ * @return the number of columns in the terminal
+ * @see #getSize()
+ */
default int getWidth() {
return getSize().getColumns();
}
+ /**
+ * Returns the height (number of rows) of the terminal.
+ *
+ *
+ * This is a convenience method equivalent to {@code getSize().getRows()}.
+ *
+ *
+ * @return the number of rows in the terminal
+ * @see #getSize()
+ */
default int getHeight() {
return getSize().getRows();
}
@@ -273,20 +812,161 @@ default Size getBufferSize() {
return getSize();
}
+ /**
+ * Flushes any buffered output to the terminal.
+ *
+ *
+ * Terminal implementations may buffer output for efficiency. This method ensures
+ * that any buffered data is written to the terminal immediately. It's important
+ * to call this method when immediate display of output is required, such as when
+ * prompting for user input or updating status information.
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ * terminal.writer().print("Enter your name: ");
+ * terminal.flush(); // Ensure the prompt is displayed before reading input
+ * String name = terminal.reader().readLine();
+ *
+ */
void flush();
//
// Infocmp capabilities
//
+ /**
+ * Returns the type of this terminal.
+ *
+ *
+ * The terminal type is a string identifier that describes the terminal's capabilities
+ * and behavior. Common terminal types include "xterm", "vt100", "ansi", and "dumb".
+ * This type is often used to look up terminal capabilities in the terminfo database.
+ *
+ *
+ *
+ * Special terminal types include:
+ *
+ *
+ * {@link #TYPE_DUMB} - A terminal with minimal capabilities, typically not supporting
+ * cursor movement or colors
+ * {@link #TYPE_DUMB_COLOR} - A dumb terminal that supports basic color output
+ *
+ *
+ * @return the terminal type identifier
+ * @see #TYPE_DUMB
+ * @see #TYPE_DUMB_COLOR
+ */
String getType();
+ /**
+ * Outputs a terminal control string for the specified capability.
+ *
+ *
+ * This method formats and outputs a control sequence for the specified terminal capability,
+ * with the given parameters. It's used to perform terminal operations such as cursor movement,
+ * screen clearing, color changes, and other terminal-specific functions.
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ *
+ * // Clear the screen
+ * terminal.puts(Capability.clear_screen);
+ *
+ * // Move cursor to position (10, 20)
+ * terminal.puts(Capability.cursor_address, 20, 10);
+ *
+ * // Set foreground color to red
+ * terminal.puts(Capability.set_a_foreground, 1);
+ *
+ *
+ * @param capability the terminal capability to use
+ * @param params the parameters for the capability
+ * @return {@code true} if the capability is supported and was output, {@code false} otherwise
+ * @see #getStringCapability(Capability)
+ */
boolean puts(Capability capability, Object... params);
+ /**
+ * Returns whether the terminal supports the specified boolean capability.
+ *
+ *
+ * Boolean capabilities indicate whether the terminal supports specific features,
+ * such as color support, automatic margins, or status line support.
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ *
+ * // Check if terminal supports colors
+ * if (terminal.getBooleanCapability(Capability.colors)) {
+ * // Use color output
+ * } else {
+ * // Use monochrome output
+ * }
+ *
+ *
+ * @param capability the boolean capability to check
+ * @return {@code true} if the terminal supports the capability, {@code false} otherwise
+ */
boolean getBooleanCapability(Capability capability);
+ /**
+ * Returns the value of the specified numeric capability for this terminal.
+ *
+ *
+ * Numeric capabilities represent terminal properties with numeric values, such as
+ * the maximum number of colors supported, the number of function keys, or timing
+ * parameters for certain operations.
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ *
+ * // Get the number of colors supported by the terminal
+ * Integer colors = terminal.getNumericCapability(Capability.max_colors);
+ * if (colors != null && colors >= 256) {
+ * // Terminal supports 256 colors
+ * }
+ *
+ *
+ * @param capability the numeric capability to retrieve
+ * @return the value of the capability, or {@code null} if the capability is not supported
+ */
Integer getNumericCapability(Capability capability);
+ /**
+ * Returns the string value of the specified capability for this terminal.
+ *
+ *
+ * String capabilities represent terminal control sequences that can be used to perform
+ * various operations, such as moving the cursor, changing colors, clearing the screen,
+ * or ringing the bell. These sequences can be parameterized using the {@link #puts(Capability, Object...)}
+ * method.
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ *
+ * // Get the control sequence for clearing the screen
+ * String clearScreen = terminal.getStringCapability(Capability.clear_screen);
+ * if (clearScreen != null) {
+ * // Use the sequence directly
+ * terminal.writer().print(clearScreen);
+ * terminal.flush();
+ * }
+ *
+ *
+ * @param capability the string capability to retrieve
+ * @return the string value of the capability, or {@code null} if the capability is not supported
+ * @see #puts(Capability, Object...)
+ */
String getStringCapability(Capability capability);
//
@@ -332,62 +1012,467 @@ enum MouseTracking {
}
/**
- * Returns true if the terminal has support for mouse.
- * @return whether mouse is supported by the terminal
+ * Returns whether the terminal has support for mouse tracking.
+ *
+ *
+ * Mouse support allows the terminal to report mouse events such as clicks, movement,
+ * and wheel scrolling. Not all terminals support mouse tracking, so this method
+ * should be called before attempting to enable mouse tracking with
+ * {@link #trackMouse(MouseTracking)}.
+ *
+ *
+ *
+ * Common terminal emulators that support mouse tracking include xterm, iTerm2,
+ * and modern versions of GNOME Terminal and Konsole. Terminal multiplexers like
+ * tmux and screen may also support mouse tracking depending on their configuration
+ * and the capabilities of the underlying terminal.
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ *
+ * if (terminal.hasMouseSupport()) {
+ * // Enable mouse tracking
+ * terminal.trackMouse(MouseTracking.Normal);
+ *
+ * // Process mouse events
+ * // ...
+ * } else {
+ * System.out.println("Mouse tracking not supported by this terminal");
+ * }
+ *
+ *
+ * @return {@code true} if the terminal supports mouse tracking, {@code false} otherwise
* @see #trackMouse(MouseTracking)
+ * @see #readMouseEvent()
*/
boolean hasMouseSupport();
/**
- * Change the mouse tracking mouse.
- * To start mouse tracking, this method must be called with a valid mouse tracking mode.
- * Mouse events will be reported by writing the {@link Capability#key_mouse} to the input stream.
- * When this character sequence is detected, the {@link #readMouseEvent()} method can be
- * called to actually read the corresponding mouse event.
+ * Enables or disables mouse tracking with the specified mode.
+ *
+ *
+ * This method configures the terminal to report mouse events according to the
+ * specified tracking mode. When mouse tracking is enabled, the terminal will
+ * send special escape sequences to the input stream whenever mouse events occur.
+ * These sequences begin with the {@link Capability#key_mouse} sequence, followed
+ * by data that describes the specific mouse event.
+ *
*
- * @param tracking the mouse tracking mode
- * @return true if mouse tracking is supported
+ *
+ * The tracking mode determines which mouse events are reported:
+ *
+ *
+ * {@link MouseTracking#Off} - Disables mouse tracking
+ * {@link MouseTracking#Normal} - Reports button press and release events
+ * {@link MouseTracking#Button} - Reports button press, release, and motion events while buttons are pressed
+ * {@link MouseTracking#Any} - Reports all mouse events, including movement without buttons pressed
+ *
+ *
+ *
+ * To process mouse events, applications should:
+ *
+ *
+ * Enable mouse tracking by calling this method with the desired mode
+ * Monitor the input stream for the {@link Capability#key_mouse} sequence
+ * When this sequence is detected, call {@link #readMouseEvent()} to decode the event
+ * Process the returned {@link MouseEvent} as needed
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ *
+ * if (terminal.hasMouseSupport()) {
+ * // Enable tracking of all mouse events
+ * boolean supported = terminal.trackMouse(MouseTracking.Any);
+ *
+ * if (supported) {
+ * System.out.println("Mouse tracking enabled");
+ * // Set up input processing to detect and handle mouse events
+ * }
+ * }
+ *
+ *
+ * @param tracking the mouse tracking mode to enable, or {@link MouseTracking#Off} to disable tracking
+ * @return {@code true} if the requested mouse tracking mode is supported, {@code false} otherwise
+ * @see MouseTracking
+ * @see #hasMouseSupport()
+ * @see #readMouseEvent()
*/
boolean trackMouse(MouseTracking tracking);
+ /**
+ * Returns the current mouse tracking mode.
+ *
+ * @see #trackMouse(MouseTracking)
+ * @since 3.30.0
+ */
+ MouseTracking getCurrentMouseTracking();
+
/**
* Read a MouseEvent from the terminal input stream.
* Such an event must have been detected by scanning the terminal's {@link Capability#key_mouse}
* in the stream immediately before reading the event.
*
- * @return the decoded mouse event.
+ *
+ * This method should be called after detecting the terminal's {@link Capability#key_mouse}
+ * sequence in the input stream, which indicates that a mouse event has occurred.
+ * The method reads the necessary data from the input stream and decodes it into
+ * a {@link MouseEvent} object containing information about the event type, button,
+ * modifiers, and coordinates.
+ *
+ *
+ *
+ * Before calling this method, mouse tracking must be enabled using
+ * {@link #trackMouse(MouseTracking)} with an appropriate tracking mode.
+ *
+ *
+ *
+ * The typical pattern for handling mouse events is:
+ *
+ *
+ * Enable mouse tracking with {@link #trackMouse(MouseTracking)}
+ * Read input from the terminal
+ * When the {@link Capability#key_mouse} sequence is detected, call this method
+ * Process the returned {@link MouseEvent}
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ *
+ * if (terminal.hasMouseSupport()) {
+ * terminal.trackMouse(MouseTracking.Normal);
+ *
+ * // Read input and look for mouse events
+ * String keyMouse = terminal.getStringCapability(Capability.key_mouse);
+ * // When keyMouse sequence is detected in the input:
+ * MouseEvent event = terminal.readMouseEvent();
+ * System.out.println("Mouse event: " + event.getType() +
+ * " at " + event.getX() + "," + event.getY());
+ * }
+ *
+ *
+ * @return the decoded mouse event containing event type, button, modifiers, and coordinates
* @see #trackMouse(MouseTracking)
+ * @see #hasMouseSupport()
+ * @see MouseEvent
*/
MouseEvent readMouseEvent();
/**
- * Read a MouseEvent from the given input stream.
+ * Reads and decodes a mouse event using the provided input supplier.
+ *
+ *
+ * This method is similar to {@link #readMouseEvent()}, but allows reading mouse event
+ * data from a custom input source rather than the terminal's default input stream.
+ * This can be useful in situations where input is being processed through a different
+ * channel or when implementing custom input handling.
+ *
+ *
+ *
+ * The input supplier should provide the raw bytes of the mouse event data as integers.
+ * The method will read the necessary data from the supplier and decode it into a
+ * {@link MouseEvent} object containing information about the event type, button,
+ * modifiers, and coordinates.
+ *
+ *
+ *
+ * This method is primarily intended for advanced use cases where the standard
+ * {@link #readMouseEvent()} method is not sufficient.
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ *
+ * // Create a custom input supplier
+ * IntSupplier customReader = new IntSupplier() {
+ * private byte[] data = ...; // Mouse event data
+ * private int index = 0;
+ *
+ * public int getAsInt() {
+ * return (index < data.length) ? data[index++] & 0xFF : -1;
+ * }
+ * };
*
- * @param reader the input supplier
- * @return the decoded mouse event
+ * // Read mouse event using the custom supplier
+ * MouseEvent event = terminal.readMouseEvent(customReader);
+ *
+ *
+ * @param reader the input supplier that provides the raw bytes of the mouse event data
+ * @return the decoded mouse event containing event type, button, modifiers, and coordinates
+ * @see #readMouseEvent()
+ * @see MouseEvent
*/
MouseEvent readMouseEvent(IntSupplier reader);
/**
- * Returns true if the terminal has support for focus tracking.
- * @return whether focus tracking is supported by the terminal
+ * Reads and decodes a mouse event with a specified prefix that has already been consumed.
+ *
+ *
+ * This method is similar to {@link #readMouseEvent()}, but it allows specifying a prefix
+ * that has already been consumed. This is useful when the mouse event prefix (e.g., "\033[<"
+ * or "\033[M") has been consumed by the key binding detection, and we need to continue
+ * parsing from the current position.
+ *
+ *
+ *
+ * This method is primarily intended for advanced use cases where the standard
+ * {@link #readMouseEvent()} method is not sufficient, particularly when dealing with
+ * key binding systems that may consume part of the mouse event sequence.
+ *
+ *
+ * @param prefix the prefix that has already been consumed, or null if none
+ * @return the decoded mouse event containing event type, button, modifiers, and coordinates
+ * @see #readMouseEvent()
+ * @see MouseEvent
+ * @since 3.30.0
+ */
+ MouseEvent readMouseEvent(String prefix);
+
+ /**
+ * Reads and decodes a mouse event using the provided input supplier with a specified prefix
+ * that has already been consumed.
+ *
+ *
+ * This method combines the functionality of {@link #readMouseEvent(IntSupplier)} and
+ * {@link #readMouseEvent(String)}, allowing both a custom input supplier and a prefix
+ * to be specified. This is useful for advanced input handling scenarios where both
+ * customization of the input source and handling of partially consumed sequences are needed.
+ *
+ *
+ * @param reader the input supplier that provides the raw bytes of the mouse event data
+ * @param prefix the prefix that has already been consumed, or null if none
+ * @return the decoded mouse event containing event type, button, modifiers, and coordinates
+ * @see #readMouseEvent()
+ * @see #readMouseEvent(IntSupplier)
+ * @see #readMouseEvent(String)
+ * @see MouseEvent
+ * @since 3.30.0
+ */
+ MouseEvent readMouseEvent(IntSupplier reader, String prefix);
+
+ /**
+ * Returns whether the terminal has support for focus tracking.
+ *
+ *
+ * Focus tracking allows the terminal to report when it gains or loses focus.
+ * This can be useful for applications that need to change their behavior or
+ * appearance based on whether they are currently in focus.
+ *
+ *
+ *
+ * Not all terminals support focus tracking, so this method should be called
+ * before attempting to enable focus tracking with {@link #trackFocus(boolean)}.
+ *
+ *
+ *
+ * When focus tracking is enabled and supported, the terminal will send special
+ * escape sequences to the input stream when focus is gained ("\33[I") or
+ * lost ("\33[O"). Applications can detect these sequences to respond to
+ * focus changes.
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ *
+ * if (terminal.hasFocusSupport()) {
+ * // Enable focus tracking
+ * terminal.trackFocus(true);
+ *
+ * // Now the application can detect focus changes
+ * // by looking for "\33[I" and "\33[O" in the input stream
+ * } else {
+ * System.out.println("Focus tracking not supported by this terminal");
+ * }
+ *
+ *
+ * @return {@code true} if the terminal supports focus tracking, {@code false} otherwise
* @see #trackFocus(boolean)
*/
boolean hasFocusSupport();
/**
- * Enable or disable focus tracking mode.
- * When focus tracking has been activated, each time the terminal grabs the focus,
- * the string "\33[I" will be sent to the input stream and each time the focus is lost,
- * the string "\33[O" will be sent to the input stream.
+ * Enables or disables focus tracking mode.
+ *
+ *
+ * Focus tracking allows applications to detect when the terminal window gains or loses
+ * focus. When focus tracking is enabled, the terminal will send special escape sequences
+ * to the input stream whenever the focus state changes:
+ *
+ *
+ * When the terminal gains focus: "\33[I" (ESC [ I)
+ * When the terminal loses focus: "\33[O" (ESC [ O)
+ *
+ *
+ *
+ * Applications can monitor the input stream for these sequences to detect focus changes
+ * and respond accordingly, such as by changing the cursor appearance, pausing animations,
+ * or adjusting the display.
+ *
+ *
+ *
+ * Not all terminals support focus tracking. Use {@link #hasFocusSupport()} to check
+ * whether focus tracking is supported before enabling it.
+ *
*
- * @param tracking whether the focus tracking mode should be enabled or not
- * @return true if focus tracking is supported
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ *
+ * if (terminal.hasFocusSupport()) {
+ * // Enable focus tracking
+ * boolean enabled = terminal.trackFocus(true);
+ *
+ * if (enabled) {
+ * System.out.println("Focus tracking enabled");
+ * // Set up input processing to detect focus change sequences
+ * }
+ * }
+ *
+ *
+ * @param tracking {@code true} to enable focus tracking, {@code false} to disable it
+ * @return {@code true} if focus tracking is supported and the operation succeeded, {@code false} otherwise
+ * @see #hasFocusSupport()
*/
boolean trackFocus(boolean tracking);
/**
- * Color support
+ * Returns whether the terminal supports mode 2027 (grapheme cluster / Unicode Core).
+ *
+ *
+ * Mode 2027 allows the terminal to use UAX #29 grapheme cluster segmentation
+ * instead of per-codepoint {@code wcwidth()} for cursor positioning. This matters
+ * for multi-codepoint characters like ZWJ emoji sequences (e.g., family emoji),
+ * which would otherwise be counted as multiple separate characters.
+ *
+ *
+ *
+ * Support detection uses DECRQM probing, which is only performed on terminals
+ * whose type starts with {@code "xterm"} (or similar modern terminals). The probe
+ * is never sent to dumb terminals or terminals that are unlikely to understand
+ * DECRQM, avoiding the risk of printing garbage on unsupported terminals.
+ *
+ *
+ * @return {@code true} if the terminal supports mode 2027, {@code false} otherwise
+ * @see #setGraphemeClusterMode(boolean, boolean)
+ * @see #getGraphemeClusterMode()
+ */
+ default boolean supportsGraphemeClusterMode() {
+ return false;
+ }
+
+ /**
+ * Returns whether mode 2027 (grapheme cluster) is currently enabled.
+ *
+ * @return {@code true} if grapheme cluster mode is currently enabled, {@code false} otherwise
+ * @see #setGraphemeClusterMode(boolean, boolean)
+ */
+ default boolean getGraphemeClusterMode() {
+ return false;
+ }
+
+ /**
+ * Enables or disables mode 2027 (grapheme cluster / Unicode Core).
+ *
+ *
+ * When enabled, the terminal uses UAX #29 grapheme cluster segmentation for
+ * cursor positioning. This allows multi-codepoint characters like ZWJ emoji
+ * sequences to be treated as single display units.
+ *
+ *
+ *
+ * The mode is tracked internally and will be automatically disabled when the
+ * terminal is closed, restoring the terminal to its previous state.
+ *
+ *
+ * @param enable {@code true} to enable grapheme cluster mode, {@code false} to disable it
+ * @param force if {@code true}, skip capability probing and treat the terminal as
+ * natively supporting grapheme clusters (no Mode 2027 escape sequences sent)
+ * @return {@code true} if the operation succeeded, {@code false} otherwise
+ * @see #supportsGraphemeClusterMode()
+ * @see #getGraphemeClusterMode()
+ */
+ default boolean setGraphemeClusterMode(boolean enable, boolean force) {
+ return false;
+ }
+
+ /**
+ * Returns the color palette for this terminal.
+ *
+ *
+ * The color palette provides access to the terminal's color capabilities,
+ * allowing for customization and mapping of colors to terminal-specific values.
+ * This is particularly useful for terminals that support different color modes
+ * (8-color, 256-color, or true color).
+ *
+ *
+ *
+ * The palette allows mapping between color values and their RGB representations,
+ * and provides methods for color conversion and manipulation.
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.terminal();
+ * ColorPalette palette = terminal.getPalette();
+ *
+ * // Get RGB values for a specific color
+ * int[] rgb = palette.toRgb(AttributedStyle.RED);
+ *
+ *
+ * @return the terminal's color palette
+ * @see org.jline.utils.ColorPalette
*/
ColorPalette getPalette();
+
+ /**
+ * Returns the terminal's default foreground color as an RGB value.
+ *
+ *
+ * This method provides access to the terminal's default text color, which can be
+ * useful for creating color schemes that complement the terminal's default colors.
+ * The color is returned as a packed RGB integer value (0xRRGGBB).
+ *
+ *
+ *
+ * If the terminal does not support color detection or the default color cannot
+ * be determined, this method returns -1.
+ *
+ *
+ * @return the RGB value (0xRRGGBB) of the default foreground color, or -1 if not available
+ * @see #getDefaultBackgroundColor()
+ * @see #getPalette()
+ * @since 3.30.0
+ */
+ default int getDefaultForegroundColor() {
+ return getPalette().getDefaultForeground();
+ }
+
+ /**
+ * Returns the terminal's default background color as an RGB value.
+ *
+ *
+ * This method provides access to the terminal's default background color, which can be
+ * useful for creating color schemes that complement the terminal's default colors.
+ * The color is returned as a packed RGB integer value (0xRRGGBB).
+ *
+ *
+ *
+ * If the terminal does not support color detection or the default color cannot
+ * be determined, this method returns -1.
+ *
+ *
+ * @return the RGB value (0xRRGGBB) of the default background color, or -1 if not available
+ * @see #getDefaultForegroundColor()
+ * @see #getPalette()
+ * @since 3.30.0
+ */
+ default int getDefaultBackgroundColor() {
+ return getPalette().getDefaultBackground();
+ }
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/TerminalBuilder.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/TerminalBuilder.java
index d66b124448e35..62b514c2cb4d6 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/TerminalBuilder.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/TerminalBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2021, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -17,15 +17,12 @@
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.Comparator;
-import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.ServiceLoader;
-import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -36,13 +33,97 @@
import jdk.internal.org.jline.terminal.impl.DumbTerminal;
import jdk.internal.org.jline.terminal.impl.DumbTerminalProvider;
import jdk.internal.org.jline.terminal.spi.SystemStream;
-import jdk.internal.org.jline.terminal.spi.TerminalExt;
import jdk.internal.org.jline.terminal.spi.TerminalProvider;
import jdk.internal.org.jline.utils.Log;
import jdk.internal.org.jline.utils.OSUtils;
/**
- * Builder class to create terminals.
+ * Builder class to create {@link Terminal} instances with flexible configuration options.
+ *
+ * TerminalBuilder provides a fluent API for creating and configuring terminals with various
+ * characteristics. It supports multiple implementation providers and handles the complexities
+ * of terminal creation across different platforms and environments.
+ *
+ *
+ * Terminal Providers
+ *
+ * JLine supports multiple terminal provider implementations:
+ *
+ *
+ * FFM - Foreign Function Memory (Java 22+) based implementation
+ * JNI - Java Native Interface based implementation
+ * Jansi - Implementation based on the Jansi library
+ * JNA - Java Native Access based implementation
+ * Exec - Implementation using external commands
+ * Dumb - Fallback implementation with limited capabilities
+ *
+ *
+ * The provider selection can be controlled using the {@link #provider(String)} method or the
+ * {@code org.jline.terminal.provider} system property. By default, providers are tried in the
+ * order: FFM, JNI, Jansi, JNA, Exec.
+ *
+ *
+ * Native Library Support
+ *
+ * When using providers that require native libraries (such as JNI, JNA, or Jansi), the appropriate
+ * native library will be loaded automatically. The loading of these libraries is handled by
+ * {@link org.jline.nativ.JLineNativeLoader} for the JNI provider.
+ *
+ *
+ * The native library loading can be configured using system properties as documented in
+ * {@link org.jline.nativ.JLineNativeLoader}.
+ *
+ *
+ * System vs. Non-System Terminals
+ *
+ * TerminalBuilder can create two types of terminals:
+ *
+ *
+ * System terminals - Connected to the actual system input/output streams
+ * Non-system terminals - Connected to custom input/output streams
+ *
+ *
+ * System terminals are created using {@link #system(boolean)} with a value of {@code true},
+ * while non-system terminals require specifying input and output streams using
+ * {@link #streams(InputStream, OutputStream)}.
+ *
+ *
+ * Usage Examples
+ *
+ * Creating a default system terminal:
+ *
+ * Terminal terminal = TerminalBuilder.builder()
+ * .system(true)
+ * .build();
+ *
+ *
+ * Creating a terminal with custom streams:
+ *
+ * Terminal terminal = TerminalBuilder.builder()
+ * .name("CustomTerminal")
+ * .streams(inputStream, outputStream)
+ * .encoding(StandardCharsets.UTF_8)
+ * .build();
+ *
+ *
+ * Creating a terminal with a specific provider:
+ *
+ * Terminal terminal = TerminalBuilder.builder()
+ * .system(true)
+ * .provider("jni")
+ * .build();
+ *
+ *
+ * Creating a dumb terminal (with limited capabilities):
+ *
+ * Terminal terminal = TerminalBuilder.builder()
+ * .dumb(true)
+ * .build();
+ *
+ *
+ * @see Terminal
+ * @see org.jline.nativ.JLineNativeLoader
+ * @see org.jline.terminal.spi.TerminalProvider
*/
public final class TerminalBuilder {
@@ -51,22 +132,21 @@ public final class TerminalBuilder {
//
public static final String PROP_ENCODING = "org.jline.terminal.encoding";
+ public static final String PROP_STDIN_ENCODING = "org.jline.terminal.stdin.encoding";
+ public static final String PROP_STDOUT_ENCODING = "org.jline.terminal.stdout.encoding";
+ public static final String PROP_STDERR_ENCODING = "org.jline.terminal.stderr.encoding";
public static final String PROP_CODEPAGE = "org.jline.terminal.codepage";
public static final String PROP_TYPE = "org.jline.terminal.type";
public static final String PROP_PROVIDER = "org.jline.terminal.provider";
public static final String PROP_PROVIDERS = "org.jline.terminal.providers";
public static final String PROP_PROVIDER_FFM = "ffm";
public static final String PROP_PROVIDER_JNI = "jni";
- public static final String PROP_PROVIDER_JANSI = "jansi";
- public static final String PROP_PROVIDER_JNA = "jna";
public static final String PROP_PROVIDER_EXEC = "exec";
public static final String PROP_PROVIDER_DUMB = "dumb";
- public static final String PROP_PROVIDERS_DEFAULT = String.join(
- ",", PROP_PROVIDER_FFM, PROP_PROVIDER_JNI, PROP_PROVIDER_JANSI, PROP_PROVIDER_JNA, PROP_PROVIDER_EXEC);
+ public static final String PROP_PROVIDERS_DEFAULT =
+ String.join(",", PROP_PROVIDER_FFM, PROP_PROVIDER_JNI, PROP_PROVIDER_EXEC);
public static final String PROP_FFM = "org.jline.terminal." + PROP_PROVIDER_FFM;
public static final String PROP_JNI = "org.jline.terminal." + PROP_PROVIDER_JNI;
- public static final String PROP_JANSI = "org.jline.terminal." + PROP_PROVIDER_JANSI;
- public static final String PROP_JNA = "org.jline.terminal." + PROP_PROVIDER_JNA;
public static final String PROP_EXEC = "org.jline.terminal." + PROP_PROVIDER_EXEC;
public static final String PROP_DUMB = "org.jline.terminal." + PROP_PROVIDER_DUMB;
public static final String PROP_DUMB_COLOR = "org.jline.terminal.dumb.color";
@@ -86,6 +166,55 @@ public final class TerminalBuilder {
public static final String PROP_COLOR_DISTANCE = "org.jline.utils.colorDistance";
public static final String PROP_DISABLE_ALTERNATE_CHARSET = "org.jline.utils.disableAlternateCharset";
+ /**
+ * System property to control terminal stream closure behavior.
+ *
+ * This property controls what happens when code attempts to read from or write to
+ * terminal streams (reader, writer, input, output) after the terminal has been closed.
+ *
+ *
+ * Two levels of closure enforcement:
+ *
+ *
+ * Terminal-level: Calling methods on the terminal itself after {@code close()}
+ * always throws {@link IllegalStateException}, regardless of this property.
+ * Stream-level: Using held references to streams obtained before {@code close()}
+ * is controlled by this property.
+ *
+ *
+ * Property values:
+ *
+ *
+ * {@code "strict"} (default in JLine 4.x): Accessing closed streams
+ * throws {@link org.jline.utils.ClosedException}
+ * {@code "warn"} (default in JLine 3.x): Accessing closed streams
+ * logs a warning but continues to operate
+ * {@code "lenient"} : Accessing closed streams is silently allowed
+ * (no warning, no exception)
+ *
+ *
+ * Example:
+ *
+ * {@code
+ * Terminal terminal = TerminalBuilder.terminal();
+ * NonBlockingReader reader = terminal.reader(); // Get reference before close
+ * terminal.close();
+ *
+ * // This always throws IllegalStateException (terminal-level):
+ * terminal.reader(); // throws IllegalStateException
+ *
+ * // This behavior depends on jline.terminal.closeMode (stream-level):
+ * reader.read(); // throws ClosedException in "strict" mode
+ * // logs warning in "warn" mode
+ * // silently continues in "lenient" mode
+ * }
+ *
+ * @see org.jline.utils.ClosedException
+ * @see org.jline.utils.NonBlockingInputStream
+ * @see org.jline.utils.NonBlockingReader
+ */
+ public static final String PROP_CLOSE_MODE = "jline.terminal.closeMode";
+
//
// System properties controlling how FileDescriptor are create.
// The value can be a comma separated list of defined mechanisms.
@@ -106,11 +235,15 @@ public final class TerminalBuilder {
public static final String PROP_REDIRECT_PIPE_CREATION_MODE_DEFAULT =
String.join(",", PROP_REDIRECT_PIPE_CREATION_MODE_REFLECTION, PROP_REDIRECT_PIPE_CREATION_MODE_NATIVE);
- public static final Set DEPRECATED_PROVIDERS =
- Collections.unmodifiableSet(new HashSet<>(Arrays.asList(PROP_PROVIDER_JNA, PROP_PROVIDER_JANSI)));
+ public static final String PROP_GRAPHEME_CLUSTER = "org.jline.terminal.graphemeCluster";
- public static final String PROP_DISABLE_DEPRECATED_PROVIDER_WARNING =
- "org.jline.terminal.disableDeprecatedProviderWarning";
+ // Graphics protocol properties
+ public static final String GRAPHICS_SIXEL_TIMEOUT = "org.jline.terminal.graphics.sixel.timeout";
+ public static final String GRAPHICS_SIXEL_SUBSEQUENT_TIMEOUT =
+ "org.jline.terminal.graphics.sixel.subsequent.timeout";
+ public static final String GRAPHICS_KITTY_TIMEOUT = "org.jline.terminal.graphics.kitty.timeout";
+ public static final String GRAPHICS_KITTY_SUBSEQUENT_TIMEOUT =
+ "org.jline.terminal.graphics.kitty.subsequent.timeout";
//
// Terminal output control
@@ -125,26 +258,70 @@ public enum SystemOutput {
}
/**
- * Returns the default system terminal.
- * Terminals should be closed properly using the {@link Terminal#close()}
- * method in order to restore the original terminal state.
+ * Returns the default system terminal with automatic configuration.
+ *
+ *
+ * This method creates a terminal connected to the system's standard input and output streams,
+ * automatically detecting the appropriate terminal type and capabilities for the current environment.
+ * It's the simplest way to get a working terminal instance for most applications.
+ *
+ *
+ *
+ * The terminal is created with default settings, which include:
+ *
+ * System streams for input and output
+ * Auto-detected terminal type
+ * System default encoding
+ * Native signal handling
+ *
*
*
* This call is equivalent to:
* builder().build()
*
*
- * @return the default system terminal
- * @throws IOException if an error occurs
+ *
+ * Important: Terminals should be closed properly using the {@link Terminal#close()}
+ * method when they are no longer needed in order to restore the original terminal state.
+ *
+ *
+ * Example usage:
+ *
+ * try (Terminal terminal = TerminalBuilder.terminal()) {
+ * terminal.writer().println("Hello, terminal!");
+ * terminal.flush();
+ * // Use terminal...
+ * }
+ *
+ *
+ * @return the default system terminal, never {@code null}
+ * @throws IOException if an error occurs during terminal creation
+ * @see #builder()
*/
public static Terminal terminal() throws IOException {
return builder().build();
}
/**
- * Creates a new terminal builder instance.
+ * Creates a new terminal builder instance for configuring and creating terminals.
*
- * @return a builder
+ *
+ * This method returns a builder that can be used to configure various aspects of the terminal
+ * before creating it. The builder provides a fluent API for setting terminal properties such as
+ * name, type, encoding, input/output streams, and more.
+ *
+ *
+ * Example usage:
+ *
+ * Terminal terminal = TerminalBuilder.builder()
+ * .name("MyTerminal")
+ * .system(true)
+ * .encoding(StandardCharsets.UTF_8)
+ * .build();
+ *
+ *
+ * @return a new terminal builder instance, never {@code null}
+ * @see #terminal()
*/
public static TerminalBuilder builder() {
return new TerminalBuilder();
@@ -158,13 +335,14 @@ public static TerminalBuilder builder() {
private OutputStream out;
private String type;
private Charset encoding;
+ private Charset stdinEncoding;
+ private Charset stdoutEncoding;
+ private Charset stderrEncoding;
private int codepage;
private Boolean system;
private SystemOutput systemOutput;
private String provider;
private String providers;
- private Boolean jna;
- private Boolean jansi;
private Boolean jni;
private Boolean exec;
private Boolean ffm;
@@ -176,6 +354,7 @@ public static TerminalBuilder builder() {
private Function inputStreamWrapper = in -> in;
private Terminal.SignalHandler signalHandler = Terminal.SignalHandler.SIG_DFL;
private boolean paused = false;
+ private Boolean graphemeCluster;
private TerminalBuilder() {}
@@ -233,30 +412,22 @@ public TerminalBuilder providers(String providers) {
return this;
}
- /**
- * Enables or disables the {@link #PROP_PROVIDER_JNA}/{@code jna} terminal provider.
- * If not specified, the system property {@link #PROP_JNA} will be used if set.
- * If not specified, the provider will be checked.
- */
- public TerminalBuilder jna(boolean jna) {
- this.jna = jna;
- return this;
- }
-
- /**
- * Enables or disables the {@link #PROP_PROVIDER_JANSI}/{@code jansi} terminal provider.
- * If not specified, the system property {@link #PROP_JANSI} will be used if set.
- * If not specified, the provider will be checked.
- */
- public TerminalBuilder jansi(boolean jansi) {
- this.jansi = jansi;
- return this;
- }
-
/**
* Enables or disables the {@link #PROP_PROVIDER_JNI}/{@code jni} terminal provider.
+ *
+ * The JNI provider uses the JLine native library loaded by {@link org.jline.nativ.JLineNativeLoader}
+ * to access low-level terminal functionality. This provider generally offers the best performance
+ * and most complete terminal support.
+ *
* If not specified, the system property {@link #PROP_JNI} will be used if set.
- * If not specified, the provider will be checked.
+ * If not specified, the provider will be checked for availability.
+ *
+ * The native library loading can be configured using system properties as documented in
+ * {@link org.jline.nativ.JLineNativeLoader}.
+ *
+ * @param jni true to enable the JNI provider, false to disable it
+ * @return this builder
+ * @see org.jline.nativ.JLineNativeLoader
*/
public TerminalBuilder jni(boolean jni) {
this.jni = jni;
@@ -283,6 +454,40 @@ public TerminalBuilder ffm(boolean ffm) {
return this;
}
+ /**
+ * No-op retained for backwards compatibility with JLine 3.
+ *
+ * In JLine 3, this method enabled native terminal support via Jansi.
+ * JLine 4 replaced Jansi with its own FFM/JNI providers that are used
+ * automatically, so this method is no longer needed.
+ *
+ * @param jansi ignored
+ * @return this builder
+ * @deprecated Jansi is no longer used in JLine 4. Native support is provided
+ * automatically via FFM or JNI providers.
+ */
+ @Deprecated
+ public TerminalBuilder jansi(boolean jansi) {
+ return this;
+ }
+
+ /**
+ * No-op retained for backwards compatibility with JLine 3.
+ *
+ * In JLine 3, this method enabled native terminal support via JNA.
+ * JLine 4 replaced JNA with its own FFM/JNI providers that are used
+ * automatically, so this method is no longer needed.
+ *
+ * @param jna ignored
+ * @return this builder
+ * @deprecated JNA is no longer used in JLine 4. Native support is provided
+ * automatically via FFM or JNI providers.
+ */
+ @Deprecated
+ public TerminalBuilder jna(boolean jna) {
+ return this;
+ }
+
/**
* Enables or disables the {@link #PROP_PROVIDER_DUMB}/{@code dumb} terminal provider.
* If not specified, the system property {@link #PROP_DUMB} will be used if set.
@@ -313,6 +518,10 @@ public TerminalBuilder color(boolean color) {
*
Use {@link Terminal#encoding()} to get the {@link Charset} that
* should be used for a {@link Terminal}.
*
+ * This method sets a single encoding for all streams (stdin, stdout, stderr).
+ * To set separate encodings for each stream, use {@link #stdinEncoding(Charset)},
+ * {@link #stdoutEncoding(Charset)}, and {@link #stderrEncoding(Charset)}.
+ *
* @param encoding The encoding to use or null to automatically select one
* @return The builder
* @throws UnsupportedCharsetException If the given encoding is not supported
@@ -332,6 +541,10 @@ public TerminalBuilder encoding(String encoding) throws UnsupportedCharsetExcept
* Use {@link Terminal#encoding()} to get the {@link Charset} that
* should be used to read/write from a {@link Terminal}.
*
+ * This method sets a single encoding for all streams (stdin, stdout, stderr).
+ * To set separate encodings for each stream, use {@link #stdinEncoding(Charset)},
+ * {@link #stdoutEncoding(Charset)}, and {@link #stderrEncoding(Charset)}.
+ *
* @param encoding The encoding to use or null to automatically select one
* @return The builder
* @see Terminal#encoding()
@@ -342,15 +555,92 @@ public TerminalBuilder encoding(Charset encoding) {
}
/**
- * @param codepage the codepage
+ * Set the encoding to use for reading from standard input.
+ * If {@code null} (the default value), JLine will use the value from
+ * the "stdin.encoding" system property if set, or fall back to the
+ * general encoding.
+ *
+ * @param encoding The encoding to use or null to automatically select one
* @return The builder
- * @deprecated JLine now writes Unicode output independently from the selected
- * code page. Using this option will only make it emulate the selected code
- * page for {@link Terminal#input()} and {@link Terminal#output()}.
+ * @throws UnsupportedCharsetException If the given encoding is not supported
+ * @see Terminal#inputEncoding()
*/
- @Deprecated
- public TerminalBuilder codepage(int codepage) {
- this.codepage = codepage;
+ public TerminalBuilder stdinEncoding(String encoding) throws UnsupportedCharsetException {
+ return stdinEncoding(encoding != null ? Charset.forName(encoding) : null);
+ }
+
+ /**
+ * Set the {@link Charset} to use for reading from standard input.
+ * If {@code null} (the default value), JLine will use the value from
+ * the "stdin.encoding" system property if set, or fall back to the
+ * general encoding.
+ *
+ * @param encoding The encoding to use or null to automatically select one
+ * @return The builder
+ * @see Terminal#inputEncoding()
+ */
+ public TerminalBuilder stdinEncoding(Charset encoding) {
+ this.stdinEncoding = encoding;
+ return this;
+ }
+
+ /**
+ * Set the encoding to use for writing to standard output.
+ * If {@code null} (the default value), JLine will use the value from
+ * the "stdout.encoding" system property if set, or fall back to the
+ * general encoding.
+ *
+ * @param encoding The encoding to use or null to automatically select one
+ * @return The builder
+ * @throws UnsupportedCharsetException If the given encoding is not supported
+ * @see Terminal#outputEncoding()
+ */
+ public TerminalBuilder stdoutEncoding(String encoding) throws UnsupportedCharsetException {
+ return stdoutEncoding(encoding != null ? Charset.forName(encoding) : null);
+ }
+
+ /**
+ * Set the {@link Charset} to use for writing to standard output.
+ * If {@code null} (the default value), JLine will use the value from
+ * the "stdout.encoding" system property if set, or fall back to the
+ * general encoding.
+ *
+ * @param encoding The encoding to use or null to automatically select one
+ * @return The builder
+ * @see Terminal#outputEncoding()
+ */
+ public TerminalBuilder stdoutEncoding(Charset encoding) {
+ this.stdoutEncoding = encoding;
+ return this;
+ }
+
+ /**
+ * Set the encoding to use for writing to standard error.
+ * If {@code null} (the default value), JLine will use the value from
+ * the "stderr.encoding" system property if set, or fall back to the
+ * general encoding.
+ *
+ * @param encoding The encoding to use or null to automatically select one
+ * @return The builder
+ * @throws UnsupportedCharsetException If the given encoding is not supported
+ * @see Terminal#outputEncoding()
+ */
+ public TerminalBuilder stderrEncoding(String encoding) throws UnsupportedCharsetException {
+ return stderrEncoding(encoding != null ? Charset.forName(encoding) : null);
+ }
+
+ /**
+ * Set the {@link Charset} to use for writing to standard error.
+ * If {@code null} (the default value), JLine will use the value from
+ * the "stderr.encoding" system property if set, or fall back to the
+ * general encoding.
+ *
+ * @param encoding The encoding to use or null to automatically select one
+ * @return The builder
+ * @see Terminal#outputEncoding()
+ */
+ public TerminalBuilder stderrEncoding(Charset encoding) {
+ this.stderrEncoding = encoding;
return this;
}
@@ -425,9 +715,42 @@ public TerminalBuilder paused(boolean paused) {
}
/**
- * Builds the terminal.
- * @return the newly created terminal, never {@code null}
- * @throws IOException if an error occurs
+ * Controls whether mode 2027 (grapheme cluster segmentation) should be
+ * enabled on the terminal after construction.
+ *
+ * When enabled, the terminal uses UAX #29 grapheme cluster boundaries
+ * for cursor positioning, which allows multi-codepoint characters like
+ * ZWJ emoji sequences (e.g., family emoji) to be treated as single
+ * display units.
+ *
+ * By default ({@code null}), mode 2027 is automatically enabled if
+ * the terminal supports it. Set to {@code false} to explicitly disable
+ * grapheme cluster mode even on terminals that support it.
+ *
+ * This can also be controlled via the system property
+ * {@link #PROP_GRAPHEME_CLUSTER}.
+ *
+ * @param graphemeCluster {@code true} to enable, {@code false} to disable,
+ * or leave unset for auto-detection
+ * @return The builder
+ * @see Terminal#supportsGraphemeClusterMode()
+ * @see Terminal#setGraphemeClusterMode(boolean, boolean)
+ */
+ public TerminalBuilder graphemeCluster(boolean graphemeCluster) {
+ this.graphemeCluster = graphemeCluster;
+ return this;
+ }
+
+ /**
+ * Create and configure a Terminal instance according to this builder's settings.
+ *
+ * If a global terminal override has been set, that instance is returned instead of creating a new one.
+ * The method also applies grapheme-cluster mode to the created terminal based on the builder setting
+ * or the `org.jline.terminal.graphemeCluster` system property: it may force-enable, disable, or
+ * probe-and-enable grapheme-cluster handling on the terminal.
+ *
+ * @return the configured Terminal instance; never {@code null}
+ * @throws IOException if terminal creation or configuration fails
*/
public Terminal build() throws IOException {
Terminal override = TERMINAL_OVERRIDE.get();
@@ -440,6 +763,24 @@ public Terminal build() throws IOException {
Log.debug(() -> "Using pty "
+ ((AbstractPosixTerminal) terminal).getPty().getClass().getSimpleName());
}
+ // Enable grapheme cluster mode if supported
+ Boolean gc = this.graphemeCluster;
+ if (gc == null) {
+ gc = getBoolean(PROP_GRAPHEME_CLUSTER, null);
+ }
+ if (Boolean.TRUE.equals(gc)) {
+ // Force-enable: skip probing, treat as native grapheme support
+ terminal.setGraphemeClusterMode(true, true);
+ Log.debug(() -> "Grapheme cluster mode: force-enabled (skipping probe)");
+ } else if (Boolean.FALSE.equals(gc)) {
+ Log.debug(() -> "Grapheme cluster mode: disabled by configuration");
+ } else if (terminal.supportsGraphemeClusterMode()) {
+ // Auto-detect: probe terminal and enable if supported
+ terminal.setGraphemeClusterMode(true, false);
+ Log.debug(() -> "Grapheme cluster mode: enabled (auto-detected)");
+ } else {
+ Log.debug(() -> "Grapheme cluster mode: not supported by terminal");
+ }
return terminal;
}
@@ -448,7 +789,6 @@ private Terminal doBuild() throws IOException {
if (name == null) {
name = "JLine terminal";
}
- Charset encoding = computeEncoding();
String type = computeType();
String provider = this.provider;
@@ -465,6 +805,20 @@ private Terminal doBuild() throws IOException {
}
IllegalStateException exception = new IllegalStateException("Unable to create a terminal");
List providers = getProviders(provider, exception);
+
+ // Query providers for console codepage (Windows auto-detection)
+ int consoleCodepage = -1;
+ for (TerminalProvider prov : providers) {
+ consoleCodepage = prov.getConsoleCodepage();
+ if (consoleCodepage >= 0) {
+ break;
+ }
+ }
+
+ Charset encoding = computeEncoding(consoleCodepage);
+ Charset stdinEncoding = computeStdinEncoding();
+ Charset stdoutEncoding = computeStdoutEncoding();
+ Charset stderrEncoding = computeStderrEncoding();
Terminal terminal = null;
if ((system != null && system) || (system == null && in == null && out == null)) {
if (system != null
@@ -497,11 +851,16 @@ private Terminal doBuild() throws IOException {
for (TerminalProvider prov : providers) {
if (terminal == null) {
try {
+ // Use the appropriate output encoding based on the system stream
+ Charset outputEncoding =
+ systemStream == SystemStream.Error ? stderrEncoding : stdoutEncoding;
terminal = prov.sysTerminal(
name,
type,
ansiPassThrough,
encoding,
+ stdinEncoding,
+ outputEncoding,
nativeSignals,
signalHandler,
paused,
@@ -534,19 +893,40 @@ private Terminal doBuild() throws IOException {
}
if (terminal == null && (forceDumb || dumb == null || dumb)) {
if (!forceDumb && dumb == null) {
- if (Log.isDebugEnabled()) {
- Log.warn("input is tty: " + system.get(SystemStream.Input));
- Log.warn("output is tty: " + system.get(SystemStream.Output));
- Log.warn("error is tty: " + system.get(SystemStream.Error));
- Log.warn("Creating a dumb terminal", exception);
- } else {
- Log.warn(
- "Unable to create a system terminal, creating a dumb terminal (enable debug logging for more information)");
+ // Only warn if providers were available but couldn't create a terminal,
+ // or if no providers loaded at all (can't determine TTY status).
+ // When providers detect that no streams are TTYs, dumb fallback is expected.
+ boolean noTty = !system.get(SystemStream.Input)
+ && !system.get(SystemStream.Output)
+ && !system.get(SystemStream.Error);
+ if (providers.isEmpty() || !noTty) {
+ if (Log.isDebugEnabled()) {
+ Log.warn("input is tty: " + system.get(SystemStream.Input));
+ Log.warn("output is tty: " + system.get(SystemStream.Output));
+ Log.warn("error is tty: " + system.get(SystemStream.Error));
+ Log.warn("Creating a dumb terminal", exception);
+ } else {
+ Log.warn(
+ "Unable to create a system terminal, creating a dumb terminal (enable debug logging for more information)");
+ }
}
}
type = getDumbTerminalType(dumb, systemStream);
+ // Use the appropriate output encoding based on the system stream
+ Charset outputEncoding = systemStream == SystemStream.Error ? stderrEncoding : stdoutEncoding;
terminal = new DumbTerminalProvider()
- .sysTerminal(name, type, false, encoding, nativeSignals, signalHandler, paused, systemStream, inputStreamWrapper);
+ .sysTerminal(
+ name,
+ type,
+ false,
+ encoding,
+ stdinEncoding,
+ outputEncoding,
+ nativeSignals,
+ signalHandler,
+ paused,
+ systemStream,
+ inputStreamWrapper);
if (OSUtils.IS_WINDOWS) {
Attributes attr = terminal.getAttributes();
attr.setInputFlag(Attributes.InputFlag.IGNCR, true);
@@ -558,7 +938,17 @@ private Terminal doBuild() throws IOException {
if (terminal == null) {
try {
terminal = prov.newTerminal(
- name, type, in, out, encoding, signalHandler, paused, attributes, size);
+ name,
+ type,
+ in,
+ out,
+ encoding,
+ stdinEncoding,
+ stdoutEncoding,
+ signalHandler,
+ paused,
+ attributes,
+ size);
} catch (Throwable t) {
Log.debug("Error creating " + prov.name() + " based terminal: ", t.getMessage(), t);
exception.addSuppressed(t);
@@ -569,15 +959,7 @@ private Terminal doBuild() throws IOException {
if (terminal == null) {
throw exception;
}
- if (terminal instanceof TerminalExt) {
- TerminalExt te = (TerminalExt) terminal;
- if (DEPRECATED_PROVIDERS.contains(te.getProvider().name())
- && !getBoolean(PROP_DISABLE_DEPRECATED_PROVIDER_WARNING, false)) {
- Log.warn("The terminal provider " + te.getProvider().name()
- + " has been deprecated, check your configuration. This warning can be disabled by setting the system property "
- + PROP_DISABLE_DEPRECATED_PROVIDER_WARNING + " to true.");
- }
- }
+
return terminal;
}
@@ -684,6 +1066,10 @@ public String computeType() {
}
public Charset computeEncoding() {
+ return computeEncoding(-1);
+ }
+
+ Charset computeEncoding(int consoleCodepage) {
Charset encoding = this.encoding;
if (encoding == null) {
String charsetName = System.getProperty(PROP_ENCODING);
@@ -699,7 +1085,12 @@ public Charset computeEncoding() {
codepage = Integer.parseInt(str);
}
}
- if (codepage >= 0) {
+ // Auto-detect Windows console codepage if not explicitly set
+ // Only auto-detect when codepage == 0 (unset), not -1 (explicitly set to force UTF-8)
+ if (codepage == 0 && OSUtils.IS_WINDOWS && !OSUtils.IS_CYGWIN && !OSUtils.IS_MSYSTEM) {
+ codepage = consoleCodepage;
+ }
+ if (codepage > 0) {
encoding = getCodepageCharset(codepage);
} else {
encoding = StandardCharsets.UTF_8;
@@ -708,6 +1099,48 @@ public Charset computeEncoding() {
return encoding;
}
+ public Charset computeStdinEncoding() {
+ return computeSpecificEncoding(this.stdinEncoding, PROP_STDIN_ENCODING, "stdin.encoding");
+ }
+
+ public Charset computeStdoutEncoding() {
+ return computeSpecificEncoding(this.stdoutEncoding, PROP_STDOUT_ENCODING, "stdout.encoding");
+ }
+
+ public Charset computeStderrEncoding() {
+ return computeSpecificEncoding(this.stderrEncoding, PROP_STDERR_ENCODING, "stderr.encoding");
+ }
+
+ /**
+ * Helper method to compute encoding from a specific field, JLine property, and standard Java property.
+ *
+ * @param specificEncoding the specific encoding field value
+ * @param jlineProperty the JLine-specific property name
+ * @param standardProperty the standard Java property name
+ * @return the computed encoding, falling back to the general encoding if needed
+ */
+ private Charset computeSpecificEncoding(Charset specificEncoding, String jlineProperty, String standardProperty) {
+ Charset encoding = specificEncoding;
+ if (encoding == null) {
+ // First try JLine specific property
+ String charsetName = System.getProperty(jlineProperty);
+ if (charsetName != null && Charset.isSupported(charsetName)) {
+ encoding = Charset.forName(charsetName);
+ }
+ // Then try standard Java property
+ if (encoding == null) {
+ charsetName = System.getProperty(standardProperty);
+ if (charsetName != null && Charset.isSupported(charsetName)) {
+ encoding = Charset.forName(charsetName);
+ }
+ }
+ }
+ if (encoding == null) {
+ encoding = computeEncoding();
+ }
+ return encoding;
+ }
+
/**
* Get the list of available terminal providers.
* This list is sorted according to the {@link #PROP_PROVIDERS} system property.
@@ -721,10 +1154,6 @@ public List getProviders(String provider, IllegalStateExceptio
checkProvider(provider, exception, providers, ffm, PROP_FFM, PROP_PROVIDER_FFM);
// Check jni provider
checkProvider(provider, exception, providers, jni, PROP_JNI, PROP_PROVIDER_JNI);
- // Check jansi provider
- checkProvider(provider, exception, providers, jansi, PROP_JANSI, PROP_PROVIDER_JANSI);
- // Check jna provider
- checkProvider(provider, exception, providers, jna, PROP_JNA, PROP_PROVIDER_JNA);
// Check exec provider
checkProvider(provider, exception, providers, exec, PROP_EXEC, PROP_PROVIDER_EXEC);
// Order providers
@@ -866,6 +1295,9 @@ private static Charset getCodepageCharset(int codepage) {
*
*
* @param terminal the {@link Terminal} to globally override
+ * @deprecated This method is deprecated to discourage its use. It will remain
+ * available but users should avoid it when possible and use proper
+ * dependency injection instead.
*/
@Deprecated
public static void setTerminalOverride(final Terminal terminal) {
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractPosixTerminal.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractPosixTerminal.java
index 862bf93cd46a6..f4ee4a3d8169e 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractPosixTerminal.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractPosixTerminal.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -11,6 +11,9 @@
import java.io.IOError;
import java.io.IOException;
import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.Objects;
import java.util.function.IntConsumer;
@@ -20,7 +23,44 @@
import jdk.internal.org.jline.terminal.spi.Pty;
import jdk.internal.org.jline.terminal.spi.SystemStream;
import jdk.internal.org.jline.terminal.spi.TerminalProvider;
+import jdk.internal.org.jline.utils.NonBlockingReader;
+/**
+ * Base implementation for terminals on POSIX-compliant systems.
+ *
+ *
+ * The AbstractPosixTerminal class provides a foundation for terminal implementations
+ * on POSIX-compliant systems such as Linux, macOS, and other Unix-like operating
+ * systems. It builds on the AbstractTerminal class and adds POSIX-specific
+ * functionality, particularly related to pseudoterminal (PTY) handling.
+ *
+ *
+ *
+ * This class manages the interaction with the underlying PTY, handling terminal
+ * attributes, size changes, and other POSIX-specific terminal operations. It
+ * provides implementations for many of the abstract methods defined in
+ * AbstractTerminal, leaving only a few methods to be implemented by concrete
+ * subclasses.
+ *
+ *
+ *
+ * Key features provided by this class include:
+ *
+ *
+ * PTY management and interaction
+ * Terminal attribute preservation and restoration
+ * Size handling and window change signals
+ * Cursor position detection
+ *
+ *
+ *
+ * This class is designed to be extended by concrete implementations that target
+ * specific POSIX platforms or environments.
+ *
+ *
+ * @see org.jline.terminal.impl.AbstractTerminal
+ * @see org.jline.terminal.spi.Pty
+ */
public abstract class AbstractPosixTerminal extends AbstractTerminal {
protected final Pty pty;
@@ -32,7 +72,19 @@ public AbstractPosixTerminal(String name, String type, Pty pty) throws IOExcepti
public AbstractPosixTerminal(String name, String type, Pty pty, Charset encoding, SignalHandler signalHandler)
throws IOException {
- super(name, type, encoding, signalHandler);
+ this(name, type, pty, encoding, encoding, encoding, signalHandler);
+ }
+
+ public AbstractPosixTerminal(
+ String name,
+ String type,
+ Pty pty,
+ Charset encoding,
+ Charset inputEncoding,
+ Charset outputEncoding,
+ SignalHandler signalHandler)
+ throws IOException {
+ super(name, type, encoding, inputEncoding, outputEncoding, signalHandler);
Objects.requireNonNull(pty);
this.pty = pty;
this.originalAttributes = this.pty.getAttr();
@@ -43,6 +95,7 @@ public Pty getPty() {
}
public Attributes getAttributes() {
+ checkClosed();
try {
return pty.getAttr();
} catch (IOException e) {
@@ -51,6 +104,7 @@ public Attributes getAttributes() {
}
public void setAttributes(Attributes attr) {
+ checkClosed();
try {
pty.setAttr(attr);
} catch (IOException e) {
@@ -59,6 +113,7 @@ public void setAttributes(Attributes attr) {
}
public Size getSize() {
+ checkClosed();
try {
return pty.getSize();
} catch (IOException e) {
@@ -67,6 +122,7 @@ public Size getSize() {
}
public void setSize(Size size) {
+ checkClosed();
try {
pty.setSize(size);
} catch (IOException e) {
@@ -103,4 +159,135 @@ public String toString() {
+ type + '\'' + ", size='"
+ getSize() + '\'' + ']';
}
+
+ @Override
+ public int getDefaultForegroundColor() {
+ try {
+ // Send OSC 10 query
+ writer().write("\033]10;?\033\\");
+ writer().flush();
+
+ // Read response
+ return parseColorResponse(reader(), 10);
+ } catch (IOException e) {
+ return -1;
+ }
+ }
+
+ @Override
+ public int getDefaultBackgroundColor() {
+ try {
+ // Send OSC 11 query
+ writer().write("\033]11;?\033\\");
+ writer().flush();
+
+ // Read response
+ return parseColorResponse(reader(), 11);
+ } catch (IOException e) {
+ return -1;
+ }
+ }
+
+ int parseColorResponse(NonBlockingReader reader, int colorType) throws IOException {
+ if (reader.peek(50) < 0) {
+ return -1;
+ }
+
+ if (!readOscHeader(reader, colorType)) {
+ return -1;
+ }
+
+ List rgb = readRgbValues(reader);
+ if (rgb.size() != 3) {
+ return -1;
+ }
+
+ return convertRgbToInt(rgb);
+ }
+
+ /**
+ * Reads and validates the OSC header: ESC ] {colorType} ; rgb:
+ */
+ private boolean readOscHeader(NonBlockingReader reader, int colorType) throws IOException {
+ // Check for OSC sequence start
+ if (reader.read(10) != '\033' || reader.read(10) != ']') {
+ return false;
+ }
+
+ // Check for color type (10 or 11)
+ int tens = reader.read(10);
+ int ones = reader.read(10);
+ if (tens != '1' || (ones != '0' && ones != '1')) {
+ return false;
+ }
+
+ // Check that the type matches what we expect
+ int type = (ones - '0') + 10;
+ if (type != colorType) {
+ return false;
+ }
+
+ // Check for separator
+ if (reader.read(10) != ';') {
+ return false;
+ }
+
+ // Check for rgb: format
+ return reader.read(10) == 'r' && reader.read(10) == 'g' && reader.read(10) == 'b' && reader.read(10) == ':';
+ }
+
+ /**
+ * Reads RGB hex values separated by '/' and terminated by BEL or ST.
+ * Returns an empty list on EOF, timeout, or invalid input.
+ */
+ private List readRgbValues(NonBlockingReader reader) throws IOException {
+ StringBuilder sb = new StringBuilder(16);
+ List rgb = new ArrayList<>();
+ while (true) {
+ int c = reader.read(10);
+ if (c == -1) {
+ return Collections.emptyList(); // EOF โ stream closed
+ }
+ if (c == NonBlockingReader.READ_EXPIRED) {
+ return Collections.emptyList(); // timeout โ terminal not responding within probe window
+ }
+ if (c == '\007') {
+ rgb.add(sb.toString());
+ return rgb;
+ }
+ if (c == '\033') {
+ return readStTerminator(reader) ? addAndReturn(rgb, sb.toString()) : Collections.emptyList();
+ }
+ if (isHexChar(c)) {
+ sb.append((char) c);
+ } else if (c == '/') {
+ rgb.add(sb.toString());
+ sb.setLength(0);
+ }
+ }
+ }
+
+ private boolean readStTerminator(NonBlockingReader reader) throws IOException {
+ return reader.read(10) == '\\';
+ }
+
+ private static List addAndReturn(List list, String value) {
+ list.add(value);
+ return list;
+ }
+
+ private static boolean isHexChar(int c) {
+ return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
+ }
+
+ /**
+ * Converts a list of three hex RGB strings to a single packed int (0xRRGGBB).
+ */
+ private static int convertRgbToInt(List rgb) {
+ double r = Integer.parseInt(rgb.get(0), 16) / ((1 << (4 * rgb.get(0).length())) - 1.0);
+ double g = Integer.parseInt(rgb.get(1), 16) / ((1 << (4 * rgb.get(1).length())) - 1.0);
+ double b = Integer.parseInt(rgb.get(2), 16) / ((1 << (4 * rgb.get(2).length())) - 1.0);
+
+ return (int) ((Math.round(r * 255) << 16) + (Math.round(g * 255) << 8) + Math.round(b * 255));
+ }
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractPty.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractPty.java
index 3fbe63f3c5d6b..1a9a7d3b410c4 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractPty.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractPty.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2019, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -30,6 +30,32 @@
import static jdk.internal.org.jline.terminal.TerminalBuilder.PROP_FILE_DESCRIPTOR_CREATION_MODE_REFLECTION;
import static jdk.internal.org.jline.terminal.TerminalBuilder.PROP_NON_BLOCKING_READS;
+/**
+ * Base implementation of the Pty interface.
+ *
+ *
+ * The AbstractPty class provides a common foundation for pseudoterminal (PTY)
+ * implementations. It handles common functionality such as system stream management
+ * and provider access, while leaving platform-specific PTY operations to be
+ * implemented by concrete subclasses.
+ *
+ *
+ *
+ * This class serves as the base for various PTY implementations, including:
+ *
+ *
+ * Native PTY implementations (JNI, JNA, FFM) for direct access to system PTYs
+ * Exec PTY implementation that uses external commands
+ *
+ *
+ *
+ * The AbstractPty maintains information about the associated system stream and
+ * terminal provider, which are common to all PTY implementations regardless of
+ * the underlying mechanism used to interact with the terminal.
+ *
+ *
+ * @see org.jline.terminal.spi.Pty
+ */
public abstract class AbstractPty implements Pty {
protected final TerminalProvider provider;
@@ -110,6 +136,7 @@ class PtyInputStream extends NonBlockingInputStream {
@Override
public int read(long timeout, boolean isPeek) throws IOException {
+ checkClosed();
checkInterrupted();
if (c != 0) {
int r = c;
@@ -192,7 +219,7 @@ interface FileDescriptorCreator {
/*
* Class that could be used on OpenJDK 17. However, it requires the following JVM option
- * --add-exports java.base/jdk.internal.access=ALL-UNNAMED
+ * --add-exports java.base/jdk.internal.access=org.jline.terminal
* so the benefit does not seem important enough to warrant the problems caused
* by access the jdk.internal.access package at compile time, which itself requires
* custom compiler options and a different maven module, or at least a different compile
@@ -217,7 +244,7 @@ public FileDescriptor newDescriptor(int fd) {
/**
* Reflection based file descriptor creator.
* This requires the following option
- * --add-opens java.base/java.io=ALL-UNNAMED
+ * --add-opens java.base/java.io=org.jline.terminal
*/
static class ReflectionFileDescriptorCreator implements FileDescriptorCreator {
private final Field fileDescriptorField;
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractTerminal.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractTerminal.java
index 34b070487908d..0ce46191200ea 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractTerminal.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractTerminal.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2021, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -8,6 +8,7 @@
*/
package jdk.internal.org.jline.terminal.impl;
+import java.io.IOError;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.nio.charset.Charset;
@@ -28,18 +29,54 @@
import jdk.internal.org.jline.terminal.Cursor;
import jdk.internal.org.jline.terminal.MouseEvent;
import jdk.internal.org.jline.terminal.spi.TerminalExt;
+import jdk.internal.org.jline.utils.AttributedCharSequence;
import jdk.internal.org.jline.utils.ColorPalette;
import jdk.internal.org.jline.utils.Curses;
import jdk.internal.org.jline.utils.InfoCmp;
import jdk.internal.org.jline.utils.InfoCmp.Capability;
import jdk.internal.org.jline.utils.Log;
import jdk.internal.org.jline.utils.Status;
+import jdk.internal.org.jline.utils.WCWidth;
+/**
+ * Base implementation of the Terminal interface.
+ *
+ *
+ * This abstract class provides a common foundation for terminal implementations,
+ * handling many of the core terminal functions such as signal handling, attribute
+ * management, and capability lookup. It implements most of the methods defined in
+ * the {@link org.jline.terminal.Terminal} interface, leaving only a few abstract
+ * methods to be implemented by concrete subclasses.
+ *
+ *
+ *
+ * Terminal implementations typically extend this class and provide implementations
+ * for the abstract methods related to their specific platform or environment.
+ * This class handles the common functionality, allowing subclasses to focus on
+ * platform-specific details.
+ *
+ *
+ *
+ * Key features provided by this class include:
+ *
+ *
+ * Signal handling infrastructure
+ * Terminal attribute management
+ * Terminal capability lookup and caching
+ * Size and cursor position handling
+ * Mouse and focus tracking support
+ *
+ *
+ * @see org.jline.terminal.Terminal
+ * @see org.jline.terminal.spi.TerminalExt
+ */
public abstract class AbstractTerminal implements TerminalExt {
protected final String name;
protected final String type;
protected final Charset encoding;
+ protected final Charset inputEncoding;
+ protected final Charset outputEncoding;
protected final Map handlers = new ConcurrentHashMap<>();
protected final Set bools = new HashSet<>();
protected final Map ints = new HashMap<>();
@@ -47,7 +84,31 @@ public abstract class AbstractTerminal implements TerminalExt {
protected final ColorPalette palette;
protected Status status;
protected Runnable onClose;
-
+ protected MouseTracking currentMouseTracking = MouseTracking.Off;
+ protected volatile boolean closed = false;
+ private Boolean graphemeClusterModeSupported;
+ private boolean graphemeClusterModeEnabled;
+ private boolean graphemeClusterNative;
+ private boolean groupsRegionalIndicators;
+ private boolean groupsZwjSequences;
+
+ /** Result of the Mode 2027 probe. */
+ private enum ProbeResult {
+ /** Mode 2027 is supported. */
+ SUPPORTED,
+ /** Terminal responded but does not support Mode 2027. */
+ NOT_SUPPORTED,
+ /** Terminal did not respond at all. */
+ NO_RESPONSE
+ }
+
+ /**
+ * Create a terminal with the given name and type using the platform default charset and the default signal handler.
+ *
+ * @param name the terminal name (may be {@code null})
+ * @param type the terminal type (may be {@code null}, a default will be used when absent)
+ * @throws IOException if an I/O error occurs while constructing the terminal
+ */
public AbstractTerminal(String name, String type) throws IOException {
this(name, type, null, SignalHandler.SIG_DFL);
}
@@ -55,9 +116,23 @@ public AbstractTerminal(String name, String type) throws IOException {
@SuppressWarnings("this-escape")
public AbstractTerminal(String name, String type, Charset encoding, SignalHandler signalHandler)
throws IOException {
+ this(name, type, encoding, encoding, encoding, signalHandler);
+ }
+
+ @SuppressWarnings("this-escape")
+ public AbstractTerminal(
+ String name,
+ String type,
+ Charset encoding,
+ Charset inputEncoding,
+ Charset outputEncoding,
+ SignalHandler signalHandler)
+ throws IOException {
this.name = name;
this.type = type != null ? type : "ansi";
this.encoding = encoding != null ? encoding : System.out.charset();
+ this.inputEncoding = inputEncoding != null ? inputEncoding : this.encoding;
+ this.outputEncoding = outputEncoding != null ? outputEncoding : this.encoding;
this.palette = new ColorPalette(this);
for (Signal signal : Signal.values()) {
handlers.put(signal, signalHandler);
@@ -108,9 +183,13 @@ public final void close() throws IOException {
}
protected void doClose() throws IOException {
+ if (graphemeClusterModeEnabled) {
+ setGraphemeClusterMode(false, false);
+ }
if (status != null) {
status.close();
}
+ closed = true;
}
protected void echoSignal(Signal signal) {
@@ -176,6 +255,16 @@ public Charset encoding() {
return this.encoding;
}
+ @Override
+ public Charset inputEncoding() {
+ return this.inputEncoding;
+ }
+
+ @Override
+ public Charset outputEncoding() {
+ return this.outputEncoding;
+ }
+
public void flush() {
writer().flush();
}
@@ -209,9 +298,33 @@ protected void parseInfoCmp() {
Log.warn("Unable to retrieve infocmp for type " + type, e);
}
if (capabilities == null) {
- capabilities = InfoCmp.getLoadedInfoCmp("ansi");
+ capabilities = InfoCmp.getDefaultInfoCmp("ansi");
}
InfoCmp.parseInfoCmp(capabilities, bools, ints, strings);
+ detectTrueColorSupport();
+ }
+
+ /**
+ * Detects true color support from environment variables and upgrades
+ * {@code max_colors} accordingly. Subclasses for remote terminals can
+ * override this to check the remote client's environment instead.
+ *
+ * @see True Color detection
+ */
+ protected void detectTrueColorSupport() {
+ Integer maxColors = ints.get(Capability.max_colors);
+ if (maxColors != null && maxColors >= 0x7FFF) {
+ return; // already true-color capable
+ }
+ String colorterm = System.getenv("COLORTERM");
+ if (colorterm != null) {
+ colorterm = colorterm.toLowerCase(java.util.Locale.ROOT);
+ }
+ if (colorterm != null && (colorterm.contains("truecolor") || colorterm.contains("24bit"))) {
+ ints.put(Capability.max_colors, AttributedCharSequence.TRUE_COLORS);
+ } else if (type != null && type.contains("-direct")) {
+ ints.put(Capability.max_colors, AttributedCharSequence.TRUE_COLORS);
+ }
}
@Override
@@ -227,19 +340,39 @@ public boolean hasMouseSupport() {
return MouseSupport.hasMouseSupport(this);
}
+ @Override
+ public MouseTracking getCurrentMouseTracking() {
+ return currentMouseTracking;
+ }
+
@Override
public boolean trackMouse(MouseTracking tracking) {
- return MouseSupport.trackMouse(this, tracking);
+ if (MouseSupport.trackMouse(this, tracking)) {
+ currentMouseTracking = tracking;
+ return true;
+ } else {
+ return false;
+ }
}
@Override
public MouseEvent readMouseEvent() {
- return lastMouseEvent = MouseSupport.readMouse(this, lastMouseEvent);
+ return readMouseEvent(getStringCapability(Capability.key_mouse));
}
@Override
public MouseEvent readMouseEvent(IntSupplier reader) {
- return lastMouseEvent = MouseSupport.readMouse(reader, lastMouseEvent);
+ return readMouseEvent(reader, getStringCapability(Capability.key_mouse));
+ }
+
+ @Override
+ public MouseEvent readMouseEvent(String prefix) {
+ return lastMouseEvent = MouseSupport.readMouse(this, lastMouseEvent, prefix);
+ }
+
+ @Override
+ public MouseEvent readMouseEvent(IntSupplier reader, String prefix) {
+ return lastMouseEvent = MouseSupport.readMouse(reader, lastMouseEvent, prefix);
}
@Override
@@ -258,12 +391,351 @@ public boolean trackFocus(boolean tracking) {
}
}
+ @Override
+ public boolean supportsGraphemeClusterMode() {
+ if (graphemeClusterModeSupported == null) {
+ graphemeClusterModeSupported = probeGraphemeClusterMode();
+ }
+ return graphemeClusterModeSupported;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean getGraphemeClusterMode() {
+ return graphemeClusterModeEnabled;
+ }
+
+ /**
+ * Tests whether the terminal groups the grapheme cluster starting at
+ * {@code index} as a single display unit. Used by {@link org.jline.utils.WCWidth}
+ * for per-category width computation when partial emoji support is detected.
+ *
+ * @param cs the character sequence
+ * @param index the start index of the cluster
+ * @param charCount the number of Java chars in the cluster
+ * @return {@code true} if the terminal renders the cluster as a single glyph
+ */
+ public boolean isClusterGrouped(CharSequence cs, int index, int charCount) {
+ if (!graphemeClusterModeEnabled) {
+ return false;
+ }
+ if (groupsRegionalIndicators && groupsZwjSequences) {
+ return true;
+ }
+ if (!groupsRegionalIndicators && !groupsZwjSequences) {
+ return false;
+ }
+ // Partial support โ classify and check
+ if (charCount <= Character.charCount(Character.codePointAt(cs, index))) {
+ return false; // Single codepoint
+ }
+ int cp = Character.codePointAt(cs, index);
+ if (WCWidth.isRegionalIndicator(cp)) {
+ return groupsRegionalIndicators;
+ }
+ return groupsZwjSequences;
+ }
+
+ /**
+ * Probes the terminal for grapheme cluster support.
+ *
+ * First attempts Mode 2027 detection via DECRQM ({@link #probeMode2027()}).
+ * If the terminal responds but does not support Mode 2027, falls back to a
+ * cursor position probe ({@link #probeCursorPosition()}): writes test
+ * emoji (a flag and a ZWJ sequence) and measures cursor displacement via
+ * DSR/CPR. If the cursor advances by exactly 2 columns, the terminal
+ * natively groups that emoji category as a single cluster.
+ *
+ * The cursor probe is only attempted when the terminal has already
+ * responded to the DECRQM/DA1 query, which guarantees it handles escape
+ * sequences and avoids blocking indefinitely on an unresponsive terminal.
+ *
+ * @return {@code true} if the terminal supports grapheme clusters
+ */
+ private boolean probeGraphemeClusterMode() {
+ if (TYPE_DUMB.equals(type) || TYPE_DUMB_COLOR.equals(type)) {
+ return false;
+ }
+ // Enter raw mode to prevent the terminal response from being echoed.
+ Attributes prev = enterRawMode();
+ try {
+ ProbeResult mode2027 = probeMode2027();
+ if (mode2027 == ProbeResult.SUPPORTED) {
+ return true;
+ }
+ // Cursor probe is only safe when the terminal actually responded to the
+ // DECRQM/DA1 query; otherwise getCursorPosition would block indefinitely.
+ if (mode2027 == ProbeResult.NOT_SUPPORTED) {
+ return probeCursorPosition();
+ }
+ return false;
+ } finally {
+ setAttributes(prev);
+ }
+ }
+
+ /**
+ * Probes the terminal for mode 2027 support using DECRQM.
+ *
+ * Sends {@code CSI ? 2027 $ p} followed by a DA1 (Primary Device
+ * Attributes) query {@code CSI c} as a sentinel. DA1 is near-universally
+ * supported, so its response acts as a fence: if we receive the DA1
+ * response without a preceding DECRPM, the terminal does not support
+ * DECRQM and we return immediately instead of waiting for a timeout.
+ *
+ * The expected DECRPM response is {@code CSI ? 2027 ; Ps $ y} where
+ * Ps indicates the mode status. Both DECRPM and DA1 responses share the
+ * {@code CSI ?} prefix, but diverge immediately after ({@code 2027;}
+ * vs the DA1 device-type parameter), so they are easy to distinguish.
+ *
+ * macOS Terminal.app is explicitly skipped because its CSI parser does
+ * not handle the {@code $} intermediate byte and leaks the final {@code p}
+ * as visible text. A {@code false} (not {@code null}) is returned so the
+ * cursor position fallback is still attempted.
+ *
+ * @return {@link ProbeResult#SUPPORTED} if mode 2027 is supported,
+ * {@link ProbeResult#NOT_SUPPORTED} if the terminal responded but
+ * does not support it, or {@link ProbeResult#NO_RESPONSE} if the
+ * terminal did not respond at all
+ */
+ private ProbeResult probeMode2027() {
+ // Terminal.app's CSI parser does not handle intermediate bytes correctly
+ // and leaks the final byte 'p' of the DECRQM sequence as visible text.
+ // Skip DECRQM but return NOT_SUPPORTED (not NO_RESPONSE) so the cursor probe runs.
+ String termProgram = System.getenv("TERM_PROGRAM");
+ if ("Apple_Terminal".equals(termProgram)) {
+ return ProbeResult.NOT_SUPPORTED;
+ }
+ try {
+ // Send DECRQM query for mode 2027 followed by DA1 as sentinel
+ writer().write("\033[?2027$p\033[c");
+ writer().flush();
+
+ // Read response โ both DECRPM and DA1 start with ESC [ ?
+ // DECRPM: ESC [ ? 2 0 2 7 ; Ps $ y
+ // DA1: ESC [ ? Pp ; ... c
+ long timeout = 100;
+ if (reader().peek(timeout) < 0) {
+ return ProbeResult.NO_RESPONSE;
+ }
+ int[] expected = {'\033', '[', '?', '2', '0', '2', '7', ';'};
+ for (int e : expected) {
+ int c = reader().read(timeout);
+ if (c != e) {
+ // Not DECRPM โ likely the DA1 sentinel response,
+ // meaning the terminal does not support DECRQM.
+ // Read remaining DA1 bytes until the 'c' terminator.
+ drainUntilDA1(timeout);
+ return ProbeResult.NOT_SUPPORTED;
+ }
+ }
+ int ps = reader().read(timeout);
+ if (ps < '0' || ps > '4') {
+ drainUntilDA1(timeout);
+ return ProbeResult.NOT_SUPPORTED;
+ }
+ int dollar = reader().read(timeout);
+ int y = reader().read(timeout);
+ if (dollar != '$' || y != 'y') {
+ drainUntilDA1(timeout);
+ return ProbeResult.NOT_SUPPORTED;
+ }
+ // Drain the trailing DA1 sentinel response
+ drainUntilDA1(timeout);
+ // Ps: 1=set, 2=reset (can be set), 3=permanently set โ supported
+ // Ps: 0=not recognized, 4=permanently reset โ not supported
+ return (ps == '1' || ps == '2' || ps == '3') ? ProbeResult.SUPPORTED : ProbeResult.NOT_SUPPORTED;
+ } catch (IOException e) {
+ return ProbeResult.NO_RESPONSE;
+ }
+ }
+
+ /**
+ * Probes per-category emoji grouping by measuring cursor displacement.
+ *
+ * Writes a test emoji (a ZWJ sequence) followed by a DSR cursor-position
+ * query. After flushing, reads the CPR response and checks whether the
+ * cursor advanced by exactly 2 columns (meaning the terminal grouped
+ * the emoji as a single cluster).
+ *
+ * Some terminals group all emoji categories (flags + ZWJ), others
+ * only group regional indicators (flags). Two probes detect this.
+ *
+ * This method must only be called when the terminal is known to
+ * respond to escape sequences (i.e., after {@link #probeMode2027()}
+ * received a response), because DSR/CPR blocks until a response
+ * arrives.
+ *
+ * @return {@code true} if the terminal groups the test emoji
+ */
+ private boolean probeCursorPosition() {
+ // Need u6 (CPR response pattern), u7 (DSR query), and save/restore cursor
+ if (getStringCapability(Capability.user6) == null
+ || getStringCapability(Capability.user7) == null
+ || getStringCapability(Capability.save_cursor) == null
+ || getStringCapability(Capability.restore_cursor) == null) {
+ return false;
+ }
+ // Two probes cover the three observed terminal behaviours:
+ // 1. Full support โ both flag and ZWJ grouped
+ // 2. Flags only โ flag grouped, ZWJ not (e.g. Tabby, Alacritty)
+ // 3. No support โ neither grouped
+ // When ZWJ is grouped we assume skin-tone and VS16 are too (always
+ // observed together). A flag probe alone catches partial support.
+ String flagEmoji = "\uD83C\uDDEB\uD83C\uDDF7"; // ๐ซ๐ท French flag
+ String zwjEmoji = "\uD83D\uDC69\u200D\uD83D\uDD2C"; // ๐ฉโ๐ฌ woman scientist
+ try {
+ // Query current cursor column so we can compute displacement
+ // rather than relying on the cursor starting at column 1
+ puts(Capability.user7);
+ writer().flush();
+ int startCol = readCprColumn(200);
+ if (startCol < 0) {
+ return false;
+ }
+
+ // --- Flag probe ---
+ int flagCol = probeEmojiWidth(flagEmoji);
+ // --- ZWJ probe ---
+ int zwjCol = probeEmojiWidth(zwjEmoji);
+
+ // Grouped emoji โ 2-column displacement; ungrouped โ 4
+ groupsRegionalIndicators = (flagCol - startCol == 2);
+ groupsZwjSequences = (zwjCol - startCol == 2);
+
+ if (groupsRegionalIndicators || groupsZwjSequences) {
+ graphemeClusterNative = true;
+ graphemeClusterModeEnabled = true;
+ return true;
+ }
+ return false;
+ } catch (IOError | IOException e) {
+ try {
+ puts(Capability.restore_cursor);
+ writer().flush();
+ } catch (Exception ignored) {
+ // Best-effort cleanup; probing failure should not propagate
+ }
+ groupsRegionalIndicators = false;
+ groupsZwjSequences = false;
+ return false;
+ }
+ }
+
+ /**
+ * Writes a single emoji to the terminal, queries cursor position via
+ * DSR/CPR, then cleans up. Returns the 1-based column value.
+ */
+ private int probeEmojiWidth(String emoji) throws IOException {
+ puts(Capability.save_cursor);
+ writer().write(emoji);
+ puts(Capability.user7); // DSR โ request cursor position
+ writer().flush();
+
+ int col = readCprColumn(200);
+
+ puts(Capability.restore_cursor);
+ writer().flush();
+ return col;
+ }
+
+ /**
+ * Reads a single CPR (Cursor Position Report) response and extracts
+ * the column value.
+ *
+ * Expected format: {@code ESC [ row ; col R}. Returns the raw
+ * 1-based column value, or {@code -1} if the response could not be
+ * parsed.
+ */
+ private int readCprColumn(long timeout) throws IOException {
+ int c;
+ // Skip until ESC
+ while ((c = reader().read(timeout)) != '\033') {
+ if (c < 0) return -1;
+ }
+ if (reader().read(timeout) != '[') return -1;
+ // Skip row digits until ';'
+ while ((c = reader().read(timeout)) != ';') {
+ if (c < 0 || c == 'R') return -1;
+ }
+ // Read column digits until 'R'
+ int col = 0;
+ while ((c = reader().read(timeout)) != 'R') {
+ if (c < '0' || c > '9') return -1;
+ col = col * 10 + (c - '0');
+ }
+ return col;
+ }
+
+ /**
+ * Discards input until a DA1 response terminator ('c') is encountered or the reader ends.
+ *
+ * Reads bytes from the terminal via reader().read(timeout) and stops when the character
+ * 'c' is seen or the reader returns a negative value. IOExceptions during draining are ignored.
+ *
+ * @param timeout the per-read timeout value passed to the reader
+ */
+ private void drainUntilDA1(long timeout) {
+ try {
+ int c;
+ while ((c = reader().read(timeout)) >= 0) {
+ if (c == 'c') {
+ return;
+ }
+ }
+ } catch (IOException ignored) {
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean setGraphemeClusterMode(boolean enable, boolean force) {
+ if (force) {
+ graphemeClusterModeSupported = true;
+ // If Mode 2027 was active via escape sequences, disable it
+ // before switching to native mode
+ if (!enable && graphemeClusterModeEnabled && !graphemeClusterNative) {
+ writer().write("\033[?2027l");
+ writer().flush();
+ }
+ graphemeClusterNative = true;
+ graphemeClusterModeEnabled = enable;
+ groupsRegionalIndicators = true;
+ groupsZwjSequences = true;
+ return true;
+ }
+ if (supportsGraphemeClusterMode()) {
+ if (!graphemeClusterNative) {
+ writer().write(enable ? "\033[?2027h" : "\033[?2027l");
+ writer().flush();
+ // Mode 2027 handles all emoji categories
+ groupsRegionalIndicators = enable;
+ groupsZwjSequences = enable;
+ }
+ graphemeClusterModeEnabled = enable;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
protected void checkInterrupted() throws InterruptedIOException {
if (Thread.interrupted()) {
throw new InterruptedIOException();
}
}
+ /**
+ * Checks if this terminal has been closed and throws an exception if it has.
+ *
+ * @throws IllegalStateException if this terminal has been closed
+ */
+ protected void checkClosed() {
+ if (closed) {
+ throw new IllegalStateException("Terminal has been closed");
+ }
+ }
+
@Override
public boolean canPauseResume() {
return false;
@@ -295,4 +767,24 @@ public String toString() {
+ type + '\'' + ", size='"
+ getSize() + '\'' + ']';
}
+
+ /**
+ * Get the terminal's default foreground color.
+ * This method should be overridden by concrete implementations.
+ *
+ * @return the RGB value of the default foreground color, or -1 if not available
+ */
+ public int getDefaultForegroundColor() {
+ return -1;
+ }
+
+ /**
+ * Get the terminal's default background color.
+ * This method should be overridden by concrete implementations.
+ *
+ * @return the RGB value of the default background color, or -1 if not available
+ */
+ public int getDefaultBackgroundColor() {
+ return -1;
+ }
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractWindowsConsoleWriter.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractWindowsConsoleWriter.java
index 301b89eb7963f..cb74b7d201480 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractWindowsConsoleWriter.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractWindowsConsoleWriter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2017, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -11,10 +11,71 @@
import java.io.IOException;
import java.io.Writer;
+/**
+ * Base class for writing to Windows console.
+ *
+ *
+ * The AbstractWindowsConsoleWriter class provides a foundation for writing text
+ * to the Windows console. It extends the standard Writer class and handles the
+ * common aspects of writing to the console, while leaving the actual console
+ * interaction to be implemented by concrete subclasses.
+ *
+ *
+ *
+ * This class is necessary because standard Java output streams don't work well
+ * with the Windows console, particularly for non-ASCII characters and color output.
+ * Instead of using standard output streams, Windows terminal implementations use
+ * this writer to directly interact with the Windows console API.
+ *
+ *
+ *
+ * Concrete subclasses must implement the {@link #writeConsole(char[], int)} method
+ * to perform the actual writing to the console using platform-specific mechanisms
+ * (e.g., JNI, JNA, or FFM).
+ *
+ *
+ * @see java.io.Writer
+ */
public abstract class AbstractWindowsConsoleWriter extends Writer {
+ /**
+ * Default constructor.
+ */
+ public AbstractWindowsConsoleWriter() {
+ // Default constructor
+ }
+
+ /**
+ * Writes text to the Windows console.
+ *
+ *
+ * This method must be implemented by concrete subclasses to perform the actual
+ * writing to the Windows console using platform-specific mechanisms. The
+ * implementation should handle proper encoding and display of characters,
+ * including non-ASCII characters and ANSI escape sequences if supported.
+ *
+ *
+ * @param text the character array containing the text to write
+ * @param len the number of characters to write
+ * @throws IOException if an I/O error occurs
+ */
protected abstract void writeConsole(char[] text, int len) throws IOException;
+ /**
+ * Writes a portion of a character array to the Windows console.
+ *
+ *
+ * This method handles the common logic for writing to the console, including
+ * creating a new character array if the offset is not zero and synchronizing
+ * access to the console. The actual writing is delegated to the
+ * {@link #writeConsole(char[], int)} method implemented by subclasses.
+ *
+ *
+ * @param cbuf the character array containing the text to write
+ * @param off the offset from which to start reading characters
+ * @param len the number of characters to write
+ * @throws IOException if an I/O error occurs
+ */
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
char[] text = cbuf;
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractWindowsTerminal.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractWindowsTerminal.java
index 03c42fb19f7de..703c4dfc57ea3 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractWindowsTerminal.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractWindowsTerminal.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2023, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -22,6 +22,7 @@
import jdk.internal.org.jline.terminal.Size;
import jdk.internal.org.jline.terminal.spi.SystemStream;
import jdk.internal.org.jline.terminal.spi.TerminalProvider;
+import jdk.internal.org.jline.utils.AttributedCharSequence;
import jdk.internal.org.jline.utils.Curses;
import jdk.internal.org.jline.utils.InfoCmp;
import jdk.internal.org.jline.utils.Log;
@@ -34,21 +35,64 @@
import jdk.internal.org.jline.utils.WriterOutputStream;
/**
- * The AbstractWindowsTerminal is used as the base class for windows terminal.
- * Due to windows limitations, mostly the missing support for ansi sequences,
- * the only way to create a correct terminal is to use the windows api to set
- * character attributes, move the cursor, erasing, etc...
+ * Base implementation for terminals on Windows systems.
*
- * UTF-8 support is also lacking in windows and the code page supposed to
- * emulate UTF-8 is a bit broken. In order to work around this broken
- * code page, windows api WriteConsoleW is used directly. This means that
- * the writer() becomes the primary output, while the output() is bridged
- * to the writer() using a WriterOutputStream wrapper.
+ *
+ * The AbstractWindowsTerminal class provides a foundation for terminal implementations
+ * on Windows operating systems. It addresses Windows-specific limitations and
+ * peculiarities, particularly related to console handling, character encoding,
+ * and ANSI sequence support.
+ *
+ *
+ *
+ * Due to Windows limitations, particularly the historically limited support for ANSI
+ * sequences, this implementation uses Windows-specific APIs to handle terminal
+ * operations such as setting character attributes, moving the cursor, erasing content,
+ * and other terminal functions. This approach provides better compatibility and
+ * performance compared to emulating ANSI sequences on Windows.
+ *
+ *
+ *
+ * UTF-8 support has also been historically problematic on Windows, with the code page
+ * meant to emulate UTF-8 being somewhat broken. To work around these issues, this
+ * implementation uses the Windows API WriteConsoleW directly. As a result, the
+ * writer() method becomes the primary output mechanism, while the output() stream
+ * is bridged to the writer using a WriterOutputStream wrapper.
+ *
+ *
+ *
+ * Key features provided by this class include:
+ *
+ *
+ * Windows console API integration
+ * Color attribute handling
+ * Cursor positioning and manipulation
+ * Proper UTF-8 and Unicode support
+ * Input processing for Windows console events
+ *
+ *
+ *
+ * This class is designed to be extended by concrete implementations that use
+ * specific Windows API access mechanisms (e.g., JNA, JNI, FFM).
+ *
+ *
+ * @see org.jline.terminal.impl.AbstractTerminal
+ * @param the Windows console type used by the specific implementation
*/
public abstract class AbstractWindowsTerminal extends AbstractTerminal {
public static final String TYPE_WINDOWS = "windows";
public static final String TYPE_WINDOWS_256_COLOR = "windows-256color";
+
+ // Windows console color constants
+ protected static final int FOREGROUND_BLUE = 0x0001;
+ protected static final int FOREGROUND_GREEN = 0x0002;
+ protected static final int FOREGROUND_RED = 0x0004;
+ protected static final int FOREGROUND_INTENSITY = 0x0008;
+ protected static final int BACKGROUND_BLUE = 0x0010;
+ protected static final int BACKGROUND_GREEN = 0x0020;
+ protected static final int BACKGROUND_RED = 0x0040;
+ protected static final int BACKGROUND_INTENSITY = 0x0080;
public static final String TYPE_WINDOWS_CONEMU = "windows-conemu";
public static final String TYPE_WINDOWS_VTP = "windows-vtp";
@@ -105,7 +149,43 @@ public AbstractWindowsTerminal(
int outConsoleMode,
Function inputStreamWrapper)
throws IOException {
- super(name, type, encoding, signalHandler);
+ this(
+ provider,
+ systemStream,
+ writer,
+ name,
+ type,
+ encoding,
+ encoding,
+ encoding,
+ nativeSignals,
+ signalHandler,
+ inConsole,
+ inConsoleMode,
+ outConsole,
+ outConsoleMode,
+ inputStreamWrapper);
+ }
+
+ @SuppressWarnings("this-escape")
+ public AbstractWindowsTerminal(
+ TerminalProvider provider,
+ SystemStream systemStream,
+ Writer writer,
+ String name,
+ String type,
+ Charset encoding,
+ Charset inputEncoding,
+ Charset outputEncoding,
+ boolean nativeSignals,
+ SignalHandler signalHandler,
+ Console inConsole,
+ int inConsoleMode,
+ Console outConsole,
+ int outConsoleMode,
+ Function inputStreamWrapper)
+ throws IOException {
+ super(name, type, encoding, inputEncoding, outputEncoding, signalHandler);
this.provider = provider;
this.systemStream = systemStream;
NonBlockingPumpReader reader = NonBlocking.nonBlockingPumpReader();
@@ -113,7 +193,7 @@ public AbstractWindowsTerminal(
this.input = inputStreamWrapper.apply(NonBlocking.nonBlockingStream(reader, encoding()));
this.reader = NonBlocking.nonBlocking(name, input, encoding());
this.writer = new PrintWriter(writer);
- this.output = new WriterOutputStream(writer, encoding());
+ this.output = new WriterOutputStream(writer, outputEncoding());
this.inConsole = inConsole;
this.outConsole = outConsole;
parseInfoCmp();
@@ -158,24 +238,29 @@ public SignalHandler handle(Signal signal, SignalHandler handler) {
}
public NonBlockingReader reader() {
+ checkClosed();
return reader;
}
public PrintWriter writer() {
+ checkClosed();
return writer;
}
@Override
public InputStream input() {
+ checkClosed();
return input;
}
@Override
public OutputStream output() {
+ checkClosed();
return output;
}
public Attributes getAttributes() {
+ checkClosed();
int mode = getConsoleMode(inConsole);
if ((mode & ENABLE_ECHO_INPUT) != 0) {
attributes.setLocalFlag(Attributes.LocalFlag.ECHO, true);
@@ -187,6 +272,7 @@ public Attributes getAttributes() {
}
public void setAttributes(Attributes attr) {
+ checkClosed();
attributes.copy(attr);
updateConsoleMode();
}
@@ -213,6 +299,7 @@ protected int ctrl(char key) {
}
public void setSize(Size size) {
+ checkClosed();
throw new UnsupportedOperationException("Can not resize windows terminal");
}
@@ -281,7 +368,7 @@ protected void processKeyEvent(
if (isAlt) {
processInputChar('\033');
}
- if (isCtrl && ch != ' ' && ch != '\n' && ch != 0x7f) {
+ if (isCtrl && ch != '\n' && ch != 0x7f) {
processInputChar((char) (ch == '?' ? 0x7f : Character.toUpperCase(ch) & 0x1f));
} else if (isCtrl && ch == '\n') {
//simulate Alt-Enter:
@@ -416,6 +503,80 @@ public boolean trackFocus(boolean tracking) {
return true;
}
+ @Override
+ protected void detectTrueColorSupport() {
+ super.detectTrueColorSupport();
+ Integer maxColors = ints.get(InfoCmp.Capability.max_colors);
+ if (maxColors != null && maxColors >= 256) {
+ return; // already sufficient
+ }
+ if (TYPE_WINDOWS_VTP.equals(getType())) {
+ ints.put(InfoCmp.Capability.max_colors, AttributedCharSequence.TRUE_COLORS);
+ } else if (TYPE_WINDOWS_256_COLOR.equals(getType()) || TYPE_WINDOWS_CONEMU.equals(getType())) {
+ ints.put(InfoCmp.Capability.max_colors, 256);
+ }
+ }
+
+ /**
+ * Get the default foreground color for Windows terminals.
+ *
+ * @return the RGB value of the default foreground color, or -1 if not available
+ */
+ public abstract int getDefaultForegroundColor();
+
+ /**
+ * Get the default background color for Windows terminals.
+ *
+ * @return the RGB value of the default background color, or -1 if not available
+ */
+ public abstract int getDefaultBackgroundColor();
+
+ /**
+ * Convert Windows console attribute to RGB color.
+ *
+ * @param attribute the Windows console attribute
+ * @param foreground true for foreground color, false for background color
+ * @return the RGB value of the color
+ */
+ protected int convertAttributeToRgb(int attribute, boolean foreground) {
+ // Map Windows console attributes to ANSI colors
+ int index = 0;
+ if (foreground) {
+ if ((attribute & FOREGROUND_RED) != 0) index |= 0x1;
+ if ((attribute & FOREGROUND_GREEN) != 0) index |= 0x2;
+ if ((attribute & FOREGROUND_BLUE) != 0) index |= 0x4;
+ if ((attribute & FOREGROUND_INTENSITY) != 0) index |= 0x8;
+ } else {
+ if ((attribute & BACKGROUND_RED) != 0) index |= 0x1;
+ if ((attribute & BACKGROUND_GREEN) != 0) index |= 0x2;
+ if ((attribute & BACKGROUND_BLUE) != 0) index |= 0x4;
+ if ((attribute & BACKGROUND_INTENSITY) != 0) index |= 0x8;
+ }
+ return ANSI_COLORS[index];
+ }
+
+ /**
+ * ANSI colors mapping.
+ */
+ protected static final int[] ANSI_COLORS = {
+ 0x000000, // black
+ 0xcd0000, // red
+ 0x00cd00, // green
+ 0xcdcd00, // yellow
+ 0x0000ee, // blue
+ 0xcd00cd, // magenta
+ 0x00cdcd, // cyan
+ 0xe5e5e5, // white
+ 0x7f7f7f, // bright black
+ 0xff0000, // bright red
+ 0x00ff00, // bright green
+ 0xffff00, // bright yellow
+ 0x5c5cff, // bright blue
+ 0xff00ff, // bright magenta
+ 0x00ffff, // bright cyan
+ 0xffffff // bright white
+ };
+
@Override
public boolean canPauseResume() {
return true;
@@ -533,6 +694,11 @@ public void processInputChar(char c) throws IOException {
slaveInputPipe.write(c);
}
+ @Override
+ public boolean supportsGraphemeClusterMode() {
+ return false;
+ }
+
@Override
public boolean trackMouse(MouseTracking tracking) {
this.tracking = tracking;
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/CursorSupport.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/CursorSupport.java
index 14253d8c1d19a..94a562586164a 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/CursorSupport.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/CursorSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -19,8 +19,74 @@
import jdk.internal.org.jline.utils.Curses;
import jdk.internal.org.jline.utils.InfoCmp;
+/**
+ * Utility class for cursor position detection in terminals.
+ *
+ *
+ * The CursorSupport class provides functionality for determining the current
+ * cursor position in a terminal. It uses terminal capabilities to request the
+ * cursor position from the terminal and parse the response.
+ *
+ *
+ *
+ * This class is used internally by terminal implementations to implement the
+ * {@link Terminal#getCursorPosition(IntConsumer)} method. It relies on specific
+ * terminal capabilities (user6 and user7) that define the sequence to request
+ * the cursor position and the format of the response.
+ *
+ *
+ *
+ * The cursor position detection works by:
+ *
+ *
+ * Sending a special escape sequence to the terminal (defined by user7 capability)
+ * Reading the terminal's response
+ * Parsing the response using a pattern derived from the user6 capability
+ * Extracting the row and column coordinates from the parsed response
+ *
+ *
+ *
+ * Note that cursor position reporting is not supported by all terminals, and
+ * this method may return null if the terminal does not support this feature
+ * or if an error occurs during the detection process.
+ *
+ *
+ * @see Terminal#getCursorPosition(IntConsumer)
+ * @see Cursor
+ */
public class CursorSupport {
+ /**
+ * Private constructor to prevent instantiation.
+ */
+ private CursorSupport() {
+ // Utility class
+ }
+
+ /**
+ * Gets the current cursor position from the terminal.
+ *
+ *
+ * This method sends a request to the terminal for its current cursor position
+ * and parses the response to extract the coordinates. It uses the terminal's
+ * user6 and user7 capabilities to determine the request sequence and response
+ * format.
+ *
+ *
+ *
+ * The method reads from the terminal's input stream until it finds a response
+ * that matches the expected pattern. Any characters read that are not part of
+ * the cursor position response can be optionally collected through the
+ * discarded consumer.
+ *
+ *
+ * @param terminal the terminal to get the cursor position from
+ * @param discarded an optional consumer for characters read that are not part
+ * of the cursor position response, or null if these characters
+ * should be ignored
+ * @return the cursor position, or null if the position could not be determined
+ * (e.g., if the terminal does not support cursor position reporting)
+ */
public static Cursor getCursorPosition(Terminal terminal, IntConsumer discarded) {
try {
String u6 = terminal.getStringCapability(InfoCmp.Capability.user6);
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/Diag.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/Diag.java
index 7b7d17499b6ad..fc74d346b4185 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/Diag.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/Diag.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -21,12 +21,63 @@
import jdk.internal.org.jline.terminal.spi.TerminalProvider;
import jdk.internal.org.jline.utils.OSUtils;
+/**
+ * Diagnostic utility for JLine terminals.
+ *
+ *
+ * The Diag class provides diagnostic tools for analyzing and troubleshooting
+ * JLine terminal configurations. It can be used to gather information about
+ * the current environment, available terminal providers, system properties,
+ * and other details relevant to terminal operation.
+ *
+ *
+ *
+ * This class can be run as a standalone application to generate a diagnostic
+ * report, which is useful for debugging terminal-related issues. The report
+ * includes information such as:
+ *
+ *
+ * Java version and system properties
+ * Operating system details
+ * Available terminal providers
+ * Terminal capabilities and attributes
+ * Console and TTY information
+ *
+ *
+ *
+ * The diagnostic information can help identify configuration issues, missing
+ * dependencies, or platform-specific problems that might affect terminal
+ * functionality.
+ *
+ */
public class Diag {
+ /**
+ * Main entry point for running the diagnostic tool.
+ *
+ *
+ * This method runs the diagnostic tool and prints the results to standard output.
+ * If the "--verbose" flag is provided as an argument, additional detailed
+ * information will be included in the output.
+ *
+ *
+ * @param args command-line arguments (use "--verbose" for detailed output)
+ */
public static void main(String[] args) {
diag(System.out, Arrays.asList(args).contains("--verbose"));
}
+ /**
+ * Generates a diagnostic report with standard verbosity.
+ *
+ *
+ * This method generates a diagnostic report with standard verbosity and
+ * writes it to the specified PrintStream. This is equivalent to calling
+ * {@link #diag(PrintStream, boolean)} with {@code verbose=false}.
+ *
+ *
+ * @param out the PrintStream to write the diagnostic report to
+ */
public static void diag(PrintStream out) {
diag(out, true);
}
@@ -78,26 +129,6 @@ public void run() {
}
out.println();
- out.println("JnaSupport");
- out.println("=================");
- try {
- TerminalProvider provider = TerminalProvider.load("jna");
- testProvider(provider);
- } catch (Throwable t) {
- error("JNA support not available", t);
- }
- out.println();
-
- out.println("Jansi2Support");
- out.println("=================");
- try {
- TerminalProvider provider = TerminalProvider.load("jansi");
- testProvider(provider);
- } catch (Throwable t) {
- error("Jansi 2 support not available", t);
- }
- out.println();
-
out.println("JniSupport");
out.println("=================");
try {
@@ -144,6 +175,8 @@ private void testProvider(TerminalProvider provider) {
"xterm",
false,
StandardCharsets.UTF_8,
+ StandardCharsets.UTF_8,
+ StandardCharsets.UTF_8,
false,
Terminal.SignalHandler.SIG_DFL,
false,
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/DoubleSizeCharacters.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/DoubleSizeCharacters.java
new file mode 100644
index 0000000000000..4fd710129bcdb
--- /dev/null
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/DoubleSizeCharacters.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) the original author(s).
+ *
+ * This software is distributable under the BSD license. See the terms of the
+ * BSD license in the documentation provided with this software.
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+package jdk.internal.org.jline.terminal.impl;
+
+import java.io.IOException;
+
+import jdk.internal.org.jline.terminal.Terminal;
+
+/**
+ * Utility class for handling double-size characters in terminals.
+ *
+ *
+ * Double-size characters are a feature supported by some terminals that allows
+ * displaying text at double width and/or double height. This is useful for
+ * creating banners, headers, or emphasizing important text.
+ *
+ *
+ *
+ * The implementation uses VT100-compatible escape sequences:
+ * - ESC # 3: Double-height, double-width line (top half)
+ * - ESC # 4: Double-height, double-width line (bottom half)
+ * - ESC # 5: Single-width, single-height line (normal)
+ * - ESC # 6: Double-width, single-height line
+ *
+ */
+public class DoubleSizeCharacters {
+
+ /**
+ * Creates a new DoubleSizeCharacters instance.
+ */
+ public DoubleSizeCharacters() {
+ // Default constructor
+ }
+
+ // VT100 double-size character control sequences
+ private static final String ESC = "\u001b";
+ private static final String DOUBLE_HEIGHT_TOP = ESC + "#3";
+ private static final String DOUBLE_HEIGHT_BOTTOM = ESC + "#4";
+ private static final String NORMAL_SIZE = ESC + "#5";
+ private static final String DOUBLE_WIDTH = ESC + "#6";
+
+ /**
+ * Enumeration of double-size character modes.
+ */
+ public enum Mode {
+ /** Normal single-width, single-height characters */
+ NORMAL,
+ /** Double-width, single-height characters */
+ DOUBLE_WIDTH,
+ /** Double-width, double-height characters (top half) */
+ DOUBLE_HEIGHT_TOP,
+ /** Double-width, double-height characters (bottom half) */
+ DOUBLE_HEIGHT_BOTTOM
+ }
+
+ /**
+ * Checks if the terminal supports double-size characters.
+ *
+ * @param terminal the terminal to check
+ * @return true if the terminal supports double-size characters, false otherwise
+ */
+ public static boolean isDoubleSizeSupported(Terminal terminal) {
+ String terminalType = terminal.getType().toLowerCase(java.util.Locale.ROOT);
+
+ // Most VT100-compatible terminals support double-size characters
+ return terminalType.contains("xterm")
+ || terminalType.contains("vt")
+ || terminalType.contains("ansi")
+ || terminalType.contains("screen")
+ || terminalType.contains("tmux");
+ }
+
+ /**
+ * Sets the double-size character mode for the current line.
+ *
+ * @param terminal the terminal to write to
+ * @param mode the double-size mode to set
+ * @throws IOException if an I/O error occurs
+ */
+ public static void setMode(Terminal terminal, Mode mode) throws IOException {
+ String sequence;
+ switch (mode) {
+ case NORMAL:
+ sequence = NORMAL_SIZE;
+ break;
+ case DOUBLE_WIDTH:
+ sequence = DOUBLE_WIDTH;
+ break;
+ case DOUBLE_HEIGHT_TOP:
+ sequence = DOUBLE_HEIGHT_TOP;
+ break;
+ case DOUBLE_HEIGHT_BOTTOM:
+ sequence = DOUBLE_HEIGHT_BOTTOM;
+ break;
+ default:
+ sequence = NORMAL_SIZE;
+ break;
+ }
+
+ terminal.writer().print(sequence);
+ terminal.writer().flush();
+ }
+
+ /**
+ * Prints text in normal size.
+ *
+ * @param terminal the terminal to write to
+ * @param text the text to print
+ * @throws IOException if an I/O error occurs
+ */
+ public static void printNormal(Terminal terminal, String text) throws IOException {
+ setMode(terminal, Mode.NORMAL);
+ terminal.writer().println(text);
+ terminal.writer().flush();
+ }
+
+ /**
+ * Prints text in double width.
+ *
+ * @param terminal the terminal to write to
+ * @param text the text to print
+ * @throws IOException if an I/O error occurs
+ */
+ public static void printDoubleWidth(Terminal terminal, String text) throws IOException {
+ setMode(terminal, Mode.DOUBLE_WIDTH);
+ terminal.writer().println(text);
+ terminal.writer().flush();
+ }
+
+ /**
+ * Prints text in double height (both top and bottom halves).
+ * This method automatically handles printing both the top and bottom halves
+ * of double-height text.
+ *
+ * @param terminal the terminal to write to
+ * @param text the text to print
+ * @throws IOException if an I/O error occurs
+ */
+ public static void printDoubleHeight(Terminal terminal, String text) throws IOException {
+ // Print top half
+ setMode(terminal, Mode.DOUBLE_HEIGHT_TOP);
+ terminal.writer().println(text);
+ terminal.writer().flush();
+
+ // Print bottom half
+ setMode(terminal, Mode.DOUBLE_HEIGHT_BOTTOM);
+ terminal.writer().println(text);
+ terminal.writer().flush();
+
+ // Reset to normal
+ setMode(terminal, Mode.NORMAL);
+ }
+
+ /**
+ * Creates a banner with the specified text using double-height characters.
+ * This is useful for creating prominent headers or titles.
+ *
+ * @param terminal the terminal to write to
+ * @param text the text for the banner
+ * @param borderChar the character to use for the border (e.g., '*', '=', '-')
+ * @throws IOException if an I/O error occurs
+ */
+ public static void printBanner(Terminal terminal, String text, char borderChar) throws IOException {
+ if (!isDoubleSizeSupported(terminal)) {
+ // Fall back to normal text with border
+ String border = new String(new char[text.length() + 4]).replace('\0', borderChar);
+ terminal.writer().println(border);
+ terminal.writer().println(borderChar + " " + text + " " + borderChar);
+ terminal.writer().println(border);
+ terminal.writer().flush();
+ return;
+ }
+
+ // Create border line
+ String border = new String(new char[text.length() + 4]).replace('\0', borderChar);
+
+ // Print top border in double width
+ printDoubleWidth(terminal, border);
+
+ // Print text in double height
+ printDoubleHeight(terminal, borderChar + " " + text + " " + borderChar);
+
+ // Print bottom border in double width
+ printDoubleWidth(terminal, border);
+
+ // Reset to normal
+ setMode(terminal, Mode.NORMAL);
+ }
+
+ /**
+ * Resets the terminal to normal character size.
+ * This is useful to ensure the terminal is in a known state.
+ *
+ * @param terminal the terminal to reset
+ * @throws IOException if an I/O error occurs
+ */
+ public static void reset(Terminal terminal) throws IOException {
+ setMode(terminal, Mode.NORMAL);
+ }
+}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/DumbTerminal.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/DumbTerminal.java
index 2922132c1bfad..00d029290343a 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/DumbTerminal.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/DumbTerminal.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -25,6 +25,41 @@
import jdk.internal.org.jline.utils.NonBlockingInputStream;
import jdk.internal.org.jline.utils.NonBlockingReader;
+/**
+ * A minimal terminal implementation with limited capabilities.
+ *
+ *
+ * The DumbTerminal class provides a basic terminal implementation that works in
+ * environments where a full-featured terminal is not available or not supported.
+ * It has minimal capabilities and does not support features like cursor movement,
+ * color output, or advanced input processing.
+ *
+ *
+ *
+ * This terminal type is often used as a fallback when more capable terminal
+ * implementations cannot be created, such as in non-interactive environments,
+ * redirected I/O scenarios, or when running inside IDEs or other tools that
+ * don't provide full terminal emulation.
+ *
+ *
+ *
+ * The DumbTerminal supports two variants:
+ *
+ *
+ * Standard dumb terminal ({@link org.jline.terminal.Terminal#TYPE_DUMB}) - No color support
+ * Color dumb terminal ({@link org.jline.terminal.Terminal#TYPE_DUMB_COLOR}) - Basic color support
+ *
+ *
+ *
+ * While limited in capabilities, the DumbTerminal still provides the core terminal
+ * functionality such as reading input and writing output, making it suitable for
+ * basic console applications that don't require advanced terminal features.
+ *
+ *
+ * @see org.jline.terminal.Terminal#TYPE_DUMB
+ * @see org.jline.terminal.Terminal#TYPE_DUMB_COLOR
+ * @see org.jline.terminal.impl.AbstractTerminal
+ */
public class DumbTerminal extends AbstractTerminal {
private final TerminalProvider provider;
@@ -58,7 +93,24 @@ public DumbTerminal(
SignalHandler signalHandler,
Function inputStreamWrapper)
throws IOException {
- super(name, type, encoding, signalHandler);
+ this(provider, systemStream, name, type, in, out, encoding, encoding, encoding, signalHandler, inputStreamWrapper);
+ }
+
+ @SuppressWarnings("this-escape")
+ public DumbTerminal(
+ TerminalProvider provider,
+ SystemStream systemStream,
+ String name,
+ String type,
+ InputStream in,
+ OutputStream out,
+ Charset encoding,
+ Charset inputEncoding,
+ Charset outputEncoding,
+ SignalHandler signalHandler,
+ Function inputStreamWrapper)
+ throws IOException {
+ super(name, type, encoding, inputEncoding, outputEncoding, signalHandler);
this.provider = provider;
this.systemStream = systemStream;
NonBlockingInputStream nbis = NonBlocking.nonBlocking(getName(), inputStreamWrapper.apply(in));
@@ -107,10 +159,21 @@ public int read(long timeout, boolean isPeek) throws IOException {
return c;
}
}
+
+ @Override
+ public void close() throws IOException {
+ super.close();
+ nbis.close();
+ }
+
+ @Override
+ public void shutdown() {
+ nbis.shutdown();
+ }
};
this.output = out;
- this.reader = NonBlocking.nonBlocking(getName(), input, encoding());
- this.writer = new PrintWriter(new OutputStreamWriter(output, encoding()));
+ this.reader = NonBlocking.nonBlocking(getName(), input, inputEncoding());
+ this.writer = new PrintWriter(new OutputStreamWriter(output, outputEncoding()));
this.attributes = new Attributes();
this.attributes.setControlChar(ControlChar.VERASE, (char) 127);
this.attributes.setControlChar(ControlChar.VWERASE, (char) 23);
@@ -121,41 +184,63 @@ public int read(long timeout, boolean isPeek) throws IOException {
}
public NonBlockingReader reader() {
+ checkClosed();
return reader;
}
public PrintWriter writer() {
+ checkClosed();
return writer;
}
@Override
public InputStream input() {
+ checkClosed();
return input;
}
@Override
public OutputStream output() {
+ checkClosed();
return output;
}
public Attributes getAttributes() {
+ checkClosed();
return new Attributes(attributes);
}
public void setAttributes(Attributes attr) {
+ checkClosed();
attributes.copy(attr);
}
public Size getSize() {
+ checkClosed();
Size sz = new Size();
sz.copy(size);
return sz;
}
public void setSize(Size sz) {
+ checkClosed();
size.copy(sz);
}
+ @Override
+ protected void doClose() throws IOException {
+ super.doClose();
+ try {
+ reader.close();
+ } finally {
+ try {
+ writer.flush();
+ } finally {
+ writer.close();
+ }
+ }
+ }
+
@Override
public TerminalProvider getProvider() {
return provider;
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/DumbTerminalProvider.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/DumbTerminalProvider.java
index 8ff9735306598..9e33dc4e687c3 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/DumbTerminalProvider.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/DumbTerminalProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -11,6 +11,8 @@
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.io.FilterInputStream;
+import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -24,8 +26,43 @@
import jdk.internal.org.jline.terminal.spi.SystemStream;
import jdk.internal.org.jline.terminal.spi.TerminalProvider;
+/**
+ * Terminal provider implementation for dumb terminals.
+ *
+ *
+ * The DumbTerminalProvider class provides a TerminalProvider implementation that
+ * creates DumbTerminal instances. Dumb terminals have minimal capabilities and
+ * are used as a fallback when more capable terminal implementations cannot be
+ * created or when running in environments with limited terminal support.
+ *
+ *
+ *
+ * This provider supports two types of dumb terminals:
+ *
+ *
+ * Standard dumb terminal ({@link Terminal#TYPE_DUMB}) - No color support
+ * Color dumb terminal ({@link Terminal#TYPE_DUMB_COLOR}) - Basic color support
+ *
+ *
+ *
+ * The provider name is "dumb", which can be specified in the {@code org.jline.terminal.provider}
+ * system property to force the use of this provider. This is useful in environments
+ * where other terminal providers might not work correctly or when terminal capabilities
+ * are not needed.
+ *
+ *
+ * @see org.jline.terminal.spi.TerminalProvider
+ * @see org.jline.terminal.impl.DumbTerminal
+ */
public class DumbTerminalProvider implements TerminalProvider {
+ /**
+ * Default constructor.
+ */
+ public DumbTerminalProvider() {
+ // Default constructor
+ }
+
@Override
public String name() {
return TerminalBuilder.PROP_PROVIDER_DUMB;
@@ -37,22 +74,21 @@ public Terminal sysTerminal(
String type,
boolean ansiPassThrough,
Charset encoding,
+ Charset inputEncoding,
+ Charset outputEncoding,
boolean nativeSignals,
Terminal.SignalHandler signalHandler,
boolean paused,
SystemStream systemStream,
Function inputStreamWrapper)
throws IOException {
+ // For system terminals, wrap the streams in non-closeable wrappers
+ // to prevent closing the underlying FileDescriptors when the terminal is closed
+ InputStream in = new NonCloseableInputStream(new FileInputStream(FileDescriptor.in));
+ OutputStream out = new NonCloseableOutputStream(
+ new FileOutputStream(systemStream == SystemStream.Error ? FileDescriptor.err : FileDescriptor.out));
return new DumbTerminal(
- this,
- systemStream,
- name,
- type,
- new FileInputStream(FileDescriptor.in),
- new FileOutputStream(systemStream == SystemStream.Error ? FileDescriptor.err : FileDescriptor.out),
- encoding,
- signalHandler,
- inputStreamWrapper);
+ this, systemStream, name, type, in, out, encoding, inputEncoding, outputEncoding, signalHandler, inputStreamWrapper);
}
@Override
@@ -62,11 +98,15 @@ public Terminal newTerminal(
InputStream masterInput,
OutputStream masterOutput,
Charset encoding,
+ Charset inputEncoding,
+ Charset outputEncoding,
Terminal.SignalHandler signalHandler,
boolean paused,
Attributes attributes,
Size size)
throws IOException {
+ // DumbTerminalProvider is only used for system terminals as a fallback.
+ // Non-system terminals with custom streams should use ExecTerminalProvider instead.
throw new UnsupportedOperationException();
}
@@ -89,4 +129,35 @@ public int systemStreamWidth(SystemStream stream) {
public String toString() {
return "TerminalProvider[" + name() + "]";
}
+
+ /**
+ * Wrapper that prevents closing the underlying input stream.
+ * Used for system streams (System.in) to prevent closing the FileDescriptor.
+ */
+ private static class NonCloseableInputStream extends FilterInputStream {
+ NonCloseableInputStream(InputStream in) {
+ super(in);
+ }
+
+ @Override
+ public void close() throws IOException {
+ // Do not close the underlying stream
+ }
+ }
+
+ /**
+ * Wrapper that prevents closing the underlying output stream.
+ * Used for system streams (System.out/err) to prevent closing the FileDescriptor.
+ */
+ private static class NonCloseableOutputStream extends FilterOutputStream {
+ NonCloseableOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ @Override
+ public void close() throws IOException {
+ // Flush but do not close the underlying stream
+ flush();
+ }
+ }
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ExternalTerminal.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ExternalTerminal.java
index 5033fd61279f2..a86515d22af54 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ExternalTerminal.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ExternalTerminal.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -21,20 +21,49 @@
import jdk.internal.org.jline.terminal.spi.TerminalProvider;
/**
- * Console implementation with embedded line disciplined.
+ * Terminal implementation designed for external connections with embedded line discipline.
*
- * This terminal is well-suited for supporting incoming external
- * connections, such as from the network (through telnet, ssh,
- * or any kind of protocol).
- * The terminal will start consuming the input in a separate thread
- * to generate interruption events.
+ *
+ * The ExternalTerminal class provides a terminal implementation that is well-suited
+ * for supporting incoming external connections, such as those from network sources
+ * (telnet, SSH, or other protocols). It extends the LineDisciplineTerminal class,
+ * inheriting its line discipline functionality while adding features specific to
+ * external connection handling.
+ *
+ *
+ *
+ * This terminal implementation starts consuming input in a separate thread to
+ * generate interruption events promptly, ensuring that signals like Ctrl+C are
+ * processed immediately rather than waiting for the application to read the input.
+ * This is particularly important for network-based terminals where latency could
+ * otherwise affect the responsiveness of signal handling.
+ *
+ *
+ *
+ * Key features of this implementation include:
+ *
+ *
+ * Support for external connections over various protocols
+ * Prompt signal handling through background input processing
+ * Configurable terminal type and attributes
+ * Support for dynamic size changes
+ *
+ *
+ *
+ * This terminal is commonly used in server applications that need to provide
+ * terminal access to remote clients, such as SSH servers, telnet servers, or
+ * custom network protocols that require terminal emulation.
+ *
*
* @see LineDisciplineTerminal
*/
public class ExternalTerminal extends LineDisciplineTerminal {
private final TerminalProvider provider;
+
+ @SuppressWarnings("java:S2387") // intentionally shadows AbstractTerminal.closed with AtomicBoolean
protected final AtomicBoolean closed = new AtomicBoolean();
+
protected final InputStream masterInput;
protected final Object lock = new Object();
protected boolean paused = true;
@@ -43,7 +72,17 @@ public class ExternalTerminal extends LineDisciplineTerminal {
public ExternalTerminal(
String name, String type, InputStream masterInput, OutputStream masterOutput, Charset encoding)
throws IOException {
- this(null, name, type, masterInput, masterOutput, encoding, SignalHandler.SIG_DFL);
+ this(
+ null,
+ name,
+ type,
+ masterInput,
+ masterOutput,
+ encoding,
+ encoding,
+ encoding,
+ encoding,
+ SignalHandler.SIG_DFL);
}
public ExternalTerminal(
@@ -55,7 +94,57 @@ public ExternalTerminal(
Charset encoding,
SignalHandler signalHandler)
throws IOException {
- this(provider, name, type, masterInput, masterOutput, encoding, signalHandler, false);
+ this(provider, name, type, masterInput, masterOutput, encoding, encoding, encoding, signalHandler, false);
+ }
+
+ public ExternalTerminal(
+ TerminalProvider provider,
+ String name,
+ String type,
+ InputStream masterInput,
+ OutputStream masterOutput,
+ Charset encoding,
+ Charset stdinEncoding,
+ Charset stdoutEncoding,
+ Charset stderrEncoding,
+ SignalHandler signalHandler)
+ throws IOException {
+ this(
+ provider,
+ name,
+ type,
+ masterInput,
+ masterOutput,
+ encoding,
+ stdinEncoding,
+ stdoutEncoding,
+ signalHandler,
+ false);
+ }
+
+ public ExternalTerminal(
+ TerminalProvider provider,
+ String name,
+ String type,
+ InputStream masterInput,
+ OutputStream masterOutput,
+ Charset encoding,
+ SignalHandler signalHandler,
+ boolean paused)
+ throws IOException {
+ this(
+ provider,
+ name,
+ type,
+ masterInput,
+ masterOutput,
+ encoding,
+ encoding,
+ encoding,
+ signalHandler,
+ paused,
+ null,
+ null);
}
public ExternalTerminal(
@@ -65,10 +154,24 @@ public ExternalTerminal(
InputStream masterInput,
OutputStream masterOutput,
Charset encoding,
+ Charset inputEncoding,
+ Charset outputEncoding,
SignalHandler signalHandler,
boolean paused)
throws IOException {
- this(provider, name, type, masterInput, masterOutput, encoding, signalHandler, paused, null, null);
+ this(
+ provider,
+ name,
+ type,
+ masterInput,
+ masterOutput,
+ encoding,
+ inputEncoding,
+ outputEncoding,
+ signalHandler,
+ paused,
+ null,
+ null);
}
@SuppressWarnings("this-escape")
@@ -84,7 +187,37 @@ public ExternalTerminal(
Attributes attributes,
Size size)
throws IOException {
- super(name, type, masterOutput, encoding, signalHandler);
+ this(
+ provider,
+ name,
+ type,
+ masterInput,
+ masterOutput,
+ encoding,
+ encoding,
+ encoding,
+ signalHandler,
+ paused,
+ attributes,
+ size);
+ }
+
+ @SuppressWarnings("this-escape")
+ public ExternalTerminal(
+ TerminalProvider provider,
+ String name,
+ String type,
+ InputStream masterInput,
+ OutputStream masterOutput,
+ Charset encoding,
+ Charset inputEncoding,
+ Charset outputEncoding,
+ SignalHandler signalHandler,
+ boolean paused,
+ Attributes attributes,
+ Size size)
+ throws IOException {
+ super(name, type, masterOutput, encoding, inputEncoding, outputEncoding, signalHandler);
this.provider = provider;
this.masterInput = masterInput;
if (attributes != null) {
@@ -98,6 +231,13 @@ public ExternalTerminal(
}
}
+ @Override
+ public boolean supportsGraphemeClusterMode() {
+ // ExternalTerminal has no real terminal emulator on the other end,
+ // so probing for mode 2027 would consume actual input data from the pipe.
+ return false;
+ }
+
protected void doClose() throws IOException {
if (closed.compareAndSet(false, true)) {
pause();
@@ -112,8 +252,10 @@ public boolean canPauseResume() {
@Override
public void pause() {
- synchronized (lock) {
- paused = true;
+ try {
+ pause(false);
+ } catch (InterruptedException e) {
+ // nah
}
}
@@ -126,7 +268,9 @@ public void pause(boolean wait) throws InterruptedException {
}
if (p != null) {
p.interrupt();
- p.join();
+ if (wait) {
+ p.join();
+ }
}
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/LineDisciplineTerminal.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/LineDisciplineTerminal.java
index 4e7a396bbc304..567a65cf5cfa9 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/LineDisciplineTerminal.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/LineDisciplineTerminal.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -45,6 +45,41 @@
* has to happen as soon as the user hit the keyboard, and not
* only when the application running in the terminal processes
* the input.
+ *
+ *
+ * The LineDisciplineTerminal class provides a terminal implementation that emulates
+ * the line discipline functionality typically provided by the operating system's
+ * terminal driver. Line discipline refers to the processing of input and output
+ * characters according to various modes and settings, such as canonical mode,
+ * echo, and special character handling.
+ *
+ *
+ *
+ * This terminal implementation is particularly useful in environments where:
+ *
+ *
+ * The underlying system does not provide native terminal capabilities
+ * The application needs precise control over terminal behavior
+ * The terminal is being used in a non-standard environment (e.g., embedded systems)
+ *
+ *
+ *
+ * Key features of this implementation include:
+ *
+ *
+ * Emulation of canonical and non-canonical input modes
+ * Support for character echoing
+ * Special character handling (e.g., interrupt, erase, kill)
+ * Input and output processing according to terminal attributes
+ *
+ *
+ *
+ * This terminal implementation works with any input and output streams, making it
+ * highly flexible and adaptable to various environments.
+ *
+ *
+ * @see org.jline.terminal.Attributes
+ * @see org.jline.terminal.impl.AbstractTerminal
*/
public class LineDisciplineTerminal extends AbstractTerminal {
@@ -86,13 +121,26 @@ public LineDisciplineTerminal(String name, String type, OutputStream masterOutpu
public LineDisciplineTerminal(
String name, String type, OutputStream masterOutput, Charset encoding, SignalHandler signalHandler)
throws IOException {
- super(name, type, encoding, signalHandler);
+ this(name, type, masterOutput, encoding, encoding, encoding, signalHandler);
+ }
+
+ @SuppressWarnings("this-escape")
+ public LineDisciplineTerminal(
+ String name,
+ String type,
+ OutputStream masterOutput,
+ Charset encoding,
+ Charset inputEncoding,
+ Charset outputEncoding,
+ SignalHandler signalHandler)
+ throws IOException {
+ super(name, type, encoding, inputEncoding, outputEncoding, signalHandler);
NonBlockingPumpInputStream input = NonBlocking.nonBlockingPumpInputStream(PIPE_SIZE);
this.slaveInputPipe = input.getOutputStream();
this.slaveInput = input;
- this.slaveReader = NonBlocking.nonBlocking(getName(), slaveInput, encoding());
+ this.slaveReader = NonBlocking.nonBlocking(getName(), slaveInput, inputEncoding());
this.slaveOutput = new FilteringOutputStream();
- this.slaveWriter = new PrintWriter(new OutputStreamWriter(slaveOutput, encoding()));
+ this.slaveWriter = new PrintWriter(new OutputStreamWriter(slaveOutput, outputEncoding()));
this.masterOutput = masterOutput;
this.attributes = getDefaultTerminalAttributes();
this.size = new Size(160, 50);
@@ -155,38 +203,46 @@ private static int ctrl(char c) {
}
public NonBlockingReader reader() {
+ checkClosed();
return slaveReader;
}
public PrintWriter writer() {
+ checkClosed();
return slaveWriter;
}
@Override
public InputStream input() {
+ checkClosed();
return slaveInput;
}
@Override
public OutputStream output() {
+ checkClosed();
return slaveOutput;
}
public Attributes getAttributes() {
+ checkClosed();
return new Attributes(attributes);
}
public void setAttributes(Attributes attr) {
+ checkClosed();
attributes.copy(attr);
}
public Size getSize() {
+ checkClosed();
Size sz = new Size();
sz.copy(size);
return sz;
}
public void setSize(Size sz) {
+ checkClosed();
size.copy(sz);
}
@@ -318,10 +374,7 @@ protected void doClose() throws IOException {
try {
slaveInputPipe.close();
} finally {
- try {
- } finally {
- slaveWriter.close();
- }
+ slaveWriter.close();
}
}
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/MouseSupport.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/MouseSupport.java
index eef25c90d8f1c..238e6000512db 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/MouseSupport.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/MouseSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -12,34 +12,128 @@
import java.io.IOError;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
import java.util.EnumSet;
import java.util.function.IntSupplier;
import jdk.internal.org.jline.terminal.MouseEvent;
import jdk.internal.org.jline.terminal.Terminal;
import jdk.internal.org.jline.utils.InfoCmp;
+import jdk.internal.org.jline.utils.InfoCmp.Capability;
import jdk.internal.org.jline.utils.InputStreamReader;
+/**
+ * Utility class for mouse support in terminals.
+ *
+ *
+ * The MouseSupport class provides functionality for enabling, disabling, and
+ * processing mouse events in terminals that support mouse tracking. It handles
+ * the details of sending the appropriate escape sequences to the terminal to
+ * enable different mouse tracking modes and parsing the responses to create
+ * MouseEvent objects.
+ *
+ *
+ *
+ * This class is used internally by terminal implementations to implement the
+ * mouse-related methods defined in the Terminal interface, such as
+ * {@link Terminal#hasMouseSupport()}, {@link Terminal#trackMouse(Terminal.MouseTracking)},
+ * and {@link Terminal#readMouseEvent()}.
+ *
+ *
+ *
+ * Mouse tracking in terminals typically works by:
+ *
+ *
+ * Sending special escape sequences to enable a specific mouse tracking mode
+ * Receiving escape sequences from the terminal when mouse events occur
+ * Parsing these sequences to extract information about the event type, button, modifiers, and coordinates
+ * Creating MouseEvent objects that represent these events
+ *
+ *
+ *
+ * Note that mouse support is not available in all terminals, and the methods in
+ * this class may not work correctly if the terminal does not support mouse tracking.
+ *
+ *
+ * @see Terminal#hasMouseSupport()
+ * @see Terminal#trackMouse(Terminal.MouseTracking)
+ * @see Terminal#readMouseEvent()
+ * @see MouseEvent
+ */
public class MouseSupport {
+ /**
+ * Private constructor to prevent instantiation.
+ */
+ private MouseSupport() {
+ // Utility class
+ }
+
+ /**
+ * Checks if the terminal supports mouse tracking.
+ *
+ *
+ * This method determines whether the terminal supports mouse tracking by
+ * checking if it has the key_mouse capability. This capability is required
+ * for mouse tracking to work correctly.
+ *
+ *
+ * @param terminal the terminal to check
+ * @return {@code true} if the terminal supports mouse tracking, {@code false} otherwise
+ */
public static boolean hasMouseSupport(Terminal terminal) {
return terminal.getStringCapability(InfoCmp.Capability.key_mouse) != null;
}
+ /**
+ * Enables or disables mouse tracking in the terminal.
+ *
+ *
+ * This method sends the appropriate escape sequences to the terminal to
+ * enable or disable mouse tracking according to the specified tracking mode.
+ * The available tracking modes are:
+ *
+ *
+ * {@link Terminal.MouseTracking#Off} - Disables mouse tracking
+ * {@link Terminal.MouseTracking#Normal} - Reports button press and release events
+ * {@link Terminal.MouseTracking#Button} - Reports button press, release, and motion events while buttons are pressed
+ * {@link Terminal.MouseTracking#Any} - Reports all mouse events, including movement without buttons pressed
+ *
+ *
+ *
+ * This implementation enables multiple mouse modes (1005, 1006, and basic) by default,
+ * which provides maximum compatibility across different terminals. The terminal will
+ * use the most advanced mode it supports:
+ *
+ *
+ * SGR mode (1006) - For terminals that support explicit release events
+ * UTF-8 mode (1005) - For terminals that need to report coordinates > 223
+ * Basic mode (1000) - For basic mouse event reporting
+ *
+ *
+ *
+ * When disabling mouse tracking, all modes are disabled to ensure a clean state.
+ *
+ *
+ * @param terminal the terminal to configure
+ * @param tracking the mouse tracking mode to enable
+ * @return {@code true} if mouse tracking is supported and was configured, {@code false} otherwise
+ */
public static boolean trackMouse(Terminal terminal, Terminal.MouseTracking tracking) {
if (hasMouseSupport(terminal)) {
switch (tracking) {
case Off:
- terminal.writer().write("\033[?1000l");
+ terminal.writer()
+ .write("\033[?1000l\033[?1002l\033[?1003l\033[?1005l\033[?1006l\033[?1015l\033[?1016l");
break;
case Normal:
- terminal.writer().write("\033[?1005h\033[?1000h");
+ terminal.writer().write("\033[?1005h\033[?1006h\033[?1000h");
break;
case Button:
- terminal.writer().write("\033[?1005h\033[?1002h");
+ terminal.writer().write("\033[?1005h\033[?1006h\033[?1002h");
break;
case Any:
- terminal.writer().write("\033[?1005h\033[?1003h");
+ terminal.writer().write("\033[?1005h\033[?1006h\033[?1003h");
break;
}
terminal.flush();
@@ -49,17 +143,362 @@ public static boolean trackMouse(Terminal terminal, Terminal.MouseTracking track
}
}
+ /**
+ * Reads a mouse event from the terminal.
+ *
+ *
+ * This method reads a mouse event from the terminal's input stream and
+ * converts it into a MouseEvent object. It uses the previous mouse event
+ * to determine the type of the new event (e.g., to distinguish between
+ * press, drag, and release events).
+ *
+ *
+ * @param terminal the terminal to read from
+ * @param last the previous mouse event, used to determine the type of the new event
+ * @return the mouse event that was read
+ */
public static MouseEvent readMouse(Terminal terminal, MouseEvent last) {
- return readMouse(() -> readExt(terminal), last);
+ return readMouse(() -> readExt(terminal), last, null);
+ }
+
+ /**
+ * Reads a mouse event from the terminal with a prefix that has already been consumed.
+ *
+ *
+ * This method is similar to {@link #readMouse(Terminal, MouseEvent)}, but it
+ * allows specifying a prefix that has already been consumed. This is useful when
+ * the mouse event prefix (e.g., "\033[<" or "\033[M") has been consumed by
+ * the key binding detection, and we need to continue parsing from the current position.
+ *
+ *
+ * @param terminal the terminal to read from
+ * @param last the previous mouse event, used to determine the type of the new event
+ * @param prefix the prefix that has already been consumed, or null if none
+ * @return the mouse event that was read
+ */
+ public static MouseEvent readMouse(Terminal terminal, MouseEvent last, String prefix) {
+ return readMouse(() -> readExt(terminal), last, prefix);
}
+ /**
+ * Reads a mouse event using the provided input supplier.
+ *
+ *
+ * This method reads a mouse event using the provided input supplier and
+ * converts it into a MouseEvent object. It uses the previous mouse event
+ * to determine the type of the new event (e.g., to distinguish between
+ * press, drag, and release events).
+ *
+ *
+ *
+ * The input supplier should provide the raw bytes of the mouse event data.
+ * This method expects the data to be in the format used by xterm-compatible
+ * terminals for mouse reporting.
+ *
+ *
+ * @param reader the input supplier to read from
+ * @param last the previous mouse event, used to determine the type of the new event
+ * @return the mouse event that was read
+ *
+ *
+ * This implementation supports multiple mouse event formats:
+ *
+ *
+ * X10 format (default) - Basic mouse reporting
+ * UTF-8 format (1005) - Extended mouse reporting with UTF-8 encoded coordinates
+ * SGR format (1006) - Extended mouse reporting with explicit release events
+ * URXVT format (1015) - Extended mouse reporting with decimal coordinates
+ * SGR-Pixels format (1016) - Like SGR but reports position in pixels
+ *
+ */
public static MouseEvent readMouse(IntSupplier reader, MouseEvent last) {
- int cb = reader.getAsInt() - ' ';
+ return readMouse(reader, last, null);
+ }
+
+ /**
+ * Reads a mouse event using the provided input supplier with a prefix that has already been consumed.
+ *
+ *
+ * This method is similar to {@link #readMouse(IntSupplier, MouseEvent)}, but it
+ * allows specifying a prefix that has already been consumed. This is useful when
+ * the mouse event prefix (e.g., "\033[<" or "\033[M") has been consumed by
+ * the key binding detection, and we need to continue parsing from the current position.
+ *
+ *
+ * @param reader the input supplier to read from
+ * @param last the previous mouse event, used to determine the type of the new event
+ * @param prefix the prefix that has already been consumed, or null if none
+ * @return the mouse event that was read
+ */
+ public static MouseEvent readMouse(IntSupplier reader, MouseEvent last, String prefix) {
+ // If a prefix was provided, create a reader that first returns the prefix characters
+ // and then reads from the original reader
+ if (prefix != null && !prefix.isEmpty()) {
+ IntSupplier prefixReader;
+ if (prefix.equals("\033[<")) {
+ // SGR format
+ prefixReader = createReaderFromString("<");
+ return readMouse(chainReaders(prefixReader, reader), last, null);
+ } else if (prefix.equals("\033[M")) {
+ // X10 or UTF-8 format
+ prefixReader = createReaderFromString("M");
+ return readMouse(chainReaders(prefixReader, reader), last, null);
+ }
+ }
+
+ int c = reader.getAsInt();
+
+ // Detect the mouse event format based on the first character
+ if (c == '<') {
+ // SGR (1006) or SGR-Pixels (1016) format
+ return readMouseSGR(reader, last);
+ } else if (c >= '0' && c <= '9') {
+ // URXVT (1015) format
+ return readMouseURXVT(c, reader, last);
+ } else if (c == 'M') {
+ // This is the ESC [ M prefix, now we need to read the next byte to determine
+ // if it's basic X10 or UTF-8 encoded (1005)
+ int cb = reader.getAsInt();
+ // Read the next two characters to determine if they're UTF-8 encoded
+ int cx = reader.getAsInt();
+ int cy = reader.getAsInt();
+
+ // Check if this is likely UTF-8 encoded (1005)
+ // In UTF-8 mode, coordinates > 95 will be encoded as multibyte sequences
+ // which means their first byte will have the high bit set
+ if ((cx & 0x80) != 0 || (cy & 0x80) != 0) {
+ return readMouseUTF8(cb, cx, cy, reader, last);
+ } else {
+ // Basic X10 format
+ return readMouseX10(cb - ' ', cx - ' ' - 1, cy - ' ' - 1, last);
+ }
+ } else {
+ // X10 format (default) - first byte is the button code
+ return readMouseX10(c - ' ', reader, last);
+ }
+ }
+
+ /**
+ * Reads a mouse event in X10 format.
+ *
+ * @param cb the button code (already read)
+ * @param reader the input supplier to read from
+ * @param last the previous mouse event
+ * @return the mouse event that was read
+ */
+ private static MouseEvent readMouseX10(int cb, IntSupplier reader, MouseEvent last) {
int cx = reader.getAsInt() - ' ' - 1;
int cy = reader.getAsInt() - ' ' - 1;
+ return parseMouseEvent(cb, cx, cy, false, last);
+ }
+
+ /**
+ * Reads a mouse event in X10 format with pre-read coordinates.
+ *
+ * @param cb the button code (already read)
+ * @param cx the x coordinate (already read and processed)
+ * @param cy the y coordinate (already read and processed)
+ * @param last the previous mouse event
+ * @return the mouse event that was read
+ */
+ private static MouseEvent readMouseX10(int cb, int cx, int cy, MouseEvent last) {
+ return parseMouseEvent(cb, cx, cy, false, last);
+ }
+
+ /**
+ * Reads a mouse event in UTF-8 format (1005).
+ * In this format, coordinates are UTF-8 encoded to support values > 223.
+ *
+ * @param cb the button code (already read)
+ * @param cx the first byte of the x coordinate (already read)
+ * @param cy the first byte of the y coordinate (already read)
+ * @param reader the input supplier to read from
+ * @param last the previous mouse event
+ * @return the mouse event that was read
+ */
+ private static MouseEvent readMouseUTF8(int cb, int cx, int cy, IntSupplier reader, MouseEvent last) {
+ // Decode the UTF-8 encoded coordinates
+ int x = decodeUtf8Coordinate(cx, reader);
+ int y = decodeUtf8Coordinate(cy, reader);
+
+ // Adjust coordinates (they're 1-based in the protocol)
+ x = x - 1;
+ y = y - 1;
+
+ return parseMouseEvent(cb - ' ', x, y, false, last);
+ }
+
+ /**
+ * Decodes a UTF-8 encoded coordinate value.
+ *
+ * @param firstByte the first byte of the UTF-8 encoded value
+ * @param reader the input supplier to read additional bytes if needed
+ * @return the decoded coordinate value
+ */
+ private static int decodeUtf8Coordinate(int firstByte, IntSupplier reader) {
+ // UTF-8 encoding rules:
+ // 0xxxxxxx - Single byte (values 0-127)
+ // 110xxxxx 10xxxxxx - Two bytes (values 128-2047)
+ // 1110xxxx 10xxxxxx 10xxxxxx - Three bytes (values 2048-65535)
+
+ if ((firstByte & 0x80) == 0) {
+ // Single byte (0xxxxxxx)
+ return firstByte - 32; // Subtract 32 as per mouse protocol
+ } else if ((firstByte & 0xE0) == 0xC0) {
+ // Two bytes (110xxxxx 10xxxxxx)
+ int secondByte = reader.getAsInt();
+ int value = ((firstByte & 0x1F) << 6) | (secondByte & 0x3F);
+ return value - 32;
+ } else if ((firstByte & 0xF0) == 0xE0) {
+ // Three bytes (1110xxxx 10xxxxxx 10xxxxxx)
+ int secondByte = reader.getAsInt();
+ int thirdByte = reader.getAsInt();
+ int value = ((firstByte & 0x0F) << 12) | ((secondByte & 0x3F) << 6) | (thirdByte & 0x3F);
+ return value - 32;
+ }
+
+ // Fallback for invalid UTF-8 sequence
+ return firstByte - 32;
+ }
+
+ /**
+ * Reads a mouse event in SGR format (1006 or 1016).
+ * Format: CSI < Cb ; Cx ; Cy M/m
+ *
+ *
+ * This method handles both standard SGR format (1006) and SGR-Pixels format (1016).
+ * In SGR format, coordinates are reported in character cells.
+ * In SGR-Pixels format, coordinates are reported in pixels.
+ *
+ *
+ *
+ * Currently, the MouseEvent class doesn't distinguish between cell and pixel
+ * coordinates, so both formats are treated the same. In the future, this could
+ * be enhanced to provide different handling for pixel coordinates.
+ *
+ *
+ * @param reader the input supplier to read from
+ * @param last the previous mouse event
+ * @return the mouse event that was read
+ */
+ private static MouseEvent readMouseSGR(IntSupplier reader, MouseEvent last) {
+ StringBuilder sb = new StringBuilder();
+ int[] params = new int[3];
+ int paramIndex = 0;
+ boolean isPixels = false;
+ boolean isRelease = false;
+
+ // Read parameters until 'M' or 'm' is encountered
+ int c;
+ while ((c = reader.getAsInt()) != -1) {
+ if (c == 'M' || c == 'm') {
+ isRelease = (c == 'm');
+ break;
+ } else if (c == ';') {
+ if (paramIndex < params.length) {
+ try {
+ params[paramIndex++] = Integer.parseInt(sb.toString());
+ } catch (NumberFormatException e) {
+ // Invalid parameter, use default
+ params[paramIndex++] = 0;
+ }
+ sb.setLength(0);
+ }
+ } else if (c >= '0' && c <= '9') {
+ sb.append((char) c);
+ }
+ }
+
+ // Parse the last parameter if any
+ if (sb.length() > 0 && paramIndex < params.length) {
+ try {
+ params[paramIndex] = Integer.parseInt(sb.toString());
+ } catch (NumberFormatException e) {
+ // Invalid parameter, use default
+ params[paramIndex] = 0;
+ }
+ }
+
+ int cb = params[0];
+ int cx = params[1] - 1; // Convert to 0-based
+ int cy = params[2] - 1; // Convert to 0-based
+
+ // Check if this is SGR-Pixels format (1016)
+ // The button code in SGR-Pixels mode is the same as in SGR mode
+ // The only difference is that coordinates are reported in pixels
+ // Currently, we don't distinguish between cell and pixel coordinates
+ // in the MouseEvent class, so we treat them the same
+
+ return parseMouseEvent(cb, cx, cy, isRelease, last);
+ }
+
+ /**
+ * Reads a mouse event in URXVT format (1015).
+ * Format: CSI Cb ; Cx ; Cy M
+ *
+ * @param firstDigit the first digit of the button code (already read)
+ * @param reader the input supplier to read from
+ * @param last the previous mouse event
+ * @return the mouse event that was read
+ */
+ private static MouseEvent readMouseURXVT(int firstDigit, IntSupplier reader, MouseEvent last) {
+ StringBuilder sb = new StringBuilder().append((char) firstDigit);
+ int[] params = new int[3];
+ int paramIndex = 0;
+
+ // Read parameters until 'M' is encountered
+ int c;
+ while ((c = reader.getAsInt()) != -1) {
+ if (c == 'M') {
+ break;
+ } else if (c == ';') {
+ if (paramIndex < params.length) {
+ try {
+ params[paramIndex++] = Integer.parseInt(sb.toString());
+ } catch (NumberFormatException e) {
+ // Invalid parameter, use default
+ params[paramIndex++] = 0;
+ }
+ sb.setLength(0);
+ }
+ } else if (c >= '0' && c <= '9') {
+ sb.append((char) c);
+ }
+ }
+
+ // Parse the last parameter if any
+ if (sb.length() > 0 && paramIndex < params.length) {
+ try {
+ params[paramIndex] = Integer.parseInt(sb.toString());
+ } catch (NumberFormatException e) {
+ // Invalid parameter, use default
+ params[paramIndex] = 0;
+ }
+ }
+
+ int cb = params[0];
+ int cx = params[1] - 1; // Convert to 0-based
+ int cy = params[2] - 1; // Convert to 0-based
+
+ return parseMouseEvent(cb, cx, cy, false, last);
+ }
+
+ /**
+ * Parses a mouse event from the given parameters.
+ *
+ * @param cb the button code
+ * @param cx the x coordinate
+ * @param cy the y coordinate
+ * @param isRelease whether this is an explicit release event (SGR format)
+ * @param last the previous mouse event
+ * @return the parsed mouse event
+ */
+ private static MouseEvent parseMouseEvent(int cb, int cx, int cy, boolean isRelease, MouseEvent last) {
MouseEvent.Type type;
MouseEvent.Button button;
EnumSet modifiers = EnumSet.noneOf(MouseEvent.Modifier.class);
+
+ // Parse modifiers
if ((cb & 4) == 4) {
modifiers.add(MouseEvent.Modifier.Shift);
}
@@ -69,56 +508,154 @@ public static MouseEvent readMouse(IntSupplier reader, MouseEvent last) {
if ((cb & 16) == 16) {
modifiers.add(MouseEvent.Modifier.Control);
}
+
+ // Handle wheel events
if ((cb & 64) == 64) {
type = MouseEvent.Type.Wheel;
button = (cb & 1) == 1 ? MouseEvent.Button.WheelDown : MouseEvent.Button.WheelUp;
} else {
- int b = (cb & 3);
- switch (b) {
- case 0:
- button = MouseEvent.Button.Button1;
- if (last.getButton() == button
- && (last.getType() == MouseEvent.Type.Pressed
- || last.getType() == MouseEvent.Type.Dragged)) {
- type = MouseEvent.Type.Dragged;
- } else {
- type = MouseEvent.Type.Pressed;
- }
- break;
- case 1:
- button = MouseEvent.Button.Button2;
- if (last.getButton() == button
- && (last.getType() == MouseEvent.Type.Pressed
- || last.getType() == MouseEvent.Type.Dragged)) {
- type = MouseEvent.Type.Dragged;
- } else {
- type = MouseEvent.Type.Pressed;
- }
- break;
- case 2:
- button = MouseEvent.Button.Button3;
- if (last.getButton() == button
- && (last.getType() == MouseEvent.Type.Pressed
- || last.getType() == MouseEvent.Type.Dragged)) {
- type = MouseEvent.Type.Dragged;
- } else {
- type = MouseEvent.Type.Pressed;
- }
- break;
- default:
- if (last.getType() == MouseEvent.Type.Pressed || last.getType() == MouseEvent.Type.Dragged) {
- button = last.getButton();
- type = MouseEvent.Type.Released;
- } else {
- button = MouseEvent.Button.NoButton;
- type = MouseEvent.Type.Moved;
- }
- break;
+ // Handle button events
+ if (isRelease) {
+ // Explicit release event (SGR format)
+ button = getButtonForCode(cb & 3);
+ type = MouseEvent.Type.Released;
+ } else {
+ int b = (cb & 3);
+ switch (b) {
+ case 0:
+ button = MouseEvent.Button.Button1;
+ if (last.getButton() == button
+ && (last.getType() == MouseEvent.Type.Pressed
+ || last.getType() == MouseEvent.Type.Dragged)) {
+ type = MouseEvent.Type.Dragged;
+ } else {
+ type = MouseEvent.Type.Pressed;
+ }
+ break;
+ case 1:
+ button = MouseEvent.Button.Button2;
+ if (last.getButton() == button
+ && (last.getType() == MouseEvent.Type.Pressed
+ || last.getType() == MouseEvent.Type.Dragged)) {
+ type = MouseEvent.Type.Dragged;
+ } else {
+ type = MouseEvent.Type.Pressed;
+ }
+ break;
+ case 2:
+ button = MouseEvent.Button.Button3;
+ if (last.getButton() == button
+ && (last.getType() == MouseEvent.Type.Pressed
+ || last.getType() == MouseEvent.Type.Dragged)) {
+ type = MouseEvent.Type.Dragged;
+ } else {
+ type = MouseEvent.Type.Pressed;
+ }
+ break;
+ default:
+ if (last.getType() == MouseEvent.Type.Pressed || last.getType() == MouseEvent.Type.Dragged) {
+ button = last.getButton();
+ type = MouseEvent.Type.Released;
+ } else {
+ button = MouseEvent.Button.NoButton;
+ type = MouseEvent.Type.Moved;
+ }
+ break;
+ }
}
}
+
return new MouseEvent(type, button, modifiers, cx, cy);
}
+ /**
+ * Gets the button for the given button code.
+ *
+ * @param code the button code
+ * @return the corresponding button
+ */
+ private static MouseEvent.Button getButtonForCode(int code) {
+ switch (code) {
+ case 0:
+ return MouseEvent.Button.Button1;
+ case 1:
+ return MouseEvent.Button.Button2;
+ case 2:
+ return MouseEvent.Button.Button3;
+ default:
+ return MouseEvent.Button.NoButton;
+ }
+ }
+
+ /**
+ * Returns a list of key sequences that could be used for mouse events
+ * based on the current mouse mode configuration.
+ *
+ *
+ * This method returns the possible prefixes for mouse events that applications
+ * should recognize. This is useful for applications that need to handle mouse
+ * events but don't want to rely on the terminal's kmous capability, which
+ * might not accurately reflect the actual mouse mode being used.
+ *
+ *
+ * @return array of possible mouse event prefixes
+ */
+ public static String[] keys() {
+ // Return all possible mouse event prefixes
+ return new String[] {
+ "\033[<", // SGR format (1006)
+ "\033[M" // Basic (1000) and UTF-8 (1005) formats
+ };
+ }
+
+ /**
+ * Returns a list of key sequences that could be used for mouse events,
+ * including the terminal's key_mouse capability if available.
+ *
+ *
+ * This method combines the standard mouse event prefixes with the terminal's
+ * key_mouse capability. This is useful for applications that need to bind
+ * all possible mouse event sequences to ensure compatibility across different
+ * terminals.
+ *
+ *
+ * @param terminal the terminal to get the key_mouse capability from
+ * @return array of possible mouse event prefixes including the terminal's key_mouse capability
+ */
+ public static String[] keys(Terminal terminal) {
+ String keyMouse = terminal.getStringCapability(Capability.key_mouse);
+ if (keyMouse != null) {
+ // Check if keyMouse is one of our standard prefixes
+ if (Arrays.asList(keys()).contains(keyMouse)) {
+ // If it's already in our standard prefixes, just return those
+ return keys();
+ }
+ // Include the terminal's key_mouse capability if it's not already in our standard prefixes
+ return new String[] {
+ keyMouse, // Terminal's key_mouse capability
+ "\033[<", // SGR format (1006 and 1016)
+ "\033[M" // Basic (1000) and UTF-8 (1005) formats
+ };
+ } else {
+ // Just return the standard prefixes if key_mouse is not available
+ return keys();
+ }
+ }
+
+ /**
+ * Reads a single character from the terminal's input stream.
+ *
+ *
+ * This method reads a single character from the terminal's input stream,
+ * handling the case where the terminal's encoding is not UTF-8. Mouse events
+ * are encoded in UTF-8, so if the terminal is using a different encoding,
+ * this method creates a temporary UTF-8 reader to read the character.
+ *
+ *
+ * @param terminal the terminal to read from
+ * @return the character that was read
+ * @throws IOError if an I/O error occurs while reading
+ */
private static int readExt(Terminal terminal) {
try {
// The coordinates are encoded in UTF-8, so if that's not the input encoding,
@@ -137,4 +674,48 @@ private static int readExt(Terminal terminal) {
throw new IOError(e);
}
}
+
+ /**
+ * Creates a reader from a string.
+ *
+ * @param s the string to read from
+ * @return an IntSupplier that reads from the string
+ */
+ private static IntSupplier createReaderFromString(String s) {
+ final int[] chars = s.chars().toArray();
+ final int[] index = {0};
+
+ return () -> {
+ if (index[0] < chars.length) {
+ return chars[index[0]++];
+ }
+ return -1;
+ };
+ }
+
+ /**
+ * Chains two readers together, reading from the first reader until it's exhausted,
+ * then reading from the second reader.
+ *
+ * @param first the first reader to read from
+ * @param second the second reader to read from after the first is exhausted
+ * @return an IntSupplier that reads from both readers in sequence
+ */
+ private static IntSupplier chainReaders(IntSupplier first, IntSupplier second) {
+ return new IntSupplier() {
+ private boolean firstExhausted = false;
+
+ @Override
+ public int getAsInt() {
+ if (!firstExhausted) {
+ int c = first.getAsInt();
+ if (c != -1) {
+ return c;
+ }
+ firstExhausted = true;
+ }
+ return second.getAsInt();
+ }
+ };
+ }
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/NativeSignalHandler.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/NativeSignalHandler.java
index bba177794a5e0..3a47a419a32fe 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/NativeSignalHandler.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/NativeSignalHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -11,14 +11,78 @@
import jdk.internal.org.jline.terminal.Terminal.Signal;
import jdk.internal.org.jline.terminal.Terminal.SignalHandler;
+/**
+ * Implementation of SignalHandler for native signal handling.
+ *
+ *
+ * The NativeSignalHandler class provides an implementation of the SignalHandler
+ * interface that represents native signal handlers. It defines two special
+ * instances that correspond to the standard POSIX signal dispositions:
+ *
+ *
+ * {@link #SIG_DFL} - The default signal handler
+ * {@link #SIG_IGN} - The signal handler that ignores the signal
+ *
+ *
+ *
+ * This class is used internally by terminal implementations to represent native
+ * signal handlers. It cannot be instantiated directly, and its {@link #handle(Signal)}
+ * method throws an UnsupportedOperationException because native signal handling
+ * is performed by the underlying platform, not by Java code.
+ *
+ *
+ * @see org.jline.terminal.Terminal.SignalHandler
+ * @see org.jline.terminal.Terminal#handle(Signal, SignalHandler)
+ */
public final class NativeSignalHandler implements SignalHandler {
+ /**
+ * The default signal handler.
+ *
+ *
+ * This constant represents the default signal handler, which corresponds to
+ * the SIG_DFL disposition in POSIX systems. When a signal is handled by the
+ * default handler, the default action for that signal is taken, which varies
+ * depending on the signal (e.g., termination, core dump, ignore, etc.).
+ *
+ */
public static final NativeSignalHandler SIG_DFL = new NativeSignalHandler();
+ /**
+ * The signal handler that ignores signals.
+ *
+ *
+ * This constant represents the signal handler that ignores signals, which
+ * corresponds to the SIG_IGN disposition in POSIX systems. When a signal is
+ * handled by this handler, the signal is ignored and no action is taken.
+ *
+ */
public static final NativeSignalHandler SIG_IGN = new NativeSignalHandler();
+ /**
+ * Private constructor to prevent direct instantiation.
+ *
+ *
+ * This constructor is private because NativeSignalHandler instances should
+ * only be created for the predefined constants SIG_DFL and SIG_IGN.
+ *
+ */
private NativeSignalHandler() {}
+ /**
+ * Handles the specified signal.
+ *
+ *
+ * This method always throws an UnsupportedOperationException because native
+ * signal handling is performed by the underlying platform, not by Java code.
+ * The NativeSignalHandler instances are only used as markers to indicate
+ * which native signal handler should be used.
+ *
+ *
+ * @param signal the signal to handle
+ * @throws UnsupportedOperationException always thrown to indicate that this
+ * method cannot be called directly
+ */
public void handle(Signal signal) {
throw new UnsupportedOperationException();
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/PosixPtyTerminal.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/PosixPtyTerminal.java
index d34103e4d5e1d..08b5042eb02db 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/PosixPtyTerminal.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/PosixPtyTerminal.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -17,11 +17,41 @@
import java.util.Objects;
import jdk.internal.org.jline.terminal.spi.Pty;
-import jdk.internal.org.jline.utils.ClosedException;
+import jdk.internal.org.jline.utils.Log;
import jdk.internal.org.jline.utils.NonBlocking;
import jdk.internal.org.jline.utils.NonBlockingInputStream;
import jdk.internal.org.jline.utils.NonBlockingReader;
+/**
+ * Terminal implementation for POSIX systems using a pseudoterminal (PTY).
+ *
+ *
+ * The PosixPtyTerminal class provides a terminal implementation for POSIX systems
+ * (Linux, macOS, etc.) that uses a pseudoterminal (PTY) for terminal operations.
+ * It extends the AbstractPosixTerminal class and adds functionality specific to
+ * PTY-based terminals.
+ *
+ *
+ *
+ * This implementation is used when a full terminal emulation is needed, such as
+ * when creating a terminal for an external process or when connecting to a remote
+ * terminal. It provides access to the master and slave sides of the PTY, allowing
+ * for bidirectional communication with the terminal.
+ *
+ *
+ *
+ * Key features of this implementation include:
+ *
+ *
+ * Full terminal emulation using a pseudoterminal
+ * Support for terminal attributes and size changes
+ * Access to both master and slave sides of the PTY
+ * Support for non-blocking I/O
+ *
+ *
+ * @see org.jline.terminal.impl.AbstractPosixTerminal
+ * @see org.jline.terminal.spi.Pty
+ */
public class PosixPtyTerminal extends AbstractPosixTerminal {
private final InputStream in;
@@ -66,15 +96,31 @@ public PosixPtyTerminal(
SignalHandler signalHandler,
boolean paused)
throws IOException {
- super(name, type, pty, encoding, signalHandler);
+ this(name, type, pty, in, out, encoding, encoding, encoding, signalHandler, paused);
+ }
+
+ @SuppressWarnings("this-escape")
+ public PosixPtyTerminal(
+ String name,
+ String type,
+ Pty pty,
+ InputStream in,
+ OutputStream out,
+ Charset encoding,
+ Charset inputEncoding,
+ Charset outputEncoding,
+ SignalHandler signalHandler,
+ boolean paused)
+ throws IOException {
+ super(name, type, pty, encoding, inputEncoding, outputEncoding, signalHandler);
this.in = Objects.requireNonNull(in);
this.out = Objects.requireNonNull(out);
this.masterInput = pty.getMasterInput();
this.masterOutput = pty.getMasterOutput();
- this.input = new InputStreamWrapper(NonBlocking.nonBlocking(name, pty.getSlaveInput()));
+ this.input = NonBlocking.nonBlocking(name, pty.getSlaveInput());
this.output = pty.getSlaveOutput();
- this.reader = NonBlocking.nonBlocking(name, input, encoding());
- this.writer = new PrintWriter(new OutputStreamWriter(output, encoding()));
+ this.reader = NonBlocking.nonBlocking(name, input, inputEncoding());
+ this.writer = new PrintWriter(new OutputStreamWriter(output, outputEncoding()));
parseInfoCmp();
if (!paused) {
resume();
@@ -82,18 +128,22 @@ public PosixPtyTerminal(
}
public InputStream input() {
+ checkClosed();
return input;
}
public NonBlockingReader reader() {
+ checkClosed();
return reader;
}
public OutputStream output() {
+ checkClosed();
return output;
}
public PrintWriter writer() {
+ checkClosed();
return writer;
}
@@ -110,8 +160,10 @@ public boolean canPauseResume() {
@Override
public void pause() {
- synchronized (lock) {
- paused = true;
+ try {
+ pause(false);
+ } catch (InterruptedException e) {
+ // nah
}
}
@@ -129,11 +181,13 @@ public void pause(boolean wait) throws InterruptedException {
if (p2 != null) {
p2.interrupt();
}
- if (p1 != null) {
- p1.join();
- }
- if (p2 != null) {
- p2.join();
+ if (wait) {
+ if (p1 != null) {
+ p1.join();
+ }
+ if (p2 != null) {
+ p2.join();
+ }
}
}
@@ -161,29 +215,6 @@ public boolean paused() {
}
}
- private static class InputStreamWrapper extends NonBlockingInputStream {
-
- private final NonBlockingInputStream in;
- private volatile boolean closed;
-
- protected InputStreamWrapper(NonBlockingInputStream in) {
- this.in = in;
- }
-
- @Override
- public int read(long timeout, boolean isPeek) throws IOException {
- if (closed) {
- throw new ClosedException();
- }
- return in.read(timeout, isPeek);
- }
-
- @Override
- public void close() throws IOException {
- closed = true;
- }
- }
-
private void pumpIn() {
try {
for (; ; ) {
@@ -202,7 +233,9 @@ private void pumpIn() {
masterOutput.flush();
}
} catch (IOException e) {
- e.printStackTrace();
+ if (!closed) {
+ Log.warn("Error in input pump", e);
+ }
} finally {
synchronized (lock) {
inputPumpThread = null;
@@ -228,7 +261,9 @@ private void pumpOut() {
out.flush();
}
} catch (IOException e) {
- e.printStackTrace();
+ if (!closed) {
+ Log.warn("Error in output pump", e);
+ }
} finally {
synchronized (lock) {
outputPumpThread = null;
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/PosixSysTerminal.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/PosixSysTerminal.java
index 135ed0bb0c04b..2bfe89a049000 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/PosixSysTerminal.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/PosixSysTerminal.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -23,10 +23,41 @@
import jdk.internal.org.jline.utils.NonBlocking;
import jdk.internal.org.jline.utils.NonBlockingInputStream;
import jdk.internal.org.jline.utils.NonBlockingReader;
+import jdk.internal.org.jline.utils.OSUtils;
import jdk.internal.org.jline.utils.ShutdownHooks;
import jdk.internal.org.jline.utils.ShutdownHooks.Task;
import jdk.internal.org.jline.utils.Signals;
+/**
+ * Terminal implementation for POSIX systems using system streams.
+ *
+ *
+ * The PosixSysTerminal class provides a terminal implementation for POSIX systems
+ * (Linux, macOS, etc.) that uses the system standard input and output streams.
+ * It extends the AbstractPosixTerminal class and adds functionality specific to
+ * system stream-based terminals.
+ *
+ *
+ *
+ * This implementation is used when connecting to the actual system terminal, such
+ * as when running a console application in a terminal window. It provides access
+ * to the standard input and output streams, allowing for interaction with the
+ * user through the terminal.
+ *
+ *
+ *
+ * Key features of this implementation include:
+ *
+ *
+ * Direct access to system standard input and output
+ * Support for terminal attributes and size changes
+ * Support for non-blocking I/O
+ * Automatic restoration of terminal state on shutdown
+ *
+ *
+ * @see org.jline.terminal.impl.AbstractPosixTerminal
+ * @see org.jline.terminal.spi.Pty
+ */
public class PosixSysTerminal extends AbstractPosixTerminal {
protected final NonBlockingInputStream input;
@@ -41,11 +72,26 @@ public PosixSysTerminal(
String name, String type, Pty pty, Charset encoding, boolean nativeSignals, SignalHandler signalHandler,
Function inputStreamWrapper)
throws IOException {
- super(name, type, pty, encoding, signalHandler);
+ this(name, type, pty, encoding, encoding, encoding, nativeSignals, signalHandler, inputStreamWrapper);
+ }
+
+ @SuppressWarnings("this-escape")
+ public PosixSysTerminal(
+ String name,
+ String type,
+ Pty pty,
+ Charset encoding,
+ Charset inputEncoding,
+ Charset outputEncoding,
+ boolean nativeSignals,
+ SignalHandler signalHandler,
+ Function inputStreamWrapper)
+ throws IOException {
+ super(name, type, pty, encoding, inputEncoding, outputEncoding, signalHandler);
this.input = NonBlocking.nonBlocking(getName(), inputStreamWrapper.apply(pty.getSlaveInput()));
this.output = new FastBufferedOutputStream(pty.getSlaveOutput());
- this.reader = NonBlocking.nonBlocking(getName(), input, encoding());
- this.writer = new PrintWriter(new OutputStreamWriter(output, encoding()));
+ this.reader = NonBlocking.nonBlocking(getName(), input, inputEncoding());
+ this.writer = new PrintWriter(new OutputStreamWriter(output, outputEncoding()));
parseInfoCmp();
if (nativeSignals) {
for (final Signal signal : Signal.values()) {
@@ -73,32 +119,50 @@ public SignalHandler handle(Signal signal, SignalHandler handler) {
return prev;
}
+ @Override
+ public boolean supportsGraphemeClusterMode() {
+ // On Windows (Cygwin/MSYSTEM), the slave output goes to a raw
+ // FileDescriptor (stdout/stderr) rather than a real PTY device.
+ // Writing the DECRQM probe to such a descriptor contaminates the
+ // process output when the fd is piped (e.g. subprocess with captured
+ // output). Detecting whether the fd is truly a terminal is unreliable
+ // on Windows, so disable the probe entirely.
+ if (OSUtils.IS_WINDOWS) {
+ return false;
+ }
+ return super.supportsGraphemeClusterMode();
+ }
+
public NonBlockingReader reader() {
+ checkClosed();
return reader;
}
public PrintWriter writer() {
+ checkClosed();
return writer;
}
@Override
public InputStream input() {
+ checkClosed();
return input;
}
@Override
public OutputStream output() {
+ checkClosed();
return output;
}
@Override
protected void doClose() throws IOException {
+ writer.flush();
ShutdownHooks.remove(closer);
for (Map.Entry entry : nativeHandlers.entrySet()) {
Signals.unregister(entry.getKey().name(), entry.getValue());
}
super.doClose();
- // Do not call reader.close()
- reader.shutdown();
+ reader.close();
}
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/exec/ExecPty.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/exec/ExecPty.java
index f21920160d6ba..b79d6001d2b1f 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/exec/ExecPty.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/exec/ExecPty.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -34,10 +34,54 @@
import static jdk.internal.org.jline.utils.ExecHelper.exec;
+/**
+ * A pseudoterminal implementation that uses external commands to interact with the terminal.
+ *
+ *
+ * The ExecPty class provides a Pty implementation that uses external commands (such as
+ * stty, tput, etc.) to interact with the terminal. This approach allows JLine to work
+ * in environments where native libraries are not available or cannot be used, by relying
+ * on standard command-line utilities that are typically available on Unix-like systems.
+ *
+ *
+ *
+ * This implementation executes external commands to perform operations such as:
+ *
+ *
+ * Getting and setting terminal attributes
+ * Getting and setting terminal size
+ * Determining the current terminal device
+ *
+ *
+ *
+ * The ExecPty is typically used as a fallback when more direct methods of terminal
+ * interaction (such as JNI or JNA) are not available. While it provides good compatibility,
+ * it may have higher overhead due to the need to spawn external processes for many operations.
+ *
+ *
+ * @see org.jline.terminal.impl.AbstractPty
+ * @see org.jline.terminal.spi.Pty
+ */
public class ExecPty extends AbstractPty implements Pty {
private final String name;
+ /**
+ * Creates an ExecPty instance for the current terminal.
+ *
+ *
+ * This method creates an ExecPty instance for the current terminal by executing
+ * the 'tty' command to determine the terminal device name. It is used to obtain
+ * a Pty object that can interact with the current terminal using external commands.
+ *
+ *
+ * @param provider the terminal provider that will own this Pty
+ * @param systemStream the system stream (must be Output or Error) associated with this Pty
+ * @return a new ExecPty instance for the current terminal
+ * @throws IOException if the current terminal is not a TTY or if an error occurs
+ * while executing the 'tty' command
+ * @throws IllegalArgumentException if systemStream is not Output or Error
+ */
public static Pty current(TerminalProvider provider, SystemStream systemStream) throws IOException {
try {
String result = exec(true, OSUtils.TTY_COMMAND);
@@ -50,14 +94,49 @@ public static Pty current(TerminalProvider provider, SystemStream systemStream)
}
}
+ /**
+ * Creates a new ExecPty instance.
+ *
+ *
+ * This constructor creates a new ExecPty instance with the specified provider,
+ * system stream, and terminal device name. It is protected because instances should
+ * typically be created using the {@link #current(TerminalProvider, SystemStream)} method.
+ *
+ *
+ * @param provider the terminal provider that will own this Pty
+ * @param systemStream the system stream associated with this Pty
+ * @param name the name of the terminal device (e.g., "/dev/tty")
+ */
protected ExecPty(TerminalProvider provider, SystemStream systemStream, String name) {
super(provider, systemStream);
this.name = name;
}
+ /**
+ * Closes this Pty.
+ *
+ *
+ * This implementation does nothing, as there are no resources to release.
+ * The terminal device is not actually opened by this class, so it does not
+ * need to be closed.
+ *
+ *
+ * @throws IOException if an I/O error occurs (never thrown by this implementation)
+ */
@Override
public void close() throws IOException {}
+ /**
+ * Returns the name of the terminal device.
+ *
+ *
+ * This method returns the name of the terminal device associated with this Pty,
+ * which was determined when the Pty was created. This is typically a device path
+ * such as "/dev/tty" or "/dev/pts/0".
+ *
+ *
+ * @return the name of the terminal device
+ */
public String getName() {
return name;
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/exec/ExecTerminalProvider.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/exec/ExecTerminalProvider.java
index 74c68859a1ef9..385878a9d4d94 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/exec/ExecTerminalProvider.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/exec/ExecTerminalProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -14,6 +14,7 @@
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
+import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.function.Function;
@@ -37,14 +38,71 @@
import static jdk.internal.org.jline.terminal.TerminalBuilder.PROP_REDIRECT_PIPE_CREATION_MODE_NATIVE;
import static jdk.internal.org.jline.terminal.TerminalBuilder.PROP_REDIRECT_PIPE_CREATION_MODE_REFLECTION;
+/**
+ * A terminal provider implementation that uses external commands to interact with the terminal.
+ *
+ *
+ * The ExecTerminalProvider class provides a TerminalProvider implementation that uses
+ * external commands (such as stty, tput, etc.) to interact with the terminal. This approach
+ * allows JLine to work in environments where native libraries are not available or cannot
+ * be used, by relying on standard command-line utilities that are typically available on
+ * Unix-like systems.
+ *
+ *
+ *
+ * This provider is typically used as a fallback when more direct methods of terminal
+ * interaction (such as JNI or JNA) are not available. While it provides good compatibility,
+ * it may have higher overhead due to the need to spawn external processes for many operations.
+ *
+ *
+ *
+ * The provider name is "exec", which can be specified in the {@code org.jline.terminal.provider}
+ * system property to force the use of this provider.
+ *
+ *
+ * @see org.jline.terminal.spi.TerminalProvider
+ * @see org.jline.terminal.impl.exec.ExecPty
+ */
public class ExecTerminalProvider implements TerminalProvider {
+ /**
+ * Default constructor.
+ */
+ public ExecTerminalProvider() {
+ // Default constructor
+ }
+
private static boolean warned;
+ /**
+ * Returns the name of this terminal provider.
+ *
+ *
+ * This method returns the name of this terminal provider, which is "exec".
+ * This name can be specified in the {@code org.jline.terminal.provider} system
+ * property to force the use of this provider.
+ *
+ *
+ * @return the name of this terminal provider ("exec")
+ */
public String name() {
return TerminalBuilder.PROP_PROVIDER_EXEC;
}
+ /**
+ * Creates a Pty for the current terminal.
+ *
+ *
+ * This method creates an ExecPty instance for the current terminal by executing
+ * the 'tty' command to determine the terminal device name. It is used to obtain
+ * a Pty object that can interact with the current terminal using external commands.
+ *
+ *
+ * @param systemStream the system stream to associate with the Pty
+ * @return a new ExecPty instance for the current terminal
+ * @throws IOException if the current terminal is not a TTY or if an error occurs
+ * while executing the 'tty' command
+ */
public Pty current(SystemStream systemStream) throws IOException {
if (!isSystemStream(systemStream)) {
throw new IOException("Not a system stream: " + systemStream);
@@ -52,12 +110,36 @@ public Pty current(SystemStream systemStream) throws IOException {
return ExecPty.current(this, systemStream);
}
+ /**
+ * Creates a terminal connected to a system stream.
+ *
+ *
+ * This method creates a terminal that is connected to one of the standard
+ * system streams (standard input, standard output, or standard error). It uses
+ * the ExecPty implementation to interact with the terminal using external commands.
+ *
+ *
+ * @param name the name of the terminal
+ * @param type the terminal type (e.g., "xterm", "dumb")
+ * @param ansiPassThrough whether to pass through ANSI escape sequences
+ * @param encoding the character encoding to use
+ * @param inputEncoding the character encoding to use for input
+ * @param outputEncoding the character encoding to use for output
+ * @param nativeSignals whether to use native signal handling
+ * @param signalHandler the signal handler to use
+ * @param paused whether the terminal should start in a paused state
+ * @param systemStream the system stream to connect to
+ * @return a new terminal connected to the specified system stream
+ * @throws IOException if an I/O error occurs
+ */
@Override
public Terminal sysTerminal(
String name,
String type,
boolean ansiPassThrough,
Charset encoding,
+ Charset inputEncoding,
+ Charset outputEncoding,
boolean nativeSignals,
Terminal.SignalHandler signalHandler,
boolean paused,
@@ -66,18 +148,115 @@ public Terminal sysTerminal(
throws IOException {
if (OSUtils.IS_WINDOWS) {
return winSysTerminal(
- name, type, ansiPassThrough, encoding, nativeSignals, signalHandler, paused, systemStream, inputStreamWrapper);
+ name,
+ type,
+ ansiPassThrough,
+ encoding,
+ inputEncoding,
+ outputEncoding,
+ outputEncoding,
+ nativeSignals,
+ signalHandler,
+ paused,
+ systemStream,
+ inputStreamWrapper);
} else {
return posixSysTerminal(
- name, type, ansiPassThrough, encoding, nativeSignals, signalHandler, paused, systemStream, inputStreamWrapper);
+ name,
+ type,
+ ansiPassThrough,
+ encoding,
+ inputEncoding,
+ outputEncoding,
+ outputEncoding,
+ nativeSignals,
+ signalHandler,
+ paused,
+ systemStream,
+ inputStreamWrapper);
}
}
+ /**
+ * Creates a terminal connected to a system stream.
+ *
+ *
+ * This method creates a terminal that is connected to one of the standard
+ * system streams (standard input, standard output, or standard error). It uses
+ * the ExecPty implementation to interact with the terminal using external commands.
+ *
+ *
+ * @param name the name of the terminal
+ * @param type the terminal type (e.g., "xterm", "dumb")
+ * @param ansiPassThrough whether to pass through ANSI escape sequences
+ * @param encoding the character encoding to use
+ * @param nativeSignals whether to use native signal handling
+ * @param signalHandler the signal handler to use
+ * @param paused whether the terminal should start in a paused state
+ * @param systemStream the system stream to connect to
+ * @return a new terminal connected to the specified system stream
+ * @throws IOException if an I/O error occurs
+ *
+ *
+ * /**
+ * Creates a terminal connected to a system stream on Windows.
+ *
+ *
+ * This method creates a terminal that is connected to one of the standard
+ * system streams on Windows. It uses the ExecPty implementation to interact
+ * with the terminal using external commands.
+ *
+ *
+ *
+ * Note that on Windows, the exec provider has limited functionality and may
+ * not work as well as the native providers (JNI, JNA, etc.).
+ *
+ *
+ * @param name the name of the terminal
+ * @param type the terminal type (e.g., "xterm", "dumb")
+ * @param ansiPassThrough whether to pass through ANSI escape sequences
+ * @param encoding the character encoding to use
+ * @param nativeSignals whether to use native signal handling
+ * @param signalHandler the signal handler to use
+ * @param paused whether the terminal should start in a paused state
+ * @param systemStream the system stream to connect to
+ * @return a new terminal connected to the specified system stream
+ * @throws IOException if an I/O error occurs
+ */
+ public Terminal winSysTerminal(
+ String name,
+ String type,
+ boolean ansiPassThrough,
+ Charset encoding,
+ boolean nativeSignals,
+ Terminal.SignalHandler signalHandler,
+ boolean paused,
+ SystemStream systemStream,
+ Function inputStreamWrapper)
+ throws IOException {
+ return winSysTerminal(
+ name,
+ type,
+ ansiPassThrough,
+ encoding,
+ encoding,
+ encoding,
+ encoding,
+ nativeSignals,
+ signalHandler,
+ paused,
+ systemStream,
+ inputStreamWrapper);
+ }
+
public Terminal winSysTerminal(
String name,
String type,
boolean ansiPassThrough,
Charset encoding,
+ Charset stdinEncoding,
+ Charset stdoutEncoding,
+ Charset stderrEncoding,
boolean nativeSignals,
Terminal.SignalHandler signalHandler,
boolean paused,
@@ -86,12 +265,35 @@ public Terminal winSysTerminal(
throws IOException {
if (OSUtils.IS_CYGWIN || OSUtils.IS_MSYSTEM) {
Pty pty = current(systemStream);
- return new PosixSysTerminal(name, type, pty, encoding, nativeSignals, signalHandler, inputStreamWrapper);
+ // Use the appropriate output encoding based on the system stream
+ Charset outputEncoding = systemStream == SystemStream.Error ? stderrEncoding : stdoutEncoding;
+ return new PosixSysTerminal(
+ name, type, pty, encoding, stdinEncoding, outputEncoding, nativeSignals, signalHandler, inputStreamWrapper);
} else {
return null;
}
}
+ /**
+ * Creates a terminal connected to a system stream on POSIX systems.
+ *
+ *
+ * This method creates a terminal that is connected to one of the standard
+ * system streams on POSIX systems (Linux, macOS, etc.). It uses the ExecPty
+ * implementation to interact with the terminal using external commands.
+ *
+ *
+ * @param name the name of the terminal
+ * @param type the terminal type (e.g., "xterm", "dumb")
+ * @param ansiPassThrough whether to pass through ANSI escape sequences
+ * @param encoding the character encoding to use
+ * @param nativeSignals whether to use native signal handling
+ * @param signalHandler the signal handler to use
+ * @param paused whether the terminal should start in a paused state
+ * @param systemStream the system stream to connect to
+ * @return a new terminal connected to the specified system stream
+ * @throws IOException if an I/O error occurs
+ */
public Terminal posixSysTerminal(
String name,
String type,
@@ -103,10 +305,66 @@ public Terminal posixSysTerminal(
SystemStream systemStream,
Function inputStreamWrapper)
throws IOException {
+ return posixSysTerminal(
+ name,
+ type,
+ ansiPassThrough,
+ encoding,
+ encoding,
+ encoding,
+ encoding,
+ nativeSignals,
+ signalHandler,
+ paused,
+ systemStream,
+ inputStreamWrapper);
+ }
+
+ public Terminal posixSysTerminal(
+ String name,
+ String type,
+ boolean ansiPassThrough,
+ Charset encoding,
+ Charset stdinEncoding,
+ Charset stdoutEncoding,
+ Charset stderrEncoding,
+ boolean nativeSignals,
+ Terminal.SignalHandler signalHandler,
+ boolean paused,
+ SystemStream systemStream,
+ Function inputStreamWrapper)
+ throws IOException {
Pty pty = current(systemStream);
- return new PosixSysTerminal(name, type, pty, encoding, nativeSignals, signalHandler, inputStreamWrapper);
+ // Use the appropriate output encoding based on the system stream
+ Charset outputEncoding = systemStream == SystemStream.Error ? stderrEncoding : stdoutEncoding;
+ return new PosixSysTerminal(
+ name, type, pty, encoding, stdinEncoding, outputEncoding, nativeSignals, signalHandler, inputStreamWrapper);
}
+ /**
+ * Creates a new terminal with custom input and output streams.
+ *
+ *
+ * This method creates a terminal that is connected to the specified input and
+ * output streams. It creates an ExternalTerminal that emulates the line
+ * discipline functionality typically provided by the operating system's terminal
+ * driver.
+ *
+ *
+ * @param name the name of the terminal
+ * @param type the terminal type (e.g., "xterm", "dumb")
+ * @param in the input stream to read from
+ * @param out the output stream to write to
+ * @param encoding the character encoding to use
+ * @param inputEncoding the character encoding to use for input
+ * @param outputEncoding the character encoding to use for output
+ * @param signalHandler the signal handler to use
+ * @param paused whether the terminal should start in a paused state
+ * @param attributes the initial terminal attributes
+ * @param size the initial terminal size
+ * @return a new terminal connected to the specified streams
+ * @throws IOException if an I/O error occurs
+ */
@Override
public Terminal newTerminal(
String name,
@@ -114,14 +372,63 @@ public Terminal newTerminal(
InputStream in,
OutputStream out,
Charset encoding,
+ Charset inputEncoding,
+ Charset outputEncoding,
Terminal.SignalHandler signalHandler,
boolean paused,
Attributes attributes,
Size size)
throws IOException {
- return new ExternalTerminal(this, name, type, in, out, encoding, signalHandler, paused, attributes, size);
+ return new ExternalTerminal(
+ this,
+ name,
+ type,
+ in,
+ out,
+ encoding,
+ inputEncoding,
+ outputEncoding,
+ signalHandler,
+ paused,
+ attributes,
+ size);
}
+ /**
+ * Creates a new terminal with custom input and output streams.
+ *
+ *
+ * This method creates a terminal that is connected to the specified input and
+ * output streams. It creates an ExternalTerminal that emulates the line
+ * discipline functionality typically provided by the operating system's terminal
+ * driver.
+ *
+ *
+ * @param name the name of the terminal
+ * @param type the terminal type (e.g., "xterm", "dumb")
+ * @param in the input stream to read from
+ * @param out the output stream to write to
+ * @param encoding the character encoding to use
+ * @param signalHandler the signal handler to use
+ * @param paused whether the terminal should start in a paused state
+ * @param attributes the initial terminal attributes
+ * @param size the initial terminal size
+ * @return a new terminal connected to the specified streams
+ * @throws IOException if an I/O error occurs
+ *
+ *
+ * /**
+ * Checks if the specified system stream is available on this platform.
+ *
+ *
+ * This method determines whether the specified system stream (standard input,
+ * standard output, or standard error) is available for use on the current
+ * platform. It checks both POSIX and Windows system streams.
+ *
+ *
+ * @param stream the system stream to check
+ * @return {@code true} if the system stream is available, {@code false} otherwise
+ */
@Override
public boolean isSystemStream(SystemStream stream) {
try {
@@ -137,10 +444,36 @@ public boolean isWindowsSystemStream(SystemStream stream) {
public boolean isPosixSystemStream(SystemStream stream) {
try {
- Process p = new ProcessBuilder(OSUtils.TEST_COMMAND, "-t", Integer.toString(stream.ordinal()))
- .inheritIO()
- .start();
- return p.waitFor() == 0;
+ ProcessBuilder pb = new ProcessBuilder(OSUtils.TEST_COMMAND, "-t", Integer.toString(stream.ordinal()));
+
+ if (OSUtils.IS_WINDOWS) {
+ // On Windows, avoid using inheritIO() to prevent the parent's
+ // stdin pipe from being closed when the subprocess terminates.
+ // Only inherit the specific stream being tested.
+ if (stream == SystemStream.Output) {
+ pb.redirectInput(ProcessBuilder.Redirect.PIPE);
+ pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
+ pb.redirectError(ProcessBuilder.Redirect.PIPE);
+ } else if (stream == SystemStream.Error) {
+ pb.redirectInput(ProcessBuilder.Redirect.PIPE);
+ pb.redirectOutput(ProcessBuilder.Redirect.PIPE);
+ pb.redirectError(ProcessBuilder.Redirect.INHERIT);
+ } else {
+ pb.redirectInput(newDescriptor(FileDescriptor.in));
+ pb.redirectOutput(ProcessBuilder.Redirect.PIPE);
+ pb.redirectError(ProcessBuilder.Redirect.PIPE);
+ }
+ } else {
+ // On non-Windows platforms, we can use inheritIO() safely
+ pb.inheritIO();
+ }
+
+ Process p = pb.start();
+ try {
+ return p.waitFor() == 0;
+ } finally {
+ p.destroy();
+ }
} catch (Throwable t) {
Log.debug("ExecTerminalProvider failed 'test -t' for " + stream, t);
// ignore
@@ -148,14 +481,32 @@ public boolean isPosixSystemStream(SystemStream stream) {
return false;
}
+ /**
+ * Returns the name of the specified system stream on this platform.
+ *
+ *
+ * This method returns a platform-specific name or identifier for the specified
+ * system stream. The name may be used for display purposes or for accessing
+ * the stream through platform-specific APIs.
+ *
+ *
+ * @param stream the system stream
+ * @return the name of the system stream on this platform
+ */
@Override
public String systemStreamName(SystemStream stream) {
try {
- ProcessBuilder.Redirect input = stream == SystemStream.Input
- ? ProcessBuilder.Redirect.INHERIT
- : newDescriptor(stream == SystemStream.Output ? FileDescriptor.out : FileDescriptor.err);
- Process p =
- new ProcessBuilder(OSUtils.TTY_COMMAND).redirectInput(input).start();
+ ProcessBuilder pb = new ProcessBuilder(OSUtils.TTY_COMMAND);
+
+ if (stream == SystemStream.Input) {
+ pb.redirectInput(
+ OSUtils.IS_WINDOWS ? newDescriptor(FileDescriptor.in) : ProcessBuilder.Redirect.INHERIT);
+ } else {
+ pb.redirectInput(
+ newDescriptor(stream == SystemStream.Output ? FileDescriptor.out : FileDescriptor.err));
+ }
+
+ Process p = pb.start();
String result = ExecHelper.waitAndCapture(p);
if (p.exitValue() == 0) {
return result.trim();
@@ -165,7 +516,7 @@ public String systemStreamName(SystemStream stream) {
.equals(t.getClass().getName())
&& !warned) {
Log.warn(
- "The ExecTerminalProvider requires the JVM options: '--add-opens java.base/java.lang=ALL-UNNAMED'");
+ "The ExecTerminalProvider requires the JVM options: '--add-opens java.base/java.lang=org.jline.terminal'");
warned = true;
}
// ignore
@@ -173,6 +524,24 @@ public String systemStreamName(SystemStream stream) {
return null;
}
+ /**
+ * Returns the width (number of columns) of the specified system stream.
+ *
+ *
+ * This method determines the width of the terminal associated with the specified
+ * system stream. The width is measured in character cells and represents the
+ * number of columns available for display.
+ *
+ *
+ *
+ * This implementation uses the 'tput cols' command to determine the terminal width.
+ * If the command fails or returns an invalid value, a default width of 80 columns
+ * is returned.
+ *
+ *
+ * @param stream the system stream
+ * @return the width of the system stream in character columns
+ */
@Override
public int systemStreamWidth(SystemStream stream) {
try (ExecPty pty = new ExecPty(this, stream, null)) {
@@ -221,7 +590,7 @@ interface RedirectPipeCreator {
/**
* Reflection based file descriptor creator.
* This requires the following option
- * --add-opens java.base/java.lang=ALL-UNNAMED
+ * --add-opens java.base/java.lang=org.jline.terminal
*/
static class ReflectionRedirectPipeCreator implements RedirectPipeCreator {
private final Constructor constructor;
@@ -249,18 +618,6 @@ public ProcessBuilder.Redirect newRedirectPipe(FileDescriptor fd) {
}
}
-// static class NativeRedirectPipeCreator implements RedirectPipeCreator {
-// public NativeRedirectPipeCreator() {
-// // Force load the library
-// JLineNativeLoader.initialize();
-// }
-//
-// @Override
-// public ProcessBuilder.Redirect newRedirectPipe(FileDescriptor fd) {
-// return JLineLibrary.newRedirectPipe(fd);
-// }
-// }
-
@Override
public String toString() {
return "TerminalProvider[" + name() + "]";
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/exec/package-info.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/exec/package-info.java
new file mode 100644
index 0000000000000..ab66cd4eed6c3
--- /dev/null
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/exec/package-info.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2002-2025, the original author(s).
+ *
+ * This software is distributable under the BSD license. See the terms of the
+ * BSD license in the documentation provided with this software.
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+
+/**
+ * Implementation of terminal functionality using external command-line utilities.
+ *
+ *
+ * This package provides implementations of terminal-related interfaces that rely on
+ * external command-line utilities (such as stty, tput, etc.) rather than native code
+ * or JNI libraries. This approach allows JLine to work in environments where native
+ * libraries are not available or cannot be used.
+ *
+ *
+ *
+ * The key components in this package are:
+ *
+ *
+ *
+ * {@link org.jline.terminal.impl.exec.ExecPty} - A pseudoterminal implementation
+ * that uses external commands to interact with the terminal. It provides functionality
+ * for terminal attribute manipulation, size detection, and process control using
+ * standard Unix utilities.
+ * {@link org.jline.terminal.impl.exec.ExecTerminalProvider} - A terminal provider
+ * that creates terminals using the exec-based approach. It serves as a fallback
+ * when native terminal access is not available.
+ *
+ *
+ *
+ * This package is particularly useful in the following scenarios:
+ *
+ *
+ *
+ * When running on platforms where JLine's native libraries cannot be loaded
+ * In restricted environments where JNI access is limited or prohibited
+ * As a fallback mechanism when preferred terminal access methods fail
+ * For debugging or testing terminal functionality without native dependencies
+ *
+ *
+ *
+ * The implementations in this package execute external commands to perform operations
+ * such as:
+ *
+ *
+ *
+ * Getting and setting terminal attributes (using stty)
+ * Determining terminal size (using stty or tput)
+ * Sending signals to processes
+ * Creating and managing pseudoterminals
+ *
+ *
+ *
+ * While this approach is more portable than native code, it may have performance
+ * implications due to the overhead of executing external processes. It is typically
+ * used as a fallback when more efficient methods are not available.
+ *
+ *
+ * @see org.jline.terminal.spi.TerminalProvider
+ * @see org.jline.terminal.spi.Pty
+ */
+package jdk.internal.org.jline.terminal.impl.exec;
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/CLibrary.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/CLibrary.java
index 453ce290d3ce8..cd99d7da0ea27 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/CLibrary.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/CLibrary.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022-2023, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -11,6 +11,7 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.UncheckedIOException;
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
@@ -57,10 +58,10 @@ static class winsize {
ws_col = FfmTerminalProvider.lookupVarHandle(LAYOUT, MemoryLayout.PathElement.groupElement("ws_col"));
}
- private final java.lang.foreign.MemorySegment seg;
+ private final MemorySegment seg;
winsize() {
- seg = java.lang.foreign.Arena.ofAuto().allocate(LAYOUT);
+ seg = Arena.ofAuto().allocate(LAYOUT);
}
winsize(short ws_col, short ws_row) {
@@ -69,7 +70,7 @@ static class winsize {
ws_row(ws_row);
}
- java.lang.foreign.MemorySegment segment() {
+ MemorySegment segment() {
return seg;
}
@@ -154,10 +155,10 @@ private static VarHandle adjust2LinuxHandle(VarHandle v) {
return v;
}
- private final java.lang.foreign.MemorySegment seg;
+ private final MemorySegment seg;
termios() {
- seg = java.lang.foreign.Arena.ofAuto().allocate(LAYOUT);
+ seg = Arena.ofAuto().allocate(LAYOUT);
}
termios(Attributes t) {
@@ -260,10 +261,10 @@ private static VarHandle adjust2LinuxHandle(VarHandle v) {
if (VSTATUS != (-1)) {
c_cc[VSTATUS] = (byte) t.getControlChar(Attributes.ControlChar.VSTATUS);
}
- c_cc().copyFrom(java.lang.foreign.MemorySegment.ofArray(c_cc));
+ c_cc().copyFrom(MemorySegment.ofArray(c_cc));
}
- java.lang.foreign.MemorySegment segment() {
+ MemorySegment segment() {
return seg;
}
@@ -299,7 +300,7 @@ void c_lflag(long f) {
c_lflag.set(seg, f);
}
- java.lang.foreign.MemorySegment c_cc() {
+ MemorySegment c_cc() {
return seg.asSlice(c_cc_offset, 20);
}
@@ -335,7 +336,6 @@ public Attributes asAttributes() {
long c_iflag = c_iflag();
EnumSet iflag = attr.getInputFlags();
addFlag(c_iflag, iflag, Attributes.InputFlag.IGNBRK, IGNBRK);
- addFlag(c_iflag, iflag, Attributes.InputFlag.IGNBRK, IGNBRK);
addFlag(c_iflag, iflag, Attributes.InputFlag.BRKINT, BRKINT);
addFlag(c_iflag, iflag, Attributes.InputFlag.IGNPAR, IGNPAR);
addFlag(c_iflag, iflag, Attributes.InputFlag.PARMRK, PARMRK);
@@ -435,12 +435,12 @@ public Attributes asAttributes() {
}
}
- static MethodHandle ioctl;
- static MethodHandle isatty;
- static MethodHandle openpty;
- static MethodHandle tcsetattr;
- static MethodHandle tcgetattr;
- static MethodHandle ttyname_r;
+ static final MethodHandle ioctl;
+ static final MethodHandle isatty;
+ static final MethodHandle openptyHandle;
+ static final MethodHandle tcsetattr;
+ static final MethodHandle tcgetattr;
+ static final MethodHandle ttyname_r;
static LinkageError openptyError;
static {
@@ -474,7 +474,7 @@ public Attributes asAttributes() {
LinkageError error = null;
Optional openPtyAddr = lookup.find("openpty");
if (openPtyAddr.isPresent()) {
- openpty = linker.downcallHandle(
+ openptyHandle = linker.downcallHandle(
openPtyAddr.get(),
FunctionDescriptor.of(
ValueLayout.JAVA_INT,
@@ -485,7 +485,7 @@ public Attributes asAttributes() {
ValueLayout.ADDRESS));
openptyError = null;
} else {
- openpty = null;
+ openptyHandle = null;
openptyError = error;
}
}
@@ -550,8 +550,7 @@ static boolean isTty(int fd) {
static String ttyName(int fd) {
try {
- java.lang.foreign.MemorySegment buf =
- java.lang.foreign.Arena.ofAuto().allocate(64);
+ MemorySegment buf = Arena.ofAuto().allocate(64);
int res = (int) ttyname_r.invoke(fd, buf, buf.byteSize());
byte[] data = buf.toArray(ValueLayout.JAVA_BYTE);
int len = 0;
@@ -569,20 +568,20 @@ static Pty openpty(TerminalProvider provider, Attributes attr, Size size) {
throw openptyError;
}
try {
- java.lang.foreign.MemorySegment buf =
- java.lang.foreign.Arena.ofAuto().allocate(64);
- java.lang.foreign.MemorySegment master =
- java.lang.foreign.Arena.ofAuto().allocate(ValueLayout.JAVA_INT);
- java.lang.foreign.MemorySegment slave =
- java.lang.foreign.Arena.ofAuto().allocate(ValueLayout.JAVA_INT);
- int res = (int) openpty.invoke(
+ MemorySegment buf = Arena.ofAuto().allocate(64);
+ MemorySegment master = Arena.ofAuto().allocate(ValueLayout.JAVA_INT);
+ MemorySegment slave = Arena.ofAuto().allocate(ValueLayout.JAVA_INT);
+ int res = (int) openptyHandle.invoke(
master,
slave,
buf,
- attr != null ? new termios(attr).segment() : java.lang.foreign.MemorySegment.NULL,
+ attr != null ? new termios(attr).segment() : MemorySegment.NULL,
size != null
? new winsize((short) size.getRows(), (short) size.getColumns()).segment()
- : java.lang.foreign.MemorySegment.NULL);
+ : MemorySegment.NULL);
+ if (res != 0) {
+ throw new UncheckedIOException(new IOException("Unable to call openpty(): return code " + res));
+ }
byte[] str = buf.toArray(ValueLayout.JAVA_BYTE);
int len = 0;
while (str[len] != 0) {
@@ -591,6 +590,8 @@ static Pty openpty(TerminalProvider provider, Attributes attr, Size size) {
String device = new String(str, 0, len);
return new FfmNativePty(
provider, null, master.get(ValueLayout.JAVA_INT, 0), slave.get(ValueLayout.JAVA_INT, 0), device);
+ } catch (UncheckedIOException e) {
+ throw e;
} catch (Throwable e) {
throw new RuntimeException("Unable to call openpty()", e);
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/FfmNativePty.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/FfmNativePty.java
index 1f8b668115aae..5abd3f998cd31 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/FfmNativePty.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/FfmNativePty.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022-2023, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/FfmTerminalProvider.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/FfmTerminalProvider.java
index 76f07926b125b..aff7ab545a1f0 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/FfmTerminalProvider.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/FfmTerminalProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022-2023, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -44,12 +44,22 @@ public String name() {
return TerminalBuilder.PROP_PROVIDER_FFM;
}
+ @Override
+ public int getConsoleCodepage() {
+ if (OSUtils.IS_WINDOWS) {
+ return Kernel32.GetConsoleOutputCP();
+ }
+ return -1;
+ }
+
@Override
public Terminal sysTerminal(
String name,
String type,
boolean ansiPassThrough,
Charset encoding,
+ Charset inputEncoding,
+ Charset outputEncoding,
boolean nativeSignals,
Terminal.SignalHandler signalHandler,
boolean paused,
@@ -58,7 +68,18 @@ public Terminal sysTerminal(
throws IOException {
if (OSUtils.IS_WINDOWS) {
return NativeWinSysTerminal.createTerminal(
- this, systemStream, name, type, ansiPassThrough, encoding, nativeSignals, signalHandler, paused, inputStreamWrapper);
+ this,
+ systemStream,
+ name,
+ type,
+ ansiPassThrough,
+ encoding,
+ inputEncoding,
+ outputEncoding,
+ nativeSignals,
+ signalHandler,
+ paused,
+ inputStreamWrapper);
} else {
Pty pty = new FfmNativePty(
this,
@@ -70,7 +91,8 @@ public Terminal sysTerminal(
systemStream == SystemStream.Output ? 1 : 2,
systemStream == SystemStream.Output ? FileDescriptor.out : FileDescriptor.err,
CLibrary.ttyName(0));
- return new PosixSysTerminal(name, type, pty, encoding, nativeSignals, signalHandler, inputStreamWrapper);
+ return new PosixSysTerminal(
+ name, type, pty, encoding, inputEncoding, outputEncoding, nativeSignals, signalHandler, inputStreamWrapper);
}
}
@@ -81,13 +103,16 @@ public Terminal newTerminal(
InputStream in,
OutputStream out,
Charset encoding,
+ Charset inputEncoding,
+ Charset outputEncoding,
Terminal.SignalHandler signalHandler,
boolean paused,
Attributes attributes,
Size size)
throws IOException {
Pty pty = CLibrary.openpty(this, attributes, size);
- return new PosixPtyTerminal(name, type, pty, in, out, encoding, signalHandler, paused);
+ return new PosixPtyTerminal(
+ name, type, pty, in, out, encoding, inputEncoding, outputEncoding, signalHandler, paused);
}
@Override
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/Kernel32.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/Kernel32.java
index baa4aad0ee0bb..f881790b8c86b 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/Kernel32.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/Kernel32.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009-2023, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -9,6 +9,15 @@
package jdk.internal.org.jline.terminal.impl.ffm;
import java.io.IOException;
+import java.lang.foreign.AddressLayout;
+import java.lang.foreign.Arena;
+import java.lang.foreign.FunctionDescriptor;
+import java.lang.foreign.GroupLayout;
+import java.lang.foreign.Linker;
+import java.lang.foreign.MemoryLayout;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.SymbolLookup;
+import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.VarHandle;
import java.nio.charset.StandardCharsets;
@@ -68,7 +77,7 @@ final class Kernel32 {
public static final short MENU_EVENT = 0x0008;
public static final short FOCUS_EVENT = 0x0010;
- public static int WaitForSingleObject(java.lang.foreign.MemorySegment hHandle, int dwMilliseconds) {
+ public static int WaitForSingleObject(MemorySegment hHandle, int dwMilliseconds) {
MethodHandle mh$ = requireNonNull(WaitForSingleObject$MH, "WaitForSingleObject");
try {
return (int) mh$.invokeExact(hHandle, dwMilliseconds);
@@ -77,10 +86,10 @@ public static int WaitForSingleObject(java.lang.foreign.MemorySegment hHandle, i
}
}
- public static java.lang.foreign.MemorySegment GetStdHandle(int nStdHandle) {
+ public static MemorySegment GetStdHandle(int nStdHandle) {
MethodHandle mh$ = requireNonNull(GetStdHandle$MH, "GetStdHandle");
try {
- return (java.lang.foreign.MemorySegment) mh$.invokeExact(nStdHandle);
+ return (MemorySegment) mh$.invokeExact(nStdHandle);
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
}
@@ -88,12 +97,12 @@ public static java.lang.foreign.MemorySegment GetStdHandle(int nStdHandle) {
public static int FormatMessageW(
int dwFlags,
- java.lang.foreign.MemorySegment lpSource,
+ MemorySegment lpSource,
int dwMessageId,
int dwLanguageId,
- java.lang.foreign.MemorySegment lpBuffer,
+ MemorySegment lpBuffer,
int nSize,
- java.lang.foreign.MemorySegment Arguments) {
+ MemorySegment Arguments) {
MethodHandle mh$ = requireNonNull(FormatMessageW$MH, "FormatMessageW");
try {
return (int) mh$.invokeExact(dwFlags, lpSource, dwMessageId, dwLanguageId, lpBuffer, nSize, Arguments);
@@ -102,7 +111,7 @@ public static int FormatMessageW(
}
}
- public static int SetConsoleTextAttribute(java.lang.foreign.MemorySegment hConsoleOutput, short wAttributes) {
+ public static int SetConsoleTextAttribute(MemorySegment hConsoleOutput, short wAttributes) {
MethodHandle mh$ = requireNonNull(SetConsoleTextAttribute$MH, "SetConsoleTextAttribute");
try {
return (int) mh$.invokeExact(hConsoleOutput, wAttributes);
@@ -111,7 +120,7 @@ public static int SetConsoleTextAttribute(java.lang.foreign.MemorySegment hConso
}
}
- public static int SetConsoleMode(java.lang.foreign.MemorySegment hConsoleHandle, int dwMode) {
+ public static int SetConsoleMode(MemorySegment hConsoleHandle, int dwMode) {
MethodHandle mh$ = requireNonNull(SetConsoleMode$MH, "SetConsoleMode");
try {
return (int) mh$.invokeExact(hConsoleHandle, dwMode);
@@ -120,8 +129,7 @@ public static int SetConsoleMode(java.lang.foreign.MemorySegment hConsoleHandle,
}
}
- public static int GetConsoleMode(
- java.lang.foreign.MemorySegment hConsoleHandle, java.lang.foreign.MemorySegment lpMode) {
+ public static int GetConsoleMode(MemorySegment hConsoleHandle, MemorySegment lpMode) {
MethodHandle mh$ = requireNonNull(GetConsoleMode$MH, "GetConsoleMode");
try {
return (int) mh$.invokeExact(hConsoleHandle, lpMode);
@@ -130,7 +138,16 @@ public static int GetConsoleMode(
}
}
- public static int SetConsoleTitleW(java.lang.foreign.MemorySegment lpConsoleTitle) {
+ public static int GetConsoleOutputCP() {
+ MethodHandle mh$ = requireNonNull(GetConsoleOutputCP$MH, "GetConsoleOutputCP");
+ try {
+ return (int) mh$.invokeExact();
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+
+ public static int SetConsoleTitleW(MemorySegment lpConsoleTitle) {
MethodHandle mh$ = requireNonNull(SetConsoleTitleW$MH, "SetConsoleTitleW");
try {
return (int) mh$.invokeExact(lpConsoleTitle);
@@ -139,7 +156,7 @@ public static int SetConsoleTitleW(java.lang.foreign.MemorySegment lpConsoleTitl
}
}
- public static int SetConsoleCursorPosition(java.lang.foreign.MemorySegment hConsoleOutput, COORD dwCursorPosition) {
+ public static int SetConsoleCursorPosition(MemorySegment hConsoleOutput, COORD dwCursorPosition) {
MethodHandle mh$ = requireNonNull(SetConsoleCursorPosition$MH, "SetConsoleCursorPosition");
try {
return (int) mh$.invokeExact(hConsoleOutput, dwCursorPosition.seg);
@@ -149,11 +166,11 @@ public static int SetConsoleCursorPosition(java.lang.foreign.MemorySegment hCons
}
public static int FillConsoleOutputCharacterW(
- java.lang.foreign.MemorySegment hConsoleOutput,
+ MemorySegment hConsoleOutput,
char cCharacter,
int nLength,
COORD dwWriteCoord,
- java.lang.foreign.MemorySegment lpNumberOfCharsWritten) {
+ MemorySegment lpNumberOfCharsWritten) {
MethodHandle mh$ = requireNonNull(FillConsoleOutputCharacterW$MH, "FillConsoleOutputCharacterW");
try {
return (int) mh$.invokeExact(hConsoleOutput, cCharacter, nLength, dwWriteCoord.seg, lpNumberOfCharsWritten);
@@ -163,11 +180,11 @@ public static int FillConsoleOutputCharacterW(
}
public static int FillConsoleOutputAttribute(
- java.lang.foreign.MemorySegment hConsoleOutput,
+ MemorySegment hConsoleOutput,
short wAttribute,
int nLength,
COORD dwWriteCoord,
- java.lang.foreign.MemorySegment lpNumberOfAttrsWritten) {
+ MemorySegment lpNumberOfAttrsWritten) {
MethodHandle mh$ = requireNonNull(FillConsoleOutputAttribute$MH, "FillConsoleOutputAttribute");
try {
return (int) mh$.invokeExact(hConsoleOutput, wAttribute, nLength, dwWriteCoord.seg, lpNumberOfAttrsWritten);
@@ -177,11 +194,11 @@ public static int FillConsoleOutputAttribute(
}
public static int WriteConsoleW(
- java.lang.foreign.MemorySegment hConsoleOutput,
- java.lang.foreign.MemorySegment lpBuffer,
+ MemorySegment hConsoleOutput,
+ MemorySegment lpBuffer,
int nNumberOfCharsToWrite,
- java.lang.foreign.MemorySegment lpNumberOfCharsWritten,
- java.lang.foreign.MemorySegment lpReserved) {
+ MemorySegment lpNumberOfCharsWritten,
+ MemorySegment lpReserved) {
MethodHandle mh$ = requireNonNull(WriteConsoleW$MH, "WriteConsoleW");
try {
return (int) mh$.invokeExact(
@@ -192,10 +209,7 @@ public static int WriteConsoleW(
}
public static int ReadConsoleInputW(
- java.lang.foreign.MemorySegment hConsoleInput,
- java.lang.foreign.MemorySegment lpBuffer,
- int nLength,
- java.lang.foreign.MemorySegment lpNumberOfEventsRead) {
+ MemorySegment hConsoleInput, MemorySegment lpBuffer, int nLength, MemorySegment lpNumberOfEventsRead) {
MethodHandle mh$ = requireNonNull(ReadConsoleInputW$MH, "ReadConsoleInputW");
try {
return (int) mh$.invokeExact(hConsoleInput, lpBuffer, nLength, lpNumberOfEventsRead);
@@ -205,10 +219,7 @@ public static int ReadConsoleInputW(
}
public static int PeekConsoleInputW(
- java.lang.foreign.MemorySegment hConsoleInput,
- java.lang.foreign.MemorySegment lpBuffer,
- int nLength,
- java.lang.foreign.MemorySegment lpNumberOfEventsRead) {
+ MemorySegment hConsoleInput, MemorySegment lpBuffer, int nLength, MemorySegment lpNumberOfEventsRead) {
MethodHandle mh$ = requireNonNull(PeekConsoleInputW$MH, "PeekConsoleInputW");
try {
return (int) mh$.invokeExact(hConsoleInput, lpBuffer, nLength, lpNumberOfEventsRead);
@@ -218,7 +229,7 @@ public static int PeekConsoleInputW(
}
public static int GetConsoleScreenBufferInfo(
- java.lang.foreign.MemorySegment hConsoleOutput, CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo) {
+ MemorySegment hConsoleOutput, CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo) {
MethodHandle mh$ = requireNonNull(GetConsoleScreenBufferInfo$MH, "GetConsoleScreenBufferInfo");
try {
return (int) mh$.invokeExact(hConsoleOutput, lpConsoleScreenBufferInfo.seg);
@@ -228,7 +239,7 @@ public static int GetConsoleScreenBufferInfo(
}
public static int ScrollConsoleScreenBuffer(
- java.lang.foreign.MemorySegment hConsoleOutput,
+ MemorySegment hConsoleOutput,
SMALL_RECT lpScrollRectangle,
SMALL_RECT lpClipRectangle,
COORD dwDestinationOrigin,
@@ -251,7 +262,7 @@ public static int GetLastError() {
}
}
- public static int GetFileType(java.lang.foreign.MemorySegment hFile) {
+ public static int GetFileType(MemorySegment hFile) {
MethodHandle mh$ = requireNonNull(GetFileType$MH, "GetFileType");
try {
return (int) mh$.invokeExact(hFile);
@@ -260,32 +271,31 @@ public static int GetFileType(java.lang.foreign.MemorySegment hFile) {
}
}
- public static java.lang.foreign.MemorySegment _get_osfhandle(int fd) {
+ public static MemorySegment _get_osfhandle(int fd) {
MethodHandle mh$ = requireNonNull(_get_osfhandle$MH, "_get_osfhandle");
try {
- return (java.lang.foreign.MemorySegment) mh$.invokeExact(fd);
+ return (MemorySegment) mh$.invokeExact(fd);
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
}
}
- public static INPUT_RECORD[] readConsoleInputHelper(java.lang.foreign.MemorySegment handle, int count, boolean peek)
+ public static INPUT_RECORD[] readConsoleInputHelper(MemorySegment handle, int count, boolean peek)
throws IOException {
- return readConsoleInputHelper(java.lang.foreign.Arena.ofAuto(), handle, count, peek);
+ return readConsoleInputHelper(Arena.ofAuto(), handle, count, peek);
}
- public static INPUT_RECORD[] readConsoleInputHelper(
- java.lang.foreign.Arena arena, java.lang.foreign.MemorySegment handle, int count, boolean peek)
+ public static INPUT_RECORD[] readConsoleInputHelper(Arena arena, MemorySegment handle, int count, boolean peek)
throws IOException {
- java.lang.foreign.MemorySegment inputRecordPtr = arena.allocate(INPUT_RECORD.LAYOUT, count);
- java.lang.foreign.MemorySegment length = arena.allocate(java.lang.foreign.ValueLayout.JAVA_INT, 1);
+ MemorySegment inputRecordPtr = arena.allocate(INPUT_RECORD.LAYOUT, count);
+ MemorySegment length = arena.allocate(ValueLayout.JAVA_INT, 1);
int res = peek
? PeekConsoleInputW(handle, inputRecordPtr, count, length)
: ReadConsoleInputW(handle, inputRecordPtr, count, length);
if (res == 0) {
throw new IOException("ReadConsoleInputW failed: " + getLastErrorMessage());
}
- int len = length.get(java.lang.foreign.ValueLayout.JAVA_INT, 0);
+ int len = length.get(ValueLayout.JAVA_INT, 0);
return inputRecordPtr
.elements(INPUT_RECORD.LAYOUT)
.map(INPUT_RECORD::new)
@@ -300,56 +310,49 @@ public static String getLastErrorMessage() {
public static String getErrorMessage(int errorCode) {
int bufferSize = 160;
- try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
- java.lang.foreign.MemorySegment data = arena.allocate(bufferSize);
+ try (Arena arena = Arena.ofConfined()) {
+ MemorySegment data = arena.allocate(bufferSize);
FormatMessageW(
- FORMAT_MESSAGE_FROM_SYSTEM,
- java.lang.foreign.MemorySegment.NULL,
- errorCode,
- 0,
- data,
- bufferSize,
- java.lang.foreign.MemorySegment.NULL);
- return new String(data.toArray(java.lang.foreign.ValueLayout.JAVA_BYTE), StandardCharsets.UTF_16LE).trim();
+ FORMAT_MESSAGE_FROM_SYSTEM, MemorySegment.NULL, errorCode, 0, data, bufferSize, MemorySegment.NULL);
+ return new String(data.toArray(ValueLayout.JAVA_BYTE), StandardCharsets.UTF_16LE).trim();
}
}
- private static final java.lang.foreign.SymbolLookup SYMBOL_LOOKUP;
+ private static final SymbolLookup SYMBOL_LOOKUP;
static {
System.loadLibrary("msvcrt");
System.loadLibrary("Kernel32");
- SYMBOL_LOOKUP = java.lang.foreign.SymbolLookup.loaderLookup();
+ SYMBOL_LOOKUP = SymbolLookup.loaderLookup();
}
- static MethodHandle downcallHandle(String name, java.lang.foreign.FunctionDescriptor fdesc) {
+ static MethodHandle downcallHandle(String name, FunctionDescriptor fdesc) {
return SYMBOL_LOOKUP
.find(name)
- .map(addr -> java.lang.foreign.Linker.nativeLinker().downcallHandle(addr, fdesc))
+ .map(addr -> Linker.nativeLinker().downcallHandle(addr, fdesc))
.orElse(null);
}
- static final java.lang.foreign.ValueLayout.OfBoolean C_BOOL$LAYOUT = java.lang.foreign.ValueLayout.JAVA_BOOLEAN;
- static final java.lang.foreign.ValueLayout.OfByte C_CHAR$LAYOUT = java.lang.foreign.ValueLayout.JAVA_BYTE;
- static final java.lang.foreign.ValueLayout.OfChar C_WCHAR$LAYOUT = java.lang.foreign.ValueLayout.JAVA_CHAR;
- static final java.lang.foreign.ValueLayout.OfShort C_SHORT$LAYOUT = java.lang.foreign.ValueLayout.JAVA_SHORT;
- static final java.lang.foreign.ValueLayout.OfShort C_WORD$LAYOUT = java.lang.foreign.ValueLayout.JAVA_SHORT;
- static final java.lang.foreign.ValueLayout.OfInt C_DWORD$LAYOUT = java.lang.foreign.ValueLayout.JAVA_INT;
- static final java.lang.foreign.ValueLayout.OfInt C_INT$LAYOUT = java.lang.foreign.ValueLayout.JAVA_INT;
- static final java.lang.foreign.ValueLayout.OfLong C_LONG$LAYOUT = java.lang.foreign.ValueLayout.JAVA_LONG;
- static final java.lang.foreign.ValueLayout.OfLong C_LONG_LONG$LAYOUT = java.lang.foreign.ValueLayout.JAVA_LONG;
- static final java.lang.foreign.ValueLayout.OfFloat C_FLOAT$LAYOUT = java.lang.foreign.ValueLayout.JAVA_FLOAT;
- static final java.lang.foreign.ValueLayout.OfDouble C_DOUBLE$LAYOUT = java.lang.foreign.ValueLayout.JAVA_DOUBLE;
- static final java.lang.foreign.AddressLayout C_POINTER$LAYOUT = java.lang.foreign.ValueLayout.ADDRESS;
-
- static final MethodHandle WaitForSingleObject$MH = downcallHandle(
- "WaitForSingleObject",
- java.lang.foreign.FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_INT$LAYOUT));
+ static final ValueLayout.OfBoolean C_BOOL$LAYOUT = ValueLayout.JAVA_BOOLEAN;
+ static final ValueLayout.OfByte C_CHAR$LAYOUT = ValueLayout.JAVA_BYTE;
+ static final ValueLayout.OfChar C_WCHAR$LAYOUT = ValueLayout.JAVA_CHAR;
+ static final ValueLayout.OfShort C_SHORT$LAYOUT = ValueLayout.JAVA_SHORT;
+ static final ValueLayout.OfShort C_WORD$LAYOUT = ValueLayout.JAVA_SHORT;
+ static final ValueLayout.OfInt C_DWORD$LAYOUT = ValueLayout.JAVA_INT;
+ static final ValueLayout.OfInt C_INT$LAYOUT = ValueLayout.JAVA_INT;
+ static final ValueLayout.OfLong C_LONG$LAYOUT = ValueLayout.JAVA_LONG;
+ static final ValueLayout.OfLong C_LONG_LONG$LAYOUT = ValueLayout.JAVA_LONG;
+ static final ValueLayout.OfFloat C_FLOAT$LAYOUT = ValueLayout.JAVA_FLOAT;
+ static final ValueLayout.OfDouble C_DOUBLE$LAYOUT = ValueLayout.JAVA_DOUBLE;
+ static final AddressLayout C_POINTER$LAYOUT = ValueLayout.ADDRESS;
+
+ static final MethodHandle WaitForSingleObject$MH =
+ downcallHandle("WaitForSingleObject", FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_INT$LAYOUT));
static final MethodHandle GetStdHandle$MH =
- downcallHandle("GetStdHandle", java.lang.foreign.FunctionDescriptor.of(C_POINTER$LAYOUT, C_INT$LAYOUT));
+ downcallHandle("GetStdHandle", FunctionDescriptor.of(C_POINTER$LAYOUT, C_INT$LAYOUT));
static final MethodHandle FormatMessageW$MH = downcallHandle(
"FormatMessageW",
- java.lang.foreign.FunctionDescriptor.of(
+ FunctionDescriptor.of(
C_INT$LAYOUT,
C_INT$LAYOUT,
C_POINTER$LAYOUT,
@@ -359,30 +362,29 @@ static MethodHandle downcallHandle(String name, java.lang.foreign.FunctionDescri
C_INT$LAYOUT,
C_POINTER$LAYOUT));
static final MethodHandle SetConsoleTextAttribute$MH = downcallHandle(
- "SetConsoleTextAttribute",
- java.lang.foreign.FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_SHORT$LAYOUT));
- static final MethodHandle SetConsoleMode$MH = downcallHandle(
- "SetConsoleMode", java.lang.foreign.FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_INT$LAYOUT));
- static final MethodHandle GetConsoleMode$MH = downcallHandle(
- "GetConsoleMode",
- java.lang.foreign.FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_POINTER$LAYOUT));
+ "SetConsoleTextAttribute", FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_SHORT$LAYOUT));
+ static final MethodHandle SetConsoleMode$MH =
+ downcallHandle("SetConsoleMode", FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_INT$LAYOUT));
+ static final MethodHandle GetConsoleMode$MH =
+ downcallHandle("GetConsoleMode", FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_POINTER$LAYOUT));
+ static final MethodHandle GetConsoleOutputCP$MH =
+ downcallHandle("GetConsoleOutputCP", FunctionDescriptor.of(C_INT$LAYOUT));
static final MethodHandle SetConsoleTitleW$MH =
- downcallHandle("SetConsoleTitleW", java.lang.foreign.FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT));
+ downcallHandle("SetConsoleTitleW", FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT));
static final MethodHandle SetConsoleCursorPosition$MH = downcallHandle(
- "SetConsoleCursorPosition",
- java.lang.foreign.FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, COORD.LAYOUT));
+ "SetConsoleCursorPosition", FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, COORD.LAYOUT));
static final MethodHandle FillConsoleOutputCharacterW$MH = downcallHandle(
"FillConsoleOutputCharacterW",
- java.lang.foreign.FunctionDescriptor.of(
+ FunctionDescriptor.of(
C_INT$LAYOUT, C_POINTER$LAYOUT, C_WCHAR$LAYOUT, C_INT$LAYOUT, COORD.LAYOUT, C_POINTER$LAYOUT));
static final MethodHandle FillConsoleOutputAttribute$MH = downcallHandle(
"FillConsoleOutputAttribute",
- java.lang.foreign.FunctionDescriptor.of(
+ FunctionDescriptor.of(
C_INT$LAYOUT, C_POINTER$LAYOUT, C_SHORT$LAYOUT, C_INT$LAYOUT, COORD.LAYOUT, C_POINTER$LAYOUT));
static final MethodHandle WriteConsoleW$MH = downcallHandle(
"WriteConsoleW",
- java.lang.foreign.FunctionDescriptor.of(
+ FunctionDescriptor.of(
C_INT$LAYOUT,
C_POINTER$LAYOUT,
C_POINTER$LAYOUT,
@@ -392,38 +394,34 @@ static MethodHandle downcallHandle(String name, java.lang.foreign.FunctionDescri
static final MethodHandle ReadConsoleInputW$MH = downcallHandle(
"ReadConsoleInputW",
- java.lang.foreign.FunctionDescriptor.of(
- C_INT$LAYOUT, C_POINTER$LAYOUT, C_POINTER$LAYOUT, C_INT$LAYOUT, C_POINTER$LAYOUT));
+ FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_POINTER$LAYOUT, C_INT$LAYOUT, C_POINTER$LAYOUT));
static final MethodHandle PeekConsoleInputW$MH = downcallHandle(
"PeekConsoleInputW",
- java.lang.foreign.FunctionDescriptor.of(
- C_INT$LAYOUT, C_POINTER$LAYOUT, C_POINTER$LAYOUT, C_INT$LAYOUT, C_POINTER$LAYOUT));
+ FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_POINTER$LAYOUT, C_INT$LAYOUT, C_POINTER$LAYOUT));
static final MethodHandle GetConsoleScreenBufferInfo$MH = downcallHandle(
- "GetConsoleScreenBufferInfo",
- java.lang.foreign.FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_POINTER$LAYOUT));
+ "GetConsoleScreenBufferInfo", FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_POINTER$LAYOUT));
static final MethodHandle ScrollConsoleScreenBufferW$MH = downcallHandle(
"ScrollConsoleScreenBufferW",
- java.lang.foreign.FunctionDescriptor.of(
+ FunctionDescriptor.of(
C_INT$LAYOUT,
C_POINTER$LAYOUT,
C_POINTER$LAYOUT,
C_POINTER$LAYOUT,
COORD.LAYOUT,
C_POINTER$LAYOUT));
- static final MethodHandle GetLastError$MH =
- downcallHandle("GetLastError", java.lang.foreign.FunctionDescriptor.of(C_INT$LAYOUT));
+ static final MethodHandle GetLastError$MH = downcallHandle("GetLastError", FunctionDescriptor.of(C_INT$LAYOUT));
static final MethodHandle GetFileType$MH =
- downcallHandle("GetFileType", java.lang.foreign.FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT));
+ downcallHandle("GetFileType", FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT));
static final MethodHandle _get_osfhandle$MH =
- downcallHandle("_get_osfhandle", java.lang.foreign.FunctionDescriptor.of(C_POINTER$LAYOUT, C_INT$LAYOUT));
+ downcallHandle("_get_osfhandle", FunctionDescriptor.of(C_POINTER$LAYOUT, C_INT$LAYOUT));
public static final class INPUT_RECORD {
- static final java.lang.foreign.MemoryLayout LAYOUT = java.lang.foreign.MemoryLayout.structLayout(
- java.lang.foreign.ValueLayout.JAVA_SHORT.withName("EventType"),
- java.lang.foreign.ValueLayout.JAVA_SHORT, // padding
- java.lang.foreign.MemoryLayout.unionLayout(
+ static final MemoryLayout LAYOUT = MemoryLayout.structLayout(
+ ValueLayout.JAVA_SHORT.withName("EventType"),
+ ValueLayout.JAVA_SHORT, // padding
+ MemoryLayout.unionLayout(
KEY_EVENT_RECORD.LAYOUT.withName("KeyEvent"),
MOUSE_EVENT_RECORD.LAYOUT.withName("MouseEvent"),
WINDOW_BUFFER_SIZE_RECORD.LAYOUT.withName("WindowBufferSizeEvent"),
@@ -433,17 +431,17 @@ public static final class INPUT_RECORD {
static final VarHandle EventType$VH = varHandle(LAYOUT, "EventType");
static final long Event$OFFSET = byteOffset(LAYOUT, "Event");
- private final java.lang.foreign.MemorySegment seg;
+ private final MemorySegment seg;
public INPUT_RECORD() {
- this(java.lang.foreign.Arena.ofAuto());
+ this(Arena.ofAuto());
}
- public INPUT_RECORD(java.lang.foreign.Arena arena) {
+ public INPUT_RECORD(Arena arena) {
this(arena.allocate(LAYOUT));
}
- public INPUT_RECORD(java.lang.foreign.MemorySegment seg) {
+ public INPUT_RECORD(MemorySegment seg) {
this.seg = seg;
}
@@ -466,21 +464,20 @@ public FOCUS_EVENT_RECORD focusEvent() {
public static final class MENU_EVENT_RECORD {
- static final java.lang.foreign.GroupLayout LAYOUT =
- java.lang.foreign.MemoryLayout.structLayout(C_DWORD$LAYOUT.withName("dwCommandId"));
+ static final GroupLayout LAYOUT = MemoryLayout.structLayout(C_DWORD$LAYOUT.withName("dwCommandId"));
static final VarHandle COMMAND_ID = varHandle(LAYOUT, "dwCommandId");
- private final java.lang.foreign.MemorySegment seg;
+ private final MemorySegment seg;
public MENU_EVENT_RECORD() {
- this(java.lang.foreign.Arena.ofAuto());
+ this(Arena.ofAuto());
}
- public MENU_EVENT_RECORD(java.lang.foreign.Arena arena) {
+ public MENU_EVENT_RECORD(Arena arena) {
this(arena.allocate(LAYOUT));
}
- public MENU_EVENT_RECORD(java.lang.foreign.MemorySegment seg) {
+ public MENU_EVENT_RECORD(MemorySegment seg) {
this.seg = seg;
}
@@ -495,25 +492,24 @@ public void commandId(int commandId) {
public static final class FOCUS_EVENT_RECORD {
- static final java.lang.foreign.GroupLayout LAYOUT =
- java.lang.foreign.MemoryLayout.structLayout(C_INT$LAYOUT.withName("bSetFocus"));
+ static final GroupLayout LAYOUT = MemoryLayout.structLayout(C_INT$LAYOUT.withName("bSetFocus"));
static final VarHandle SET_FOCUS = varHandle(LAYOUT, "bSetFocus");
- private final java.lang.foreign.MemorySegment seg;
+ private final MemorySegment seg;
public FOCUS_EVENT_RECORD() {
- this(java.lang.foreign.Arena.ofAuto());
+ this(Arena.ofAuto());
}
- public FOCUS_EVENT_RECORD(java.lang.foreign.Arena arena) {
+ public FOCUS_EVENT_RECORD(Arena arena) {
this(arena.allocate(LAYOUT));
}
- public FOCUS_EVENT_RECORD(java.lang.foreign.MemorySegment seg) {
+ public FOCUS_EVENT_RECORD(MemorySegment seg) {
this.seg = Objects.requireNonNull(seg);
}
- public FOCUS_EVENT_RECORD(java.lang.foreign.MemorySegment seg, long offset) {
+ public FOCUS_EVENT_RECORD(MemorySegment seg, long offset) {
this.seg = Objects.requireNonNull(seg).asSlice(offset, LAYOUT.byteSize());
}
@@ -528,21 +524,20 @@ public void setFocus(boolean setFocus) {
public static final class WINDOW_BUFFER_SIZE_RECORD {
- static final java.lang.foreign.GroupLayout LAYOUT =
- java.lang.foreign.MemoryLayout.structLayout(COORD.LAYOUT.withName("size"));
+ static final GroupLayout LAYOUT = MemoryLayout.structLayout(COORD.LAYOUT.withName("size"));
static final long SIZE_OFFSET = byteOffset(LAYOUT, "size");
- private final java.lang.foreign.MemorySegment seg;
+ private final MemorySegment seg;
public WINDOW_BUFFER_SIZE_RECORD() {
- this(java.lang.foreign.Arena.ofAuto());
+ this(Arena.ofAuto());
}
- public WINDOW_BUFFER_SIZE_RECORD(java.lang.foreign.Arena arena) {
+ public WINDOW_BUFFER_SIZE_RECORD(Arena arena) {
this(arena.allocate(LAYOUT));
}
- public WINDOW_BUFFER_SIZE_RECORD(java.lang.foreign.MemorySegment seg) {
+ public WINDOW_BUFFER_SIZE_RECORD(MemorySegment seg) {
this.seg = seg;
}
@@ -557,7 +552,7 @@ public String toString() {
public static final class MOUSE_EVENT_RECORD {
- static final java.lang.foreign.MemoryLayout LAYOUT = java.lang.foreign.MemoryLayout.structLayout(
+ static final MemoryLayout LAYOUT = MemoryLayout.structLayout(
COORD.LAYOUT.withName("dwMousePosition"),
C_DWORD$LAYOUT.withName("dwButtonState"),
C_DWORD$LAYOUT.withName("dwControlKeyState"),
@@ -567,21 +562,21 @@ public static final class MOUSE_EVENT_RECORD {
static final VarHandle CONTROL_KEY_STATE = varHandle(LAYOUT, "dwControlKeyState");
static final VarHandle EVENT_FLAGS = varHandle(LAYOUT, "dwEventFlags");
- private final java.lang.foreign.MemorySegment seg;
+ private final MemorySegment seg;
public MOUSE_EVENT_RECORD() {
- this(java.lang.foreign.Arena.ofAuto());
+ this(Arena.ofAuto());
}
- public MOUSE_EVENT_RECORD(java.lang.foreign.Arena arena) {
+ public MOUSE_EVENT_RECORD(Arena arena) {
this(arena.allocate(LAYOUT));
}
- public MOUSE_EVENT_RECORD(java.lang.foreign.MemorySegment seg) {
+ public MOUSE_EVENT_RECORD(MemorySegment seg) {
this.seg = Objects.requireNonNull(seg);
}
- public MOUSE_EVENT_RECORD(java.lang.foreign.MemorySegment seg, long offset) {
+ public MOUSE_EVENT_RECORD(MemorySegment seg, long offset) {
this.seg = Objects.requireNonNull(seg).asSlice(offset, LAYOUT.byteSize());
}
@@ -609,16 +604,16 @@ public String toString() {
public static final class KEY_EVENT_RECORD {
- static final java.lang.foreign.MemoryLayout LAYOUT = java.lang.foreign.MemoryLayout.structLayout(
- java.lang.foreign.ValueLayout.JAVA_INT.withName("bKeyDown"),
- java.lang.foreign.ValueLayout.JAVA_SHORT.withName("wRepeatCount"),
- java.lang.foreign.ValueLayout.JAVA_SHORT.withName("wVirtualKeyCode"),
- java.lang.foreign.ValueLayout.JAVA_SHORT.withName("wVirtualScanCode"),
- java.lang.foreign.MemoryLayout.unionLayout(
- java.lang.foreign.ValueLayout.JAVA_CHAR.withName("UnicodeChar"),
- java.lang.foreign.ValueLayout.JAVA_BYTE.withName("AsciiChar"))
+ static final MemoryLayout LAYOUT = MemoryLayout.structLayout(
+ ValueLayout.JAVA_INT.withName("bKeyDown"),
+ ValueLayout.JAVA_SHORT.withName("wRepeatCount"),
+ ValueLayout.JAVA_SHORT.withName("wVirtualKeyCode"),
+ ValueLayout.JAVA_SHORT.withName("wVirtualScanCode"),
+ MemoryLayout.unionLayout(
+ ValueLayout.JAVA_CHAR.withName("UnicodeChar"),
+ ValueLayout.JAVA_BYTE.withName("AsciiChar"))
.withName("uChar"),
- java.lang.foreign.ValueLayout.JAVA_INT.withName("dwControlKeyState"));
+ ValueLayout.JAVA_INT.withName("dwControlKeyState"));
static final VarHandle bKeyDown$VH = varHandle(LAYOUT, "bKeyDown");
static final VarHandle wRepeatCount$VH = varHandle(LAYOUT, "wRepeatCount");
static final VarHandle wVirtualKeyCode$VH = varHandle(LAYOUT, "wVirtualKeyCode");
@@ -627,21 +622,21 @@ public static final class KEY_EVENT_RECORD {
static final VarHandle AsciiChar$VH = varHandle(LAYOUT, "uChar", "AsciiChar");
static final VarHandle dwControlKeyState$VH = varHandle(LAYOUT, "dwControlKeyState");
- final java.lang.foreign.MemorySegment seg;
+ final MemorySegment seg;
public KEY_EVENT_RECORD() {
- this(java.lang.foreign.Arena.ofAuto());
+ this(Arena.ofAuto());
}
- public KEY_EVENT_RECORD(java.lang.foreign.Arena arena) {
+ public KEY_EVENT_RECORD(Arena arena) {
this(arena.allocate(LAYOUT));
}
- public KEY_EVENT_RECORD(java.lang.foreign.MemorySegment seg) {
+ public KEY_EVENT_RECORD(MemorySegment seg) {
this.seg = seg;
}
- public KEY_EVENT_RECORD(java.lang.foreign.MemorySegment seg, long offset) {
+ public KEY_EVENT_RECORD(MemorySegment seg, long offset) {
this.seg = Objects.requireNonNull(seg).asSlice(offset, LAYOUT.byteSize());
}
@@ -679,31 +674,30 @@ public String toString() {
public static final class CHAR_INFO {
- static final java.lang.foreign.GroupLayout LAYOUT = java.lang.foreign.MemoryLayout.structLayout(
- java.lang.foreign.MemoryLayout.unionLayout(
- C_WCHAR$LAYOUT.withName("UnicodeChar"), C_CHAR$LAYOUT.withName("AsciiChar"))
+ static final GroupLayout LAYOUT = MemoryLayout.structLayout(
+ MemoryLayout.unionLayout(C_WCHAR$LAYOUT.withName("UnicodeChar"), C_CHAR$LAYOUT.withName("AsciiChar"))
.withName("Char"),
C_WORD$LAYOUT.withName("Attributes"));
static final VarHandle UnicodeChar$VH = varHandle(LAYOUT, "Char", "UnicodeChar");
static final VarHandle Attributes$VH = varHandle(LAYOUT, "Attributes");
- final java.lang.foreign.MemorySegment seg;
+ final MemorySegment seg;
public CHAR_INFO() {
- this(java.lang.foreign.Arena.ofAuto());
+ this(Arena.ofAuto());
}
- public CHAR_INFO(java.lang.foreign.Arena arena) {
+ public CHAR_INFO(Arena arena) {
this(arena.allocate(LAYOUT));
}
- public CHAR_INFO(java.lang.foreign.Arena arena, char c, short a) {
+ public CHAR_INFO(Arena arena, char c, short a) {
this(arena);
UnicodeChar$VH.set(seg, c);
Attributes$VH.set(seg, a);
}
- public CHAR_INFO(java.lang.foreign.MemorySegment seg) {
+ public CHAR_INFO(MemorySegment seg) {
this.seg = seg;
}
@@ -713,7 +707,7 @@ public char unicodeChar() {
}
public static final class CONSOLE_SCREEN_BUFFER_INFO {
- static final java.lang.foreign.GroupLayout LAYOUT = java.lang.foreign.MemoryLayout.structLayout(
+ static final GroupLayout LAYOUT = MemoryLayout.structLayout(
COORD.LAYOUT.withName("dwSize"),
COORD.LAYOUT.withName("dwCursorPosition"),
C_WORD$LAYOUT.withName("wAttributes"),
@@ -724,17 +718,17 @@ public static final class CONSOLE_SCREEN_BUFFER_INFO {
static final VarHandle wAttributes$VH = varHandle(LAYOUT, "wAttributes");
static final long srWindow$OFFSET = byteOffset(LAYOUT, "srWindow");
- private final java.lang.foreign.MemorySegment seg;
+ private final MemorySegment seg;
public CONSOLE_SCREEN_BUFFER_INFO() {
- this(java.lang.foreign.Arena.ofAuto());
+ this(Arena.ofAuto());
}
- public CONSOLE_SCREEN_BUFFER_INFO(java.lang.foreign.Arena arena) {
+ public CONSOLE_SCREEN_BUFFER_INFO(Arena arena) {
this(arena.allocate(LAYOUT));
}
- public CONSOLE_SCREEN_BUFFER_INFO(java.lang.foreign.MemorySegment seg) {
+ public CONSOLE_SCREEN_BUFFER_INFO(MemorySegment seg) {
this.seg = seg;
}
@@ -769,32 +763,32 @@ public void attributes(short attr) {
public static final class COORD {
- static final java.lang.foreign.GroupLayout LAYOUT =
- java.lang.foreign.MemoryLayout.structLayout(C_SHORT$LAYOUT.withName("x"), C_SHORT$LAYOUT.withName("y"));
+ static final GroupLayout LAYOUT =
+ MemoryLayout.structLayout(C_SHORT$LAYOUT.withName("x"), C_SHORT$LAYOUT.withName("y"));
static final VarHandle x$VH = varHandle(LAYOUT, "x");
static final VarHandle y$VH = varHandle(LAYOUT, "y");
- private final java.lang.foreign.MemorySegment seg;
+ private final MemorySegment seg;
public COORD() {
- this(java.lang.foreign.Arena.ofAuto());
+ this(Arena.ofAuto());
}
- public COORD(java.lang.foreign.Arena arena) {
+ public COORD(Arena arena) {
this(arena.allocate(LAYOUT));
}
- public COORD(java.lang.foreign.Arena arena, short x, short y) {
+ public COORD(Arena arena, short x, short y) {
this(arena.allocate(LAYOUT));
x(x);
y(y);
}
- public COORD(java.lang.foreign.MemorySegment seg) {
+ public COORD(MemorySegment seg) {
this.seg = seg;
}
- public COORD(java.lang.foreign.MemorySegment seg, long offset) {
+ public COORD(MemorySegment seg, long offset) {
this.seg = Objects.requireNonNull(seg).asSlice(offset, LAYOUT.byteSize());
}
@@ -814,14 +808,14 @@ public void y(short y) {
COORD.y$VH.set(seg, y);
}
- public COORD copy(java.lang.foreign.Arena arena) {
+ public COORD copy(Arena arena) {
return new COORD(arena.allocate(LAYOUT).copyFrom(seg));
}
}
public static final class SMALL_RECT {
- static final java.lang.foreign.GroupLayout LAYOUT = java.lang.foreign.MemoryLayout.structLayout(
+ static final GroupLayout LAYOUT = MemoryLayout.structLayout(
C_SHORT$LAYOUT.withName("Left"),
C_SHORT$LAYOUT.withName("Top"),
C_SHORT$LAYOUT.withName("Right"),
@@ -831,17 +825,17 @@ public static final class SMALL_RECT {
static final VarHandle Right$VH = varHandle(LAYOUT, "Right");
static final VarHandle Bottom$VH = varHandle(LAYOUT, "Bottom");
- private final java.lang.foreign.MemorySegment seg;
+ private final MemorySegment seg;
public SMALL_RECT() {
- this(java.lang.foreign.Arena.ofAuto());
+ this(Arena.ofAuto());
}
- public SMALL_RECT(java.lang.foreign.Arena arena) {
+ public SMALL_RECT(Arena arena) {
this(arena.allocate(LAYOUT));
}
- public SMALL_RECT(java.lang.foreign.Arena arena, SMALL_RECT rect) {
+ public SMALL_RECT(Arena arena, SMALL_RECT rect) {
this(arena);
left(rect.left());
right(rect.right());
@@ -849,11 +843,11 @@ public SMALL_RECT(java.lang.foreign.Arena arena, SMALL_RECT rect) {
bottom(rect.bottom());
}
- public SMALL_RECT(java.lang.foreign.MemorySegment seg, long offset) {
+ public SMALL_RECT(MemorySegment seg, long offset) {
this(seg.asSlice(offset, LAYOUT.byteSize()));
}
- public SMALL_RECT(java.lang.foreign.MemorySegment seg) {
+ public SMALL_RECT(MemorySegment seg) {
this.seg = seg;
}
@@ -897,7 +891,7 @@ public void bottom(short b) {
Bottom$VH.set(seg, b);
}
- public SMALL_RECT copy(java.lang.foreign.Arena arena) {
+ public SMALL_RECT copy(Arena arena) {
return new SMALL_RECT(arena.allocate(LAYOUT).copyFrom(seg));
}
}
@@ -909,19 +903,16 @@ static T requireNonNull(T obj, String symbolName) {
return obj;
}
- static VarHandle varHandle(java.lang.foreign.MemoryLayout layout, String name) {
- return FfmTerminalProvider.lookupVarHandle(
- layout, java.lang.foreign.MemoryLayout.PathElement.groupElement(name));
+ static VarHandle varHandle(MemoryLayout layout, String name) {
+ return FfmTerminalProvider.lookupVarHandle(layout, MemoryLayout.PathElement.groupElement(name));
}
- static VarHandle varHandle(java.lang.foreign.MemoryLayout layout, String e1, String name) {
+ static VarHandle varHandle(MemoryLayout layout, String e1, String name) {
return FfmTerminalProvider.lookupVarHandle(
- layout,
- java.lang.foreign.MemoryLayout.PathElement.groupElement(e1),
- java.lang.foreign.MemoryLayout.PathElement.groupElement(name));
+ layout, MemoryLayout.PathElement.groupElement(e1), MemoryLayout.PathElement.groupElement(name));
}
- static long byteOffset(java.lang.foreign.MemoryLayout layout, String name) {
- return layout.byteOffset(java.lang.foreign.MemoryLayout.PathElement.groupElement(name));
+ static long byteOffset(MemoryLayout layout, String name) {
+ return layout.byteOffset(MemoryLayout.PathElement.groupElement(name));
}
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/NativeWinConsoleWriter.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/NativeWinConsoleWriter.java
index 55a2ec650552a..6189fc050b6be 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/NativeWinConsoleWriter.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/NativeWinConsoleWriter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022-2023, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -9,6 +9,7 @@
package jdk.internal.org.jline.terminal.impl.ffm;
import java.io.IOException;
+import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
@@ -21,12 +22,12 @@
class NativeWinConsoleWriter extends AbstractWindowsConsoleWriter {
- private final java.lang.foreign.MemorySegment console = GetStdHandle(STD_OUTPUT_HANDLE);
+ private final MemorySegment console = GetStdHandle(STD_OUTPUT_HANDLE);
@Override
protected void writeConsole(char[] text, int len) throws IOException {
- try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
- java.lang.foreign.MemorySegment txt = arena.allocateFrom(ValueLayout.JAVA_CHAR, text);
+ try (Arena arena = Arena.ofConfined()) {
+ MemorySegment txt = arena.allocateFrom(ValueLayout.JAVA_CHAR, text);
if (WriteConsoleW(console, txt, len, MemorySegment.NULL, MemorySegment.NULL) == 0) {
throw new IOException("Failed to write to console: " + getLastErrorMessage());
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/NativeWinSysTerminal.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/NativeWinSysTerminal.java
index e4c123711f3f6..f22eb369e6c69 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/NativeWinSysTerminal.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/NativeWinSysTerminal.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022-2023, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -13,6 +13,9 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
+import java.lang.foreign.Arena;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
import java.nio.charset.Charset;
import java.util.function.Function;
import java.util.function.IntConsumer;
@@ -40,7 +43,7 @@
import static jdk.internal.org.jline.terminal.impl.ffm.Kernel32.getLastErrorMessage;
import static jdk.internal.org.jline.terminal.impl.ffm.Kernel32.readConsoleInputHelper;
-public class NativeWinSysTerminal extends AbstractWindowsTerminal {
+public class NativeWinSysTerminal extends AbstractWindowsTerminal {
public static NativeWinSysTerminal createTerminal(
TerminalProvider provider,
@@ -54,15 +57,44 @@ public static NativeWinSysTerminal createTerminal(
boolean paused,
Function inputStreamWrapper)
throws IOException {
- try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ return createTerminal(
+ provider,
+ systemStream,
+ name,
+ type,
+ ansiPassThrough,
+ encoding,
+ encoding,
+ encoding,
+ nativeSignals,
+ signalHandler,
+ paused,
+ inputStreamWrapper);
+ }
+
+ public static NativeWinSysTerminal createTerminal(
+ TerminalProvider provider,
+ SystemStream systemStream,
+ String name,
+ String type,
+ boolean ansiPassThrough,
+ Charset encoding,
+ Charset inputEncoding,
+ Charset outputEncoding,
+ boolean nativeSignals,
+ SignalHandler signalHandler,
+ boolean paused,
+ Function inputStreamWrapper)
+ throws IOException {
+ try (Arena arena = Arena.ofConfined()) {
// Get input console mode
- java.lang.foreign.MemorySegment consoleIn = GetStdHandle(STD_INPUT_HANDLE);
- java.lang.foreign.MemorySegment inMode = allocateInt(arena);
+ MemorySegment consoleIn = GetStdHandle(STD_INPUT_HANDLE);
+ MemorySegment inMode = allocateInt(arena);
if (GetConsoleMode(consoleIn, inMode) == 0) {
throw new IOException("Failed to get console mode: " + getLastErrorMessage());
}
// Get output console and mode
- java.lang.foreign.MemorySegment console;
+ MemorySegment console;
switch (systemStream) {
case Output:
console = GetStdHandle(STD_OUTPUT_HANDLE);
@@ -73,7 +105,7 @@ public static NativeWinSysTerminal createTerminal(
default:
throw new IllegalArgumentException("Unsupported stream for console: " + systemStream);
}
- java.lang.foreign.MemorySegment outMode = allocateInt(arena);
+ MemorySegment outMode = allocateInt(arena);
if (GetConsoleMode(console, outMode) == 0) {
throw new IOException("Failed to get console mode: " + getLastErrorMessage());
}
@@ -83,7 +115,7 @@ public static NativeWinSysTerminal createTerminal(
type = type != null ? type : OSUtils.IS_CONEMU ? TYPE_WINDOWS_CONEMU : TYPE_WINDOWS;
writer = new NativeWinConsoleWriter();
} else {
- int m = outMode.get(java.lang.foreign.ValueLayout.JAVA_INT, 0);
+ int m = outMode.get(ValueLayout.JAVA_INT, 0);
if (enableVtp(console, m)) {
type = type != null ? type : TYPE_WINDOWS_VTP;
writer = new NativeWinConsoleWriter();
@@ -103,12 +135,14 @@ public static NativeWinSysTerminal createTerminal(
name,
type,
encoding,
+ inputEncoding,
+ outputEncoding,
nativeSignals,
signalHandler,
consoleIn,
- inMode.get(java.lang.foreign.ValueLayout.JAVA_INT, 0),
+ inMode.get(ValueLayout.JAVA_INT, 0),
console,
- outMode.get(java.lang.foreign.ValueLayout.JAVA_INT, 0),
+ outMode.get(ValueLayout.JAVA_INT, 0),
inputStreamWrapper);
// Start input pump thread
if (!paused) {
@@ -118,14 +152,14 @@ public static NativeWinSysTerminal createTerminal(
}
}
- private static boolean enableVtp(java.lang.foreign.MemorySegment console, int m) {
+ private static boolean enableVtp(MemorySegment console, int m) {
return SetConsoleMode(console, m | AbstractWindowsTerminal.ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0;
}
public static boolean isWindowsSystemStream(SystemStream stream) {
- try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
- java.lang.foreign.MemorySegment console;
- java.lang.foreign.MemorySegment mode = allocateInt(arena);
+ try (Arena arena = Arena.ofConfined()) {
+ MemorySegment console;
+ MemorySegment mode = allocateInt(arena);
switch (stream) {
case Input:
console = GetStdHandle(STD_INPUT_HANDLE);
@@ -143,8 +177,41 @@ public static boolean isWindowsSystemStream(SystemStream stream) {
}
}
- private static java.lang.foreign.MemorySegment allocateInt(java.lang.foreign.Arena arena) {
- return arena.allocate(java.lang.foreign.ValueLayout.JAVA_INT);
+ private static MemorySegment allocateInt(Arena arena) {
+ return arena.allocate(ValueLayout.JAVA_INT);
+ }
+
+ NativeWinSysTerminal(
+ TerminalProvider provider,
+ SystemStream systemStream,
+ Writer writer,
+ String name,
+ String type,
+ Charset encoding,
+ boolean nativeSignals,
+ SignalHandler signalHandler,
+ MemorySegment inConsole,
+ int inConsoleMode,
+ MemorySegment outConsole,
+ int outConsoleMode,
+ Function inputStreamWrapper)
+ throws IOException {
+ this(
+ provider,
+ systemStream,
+ writer,
+ name,
+ type,
+ encoding,
+ encoding,
+ encoding,
+ nativeSignals,
+ signalHandler,
+ inConsole,
+ inConsoleMode,
+ outConsole,
+ outConsoleMode,
+ inputStreamWrapper);
}
NativeWinSysTerminal(
@@ -154,11 +221,13 @@ private static java.lang.foreign.MemorySegment allocateInt(java.lang.foreign.Are
String name,
String type,
Charset encoding,
+ Charset inputEncoding,
+ Charset outputEncoding,
boolean nativeSignals,
SignalHandler signalHandler,
- java.lang.foreign.MemorySegment inConsole,
+ MemorySegment inConsole,
int inConsoleMode,
- java.lang.foreign.MemorySegment outConsole,
+ MemorySegment outConsole,
int outConsoleMode,
Function inputStreamWrapper)
throws IOException {
@@ -169,6 +238,8 @@ private static java.lang.foreign.MemorySegment allocateInt(java.lang.foreign.Are
name,
type,
encoding,
+ inputEncoding,
+ outputEncoding,
nativeSignals,
signalHandler,
inConsole,
@@ -179,23 +250,23 @@ private static java.lang.foreign.MemorySegment allocateInt(java.lang.foreign.Are
}
@Override
- protected int getConsoleMode(java.lang.foreign.MemorySegment console) {
- try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
- java.lang.foreign.MemorySegment mode = arena.allocate(java.lang.foreign.ValueLayout.JAVA_INT);
+ protected int getConsoleMode(MemorySegment console) {
+ try (Arena arena = Arena.ofConfined()) {
+ MemorySegment mode = arena.allocate(ValueLayout.JAVA_INT);
if (GetConsoleMode(console, mode) == 0) {
return -1;
}
- return mode.get(java.lang.foreign.ValueLayout.JAVA_INT, 0);
+ return mode.get(ValueLayout.JAVA_INT, 0);
}
}
@Override
- protected void setConsoleMode(java.lang.foreign.MemorySegment console, int mode) {
+ protected void setConsoleMode(MemorySegment console, int mode) {
SetConsoleMode(console, mode);
}
public Size getSize() {
- try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ try (Arena arena = Arena.ofConfined()) {
CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO(arena);
GetConsoleScreenBufferInfo(outConsole, info);
return new Size(info.windowWidth(), info.windowHeight());
@@ -204,7 +275,7 @@ public Size getSize() {
@Override
public Size getBufferSize() {
- try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ try (Arena arena = Arena.ofConfined()) {
CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO(arena);
GetConsoleScreenBufferInfo(outConsole, info);
return new Size(info.size().x(), info.size().y());
@@ -212,7 +283,7 @@ public Size getBufferSize() {
}
protected boolean processConsoleInput() throws IOException {
- try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ try (Arena arena = Arena.ofConfined()) {
INPUT_RECORD[] events;
if (inConsole != null
&& inConsole.address() != INVALID_HANDLE_VALUE
@@ -273,7 +344,7 @@ private void processMouseEvent(MOUSE_EVENT_RECORD mouseEvent) throws IOException
} else if (dwEventFlags == MOUSE_HWHEELED) {
return;
} else if ((dwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED) != 0) {
- cb |= 0x00;
+ // cb is already 0 for left button
} else if ((dwButtonState & RIGHTMOST_BUTTON_PRESSED) != 0) {
cb |= 0x01;
} else if ((dwButtonState & FROM_LEFT_2ND_BUTTON_PRESSED) != 0) {
@@ -291,7 +362,7 @@ private void processMouseEvent(MOUSE_EVENT_RECORD mouseEvent) throws IOException
@Override
public Cursor getCursorPosition(IntConsumer discarded) {
- try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ try (Arena arena = Arena.ofConfined()) {
CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO(arena);
if (GetConsoleScreenBufferInfo(outConsole, info) == 0) {
throw new IOError(new IOException("Could not get the cursor position: " + getLastErrorMessage()));
@@ -299,4 +370,26 @@ public Cursor getCursorPosition(IntConsumer discarded) {
return new Cursor(info.cursorPosition().x(), info.cursorPosition().y());
}
}
+
+ @Override
+ public int getDefaultForegroundColor() {
+ try (Arena arena = Arena.ofConfined()) {
+ CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO(arena);
+ if (GetConsoleScreenBufferInfo(outConsole, info) == 0) {
+ return -1;
+ }
+ return convertAttributeToRgb(info.attributes() & 0x0F, true);
+ }
+ }
+
+ @Override
+ public int getDefaultBackgroundColor() {
+ try (Arena arena = Arena.ofConfined()) {
+ CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO(arena);
+ if (GetConsoleScreenBufferInfo(outConsole, info) == 0) {
+ return -1;
+ }
+ return convertAttributeToRgb((info.attributes() & 0xF0) >> 4, false);
+ }
+ }
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/WindowsAnsiWriter.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/WindowsAnsiWriter.java
index 6330e975952ce..64378ad8829f3 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/WindowsAnsiWriter.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ffm/WindowsAnsiWriter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022-2023, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -10,6 +10,9 @@
import java.io.IOException;
import java.io.Writer;
+import java.lang.foreign.Arena;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
import jdk.internal.org.jline.utils.AnsiWriter;
import jdk.internal.org.jline.utils.Colors;
@@ -39,7 +42,7 @@
class WindowsAnsiWriter extends AnsiWriter {
- private static final java.lang.foreign.MemorySegment console = GetStdHandle(STD_OUTPUT_HANDLE);
+ private static final MemorySegment console = GetStdHandle(STD_OUTPUT_HANDLE);
private static final short FOREGROUND_BLACK = 0;
private static final short FOREGROUND_YELLOW = (short) (FOREGROUND_RED | FOREGROUND_GREEN);
@@ -75,7 +78,7 @@ class WindowsAnsiWriter extends AnsiWriter {
BACKGROUND_WHITE,
};
- private final CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO(java.lang.foreign.Arena.ofAuto());
+ private final CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO(Arena.ofAuto());
private final short originalColors;
private boolean negative;
@@ -142,8 +145,8 @@ private void applyCursorPosition() throws IOException {
@Override
protected void processEraseScreen(int eraseOption) throws IOException {
getConsoleInfo();
- try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
- java.lang.foreign.MemorySegment written = arena.allocate(java.lang.foreign.ValueLayout.JAVA_INT);
+ try (Arena arena = Arena.ofConfined()) {
+ MemorySegment written = arena.allocate(ValueLayout.JAVA_INT);
switch (eraseOption) {
case ERASE_SCREEN -> {
COORD topLeft = new COORD(arena, (short) 0, info.window().top());
@@ -176,8 +179,8 @@ protected void processEraseScreen(int eraseOption) throws IOException {
@Override
protected void processEraseLine(int eraseOption) throws IOException {
getConsoleInfo();
- try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
- java.lang.foreign.MemorySegment written = arena.allocate(java.lang.foreign.ValueLayout.JAVA_INT);
+ try (Arena arena = Arena.ofConfined()) {
+ MemorySegment written = arena.allocate(ValueLayout.JAVA_INT);
switch (eraseOption) {
case ERASE_LINE -> {
COORD leftColCurrRow =
@@ -243,7 +246,7 @@ protected void processCursorDown(int count) throws IOException {
applyCursorPosition();
}
if (nb > 0) {
- try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ try (Arena arena = Arena.ofConfined()) {
SMALL_RECT scroll = new SMALL_RECT(arena, info.window());
scroll.top((short) 0);
COORD org = new COORD(arena);
@@ -369,7 +372,7 @@ protected void processRestoreCursorPosition() throws IOException {
@Override
protected void processInsertLine(int optionInt) throws IOException {
- try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ try (Arena arena = Arena.ofConfined()) {
getConsoleInfo();
SMALL_RECT scroll = info.window().copy(arena);
scroll.top(info.cursorPosition().y());
@@ -384,7 +387,7 @@ protected void processInsertLine(int optionInt) throws IOException {
@Override
protected void processDeleteLine(int optionInt) throws IOException {
- try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ try (Arena arena = Arena.ofConfined()) {
getConsoleInfo();
SMALL_RECT scroll = info.window().copy(arena);
scroll.top(info.cursorPosition().y());
@@ -399,8 +402,8 @@ protected void processDeleteLine(int optionInt) throws IOException {
@Override
protected void processChangeWindowTitle(String title) {
- try (java.lang.foreign.Arena session = java.lang.foreign.Arena.ofConfined()) {
- java.lang.foreign.MemorySegment str = session.allocateFrom(title);
+ try (Arena session = Arena.ofConfined()) {
+ MemorySegment str = session.allocateFrom(title);
SetConsoleTitleW(str);
}
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/package-info.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/package-info.java
index 980b65b617165..9f1fbe72ae7ea 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/package-info.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author or authors.
+ * Copyright (c) 2002-2025, 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.
@@ -7,7 +7,9 @@
* https://opensource.org/licenses/BSD-3-Clause
*/
/**
- * JLine 3.
+ * JLine Terminal implementation classes.
+ *
+ * This package contains the implementation classes for the JLine Terminal API.
*
* @since 3.0
*/
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/package-info.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/package-info.java
new file mode 100644
index 0000000000000..010a6edf870da
--- /dev/null
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/package-info.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2002-2025, 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.
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+/**
+ * JLine Terminal API - Core abstractions for terminal operations across different platforms.
+ *
+ * This package provides a comprehensive API for interacting with terminals in a platform-independent
+ * way. It abstracts the differences between various terminal types and operating systems, allowing
+ * applications to work consistently across environments.
+ *
+ * Key Components
+ *
+ * Terminal Interface
+ * The {@link org.jline.terminal.Terminal} interface is the central abstraction, representing a virtual
+ * terminal. It provides methods for:
+ *
+ * Reading input and writing output
+ * Managing terminal attributes
+ * Handling terminal size and cursor positioning
+ * Processing signals (like CTRL+C)
+ * Supporting mouse events
+ * Accessing terminal capabilities
+ *
+ *
+ * Terminal Creation
+ * The {@link org.jline.terminal.TerminalBuilder} class provides a fluent API for creating terminal
+ * instances with specific configurations. It supports various terminal types and providers, allowing
+ * for flexible terminal creation based on the environment and requirements.
+ *
+ * Terminal Attributes
+ * The {@link org.jline.terminal.Attributes} class encapsulates terminal settings such as:
+ *
+ * Input flags (e.g., character echoing, canonical mode)
+ * Output flags (e.g., output processing)
+ * Control flags (e.g., baud rate, character size)
+ * Local flags (e.g., echo, canonical processing)
+ * Control characters (e.g., EOF, interrupt, erase)
+ *
+ *
+ * Supporting Classes
+ *
+ * {@link org.jline.terminal.Size} - Represents the dimensions of a terminal (rows and columns)
+ * {@link org.jline.terminal.Cursor} - Represents a cursor position within the terminal
+ * {@link org.jline.terminal.MouseEvent} - Encapsulates mouse events (clicks, movements) in the terminal
+ *
+ *
+ * Usage Example
+ *
+ * // Create a terminal
+ * Terminal terminal = TerminalBuilder.builder()
+ * .system(true)
+ * .build();
+ *
+ * // Get terminal size
+ * Size size = terminal.getSize();
+ * System.out.println("Terminal size: " + size.getColumns() + "x" + size.getRows());
+ *
+ * // Write to the terminal
+ * terminal.writer().println("Hello, JLine Terminal!");
+ * terminal.flush();
+ *
+ * // Read from the terminal
+ * int c = terminal.reader().read();
+ *
+ * // Close the terminal when done
+ * terminal.close();
+ *
+ *
+ * Terminal Implementations
+ * The actual terminal implementations are provided in the {@code org.jline.terminal.impl} package,
+ * with platform-specific implementations available through various provider modules like:
+ *
+ * jline-terminal-ffm - Foreign Function & Memory (Java 22+) based implementation
+ * jline-terminal-jni - JNI-based implementation
+ * jline-terminal-jansi - JANSI-based implementation
+ *
+ *
+ * The Service Provider Interface (SPI) for terminal implementations is defined in the
+ * {@code org.jline.terminal.spi} package.
+ *
+ * @since 3.0
+ */
+package jdk.internal.org.jline.terminal;
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/Pty.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/Pty.java
index d72e0e669d102..d0bc15ee0f665 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/Pty.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/Pty.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -16,25 +16,165 @@
import jdk.internal.org.jline.terminal.Attributes;
import jdk.internal.org.jline.terminal.Size;
+/**
+ * Represents a pseudoterminal (PTY) that provides terminal emulation.
+ *
+ *
+ * A pseudoterminal (PTY) is a pair of virtual character devices that provide a bidirectional
+ * communication channel. The PTY consists of a master side and a slave side. The slave side
+ * appears as a terminal device to processes, while the master side is used by terminal emulators
+ * to control the slave side.
+ *
+ *
+ *
+ * This interface provides methods to access the input and output streams for both the master
+ * and slave sides of the PTY, as well as methods to get and set terminal attributes and size.
+ *
+ *
+ *
+ * PTY implementations are typically platform-specific and may use different underlying
+ * mechanisms depending on the operating system (e.g., /dev/pts on Unix-like systems).
+ *
+ *
+ * @see java.io.Closeable
+ */
public interface Pty extends Closeable {
+ /**
+ * Returns the input stream for the master side of the PTY.
+ *
+ *
+ * This stream receives data that has been written to the slave's output stream.
+ * Terminal emulators typically read from this stream to get the output from
+ * processes running in the terminal.
+ *
+ *
+ * @return the master's input stream
+ * @throws IOException if an I/O error occurs
+ */
InputStream getMasterInput() throws IOException;
+ /**
+ * Returns the output stream for the master side of the PTY.
+ *
+ *
+ * Data written to this stream will be available for reading from the slave's
+ * input stream. Terminal emulators typically write to this stream to send input
+ * to processes running in the terminal.
+ *
+ *
+ * @return the master's output stream
+ * @throws IOException if an I/O error occurs
+ */
OutputStream getMasterOutput() throws IOException;
+ /**
+ * Returns the input stream for the slave side of the PTY.
+ *
+ *
+ * This stream receives data that has been written to the master's output stream.
+ * Processes running in the terminal read from this stream to get their input.
+ *
+ *
+ * @return the slave's input stream
+ * @throws IOException if an I/O error occurs
+ */
InputStream getSlaveInput() throws IOException;
+ /**
+ * Returns the output stream for the slave side of the PTY.
+ *
+ *
+ * Data written to this stream will be available for reading from the master's
+ * input stream. Processes running in the terminal write to this stream to
+ * produce their output.
+ *
+ *
+ * @return the slave's output stream
+ * @throws IOException if an I/O error occurs
+ */
OutputStream getSlaveOutput() throws IOException;
+ /**
+ * Returns the current terminal attributes for this PTY.
+ *
+ *
+ * Terminal attributes control various aspects of terminal behavior, such as
+ * echo settings, line discipline, and control characters.
+ *
+ *
+ * @return the current terminal attributes
+ * @throws IOException if an I/O error occurs
+ * @see org.jline.terminal.Attributes
+ */
Attributes getAttr() throws IOException;
+ /**
+ * Sets the terminal attributes for this PTY.
+ *
+ *
+ * This method allows changing various aspects of terminal behavior, such as
+ * echo settings, line discipline, and control characters.
+ *
+ *
+ * @param attr the terminal attributes to set
+ * @throws IOException if an I/O error occurs
+ * @see org.jline.terminal.Attributes
+ */
void setAttr(Attributes attr) throws IOException;
+ /**
+ * Returns the current size (dimensions) of this PTY.
+ *
+ *
+ * The size includes the number of rows and columns in the terminal window.
+ *
+ *
+ * @return the current terminal size
+ * @throws IOException if an I/O error occurs
+ * @see org.jline.terminal.Size
+ */
Size getSize() throws IOException;
+ /**
+ * Sets the size (dimensions) of this PTY.
+ *
+ *
+ * This method changes the number of rows and columns in the terminal window.
+ * When the size changes, a SIGWINCH signal is typically sent to processes
+ * running in the terminal.
+ *
+ *
+ * @param size the new terminal size to set
+ * @throws IOException if an I/O error occurs
+ * @see org.jline.terminal.Size
+ */
void setSize(Size size) throws IOException;
+ /**
+ * Returns the system stream associated with this PTY, if any.
+ *
+ *
+ * The system stream indicates whether this PTY is connected to standard input,
+ * standard output, or standard error.
+ *
+ *
+ * @return the associated system stream, or {@code null} if this PTY is not
+ * associated with a system stream
+ * @see SystemStream
+ */
SystemStream getSystemStream();
+ /**
+ * Returns the terminal provider that created this PTY.
+ *
+ *
+ * The terminal provider is responsible for creating and managing terminal
+ * instances on a specific platform.
+ *
+ *
+ * @return the terminal provider that created this PTY
+ * @see TerminalProvider
+ */
TerminalProvider getProvider();
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/SystemStream.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/SystemStream.java
index 5f95024829556..08709a075e985 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/SystemStream.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/SystemStream.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -8,8 +8,53 @@
*/
package jdk.internal.org.jline.terminal.spi;
+/**
+ * Represents the standard system streams available in a terminal environment.
+ *
+ *
+ * This enum defines the three standard streams that are typically available
+ * in a terminal environment: standard input, standard output, and standard error.
+ * These streams are used for communication between the terminal and the processes
+ * running within it.
+ *
+ *
+ *
+ * Terminal implementations and PTY objects may be associated with one of these
+ * system streams to indicate their role in the terminal environment.
+ *
+ *
+ * @see org.jline.terminal.spi.Pty#getSystemStream()
+ * @see org.jline.terminal.spi.TerminalExt#getSystemStream()
+ */
public enum SystemStream {
+ /**
+ * Standard input stream (stdin).
+ *
+ *
+ * This stream is used to provide input to processes running in the terminal.
+ * It typically represents keyboard input from the user.
+ *
+ */
Input,
+
+ /**
+ * Standard output stream (stdout).
+ *
+ *
+ * This stream is used by processes to output normal data. It typically
+ * represents the main display output of the terminal.
+ *
+ */
Output,
+
+ /**
+ * Standard error stream (stderr).
+ *
+ *
+ * This stream is used by processes to output error messages and diagnostic
+ * information. It may be displayed differently from standard output or
+ * redirected to a separate destination.
+ *
+ */
Error
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/TerminalExt.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/TerminalExt.java
index 00cfb2e9d475e..6c060bca05df5 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/TerminalExt.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/TerminalExt.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -11,21 +11,63 @@
import jdk.internal.org.jline.terminal.Terminal;
/**
- * The {@code TerminalExt} interface is implemented by {@code Terminal}s
- * and provides access to the Terminal's internals.
+ * Extended Terminal interface that provides access to internal implementation details.
+ *
+ *
+ * The {@code TerminalExt} interface extends the standard {@link Terminal} interface
+ * with additional methods that provide access to the terminal's internal implementation
+ * details. These methods are primarily used by terminal providers and other internal
+ * components of the JLine library.
+ *
+ *
+ *
+ * Terminal implementations typically implement this interface to expose information
+ * about their creation and configuration, such as the provider that created them
+ * and the system stream they are associated with.
+ *
+ *
+ *
+ * Application code should generally use the standard {@link Terminal} interface
+ * rather than this extended interface, unless specific access to these internal
+ * details is required.
+ *
+ *
+ * @see Terminal
+ * @see TerminalProvider
+ * @see SystemStream
*/
public interface TerminalExt extends Terminal {
/**
- * Returns the {@code TerminalProvider} that created this terminal
- * or {@code null} if the terminal was created with no provider.
+ * Returns the terminal provider that created this terminal.
+ *
+ *
+ * The terminal provider is responsible for creating and managing terminal
+ * instances on a specific platform. This method allows access to the provider
+ * that created this terminal, which can be useful for accessing provider-specific
+ * functionality or for creating additional terminals with the same provider.
+ *
+ *
+ * @return the {@code TerminalProvider} that created this terminal,
+ * or {@code null} if the terminal was created with no provider
+ * @see TerminalProvider
*/
TerminalProvider getProvider();
/**
- * The underlying system stream, may be {@link SystemStream#Output},
- * {@link SystemStream#Error}, or {@code null} if this terminal is not bound
- * to a system stream.
+ * Returns the system stream associated with this terminal, if any.
+ *
+ *
+ * This method indicates whether the terminal is bound to a standard system stream
+ * (standard input, standard output, or standard error). Terminals that are connected
+ * to system streams typically represent the actual terminal window or console that
+ * the application is running in.
+ *
+ *
+ * @return the underlying system stream, which may be {@link SystemStream#Input},
+ * {@link SystemStream#Output}, {@link SystemStream#Error}, or {@code null}
+ * if this terminal is not bound to a system stream
+ * @see SystemStream
*/
SystemStream getSystemStream();
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/TerminalProvider.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/TerminalProvider.java
index 055bf224fc516..c717530e3c35d 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/TerminalProvider.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/TerminalProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -8,11 +8,13 @@
*/
package jdk.internal.org.jline.terminal.spi;
+import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.Charset;
-import java.util.Properties;
+import java.nio.charset.StandardCharsets;
import java.util.function.Function;
import jdk.internal.org.jline.terminal.Attributes;
@@ -21,15 +23,81 @@
import jdk.internal.org.jline.terminal.impl.exec.ExecTerminalProvider;
import jdk.internal.org.jline.terminal.impl.ffm.FfmTerminalProvider;
+/**
+ * Service provider interface for terminal implementations.
+ *
+ *
+ * The TerminalProvider interface defines the contract for classes that can create
+ * and manage terminal instances on specific platforms. Each provider implements
+ * platform-specific terminal functionality, allowing JLine to work across different
+ * operating systems and environments.
+ *
+ *
+ *
+ * JLine includes several built-in terminal providers:
+ *
+ *
+ * FFM - Foreign Function Memory (Java 22+) based implementation
+ * JNI - Java Native Interface based implementation
+ * Jansi - Implementation based on the Jansi library
+ * JNA - Java Native Access based implementation
+ * Exec - Implementation using external commands
+ * Dumb - Fallback implementation with limited capabilities
+ *
+ *
+ *
+ * Terminal providers are loaded dynamically using the {@link #load(String)} method,
+ * which looks up provider implementations in the classpath based on their name.
+ *
+ *
+ * @see Terminal
+ * @see org.jline.terminal.TerminalBuilder
+ */
public interface TerminalProvider {
+ /**
+ * Returns the name of this terminal provider.
+ *
+ *
+ * The provider name is a unique identifier that can be used to request this
+ * specific provider when creating terminals. Common provider names include
+ * "ffm", "jni", "jansi", "exec", and "dumb".
+ *
+ *
+ * @return the name of this terminal provider
+ */
String name();
+ /**
+ * Creates a terminal connected to a system stream.
+ *
+ *
+ * This method creates a terminal that is connected to one of the standard
+ * system streams (standard input, standard output, or standard error). Such
+ * terminals typically represent the actual terminal window or console that
+ * the application is running in.
+ *
+ *
+ * @param name the name of the terminal
+ * @param type the terminal type (e.g., "xterm", "dumb")
+ * @param ansiPassThrough whether to pass through ANSI escape sequences
+ * @param encoding the general character encoding to use
+ * @param inputEncoding the character encoding to use for input
+ * @param outputEncoding the character encoding to use for output
+ * @param nativeSignals whether to use native signal handling
+ * @param signalHandler the signal handler to use
+ * @param paused whether the terminal should start in a paused state
+ * @param systemStream the system stream to connect to
+ * @return a new terminal connected to the specified system stream
+ * @throws IOException if an I/O error occurs
+ */
Terminal sysTerminal(
String name,
String type,
boolean ansiPassThrough,
Charset encoding,
+ Charset inputEncoding,
+ Charset outputEncoding,
boolean nativeSignals,
Terminal.SignalHandler signalHandler,
boolean paused,
@@ -37,24 +105,154 @@ Terminal sysTerminal(
Function inputStreamWrapper)
throws IOException;
+ /**
+ * Creates a new terminal with custom input and output streams.
+ *
+ *
+ * This method creates a terminal that is connected to the specified input and
+ * output streams. Such terminals can be used for various purposes, such as
+ * connecting to remote terminals over network connections or creating virtual
+ * terminals for testing.
+ *
+ *
+ * @param name the name of the terminal
+ * @param type the terminal type (e.g., "xterm", "dumb")
+ * @param masterInput the input stream to read from
+ * @param masterOutput the output stream to write to
+ * @param encoding the general character encoding to use
+ * @param inputEncoding the character encoding to use for input
+ * @param outputEncoding the character encoding to use for output
+ * @param signalHandler the signal handler to use
+ * @param paused whether the terminal should start in a paused state
+ * @param attributes the initial terminal attributes
+ * @param size the initial terminal size
+ * @return a new terminal connected to the specified streams
+ * @throws IOException if an I/O error occurs
+ */
Terminal newTerminal(
String name,
String type,
InputStream masterInput,
OutputStream masterOutput,
Charset encoding,
+ Charset inputEncoding,
+ Charset outputEncoding,
Terminal.SignalHandler signalHandler,
boolean paused,
Attributes attributes,
Size size)
throws IOException;
+ /**
+ * Checks if the specified system stream is available on this platform.
+ *
+ *
+ * This method determines whether the specified system stream (standard input,
+ * standard output, or standard error) is available for use on the current
+ * platform. Some platforms or environments may restrict access to certain
+ * system streams.
+ *
+ *
+ * @param stream the system stream to check
+ * @return {@code true} if the system stream is available, {@code false} otherwise
+ */
boolean isSystemStream(SystemStream stream);
+ /**
+ * Returns the name of the specified system stream on this platform.
+ *
+ *
+ * This method returns a platform-specific name or identifier for the specified
+ * system stream. The name may be used for display purposes or for accessing
+ * the stream through platform-specific APIs.
+ *
+ *
+ * @param stream the system stream
+ * @return the name of the system stream on this platform
+ */
String systemStreamName(SystemStream stream);
+ /**
+ * Returns the width (number of columns) of the specified system stream.
+ *
+ *
+ * This method determines the width of the terminal associated with the specified
+ * system stream. The width is measured in character cells and represents the
+ * number of columns available for display.
+ *
+ *
+ * @param stream the system stream
+ * @return the width of the system stream in character columns
+ */
int systemStreamWidth(SystemStream stream);
+ /**
+ * Returns the Windows console output codepage.
+ *
+ *
+ * On Windows, this method returns the console output codepage (equivalent to
+ * {@code GetConsoleOutputCP()}). On non-Windows platforms, or if the codepage
+ * cannot be determined, this method returns {@code -1}.
+ *
+ *
+ * @return the console output codepage, or {@code -1} if not available
+ */
+ default int getConsoleCodepage() {
+ return -1;
+ }
+
+ /**
+ * Loads a terminal provider with the specified name.
+ *
+ * Provider Discovery Mechanism
+ *
+ * This method loads a terminal provider implementation based on its name by reading
+ * a provider-specific resource file at {@code META-INF/jline/providers/[name]} which
+ * contains the fully qualified class name of the provider implementation.
+ *
+ *
+ *
+ * This on-demand loading approach is used instead of {@link java.util.ServiceLoader}
+ * because it allows loading a specific provider by name without instantiating all
+ * available providers. This is critical for providers that may fail to initialize
+ * due to missing native libraries (JNI, FFM) or other platform-specific dependencies.
+ *
+ *
+ * Dual-Purpose Service Files
+ *
+ * JLine maintains two types of service registration files:
+ *
+ *
+ * {@code META-INF/services/org.jline.terminal.spi.TerminalProvider} -
+ * Standard Java SPI files required by jlink and JPMS module tools to discover
+ * service implementations and establish proper module dependencies. These files
+ * are not used at runtime by JLine.
+ * {@code META-INF/jline/providers/[name]} -
+ * Provider-specific files used by this method for efficient runtime loading.
+ * Each file contains the class name of a single provider and allows loading
+ * by provider name without scanning all available providers.
+ *
+ *
+ * File Format
+ *
+ * The provider file format follows standard Java SPI conventions:
+ *
+ *
+ * One fully qualified class name per line
+ * Comments start with {@code #} and extend to end of line
+ * Blank lines and whitespace are ignored
+ *
+ *
+ * Example: {@code META-INF/jline/providers/ffm}
+ *
+ * # JLine FFM Terminal Provider
+ * org.jline.terminal.impl.ffm.FfmTerminalProvider
+ *
+ *
+ * @param name the name of the provider to load (e.g., "ffm", "jni", "exec", "dumb")
+ * @return the loaded terminal provider
+ * @throws IOException if the provider cannot be loaded or is not found
+ */
static TerminalProvider load(String name) throws IOException {
switch (name) {
case "exec": return new ExecTerminalProvider();
@@ -62,24 +260,44 @@ static TerminalProvider load(String name) throws IOException {
}
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null) {
- cl = ClassLoader.getSystemClassLoader();
+ cl = TerminalProvider.class.getClassLoader();
}
- InputStream is = cl.getResourceAsStream("META-INF/services/org/jline/terminal/provider/" + name);
- if (is != null) {
- Properties props = new Properties();
- try {
- props.load(is);
- String className = props.getProperty("class");
- if (className == null) {
- throw new IOException("No class defined in terminal provider file " + name);
+
+ // Read the provider-specific resource file to get the class name.
+ // We use META-INF/jline/providers/{name} instead of ServiceLoader to avoid
+ // instantiating all providers when we only need one. This is critical because
+ // some providers (JNI, FFM) may fail to load due to missing native libraries.
+ String providerResource = "META-INF/jline/providers/" + name;
+ try (InputStream is = cl.getResourceAsStream(providerResource)) {
+ if (is != null) {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ // Remove comments and trim whitespace
+ int commentIndex = line.indexOf('#');
+ if (commentIndex >= 0) {
+ line = line.substring(0, commentIndex);
+ }
+ line = line.trim();
+
+ // Skip empty lines
+ if (line.isEmpty()) {
+ continue;
+ }
+
+ // Found a provider class name, try to load it
+ try {
+ Class> providerClass = cl.loadClass(line);
+ return (TerminalProvider) providerClass.getConstructor().newInstance();
+ } catch (Exception | LinkageError e) {
+ throw new IOException("Unable to load terminal provider " + name + ": " + e.getMessage(), e);
+ }
}
- Class> clazz = cl.loadClass(className);
- return (TerminalProvider) clazz.getConstructor().newInstance();
- } catch (Exception e) {
- throw new IOException("Unable to load terminal provider " + name + ": " + e.getMessage(), e);
}
- } else {
- throw new IOException("Unable to find terminal provider " + name);
+ } catch (IOException e) {
+ throw new IOException("Error reading provider resource file: " + e.getMessage(), e);
}
+
+ throw new IOException("Unable to find terminal provider " + name);
}
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/package-info.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/package-info.java
new file mode 100644
index 0000000000000..ee750d1d3203b
--- /dev/null
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2002-2025, 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.
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+/**
+ * JLine Terminal Service Provider Interface (SPI).
+ *
+ * This package contains interfaces that define the Service Provider Interface (SPI)
+ * for terminal implementations. These interfaces allow for pluggable terminal
+ * implementations across different platforms and environments.
+ *
+ * @since 3.0
+ */
+package jdk.internal.org.jline.terminal.spi;
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/AnsiWriter.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/AnsiWriter.java
index 32d8282d4c0b7..34d0af19aad7b 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/AnsiWriter.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/AnsiWriter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -15,19 +15,43 @@
import java.util.Iterator;
/**
- * A ANSI writer extracts ANSI escape codes written to
- * a {@link Writer} and calls corresponding process* methods.
+ * A writer that processes ANSI escape sequences.
*
- * For more information about ANSI escape codes, see:
- * http://en.wikipedia.org/wiki/ANSI_escape_code
+ *
+ * The AnsiWriter class extends FilterWriter to intercept and process ANSI escape
+ * sequences written to the underlying writer. It extracts ANSI escape codes and
+ * calls corresponding process* methods for each recognized sequence.
+ *
+ *
+ *
+ * This class just filters out the escape codes so that they are not sent out to
+ * the underlying {@link Writer}: process* methods are empty. Subclasses
+ * should actually perform the ANSI escape behaviors by implementing active code
+ * in process* methods.
+ *
+ *
+ *
+ * This class is useful for implementing terminal emulation, where ANSI escape
+ * sequences need to be interpreted to control cursor movement, text attributes,
+ * colors, and other terminal features.
+ *
*
- * This class just filters out the escape codes so that they are not
- * sent out to the underlying {@link Writer}: process* methods
- * are empty. Subclasses should actually perform the ANSI escape behaviors
- * by implementing active code in process* methods.
+ *
+ * The class handles various ANSI escape sequences, including:
+ *
+ *
+ * Cursor movement (up, down, left, right)
+ * Cursor positioning (absolute and relative)
+ * Text attributes (bold, underline, blink, etc.)
+ * Colors (foreground and background)
+ * Screen clearing and line manipulation
+ *
+ *
+ *
+ * For more information about ANSI escape codes, see:
+ * Wikipedia: ANSI escape code
+ *
*
- * @author Hiram Chirino
- * @author Joris Kuipers
* @since 1.0
*/
public class AnsiWriter extends FilterWriter {
@@ -506,9 +530,6 @@ protected void processEraseLine(int eraseOption) throws IOException {}
protected static final int ATTRIBUTE_UNDERLINE_OFF = 24; // Underline; None
protected static final int ATTRIBUTE_BLINK_OFF = 25; // Blink; off
- @Deprecated
- protected static final int ATTRIBUTE_NEGATIVE_Off = 27; // Image; Positive
-
protected static final int ATTRIBUTE_NEGATIVE_OFF = 27; // Image; Positive
protected static final int ATTRIBUTE_CONCEAL_OFF = 28; // Reveal conceal off
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/AttributedCharSequence.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/AttributedCharSequence.java
index 6386f5bba3901..3d1663906fe37 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/AttributedCharSequence.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/AttributedCharSequence.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2021, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -8,11 +8,11 @@
*/
package jdk.internal.org.jline.utils;
+import java.text.BreakIterator;
import java.util.ArrayList;
import java.util.List;
import jdk.internal.org.jline.terminal.Terminal;
-import jdk.internal.org.jline.terminal.impl.AbstractWindowsTerminal;
import jdk.internal.org.jline.utils.InfoCmp.Capability;
import static jdk.internal.org.jline.terminal.TerminalBuilder.PROP_DISABLE_ALTERNATE_CHARSET;
@@ -37,32 +37,138 @@
import static jdk.internal.org.jline.utils.AttributedStyle.F_UNDERLINE;
import static jdk.internal.org.jline.utils.AttributedStyle.MASK;
+/**
+ * A character sequence with ANSI style attributes.
+ *
+ *
+ * The AttributedCharSequence class is an abstract base class for character sequences
+ * that have ANSI style attributes (colors, bold, underline, etc.) associated with
+ * each character. It provides methods for rendering the character sequence with its
+ * attributes to various outputs, such as ANSI terminals, non-ANSI terminals, and
+ * plain text.
+ *
+ *
+ *
+ * This class serves as the foundation for styled text in JLine, allowing for rich
+ * text formatting in terminal applications. It is extended by concrete classes like
+ * {@link AttributedString} and {@link AttributedStringBuilder} that provide specific
+ * implementations for different use cases.
+ *
+ *
+ *
+ * The class provides methods to:
+ *
+ *
+ * Convert the sequence to a plain string without attributes
+ * Render the sequence with ANSI escape codes for compatible terminals
+ * Render the sequence for terminals with limited attribute support
+ * Calculate the visible length of the sequence (excluding escape codes)
+ * Extract substrings while preserving attributes
+ *
+ */
public abstract class AttributedCharSequence implements CharSequence {
+ /**
+ * Default constructor.
+ */
+ public AttributedCharSequence() {
+ // Default constructor
+ }
+
public static final int TRUE_COLORS = 0x1000000;
private static final int HIGH_COLORS = 0x7FFF;
+ /**
+ * Enum defining color mode forcing options for ANSI rendering.
+ *
+ *
+ * This enum specifies how color rendering should be forced when generating
+ * ANSI escape sequences, regardless of the terminal's reported capabilities.
+ *
+ */
public enum ForceMode {
+ /**
+ * No forcing; use the terminal's reported color capabilities.
+ */
None,
+
+ /**
+ * Force the use of 256-color mode (8-bit colors).
+ */
Force256Colors,
+
+ /**
+ * Force the use of true color mode (24-bit RGB colors).
+ */
ForceTrueColors
}
// cache the value here as we can't afford to get it each time
static final boolean DISABLE_ALTERNATE_CHARSET = Boolean.getBoolean(PROP_DISABLE_ALTERNATE_CHARSET);
+ /**
+ * Prints this attributed string to the specified terminal.
+ *
+ *
+ * This method renders the attributed string with appropriate ANSI escape
+ * sequences for the specified terminal and prints it to the terminal's writer.
+ *
+ *
+ * @param terminal the terminal to print to
+ */
public void print(Terminal terminal) {
terminal.writer().print(toAnsi(terminal));
}
+ /**
+ * Prints this attributed string to the specified terminal, followed by a line break.
+ *
+ *
+ * This method renders the attributed string with appropriate ANSI escape
+ * sequences for the specified terminal and prints it to the terminal's writer,
+ * followed by a line break.
+ *
+ *
+ * @param terminal the terminal to print to
+ */
public void println(Terminal terminal) {
terminal.writer().println(toAnsi(terminal));
}
+ /**
+ * Converts this attributed string to an ANSI escape sequence string.
+ *
+ *
+ * This method renders the attributed string with ANSI escape sequences
+ * to represent the text attributes (colors, bold, underline, etc.).
+ * It uses default color capabilities (256 colors) and no forced color mode.
+ *
+ *
+ * @return a string with ANSI escape sequences representing this attributed string
+ * @see #toAnsi(Terminal)
+ */
public String toAnsi() {
return toAnsi(null);
}
+ /**
+ * Converts this attributed string to an ANSI escape sequence string
+ * appropriate for the specified terminal.
+ *
+ *
+ * This method renders the attributed string with ANSI escape sequences
+ * to represent the text attributes (colors, bold, underline, etc.),
+ * taking into account the capabilities of the specified terminal.
+ *
+ *
+ *
+ * If the terminal is a dumb terminal (Terminal.TYPE_DUMB), this method
+ * returns the plain text without any escape sequences.
+ *
+ *
+ * @param terminal the terminal to generate ANSI sequences for, or null to use default capabilities
+ * @return a string with ANSI escape sequences representing this attributed string
+ */
public String toAnsi(Terminal terminal) {
if (terminal != null && Terminal.TYPE_DUMB.equals(terminal.getType())) {
return toString();
@@ -76,10 +182,6 @@ public String toAnsi(Terminal terminal) {
if (max_colors != null) {
colors = max_colors;
}
- if (AbstractWindowsTerminal.TYPE_WINDOWS_256_COLOR.equals(terminal.getType())
- || AbstractWindowsTerminal.TYPE_WINDOWS_CONEMU.equals(terminal.getType())) {
- forceMode = ForceMode.Force256Colors;
- }
palette = terminal.getPalette();
if (!DISABLE_ALTERNATE_CHARSET) {
alternateIn = Curses.tputs(terminal.getStringCapability(Capability.enter_alt_charset_mode));
@@ -89,24 +191,59 @@ public String toAnsi(Terminal terminal) {
return toAnsi(colors, forceMode, palette, alternateIn, alternateOut);
}
- @Deprecated
- public String toAnsi(int colors, boolean force256colors) {
- return toAnsi(colors, force256colors, null, null);
- }
-
- @Deprecated
- public String toAnsi(int colors, boolean force256colors, String altIn, String altOut) {
- return toAnsi(colors, force256colors ? ForceMode.Force256Colors : ForceMode.None, null, altIn, altOut);
- }
-
+ /**
+ * Converts this attributed string to an ANSI escape sequence string
+ * with the specified color capabilities and force mode.
+ *
+ *
+ * This method renders the attributed string with ANSI escape sequences
+ * using the specified number of colors and force mode.
+ *
+ *
+ * @param colors the number of colors to use (8, 256, or 16777216 for true colors)
+ * @param force the force mode to use for color rendering
+ * @return a string with ANSI escape sequences representing this attributed string
+ */
public String toAnsi(int colors, ForceMode force) {
return toAnsi(colors, force, null, null, null);
}
+ /**
+ * Converts this attributed string to an ANSI escape sequence string
+ * with the specified color capabilities, force mode, and color palette.
+ *
+ *
+ * This method renders the attributed string with ANSI escape sequences
+ * using the specified number of colors, force mode, and color palette.
+ *
+ *
+ * @param colors the number of colors to use (8, 256, or 16777216 for true colors)
+ * @param force the force mode to use for color rendering
+ * @param palette the color palette to use for color conversion
+ * @return a string with ANSI escape sequences representing this attributed string
+ */
public String toAnsi(int colors, ForceMode force, ColorPalette palette) {
return toAnsi(colors, force, palette, null, null);
}
+ /**
+ * Converts this attributed string to an ANSI escape sequence string
+ * with the specified color capabilities, force mode, color palette,
+ * and alternate character set sequences.
+ *
+ *
+ * This method renders the attributed string with ANSI escape sequences
+ * using the specified number of colors, force mode, color palette, and
+ * alternate character set sequences for box drawing characters.
+ *
+ *
+ * @param colors the number of colors to use (8, 256, or 16777216 for true colors)
+ * @param force the force mode to use for color rendering
+ * @param palette the color palette to use for color conversion, or null for the default palette
+ * @param altIn the sequence to enable the alternate character set, or null to disable
+ * @param altOut the sequence to disable the alternate character set, or null to disable
+ * @return a string with ANSI escape sequences representing this attributed string
+ */
public String toAnsi(int colors, ForceMode force, ColorPalette palette, String altIn, String altOut) {
StringBuilder sb = new StringBuilder();
long style = 0;
@@ -140,6 +277,17 @@ public String toAnsi(int colors, ForceMode force, ColorPalette palette, String a
if (oldalt ^ alt) {
sb.append(alt ? altIn : altOut);
}
+ } else {
+ // Fallback to ASCII when alternate charset mode is not supported
+ // @spotless:off
+ switch (c) {
+ case 'โ': case 'โ': case 'โ': case 'โ': c = '+'; break;
+ case 'โผ': c = '+'; break;
+ case 'โ': c = '-'; break;
+ case 'โ': case 'โค': case 'โด': case 'โฌ': c = '+'; break;
+ case 'โ': c = '|'; break;
+ }
+ // @spotless:on
}
long s = styleCodeAt(i) & ~F_HIDDEN; // The hidden flag does not change the ansi styles
if (style != s) {
@@ -270,21 +418,6 @@ public String toAnsi(int colors, ForceMode force, ColorPalette palette, String a
return sb.toString();
}
- @Deprecated
- public static int rgbColor(int col) {
- return Colors.rgbColor(col);
- }
-
- @Deprecated
- public static int roundColor(int col, int max) {
- return Colors.roundColor(col, max);
- }
-
- @Deprecated
- public static int roundRgbColor(int r, int g, int b, int max) {
- return Colors.roundRgbColor(r, g, b, max);
- }
-
private static boolean attr(StringBuilder sb, String s, boolean first) {
if (!first) {
sb.append(";");
@@ -293,16 +426,66 @@ private static boolean attr(StringBuilder sb, String s, boolean first) {
return false;
}
+ /**
+ * Returns the style at the specified index in this attributed string.
+ *
+ *
+ * This method returns the AttributedStyle object associated with the
+ * character at the specified index in this attributed string.
+ *
+ *
+ * @param index the index of the character whose style to return
+ * @return the style at the specified index
+ * @throws IndexOutOfBoundsException if the index is out of range
+ */
public abstract AttributedStyle styleAt(int index);
+ /**
+ * Returns the style code at the specified index in this attributed string.
+ *
+ *
+ * This method returns the raw style code (as a long value) associated with the
+ * character at the specified index in this attributed string.
+ *
+ *
+ * @param index the index of the character whose style code to return
+ * @return the style code at the specified index
+ * @throws IndexOutOfBoundsException if the index is out of range
+ */
long styleCodeAt(int index) {
return styleAt(index).getStyle();
}
+ /**
+ * Returns whether the character at the specified index is hidden.
+ *
+ *
+ * This method checks if the character at the specified index has the
+ * hidden attribute set, which means it should not be displayed.
+ *
+ *
+ * @param index the index of the character to check
+ * @return true if the character is hidden, false otherwise
+ * @throws IndexOutOfBoundsException if the index is out of range
+ */
public boolean isHidden(int index) {
return (styleCodeAt(index) & F_HIDDEN) != 0;
}
+ /**
+ * Returns the start index of the run of characters with the same style
+ * that includes the character at the specified index.
+ *
+ *
+ * A run is a sequence of consecutive characters that have the same style.
+ * This method finds the first character in the run that includes the
+ * character at the specified index.
+ *
+ *
+ * @param index the index of a character in the run
+ * @return the start index of the run (inclusive)
+ * @throws IndexOutOfBoundsException if the index is out of range
+ */
public int runStart(int index) {
AttributedStyle style = styleAt(index);
while (index > 0 && styleAt(index - 1).equals(style)) {
@@ -311,6 +494,20 @@ public int runStart(int index) {
return index;
}
+ /**
+ * Returns the limit index of the run of characters with the same style
+ * that includes the character at the specified index.
+ *
+ *
+ * A run is a sequence of consecutive characters that have the same style.
+ * This method finds the index after the last character in the run that
+ * includes the character at the specified index.
+ *
+ *
+ * @param index the index of a character in the run
+ * @return the limit index of the run (exclusive)
+ * @throws IndexOutOfBoundsException if the index is out of range
+ */
public int runLimit(int index) {
AttributedStyle style = styleAt(index);
while (index < length() - 1 && styleAt(index + 1).equals(style)) {
@@ -322,6 +519,26 @@ public int runLimit(int index) {
@Override
public abstract AttributedString subSequence(int start, int end);
+ /**
+ * Returns a new AttributedString that is a substring of this attributed string.
+ *
+ *
+ * This method returns a new AttributedString that contains the characters and
+ * styles from this attributed string starting at the specified start index
+ * (inclusive) and ending at the specified end index (exclusive).
+ *
+ *
+ *
+ * This method is equivalent to {@link #subSequence(int, int)} but returns
+ * an AttributedString instead of an AttributedCharSequence.
+ *
+ *
+ * @param start the start index, inclusive
+ * @param end the end index, exclusive
+ * @return the specified substring with its attributes
+ * @throws IndexOutOfBoundsException if start or end are negative,
+ * if end is greater than length(), or if start is greater than end
+ */
public AttributedString substring(int start, int end) {
return subSequence(start, end);
}
@@ -335,10 +552,35 @@ public char charAt(int index) {
return buffer()[offset() + index];
}
+ /**
+ * Returns the code point at the specified index in this attributed string.
+ *
+ *
+ * This method returns the Unicode code point at the specified index.
+ * If the character at the specified index is a high-surrogate code unit
+ * and the following character is a low-surrogate code unit, then the
+ * supplementary code point is returned; otherwise, the code unit at the
+ * specified index is returned.
+ *
+ *
+ * @param index the index to the code point
+ * @return the code point at the specified index
+ * @throws IndexOutOfBoundsException if the index is out of range
+ */
public int codePointAt(int index) {
return Character.codePointAt(buffer(), index + offset());
}
+ /**
+ * Returns whether this attributed string contains the specified character.
+ *
+ *
+ * This method checks if the specified character appears in this attributed string.
+ *
+ *
+ * @param c the character to search for
+ * @return true if this attributed string contains the specified character, false otherwise
+ */
public boolean contains(char c) {
for (int i = 0; i < length(); i++) {
if (charAt(i) == c) {
@@ -348,63 +590,210 @@ public boolean contains(char c) {
return false;
}
+ /**
+ * Returns the code point before the specified index in this attributed string.
+ *
+ *
+ * This method returns the Unicode code point before the specified index.
+ * If the character before the specified index is a low-surrogate code unit
+ * and the character before that is a high-surrogate code unit, then the
+ * supplementary code point is returned; otherwise, the code unit before the
+ * specified index is returned.
+ *
+ *
+ * @param index the index following the code point that should be returned
+ * @return the Unicode code point value before the specified index
+ * @throws IndexOutOfBoundsException if the index is less than 1 or greater than length()
+ */
public int codePointBefore(int index) {
return Character.codePointBefore(buffer(), index + offset());
}
+ /**
+ * Returns the number of Unicode code points in the specified range of this attributed string.
+ *
+ *
+ * This method counts the number of Unicode code points in the range of this
+ * attributed string starting at the specified index (inclusive) and extending
+ * for the specified length. A surrogate pair is counted as one code point.
+ *
+ *
+ * @param index the index to the first character of the range
+ * @param length the length of the range in characters
+ * @return the number of Unicode code points in the specified range
+ * @throws IndexOutOfBoundsException if index is negative, or length is negative,
+ * or index + length is greater than length()
+ */
public int codePointCount(int index, int length) {
return Character.codePointCount(buffer(), index + offset(), length);
}
+ /**
+ * Returns the display width of this attributed string in columns.
+ *
+ *
+ * This method calculates the display width of this attributed string in columns,
+ * taking into account wide characters (such as East Asian characters), zero-width
+ * characters (such as combining marks), and hidden characters. This is useful for
+ * determining how much space the string will occupy when displayed in a terminal.
+ *
+ *
+ *
+ * Hidden characters (those with the hidden attribute set) are not counted in the
+ * column length.
+ *
+ *
+ * @return the display width of this attributed string in columns
+ */
public int columnLength() {
+ return columnLength(null);
+ }
+
+ /**
+ * Returns the display width of this attributed string in columns.
+ *
+ * When the terminal has grapheme cluster mode enabled, multi-codepoint
+ * sequences (ZWJ emoji, flags, etc.) are measured as single display units
+ * matching the terminal's cursor positioning.
+ *
+ * @param terminal the terminal to query for grapheme cluster mode, or {@code null}
+ * @return the display width in columns
+ */
+ public int columnLength(Terminal terminal) {
int cols = 0;
int len = length();
+ BreakIterator bi = WCWidth.createGraphemeBreakIterator(this);
for (int cur = 0; cur < len; ) {
- int cp = codePointAt(cur);
- if (!isHidden(cur)) cols += WCWidth.wcwidth(cp);
- cur += Character.charCount(cp);
+ int charCount = WCWidth.charCountForDisplay(this, cur, terminal, bi);
+ int w = isHidden(cur) ? 0 : WCWidth.wcwidthForDisplay(this, cur, terminal, charCount);
+ cur += charCount;
+ cols += w;
}
return cols;
}
+ /**
+ * Returns a subsequence of this attributed string based on column positions.
+ *
+ *
+ * This method returns a subsequence of this attributed string that spans from
+ * the specified start column position (inclusive) to the specified stop column
+ * position (exclusive). Column positions are determined by the display width of
+ * characters, taking into account wide characters, zero-width characters, and
+ * hidden characters.
+ *
+ *
+ *
+ * This method is useful for extracting portions of text based on their visual
+ * position in a terminal, rather than their character indices.
+ *
+ *
+ * @param start the starting column position (inclusive)
+ * @param stop the ending column position (exclusive)
+ * @return the subsequence spanning the specified column range
+ */
public AttributedString columnSubSequence(int start, int stop) {
+ return columnSubSequence(start, stop, null);
+ }
+
+ /**
+ * Returns a subsequence of this attributed string based on column positions.
+ *
+ * @param start the starting column position (inclusive)
+ * @param stop the ending column position (exclusive)
+ * @param terminal the terminal to query for grapheme cluster mode, or {@code null}
+ * @return the subsequence spanning the specified column range
+ */
+ public AttributedString columnSubSequence(int start, int stop, Terminal terminal) {
+ BreakIterator bi = WCWidth.createGraphemeBreakIterator(this);
int begin = 0;
int col = 0;
while (begin < this.length()) {
- int cp = codePointAt(begin);
- int w = isHidden(begin) ? 0 : WCWidth.wcwidth(cp);
+ int charCount = WCWidth.charCountForDisplay(this, begin, terminal, bi);
+ int w = isHidden(begin) ? 0 : WCWidth.wcwidthForDisplay(this, begin, terminal, charCount);
if (col + w > start) {
break;
}
- begin += Character.charCount(cp);
+ begin += charCount;
col += w;
}
int end = begin;
while (end < this.length()) {
int cp = codePointAt(end);
if (cp == '\n') break;
- int w = isHidden(end) ? 0 : WCWidth.wcwidth(cp);
+ int charCount = WCWidth.charCountForDisplay(this, end, terminal, bi);
+ int w = isHidden(end) ? 0 : WCWidth.wcwidthForDisplay(this, end, terminal, charCount);
if (col + w > stop) {
break;
}
- end += Character.charCount(cp);
+ end += charCount;
col += w;
}
return subSequence(begin, end);
}
+ /**
+ * Splits this attributed string into multiple lines based on column width.
+ *
+ *
+ * This method splits this attributed string into multiple lines, each with a
+ * maximum width of the specified number of columns. The splitting is done based
+ * on the display width of characters, taking into account wide characters,
+ * zero-width characters, and hidden characters.
+ *
+ *
+ *
+ * This is equivalent to calling {@link #columnSplitLength(int, boolean, boolean)}
+ * with {@code includeNewlines=false} and {@code delayLineWrap=true}.
+ *
+ *
+ * @param columns the maximum width of each line in columns
+ * @return a list of attributed strings, each representing a line
+ */
public List columnSplitLength(int columns) {
return columnSplitLength(columns, false, true);
}
+ /**
+ * Splits this attributed string into multiple lines based on column width,
+ * with options for handling newlines and line wrapping.
+ *
+ *
+ * This method splits this attributed string into multiple lines, each with a
+ * maximum width of the specified number of columns. The splitting is done based
+ * on the display width of characters, taking into account wide characters,
+ * zero-width characters, and hidden characters.
+ *
+ *
+ * @param columns the maximum width of each line in columns
+ * @param includeNewlines whether to include newline characters in the resulting lines
+ * @param delayLineWrap whether to delay line wrapping until the last possible moment
+ * @return a list of attributed strings, each representing a line
+ */
public List columnSplitLength(int columns, boolean includeNewlines, boolean delayLineWrap) {
+ return columnSplitLength(columns, includeNewlines, delayLineWrap, (Terminal) null);
+ }
+
+ /**
+ * Splits this attributed string into multiple lines based on column width.
+ *
+ * @param columns the maximum width of each line in columns
+ * @param includeNewlines whether to include newline characters in the resulting lines
+ * @param delayLineWrap whether to delay line wrapping until the last possible moment
+ * @param terminal the terminal to query for grapheme cluster mode, or {@code null}
+ * @return a list of attributed strings, each representing a line
+ */
+ public List columnSplitLength(
+ int columns, boolean includeNewlines, boolean delayLineWrap, Terminal terminal) {
List strings = new ArrayList<>();
int cur = 0;
int beg = cur;
int col = 0;
+ BreakIterator bi = WCWidth.createGraphemeBreakIterator(this);
while (cur < length()) {
int cp = codePointAt(cur);
- int w = isHidden(cur) ? 0 : WCWidth.wcwidth(cp);
+ int charCount = WCWidth.charCountForDisplay(this, cur, terminal, bi);
+ int w = isHidden(cur) ? 0 : WCWidth.wcwidthForDisplay(this, cur, terminal, charCount);
if (cp == '\n') {
strings.add(subSequence(beg, includeNewlines ? cur + 1 : cur));
beg = cur + 1;
@@ -414,7 +803,7 @@ public List columnSplitLength(int columns, boolean includeNewl
beg = cur;
col = w;
}
- cur += Character.charCount(cp);
+ cur += charCount;
}
strings.add(subSequence(beg, cur));
return strings;
@@ -425,6 +814,17 @@ public String toString() {
return new String(buffer(), offset(), length());
}
+ /**
+ * Converts this attributed character sequence to an AttributedString.
+ *
+ *
+ * This method creates a new AttributedString that contains all the characters
+ * and styles from this attributed character sequence. This is useful for
+ * converting an AttributedCharSequence to an immutable AttributedString.
+ *
+ *
+ * @return a new AttributedString containing all characters and styles from this sequence
+ */
public AttributedString toAttributedString() {
return substring(0, length());
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/AttributedString.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/AttributedString.java
index 3c70a6d0e7bf4..beadbcb9de14f 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/AttributedString.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/AttributedString.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -18,11 +18,35 @@
import jdk.internal.org.jline.terminal.Terminal;
/**
- * Attributed string.
- * Instances of this class are immutables.
- * Substrings are created without any memory copy.
+ * An immutable character sequence with ANSI style attributes.
*
- * @author Guillaume Nodet
+ *
+ * The AttributedString class represents a character sequence where each character
+ * has associated style attributes (colors, bold, underline, etc.). It extends
+ * AttributedCharSequence and provides an immutable implementation, making it safe
+ * to use in concurrent contexts.
+ *
+ *
+ *
+ * Key features of this class include:
+ *
+ *
+ * Immutability - Once created, instances cannot be modified
+ * Memory efficiency - Substrings are created without any memory copy
+ * Rich styling - Support for foreground/background colors and text attributes
+ * Concatenation - Multiple AttributedStrings can be joined together
+ * Pattern matching - Regular expressions can be applied to find and extract parts
+ *
+ *
+ *
+ * This class is commonly used for displaying styled text in terminal applications,
+ * such as prompts, menus, and highlighted output. For building AttributedStrings
+ * dynamically, use {@link AttributedStringBuilder}.
+ *
+ *
+ * @see AttributedCharSequence
+ * @see AttributedStringBuilder
+ * @see AttributedStyle
*/
public class AttributedString extends AttributedCharSequence {
@@ -30,21 +54,84 @@ public class AttributedString extends AttributedCharSequence {
final long[] style;
final int start;
final int end;
+ /**
+ * An empty AttributedString with no characters.
+ */
public static final AttributedString EMPTY = new AttributedString("");
+
+ /**
+ * An AttributedString containing only a newline character.
+ */
public static final AttributedString NEWLINE = new AttributedString("\n");
+ /**
+ * Creates a new AttributedString from the specified character sequence.
+ *
+ *
+ * This constructor creates a new AttributedString containing the characters
+ * from the specified character sequence with default style (no attributes).
+ *
+ *
+ * @param str the character sequence to copy
+ */
public AttributedString(CharSequence str) {
this(str, 0, str.length(), null);
}
+ /**
+ * Creates a new AttributedString from a subsequence of the specified character sequence.
+ *
+ *
+ * This constructor creates a new AttributedString containing the characters
+ * from the specified range of the character sequence with default style (no attributes).
+ *
+ *
+ * @param str the character sequence to copy
+ * @param start the index of the first character to copy (inclusive)
+ * @param end the index after the last character to copy (exclusive)
+ * @throws InvalidParameterException if end is less than start
+ */
public AttributedString(CharSequence str, int start, int end) {
this(str, start, end, null);
}
+ /**
+ * Creates a new AttributedString from the specified character sequence with the specified style.
+ *
+ *
+ * This constructor creates a new AttributedString containing the characters
+ * from the specified character sequence with the specified style applied to all characters.
+ *
+ *
+ * @param str the character sequence to copy
+ * @param s the style to apply to all characters, or null for default style
+ */
public AttributedString(CharSequence str, AttributedStyle s) {
this(str, 0, str.length(), s);
}
+ /**
+ * Creates a new AttributedString from a subsequence of the specified character sequence
+ * with the specified style.
+ *
+ *
+ * This constructor creates a new AttributedString containing the characters
+ * from the specified range of the character sequence with the specified style
+ * applied to all characters.
+ *
+ *
+ *
+ * If the character sequence is an AttributedString or AttributedStringBuilder,
+ * this constructor preserves the existing style information and applies the
+ * specified style on top of it (if not null).
+ *
+ *
+ * @param str the character sequence to copy
+ * @param start the index of the first character to copy (inclusive)
+ * @param end the index after the last character to copy (exclusive)
+ * @param s the style to apply to all characters, or null to preserve existing styles
+ * @throws InvalidParameterException if end is less than start
+ */
public AttributedString(CharSequence str, int start, int end, AttributedStyle s) {
if (end < start) {
throw new InvalidParameterException();
@@ -89,6 +176,19 @@ public AttributedString(CharSequence str, int start, int end, AttributedStyle s)
}
}
+ /**
+ * Creates a new AttributedString with the specified buffer, style, and range.
+ *
+ *
+ * This constructor is package-private and used internally for creating
+ * AttributedString instances without copying the buffer and style arrays.
+ *
+ *
+ * @param buffer the character buffer
+ * @param style the style buffer
+ * @param start the start index in the buffers
+ * @param end the end index in the buffers
+ */
AttributedString(char[] buffer, long[] style, int start, int end) {
this.buffer = buffer;
this.style = style;
@@ -96,18 +196,71 @@ public AttributedString(CharSequence str, int start, int end, AttributedStyle s)
this.end = end;
}
+ /**
+ * Creates an AttributedString from an ANSI-encoded string.
+ *
+ *
+ * This method parses the ANSI escape sequences in the input string and
+ * creates an AttributedString with the corresponding styles. This is useful
+ * for converting ANSI-colored output from external commands into styled
+ * text that can be displayed in the terminal.
+ *
+ *
+ * @param ansi the ANSI-encoded string to parse
+ * @return an AttributedString with styles based on the ANSI escape sequences
+ * @see #fromAnsi(String, Terminal)
+ */
public static AttributedString fromAnsi(String ansi) {
return fromAnsi(ansi, 0);
}
+ /**
+ * Creates an AttributedString from an ANSI-encoded string with a specified tab size.
+ *
+ *
+ * This method parses the ANSI escape sequences in the input string and
+ * creates an AttributedString with the corresponding styles. It also handles
+ * tab characters according to the specified tab size.
+ *
+ *
+ * @param ansi the ANSI-encoded string to parse
+ * @param tabs the tab size in columns
+ * @return an AttributedString with styles based on the ANSI escape sequences
+ */
public static AttributedString fromAnsi(String ansi, int tabs) {
return fromAnsi(ansi, Arrays.asList(tabs));
}
+ /**
+ * Creates an AttributedString from an ANSI-encoded string with custom tab stops.
+ *
+ *
+ * This method parses the ANSI escape sequences in the input string and
+ * creates an AttributedString with the corresponding styles. It also handles
+ * tab characters according to the specified tab stops.
+ *
+ *
+ * @param ansi the ANSI-encoded string to parse
+ * @param tabs the list of tab stop positions, or null for default tab stops
+ * @return an AttributedString with styles based on the ANSI escape sequences
+ */
public static AttributedString fromAnsi(String ansi, List tabs) {
return fromAnsi(ansi, tabs, null, null);
}
+ /**
+ * Creates an AttributedString from an ANSI-encoded string, using terminal capabilities.
+ *
+ *
+ * This method parses the ANSI escape sequences in the input string and
+ * creates an AttributedString with the corresponding styles. It uses the
+ * specified terminal's capabilities to handle alternate character sets.
+ *
+ *
+ * @param ansi the ANSI-encoded string to parse
+ * @param terminal the terminal to use for capabilities, or null
+ * @return an AttributedString with styles based on the ANSI escape sequences
+ */
public static AttributedString fromAnsi(String ansi, Terminal terminal) {
String alternateIn, alternateOut;
if (!DISABLE_ALTERNATE_CHARSET) {
@@ -120,6 +273,23 @@ public static AttributedString fromAnsi(String ansi, Terminal terminal) {
return fromAnsi(ansi, Arrays.asList(0), alternateIn, alternateOut);
}
+ /**
+ * Creates an AttributedString from an ANSI-encoded string with custom tab stops
+ * and alternate character set sequences.
+ *
+ *
+ * This method parses the ANSI escape sequences in the input string and
+ * creates an AttributedString with the corresponding styles. It also handles
+ * tab characters according to the specified tab stops and alternate character
+ * set sequences.
+ *
+ *
+ * @param ansi the ANSI-encoded string to parse
+ * @param tabs the list of tab stop positions, or null for default tab stops
+ * @param altIn the sequence to enable the alternate character set, or null
+ * @param altOut the sequence to disable the alternate character set, or null
+ * @return an AttributedString with styles based on the ANSI escape sequences
+ */
public static AttributedString fromAnsi(String ansi, List tabs, String altIn, String altOut) {
if (ansi == null) {
return null;
@@ -131,6 +301,18 @@ public static AttributedString fromAnsi(String ansi, List tabs, String
.toAttributedString();
}
+ /**
+ * Strips ANSI escape sequences from a string.
+ *
+ *
+ * This method removes all ANSI escape sequences from the input string,
+ * returning a plain text string with no styling information. This is useful
+ * for extracting the text content from ANSI-colored output.
+ *
+ *
+ * @param ansi the ANSI-encoded string to strip
+ * @return a plain text string with ANSI escape sequences removed, or null if the input is null
+ */
public static String stripAnsi(String ansi) {
if (ansi == null) {
return null;
@@ -138,36 +320,125 @@ public static String stripAnsi(String ansi) {
return new AttributedStringBuilder(ansi.length()).ansiAppend(ansi).toString();
}
+ /**
+ * Returns the character buffer for this attributed string.
+ *
+ *
+ * This method is used internally by the AttributedCharSequence implementation
+ * to access the underlying character buffer.
+ *
+ *
+ * @return the character buffer
+ */
@Override
protected char[] buffer() {
return buffer;
}
+ /**
+ * Returns the offset in the buffer where this attributed string starts.
+ *
+ *
+ * This method is used internally by the AttributedCharSequence implementation
+ * to determine the starting position in the buffer.
+ *
+ *
+ * @return the offset in the buffer
+ */
@Override
protected int offset() {
return start;
}
+ /**
+ * Returns the length of this attributed string.
+ *
+ *
+ * This method returns the number of characters in this attributed string.
+ *
+ *
+ * @return the number of characters in this attributed string
+ */
@Override
public int length() {
return end - start;
}
+ /**
+ * Returns the style at the specified index in this attributed string.
+ *
+ *
+ * This method returns the AttributedStyle object associated with the
+ * character at the specified index in this attributed string.
+ *
+ *
+ * @param index the index of the character whose style to return
+ * @return the style at the specified index
+ * @throws IndexOutOfBoundsException if the index is out of range
+ */
@Override
public AttributedStyle styleAt(int index) {
return new AttributedStyle(style[start + index], style[start + index]);
}
+ /**
+ * Returns the style code at the specified index in this attributed string.
+ *
+ *
+ * This method returns the raw style code (as a long value) associated with the
+ * character at the specified index in this attributed string.
+ *
+ *
+ * @param index the index of the character whose style code to return
+ * @return the style code at the specified index
+ * @throws IndexOutOfBoundsException if the index is out of range
+ */
@Override
long styleCodeAt(int index) {
return style[start + index];
}
+ /**
+ * Returns a new AttributedString that is a subsequence of this attributed string.
+ *
+ *
+ * This method returns a new AttributedString that contains the characters and
+ * styles from this attributed string starting at the specified start index
+ * (inclusive) and ending at the specified end index (exclusive).
+ *
+ *
+ *
+ * The subsequence preserves the style information from the original string.
+ *
+ *
+ * @param start the start index, inclusive
+ * @param end the end index, exclusive
+ * @return the specified subsequence with its attributes
+ * @throws IndexOutOfBoundsException if start or end are negative,
+ * if end is greater than length(), or if start is greater than end
+ */
@Override
public AttributedString subSequence(int start, int end) {
return new AttributedString(this, start, end);
}
+ /**
+ * Returns a new AttributedString with the specified style applied to all matches of the pattern.
+ *
+ *
+ * This method finds all matches of the specified regular expression pattern in this
+ * attributed string and applies the specified style to the matching regions. It returns
+ * a new AttributedString with the modified styles.
+ *
+ *
+ *
+ * If no matches are found, this method returns the original attributed string.
+ *
+ *
+ * @param pattern the regular expression pattern to match
+ * @param style the style to apply to matching regions
+ * @return a new AttributedString with the specified style applied to matching regions
+ */
public AttributedString styleMatches(Pattern pattern, AttributedStyle style) {
Matcher matcher = pattern.matcher(this);
boolean result = matcher.find();
@@ -184,6 +455,17 @@ public AttributedString styleMatches(Pattern pattern, AttributedStyle style) {
return this;
}
+ /**
+ * Compares this AttributedString with another object for equality.
+ *
+ *
+ * Two AttributedString objects are considered equal if they have the same
+ * length, the same characters, and the same styles at each position.
+ *
+ *
+ * @param o the object to compare with
+ * @return {@code true} if the objects are equal, {@code false} otherwise
+ */
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -194,6 +476,21 @@ && arrEq(buffer, that.buffer, start, that.start, end - start)
&& arrEq(style, that.style, start, that.start, end - start);
}
+ /**
+ * Compares two character arrays for equality within a specified range.
+ *
+ *
+ * This private helper method compares two character arrays starting at the
+ * specified offsets for the specified length.
+ *
+ *
+ * @param a1 the first array
+ * @param a2 the second array
+ * @param s1 the starting offset in the first array
+ * @param s2 the starting offset in the second array
+ * @param l the length to compare
+ * @return {@code true} if the arrays are equal in the specified range, {@code false} otherwise
+ */
private boolean arrEq(char[] a1, char[] a2, int s1, int s2, int l) {
for (int i = 0; i < l; i++) {
if (a1[s1 + i] != a2[s2 + i]) {
@@ -203,6 +500,21 @@ private boolean arrEq(char[] a1, char[] a2, int s1, int s2, int l) {
return true;
}
+ /**
+ * Compares two long arrays for equality within a specified range.
+ *
+ *
+ * This private helper method compares two long arrays starting at the
+ * specified offsets for the specified length.
+ *
+ *
+ * @param a1 the first array
+ * @param a2 the second array
+ * @param s1 the starting offset in the first array
+ * @param s2 the starting offset in the second array
+ * @param l the length to compare
+ * @return {@code true} if the arrays are equal in the specified range, {@code false} otherwise
+ */
private boolean arrEq(long[] a1, long[] a2, int s1, int s2, int l) {
for (int i = 0; i < l; i++) {
if (a1[s1 + i] != a2[s2 + i]) {
@@ -212,6 +524,16 @@ private boolean arrEq(long[] a1, long[] a2, int s1, int s2, int l) {
return true;
}
+ /**
+ * Returns a hash code for this AttributedString.
+ *
+ *
+ * The hash code is computed based on the characters, styles, and range
+ * of this attributed string.
+ *
+ *
+ * @return a hash code value for this object
+ */
@Override
public int hashCode() {
int result = Arrays.hashCode(buffer);
@@ -221,12 +543,44 @@ public int hashCode() {
return result;
}
+ /**
+ * Joins multiple AttributedString objects with a delimiter.
+ *
+ *
+ * This method concatenates the specified AttributedString elements, inserting
+ * the specified delimiter between each element. The resulting AttributedString
+ * preserves the styles of all elements and the delimiter.
+ *
+ *
+ * @param delimiter the delimiter to insert between elements
+ * @param elements the elements to join
+ * @return a new AttributedString containing the joined elements
+ * @throws NullPointerException if delimiter or elements is null
+ */
public static AttributedString join(AttributedString delimiter, AttributedString... elements) {
Objects.requireNonNull(delimiter);
Objects.requireNonNull(elements);
return join(delimiter, Arrays.asList(elements));
}
+ /**
+ * Joins an Iterable of AttributedString objects with a delimiter.
+ *
+ *
+ * This method concatenates the AttributedString elements in the specified Iterable,
+ * inserting the specified delimiter between each element. The resulting AttributedString
+ * preserves the styles of all elements and the delimiter.
+ *
+ *
+ *
+ * If the delimiter is null, the elements are concatenated without any separator.
+ *
+ *
+ * @param delimiter the delimiter to insert between elements, or null for no delimiter
+ * @param elements the elements to join
+ * @return a new AttributedString containing the joined elements
+ * @throws NullPointerException if elements is null
+ */
public static AttributedString join(AttributedString delimiter, Iterable elements) {
Objects.requireNonNull(elements);
AttributedStringBuilder sb = new AttributedStringBuilder();
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/AttributedStringBuilder.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/AttributedStringBuilder.java
index 554381d367d94..22ac59a0839f4 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/AttributedStringBuilder.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/AttributedStringBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -17,9 +17,39 @@
import java.util.regex.Pattern;
/**
- * Attributed string builder
+ * A mutable builder for creating styled text strings with ANSI attributes.
*
- * @author Guillaume Nodet
+ *
+ * The AttributedStringBuilder class provides a mutable implementation of AttributedCharSequence
+ * for constructing styled text strings. It allows for dynamic building of attributed strings
+ * by appending characters, strings, or other attributed strings with various styles.
+ *
+ *
+ *
+ * This class is similar to StringBuilder but with added support for ANSI style attributes.
+ * It provides methods for appending text with different styles, manipulating the content,
+ * and converting the result to an immutable AttributedString when building is complete.
+ *
+ *
+ *
+ * Key features include:
+ *
+ *
+ * Append operations with different styles
+ * Tab expansion with configurable tab stops
+ * Style manipulation (foreground/background colors, bold, underline, etc.)
+ * Regular expression based styling
+ * Alternative character set support
+ *
+ *
+ *
+ * This class is commonly used for building complex styled output for terminal applications,
+ * such as syntax highlighting, interactive prompts, and formatted displays.
+ *
+ *
+ * @see AttributedCharSequence
+ * @see AttributedString
+ * @see AttributedStyle
*/
public class AttributedStringBuilder extends AttributedCharSequence implements Appendable {
@@ -33,6 +63,18 @@ public class AttributedStringBuilder extends AttributedCharSequence implements A
private int lastLineLength = 0;
private AttributedStyle current = AttributedStyle.DEFAULT;
+ /**
+ * Creates an AttributedString by appending multiple character sequences.
+ *
+ *
+ * This static utility method creates a new AttributedString by appending
+ * all the specified character sequences. It is a convenient way to concatenate
+ * multiple strings or AttributedStrings into a single AttributedString.
+ *
+ *
+ * @param strings the character sequences to append
+ * @return a new AttributedString containing all the appended sequences
+ */
public static AttributedString append(CharSequence... strings) {
AttributedStringBuilder sb = new AttributedStringBuilder();
for (CharSequence s : strings) {
@@ -41,52 +83,165 @@ public static AttributedString append(CharSequence... strings) {
return sb.toAttributedString();
}
+ /**
+ * Creates a new AttributedStringBuilder with the default initial capacity.
+ *
+ *
+ * This constructor creates a new AttributedStringBuilder with an initial
+ * capacity of 64 characters. The builder will automatically grow as needed
+ * when more characters are appended.
+ *
+ */
public AttributedStringBuilder() {
this(64);
}
+ /**
+ * Creates a new AttributedStringBuilder with the specified initial capacity.
+ *
+ *
+ * This constructor creates a new AttributedStringBuilder with the specified
+ * initial capacity. The builder will automatically grow as needed when more
+ * characters are appended.
+ *
+ *
+ * @param capacity the initial capacity of the builder
+ */
public AttributedStringBuilder(int capacity) {
buffer = new char[capacity];
style = new long[capacity];
length = 0;
}
+ /**
+ * Returns the length of this attributed string builder.
+ *
+ *
+ * This method returns the number of characters in this attributed string builder.
+ *
+ *
+ * @return the number of characters in this attributed string builder
+ */
@Override
public int length() {
return length;
}
+ /**
+ * Returns the character at the specified index in this attributed string builder.
+ *
+ *
+ * This method returns the character at the specified index in this
+ * attributed string builder.
+ *
+ *
+ * @param index the index of the character to return
+ * @return the character at the specified index
+ * @throws IndexOutOfBoundsException if the index is out of range
+ */
@Override
public char charAt(int index) {
return buffer[index];
}
+ /**
+ * Returns the style at the specified index in this attributed string builder.
+ *
+ *
+ * This method returns the AttributedStyle object associated with the
+ * character at the specified index in this attributed string builder.
+ *
+ *
+ * @param index the index of the character whose style to return
+ * @return the style at the specified index
+ * @throws IndexOutOfBoundsException if the index is out of range
+ */
@Override
public AttributedStyle styleAt(int index) {
return new AttributedStyle(style[index], style[index]);
}
+ /**
+ * Returns the style code at the specified index in this attributed string builder.
+ *
+ *
+ * This method returns the raw style code (as a long value) associated with the
+ * character at the specified index in this attributed string builder.
+ *
+ *
+ * @param index the index of the character whose style code to return
+ * @return the style code at the specified index
+ * @throws IndexOutOfBoundsException if the index is out of range
+ */
@Override
long styleCodeAt(int index) {
return style[index];
}
+ /**
+ * Returns the character buffer for this attributed string builder.
+ *
+ *
+ * This method is used internally by the AttributedCharSequence implementation
+ * to access the underlying character buffer.
+ *
+ *
+ * @return the character buffer
+ */
@Override
protected char[] buffer() {
return buffer;
}
+ /**
+ * Returns the offset in the buffer where this attributed string builder starts.
+ *
+ *
+ * This method is used internally by the AttributedCharSequence implementation
+ * to determine the starting position in the buffer. For AttributedStringBuilder,
+ * this is always 0 since the builder uses the entire buffer.
+ *
+ *
+ * @return the offset in the buffer (always 0 for AttributedStringBuilder)
+ */
@Override
protected int offset() {
return 0;
}
+ /**
+ * Returns a new AttributedString that is a subsequence of this attributed string builder.
+ *
+ *
+ * This method returns a new AttributedString that contains the characters and
+ * styles from this attributed string builder starting at the specified start index
+ * (inclusive) and ending at the specified end index (exclusive).
+ *
+ *
+ * @param start the start index, inclusive
+ * @param end the end index, exclusive
+ * @return the specified subsequence with its attributes
+ * @throws IndexOutOfBoundsException if start or end are negative,
+ * if end is greater than length(), or if start is greater than end
+ */
@Override
public AttributedString subSequence(int start, int end) {
return new AttributedString(
Arrays.copyOfRange(buffer, start, end), Arrays.copyOfRange(style, start, end), 0, end - start);
}
+ /**
+ * Appends the specified character sequence to this builder.
+ *
+ *
+ * This method appends the specified character sequence to this builder,
+ * applying the current style to all characters. If the character sequence
+ * is null, the string "null" is appended, as required by the Appendable interface.
+ *
+ *
+ * @param csq the character sequence to append, or null
+ * @return this builder
+ */
@Override
public AttributedStringBuilder append(CharSequence csq) {
if (csq == null) {
@@ -95,6 +250,22 @@ public AttributedStringBuilder append(CharSequence csq) {
return append(new AttributedString(csq, current));
}
+ /**
+ * Appends a subsequence of the specified character sequence to this builder.
+ *
+ *
+ * This method appends a subsequence of the specified character sequence to this builder,
+ * applying the current style to all characters. If the character sequence
+ * is null, the string "null" is appended, as required by the Appendable interface.
+ *
+ *
+ * @param csq the character sequence to append, or null
+ * @param start the index of the first character to append
+ * @param end the index after the last character to append
+ * @return this builder
+ * @throws IndexOutOfBoundsException if start or end are negative, or if
+ * end is greater than csq.length(), or if start is greater than end
+ */
@Override
public AttributedStringBuilder append(CharSequence csq, int start, int end) {
if (csq == null) {
@@ -103,11 +274,34 @@ public AttributedStringBuilder append(CharSequence csq, int start, int end) {
return append(csq.subSequence(start, end));
}
+ /**
+ * Appends the specified character to this builder.
+ *
+ *
+ * This method appends the specified character to this builder,
+ * applying the current style to the character.
+ *
+ *
+ * @param c the character to append
+ * @return this builder
+ */
@Override
public AttributedStringBuilder append(char c) {
return append(Character.toString(c));
}
+ /**
+ * Appends the specified character to this builder multiple times.
+ *
+ *
+ * This method appends the specified character to this builder the specified
+ * number of times, applying the current style to all characters.
+ *
+ *
+ * @param c the character to append
+ * @param repeat the number of times to append the character
+ * @return this builder
+ */
public AttributedStringBuilder append(char c, int repeat) {
AttributedString s = new AttributedString(Character.toString(c), current);
while (repeat-- > 0) {
@@ -116,28 +310,117 @@ public AttributedStringBuilder append(char c, int repeat) {
return this;
}
+ /**
+ * Appends the specified character sequence to this builder with the specified style.
+ *
+ *
+ * This method appends the specified character sequence to this builder,
+ * applying the specified style to all characters. This allows appending text
+ * with a style different from the current style without changing the current style.
+ *
+ *
+ * @param csq the character sequence to append
+ * @param style the style to apply to the appended characters
+ * @return this builder
+ */
public AttributedStringBuilder append(CharSequence csq, AttributedStyle style) {
return append(new AttributedString(csq, style));
}
+ /**
+ * Sets the current style for this builder.
+ *
+ *
+ * This method sets the current style for this builder, which will be applied
+ * to all characters appended after this call (until the style is changed again).
+ *
+ *
+ * @param style the new current style
+ * @return this builder
+ */
public AttributedStringBuilder style(AttributedStyle style) {
current = style;
return this;
}
+ /**
+ * Updates the current style for this builder using a function.
+ *
+ *
+ * This method updates the current style for this builder by applying the
+ * specified function to the current style. This allows for fluent style
+ * modifications without creating intermediate style objects.
+ *
+ *
+ * Example usage:
+ *
+ * builder.style(s -> s.bold().foreground(AttributedStyle.RED));
+ *
+ *
+ * @param style the function to apply to the current style
+ * @return this builder
+ */
public AttributedStringBuilder style(Function style) {
current = style.apply(current);
return this;
}
+ /**
+ * Appends the specified character sequence with a temporarily modified style.
+ *
+ *
+ * This method temporarily modifies the current style using the specified function,
+ * appends the specified character sequence with that style, and then restores
+ * the original style. This allows for appending styled text without permanently
+ * changing the current style.
+ *
+ *
+ * @param style the function to apply to the current style
+ * @param cs the character sequence to append
+ * @return this builder
+ */
public AttributedStringBuilder styled(Function style, CharSequence cs) {
return styled(style, sb -> sb.append(cs));
}
+ /**
+ * Appends the specified character sequence with the specified style.
+ *
+ *
+ * This method temporarily sets the current style to the specified style,
+ * appends the specified character sequence with that style, and then restores
+ * the original style. This allows for appending styled text without permanently
+ * changing the current style.
+ *
+ *
+ * @param style the style to use
+ * @param cs the character sequence to append
+ * @return this builder
+ */
public AttributedStringBuilder styled(AttributedStyle style, CharSequence cs) {
return styled(s -> style, sb -> sb.append(cs));
}
+ /**
+ * Performs operations with a temporarily modified style.
+ *
+ *
+ * This method temporarily modifies the current style using the specified function,
+ * performs the operations specified by the consumer with that style, and then
+ * restores the original style. This allows for performing complex styling
+ * operations without permanently changing the current style.
+ *
+ *
+ * Example usage:
+ *
+ * builder.styled(s -> s.bold().foreground(AttributedStyle.RED),
+ * sb -> sb.append("Error: ").append(errorMessage));
+ *
+ *
+ * @param style the function to apply to the current style
+ * @param consumer the consumer that performs operations on this builder
+ * @return this builder
+ */
public AttributedStringBuilder styled(
Function style, Consumer consumer) {
AttributedStyle prev = current;
@@ -147,22 +430,93 @@ public AttributedStringBuilder styled(
return this;
}
+ /**
+ * Returns the current style for this builder.
+ *
+ *
+ * This method returns the current style for this builder, which is applied
+ * to all characters appended after the style was set (until the style is changed).
+ *
+ *
+ * @return the current style
+ */
public AttributedStyle style() {
return current;
}
+ /**
+ * Appends the specified AttributedString to this builder.
+ *
+ *
+ * This method appends the specified AttributedString to this builder,
+ * preserving its character attributes. The current style is applied to
+ * any attributes that are not explicitly set in the AttributedString.
+ *
+ *
+ * @param str the AttributedString to append
+ * @return this builder
+ */
public AttributedStringBuilder append(AttributedString str) {
return append((AttributedCharSequence) str, 0, str.length());
}
+ /**
+ * Appends a subsequence of the specified AttributedString to this builder.
+ *
+ *
+ * This method appends a subsequence of the specified AttributedString to this builder,
+ * preserving its character attributes. The current style is applied to
+ * any attributes that are not explicitly set in the AttributedString.
+ *
+ *
+ * @param str the AttributedString to append
+ * @param start the index of the first character to append
+ * @param end the index after the last character to append
+ * @return this builder
+ * @throws IndexOutOfBoundsException if start or end are negative, or if
+ * end is greater than str.length(), or if start is greater than end
+ */
public AttributedStringBuilder append(AttributedString str, int start, int end) {
return append((AttributedCharSequence) str, start, end);
}
+ /**
+ * Appends the specified AttributedCharSequence to this builder.
+ *
+ *
+ * This method appends the specified AttributedCharSequence to this builder,
+ * preserving its character attributes. The current style is applied to
+ * any attributes that are not explicitly set in the AttributedCharSequence.
+ *
+ *
+ * @param str the AttributedCharSequence to append
+ * @return this builder
+ */
public AttributedStringBuilder append(AttributedCharSequence str) {
return append(str, 0, str.length());
}
+ /**
+ * Appends a subsequence of the specified AttributedCharSequence to this builder.
+ *
+ *
+ * This method appends a subsequence of the specified AttributedCharSequence to this builder,
+ * preserving its character attributes. The current style is applied to
+ * any attributes that are not explicitly set in the AttributedCharSequence.
+ *
+ *
+ *
+ * If the sequence contains tab characters and tab stops are defined, the tabs
+ * will be expanded according to the current tab stop settings.
+ *
+ *
+ * @param str the AttributedCharSequence to append
+ * @param start the index of the first character to append
+ * @param end the index after the last character to append
+ * @return this builder
+ * @throws IndexOutOfBoundsException if start or end are negative, or if
+ * end is greater than str.length(), or if start is greater than end
+ */
public AttributedStringBuilder append(AttributedCharSequence str, int start, int end) {
ensureCapacity(length + end - start);
for (int i = start; i < end; i++) {
@@ -196,10 +550,45 @@ protected void ensureCapacity(int nl) {
}
}
+ /**
+ * Appends the specified ANSI-encoded string to this builder.
+ *
+ *
+ * This method parses the ANSI escape sequences in the input string and
+ * appends the text with the corresponding styles to this builder.
+ * This is useful for converting ANSI-colored output from external commands
+ * into styled text.
+ *
+ *
+ *
+ * This method is equivalent to {@link #ansiAppend(String)} but returns void.
+ *
+ *
+ * @param ansi the ANSI-encoded string to parse and append
+ * @see #ansiAppend(String)
+ */
public void appendAnsi(String ansi) {
ansiAppend(ansi);
}
+ /**
+ * Appends the specified ANSI-encoded string to this builder.
+ *
+ *
+ * This method parses the ANSI escape sequences in the input string and
+ * appends the text with the corresponding styles to this builder.
+ * This is useful for converting ANSI-colored output from external commands
+ * into styled text.
+ *
+ *
+ *
+ * The method recognizes standard ANSI SGR (Select Graphic Rendition) sequences
+ * for text attributes (bold, underline, etc.) and colors (foreground and background).
+ *
+ *
+ * @param ansi the ANSI-encoded string to parse and append
+ * @return this builder
+ */
public AttributedStringBuilder ansiAppend(String ansi) {
int ansiStart = 0;
int ansiState = 0;
@@ -442,6 +831,24 @@ protected void insertTab(AttributedStyle s) {
lastLineLength += nb;
}
+ /**
+ * Sets the length of this attributed string builder.
+ *
+ *
+ * This method sets the length of this attributed string builder. If the
+ * specified length is less than the current length, the builder is truncated;
+ * if it is greater than the current length, the builder is extended with
+ * undefined characters and styles.
+ *
+ *
+ *
+ * Note that extending the builder with this method may result in undefined
+ * behavior if the extended region is accessed without first setting its
+ * characters and styles.
+ *
+ *
+ * @param l the new length
+ */
public void setLength(int l) {
length = l;
}
@@ -461,6 +868,20 @@ public AttributedStringBuilder tabs(int tabsize) {
return tabs(Arrays.asList(tabsize));
}
+ /**
+ * Sets the tab stops for this attributed string builder.
+ *
+ *
+ * This method sets the tab stops for this attributed string builder,
+ * which are used for tab expansion when appending tab characters.
+ * Tab stops cannot be changed after text has been added to prevent
+ * inconsistent indentation.
+ *
+ *
+ * @param tabs the list of tab stop positions
+ * @return this attributed string builder
+ * @throws IllegalStateException if text has already been appended
+ */
public AttributedStringBuilder tabs(List tabs) {
if (length > 0) {
throw new IllegalStateException("Cannot change tab size after appending text");
@@ -469,6 +890,21 @@ public AttributedStringBuilder tabs(List tabs) {
return this;
}
+ /**
+ * Sets the alternate character set sequences for this builder.
+ *
+ *
+ * This method sets the alternate character set sequences for this builder,
+ * which are used for handling special characters like box drawing characters.
+ * The alternate character set cannot be changed after text has been added
+ * to prevent inconsistent character rendering.
+ *
+ *
+ * @param altIn the sequence to enable the alternate character set, or null to disable
+ * @param altOut the sequence to disable the alternate character set, or null to disable
+ * @return this builder
+ * @throws IllegalStateException if text has already been appended
+ */
public AttributedStringBuilder altCharset(String altIn, String altOut) {
if (length > 0) {
throw new IllegalStateException("Cannot change alternative charset after appending text");
@@ -478,6 +914,25 @@ public AttributedStringBuilder altCharset(String altIn, String altOut) {
return this;
}
+ /**
+ * Applies the specified style to all matches of the pattern in this builder.
+ *
+ *
+ * This method finds all matches of the specified regular expression pattern in this
+ * builder and applies the specified style to the matching regions. The style is
+ * applied to all characters in each match.
+ *
+ *
+ * Example usage:
+ *
+ * builder.append("Error: File not found")
+ * .styleMatches(Pattern.compile("Error:"), AttributedStyle.DEFAULT.foreground(AttributedStyle.RED));
+ *
+ *
+ * @param pattern the regular expression pattern to match
+ * @param s the style to apply to matching regions
+ * @return this builder
+ */
public AttributedStringBuilder styleMatches(Pattern pattern, AttributedStyle s) {
Matcher matcher = pattern.matcher(this);
while (matcher.find()) {
@@ -488,6 +943,30 @@ public AttributedStringBuilder styleMatches(Pattern pattern, AttributedStyle s)
return this;
}
+ /**
+ * Applies different styles to different capture groups in pattern matches.
+ *
+ *
+ * This method finds all matches of the specified regular expression pattern in this
+ * builder and applies different styles to different capture groups in each match.
+ * The first style in the list is applied to the first capture group, the second style
+ * to the second capture group, and so on.
+ *
+ *
+ * Example usage:
+ *
+ * builder.append("Error: File not found")
+ * .styleMatches(Pattern.compile("(Error): (File not found)"),
+ * Arrays.asList(
+ * AttributedStyle.DEFAULT.foreground(AttributedStyle.RED),
+ * AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW)));
+ *
+ *
+ * @param pattern the regular expression pattern to match
+ * @param styles the list of styles to apply to capture groups
+ * @return this builder
+ * @throws IndexOutOfBoundsException if the pattern has fewer capture groups than styles
+ */
public AttributedStringBuilder styleMatches(Pattern pattern, List styles) {
Matcher matcher = pattern.matcher(this);
while (matcher.find()) {
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/AttributedStyle.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/AttributedStyle.java
index 7845e99e0b0f9..acb86ea69451d 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/AttributedStyle.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/AttributedStyle.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2021, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -9,10 +9,51 @@
package jdk.internal.org.jline.utils;
/**
- * Text styling.
+ * Text styling for terminal output with support for colors, fonts, and other attributes.
*
- * @author Guillaume Nodet
+ *
+ * The AttributedStyle class represents the styling attributes that can be applied to
+ * text in a terminal. It supports various text attributes such as bold, italic, underline,
+ * as well as foreground and background colors. The class uses a bit-packed long value to
+ * efficiently store multiple attributes.
+ *
+ *
+ *
+ * This class provides a fluent API for building styles by chaining method calls. Styles
+ * are immutable, so each method returns a new instance with the requested modifications.
+ *
+ *
+ *
+ * Color support includes:
+ *
+ *
+ * 8 standard ANSI colors (black, red, green, yellow, blue, magenta, cyan, white)
+ * 8 bright variants of the standard colors
+ * 256-color indexed mode
+ * 24-bit true color (RGB) mode
+ *
+ *
+ *
+ * Text attributes include bold, faint, italic, underline, blink, inverse, conceal, and crossed-out.
+ *
+ *
+ * Example usage:
+ *
+ * // Create a style with red foreground, bold, and underline
+ * AttributedStyle style = AttributedStyle.DEFAULT
+ * .foreground(AttributedStyle.RED)
+ * .bold()
+ * .underline();
+ *
+ * // Create a string with this style
+ * AttributedString str = new AttributedString("Error message", style);
+ *
+ *
+ * @see AttributedString
+ * @see AttributedStringBuilder
*/
+// S1845: style constants are intentionally named after their factory methods (e.g., BOLD = DEFAULT.bold())
+@SuppressWarnings("java:S1845")
public class AttributedStyle {
public static final int BLACK = 0;
@@ -49,25 +90,87 @@ public class AttributedStyle {
static final long FG_COLOR = 0xFFFFFFL << FG_COLOR_EXP;
static final long BG_COLOR = 0xFFFFFFL << BG_COLOR_EXP;
+ /**
+ * Default style with no attributes or colors set.
+ */
public static final AttributedStyle DEFAULT = new AttributedStyle();
+
+ /**
+ * Style with bold attribute enabled.
+ */
public static final AttributedStyle BOLD = DEFAULT.bold();
+
+ /**
+ * Style with bold attribute explicitly disabled.
+ */
public static final AttributedStyle BOLD_OFF = DEFAULT.boldOff();
+
+ /**
+ * Style with inverse (reverse video) attribute enabled.
+ */
public static final AttributedStyle INVERSE = DEFAULT.inverse();
+
+ /**
+ * Style with inverse (reverse video) attribute explicitly disabled.
+ */
public static final AttributedStyle INVERSE_OFF = DEFAULT.inverseOff();
+
+ /**
+ * Style with hidden attribute enabled.
+ */
public static final AttributedStyle HIDDEN = DEFAULT.hidden();
+
+ /**
+ * Style with hidden attribute explicitly disabled.
+ */
public static final AttributedStyle HIDDEN_OFF = DEFAULT.hiddenOff();
final long style;
final long mask;
+ /**
+ * Creates a new AttributedStyle with no attributes or colors set.
+ *
+ *
+ * This constructor creates a default style with no attributes or colors set.
+ * It is equivalent to {@link #DEFAULT}.
+ *
+ */
public AttributedStyle() {
this(0, 0);
}
+ /**
+ * Creates a new AttributedStyle by copying another style.
+ *
+ *
+ * This constructor creates a new style with the same attributes and colors
+ * as the specified style.
+ *
+ *
+ * @param s the style to copy
+ */
public AttributedStyle(AttributedStyle s) {
this(s.style, s.mask);
}
+ /**
+ * Creates a new AttributedStyle with the specified style and mask values.
+ *
+ *
+ * This constructor creates a new style with the specified style and mask values.
+ * The style value contains the actual attributes and colors, while the mask value
+ * indicates which attributes and colors are explicitly set (as opposed to being
+ * inherited or default).
+ *
+ *
+ *
+ * This constructor is primarily for internal use and advanced scenarios.
+ *
+ *
+ * @param style the style value containing attributes and colors
+ * @param mask the mask value indicating which attributes and colors are set
+ */
public AttributedStyle(long style, long mask) {
this.style = style;
this.mask = mask & MASK
@@ -75,151 +178,669 @@ public AttributedStyle(long style, long mask) {
| ((style & F_BACKGROUND) != 0 ? BG_COLOR : 0);
}
+ /**
+ * Returns a new style with the bold attribute enabled.
+ *
+ *
+ * This method returns a new style with the bold attribute enabled.
+ * The bold attribute typically makes text appear with a heavier or thicker font.
+ *
+ *
+ * @return a new style with the bold attribute enabled
+ * @see #boldOff()
+ * @see #boldDefault()
+ */
public AttributedStyle bold() {
return new AttributedStyle(style | F_BOLD, mask | F_BOLD);
}
+ /**
+ * Returns a new style with the bold attribute explicitly disabled.
+ *
+ *
+ * This method returns a new style with the bold attribute explicitly disabled.
+ * This is different from {@link #boldDefault()}, which removes any explicit
+ * setting for the bold attribute.
+ *
+ *
+ * @return a new style with the bold attribute explicitly disabled
+ * @see #bold()
+ * @see #boldDefault()
+ */
public AttributedStyle boldOff() {
return new AttributedStyle(style & ~F_BOLD, mask | F_BOLD);
}
+ /**
+ * Returns a new style with the bold attribute set to its default state.
+ *
+ *
+ * This method returns a new style with no explicit setting for the bold attribute.
+ * When this style is applied, the bold attribute will be inherited from the
+ * parent style or use the terminal's default.
+ *
+ *
+ * @return a new style with the bold attribute set to its default state
+ * @see #bold()
+ * @see #boldOff()
+ */
public AttributedStyle boldDefault() {
return new AttributedStyle(style & ~F_BOLD, mask & ~F_BOLD);
}
+ /**
+ * Returns a new style with the faint attribute enabled.
+ *
+ *
+ * This method returns a new style with the faint attribute enabled.
+ * The faint attribute typically makes text appear with a lighter or thinner font,
+ * or with reduced intensity.
+ *
+ *
+ * @return a new style with the faint attribute enabled
+ * @see #faintOff()
+ * @see #faintDefault()
+ */
public AttributedStyle faint() {
return new AttributedStyle(style | F_FAINT, mask | F_FAINT);
}
+ /**
+ * Returns a new style with the faint attribute explicitly disabled.
+ *
+ *
+ * This method returns a new style with the faint attribute explicitly disabled.
+ * This is different from {@link #faintDefault()}, which removes any explicit
+ * setting for the faint attribute.
+ *
+ *
+ * @return a new style with the faint attribute explicitly disabled
+ * @see #faint()
+ * @see #faintDefault()
+ */
public AttributedStyle faintOff() {
return new AttributedStyle(style & ~F_FAINT, mask | F_FAINT);
}
+ /**
+ * Returns a new style with the faint attribute set to its default state.
+ *
+ *
+ * This method returns a new style with no explicit setting for the faint attribute.
+ * When this style is applied, the faint attribute will be inherited from the
+ * parent style or use the terminal's default.
+ *
+ *
+ * @return a new style with the faint attribute set to its default state
+ * @see #faint()
+ * @see #faintOff()
+ */
public AttributedStyle faintDefault() {
return new AttributedStyle(style & ~F_FAINT, mask & ~F_FAINT);
}
+ /**
+ * Returns a new style with the italic attribute enabled.
+ *
+ *
+ * This method returns a new style with the italic attribute enabled.
+ * The italic attribute typically makes text appear slanted or cursive.
+ *
+ *
+ * @return a new style with the italic attribute enabled
+ * @see #italicOff()
+ * @see #italicDefault()
+ */
public AttributedStyle italic() {
return new AttributedStyle(style | F_ITALIC, mask | F_ITALIC);
}
+ /**
+ * Returns a new style with the italic attribute explicitly disabled.
+ *
+ *
+ * This method returns a new style with the italic attribute explicitly disabled.
+ * This is different from {@link #italicDefault()}, which removes any explicit
+ * setting for the italic attribute.
+ *
+ *
+ * @return a new style with the italic attribute explicitly disabled
+ * @see #italic()
+ * @see #italicDefault()
+ */
public AttributedStyle italicOff() {
return new AttributedStyle(style & ~F_ITALIC, mask | F_ITALIC);
}
+ /**
+ * Returns a new style with the italic attribute set to its default state.
+ *
+ *
+ * This method returns a new style with no explicit setting for the italic attribute.
+ * When this style is applied, the italic attribute will be inherited from the
+ * parent style or use the terminal's default.
+ *
+ *
+ * @return a new style with the italic attribute set to its default state
+ * @see #italic()
+ * @see #italicOff()
+ */
public AttributedStyle italicDefault() {
return new AttributedStyle(style & ~F_ITALIC, mask & ~F_ITALIC);
}
+ /**
+ * Returns a new style with the underline attribute enabled.
+ *
+ *
+ * This method returns a new style with the underline attribute enabled.
+ * The underline attribute typically draws a line under the text.
+ *
+ *
+ * @return a new style with the underline attribute enabled
+ * @see #underlineOff()
+ * @see #underlineDefault()
+ */
public AttributedStyle underline() {
return new AttributedStyle(style | F_UNDERLINE, mask | F_UNDERLINE);
}
+ /**
+ * Returns a new style with the underline attribute explicitly disabled.
+ *
+ *
+ * This method returns a new style with the underline attribute explicitly disabled.
+ * This is different from {@link #underlineDefault()}, which removes any explicit
+ * setting for the underline attribute.
+ *
+ *
+ * @return a new style with the underline attribute explicitly disabled
+ * @see #underline()
+ * @see #underlineDefault()
+ */
public AttributedStyle underlineOff() {
return new AttributedStyle(style & ~F_UNDERLINE, mask | F_UNDERLINE);
}
+ /**
+ * Returns a new style with the underline attribute set to its default state.
+ *
+ *
+ * This method returns a new style with no explicit setting for the underline attribute.
+ * When this style is applied, the underline attribute will be inherited from the
+ * parent style or use the terminal's default.
+ *
+ *
+ * @return a new style with the underline attribute set to its default state
+ * @see #underline()
+ * @see #underlineOff()
+ */
public AttributedStyle underlineDefault() {
return new AttributedStyle(style & ~F_UNDERLINE, mask & ~F_UNDERLINE);
}
+ /**
+ * Returns a new style with the blink attribute enabled.
+ *
+ *
+ * This method returns a new style with the blink attribute enabled.
+ * The blink attribute typically makes text flash on and off, though
+ * support varies across terminals.
+ *
+ *
+ * @return a new style with the blink attribute enabled
+ * @see #blinkOff()
+ * @see #blinkDefault()
+ */
public AttributedStyle blink() {
return new AttributedStyle(style | F_BLINK, mask | F_BLINK);
}
+ /**
+ * Returns a new style with the blink attribute explicitly disabled.
+ *
+ *
+ * This method returns a new style with the blink attribute explicitly disabled.
+ * This is different from {@link #blinkDefault()}, which removes any explicit
+ * setting for the blink attribute.
+ *
+ *
+ * @return a new style with the blink attribute explicitly disabled
+ * @see #blink()
+ * @see #blinkDefault()
+ */
public AttributedStyle blinkOff() {
return new AttributedStyle(style & ~F_BLINK, mask | F_BLINK);
}
+ /**
+ * Returns a new style with the blink attribute set to its default state.
+ *
+ *
+ * This method returns a new style with no explicit setting for the blink attribute.
+ * When this style is applied, the blink attribute will be inherited from the
+ * parent style or use the terminal's default.
+ *
+ *
+ * @return a new style with the blink attribute set to its default state
+ * @see #blink()
+ * @see #blinkOff()
+ */
public AttributedStyle blinkDefault() {
return new AttributedStyle(style & ~F_BLINK, mask & ~F_BLINK);
}
+ /**
+ * Returns a new style with the inverse attribute enabled.
+ *
+ *
+ * This method returns a new style with the inverse attribute enabled.
+ * The inverse attribute (also known as reverse video) typically swaps
+ * the foreground and background colors of the text.
+ *
+ *
+ * @return a new style with the inverse attribute enabled
+ * @see #inverseOff()
+ * @see #inverseDefault()
+ * @see #inverseNeg()
+ */
public AttributedStyle inverse() {
return new AttributedStyle(style | F_INVERSE, mask | F_INVERSE);
}
+ /**
+ * Returns a new style with the inverse attribute toggled.
+ *
+ *
+ * This method returns a new style with the inverse attribute toggled from its
+ * current state. If the inverse attribute is currently enabled, it will be disabled,
+ * and if it is currently disabled, it will be enabled.
+ *
+ *
+ * @return a new style with the inverse attribute toggled
+ * @see #inverse()
+ * @see #inverseOff()
+ * @see #inverseDefault()
+ */
public AttributedStyle inverseNeg() {
long s = (style & F_INVERSE) != 0 ? style & ~F_INVERSE : style | F_INVERSE;
return new AttributedStyle(s, mask | F_INVERSE);
}
+ /**
+ * Returns a new style with the inverse attribute explicitly disabled.
+ *
+ *
+ * This method returns a new style with the inverse attribute explicitly disabled.
+ * This is different from {@link #inverseDefault()}, which removes any explicit
+ * setting for the inverse attribute.
+ *
+ *
+ * @return a new style with the inverse attribute explicitly disabled
+ * @see #inverse()
+ * @see #inverseDefault()
+ * @see #inverseNeg()
+ */
public AttributedStyle inverseOff() {
return new AttributedStyle(style & ~F_INVERSE, mask | F_INVERSE);
}
+ /**
+ * Returns a new style with the inverse attribute set to its default state.
+ *
+ *
+ * This method returns a new style with no explicit setting for the inverse attribute.
+ * When this style is applied, the inverse attribute will be inherited from the
+ * parent style or use the terminal's default.
+ *
+ *
+ * @return a new style with the inverse attribute set to its default state
+ * @see #inverse()
+ * @see #inverseOff()
+ * @see #inverseNeg()
+ */
public AttributedStyle inverseDefault() {
return new AttributedStyle(style & ~F_INVERSE, mask & ~F_INVERSE);
}
+ /**
+ * Returns a new style with the conceal attribute enabled.
+ *
+ *
+ * This method returns a new style with the conceal attribute enabled.
+ * The conceal attribute typically hides text from display, though
+ * support varies across terminals.
+ *
+ *
+ * @return a new style with the conceal attribute enabled
+ * @see #concealOff()
+ * @see #concealDefault()
+ */
public AttributedStyle conceal() {
return new AttributedStyle(style | F_CONCEAL, mask | F_CONCEAL);
}
+ /**
+ * Returns a new style with the conceal attribute explicitly disabled.
+ *
+ *
+ * This method returns a new style with the conceal attribute explicitly disabled.
+ * This is different from {@link #concealDefault()}, which removes any explicit
+ * setting for the conceal attribute.
+ *
+ *
+ * @return a new style with the conceal attribute explicitly disabled
+ * @see #conceal()
+ * @see #concealDefault()
+ */
public AttributedStyle concealOff() {
return new AttributedStyle(style & ~F_CONCEAL, mask | F_CONCEAL);
}
+ /**
+ * Returns a new style with the conceal attribute set to its default state.
+ *
+ *
+ * This method returns a new style with no explicit setting for the conceal attribute.
+ * When this style is applied, the conceal attribute will be inherited from the
+ * parent style or use the terminal's default.
+ *
+ *
+ * @return a new style with the conceal attribute set to its default state
+ * @see #conceal()
+ * @see #concealOff()
+ */
public AttributedStyle concealDefault() {
return new AttributedStyle(style & ~F_CONCEAL, mask & ~F_CONCEAL);
}
+ /**
+ * Returns a new style with the crossed-out attribute enabled.
+ *
+ *
+ * This method returns a new style with the crossed-out attribute enabled.
+ * The crossed-out attribute typically draws a line through the text,
+ * though support varies across terminals.
+ *
+ *
+ * @return a new style with the crossed-out attribute enabled
+ * @see #crossedOutOff()
+ * @see #crossedOutDefault()
+ */
public AttributedStyle crossedOut() {
return new AttributedStyle(style | F_CROSSED_OUT, mask | F_CROSSED_OUT);
}
+ /**
+ * Returns a new style with the crossed-out attribute explicitly disabled.
+ *
+ *
+ * This method returns a new style with the crossed-out attribute explicitly disabled.
+ * This is different from {@link #crossedOutDefault()}, which removes any explicit
+ * setting for the crossed-out attribute.
+ *
+ *
+ * @return a new style with the crossed-out attribute explicitly disabled
+ * @see #crossedOut()
+ * @see #crossedOutDefault()
+ */
public AttributedStyle crossedOutOff() {
return new AttributedStyle(style & ~F_CROSSED_OUT, mask | F_CROSSED_OUT);
}
+ /**
+ * Returns a new style with the crossed-out attribute set to its default state.
+ *
+ *
+ * This method returns a new style with no explicit setting for the crossed-out attribute.
+ * When this style is applied, the crossed-out attribute will be inherited from the
+ * parent style or use the terminal's default.
+ *
+ *
+ * @return a new style with the crossed-out attribute set to its default state
+ * @see #crossedOut()
+ * @see #crossedOutOff()
+ */
public AttributedStyle crossedOutDefault() {
return new AttributedStyle(style & ~F_CROSSED_OUT, mask & ~F_CROSSED_OUT);
}
+ /**
+ * Returns a new style with the specified foreground color.
+ *
+ *
+ * This method returns a new style with the specified foreground color.
+ * The color is specified as an index into the terminal's color palette.
+ * Standard ANSI colors are defined as constants in this class (BLACK, RED, etc.).
+ *
+ *
+ *
+ * For 256-color support, use values from 0 to 255. For standard ANSI colors,
+ * use values from 0 to 7, or add BRIGHT (8) for bright variants.
+ *
+ *
+ * @param color the foreground color index
+ * @return a new style with the specified foreground color
+ * @see #foregroundOff()
+ * @see #foregroundDefault()
+ * @see #foreground(int, int, int)
+ * @see #foregroundRgb(int)
+ */
public AttributedStyle foreground(int color) {
return new AttributedStyle(
style & ~FG_COLOR | F_FOREGROUND_IND | (((long) color << FG_COLOR_EXP) & FG_COLOR),
mask | F_FOREGROUND_IND);
}
+ /**
+ * Returns a new style with the specified RGB foreground color.
+ *
+ *
+ * This method returns a new style with the specified RGB foreground color.
+ * The color is specified as separate red, green, and blue components,
+ * each with a value from 0 to 255.
+ *
+ *
+ *
+ * Note that true color support (24-bit RGB) may not be available in all terminals.
+ * In terminals without true color support, the color will be approximated using
+ * the closest available color in the terminal's palette.
+ *
+ *
+ * @param r the red component (0-255)
+ * @param g the green component (0-255)
+ * @param b the blue component (0-255)
+ * @return a new style with the specified RGB foreground color
+ * @see #foregroundRgb(int)
+ * @see #foregroundOff()
+ * @see #foregroundDefault()
+ */
public AttributedStyle foreground(int r, int g, int b) {
return foregroundRgb(r << 16 | g << 8 | b);
}
+ /**
+ * Returns a new style with the specified RGB foreground color.
+ *
+ *
+ * This method returns a new style with the specified RGB foreground color.
+ * The color is specified as a single integer in the format 0xRRGGBB,
+ * where RR, GG, and BB are the red, green, and blue components in hexadecimal.
+ *
+ *
+ *
+ * Note that true color support (24-bit RGB) may not be available in all terminals.
+ * In terminals without true color support, the color will be approximated using
+ * the closest available color in the terminal's palette.
+ *
+ *
+ * @param color the RGB color value (0xRRGGBB)
+ * @return a new style with the specified RGB foreground color
+ * @see #foreground(int, int, int)
+ * @see #foregroundOff()
+ * @see #foregroundDefault()
+ */
public AttributedStyle foregroundRgb(int color) {
return new AttributedStyle(
style & ~FG_COLOR | F_FOREGROUND_RGB | ((((long) color & 0xFFFFFF) << FG_COLOR_EXP) & FG_COLOR),
mask | F_FOREGROUND_RGB);
}
+ /**
+ * Returns a new style with the foreground color explicitly disabled.
+ *
+ *
+ * This method returns a new style with the foreground color explicitly disabled.
+ * This is different from {@link #foregroundDefault()}, which removes any explicit
+ * setting for the foreground color.
+ *
+ *
+ *
+ * When a style with the foreground color disabled is applied, the text will
+ * typically be displayed using the terminal's default foreground color.
+ *
+ *
+ * @return a new style with the foreground color explicitly disabled
+ * @see #foreground(int)
+ * @see #foregroundDefault()
+ */
public AttributedStyle foregroundOff() {
return new AttributedStyle(style & ~FG_COLOR & ~F_FOREGROUND, mask | F_FOREGROUND);
}
+ /**
+ * Returns a new style with the foreground color set to its default state.
+ *
+ *
+ * This method returns a new style with no explicit setting for the foreground color.
+ * When this style is applied, the foreground color will be inherited from the
+ * parent style or use the terminal's default.
+ *
+ *
+ * @return a new style with the foreground color set to its default state
+ * @see #foreground(int)
+ * @see #foregroundOff()
+ */
public AttributedStyle foregroundDefault() {
return new AttributedStyle(style & ~FG_COLOR & ~F_FOREGROUND, mask & ~(F_FOREGROUND | FG_COLOR));
}
+ /**
+ * Returns a new style with the specified background color.
+ *
+ *
+ * This method returns a new style with the specified background color.
+ * The color is specified as an index into the terminal's color palette.
+ * Standard ANSI colors are defined as constants in this class (BLACK, RED, etc.).
+ *
+ *
+ *
+ * For 256-color support, use values from 0 to 255. For standard ANSI colors,
+ * use values from 0 to 7, or add BRIGHT (8) for bright variants.
+ *
+ *
+ * @param color the background color index
+ * @return a new style with the specified background color
+ * @see #backgroundOff()
+ * @see #backgroundDefault()
+ * @see #background(int, int, int)
+ * @see #backgroundRgb(int)
+ */
public AttributedStyle background(int color) {
return new AttributedStyle(
style & ~BG_COLOR | F_BACKGROUND_IND | (((long) color << BG_COLOR_EXP) & BG_COLOR),
mask | F_BACKGROUND_IND);
}
+ /**
+ * Returns a new style with the specified RGB background color.
+ *
+ *
+ * This method returns a new style with the specified RGB background color.
+ * The color is specified as separate red, green, and blue components,
+ * each with a value from 0 to 255.
+ *
+ *
+ *
+ * Note that true color support (24-bit RGB) may not be available in all terminals.
+ * In terminals without true color support, the color will be approximated using
+ * the closest available color in the terminal's palette.
+ *
+ *
+ * @param r the red component (0-255)
+ * @param g the green component (0-255)
+ * @param b the blue component (0-255)
+ * @return a new style with the specified RGB background color
+ * @see #backgroundRgb(int)
+ * @see #backgroundOff()
+ * @see #backgroundDefault()
+ */
public AttributedStyle background(int r, int g, int b) {
return backgroundRgb(r << 16 | g << 8 | b);
}
+ /**
+ * Returns a new style with the specified RGB background color.
+ *
+ *
+ * This method returns a new style with the specified RGB background color.
+ * The color is specified as a single integer in the format 0xRRGGBB,
+ * where RR, GG, and BB are the red, green, and blue components in hexadecimal.
+ *
+ *
+ *
+ * Note that true color support (24-bit RGB) may not be available in all terminals.
+ * In terminals without true color support, the color will be approximated using
+ * the closest available color in the terminal's palette.
+ *
+ *
+ * @param color the RGB color value (0xRRGGBB)
+ * @return a new style with the specified RGB background color
+ * @see #background(int, int, int)
+ * @see #backgroundOff()
+ * @see #backgroundDefault()
+ */
public AttributedStyle backgroundRgb(int color) {
return new AttributedStyle(
style & ~BG_COLOR | F_BACKGROUND_RGB | ((((long) color & 0xFFFFFF) << BG_COLOR_EXP) & BG_COLOR),
mask | F_BACKGROUND_RGB);
}
+ /**
+ * Returns a new style with the background color explicitly disabled.
+ *
+ *
+ * This method returns a new style with the background color explicitly disabled.
+ * This is different from {@link #backgroundDefault()}, which removes any explicit
+ * setting for the background color.
+ *
+ *
+ *
+ * When a style with the background color disabled is applied, the text will
+ * typically be displayed using the terminal's default background color.
+ *
+ *
+ * @return a new style with the background color explicitly disabled
+ * @see #background(int)
+ * @see #backgroundDefault()
+ */
public AttributedStyle backgroundOff() {
return new AttributedStyle(style & ~BG_COLOR & ~F_BACKGROUND, mask | F_BACKGROUND);
}
+ /**
+ * Returns a new style with the background color set to its default state.
+ *
+ *
+ * This method returns a new style with no explicit setting for the background color.
+ * When this style is applied, the background color will be inherited from the
+ * parent style or use the terminal's default.
+ *
+ *
+ * @return a new style with the background color set to its default state
+ * @see #background(int)
+ * @see #backgroundOff()
+ */
public AttributedStyle backgroundDefault() {
return new AttributedStyle(style & ~BG_COLOR & ~F_BACKGROUND, mask & ~(F_BACKGROUND | BG_COLOR));
}
@@ -235,22 +856,83 @@ public AttributedStyle hidden() {
return new AttributedStyle(style | F_HIDDEN, mask | F_HIDDEN);
}
+ /**
+ * Returns a new style with the hidden attribute explicitly disabled.
+ *
+ *
+ * This method returns a new style with the hidden attribute explicitly disabled.
+ * This is different from {@link #hiddenDefault()}, which removes any explicit
+ * setting for the hidden attribute.
+ *
+ *
+ * @return a new style with the hidden attribute explicitly disabled
+ * @see #hidden()
+ * @see #hiddenDefault()
+ */
public AttributedStyle hiddenOff() {
return new AttributedStyle(style & ~F_HIDDEN, mask | F_HIDDEN);
}
+ /**
+ * Returns a new style with the hidden attribute set to its default state.
+ *
+ *
+ * This method returns a new style with no explicit setting for the hidden attribute.
+ * When this style is applied, the hidden attribute will be inherited from the
+ * parent style or use the terminal's default.
+ *
+ *
+ * @return a new style with the hidden attribute set to its default state
+ * @see #hidden()
+ * @see #hiddenOff()
+ */
public AttributedStyle hiddenDefault() {
return new AttributedStyle(style & ~F_HIDDEN, mask & ~F_HIDDEN);
}
+ /**
+ * Returns the raw style value of this style.
+ *
+ *
+ * This method returns the raw style value, which contains all the attributes
+ * and colors encoded as bit flags in a long value. This is primarily for
+ * internal use and advanced scenarios.
+ *
+ *
+ * @return the raw style value
+ * @see #getMask()
+ */
public long getStyle() {
return style;
}
+ /**
+ * Returns the raw mask value of this style.
+ *
+ *
+ * This method returns the raw mask value, which indicates which attributes
+ * and colors are explicitly set (as opposed to being inherited or default).
+ * This is primarily for internal use and advanced scenarios.
+ *
+ *
+ * @return the raw mask value
+ * @see #getStyle()
+ */
public long getMask() {
return mask;
}
+ /**
+ * Compares this AttributedStyle with another object for equality.
+ *
+ *
+ * Two AttributedStyle objects are considered equal if they have the same
+ * style and mask values.
+ *
+ *
+ * @param o the object to compare with
+ * @return {@code true} if the objects are equal, {@code false} otherwise
+ */
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -260,11 +942,37 @@ public boolean equals(Object o) {
return mask == that.mask;
}
+ /**
+ * Returns a hash code for this AttributedStyle.
+ *
+ *
+ * The hash code is computed based on the style and mask values.
+ *
+ *
+ * @return a hash code value for this object
+ */
@Override
public int hashCode() {
return 31 * Long.hashCode(style) + Long.hashCode(mask);
}
+ /**
+ * Returns an ANSI escape sequence string that represents this style.
+ *
+ *
+ * This method generates an ANSI escape sequence string that, when printed to
+ * a terminal, would apply this style. This is useful for debugging or for
+ * generating ANSI-colored output for terminals that support it.
+ *
+ *
+ *
+ * The method works by creating a temporary AttributedStringBuilder, applying
+ * this style to a space character, and then extracting the ANSI escape sequences
+ * from the resulting string.
+ *
+ *
+ * @return an ANSI escape sequence string representing this style
+ */
public String toAnsi() {
AttributedStringBuilder sb = new AttributedStringBuilder();
sb.styled(this, " ");
@@ -272,6 +980,17 @@ public String toAnsi() {
return s.length() > 1 ? s.substring(2, s.indexOf('m')) : s;
}
+ /**
+ * Returns a string representation of this AttributedStyle.
+ *
+ *
+ * This method returns a string representation of this AttributedStyle,
+ * including the style value, mask value, and ANSI escape sequence.
+ * This is primarily useful for debugging.
+ *
+ *
+ * @return a string representation of this AttributedStyle
+ */
@Override
public String toString() {
return "AttributedStyle{" + "style=" + style + ", mask=" + mask + ", ansi=" + toAnsi() + '}';
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/ClosedException.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/ClosedException.java
index a6f5bfb5b5463..e388c617f33f7 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/ClosedException.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/ClosedException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -10,6 +10,22 @@
import java.io.IOException;
+/**
+ * Exception thrown when attempting to use a closed resource.
+ *
+ *
+ * The ClosedException is thrown when an operation is attempted on a resource
+ * (such as a terminal, reader, or writer) that has been closed. This exception
+ * extends IOException and provides the same constructors for different ways of
+ * specifying the error message and cause.
+ *
+ *
+ *
+ * This exception is typically thrown by JLine components when methods are called
+ * after the component has been closed, such as attempting to read from a closed
+ * terminal or write to a closed output stream.
+ *
+ */
public class ClosedException extends IOException {
private static final long serialVersionUID = 3085420657077696L;
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/ColorPalette.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/ColorPalette.java
index b4175fe13cec5..35d29fc9491dc 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/ColorPalette.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/ColorPalette.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2020, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -17,7 +17,35 @@
import jdk.internal.org.jline.terminal.Terminal;
/**
- * Color palette
+ * Color palette for terminal color management and conversion.
+ *
+ *
+ * The ColorPalette class provides functionality for managing terminal colors,
+ * including color conversion between different formats (RGB, ANSI, indexed),
+ * color distance calculation, and color remapping. It helps bridge the gap
+ * between the different color capabilities of various terminals.
+ *
+ *
+ *
+ * This class supports various terminal color modes:
+ *
+ *
+ * 8-color mode (standard ANSI colors)
+ * 16-color mode (standard ANSI colors + bright variants)
+ * 256-color mode (indexed colors)
+ * 24-bit true color mode (RGB colors)
+ *
+ *
+ *
+ * The palette can be used to convert between these color modes, find the closest
+ * matching color in a more limited palette, and even modify the terminal's color
+ * palette on supported terminals.
+ *
+ *
+ *
+ * This class is used internally by JLine components to handle color output across
+ * different terminal types with varying color support capabilities.
+ *
*/
public class ColorPalette {
@@ -266,6 +294,28 @@ private static int[] doLoad(Terminal terminal) throws IOException {
return Arrays.copyOfRange(palette, 0, max + 1);
}
+ /**
+ * Get the terminal's default foreground color.
+ * @return the RGB value of the default foreground color, or -1 if not available
+ */
+ public int getDefaultForeground() {
+ if (terminal == null) {
+ return -1;
+ }
+ return terminal.getDefaultForegroundColor();
+ }
+
+ /**
+ * Get the terminal's default background color.
+ * @return the RGB value of the default background color, or -1 if not available
+ */
+ public int getDefaultBackground() {
+ if (terminal == null) {
+ return -1;
+ }
+ return terminal.getDefaultBackgroundColor();
+ }
+
@Override
public String toString() {
return "ColorPalette[" + "length=" + getLength() + ", " + "distance='" + getDist() + "\']";
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Colors.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Colors.java
index d5e7644d3cb44..6eee03c374e9b 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Colors.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Colors.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -19,8 +19,40 @@
import static jdk.internal.org.jline.terminal.TerminalBuilder.PROP_COLOR_DISTANCE;
+/**
+ * Utility class for color-related operations and definitions.
+ *
+ *
+ * The Colors class provides utility methods and constants for working with colors
+ * in terminal applications. It includes color palettes, color name mappings, and
+ * methods for color parsing and conversion.
+ *
+ *
+ *
+ * This class defines standard color palettes for different terminal color modes:
+ *
+ *
+ * 8 standard ANSI colors
+ * 256-color indexed palette
+ * Named color mappings (e.g., "red", "blue", "navy")
+ *
+ *
+ *
+ * It also provides methods for parsing color specifications in various formats,
+ * such as RGB hex codes, CSS-style color names, and indexed color references.
+ * These utilities help with consistent color handling across different terminal
+ * types and color capabilities.
+ *
+ */
public class Colors {
+ /**
+ * Private constructor to prevent instantiation.
+ */
+ private Colors() {
+ // Utility class
+ }
+
// @spotless:off
/**
@@ -498,6 +530,7 @@ private static double[] xyz2camlch(double[] xyz, double[] vc) {
/** Hue Composition / Hue Quadrature */
public static final int H = 5;
/** Hue */
+ @SuppressWarnings("java:S1845") // H and h are standard CIECAM02 correlate notation
public static final int h = 6;
/** CIECAM02 appearance correlates */
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Curses.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Curses.java
index b9ada27070577..8367af7db027e 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Curses.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Curses.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -15,9 +15,36 @@
import java.util.ArrayDeque;
/**
- * Curses helper methods.
+ * Utility class for terminal cursor and screen manipulation using ANSI escape sequences.
*
- * @author Guillaume Nodet
+ *
+ * The Curses class provides methods for manipulating the terminal display using
+ * ANSI escape sequences and terminal capabilities. It includes functionality for
+ * cursor movement, screen clearing, text attributes, and other terminal operations.
+ *
+ *
+ *
+ * This class is named after the curses library commonly used in Unix-like systems
+ * for terminal control, though it provides a simplified subset of functionality.
+ * It handles the complexities of formatting and interpreting terminal capability
+ * strings, allowing for portable terminal manipulation across different terminal types.
+ *
+ *
+ *
+ * Key features include:
+ *
+ *
+ * Cursor positioning and movement
+ * Screen and line clearing
+ * Text attribute control (bold, underline, etc.)
+ * Color manipulation
+ * Terminal capability string parsing and execution
+ *
+ *
+ *
+ * This class is used internally by JLine components to perform terminal operations
+ * in a consistent way across different terminal types and platforms.
+ *
*/
public final class Curses {
@@ -94,9 +121,9 @@ private static void doTputs(Appendable out, String str, Object... params) throws
case 'n':
out.append('\n');
break;
- // case 'l':
- // rawPrint('\l');
- // break;
+ // case 'l':
+ // rawPrint('\l');
+ // break;
case 'r':
if (exec) {
out.append('\r');
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/DiffHelper.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/DiffHelper.java
index 93b065395de77..1b7b09f37a526 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/DiffHelper.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/DiffHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -12,12 +12,44 @@
import java.util.List;
/**
- * Class containing the diff method.
- * This diff is ANSI aware and will correctly handle text attributes
- * so that any text in a Diff object is a valid ansi string.
+ * Utility class for computing differences between strings with ANSI attribute awareness.
+ *
+ *
+ * The DiffHelper class provides methods for computing the differences between two strings
+ * while being aware of ANSI escape sequences and text attributes. This allows for proper
+ * diffing of styled text without breaking the ANSI escape sequences.
+ *
+ *
+ *
+ * Unlike standard diff algorithms, this implementation ensures that any text in a Diff
+ * object is a valid ANSI string with properly balanced escape sequences. This is particularly
+ * important when diffing AttributedStrings or other text with embedded styling information.
+ *
+ *
+ *
+ * The diff algorithm identifies three types of operations:
+ *
+ *
+ * DELETE - Text that exists in the first string but not in the second
+ * INSERT - Text that exists in the second string but not in the first
+ * EQUAL - Text that is common to both strings
+ *
+ *
+ *
+ * This class is particularly useful for implementing features like change highlighting
+ * in terminal applications, where differences between versions of text need to be
+ * displayed with proper styling.
+ *
*/
public class DiffHelper {
+ /**
+ * Private constructor to prevent instantiation.
+ */
+ private DiffHelper() {
+ // Utility class
+ }
+
/**
* The data structure representing a diff is a Linked list of Diff objects:
* {Diff(Operation.DELETE, "Hello"), Diff(Operation.INSERT, "Goodbye"),
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Display.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Display.java
index f581b334d34a8..3686ab41dedf9 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Display.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Display.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2020, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -19,9 +19,59 @@
import jdk.internal.org.jline.utils.InfoCmp.Capability;
/**
- * Handle display and visual cursor.
+ * Manages terminal display and efficient screen updates with cursor positioning.
*
- * @author Guillaume Nodet
+ *
+ * The Display class provides functionality for managing the display of content on
+ * the terminal screen. It handles the complexities of cursor positioning, line wrapping,
+ * and efficient screen updates to minimize the amount of data sent to the terminal.
+ *
+ *
+ *
+ * This class supports two main modes of operation:
+ *
+ *
+ * Full-screen mode - Takes over the entire terminal screen
+ * Partial-screen mode - Updates only a portion of the screen, preserving content above
+ *
+ *
+ *
+ * Key features include:
+ *
+ *
+ * Efficient screen updates using cursor positioning
+ * Support for multi-line content with proper wrapping
+ * Handling of ANSI-styled text (colors, attributes)
+ * Size-aware rendering that adapts to terminal dimensions
+ * Cursor positioning relative to the display area
+ *
+ *
+ *
+ * This class is used by various JLine components, such as LineReader, to provide
+ * efficient terminal display management for features like command-line editing,
+ * completion menus, and status messages.
+ *
+ *
+ * Thread Safety
+ *
+ * This class is NOT thread-safe and must be accessed from a single thread or with
+ * external synchronization. The Display class maintains mutable state including cursor
+ * position, screen content, and terminal dimensions that can be corrupted by concurrent access.
+ *
+ *
+ * Components that use Display in multi-threaded environments (such as signal handlers for
+ * window resize events) must provide their own synchronization. For example, the LineReader
+ * uses a ReentrantLock to coordinate access between the main thread and signal handlers.
+ *
+ *
+ * Warning: Concurrent access to Display methods may result in:
+ *
+ *
+ * ConcurrentModificationException
+ * Corrupted terminal output
+ * Inconsistent cursor positioning
+ * Race conditions in screen updates
+ *
*/
public class Display {
@@ -45,8 +95,10 @@ public class Display {
protected final Map cost = new HashMap<>();
protected final boolean canScroll;
- protected final boolean wrapAtEol;
- protected final boolean delayedWrapAtEol;
+ protected final boolean terminalWrapAtEol;
+ protected final boolean terminalDelayedWrapAtEol;
+ protected boolean wrapAtEol;
+ protected boolean delayedWrapAtEol;
protected final boolean cursorDownIsNewLine;
@SuppressWarnings("this-escape")
@@ -56,8 +108,11 @@ public Display(Terminal terminal, boolean fullscreen) {
this.canScroll = can(Capability.insert_line, Capability.parm_insert_line)
&& can(Capability.delete_line, Capability.parm_delete_line);
- this.wrapAtEol = terminal.getBooleanCapability(Capability.auto_right_margin);
- this.delayedWrapAtEol = this.wrapAtEol && terminal.getBooleanCapability(Capability.eat_newline_glitch);
+ this.terminalWrapAtEol = terminal.getBooleanCapability(Capability.auto_right_margin);
+ this.terminalDelayedWrapAtEol =
+ this.terminalWrapAtEol && terminal.getBooleanCapability(Capability.eat_newline_glitch);
+ this.wrapAtEol = this.terminalWrapAtEol;
+ this.delayedWrapAtEol = this.terminalDelayedWrapAtEol;
this.cursorDownIsNewLine = "\n".equals(Curses.tputs(terminal.getStringCapability(Capability.cursor_down)));
}
@@ -86,7 +141,18 @@ public void resize(int rows, int columns) {
this.columns = columns;
this.columns1 = columns + 1;
oldLines = AttributedString.join(AttributedString.EMPTY, oldLines)
- .columnSplitLength(columns, true, delayLineWrap());
+ .columnSplitLength(columns, true, delayLineWrap(), terminal);
+ }
+ // When the terminal buffer is wider than the visible window (e.g. Windows with
+ // a wide screen buffer), auto-wrap occurs at the buffer width, not the visible
+ // width. Disable wrap-at-eol reliance since content won't reach the buffer edge.
+ int bufferColumns = terminal.getBufferSize().getColumns();
+ if (bufferColumns > columns) {
+ this.wrapAtEol = false;
+ this.delayedWrapAtEol = false;
+ } else {
+ this.wrapAtEol = this.terminalWrapAtEol;
+ this.delayedWrapAtEol = this.terminalDelayedWrapAtEol;
}
}
@@ -198,8 +264,8 @@ public void update(List newLines, int targetCursorPos, boolean
int numLines = Math.min(rows, Math.max(oldLines.size(), newLines.size()));
boolean wrapNeeded = false;
while (lineIndex < numLines) {
- AttributedString oldLine = lineIndex < oldLines.size() ? oldLines.get(lineIndex) : AttributedString.NEWLINE;
- AttributedString newLine = lineIndex < newLines.size() ? newLines.get(lineIndex) : AttributedString.NEWLINE;
+ AttributedString oldLine = lineIndex < oldLines.size() ? oldLines.get(lineIndex) : AttributedString.EMPTY;
+ AttributedString newLine = lineIndex < newLines.size() ? newLines.get(lineIndex) : AttributedString.EMPTY;
currentPos = lineIndex * columns1;
int curCol = currentPos;
int oldLength = oldLine.length();
@@ -220,12 +286,12 @@ public void update(List newLines, int targetCursorPos, boolean
if (newLength == 0 || newLine.isHidden(0)) {
// go to next line column zero
rawPrint(' ');
- terminal.puts(Capability.key_backspace);
+ terminal.puts(Capability.cursor_left);
} else {
AttributedString firstChar = newLine.substring(0, 1);
// go to next line column one
rawPrint(firstChar);
- cursorPos += firstChar.columnLength(); // normally 1
+ cursorPos += firstChar.columnLength(terminal); // normally 1
newLine = newLine.substring(1, newLength);
newLength--;
if (oldLength > 0) {
@@ -235,12 +301,35 @@ public void update(List newLines, int targetCursorPos, boolean
currentPos = cursorPos;
}
}
+ // When grapheme cluster mode is active, the terminal may retroactively
+ // combine or uncombine characters as ZWJ and other combining code points
+ // are added incrementally. This invalidates cursor position tracking
+ // based on char-level diffs. Force a full line repaint in this case.
+ if (terminal.getGraphemeClusterMode() && !oldLine.equals(newLine)) {
+ cursorPos = moveVisualCursorTo(currentPos);
+ if (!terminal.puts(Capability.clr_eol)) {
+ int oldLen = oldLine.columnLength(terminal);
+ if (oldLen > 0) {
+ rawPrint(' ', oldLen);
+ cursorPos += oldLen;
+ cursorPos = moveVisualCursorTo(currentPos);
+ }
+ }
+ rawPrint(newLine);
+ cursorPos += newLine.columnLength(terminal);
+ currentPos = cursorPos;
+ lineIndex++;
+ boolean newWrap2 = !newNL && lineIndex < newLines.size();
+ if (targetCursorPos + 1 == lineIndex * columns1 && (newWrap2 || !delayLineWrap)) targetCursorPos++;
+ wrapNeeded = newWrap2;
+ continue;
+ }
List diffs = DiffHelper.diff(oldLine, newLine);
boolean ident = true;
boolean cleared = false;
for (int i = 0; i < diffs.size(); i++) {
DiffHelper.Diff diff = diffs.get(i);
- int width = diff.text.columnLength();
+ int width = diff.text.columnLength(terminal);
switch (diff.operation) {
case EQUAL:
if (!ident) {
@@ -263,7 +352,7 @@ public void update(List newLines, int targetCursorPos, boolean
}
} else if (i <= diffs.size() - 2
&& diffs.get(i + 1).operation == DiffHelper.Operation.DELETE
- && width == diffs.get(i + 1).text.columnLength()) {
+ && width == diffs.get(i + 1).text.columnLength(terminal)) {
moveVisualCursorTo(currentPos);
rawPrint(diff.text);
cursorPos += width;
@@ -285,15 +374,15 @@ public void update(List newLines, int targetCursorPos, boolean
continue;
}
if (i <= diffs.size() - 2 && diffs.get(i + 1).operation == DiffHelper.Operation.EQUAL) {
- if (currentPos + diffs.get(i + 1).text.columnLength() < columns) {
+ if (currentPos + diffs.get(i + 1).text.columnLength(terminal) < columns) {
moveVisualCursorTo(currentPos);
if (deleteChars(width)) {
break;
}
}
}
- int oldLen = oldLine.columnLength();
- int newLen = newLine.columnLength();
+ int oldLen = oldLine.columnLength(terminal);
+ int newLen = newLine.columnLength(terminal);
int nb = Math.max(oldLen, newLen) - (currentPos - curCol);
moveVisualCursorTo(currentPos);
if (!terminal.puts(Capability.clr_eol)) {
@@ -321,7 +410,7 @@ public void update(List newLines, int targetCursorPos, boolean
if (this.wrapAtEol) {
if (!fullScreen || (fullScreen && lineIndex < numLines)) {
rawPrint(' ');
- terminal.puts(Capability.key_backspace);
+ terminal.puts(Capability.cursor_left);
cursorPos++;
}
} else {
@@ -422,7 +511,7 @@ protected void moveVisualCursorTo(int targetPos, List newLines
int row = targetPos / columns1;
AttributedString lastChar = row >= newLines.size()
? AttributedString.EMPTY
- : newLines.get(row).columnSubSequence(columns - 1, columns);
+ : newLines.get(row).columnSubSequence(columns - 1, columns, terminal);
if (lastChar.length() == 0) rawPrint((int) ' ');
else rawPrint(lastChar);
cursorPos++;
@@ -494,6 +583,6 @@ void rawPrint(AttributedString str) {
}
public int wcwidth(String str) {
- return str != null ? AttributedString.fromAnsi(str).columnLength() : 0;
+ return str != null ? AttributedString.fromAnsi(str).columnLength(terminal) : 0;
}
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/ExecHelper.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/ExecHelper.java
index 5d3c235661b4c..d07f2856d2a31 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/ExecHelper.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/ExecHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -18,7 +18,30 @@
import java.util.Objects;
/**
- * Helper methods for running unix commands.
+ * Utility class for executing external commands and capturing their output.
+ *
+ *
+ * The ExecHelper class provides methods for executing external commands (primarily
+ * on Unix-like systems) and capturing their output. It handles the complexities of
+ * process creation, input/output redirection, and process termination.
+ *
+ *
+ *
+ * This class is used by various JLine components that need to interact with the
+ * underlying operating system, such as terminal detection, capability querying,
+ * and terminal size determination. It provides a simplified interface for executing
+ * commands and capturing their output as strings.
+ *
+ *
+ *
+ * The methods in this class handle common error conditions, such as interrupted
+ * execution and I/O errors, and provide appropriate logging for debugging purposes.
+ *
+ *
+ *
+ * Note that while this class is primarily designed for Unix-like systems, some
+ * functionality may work on other platforms depending on the available commands.
+ *
*/
public final class ExecHelper {
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/FastBufferedOutputStream.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/FastBufferedOutputStream.java
index 62c29c17f7816..a27ee700f41c8 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/FastBufferedOutputStream.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/FastBufferedOutputStream.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009-2023, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -13,7 +13,34 @@
import java.io.OutputStream;
/**
- * A simple buffering output stream with no synchronization.
+ * A simple, non-synchronized buffered output stream for improved performance.
+ *
+ *
+ * The FastBufferedOutputStream class provides a buffered output stream implementation
+ * that improves performance by reducing the number of calls to the underlying output
+ * stream. Unlike the standard BufferedOutputStream, this implementation does not
+ * include synchronization, making it faster but not thread-safe.
+ *
+ *
+ *
+ * This class is particularly useful in single-threaded contexts where the overhead
+ * of synchronization is unnecessary. It uses a fixed-size buffer (8192 bytes) to
+ * collect written data before flushing it to the underlying output stream.
+ *
+ *
+ *
+ * Key features include:
+ *
+ *
+ * No synchronization overhead for improved performance
+ * Fixed-size buffer to reduce system calls
+ * Compatible with the standard OutputStream API
+ *
+ *
+ *
+ * Note that this class is not thread-safe and should not be used in multi-threaded
+ * contexts without external synchronization.
+ *
*/
public class FastBufferedOutputStream extends FilterOutputStream {
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/InfoCmp.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/InfoCmp.java
index 32cf3f56318ab..7c317df57c76a 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/InfoCmp.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/InfoCmp.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2019, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -18,15 +18,49 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
+import jdk.internal.org.jline.terminal.Terminal;
/**
- * Infocmp helper methods.
+ * Utility class for terminal capability handling and terminfo database access.
*
- * @author Guillaume Nodet
+ *
+ * The InfoCmp class provides utilities for working with terminal capabilities and
+ * accessing the terminfo database. It includes functionality for parsing terminfo
+ * entries, accessing capability values, and formatting capability strings with
+ * parameters.
+ *
+ *
+ *
+ * Terminal capabilities are properties that describe what a terminal can do, such as
+ * moving the cursor, changing colors, or clearing the screen. These capabilities are
+ * typically stored in a terminfo database and are accessed by terminal type (e.g.,
+ * "xterm", "vt100").
+ *
+ *
+ *
+ * This class defines three types of capabilities:
+ *
+ *
+ * Boolean capabilities - Indicate whether a terminal supports a feature
+ * Numeric capabilities - Provide numeric values for terminal properties
+ * String capabilities - Define escape sequences for terminal operations
+ *
+ *
+ *
+ * The class is named after the "infocmp" utility found in Unix-like systems, which
+ * is used to compare or print terminfo descriptions. It provides similar functionality
+ * for accessing and comparing terminal capabilities in Java.
+ *
+ *
+ *
+ * This class is used extensively throughout JLine to determine terminal capabilities
+ * and generate appropriate escape sequences for terminal operations.
+ *
*/
public final class InfoCmp {
- private static final Map CAPS = new HashMap<>();
+ private static final Map CAPS_DEFAULT = new HashMap<>();
+ private static final Map CAPS_LOADED = new HashMap<>();
private InfoCmp() {}
@@ -496,6 +530,10 @@ public enum Capability {
enter_vertical_hl_mode, // enter_vertical_hl_mode, evhlm, Xv
set_a_attributes, // set_a_attributes, sgr1, sA
set_pglen_inch, // set_pglen_inch, slength, sL)
+
+ // Extended capabilities
+ xm, // format of mouse event escape sequences
+ XM, // enable/disable mouse event reporting
;
public String[] getNames() {
@@ -531,31 +569,77 @@ public static Map getCapabilitiesByName() {
}
public static void setDefaultInfoCmp(String terminal, String caps) {
- CAPS.putIfAbsent(terminal, caps);
+ CAPS_DEFAULT.putIfAbsent(terminal, caps);
}
public static void setDefaultInfoCmp(String terminal, Supplier caps) {
- CAPS.putIfAbsent(terminal, caps);
+ CAPS_DEFAULT.putIfAbsent(terminal, caps);
}
- public static String getInfoCmp(String terminal) throws IOException, InterruptedException {
- String caps = getLoadedInfoCmp(terminal);
- if (caps == null) {
- Process p = new ProcessBuilder(OSUtils.INFOCMP_COMMAND, terminal).start();
- caps = ExecHelper.waitAndCapture(p);
- CAPS.put(terminal, caps);
+ public static String getDefaultInfoCmp(String terminal) {
+ Object caps = CAPS_DEFAULT.get(terminal);
+ if (caps instanceof Supplier) {
+ caps = ((Supplier) caps).get();
}
- return caps;
+ return (String) caps;
+ }
+
+ public static void setLoadedInfoCmp(String terminal, String caps) {
+ CAPS_LOADED.put(terminal, caps);
+ }
+
+ public static void setLoadedInfoCmp(String terminal, Supplier caps) {
+ CAPS_LOADED.put(terminal, caps);
}
public static String getLoadedInfoCmp(String terminal) {
- Object caps = CAPS.get(terminal);
+ Object caps = CAPS_LOADED.get(terminal);
if (caps instanceof Supplier) {
caps = ((Supplier) caps).get();
}
return (String) caps;
}
+ public static String getInfoCmp(String terminal) throws IOException, InterruptedException {
+ IOException error = new IOException("Unable to retrieve infocmp for " + terminal);
+ String caps = getLoadedInfoCmp(terminal);
+ if (false && caps == null) {
+ try {
+ Process p = new ProcessBuilder(OSUtils.INFOCMP_COMMAND, "-x", terminal).start();
+ caps = ExecHelper.waitAndCapture(p);
+ if (p.exitValue() != 0) {
+ error.addSuppressed(new IOException("Command '" + OSUtils.INFOCMP_COMMAND + " -x " + terminal
+ + "' failed with exit code " + p.exitValue() + " and output '" + caps + "'"));
+ caps = null;
+ }
+ } catch (IOException e) {
+ error.addSuppressed(e);
+ }
+ }
+ if (false && caps == null) {
+ try {
+ Process p = new ProcessBuilder(OSUtils.INFOCMP_COMMAND, terminal).start();
+ caps = ExecHelper.waitAndCapture(p);
+ if (p.exitValue() != 0) {
+ error.addSuppressed(new IOException("Command '" + OSUtils.INFOCMP_COMMAND + " " + terminal
+ + "' failed with exit code " + p.exitValue() + " and output '" + caps + "'"));
+ caps = null;
+ }
+ } catch (IOException e) {
+ error.addSuppressed(e);
+ }
+ }
+ if (caps != null) {
+ setLoadedInfoCmp(terminal, caps);
+ } else {
+ caps = getDefaultInfoCmp(terminal);
+ if (caps == null) {
+ throw error;
+ }
+ }
+ return caps;
+ }
+
public static void parseInfoCmp(
String capabilities,
Set bools,
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/InputStreamReader.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/InputStreamReader.java
index 1198da4fe82dc..228154369000c 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/InputStreamReader.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/InputStreamReader.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -22,20 +22,47 @@
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
- * is not usable in a character per character model used in the terminal.
- * We thus use the harmony code which only reads the minimal number of bytes.
- */
-
/**
- * A class for turning a byte stream into a character stream. Data read from the
+ * A specialized InputStreamReader that reads the minimal number of bytes needed.
+ *
+ *
+ * This class is a custom implementation of InputStreamReader that reads only the
+ * minimal number of bytes needed from the input stream to decode characters. Unlike
+ * the standard JRE InputStreamReader, which often reads more bytes than necessary,
+ * this implementation is optimized for character-by-character reading, which is
+ * essential for terminal input handling.
+ *
+ *
+ *
+ * The standard InputStreamReader's behavior of reading ahead is problematic for
+ * terminal applications because:
+ *
+ *
+ * It can consume bytes that belong to the next user input
+ * It makes it difficult to implement non-blocking character reading
+ * It interferes with the character-by-character model used in terminals
+ *
+ *
+ *
+ * This implementation is based on the Apache Harmony code and has been adapted for
+ * JLine's needs. It provides the same functionality as the standard InputStreamReader
+ * but with more precise control over how many bytes are read from the underlying
+ * input stream.
+ *
+ *
+ *
+ * This class turns a byte stream into a character stream. Data read from the
* source input stream is converted into characters by either a default or a
* provided character converter. The default encoding is taken from the
- * "file.encoding" system property. {@code InputStreamReader} contains a buffer
- * of bytes read from the source stream and converts these into characters as
- * needed. The buffer size is 8K.
+ * "file.encoding" system property. It contains a buffer of bytes read from
+ * the source stream and converts these into characters as needed. The buffer
+ * size is 8K.
+ *
+ *
+ *
+ * This class is used internally by JLine components that need to read characters
+ * from input streams, such as terminal input handling and non-blocking readers.
+ *
*
* @see OutputStreamWriter
*/
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Levenshtein.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Levenshtein.java
index 015515f7d074e..daa1a92f0d9f2 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Levenshtein.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Levenshtein.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -12,9 +12,13 @@
import java.util.Map;
/**
+ * Utility class for computing string similarity using the Damerau-Levenshtein algorithm.
+ *
+ *
* The Damerau-Levenshtein Algorithm is an extension to the Levenshtein
* Algorithm which solves the edit distance problem between a source string and
* a target string with the following operations:
+ *
*
*
* Character Insertion
@@ -23,28 +27,43 @@
* Adjacent Character Swap
*
*
+ *
* Note that the adjacent character swap operation is an edit that may be
* applied when two adjacent characters in the source string match two adjacent
* characters in the target string, but in reverse order, rather than a general
* allowance for adjacent character swaps.
- *
+ *
*
+ *
* This implementation allows the client to specify the costs of the various
* edit operations with the restriction that the cost of two swap operations
* must not be less than the cost of a delete operation followed by an insert
* operation. This restriction is required to preclude two swaps involving the
* same character being required for optimality which, in turn, enables a fast
* dynamic programming solution.
- *
+ *
*
+ *
* The running time of the Damerau-Levenshtein algorithm is O(n*m) where n is
* the length of the source string and m is the length of the target string.
* This implementation consumes O(n*m) space.
+ *
*
- * @author Kevin L. Stern
+ *
+ * This class is used in JLine for features like command completion and suggestion,
+ * where string similarity is used to find potential matches or corrections for
+ * user input.
+ *
*/
public class Levenshtein {
+ /**
+ * Private constructor to prevent instantiation.
+ */
+ private Levenshtein() {
+ // Utility class
+ }
+
public static int distance(CharSequence lhs, CharSequence rhs) {
return distance(lhs, rhs, 1, 1, 1, 1);
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Log.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Log.java
index 254173d5bd971..69bbc23bfc49e 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Log.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Log.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2020, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -16,13 +16,60 @@
//import java.util.logging.Logger;
/**
- * Internal logger.
+ * Internal logging utility for JLine components.
+ *
+ *
+ * The Log class provides a simple logging facility for JLine components, using
+ * Java's standard logging API (java.util.logging) under the hood. It offers
+ * methods for logging at various levels (trace, debug, info, warn, error) and
+ * supports both direct message logging and lazy evaluation through suppliers.
+ *
+ *
+ *
+ * This class uses a single logger named "org.jline" for all JLine components.
+ * The actual log level can be configured through the standard Java logging
+ * configuration mechanisms, such as logging.properties files or programmatic
+ * configuration of the java.util.logging framework.
+ *
+ *
+ *
+ * Key features include:
+ *
+ *
+ * Simple static methods for different log levels
+ * Support for multiple message objects that are concatenated
+ * Lazy evaluation of log messages using Supplier interfaces
+ * Automatic exception handling with stack trace logging
+ * Performance optimization to avoid string concatenation when logging is disabled
+ *
+ *
+ * Example usage:
+ *
+ * // Simple logging
+ * Log.debug("Processing command: ", command);
+ *
+ * // Logging with lazy evaluation
+ * Log.trace(() -> "Expensive computation result: " + computeResult());
+ *
+ * // Logging exceptions
+ * try {
+ * // Some operation
+ * } catch (Exception e) {
+ * Log.error("Failed to process: ", e);
+ * }
+ *
*
- * @author Jason Dillon
- * @author Guillaume Nodet
* @since 2.0
*/
public final class Log {
+
+ /**
+ * Private constructor to prevent instantiation.
+ */
+ private Log() {
+ // Utility class
+ }
+
// private static final Logger logger = Logger.getLogger("org.jline");
public static void trace(final Object... messages) {
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlocking.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlocking.java
index 4130634ca4bfb..e75fceed02697 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlocking.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlocking.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -18,8 +18,41 @@
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
+/**
+ * Factory class for creating non-blocking I/O components.
+ *
+ *
+ * The NonBlocking class provides factory methods for creating various non-blocking
+ * input/output components used in JLine. These components allow for non-blocking
+ * reading operations, which are essential for interactive terminal applications
+ * that need to perform other tasks while waiting for user input.
+ *
+ *
+ *
+ * This class offers methods to create:
+ *
+ *
+ * Non-blocking readers from various sources (streams, readers)
+ * Non-blocking input streams
+ * Pump readers and streams for buffered non-blocking I/O
+ * Character encoding/decoding utilities for non-blocking I/O
+ *
+ *
+ *
+ * The non-blocking components created by this factory are used throughout JLine
+ * to implement features like input handling with timeouts, background processing
+ * while waiting for input, and efficient terminal I/O.
+ *
+ */
public class NonBlocking {
+ /**
+ * Private constructor to prevent instantiation.
+ */
+ private NonBlocking() {
+ // Utility class
+ }
+
public static NonBlockingPumpReader nonBlockingPumpReader() {
return new NonBlockingPumpReader();
}
@@ -89,11 +122,13 @@ public int available() {
@Override
public void close() throws IOException {
+ super.close();
reader.close();
}
@Override
public int read(long timeout, boolean isPeek) throws IOException {
+ checkClosed();
Timeout t = new Timeout(timeout);
while (!bytes.hasRemaining() && !t.elapsed()) {
int c = reader.read(t.timeout());
@@ -152,6 +187,7 @@ public NonBlockingInputStreamReader(NonBlockingInputStream input, CharsetDecoder
@Override
protected int read(long timeout, boolean isPeek) throws IOException {
+ checkClosed();
Timeout t = new Timeout(timeout);
while (!chars.hasRemaining() && !t.elapsed()) {
int b = input.read(t.timeout());
@@ -224,6 +260,7 @@ public void shutdown() {
@Override
public void close() throws IOException {
+ super.close();
input.close();
}
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingInputStream.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingInputStream.java
index 10ab68cfd28e0..5a908fc48f4e4 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingInputStream.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingInputStream.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -10,15 +10,148 @@
import java.io.IOException;
import java.io.InputStream;
+//import java.util.logging.Level;
+//import java.util.logging.Logger;
+
+import static jdk.internal.org.jline.terminal.TerminalBuilder.PROP_CLOSE_MODE;
/**
- * Non blocking input stream
+ * An input stream that supports non-blocking read operations with timeouts.
+ *
+ *
+ * The NonBlockingInputStream class extends InputStream to provide non-blocking read
+ * operations. Unlike standard input streams, which block indefinitely until data is
+ * available or the end of the stream is reached, non-blocking input streams can be
+ * configured to return immediately or after a specified timeout if no data is available.
+ *
+ *
+ *
+ * This class defines two special return values:
+ *
+ *
+ * {@link #EOF} (-1) - Indicates that the end of the stream has been reached
+ * {@link #READ_EXPIRED} (-2) - Indicates that the read operation timed out
+ *
+ *
+ *
+ * This abstract class provides the framework for non-blocking input operations, with
+ * concrete implementations handling the details of how the non-blocking behavior is
+ * achieved (e.g., through NIO, separate threads, or native methods).
+ *
+ *
+ *
+ * Non-blocking input streams are particularly useful for terminal applications that
+ * need to perform other tasks while waiting for user input, or that need to implement
+ * features like input timeouts or polling.
+ *
*/
public abstract class NonBlockingInputStream extends InputStream {
+ /**
+ * Default constructor.
+ * Initializes close mode based on the current value of the system property.
+ */
+ public NonBlockingInputStream() {
+ this.closeMode = parseCloseMode();
+ }
+
+ /**
+ * Parses the close mode from system properties.
+ */
+ private static CloseMode parseCloseMode() {
+ String mode = System.getProperty(PROP_CLOSE_MODE);
+ if (mode != null) {
+ if ("strict".equalsIgnoreCase(mode)) {
+ return CloseMode.STRICT;
+ } else if ("warn".equalsIgnoreCase(mode)) {
+ return CloseMode.WARN;
+ } else if ("lenient".equalsIgnoreCase(mode)) {
+ return CloseMode.LENIENT;
+ }
+ }
+
+ // Default: strict for v4
+ return CloseMode.STRICT;
+ }
+
public static final int EOF = -1;
public static final int READ_EXPIRED = -2;
+// private static final Logger LOG = Logger.getLogger(NonBlockingInputStream.class.getName());
+
+ /**
+ * Flag indicating whether this input stream has been closed.
+ * Marked as volatile to ensure visibility across threads.
+ */
+ protected volatile boolean closed = false;
+
+ /**
+ * Flag to track if a warning has been logged for this input stream.
+ * Used to avoid log spam in warn mode.
+ */
+ private boolean warningLogged = false;
+
+ /**
+ * Close mode for this input stream.
+ * Determined at construction time from the system property.
+ */
+ private final CloseMode closeMode;
+
+ /**
+ * Enum representing the close mode behavior.
+ */
+ private enum CloseMode {
+ /** Throw ClosedException when accessing closed streams */
+ STRICT,
+ /** Log warning when accessing closed streams */
+ WARN,
+ /** Silently allow accessing closed streams */
+ LENIENT
+ }
+
+ /**
+ * Checks if this input stream has been closed.
+ *
+ * In JLine 4.x, strict mode is enabled by default: when a closed input stream is accessed,
+ * it throws a {@code ClosedException}. This ensures proper resource management and
+ * prevents use-after-close bugs.
+ *
+ *
+ * The behavior can be controlled via the system property
+ * {@link org.jline.terminal.TerminalBuilder#PROP_CLOSE_MODE PROP_CLOSE_MODE}:
+ *
+ *
+ * {@code "strict"} - Throw {@code ClosedException} (default in JLine 4.x)
+ * {@code "warn"} - Log a warning but continue (default in JLine 3.x)
+ * {@code "lenient"} - Silently allow access (no warning, no exception)
+ *
+ *
+ * @throws ClosedException if this input stream has been closed and strict mode is enabled (default)
+ */
+ protected void checkClosed() throws IOException {
+ if (closed) {
+ switch (closeMode) {
+ case STRICT:
+ throw new ClosedException();
+ case WARN:
+ // Log warning only once per input stream instance to avoid log spam
+ if (!warningLogged) {
+// LOG.log(
+// Level.WARNING,
+// "Accessing a closed input stream. "
+// + "This may indicate a resource management issue. "
+// + "Set -D" + PROP_CLOSE_MODE + "=strict to make this an error.",
+// new Throwable("Stack trace"));
+ warningLogged = true;
+ }
+ break;
+ case LENIENT:
+ // Silently allow access
+ break;
+ }
+ }
+ }
+
/**
* Reads the next byte of data from the input stream. The value byte is
* returned as an int in the range 0 to
@@ -118,4 +251,23 @@ public int readBuffered(byte[] b, int off, int len, long timeout) throws IOExcep
public void shutdown() {}
public abstract int read(long timeout, boolean isPeek) throws IOException;
+
+ /**
+ * Closes this input stream and marks it as closed.
+ *
+ * Subsequent read operations behavior depends on the
+ * {@link org.jline.terminal.TerminalBuilder#PROP_CLOSE_MODE PROP_CLOSE_MODE} setting:
+ *
+ *
+ * {@code "strict"} - Throw {@link ClosedException} (default in JLine 4.x)
+ * {@code "warn"} - Log a warning but continue (default in JLine 3.x)
+ * {@code "lenient"} - Silently allow access
+ *
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public void close() throws IOException {
+ closed = true;
+ }
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingInputStreamImpl.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingInputStreamImpl.java
index 24cf13a0ee5ad..43c7b33ea5afb 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingInputStreamImpl.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingInputStreamImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -75,6 +75,7 @@ public void close() throws IOException {
* The underlying input stream is closed first. This means that if the
* I/O thread was blocked waiting on input, it will be woken for us.
*/
+ super.close(); // Mark as closed in base class
in.close();
shutdown();
}
@@ -89,6 +90,9 @@ public void close() throws IOException {
* @throws IOException if anything wrong happens
*/
public synchronized int read(long timeout, boolean isPeek) throws IOException {
+ // Check if this input stream has been closed
+ checkClosed();
+
/*
* If the thread hit an IOException, we report it.
*/
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingPumpInputStream.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingPumpInputStream.java
index aad6e3d0f5af0..ecacd7623a6de 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingPumpInputStream.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingPumpInputStream.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2017, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -23,8 +23,6 @@ public class NonBlockingPumpInputStream extends NonBlockingInputStream {
private final OutputStream output;
- private boolean closed;
-
private IOException ioException;
public NonBlockingPumpInputStream() {
@@ -85,6 +83,7 @@ public synchronized int available() {
@Override
public synchronized int read(long timeout, boolean isPeek) throws IOException {
+ checkClosed();
checkIoException();
// Blocks until more input is available or the reader is closed.
int res = wait(readBuffer, timeout);
@@ -104,6 +103,7 @@ public synchronized int readBuffered(byte[] b, int off, int len, long timeout) t
} else if (len == 0) {
return 0;
} else {
+ checkClosed();
checkIoException();
int res = wait(readBuffer, timeout);
if (res >= 0) {
@@ -155,7 +155,7 @@ synchronized void flush() {
@Override
public synchronized void close() throws IOException {
- this.closed = true;
+ super.close(); // Use base class closed field
notifyAll();
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingPumpReader.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingPumpReader.java
index 63db6e5f5bf7f..b4748a1ea1d60 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingPumpReader.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingPumpReader.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2017, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -25,7 +25,7 @@ public class NonBlockingPumpReader extends NonBlockingReader {
private int count;
/** Main lock guarding all access */
- final ReentrantLock lock;
+ final ReentrantLock bufferLock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
@@ -33,8 +33,6 @@ public class NonBlockingPumpReader extends NonBlockingReader {
private final Writer writer;
- private boolean closed;
-
public NonBlockingPumpReader() {
this(DEFAULT_BUFFER_SIZE);
}
@@ -42,9 +40,9 @@ public NonBlockingPumpReader() {
public NonBlockingPumpReader(int bufferSize) {
this.buffer = new char[bufferSize];
this.writer = new NbpWriter();
- this.lock = new ReentrantLock();
- this.notEmpty = lock.newCondition();
- this.notFull = lock.newCondition();
+ this.bufferLock = new ReentrantLock();
+ this.notEmpty = bufferLock.newCondition();
+ this.notFull = bufferLock.newCondition();
}
public Writer getWriter() {
@@ -57,19 +55,18 @@ public boolean ready() {
}
public int available() {
- final ReentrantLock lock = this.lock;
- lock.lock();
+ bufferLock.lock();
try {
return count;
} finally {
- lock.unlock();
+ bufferLock.unlock();
}
}
@Override
protected int read(long timeout, boolean isPeek) throws IOException {
- final ReentrantLock lock = this.lock;
- lock.lock();
+ checkClosed();
+ bufferLock.lock();
try {
// Blocks until more input is available or the reader is closed.
if (!closed && count == 0) {
@@ -101,7 +98,7 @@ protected int read(long timeout, boolean isPeek) throws IOException {
}
}
} finally {
- lock.unlock();
+ bufferLock.unlock();
}
}
@@ -114,8 +111,7 @@ public int readBuffered(char[] b, int off, int len, long timeout) throws IOExcep
} else if (len == 0) {
return 0;
} else {
- final ReentrantLock lock = this.lock;
- lock.lock();
+ bufferLock.lock();
try {
if (!closed && count == 0) {
try {
@@ -147,15 +143,14 @@ public int readBuffered(char[] b, int off, int len, long timeout) throws IOExcep
return r;
}
} finally {
- lock.unlock();
+ bufferLock.unlock();
}
}
}
void write(char[] cbuf, int off, int len) throws IOException {
if (len > 0) {
- final ReentrantLock lock = this.lock;
- lock.lock();
+ bufferLock.lock();
try {
while (len > 0) {
// Blocks until there is new space available for buffering or the
@@ -181,21 +176,20 @@ void write(char[] cbuf, int off, int len) throws IOException {
notEmpty.signal();
}
} finally {
- lock.unlock();
+ bufferLock.unlock();
}
}
}
@Override
public void close() throws IOException {
- final ReentrantLock lock = this.lock;
- lock.lock();
+ bufferLock.lock();
try {
- this.closed = true;
+ super.close(); // Use base class closed field
this.notEmpty.signalAll();
this.notFull.signalAll();
} finally {
- lock.unlock();
+ bufferLock.unlock();
}
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingReader.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingReader.java
index 460f548b5069f..8522512ef9d60 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingReader.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingReader.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -10,18 +10,170 @@
import java.io.IOException;
import java.io.Reader;
+//import java.util.logging.Level;
+//import java.util.logging.Logger;
+
+import static jdk.internal.org.jline.terminal.TerminalBuilder.PROP_CLOSE_MODE;
/**
- * Non blocking reader
+ * A reader that provides non-blocking read operations.
+ *
+ *
+ * The NonBlockingReader class extends the standard Reader class to provide
+ * non-blocking read operations. Unlike standard readers, which block until
+ * data is available or the end of the stream is reached, non-blocking readers
+ * can be configured to return immediately or after a specified timeout if no
+ * data is available.
+ *
+ *
+ *
+ * This class is particularly useful for terminal applications that need to
+ * perform other tasks while waiting for user input, or that need to implement
+ * features like input timeouts or polling.
+ *
+ *
+ *
+ * The class defines two special return values:
+ *
+ *
+ * {@link #EOF} (-1) - Indicates that the end of the stream has been reached
+ * {@link #READ_EXPIRED} (-2) - Indicates that the read operation timed out
+ *
+ *
+ *
+ * Implementations of this class typically use a separate thread to handle
+ * blocking I/O operations, allowing the main thread to continue execution.
+ * The {@link #shutdown()} method can be used to terminate this background
+ * thread when it is no longer needed.
+ *
*/
public abstract class NonBlockingReader extends Reader {
+
+ /**
+ * Default constructor.
+ * Initializes close mode based on the current value of the system property.
+ */
+ public NonBlockingReader() {
+ this.closeMode = parseCloseMode();
+ }
+
+ /**
+ * Parses the close mode from system properties.
+ */
+ private static CloseMode parseCloseMode() {
+ String mode = System.getProperty(PROP_CLOSE_MODE);
+ if (mode != null) {
+ if ("strict".equalsIgnoreCase(mode)) {
+ return CloseMode.STRICT;
+ } else if ("warn".equalsIgnoreCase(mode)) {
+ return CloseMode.WARN;
+ } else if ("lenient".equalsIgnoreCase(mode)) {
+ return CloseMode.LENIENT;
+ }
+ }
+
+ // Default: strict for v4
+ return CloseMode.STRICT;
+ }
+
public static final int EOF = -1;
public static final int READ_EXPIRED = -2;
+// private static final Logger LOG = Logger.getLogger(NonBlockingReader.class.getName());
+
+ /**
+ * Flag indicating whether this reader has been closed.
+ * Marked as volatile to ensure visibility across threads.
+ */
+ protected volatile boolean closed = false;
+
+ /**
+ * Flag to track if a warning has been logged for this reader.
+ * Used to avoid log spam in warn mode.
+ */
+ private boolean warningLogged = false;
+
/**
- * Shuts down the thread that is handling blocking I/O. Note that if the
- * thread is currently blocked waiting for I/O it will not actually
- * shut down until the I/O is received.
+ * Close mode for this reader.
+ * Determined at construction time from the system property.
+ */
+ private final CloseMode closeMode;
+
+ /**
+ * Enum representing the close mode behavior.
+ */
+ private enum CloseMode {
+ /** Throw ClosedException when accessing closed streams */
+ STRICT,
+ /** Log warning when accessing closed streams */
+ WARN,
+ /** Silently allow accessing closed streams */
+ LENIENT
+ }
+
+ /**
+ * Checks if this reader has been closed.
+ *
+ * In JLine 4.x, strict mode is enabled by default: when a closed reader is accessed,
+ * it throws a {@code ClosedException}. This ensures proper resource management and
+ * prevents use-after-close bugs.
+ *
+ *
+ * The behavior can be controlled via the system property
+ * {@link org.jline.terminal.TerminalBuilder#PROP_CLOSE_MODE PROP_CLOSE_MODE}:
+ *
+ *
+ * {@code "strict"} - Throw {@code ClosedException} (default in JLine 4.x)
+ * {@code "warn"} - Log a warning but continue (default in JLine 3.x)
+ * {@code "lenient"} - Silently allow access (no warning, no exception)
+ *
+ *
+ * @throws ClosedException if this reader has been closed and strict mode is enabled (default)
+ */
+ protected void checkClosed() throws IOException {
+ if (closed) {
+ switch (closeMode) {
+ case STRICT:
+ throw new ClosedException();
+ case WARN:
+ // Log warning only once per reader instance to avoid log spam
+ if (!warningLogged) {
+// LOG.log(
+// Level.WARNING,
+// "Accessing a closed reader. "
+// + "This may indicate a resource management issue. "
+// + "Set -D" + PROP_CLOSE_MODE + "=strict to make this an error.",
+// new Throwable("Stack trace"));
+ warningLogged = true;
+ }
+ break;
+ case LENIENT:
+ // Silently allow access
+ break;
+ }
+ }
+ }
+
+ /**
+ * Shuts down the thread that is handling blocking I/O.
+ *
+ *
+ * This method terminates the background thread that is used to handle
+ * blocking I/O operations. This allows the application to clean up resources
+ * and prevent thread leaks when the reader is no longer needed.
+ *
+ *
+ *
+ * Note that if the thread is currently blocked waiting for I/O, it will not
+ * actually shut down until the I/O is received or the thread is interrupted.
+ * In some implementations, this method may interrupt the thread to force it
+ * to shut down immediately.
+ *
+ *
+ *
+ * After calling this method, the reader should not be used anymore, as
+ * subsequent read operations may fail or block indefinitely.
+ *
*/
public void shutdown() {}
@@ -108,4 +260,23 @@ public int available() {
* @throws IOException if anything wrong happens
*/
protected abstract int read(long timeout, boolean isPeek) throws IOException;
+
+ /**
+ * Closes this reader and marks it as closed.
+ *
+ * Subsequent read operations behavior depends on the
+ * {@link org.jline.terminal.TerminalBuilder#PROP_CLOSE_MODE PROP_CLOSE_MODE} setting:
+ *
+ *
+ * {@code "strict"} - Throw {@link ClosedException} (default in JLine 4.x)
+ * {@code "warn"} - Log a warning but continue (default in JLine 3.x)
+ * {@code "lenient"} - Silently allow access
+ *
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public void close() throws IOException {
+ closed = true;
+ }
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingReaderImpl.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingReaderImpl.java
index 5aaabd1d3c0d0..f64c401f27e94 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingReaderImpl.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingReaderImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -26,7 +26,6 @@
* the thread that handles blocking I/O.
*
* @since 2.7
- * @author Scott C. Gray <scottgray1@gmail.com>
*/
public class NonBlockingReaderImpl extends NonBlockingReader {
public static final int READ_EXPIRED = -2;
@@ -79,6 +78,7 @@ public void close() throws IOException {
* The underlying input stream is closed first. This means that if the
* I/O thread was blocked waiting on input, it will be woken for us.
*/
+ super.close(); // Mark as closed in base class
in.close();
shutdown();
}
@@ -127,6 +127,9 @@ public int readBuffered(char[] b, int off, int len, long timeout) throws IOExcep
* read timed out.
*/
protected synchronized int read(long timeout, boolean isPeek) throws IOException {
+ // Check if this reader has been closed
+ checkClosed();
+
/*
* If the thread hit an IOException, we report it.
*/
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/OSUtils.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/OSUtils.java
index 7fdfb9a028fc9..98c9eea753d94 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/OSUtils.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/OSUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -10,8 +10,48 @@
import java.io.File;
+/**
+ * Utility class for operating system detection and OS-specific operations.
+ *
+ *
+ * The OSUtils class provides constants and methods for detecting the current operating
+ * system and performing OS-specific operations. It helps JLine components adapt their
+ * behavior based on the platform they're running on, which is particularly important
+ * for terminal handling where different operating systems have different terminal
+ * implementations and capabilities.
+ *
+ *
+ *
+ * This class includes constants for detecting common operating systems:
+ *
+ *
+ * Windows - Including detection of Cygwin and MSYS environments
+ * Linux
+ * macOS (OSX)
+ * AIX
+ * Other Unix-like systems
+ *
+ *
+ *
+ * It also provides utility methods for working with file paths, environment variables,
+ * and other OS-specific features that affect terminal behavior.
+ *
+ *
+ *
+ * This class is used throughout JLine to ensure correct behavior across different
+ * platforms, particularly for terminal detection, path handling, and native library
+ * loading.
+ *
+ */
public class OSUtils {
+ /**
+ * Private constructor to prevent instantiation.
+ */
+ private OSUtils() {
+ // Utility class
+ }
+
public static final boolean IS_LINUX =
System.getProperty("os.name").toLowerCase().contains("linux");
@@ -27,15 +67,7 @@ public class OSUtils {
public static final boolean IS_CYGWIN =
IS_WINDOWS && System.getenv("PWD") != null && System.getenv("PWD").startsWith("/");
- @Deprecated
- public static final boolean IS_MINGW = IS_WINDOWS
- && System.getenv("MSYSTEM") != null
- && System.getenv("MSYSTEM").startsWith("MINGW");
-
- public static final boolean IS_MSYSTEM = IS_WINDOWS
- && System.getenv("MSYSTEM") != null
- && (System.getenv("MSYSTEM").startsWith("MINGW")
- || System.getenv("MSYSTEM").equals("MSYS"));
+ public static final boolean IS_MSYSTEM = IS_WINDOWS && System.getenv("MSYSTEM") != null;
public static final boolean IS_WSL = System.getenv("WSL_DISTRO_NAME") != null;
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/PumpReader.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/PumpReader.java
index 12e9795c6f51f..abb2e98cb14fd 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/PumpReader.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/PumpReader.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2017, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -18,6 +18,37 @@
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
+/**
+ * A reader implementation with an associated writer for buffered character transfer.
+ *
+ *
+ * The PumpReader class provides a Reader implementation with an associated Writer
+ * that allows characters to be written to the writer and then read from the reader.
+ * This creates a character pipe or pump that can be used to transfer character data
+ * between different components.
+ *
+ *
+ *
+ * This class is particularly useful for:
+ *
+ *
+ * Creating character streams for testing without actual I/O
+ * Buffering characters between producer and consumer threads
+ * Implementing character-based pipes with flow control
+ * Simulating input for terminal emulation
+ *
+ *
+ *
+ * The PumpReader maintains internal buffers for reading and writing, with both buffers
+ * backed by the same array. It provides methods for reading characters with optional
+ * timeouts and for checking the availability of characters without blocking.
+ *
+ *
+ *
+ * This class is used in JLine for various purposes, including implementing non-blocking
+ * readers and for testing terminal input handling without actual terminal devices.
+ *
+ */
public class PumpReader extends Reader {
private static final int EOF = -1;
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/ShutdownHooks.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/ShutdownHooks.java
index d5456df03d36d..99cd1d310ceac 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/ShutdownHooks.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/ShutdownHooks.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -15,14 +15,72 @@
/**
* Manages the JLine shutdown-hook thread and tasks to execute on shutdown.
*
- * @author Jason Dillon
+ *
+ * The ShutdownHooks class provides a centralized mechanism for registering tasks
+ * that should be executed when the JVM shuts down. It manages a single shutdown
+ * hook thread that executes all registered tasks in the reverse order of their
+ * registration.
+ *
+ *
+ *
+ * This class is particularly useful for terminal applications that need to perform
+ * cleanup operations when the application is terminated, such as restoring the
+ * terminal to its original state, closing open files, or releasing other resources.
+ *
+ *
+ *
+ * Tasks are registered using the {@link #add(Task)} method and can be removed using
+ * the {@link #remove(Task)} method. All tasks must implement the {@link Task} interface,
+ * which defines a single {@link Task#run()} method that is called when the JVM shuts down.
+ *
+ *
+ * Example usage:
+ *
+ * // Create a task to restore the terminal on shutdown
+ * ShutdownHooks.Task task = ShutdownHooks.add(() -> {
+ * terminal.setAttributes(originalAttributes);
+ * terminal.close();
+ * });
+ *
+ * // Later, if the task is no longer needed
+ * ShutdownHooks.remove(task);
+ *
+ *
* @since 2.7
*/
public final class ShutdownHooks {
+
+ /**
+ * Private constructor to prevent instantiation.
+ */
+ private ShutdownHooks() {
+ // Utility class
+ }
+
private static final List tasks = new ArrayList<>();
private static Thread hook;
+ /**
+ * Adds a task to be executed when the JVM shuts down.
+ *
+ *
+ * This method registers a task to be executed when the JVM shuts down. Tasks are
+ * executed in the reverse order of their registration, so the most recently added
+ * task will be executed first.
+ *
+ *
+ *
+ * If this is the first task to be added, a shutdown hook thread will be created
+ * and registered with the JVM. This thread will execute all registered tasks when
+ * the JVM shuts down.
+ *
+ *
+ * @param the type of the task
+ * @param task the task to be executed on shutdown
+ * @return the task that was added (for method chaining)
+ * @throws NullPointerException if the task is null
+ */
public static synchronized T add(final T task) {
Objects.requireNonNull(task);
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Signals.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Signals.java
index 9b25a80473573..ae8203ac9116f 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Signals.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Signals.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2020, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -14,9 +14,27 @@
import java.util.Objects;
/**
- * Signals helpers.
+ * Signal handling utilities for terminal applications.
+ *
+ *
+ * The Signals class provides utilities for registering and handling system signals
+ * in a platform-independent way. It allows terminal applications to respond to
+ * signals such as SIGINT (Ctrl+C), SIGTSTP (Ctrl+Z), and others, without having
+ * to use platform-specific code.
+ *
+ *
+ *
+ * This class uses reflection to access the underlying signal handling mechanisms
+ * of the JVM, which may vary depending on the platform and JVM implementation.
+ * It provides a consistent API for signal handling across different environments.
+ *
+ *
+ *
+ * Signal handling is particularly important for terminal applications that need
+ * to respond to user interrupts or that need to perform cleanup operations when
+ * the application is terminated.
+ *
*
- * @author Guillaume Nodet
* @since 3.0
*/
public final class Signals {
@@ -24,11 +42,41 @@ public final class Signals {
private Signals() {}
/**
+ * Registers a handler for the specified signal.
+ *
+ *
+ * This method registers a handler for the specified signal. The handler will be
+ * called when the signal is received. The method returns an object that can be
+ * used to unregister the handler later.
+ *
+ *
+ *
+ * Signal names are platform-dependent, but common signals include:
+ *
+ *
+ * INT - Interrupt signal (typically Ctrl+C)
+ * TERM - Termination signal
+ * HUP - Hangup signal
+ * CONT - Continue signal
+ * STOP - Stop signal (typically Ctrl+Z)
+ * WINCH - Window change signal
+ *
+ *
+ * Example usage:
+ *
+ * Object handle = Signals.register("INT", () -> {
+ * System.out.println("Caught SIGINT");
+ * // Perform cleanup
+ * });
*
- * @param name the signal, CONT, STOP, etc...
- * @param handler the callback to run
+ * // Later, when no longer needed
+ * Signals.unregister("INT", handle);
+ *
*
- * @return an object that needs to be passed to the {@link #unregister(String, Object)}
+ * @param name the signal name (e.g., "INT", "TERM", "HUP")
+ * @param handler the callback to run when the signal is received
+ * @return an object that can be used to unregister the handler
+ * @see #unregister(String, Object)
* method to unregister the handler
*/
public static Object register(String name, Runnable handler) {
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Status.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Status.java
index d0ea5ef8962f5..d2f99828e2020 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Status.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Status.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2019, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -19,6 +19,42 @@
import jdk.internal.org.jline.terminal.impl.AbstractTerminal;
import jdk.internal.org.jline.utils.InfoCmp.Capability;
+/**
+ * Manages a status bar at the bottom of the terminal.
+ *
+ *
+ * The Status class provides functionality for displaying and managing a status bar
+ * at the bottom of the terminal. It allows applications to show persistent status
+ * information to the user while still using the rest of the terminal for normal
+ * input and output.
+ *
+ *
+ *
+ * Key features include:
+ *
+ *
+ * Support for multiple status lines
+ * Styled text with ANSI colors and attributes
+ * Automatic resizing based on terminal dimensions
+ * Optional border between main display and status area
+ * Ability to temporarily suspend the status display
+ *
+ *
+ *
+ * The status bar is particularly useful for displaying persistent information such as:
+ *
+ *
+ * Application mode or state
+ * Current file or context
+ * Key bindings or available commands
+ * Error messages or notifications
+ *
+ *
+ *
+ * This class is used by various JLine components to provide status information
+ * to the user without disrupting the main terminal interaction.
+ *
+ */
public class Status {
protected final Terminal terminal;
@@ -63,10 +99,12 @@ private boolean isValid(Size size) {
}
public void close() {
- terminal.puts(Capability.save_cursor);
- terminal.puts(Capability.change_scroll_region, 0, display.rows - 1);
- terminal.puts(Capability.restore_cursor);
- terminal.flush();
+ if (supported) {
+ terminal.puts(Capability.save_cursor);
+ terminal.puts(Capability.change_scroll_region, 0, display.rows - 1);
+ terminal.puts(Capability.restore_cursor);
+ terminal.flush();
+ }
}
public void setBorder(boolean border) {
@@ -78,7 +116,58 @@ public void resize() {
}
public void resize(Size size) {
- display.resize(size.getRows(), size.getColumns());
+ if (supported && isValid(size)) {
+ int oldRows = display.rows;
+ int oldColumns = display.columns;
+
+ display.resize(size.getRows(), size.getColumns());
+
+ // Only process if the character grid size actually changed
+ if (display.rows != oldRows || display.columns != oldColumns) {
+ int oldScrollRegion = scrollRegion;
+ display.reset();
+ int newRows = display.rows;
+
+ // Compute the scroll region that update() will expect, accounting
+ // for the current status lines and border. This prevents update()
+ // from entering the "growing" branch and scrolling content up
+ // unnecessarily โ the status bar was already present before resize.
+ int effectiveLines = size(lines);
+ scrollRegion = Math.max(0, newRows - 1 - effectiveLines);
+
+ // Use a single save/restore to preserve cursor position across
+ // both cleanup and scroll region changes. change_scroll_region
+ // moves cursor to (0,0) on most terminals (DECSTBM spec).
+ terminal.puts(Capability.save_cursor);
+
+ // Clean up old status bar remnants:
+ // - When width decreases, old padded status text wraps to extra
+ // lines above the status area during terminal reflow.
+ // - When height increases, old status lines remain at old position.
+ int clearStart = scrollRegion + 1;
+ if (newRows > oldRows && oldScrollRegion < oldRows - 1) {
+ clearStart = Math.min(clearStart, oldScrollRegion + 1);
+ }
+ if (effectiveLines > 0) {
+ // Account for wrapped status lines when width decreased
+ if (display.columns < oldColumns) {
+ int wrappedPerLine = (oldColumns + display.columns - 1) / display.columns;
+ int extraRows = (wrappedPerLine - 1) * effectiveLines;
+ clearStart = Math.max(0, clearStart - extraRows);
+ }
+ for (int row = clearStart; row < newRows; row++) {
+ terminal.puts(Capability.cursor_address, row, 0);
+ terminal.puts(Capability.clr_eol);
+ }
+ }
+
+ // Set the scroll region to match what update() expects.
+ // The terminal emulator resets it to full screen on resize,
+ // so we must re-establish the restricted region here.
+ terminal.puts(Capability.change_scroll_region, 0, scrollRegion);
+ terminal.puts(Capability.restore_cursor);
+ }
+ }
}
public void reset() {
@@ -130,15 +219,15 @@ public void update(List lines, boolean flush) {
// trim or complete lines to the full width
for (int i = 0; i < lines.size(); i++) {
AttributedString str = lines.get(i);
- if (str.columnLength() > columns) {
+ if (str.columnLength(terminal) > columns) {
str = new AttributedStringBuilder(columns)
- .append(lines.get(i).columnSubSequence(0, columns - ellipsis.columnLength()))
+ .append(lines.get(i).columnSubSequence(0, columns - ellipsis.columnLength(terminal), terminal))
.append(ellipsis)
.toAttributedString();
- } else if (str.columnLength() < columns) {
+ } else if (str.columnLength(terminal) < columns) {
str = new AttributedStringBuilder(columns)
.append(str)
- .append(' ', columns - str.columnLength())
+ .append(' ', columns - str.columnLength(terminal))
.toAttributedString();
}
lines.set(i, str);
@@ -191,7 +280,7 @@ public void update(List lines, boolean flush) {
private AttributedString getBorderString(int columns) {
if (borderString == null || borderString.length() != columns) {
- char borderChar = '\u2700';
+ char borderChar = '\u2500';
AttributedStringBuilder bb = new AttributedStringBuilder();
for (int i = 0; i < columns; i++) {
bb.append(borderChar);
@@ -253,6 +342,9 @@ public void update(List newLines, int targetCursorPos, boolean
super.update(newLines, targetCursorPos, flush);
if (cursorPos != -1) {
terminal.puts(Capability.restore_cursor);
+ if (flush) {
+ terminal.flush();
+ }
}
}
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/StyleResolver.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/StyleResolver.java
index 29d87cbc3c85f..66445d95ddbed 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/StyleResolver.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/StyleResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -19,7 +19,35 @@
// TODO: document style specification
/**
- * Resolves named (or source-referenced) {@link AttributedStyle}.
+ * Resolves named styles and style expressions into AttributedStyle objects.
+ *
+ *
+ * The StyleResolver class provides functionality for resolving style specifications
+ * into AttributedStyle objects. It supports a rich expression language for defining
+ * styles, including named colors, RGB values, and various text attributes.
+ *
+ *
+ *
+ * Style specifications can include:
+ *
+ *
+ * Named colors (e.g., "red", "blue", "bright-green")
+ * RGB color values (e.g., "#ff0000", "rgb:ff/00/00")
+ * Text attributes (e.g., "bold", "underline", "italic")
+ * Compound styles with foreground and background (e.g., "red,bold,bg:blue")
+ * References to styles defined elsewhere (e.g., "${named.style}")
+ *
+ *
+ *
+ * This class is used throughout JLine for resolving style specifications in configuration
+ * files, command-line options, and programmatic style definitions. It provides a consistent
+ * way to define and apply styles across different components.
+ *
+ *
+ *
+ * The resolver can be configured with a source function that looks up named styles,
+ * allowing for hierarchical style definitions and style inheritance.
+ *
*
* @since 3.6
*/
@@ -308,7 +336,7 @@ private AttributedStyle applyNamed(final AttributedStyle style, final String nam
}
}
- // TODO: consider simplify and always using StyleColor, for now for compat with other bits leaving syntax complexity
+ // TODO: consider simplify color handling, for now for compat with other bits leaving syntax complexity
/**
* Apply color styles specification.
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Timeout.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Timeout.java
index 1e3091e19a117..5d2c91aa3d3d3 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Timeout.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Timeout.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2018, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -9,7 +9,42 @@
package jdk.internal.org.jline.utils;
/**
- * Helper class ti use during I/O operations with an eventual timeout.
+ * Helper class for managing timeouts during I/O operations.
+ *
+ *
+ * The Timeout class provides a simple mechanism for tracking timeouts during I/O
+ * operations. It helps with implementing operations that should complete within
+ * a specified time limit, such as non-blocking reads with timeouts.
+ *
+ *
+ *
+ * This class supports both finite timeouts (specified in milliseconds) and infinite
+ * timeouts (indicated by zero or negative timeout values). It provides methods for
+ * starting the timeout countdown, checking if the timeout has expired, and calculating
+ * the remaining time.
+ *
+ *
+ *
+ * The class is designed to be used in scenarios where multiple I/O operations need
+ * to share a single timeout, ensuring that the total time for all operations does
+ * not exceed the specified limit.
+ *
+ *
+ * Example usage:
+ *
+ * // Create a timeout of 5 seconds
+ * Timeout timeout = new Timeout(5000);
+ *
+ * // Start the timeout countdown
+ * timeout.start();
+ *
+ * // Perform I/O operations, checking for timeout
+ * while (!timeout.isExpired() && !operationComplete()) {
+ * // Perform a partial operation with the remaining time
+ * long remaining = timeout.remaining();
+ * performPartialOperation(remaining);
+ * }
+ *
*/
public class Timeout {
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/WCWidth.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/WCWidth.java
index 7b0caf91c25c4..78bed72be6ffa 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/WCWidth.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/WCWidth.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -8,8 +8,66 @@
*/
package jdk.internal.org.jline.utils;
+import java.text.BreakIterator;
+
+import jdk.internal.org.jline.terminal.Terminal;
+import jdk.internal.org.jline.terminal.impl.AbstractTerminal;
+
+/**
+ * Utility class for determining the display width of Unicode characters.
+ *
+ *
+ * The WCWidth class provides methods for calculating the display width of Unicode
+ * characters in terminal environments. This is important for proper text alignment
+ * and cursor positioning, especially when dealing with wide characters (such as
+ * East Asian characters) and zero-width characters (such as combining marks).
+ *
+ *
+ *
+ * This implementation is based on Markus Kuhn's wcwidth implementation, which follows
+ * the Unicode Standard guidelines for character width. It categorizes characters as:
+ *
+ *
+ * Zero width (0) - Control characters, combining marks, format characters
+ * Single width (1) - Most Latin, Greek, Cyrillic, and other scripts
+ * Double width (2) - East Asian scripts (Chinese, Japanese, Korean)
+ * Ambiguous width (-1) - Characters with context-dependent width
+ *
+ *
+ *
+ * Tables are generated from Unicode 16.0 data files:
+ *
+ *
+ *
+ *
+ * This class is used throughout JLine for calculating string display widths,
+ * which is essential for proper terminal display formatting, cursor positioning,
+ * and text alignment.
+ *
+ */
public final class WCWidth {
+ /**
+ * Whether the JDK runtime supports grapheme cluster segmentation.
+ * JDK 21+ provides improved {@code BreakIterator} support (full UAX #29)
+ * and {@code Character.isEmoji()} for reliable emoji detection.
+ * When {@code true} and no terminal is provided, grapheme cluster-aware
+ * width calculation is used as a better default for application-level
+ * width queries.
+ */
+ static final boolean HAS_JDK_GRAPHEME_SUPPORT = Runtime.version().feature() >= 21;
+
+ /**
+ * Prevents instantiation of this utility class.
+ */
private WCWidth() {}
/* The following two functions define the column width of an ISO 10646
@@ -34,7 +92,9 @@ private WCWidth() {}
*
* - Spacing characters in the East Asian Wide (W) or East Asian
* Full-width (F) category as defined in Unicode Technical
- * Report #11 have a column width of 2.
+ * Report #11 have a column width of 2. This includes BMP
+ * characters with Emoji_Presentation=Yes, which are EAW=W
+ * in Unicode 16.0.
*
* - All remaining characters (including all printable
* ISO 8859-1 and WGL4 characters, Unicode control characters,
@@ -52,78 +112,274 @@ public static int wcwidth(int ucs) {
/* binary search in table of non-spacing characters */
if (bisearch(ucs, combining, combining.length - 1)) return 0;
- /* if we arrive here, ucs is not a combining or C0/C1 control character */
- return 1
- + ((ucs >= 0x1100
- && (ucs <= 0x115f
- || /* Hangul Jamo init. consonants */ ucs == 0x2329
- || ucs == 0x232a
- || (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs != 0x303f)
- || /* CJK ... Yi */ (ucs >= 0xac00 && ucs <= 0xd7a3)
- || /* Hangul Syllables */ (ucs >= 0xf900 && ucs <= 0xfaff)
- || /* CJK Compatibility Ideographs */ (ucs >= 0xfe10 && ucs <= 0xfe19)
- || /* 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
- : 0);
- }
-
- /* sorted list of non-overlapping intervals of non-spacing characters */
- /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
- static Interval[] combining = {
- new Interval(0x0300, 0x036F), new Interval(0x0483, 0x0486), new Interval(0x0488, 0x0489),
- new Interval(0x0591, 0x05BD), new Interval(0x05BF, 0x05BF), new Interval(0x05C1, 0x05C2),
- new Interval(0x05C4, 0x05C5), new Interval(0x05C7, 0x05C7), new Interval(0x0600, 0x0603),
- new Interval(0x0610, 0x0615), new Interval(0x064B, 0x065E), new Interval(0x0670, 0x0670),
- new Interval(0x06D6, 0x06E4), new Interval(0x06E7, 0x06E8), new Interval(0x06EA, 0x06ED),
- new Interval(0x070F, 0x070F), new Interval(0x0711, 0x0711), new Interval(0x0730, 0x074A),
- new Interval(0x07A6, 0x07B0), new Interval(0x07EB, 0x07F3), new Interval(0x0901, 0x0902),
- new Interval(0x093C, 0x093C), new Interval(0x0941, 0x0948), new Interval(0x094D, 0x094D),
- new Interval(0x0951, 0x0954), new Interval(0x0962, 0x0963), new Interval(0x0981, 0x0981),
- new Interval(0x09BC, 0x09BC), new Interval(0x09C1, 0x09C4), new Interval(0x09CD, 0x09CD),
- new Interval(0x09E2, 0x09E3), new Interval(0x0A01, 0x0A02), new Interval(0x0A3C, 0x0A3C),
- new Interval(0x0A41, 0x0A42), new Interval(0x0A47, 0x0A48), new Interval(0x0A4B, 0x0A4D),
- new Interval(0x0A70, 0x0A71), new Interval(0x0A81, 0x0A82), new Interval(0x0ABC, 0x0ABC),
- new Interval(0x0AC1, 0x0AC5), new Interval(0x0AC7, 0x0AC8), new Interval(0x0ACD, 0x0ACD),
- new Interval(0x0AE2, 0x0AE3), new Interval(0x0B01, 0x0B01), new Interval(0x0B3C, 0x0B3C),
- new Interval(0x0B3F, 0x0B3F), new Interval(0x0B41, 0x0B43), new Interval(0x0B4D, 0x0B4D),
- new Interval(0x0B56, 0x0B56), new Interval(0x0B82, 0x0B82), new Interval(0x0BC0, 0x0BC0),
- new Interval(0x0BCD, 0x0BCD), new Interval(0x0C3E, 0x0C40), new Interval(0x0C46, 0x0C48),
- new Interval(0x0C4A, 0x0C4D), new Interval(0x0C55, 0x0C56), new Interval(0x0CBC, 0x0CBC),
- new Interval(0x0CBF, 0x0CBF), new Interval(0x0CC6, 0x0CC6), new Interval(0x0CCC, 0x0CCD),
- new Interval(0x0CE2, 0x0CE3), new Interval(0x0D41, 0x0D43), new Interval(0x0D4D, 0x0D4D),
+ /* East Asian Wide (W) and Fullwidth (F) characters โ Unicode 16.0
+ * Also covers BMP Emoji_Presentation=Yes characters (U+231A, U+2615, etc.)
+ * which are EAW=W in Unicode 16.0.
+ * See https://unicode.org/Public/16.0.0/ucd/EastAsianWidth.txt */
+ if (bisearch(ucs, wide, wide.length - 1)) return 2;
+
+ /* if we arrive here, ucs is not a combining, control, emoji, or wide character */
+ return 1;
+ }
+
+ /* Sorted list of non-overlapping intervals of non-spacing characters.
+ * Generated from Unicode 16.0 UnicodeData.txt:
+ * categories Mn (Nonspacing Mark) + Me (Enclosing Mark) + Cf (Format)
+ * minus U+00AD (Soft Hyphen)
+ * plus U+1160-11FF (Hangul Jamo medial vowels and final consonants)
+ * plus U+200B (Zero Width Space)
+ * plus U+1F3FB-1F3FF (Emoji skin tone modifiers)
+ * 2367 codepoints in 369 intervals */
+ // @spotless:off
+ static final Interval[] combining = {
+ new Interval(0x0300, 0x036F), new Interval(0x0483, 0x0489), new Interval(0x0591, 0x05BD),
+ new Interval(0x05BF, 0x05BF), new Interval(0x05C1, 0x05C2), new Interval(0x05C4, 0x05C5),
+ new Interval(0x05C7, 0x05C7), new Interval(0x0600, 0x0605), new Interval(0x0610, 0x061A),
+ new Interval(0x061C, 0x061C), new Interval(0x064B, 0x065F), new Interval(0x0670, 0x0670),
+ new Interval(0x06D6, 0x06DD), new Interval(0x06DF, 0x06E4), new Interval(0x06E7, 0x06E8),
+ new Interval(0x06EA, 0x06ED), new Interval(0x070F, 0x070F), new Interval(0x0711, 0x0711),
+ new Interval(0x0730, 0x074A), new Interval(0x07A6, 0x07B0), new Interval(0x07EB, 0x07F3),
+ new Interval(0x07FD, 0x07FD), new Interval(0x0816, 0x0819), new Interval(0x081B, 0x0823),
+ new Interval(0x0825, 0x0827), new Interval(0x0829, 0x082D), new Interval(0x0859, 0x085B),
+ new Interval(0x0890, 0x0891), new Interval(0x0897, 0x089F), new Interval(0x08CA, 0x0902),
+ new Interval(0x093A, 0x093A), new Interval(0x093C, 0x093C), new Interval(0x0941, 0x0948),
+ new Interval(0x094D, 0x094D), new Interval(0x0951, 0x0957), new Interval(0x0962, 0x0963),
+ new Interval(0x0981, 0x0981), new Interval(0x09BC, 0x09BC), new Interval(0x09C1, 0x09C4),
+ new Interval(0x09CD, 0x09CD), new Interval(0x09E2, 0x09E3), new Interval(0x09FE, 0x09FE),
+ new Interval(0x0A01, 0x0A02), new Interval(0x0A3C, 0x0A3C), new Interval(0x0A41, 0x0A42),
+ new Interval(0x0A47, 0x0A48), new Interval(0x0A4B, 0x0A4D), new Interval(0x0A51, 0x0A51),
+ new Interval(0x0A70, 0x0A71), new Interval(0x0A75, 0x0A75), new Interval(0x0A81, 0x0A82),
+ new Interval(0x0ABC, 0x0ABC), new Interval(0x0AC1, 0x0AC5), new Interval(0x0AC7, 0x0AC8),
+ new Interval(0x0ACD, 0x0ACD), new Interval(0x0AE2, 0x0AE3), new Interval(0x0AFA, 0x0AFF),
+ new Interval(0x0B01, 0x0B01), new Interval(0x0B3C, 0x0B3C), new Interval(0x0B3F, 0x0B3F),
+ new Interval(0x0B41, 0x0B44), new Interval(0x0B4D, 0x0B4D), new Interval(0x0B55, 0x0B56),
+ new Interval(0x0B62, 0x0B63), new Interval(0x0B82, 0x0B82), new Interval(0x0BC0, 0x0BC0),
+ new Interval(0x0BCD, 0x0BCD), new Interval(0x0C00, 0x0C00), new Interval(0x0C04, 0x0C04),
+ new Interval(0x0C3C, 0x0C3C), new Interval(0x0C3E, 0x0C40), new Interval(0x0C46, 0x0C48),
+ new Interval(0x0C4A, 0x0C4D), new Interval(0x0C55, 0x0C56), new Interval(0x0C62, 0x0C63),
+ new Interval(0x0C81, 0x0C81), new Interval(0x0CBC, 0x0CBC), new Interval(0x0CBF, 0x0CBF),
+ new Interval(0x0CC6, 0x0CC6), new Interval(0x0CCC, 0x0CCD), new Interval(0x0CE2, 0x0CE3),
+ new Interval(0x0D00, 0x0D01), new Interval(0x0D3B, 0x0D3C), new Interval(0x0D41, 0x0D44),
+ new Interval(0x0D4D, 0x0D4D), new Interval(0x0D62, 0x0D63), new Interval(0x0D81, 0x0D81),
new Interval(0x0DCA, 0x0DCA), new Interval(0x0DD2, 0x0DD4), new Interval(0x0DD6, 0x0DD6),
new Interval(0x0E31, 0x0E31), new Interval(0x0E34, 0x0E3A), new Interval(0x0E47, 0x0E4E),
- new Interval(0x0EB1, 0x0EB1), new Interval(0x0EB4, 0x0EB9), new Interval(0x0EBB, 0x0EBC),
- new Interval(0x0EC8, 0x0ECD), new Interval(0x0F18, 0x0F19), new Interval(0x0F35, 0x0F35),
- new Interval(0x0F37, 0x0F37), new Interval(0x0F39, 0x0F39), new Interval(0x0F71, 0x0F7E),
- new Interval(0x0F80, 0x0F84), new Interval(0x0F86, 0x0F87), new Interval(0x0F90, 0x0F97),
- new Interval(0x0F99, 0x0FBC), new Interval(0x0FC6, 0x0FC6), new Interval(0x102D, 0x1030),
- new Interval(0x1032, 0x1032), new Interval(0x1036, 0x1037), new Interval(0x1039, 0x1039),
- new Interval(0x1058, 0x1059), new Interval(0x1160, 0x11FF), new Interval(0x135F, 0x135F),
- new Interval(0x1712, 0x1714), new Interval(0x1732, 0x1734), new Interval(0x1752, 0x1753),
- new Interval(0x1772, 0x1773), new Interval(0x17B4, 0x17B5), new Interval(0x17B7, 0x17BD),
- new Interval(0x17C6, 0x17C6), new Interval(0x17C9, 0x17D3), new Interval(0x17DD, 0x17DD),
- new Interval(0x180B, 0x180D), new Interval(0x18A9, 0x18A9), new Interval(0x1920, 0x1922),
+ new Interval(0x0EB1, 0x0EB1), new Interval(0x0EB4, 0x0EBC), new Interval(0x0EC8, 0x0ECE),
+ new Interval(0x0F18, 0x0F19), new Interval(0x0F35, 0x0F35), new Interval(0x0F37, 0x0F37),
+ new Interval(0x0F39, 0x0F39), new Interval(0x0F71, 0x0F7E), new Interval(0x0F80, 0x0F84),
+ new Interval(0x0F86, 0x0F87), new Interval(0x0F8D, 0x0F97), new Interval(0x0F99, 0x0FBC),
+ new Interval(0x0FC6, 0x0FC6), new Interval(0x102D, 0x1030), new Interval(0x1032, 0x1037),
+ new Interval(0x1039, 0x103A), new Interval(0x103D, 0x103E), new Interval(0x1058, 0x1059),
+ new Interval(0x105E, 0x1060), new Interval(0x1071, 0x1074), new Interval(0x1082, 0x1082),
+ new Interval(0x1085, 0x1086), new Interval(0x108D, 0x108D), new Interval(0x109D, 0x109D),
+ new Interval(0x1160, 0x11FF), new Interval(0x135D, 0x135F), new Interval(0x1712, 0x1714),
+ new Interval(0x1732, 0x1733), new Interval(0x1752, 0x1753), new Interval(0x1772, 0x1773),
+ new Interval(0x17B4, 0x17B5), new Interval(0x17B7, 0x17BD), new Interval(0x17C6, 0x17C6),
+ new Interval(0x17C9, 0x17D3), new Interval(0x17DD, 0x17DD), new Interval(0x180B, 0x180F),
+ new Interval(0x1885, 0x1886), new Interval(0x18A9, 0x18A9), new Interval(0x1920, 0x1922),
new Interval(0x1927, 0x1928), new Interval(0x1932, 0x1932), new Interval(0x1939, 0x193B),
- new Interval(0x1A17, 0x1A18), new Interval(0x1B00, 0x1B03), new Interval(0x1B34, 0x1B34),
+ new Interval(0x1A17, 0x1A18), new Interval(0x1A1B, 0x1A1B), new Interval(0x1A56, 0x1A56),
+ new Interval(0x1A58, 0x1A5E), new Interval(0x1A60, 0x1A60), new Interval(0x1A62, 0x1A62),
+ new Interval(0x1A65, 0x1A6C), new Interval(0x1A73, 0x1A7C), new Interval(0x1A7F, 0x1A7F),
+ new Interval(0x1AB0, 0x1ACE), new Interval(0x1B00, 0x1B03), new Interval(0x1B34, 0x1B34),
new Interval(0x1B36, 0x1B3A), new Interval(0x1B3C, 0x1B3C), new Interval(0x1B42, 0x1B42),
- new Interval(0x1B6B, 0x1B73), new Interval(0x1DC0, 0x1DCA), new Interval(0x1DFE, 0x1DFF),
- new Interval(0x200B, 0x200F), new Interval(0x202A, 0x202E), new Interval(0x2060, 0x2063),
- new Interval(0x206A, 0x206F), new Interval(0x20D0, 0x20EF), new Interval(0x302A, 0x302F),
- new Interval(0x3099, 0x309A), new Interval(0xA806, 0xA806), new Interval(0xA80B, 0xA80B),
- new Interval(0xA825, 0xA826), new Interval(0xFB1E, 0xFB1E), new Interval(0xFE00, 0xFE0F),
- new Interval(0xFE20, 0xFE23), new Interval(0xFEFF, 0xFEFF), new Interval(0xFFF9, 0xFFFB),
- new Interval(0x10A01, 0x10A03), new Interval(0x10A05, 0x10A06), new Interval(0x10A0C, 0x10A0F),
- new Interval(0x10A38, 0x10A3A), new Interval(0x10A3F, 0x10A3F), new Interval(0x1D167, 0x1D169),
+ new Interval(0x1B6B, 0x1B73), new Interval(0x1B80, 0x1B81), new Interval(0x1BA2, 0x1BA5),
+ new Interval(0x1BA8, 0x1BA9), new Interval(0x1BAB, 0x1BAD), new Interval(0x1BE6, 0x1BE6),
+ new Interval(0x1BE8, 0x1BE9), new Interval(0x1BED, 0x1BED), new Interval(0x1BEF, 0x1BF1),
+ new Interval(0x1C2C, 0x1C33), new Interval(0x1C36, 0x1C37), new Interval(0x1CD0, 0x1CD2),
+ new Interval(0x1CD4, 0x1CE0), new Interval(0x1CE2, 0x1CE8), new Interval(0x1CED, 0x1CED),
+ new Interval(0x1CF4, 0x1CF4), new Interval(0x1CF8, 0x1CF9), new Interval(0x1DC0, 0x1DFF),
+ new Interval(0x200B, 0x200F), new Interval(0x202A, 0x202E), new Interval(0x2060, 0x2064),
+ new Interval(0x2066, 0x206F), new Interval(0x20D0, 0x20F0), new Interval(0x2CEF, 0x2CF1),
+ new Interval(0x2D7F, 0x2D7F), new Interval(0x2DE0, 0x2DFF), new Interval(0x302A, 0x302D),
+ new Interval(0x3099, 0x309A), new Interval(0xA66F, 0xA672), new Interval(0xA674, 0xA67D),
+ new Interval(0xA69E, 0xA69F), new Interval(0xA6F0, 0xA6F1), new Interval(0xA802, 0xA802),
+ new Interval(0xA806, 0xA806), new Interval(0xA80B, 0xA80B), new Interval(0xA825, 0xA826),
+ new Interval(0xA82C, 0xA82C), new Interval(0xA8C4, 0xA8C5), new Interval(0xA8E0, 0xA8F1),
+ new Interval(0xA8FF, 0xA8FF), new Interval(0xA926, 0xA92D), new Interval(0xA947, 0xA951),
+ new Interval(0xA980, 0xA982), new Interval(0xA9B3, 0xA9B3), new Interval(0xA9B6, 0xA9B9),
+ new Interval(0xA9BC, 0xA9BD), new Interval(0xA9E5, 0xA9E5), new Interval(0xAA29, 0xAA2E),
+ new Interval(0xAA31, 0xAA32), new Interval(0xAA35, 0xAA36), new Interval(0xAA43, 0xAA43),
+ new Interval(0xAA4C, 0xAA4C), new Interval(0xAA7C, 0xAA7C), new Interval(0xAAB0, 0xAAB0),
+ new Interval(0xAAB2, 0xAAB4), new Interval(0xAAB7, 0xAAB8), new Interval(0xAABE, 0xAABF),
+ new Interval(0xAAC1, 0xAAC1), new Interval(0xAAEC, 0xAAED), new Interval(0xAAF6, 0xAAF6),
+ new Interval(0xABE5, 0xABE5), new Interval(0xABE8, 0xABE8), new Interval(0xABED, 0xABED),
+ new Interval(0xFB1E, 0xFB1E), new Interval(0xFE00, 0xFE0F), new Interval(0xFE20, 0xFE2F),
+ new Interval(0xFEFF, 0xFEFF), new Interval(0xFFF9, 0xFFFB), new Interval(0x101FD, 0x101FD),
+ new Interval(0x102E0, 0x102E0), new Interval(0x10376, 0x1037A), new Interval(0x10A01, 0x10A03),
+ new Interval(0x10A05, 0x10A06), new Interval(0x10A0C, 0x10A0F), new Interval(0x10A38, 0x10A3A),
+ new Interval(0x10A3F, 0x10A3F), new Interval(0x10AE5, 0x10AE6), new Interval(0x10D24, 0x10D27),
+ new Interval(0x10D69, 0x10D6D), new Interval(0x10EAB, 0x10EAC), new Interval(0x10EFC, 0x10EFF),
+ new Interval(0x10F46, 0x10F50), new Interval(0x10F82, 0x10F85), new Interval(0x11001, 0x11001),
+ new Interval(0x11038, 0x11046), new Interval(0x11070, 0x11070), new Interval(0x11073, 0x11074),
+ new Interval(0x1107F, 0x11081), new Interval(0x110B3, 0x110B6), new Interval(0x110B9, 0x110BA),
+ new Interval(0x110BD, 0x110BD), new Interval(0x110C2, 0x110C2), new Interval(0x110CD, 0x110CD),
+ new Interval(0x11100, 0x11102), new Interval(0x11127, 0x1112B), new Interval(0x1112D, 0x11134),
+ new Interval(0x11173, 0x11173), new Interval(0x11180, 0x11181), new Interval(0x111B6, 0x111BE),
+ new Interval(0x111C9, 0x111CC), new Interval(0x111CF, 0x111CF), new Interval(0x1122F, 0x11231),
+ new Interval(0x11234, 0x11234), new Interval(0x11236, 0x11237), new Interval(0x1123E, 0x1123E),
+ new Interval(0x11241, 0x11241), new Interval(0x112DF, 0x112DF), new Interval(0x112E3, 0x112EA),
+ new Interval(0x11300, 0x11301), new Interval(0x1133B, 0x1133C), new Interval(0x11340, 0x11340),
+ new Interval(0x11366, 0x1136C), new Interval(0x11370, 0x11374), new Interval(0x113BB, 0x113C0),
+ new Interval(0x113CE, 0x113CE), new Interval(0x113D0, 0x113D0), new Interval(0x113D2, 0x113D2),
+ new Interval(0x113E1, 0x113E2), new Interval(0x11438, 0x1143F), new Interval(0x11442, 0x11444),
+ new Interval(0x11446, 0x11446), new Interval(0x1145E, 0x1145E), new Interval(0x114B3, 0x114B8),
+ new Interval(0x114BA, 0x114BA), new Interval(0x114BF, 0x114C0), new Interval(0x114C2, 0x114C3),
+ new Interval(0x115B2, 0x115B5), new Interval(0x115BC, 0x115BD), new Interval(0x115BF, 0x115C0),
+ new Interval(0x115DC, 0x115DD), new Interval(0x11633, 0x1163A), new Interval(0x1163D, 0x1163D),
+ new Interval(0x1163F, 0x11640), new Interval(0x116AB, 0x116AB), new Interval(0x116AD, 0x116AD),
+ new Interval(0x116B0, 0x116B5), new Interval(0x116B7, 0x116B7), new Interval(0x1171D, 0x1171D),
+ new Interval(0x1171F, 0x1171F), new Interval(0x11722, 0x11725), new Interval(0x11727, 0x1172B),
+ new Interval(0x1182F, 0x11837), new Interval(0x11839, 0x1183A), new Interval(0x1193B, 0x1193C),
+ new Interval(0x1193E, 0x1193E), new Interval(0x11943, 0x11943), new Interval(0x119D4, 0x119D7),
+ new Interval(0x119DA, 0x119DB), new Interval(0x119E0, 0x119E0), new Interval(0x11A01, 0x11A0A),
+ new Interval(0x11A33, 0x11A38), new Interval(0x11A3B, 0x11A3E), new Interval(0x11A47, 0x11A47),
+ new Interval(0x11A51, 0x11A56), new Interval(0x11A59, 0x11A5B), new Interval(0x11A8A, 0x11A96),
+ new Interval(0x11A98, 0x11A99), new Interval(0x11C30, 0x11C36), new Interval(0x11C38, 0x11C3D),
+ new Interval(0x11C3F, 0x11C3F), new Interval(0x11C92, 0x11CA7), new Interval(0x11CAA, 0x11CB0),
+ new Interval(0x11CB2, 0x11CB3), new Interval(0x11CB5, 0x11CB6), new Interval(0x11D31, 0x11D36),
+ new Interval(0x11D3A, 0x11D3A), new Interval(0x11D3C, 0x11D3D), new Interval(0x11D3F, 0x11D45),
+ new Interval(0x11D47, 0x11D47), new Interval(0x11D90, 0x11D91), new Interval(0x11D95, 0x11D95),
+ new Interval(0x11D97, 0x11D97), new Interval(0x11EF3, 0x11EF4), new Interval(0x11F00, 0x11F01),
+ new Interval(0x11F36, 0x11F3A), new Interval(0x11F40, 0x11F40), new Interval(0x11F42, 0x11F42),
+ new Interval(0x11F5A, 0x11F5A), new Interval(0x13430, 0x13440), new Interval(0x13447, 0x13455),
+ new Interval(0x1611E, 0x16129), new Interval(0x1612D, 0x1612F), new Interval(0x16AF0, 0x16AF4),
+ new Interval(0x16B30, 0x16B36), new Interval(0x16F4F, 0x16F4F), new Interval(0x16F8F, 0x16F92),
+ new Interval(0x16FE4, 0x16FE4), new Interval(0x1BC9D, 0x1BC9E), new Interval(0x1BCA0, 0x1BCA3),
+ new Interval(0x1CF00, 0x1CF2D), new Interval(0x1CF30, 0x1CF46), new Interval(0x1D167, 0x1D169),
new Interval(0x1D173, 0x1D182), new Interval(0x1D185, 0x1D18B), new Interval(0x1D1AA, 0x1D1AD),
- new Interval(0x1D242, 0x1D244), new Interval(0x1F3FB, 0x1F3FF), new Interval(0xE0001, 0xE0001),
- new Interval(0xE0020, 0xE007F), new Interval(0xE0100, 0xE01EF)
+ new Interval(0x1D242, 0x1D244), new Interval(0x1DA00, 0x1DA36), new Interval(0x1DA3B, 0x1DA6C),
+ new Interval(0x1DA75, 0x1DA75), new Interval(0x1DA84, 0x1DA84), new Interval(0x1DA9B, 0x1DA9F),
+ new Interval(0x1DAA1, 0x1DAAF), new Interval(0x1E000, 0x1E006), new Interval(0x1E008, 0x1E018),
+ new Interval(0x1E01B, 0x1E021), new Interval(0x1E023, 0x1E024), new Interval(0x1E026, 0x1E02A),
+ new Interval(0x1E08F, 0x1E08F), new Interval(0x1E130, 0x1E136), new Interval(0x1E2AE, 0x1E2AE),
+ new Interval(0x1E2EC, 0x1E2EF), new Interval(0x1E4EC, 0x1E4EF), new Interval(0x1E5EE, 0x1E5EF),
+ new Interval(0x1E8D0, 0x1E8D6), new Interval(0x1E944, 0x1E94A), new Interval(0x1F3FB, 0x1F3FF),
+ new Interval(0xE0001, 0xE0001), new Interval(0xE0020, 0xE007F), new Interval(0xE0100, 0xE01EF)
+ };
+ // @spotless:on
+
+ /* Sorted list of non-overlapping intervals of East Asian Wide (W) and
+ * Fullwidth (F) characters. Generated from Unicode 16.0 EastAsianWidth.txt.
+ * Used for binary search to determine width-2 characters. */
+ // @spotless:off
+ static final Interval[] wide = {
+ new Interval(0x1100, 0x115F), /* Hangul Jamo */
+ new Interval(0x231A, 0x231B), /* Watch, Hourglass */
+ new Interval(0x2329, 0x232A), /* Angle brackets */
+ new Interval(0x23E9, 0x23EC), /* Playback symbols */
+ new Interval(0x23F0, 0x23F0), /* Alarm clock */
+ new Interval(0x23F3, 0x23F3), /* Hourglass flowing */
+ new Interval(0x25FD, 0x25FE), /* Medium small squares */
+ new Interval(0x2614, 0x2615), /* Umbrella, Hot beverage */
+ new Interval(0x2630, 0x2637), /* Trigrams */
+ new Interval(0x2648, 0x2653), /* Zodiac signs */
+ new Interval(0x267F, 0x267F), /* Wheelchair */
+ new Interval(0x268A, 0x268F), /* Yijing mono/digrams */
+ new Interval(0x2693, 0x2693), /* Anchor */
+ new Interval(0x26A1, 0x26A1), /* High voltage */
+ new Interval(0x26AA, 0x26AB), /* Circles */
+ new Interval(0x26BD, 0x26BE), /* Soccer, Baseball */
+ new Interval(0x26C4, 0x26C5), /* Snowman, Sun behind cloud */
+ new Interval(0x26CE, 0x26CE), /* Ophiuchus */
+ new Interval(0x26D4, 0x26D4), /* No entry */
+ new Interval(0x26EA, 0x26EA), /* Church */
+ new Interval(0x26F2, 0x26F3), /* Fountain, Golf */
+ new Interval(0x26F5, 0x26F5), /* Sailboat */
+ new Interval(0x26FA, 0x26FA), /* Tent */
+ new Interval(0x26FD, 0x26FD), /* Fuel pump */
+ new Interval(0x2705, 0x2705), /* Check mark */
+ new Interval(0x270A, 0x270B), /* Raised fist, Raised hand */
+ new Interval(0x2728, 0x2728), /* Sparkles */
+ new Interval(0x274C, 0x274C), /* Cross mark */
+ new Interval(0x274E, 0x274E), /* Cross mark (square) */
+ new Interval(0x2753, 0x2755), /* Question/Exclamation marks */
+ new Interval(0x2757, 0x2757), /* Exclamation mark */
+ new Interval(0x2795, 0x2797), /* Plus, Minus, Division */
+ new Interval(0x27B0, 0x27B0), /* Curly loop */
+ new Interval(0x27BF, 0x27BF), /* Double curly loop */
+ new Interval(0x2B1B, 0x2B1C), /* Black/White large squares */
+ new Interval(0x2B50, 0x2B50), /* Star */
+ new Interval(0x2B55, 0x2B55), /* Heavy circle */
+ new Interval(0x2E80, 0x303E), /* CJK Radicals .. CJK Symbols (excl. U+303F) */
+ new Interval(0x3041, 0x33BF), /* Hiragana .. CJK Compatibility */
+ new Interval(0x33C0, 0x33FF), /* CJK Compatibility (cont) */
+ new Interval(0x3400, 0x4DFF), /* CJK Unified Ideographs Extension A + Yijing Hexagrams */
+ new Interval(0x4E00, 0xA4CF), /* CJK Unified Ideographs .. Yi */
+ new Interval(0xA960, 0xA97C), /* Hangul Jamo Extended-A */
+ new Interval(0xAC00, 0xD7A3), /* Hangul Syllables */
+ new Interval(0xF900, 0xFAFF), /* CJK Compatibility Ideographs */
+ new Interval(0xFE10, 0xFE19), /* Vertical forms */
+ new Interval(0xFE30, 0xFE6F), /* CJK Compatibility Forms */
+ new Interval(0xFF00, 0xFF60), /* Fullwidth Forms */
+ new Interval(0xFFE0, 0xFFE6), /* Fullwidth Signs */
+ new Interval(0x16FE0, 0x16FF1), /* Ideographic Symbols and Punctuation */
+ new Interval(0x17000, 0x187F7), /* Tangut */
+ new Interval(0x18800, 0x18CD5), /* Tangut Components */
+ new Interval(0x18CFF, 0x18D08), /* Tangut Supplement */
+ new Interval(0x1AFF0, 0x1AFF3), /* Kana Extended-B */
+ new Interval(0x1AFF5, 0x1AFFB), /* Kana Extended-B (cont) */
+ new Interval(0x1AFFD, 0x1AFFE), /* Kana Extended-B (cont) */
+ new Interval(0x1B000, 0x1B122), /* Kana Supplement */
+ new Interval(0x1B132, 0x1B132), /* Small Kana */
+ new Interval(0x1B150, 0x1B152), /* Small Kana Extension */
+ new Interval(0x1B155, 0x1B155), /* Small Kana (cont) */
+ new Interval(0x1B164, 0x1B167), /* Small Kana Extension (cont) */
+ new Interval(0x1B170, 0x1B2FB), /* Nushu */
+ new Interval(0x1D300, 0x1D356), /* Tai Xuan Jing Symbols */
+ new Interval(0x1D360, 0x1D376), /* Counting Rod Numerals */
+ new Interval(0x1F004, 0x1F004), /* Mahjong Red Dragon */
+ new Interval(0x1F0CF, 0x1F0CF), /* Playing Card Black Joker */
+ new Interval(0x1F100, 0x1F10A), /* Enclosed Alphanumeric Supplement */
+ new Interval(0x1F110, 0x1F12D), /* Enclosed Alphanumeric Supplement (cont) */
+ new Interval(0x1F130, 0x1F169), /* Enclosed Alphanumeric Supplement (cont) */
+ new Interval(0x1F170, 0x1F1AC), /* Enclosed Alphanumeric Supplement (cont) */
+ new Interval(0x1F1E6, 0x1F202), /* Regional Indicators .. Enclosed Ideographic */
+ new Interval(0x1F210, 0x1F23B), /* Enclosed Ideographic Supplement */
+ new Interval(0x1F240, 0x1F248), /* Enclosed Ideographic Supplement (cont) */
+ new Interval(0x1F250, 0x1F251), /* Enclosed Ideographic Supplement (cont) */
+ new Interval(0x1F260, 0x1F265), /* Enclosed Ideographic Supplement (cont) */
+ new Interval(0x1F300, 0x1F320), /* Miscellaneous Symbols and Pictographs */
+ new Interval(0x1F32D, 0x1F335), /* Food and Drink */
+ new Interval(0x1F337, 0x1F37C), /* Plants and Nature */
+ new Interval(0x1F37E, 0x1F393), /* Drinks and Celebrations */
+ new Interval(0x1F3A0, 0x1F3CA), /* Activities */
+ new Interval(0x1F3CF, 0x1F3D3), /* Sports */
+ new Interval(0x1F3E0, 0x1F3F0), /* Buildings */
+ new Interval(0x1F3F4, 0x1F3F4), /* Black Flag */
+ new Interval(0x1F3F8, 0x1F43E), /* Sports and Animals */
+ new Interval(0x1F440, 0x1F440), /* Eyes */
+ new Interval(0x1F442, 0x1F4FC), /* People and Objects */
+ new Interval(0x1F4FF, 0x1F53D), /* Objects */
+ new Interval(0x1F54B, 0x1F54E), /* Religious */
+ new Interval(0x1F550, 0x1F567), /* Clock faces */
+ new Interval(0x1F57A, 0x1F57A), /* Dancing */
+ new Interval(0x1F595, 0x1F596), /* Gestures */
+ new Interval(0x1F5A4, 0x1F5A4), /* Black Heart */
+ new Interval(0x1F5FB, 0x1F64F), /* Places and People */
+ new Interval(0x1F680, 0x1F6C5), /* Transport */
+ new Interval(0x1F6CC, 0x1F6CC), /* Sleeping */
+ new Interval(0x1F6D0, 0x1F6D2), /* Shopping */
+ new Interval(0x1F6D5, 0x1F6D7), /* Places */
+ new Interval(0x1F6DC, 0x1F6DF), /* Transport (cont) */
+ new Interval(0x1F6EB, 0x1F6EC), /* Airplane */
+ new Interval(0x1F6F4, 0x1F6FC), /* Transport */
+ new Interval(0x1F7E0, 0x1F7EB), /* Colored circles/squares */
+ new Interval(0x1F7F0, 0x1F7F0), /* Heavy equals sign */
+ new Interval(0x1F90C, 0x1F93A), /* Gestures and Activities */
+ new Interval(0x1F93C, 0x1F945), /* Sports */
+ new Interval(0x1F947, 0x1F9FF), /* Awards, Objects, People */
+ new Interval(0x1FA00, 0x1FA53), /* Chess symbols */
+ new Interval(0x1FA60, 0x1FA6D), /* Xiangqi */
+ new Interval(0x1FA70, 0x1FA7C), /* Symbols and Pictographs Extended-A */
+ new Interval(0x1FA80, 0x1FA89), /* Symbols and Pictographs Extended-A (cont) */
+ new Interval(0x1FA8F, 0x1FAC6), /* Symbols and Pictographs Extended-A (cont) */
+ new Interval(0x1FACE, 0x1FADC), /* Symbols and Pictographs Extended-A (cont) */
+ new Interval(0x1FADF, 0x1FAE9), /* Symbols and Pictographs Extended-A (cont) */
+ new Interval(0x1FAF0, 0x1FAF8), /* Hand gestures */
+ new Interval(0x20000, 0x2FFFD), /* CJK Unified Ideographs Extension B..F */
+ new Interval(0x30000, 0x3FFFD), /* CJK Unified Ideographs Extension G..J */
};
+ // @spotless:on
private static class Interval {
public final int first;
@@ -135,7 +391,390 @@ public Interval(int first, int last) {
}
}
- /* auxiliary function for binary search in interval table */
+ /**
+ * Returns the number of {@code char}s consumed by the grapheme cluster
+ * starting at {@code index} in the given {@code CharSequence}.
+ *
+ * A grapheme cluster is a user-perceived character that may be composed
+ * of multiple Unicode code points. This method recognizes:
+ *
+ * ZWJ sequences (e.g., family emoji ๐จโ๐ฉโ๐งโ๐ฆ)
+ * Regional indicator pairs (flags, e.g., ๐ซ๐ท)
+ * Emoji modifier sequences (skin tones, e.g., ๐๐ฝ)
+ * Variation selector sequences (U+FE0E, U+FE0F)
+ * Combining mark sequences
+ *
+ *
+ * @param cs the character sequence
+ * @param index the starting char index
+ * @return the number of chars consumed by the grapheme cluster
+ */
+ public static int charCountForGraphemeCluster(CharSequence cs, int index) {
+ if (HAS_JDK_GRAPHEME_SUPPORT) {
+ return charCountForGraphemeClusterBreakIterator(cs, index);
+ }
+ return charCountForGraphemeClusterLegacy(cs, index);
+ }
+
+ /**
+ * Creates a {@link BreakIterator} configured for grapheme cluster segmentation
+ * of the given character sequence. Returns {@code null} when JDK grapheme
+ * support is unavailable (< 21).
+ *
+ * The returned iterator is bound to the {@code String} produced by
+ * {@code cs.toString()} at creation time. It must only be reused with the
+ * same character sequence; passing it to
+ * {@link #charCountForGraphemeClusterBreakIterator(CharSequence, int, BreakIterator)}
+ * with a different sequence will produce incorrect results. Create a new
+ * iterator for each distinct sequence.
+ */
+ static BreakIterator createGraphemeBreakIterator(CharSequence cs) {
+ if (!HAS_JDK_GRAPHEME_SUPPORT) return null;
+ BreakIterator bi = BreakIterator.getCharacterInstance();
+ bi.setText(cs.toString());
+ return bi;
+ }
+
+ /**
+ * Uses JDK 21+ {@link BreakIterator} with full UAX #29 Extended Grapheme
+ * Cluster segmentation. Automatically stays current with new Unicode
+ * versions as the JDK is updated.
+ */
+ static int charCountForGraphemeClusterBreakIterator(CharSequence cs, int index) {
+ int len = cs.length();
+ if (index >= len) return 0;
+ BreakIterator bi = BreakIterator.getCharacterInstance();
+ bi.setText(cs.toString());
+ return charCountForGraphemeClusterBreakIterator(cs, index, bi);
+ }
+
+ /**
+ * Uses a pre-configured {@link BreakIterator} to find the grapheme cluster
+ * boundary at the given index, avoiding per-call allocation.
+ */
+ static int charCountForGraphemeClusterBreakIterator(CharSequence cs, int index, BreakIterator bi) {
+ int len = cs.length();
+ if (index >= len) return 0;
+ int next = bi.following(index);
+ if (next == BreakIterator.DONE) {
+ return len - index;
+ }
+ return next - index;
+ }
+
+ /**
+ * Fallback grapheme cluster segmentation for JDK < 21.
+ * Handles ZWJ sequences, regional indicator pairs, emoji modifiers,
+ * variation selectors, and combining marks using heuristics.
+ */
+ static int charCountForGraphemeClusterLegacy(CharSequence cs, int index) {
+ int len = cs.length();
+ if (index >= len) return 0;
+
+ int cp = Character.codePointAt(cs, index);
+ int pos = index + Character.charCount(cp);
+
+ // Regional indicator pairs form a single grapheme cluster (flag)
+ if (isRegionalIndicator(cp) && pos < len) {
+ int next = Character.codePointAt(cs, pos);
+ if (isRegionalIndicator(next)) {
+ pos += Character.charCount(next);
+ }
+ return pos - index;
+ }
+
+ // Consume grapheme cluster extensions
+ while (pos < len) {
+ int ncp = Character.codePointAt(cs, pos);
+ if (ncp == 0x200D) { // ZWJ โ joins with the next character
+ int zwjSize = Character.charCount(ncp);
+ if (pos + zwjSize < len) {
+ pos += zwjSize;
+ pos += Character.charCount(Character.codePointAt(cs, pos));
+ } else {
+ break;
+ }
+ } else if (wcwidth(ncp) == 0 && ncp >= 0x20) {
+ // Zero-width extending characters: combining marks,
+ // variation selectors (FE0E/FE0F), skin tone modifiers,
+ // tag characters, etc.
+ pos += Character.charCount(ncp);
+ } else {
+ break;
+ }
+ }
+
+ return pos - index;
+ }
+
+ /**
+ * Returns the display width of the grapheme cluster starting at {@code index}.
+ *
+ * Variation selectors override the base code point's width:
+ * VS16 ({@code U+FE0F}) upgrades the cluster to emoji presentation (width 2),
+ * while VS15 ({@code U+FE0E}) downgrades it to text presentation (width 1).
+ * When neither is present, the width of the base code point (via
+ * {@link #wcwidth(int)}) is used.
+ *
+ * @param cs the character sequence
+ * @param index the starting char index
+ * @return the display width of the grapheme cluster, same range as {@link #wcwidth(int)}
+ */
+ public static int wcwidthForGraphemeCluster(CharSequence cs, int index) {
+ return wcwidthForGraphemeCluster(cs, index, charCountForGraphemeCluster(cs, index));
+ }
+
+ /**
+ * Returns the display width of the grapheme cluster of the given
+ * {@code clusterCharCount} chars starting at {@code index}.
+ *
+ * This overload avoids recomputing the cluster boundaries when the
+ * caller already obtained them from
+ * {@link #charCountForGraphemeCluster(CharSequence, int)}.
+ *
+ * @param cs the character sequence
+ * @param index the starting char index
+ * @param clusterCharCount number of {@code char}s in the cluster
+ * @return the display width of the grapheme cluster, same range as {@link #wcwidth(int)}
+ */
+ static int wcwidthForGraphemeCluster(CharSequence cs, int index, int clusterCharCount) {
+ int cp = Character.codePointAt(cs, index);
+ int w = wcwidth(cp);
+
+ // Scan the cluster for variation selectors
+ int end = index + clusterCharCount;
+ int pos = index + Character.charCount(cp);
+ while (pos < end) {
+ int ncp = Character.codePointAt(cs, pos);
+ if (ncp == 0xFE0F) {
+ return 2; // VS16 โ emoji presentation (width 2)
+ } else if (ncp == 0xFE0E) {
+ return 1; // VS15 โ text presentation (width 1)
+ }
+ pos += Character.charCount(ncp);
+ }
+
+ return w;
+ }
+
+ /**
+ * Compute the display width in terminal columns of the character or grapheme cluster
+ * that begins at the given index in the character sequence.
+ *
+ * If the terminal has grapheme-cluster mode enabled, or if {@code terminal} is
+ * {@code null} and the runtime provides JDK-level grapheme-cluster support (JDK 21+),
+ * the measurement is grapheme-cluster-aware so emoji variation selectors and ZWJ
+ * sequences are handled as a single display unit. Otherwise the width of the
+ * single code point at {@code index} is returned.
+ *
+ * @param cs the character sequence containing the cluster
+ * @param index the starting char index of the character or cluster
+ * @param terminal the terminal to query for grapheme-cluster mode, or {@code null}
+ * @return the display width in terminal columns for the character or cluster
+ */
+ public static int wcwidthForDisplay(CharSequence cs, int index, Terminal terminal) {
+ if ((terminal != null && terminal.getGraphemeClusterMode()) || (terminal == null && HAS_JDK_GRAPHEME_SUPPORT)) {
+ int charCount = charCountForGraphemeCluster(cs, index);
+ return wcwidthForDisplayWithGroupings(cs, index, terminal, charCount);
+ }
+ return wcwidth(Character.codePointAt(cs, index));
+ }
+
+ /**
+ * Compute the display width in terminal columns of the character or grapheme cluster
+ * starting at the given char index, using a provided cluster char count to avoid
+ * recomputing cluster boundaries.
+ *
+ * @param cs the character sequence containing the cluster
+ * @param index the starting char index of the character or cluster
+ * @param terminal the terminal to query for grapheme-cluster grouping behavior; may be {@code null}
+ * @param charCount the number of Java `char`s occupied by the character or grapheme cluster
+ * starting at {@code index}
+ * @return the display width in terminal columns for the character or grapheme cluster
+ */
+ static int wcwidthForDisplay(CharSequence cs, int index, Terminal terminal, int charCount) {
+ if ((terminal != null && terminal.getGraphemeClusterMode()) || (terminal == null && HAS_JDK_GRAPHEME_SUPPORT)) {
+ return wcwidthForDisplayWithGroupings(cs, index, terminal, charCount);
+ }
+ return wcwidth(Character.codePointAt(cs, index));
+ }
+
+ /**
+ * Compute the number of Java chars that form the display unit (code point or grapheme cluster) starting at {@code index} in {@code cs}.
+ *
+ * If the terminal indicates grapheme cluster mode, or when {@code terminal} is {@code null} and JDK grapheme-cluster support is available (JDK 21+), this uses grapheme-cluster segmentation so ZWJ sequences, flag pairs, skin-tone modifiers, and similar multi-code-point units are treated as a single unit and may span multiple {@code char}s. Otherwise it returns the {@link Character#charCount(int) char count} for the code point at {@code index}.
+ *
+ * @param cs the character sequence
+ * @param index the starting char index
+ * @param terminal the terminal to consult for grapheme cluster mode, or {@code null}
+ * @return the number of {@code char} units to advance past the display unit beginning at {@code index}
+ */
+ public static int charCountForDisplay(CharSequence cs, int index, Terminal terminal) {
+ return charCountForDisplay(cs, index, terminal, null);
+ }
+
+ /**
+ * Compute the number of Java chars that form the display unit starting at {@code index},
+ * reusing a pre-configured {@link BreakIterator} to avoid per-call allocation.
+ *
+ * @param cs the character sequence
+ * @param index the starting char index
+ * @param terminal the terminal to consult for grapheme cluster mode, or {@code null}
+ * @param bi a pre-configured BreakIterator from {@link #createGraphemeBreakIterator}, or {@code null}
+ * @return the number of {@code char} units to advance past the display unit beginning at {@code index}
+ */
+ static int charCountForDisplay(CharSequence cs, int index, Terminal terminal, BreakIterator bi) {
+ if ((terminal != null && terminal.getGraphemeClusterMode()) || (terminal == null && HAS_JDK_GRAPHEME_SUPPORT)) {
+ int charCount;
+ if (bi != null) {
+ charCount = charCountForGraphemeClusterBreakIterator(cs, index, bi);
+ } else {
+ charCount = charCountForGraphemeCluster(cs, index);
+ }
+ return charCountForDisplayWithGroupings(cs, index, terminal, charCount);
+ }
+ return Character.charCount(Character.codePointAt(cs, index));
+ }
+
+ /**
+ * Compute the display width in columns for the grapheme cluster starting at the given index,
+ * honoring the terminal's per-category emoji grouping support.
+ *
+ * If the terminal is null or supports grouping for this cluster, the cluster is measured as a
+ * single grapheme and its cluster width is returned. If the cluster is an emoji grouping that
+ * the terminal does not support, the returned width is the sum of the individual code-point
+ * widths in the cluster. If the cluster contains a single non-grouped code point, the width of
+ * that base code point is returned.
+ *
+ * @param cs the character sequence containing the cluster
+ * @param index the index of the first char of the cluster in {@code cs}
+ * @param terminal the terminal whose emoji grouping support determines grouping behavior; may be {@code null}
+ * @param charCount the number of Java {@code char} units that make up the grapheme cluster at {@code index}
+ * @return the display width in columns for the cluster (or base code point / summed per-code-point width)
+ */
+ private static int wcwidthForDisplayWithGroupings(CharSequence cs, int index, Terminal terminal, int charCount) {
+ if (terminal == null || isGroupedByTerminal(cs, index, terminal, charCount)) {
+ return wcwidthForGraphemeCluster(cs, index, charCount);
+ }
+ if (isMultiCodepointEmoji(cs, index, charCount)) {
+ // Multi-codepoint emoji cluster not grouped by terminal:
+ // sum individual codepoint widths
+ return wcwidthUngrouped(cs, index, charCount);
+ }
+ return wcwidth(Character.codePointAt(cs, index));
+ }
+
+ /**
+ * Determine the number of Java char units that should be consumed for display starting at the given index, considering the terminal's emoji grouping support.
+ *
+ * @param cs the character sequence containing the cluster
+ * @param index the char index within cs where the cluster begins
+ * @param terminal the terminal whose emoji grouping preferences are consulted; may be null
+ * @param charCount the full char count of the grapheme cluster beginning at index
+ * @return the number of chars to consume for display: the full cluster charCount when the terminal groups the cluster or when the cluster is an emoji sequence, otherwise the char count of the single code point at index
+ */
+ private static int charCountForDisplayWithGroupings(CharSequence cs, int index, Terminal terminal, int charCount) {
+ if (terminal == null || isGroupedByTerminal(cs, index, terminal, charCount)) {
+ return charCount;
+ }
+ if (isMultiCodepointEmoji(cs, index, charCount)) {
+ // Consume the entire cluster even when not grouped, so that
+ // combining characters (skin tone modifiers, etc.) are not
+ // processed separately with incorrect zero-width values
+ return charCount;
+ }
+ return Character.charCount(Character.codePointAt(cs, index));
+ }
+
+ /**
+ * Compute the display width in terminal character cells for a multi-codepoint cluster
+ * when the terminal does not treat the cluster as a single grapheme.
+ *
+ * The width is the sum of the display widths of the cluster's constituent code points.
+ * Skin-tone modifiers and regional indicator symbols are treated as width 2 when not
+ * grouped with a base character.
+ *
+ * @param cs the character sequence containing the cluster
+ * @param index the index (in Java chars) of the cluster's first char within {@code cs}
+ * @param charCount the number of Java chars that make up the cluster
+ * @return the total display width of the cluster in character cells
+ */
+ private static int wcwidthUngrouped(CharSequence cs, int index, int charCount) {
+ int end = index + charCount;
+ int totalWidth = 0;
+ int pos = index;
+ while (pos < end) {
+ int cp = Character.codePointAt(cs, pos);
+ if (isSkinToneModifier(cp) || isRegionalIndicator(cp)) {
+ // These are in the combining table (wcwidth=0) but render
+ // as width-2 emoji when not combined with a base character
+ totalWidth += 2;
+ } else {
+ int w = wcwidth(cp);
+ if (w > 0) {
+ totalWidth += w;
+ }
+ }
+ pos += Character.charCount(cp);
+ }
+ return totalWidth;
+ }
+
+ /**
+ * Tests whether the terminal groups the given cluster as a single unit.
+ *
+ * Delegates to {@link AbstractTerminal#isClusterGrouped} when the
+ * terminal is an AbstractTerminal instance; otherwise falls back to
+ * {@link Terminal#getGraphemeClusterMode()}.
+ */
+ private static boolean isGroupedByTerminal(CharSequence cs, int index, Terminal terminal, int charCount) {
+ if (terminal instanceof AbstractTerminal) {
+ return ((AbstractTerminal) terminal).isClusterGrouped(cs, index, charCount);
+ }
+ return terminal.getGraphemeClusterMode();
+ }
+
+ /**
+ * Tests whether the grapheme cluster starting at {@code index} is a
+ * multi-codepoint emoji sequence (as opposed to a single codepoint or a
+ * non-emoji combining sequence).
+ */
+ private static boolean isMultiCodepointEmoji(CharSequence cs, int index, int charCount) {
+ if (charCount <= Character.charCount(Character.codePointAt(cs, index))) {
+ return false; // Single codepoint
+ }
+ int cp = Character.codePointAt(cs, index);
+ return isRegionalIndicator(cp) || cp >= 0x2000;
+ }
+
+ /**
+ * Determines whether a Unicode code point is a Regional Indicator Symbol (U+1F1E6..U+1F1FF).
+ *
+ * @param cp the Unicode code point to test
+ * @return `true` if the code point is within U+1F1E6..U+1F1FF, `false` otherwise
+ */
+ public static boolean isRegionalIndicator(int cp) {
+ return cp >= 0x1F1E6 && cp <= 0x1F1FF;
+ }
+
+ /**
+ * Determines whether the Unicode code point is an Emoji Modifier (skin tone) in the range U+1F3FB..U+1F3FF.
+ *
+ * @param cp the Unicode code point to test
+ * @return true if the code point is an Emoji Modifier (skin tone) between U+1F3FB and U+1F3FF, false otherwise
+ */
+ private static boolean isSkinToneModifier(int cp) {
+ return cp >= 0x1F3FB && cp <= 0x1F3FF;
+ }
+
+ /**
+ * Determine whether a code point falls within any interval in a sorted Interval table.
+ *
+ * @param ucs the Unicode code point to test
+ * @param table a sorted array of Interval ranges
+ * @param max index of the last interval in {@code table} to consider (inclusive)
+ * @return {@code true} if an interval between {@code table[0]} and {@code table[max]} contains {@code ucs}, {@code false} otherwise
+ */
private static boolean bisearch(int ucs, Interval[] table, int max) {
int min = 0;
int mid;
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/WriterOutputStream.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/WriterOutputStream.java
index b1285f9636ea5..a8fd176b72bb0 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/WriterOutputStream.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/WriterOutputStream.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2017, the original author(s).
+ * Copyright (c) the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@@ -19,8 +19,29 @@
import java.nio.charset.CodingErrorAction;
/**
- * Redirects an {@link OutputStream} to a {@link Writer} by decoding the data
- * using the specified {@link Charset}.
+ * An OutputStream implementation that writes to a Writer, bridging byte and character streams.
+ *
+ *
+ * The WriterOutputStream class provides an OutputStream implementation that redirects
+ * its output to a Writer by decoding the bytes using a specified character encoding.
+ * This allows code that expects to write to an OutputStream to work with a Writer
+ * destination instead.
+ *
+ *
+ *
+ * This class handles the complexities of character encoding conversion, including:
+ *
+ *
+ * Proper handling of multi-byte character encodings
+ * Buffering of partial character sequences
+ * Configurable behavior for malformed input and unmappable characters
+ *
+ *
+ *
+ * This class is particularly useful in JLine for bridging between byte-oriented
+ * and character-oriented I/O in terminal handling, especially when dealing with
+ * legacy APIs that expect byte streams.
+ *
*
* Note: This class should only be used if it is necessary to
* redirect an {@link OutputStream} to a {@link Writer} for compatibility
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/package-info.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/package-info.java
index 963001def0b84..7f45e0562635d 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/package-info.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002-2016, the original author or authors.
+ * Copyright (c) 2002-2025, 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.
@@ -7,7 +7,10 @@
* https://opensource.org/licenses/BSD-3-Clause
*/
/**
- * JLine 3.
+ * JLine utility classes.
+ *
+ * This package contains utility classes for terminal operations, including text styling,
+ * cursor manipulation, ANSI escape sequence handling, and other terminal-related functionality.
*
* @since 3.0
*/
diff --git a/src/jdk.internal.le/share/legal/jline.md b/src/jdk.internal.le/share/legal/jline.md
index 17392e93659e1..5f119ad89c890 100644
--- a/src/jdk.internal.le/share/legal/jline.md
+++ b/src/jdk.internal.le/share/legal/jline.md
@@ -1,4 +1,4 @@
-## JLine v3.29.0
+## JLine v4.0.12
### JLine License
diff --git a/test/jdk/jdk/internal/jline/AbstractWindowsTerminalTest.java b/test/jdk/jdk/internal/jline/AbstractWindowsTerminalTest.java
index 513c315682a08..d4c78952b1678 100644
--- a/test/jdk/jdk/internal/jline/AbstractWindowsTerminalTest.java
+++ b/test/jdk/jdk/internal/jline/AbstractWindowsTerminalTest.java
@@ -82,6 +82,16 @@ protected boolean processConsoleInput() throws IOException {
public Size getSize() {
throw new UnsupportedOperationException("unexpected.");
}
+
+ @Override
+ public int getDefaultForegroundColor() {
+ throw new UnsupportedOperationException("unexpected.");
+ }
+
+ @Override
+ public int getDefaultBackgroundColor() {
+ throw new UnsupportedOperationException("unexpected.");
+ }
};
t.processInputChar(' ');
if (t.reader().read() != ' ') {
diff --git a/test/jdk/jdk/internal/jline/JLineConsoleProviderTest.java b/test/jdk/jdk/internal/jline/JLineConsoleProviderTest.java
index 445da167c5f15..5457ae44045f6 100644
--- a/test/jdk/jdk/internal/jline/JLineConsoleProviderTest.java
+++ b/test/jdk/jdk/internal/jline/JLineConsoleProviderTest.java
@@ -74,6 +74,7 @@ void doRunConsoleTest(String testName,
"java.base/jdk.internal.io=ALL-UNNAMED",
"--add-exports",
"jdk.internal.le/jdk.internal.org.jline=ALL-UNNAMED",
+ "-Dtest.disabled.masking.thread=true",
ConsoleTest.class.getName(),
testName);
OutputAnalyzer output = ProcessTools.executeProcess(builder, input);
diff --git a/test/jdk/jdk/internal/jline/KeyConversionTest.java b/test/jdk/jdk/internal/jline/KeyConversionTest.java
index 10e25f98df1e3..0a3ef6717bbae 100644
--- a/test/jdk/jdk/internal/jline/KeyConversionTest.java
+++ b/test/jdk/jdk/internal/jline/KeyConversionTest.java
@@ -90,6 +90,14 @@ public void processKeyEvent(boolean isKeyDown, short virtualKeyCode,
char ch, int controlKeyState) throws IOException {
super.processKeyEvent(isKeyDown, virtualKeyCode, ch, controlKeyState);
}
+ @Override
+ public int getDefaultForegroundColor() {
+ return 0;
+ }
+ @Override
+ public int getDefaultBackgroundColor() {
+ return 0;
+ }
}.processKeyEvent(event.isKeyDown, event.virtualKeyCode, event.ch, event.controlKeyState);
String actual = result.toString();
diff --git a/test/langtools/jdk/jshell/ToolTabSnippetTest.java b/test/langtools/jdk/jshell/ToolTabSnippetTest.java
index 112208c40b3cf..266dd719286f6 100644
--- a/test/langtools/jdk/jshell/ToolTabSnippetTest.java
+++ b/test/langtools/jdk/jshell/ToolTabSnippetTest.java
@@ -361,7 +361,7 @@ public void testAnnotation() throws Exception {
waitOutput(out, ".*java.lang.annotation.RetentionPolicy.*java.lang.annotation.RetentionPolicy.CLASS.*" +
REDRAW_PROMPT + "@Ann1\\(@java.lang.annotation.Retention\\(");
inputSink.write("CL" + TAB);
- waitOutput(out, "CL\\u001B\\[2Djava.lang.annotation.RetentionPolicy.CLASS \\u0008");
+ waitOutput(out, "CL\\u001B\\[2Djava.lang.annotation.RetentionPolicy.CLASS \\u001B\\[D");
});
}
}