Skip to content

Commit 294e17a

Browse files
committed
more content for connection and input reader
1 parent 525609b commit 294e17a

File tree

2 files changed

+235
-2
lines changed

2 files changed

+235
-2
lines changed

content/docs/readline/connection.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ The `Connection` interface represents a connection to a terminal (local, direct,
2424
│ │
2525
│ asWriter() ──► java.io.Writer adapter │
2626
│ asPrintWriter() ──► java.io.PrintWriter │
27+
│ InputReader.asReader(conn) ──► Reader │
2728
└─────────────────────────────────────────────┘
2829
```
2930

@@ -206,9 +207,11 @@ connection.write("Hello");
206207
connection.write("Line 1\nLine 2\n");
207208
```
208209

209-
## Writer Adapters
210+
## I/O Adapters
210211

211-
Get a `java.io.Writer` or `PrintWriter` view of the connection:
212+
### Writer Adapters
213+
214+
Get a `java.io.Writer` or `PrintWriter` view of the connection's output:
212215

213216
```java
214217
// Writer adapter
@@ -221,6 +224,29 @@ pw.println("Hello from PrintWriter");
221224
pw.printf("Formatted: %d items%n", 42);
222225
```
223226

227+
### Reader Adapter
228+
229+
Get a `java.io.Reader` view of the connection's input using `InputReader`:
230+
231+
```java
232+
import org.aesh.terminal.utils.InputReader;
233+
234+
// One-step: creates reader and wires it to the connection's stdin handler
235+
InputReader reader = InputReader.asReader(connection);
236+
237+
// Standard Reader methods work
238+
char[] buf = new char[256];
239+
int n = reader.read(buf, 0, buf.length);
240+
241+
// Timeout-aware read (useful for game loops)
242+
int ch = reader.read(50); // 50ms timeout
243+
if (ch != InputReader.TIMEOUT) {
244+
// process character
245+
}
246+
```
247+
248+
See [InputReader](input-reader) for full documentation including code point reading, bounded queue configuration, and complete examples.
249+
224250
## Raw Mode
225251

226252
Enter raw mode for character-by-character input:
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
---
2+
date: '2026-03-20T10:00:00+01:00'
3+
draft: false
4+
title: 'InputReader'
5+
weight: 10
6+
---
7+
8+
`InputReader` is a utility that bridges the terminal's push-style input (`Consumer<int[]>`) to Java's pull-style `java.io.Reader`. This is useful when you need to read terminal input using standard Java I/O APIs — for example, wrapping a `BufferedReader` around terminal input, or integrating with libraries that expect a `Reader`.
9+
10+
## Quick Start
11+
12+
The simplest way to create an `InputReader` is with the `asReader()` factory method:
13+
14+
```java
15+
import org.aesh.terminal.utils.InputReader;
16+
import org.aesh.terminal.tty.TerminalConnection;
17+
18+
TerminalConnection connection = new TerminalConnection();
19+
connection.enterRawMode();
20+
connection.openNonBlocking();
21+
22+
InputReader reader = InputReader.asReader(connection);
23+
24+
// Now use standard Reader methods
25+
char[] buf = new char[256];
26+
int n = reader.read(buf, 0, buf.length);
27+
String input = new String(buf, 0, n);
28+
```
29+
30+
This creates an `InputReader` and wires it to the connection's stdin handler in one step.
31+
32+
## How It Works
33+
34+
`Connection` delivers input as code point arrays to a `Consumer<int[]>` handler. `InputReader` converts these into `char`s (splitting supplementary code points into surrogate pairs) and buffers them in a bounded queue. You then read from the queue using standard `Reader` methods or the timeout-aware methods.
35+
36+
```
37+
Connection stdin ──► push(int) ──► LinkedBlockingQueue<Character> ──► read()
38+
(splits supplementary (blocking/
39+
into surrogates) timeout)
40+
```
41+
42+
## Creating an InputReader
43+
44+
### From a Connection (recommended)
45+
46+
```java
47+
// Default queue capacity (4096)
48+
InputReader reader = InputReader.asReader(connection);
49+
50+
// Custom queue capacity
51+
InputReader reader = InputReader.asReader(connection, 8192);
52+
```
53+
54+
### Manual Wiring
55+
56+
If you need more control over how input is fed to the reader:
57+
58+
```java
59+
InputReader reader = new InputReader();
60+
61+
// Wire it yourself
62+
connection.setStdinHandler(cps -> {
63+
for (int cp : cps) {
64+
reader.push(cp);
65+
}
66+
});
67+
```
68+
69+
### Standalone (no Connection)
70+
71+
You can also push data manually — useful for testing or non-terminal input sources:
72+
73+
```java
74+
InputReader reader = new InputReader();
75+
reader.push("Hello");
76+
reader.push(0x1F600); // emoji, split into surrogate pair
77+
reader.push('!');
78+
```
79+
80+
## Reading Input
81+
82+
### Standard Reader Methods
83+
84+
`InputReader` extends `java.io.Reader`, so all standard methods work:
85+
86+
```java
87+
// Read into a char array — blocks on first char, drains rest without blocking
88+
char[] buf = new char[1024];
89+
int n = reader.read(buf, 0, buf.length);
90+
91+
// Check if input is available without blocking
92+
if (reader.ready()) {
93+
// Data available
94+
}
95+
```
96+
97+
### Read with Timeout
98+
99+
Read a single character with a millisecond timeout. Returns `InputReader.TIMEOUT` (-2) if no input arrives, or `InputReader.EOF` (-1) on end of stream:
100+
101+
```java
102+
int ch = reader.read(50); // 50ms timeout
103+
if (ch == InputReader.TIMEOUT) {
104+
// No input within timeout
105+
} else if (ch == InputReader.EOF) {
106+
// End of stream
107+
} else {
108+
System.out.println("Got: " + (char) ch);
109+
}
110+
```
111+
112+
This is particularly useful for game loops or interactive applications that need to check for input without blocking indefinitely:
113+
114+
```java
115+
while (running) {
116+
int ch = reader.read(16); // ~60fps
117+
if (ch != InputReader.TIMEOUT && ch != InputReader.EOF) {
118+
handleInput((char) ch);
119+
}
120+
updateGame();
121+
render();
122+
}
123+
```
124+
125+
### Read Code Points
126+
127+
Read a full Unicode code point, automatically reassembling surrogate pairs:
128+
129+
```java
130+
OptionalInt cp = reader.readCodePoint(1, TimeUnit.SECONDS);
131+
if (cp.isPresent()) {
132+
System.out.println("Code point: U+" + Integer.toHexString(cp.getAsInt()));
133+
}
134+
```
135+
136+
## Push Methods
137+
138+
The `push` methods are the producer side — they feed data into the reader's internal queue:
139+
140+
| Method | Description |
141+
|--------|-------------|
142+
| `push(char ch)` | Push a single char |
143+
| `push(int codePoint)` | Push a Unicode code point (supplementary code points split into surrogate pairs) |
144+
| `push(CharSequence csq)` | Push a string or other CharSequence |
145+
146+
Push is non-blocking. If the queue is full, excess characters are silently dropped. After `close()`, pushes are silently ignored.
147+
148+
## Bounded Queue
149+
150+
The internal queue has a bounded capacity (default 4096 chars) to prevent unbounded memory growth if the reader falls behind. You can configure the capacity via the constructor:
151+
152+
```java
153+
// Small buffer for memory-constrained environments
154+
InputReader reader = new InputReader(256);
155+
156+
// Large buffer for high-throughput scenarios
157+
InputReader reader = new InputReader(16384);
158+
```
159+
160+
## Closing
161+
162+
```java
163+
reader.close();
164+
```
165+
166+
Closing the reader:
167+
- Unblocks any thread waiting on `read()` (returns -1 / EOF)
168+
- Clears the queue
169+
- Causes subsequent `read()`, `ready()`, and `readCodePoint()` to throw `IOException`
170+
- Causes subsequent `push()` calls to be silently ignored
171+
172+
## Complete Example
173+
174+
A simple terminal echo application using `InputReader`:
175+
176+
```java
177+
import org.aesh.terminal.tty.Signal;
178+
import org.aesh.terminal.tty.TerminalConnection;
179+
import org.aesh.terminal.utils.InputReader;
180+
181+
public class EchoApp {
182+
public static void main(String[] args) throws Exception {
183+
try (TerminalConnection connection = new TerminalConnection()) {
184+
connection.enterRawMode();
185+
connection.openNonBlocking();
186+
187+
InputReader reader = InputReader.asReader(connection);
188+
189+
connection.setSignalHandler(signal -> {
190+
if (signal == Signal.INT) {
191+
reader.close();
192+
}
193+
});
194+
195+
int ch;
196+
while ((ch = reader.read(100)) != InputReader.EOF) {
197+
if (ch != InputReader.TIMEOUT) {
198+
connection.write(String.valueOf((char) ch));
199+
if (ch == '\r') {
200+
connection.write("\n");
201+
}
202+
}
203+
}
204+
}
205+
}
206+
}
207+
```

0 commit comments

Comments
 (0)