Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -18,18 +18,39 @@
import jdk.internal.org.jline.utils.NonBlockingReader;

/**
* The BindingReader will transform incoming chars into
* key bindings
*
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
* The BindingReader transforms incoming characters into key bindings.
* <p>
* 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.
* <p>
* Key features include:
* <ul>
* <li>Support for timeout-based disambiguation of key sequences</li>
* <li>Buffering of input to handle multi-character sequences</li>
* <li>Support for running macros (predefined sequences of keystrokes)</li>
* <li>Access to the current input buffer and last binding read</li>
* </ul>
* <p>
* 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<Integer> 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;
}
Expand All @@ -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 (&gt;= 128) characters will be matched to {@link KeyMap#getUnicode()}.
* <p>
* 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 &lt;= 0, the current bound operation
* will be returned.
* <p>
* 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 <T> the type of bindings to be read
* @return the decoded binding or <code>null</code> if the end of
* stream has been reached
* @param <T> 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> T readBinding(KeyMap<T> keys) {
return readBinding(keys, null, true);
}

/**
* Reads a key binding from the input stream using the specified KeyMaps.
* <p>
* 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 <T> 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> T readBinding(KeyMap<T> keys, KeyMap<T> local) {
return readBinding(keys, local, true);
}

/**
* Reads a key binding from the input stream using the specified KeyMaps.
* <p>
* 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 <T> 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> T readBinding(KeyMap<T> keys, KeyMap<T> local, boolean block) {
lastBinding = null;
T o = null;
Expand Down Expand Up @@ -116,6 +166,17 @@ public <T> T readBinding(KeyMap<T> keys, KeyMap<T> local, boolean block) {
return null;
}

/**
* Reads characters from the input until a specific sequence is encountered.
* <p>
* This method reads characters one by one and accumulates them in a buffer
* until the specified terminating sequence is found.
* </p>
*
* @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()) {
Expand Down Expand Up @@ -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.
* <p>
* 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()) {
Expand All @@ -170,6 +237,17 @@ public int readCharacter() {
}
}

/**
* Reads a single character (Unicode code point) from the input stream with buffering.
* <p>
* 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()) {
Expand Down Expand Up @@ -211,6 +289,17 @@ public int readCharacterBuffered() {
}
}

/**
* Peeks at the next character in the input stream without consuming it.
* <p>
* 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();
Expand All @@ -222,14 +311,41 @@ public int peekCharacter(long timeout) {
}
}

/**
* Runs a macro by pushing its characters into the input buffer.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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;
}
Expand Down
Loading