|
| 1 | +--- |
| 2 | +sidebar_position: 3 |
| 3 | +--- |
| 4 | + |
| 5 | +# Non-Blocking Input |
| 6 | + |
| 7 | +JLine provides support for non-blocking input, allowing you to read user input without blocking the execution of your application. This is particularly useful for applications that need to perform background tasks while still being responsive to user input. |
| 8 | + |
| 9 | +## NonBlockingReader |
| 10 | + |
| 11 | +The `NonBlockingReader` class is the key component for non-blocking input in JLine: |
| 12 | + |
| 13 | +```java title="NonBlockingReaderExample.java" showLineNumbers |
| 14 | +import org.jline.terminal.Terminal; |
| 15 | +import org.jline.terminal.TerminalBuilder; |
| 16 | +import org.jline.utils.NonBlockingReader; |
| 17 | + |
| 18 | +import java.io.IOException; |
| 19 | +import java.util.concurrent.TimeUnit; |
| 20 | + |
| 21 | +public class NonBlockingReaderExample { |
| 22 | + public static void main(String[] args) throws IOException { |
| 23 | + Terminal terminal = TerminalBuilder.builder().build(); |
| 24 | + |
| 25 | + // highlight-start |
| 26 | + // Get a non-blocking reader |
| 27 | + NonBlockingReader reader = terminal.reader(); |
| 28 | + // highlight-end |
| 29 | + |
| 30 | + terminal.writer().println("Type something (program will exit after 10 seconds):"); |
| 31 | + terminal.writer().flush(); |
| 32 | + |
| 33 | + // Track start time |
| 34 | + long startTime = System.currentTimeMillis(); |
| 35 | + |
| 36 | + // Run for 10 seconds |
| 37 | + while (System.currentTimeMillis() - startTime < 10000) { |
| 38 | + try { |
| 39 | + // highlight-start |
| 40 | + // Check if input is available |
| 41 | + if (reader.available() > 0) { |
| 42 | + // Read a character (non-blocking) |
| 43 | + int c = reader.read(); |
| 44 | + terminal.writer().println("Read character: " + (char)c); |
| 45 | + terminal.writer().flush(); |
| 46 | + } |
| 47 | + // highlight-end |
| 48 | + |
| 49 | + // Simulate background work |
| 50 | + terminal.writer().print("."); |
| 51 | + terminal.writer().flush(); |
| 52 | + TimeUnit.MILLISECONDS.sleep(500); |
| 53 | + } catch (InterruptedException e) { |
| 54 | + Thread.currentThread().interrupt(); |
| 55 | + break; |
| 56 | + } |
| 57 | + } |
| 58 | + |
| 59 | + terminal.writer().println("\nTime's up!"); |
| 60 | + terminal.close(); |
| 61 | + } |
| 62 | +} |
| 63 | +``` |
| 64 | + |
| 65 | +## Reading with Timeout |
| 66 | + |
| 67 | +You can also read with a timeout, which will block for up to the specified time: |
| 68 | + |
| 69 | +```java title="TimeoutReadExample.java" |
| 70 | +import org.jline.terminal.Terminal; |
| 71 | +import org.jline.terminal.TerminalBuilder; |
| 72 | +import org.jline.utils.NonBlockingReader; |
| 73 | + |
| 74 | +import java.io.IOException; |
| 75 | + |
| 76 | +public class TimeoutReadExample { |
| 77 | + public static void main(String[] args) throws IOException { |
| 78 | + Terminal terminal = TerminalBuilder.builder().build(); |
| 79 | + NonBlockingReader reader = terminal.reader(); |
| 80 | + |
| 81 | + terminal.writer().println("Press any key within 5 seconds:"); |
| 82 | + terminal.writer().flush(); |
| 83 | + |
| 84 | + try { |
| 85 | + // highlight-start |
| 86 | + // Read with a 5-second timeout |
| 87 | + int c = reader.read(5, TimeUnit.SECONDS); |
| 88 | + // highlight-end |
| 89 | + |
| 90 | + if (c != -1) { |
| 91 | + terminal.writer().println("You pressed: " + (char)c); |
| 92 | + } else { |
| 93 | + terminal.writer().println("Timeout expired!"); |
| 94 | + } |
| 95 | + } catch (IOException e) { |
| 96 | + terminal.writer().println("Error reading input: " + e.getMessage()); |
| 97 | + } |
| 98 | + |
| 99 | + terminal.close(); |
| 100 | + } |
| 101 | +} |
| 102 | +``` |
| 103 | + |
| 104 | +## Combining with LineReader |
| 105 | + |
| 106 | +You can combine non-blocking input with the `LineReader` for more sophisticated input handling: |
| 107 | + |
| 108 | +```java title="NonBlockingLineReaderExample.java" showLineNumbers |
| 109 | +import org.jline.reader.LineReader; |
| 110 | +import org.jline.reader.LineReaderBuilder; |
| 111 | +import org.jline.reader.UserInterruptException; |
| 112 | +import org.jline.terminal.Terminal; |
| 113 | +import org.jline.terminal.TerminalBuilder; |
| 114 | +import org.jline.utils.NonBlockingReader; |
| 115 | + |
| 116 | +import java.io.IOException; |
| 117 | +import java.util.concurrent.ExecutorService; |
| 118 | +import java.util.concurrent.Executors; |
| 119 | +import java.util.concurrent.TimeUnit; |
| 120 | +import java.util.concurrent.atomic.AtomicBoolean; |
| 121 | + |
| 122 | +public class NonBlockingLineReaderExample { |
| 123 | + public static void main(String[] args) throws IOException { |
| 124 | + Terminal terminal = TerminalBuilder.builder().build(); |
| 125 | + LineReader lineReader = LineReaderBuilder.builder() |
| 126 | + .terminal(terminal) |
| 127 | + .build(); |
| 128 | + |
| 129 | + // Flag to control background task |
| 130 | + AtomicBoolean running = new AtomicBoolean(true); |
| 131 | + |
| 132 | + // Start background task |
| 133 | + ExecutorService executor = Executors.newSingleThreadExecutor(); |
| 134 | + executor.submit(() -> { |
| 135 | + try { |
| 136 | + while (running.get()) { |
| 137 | + // Simulate background work |
| 138 | + terminal.writer().print("."); |
| 139 | + terminal.writer().flush(); |
| 140 | + TimeUnit.SECONDS.sleep(1); |
| 141 | + } |
| 142 | + } catch (InterruptedException e) { |
| 143 | + Thread.currentThread().interrupt(); |
| 144 | + } catch (Exception e) { |
| 145 | + terminal.writer().println("Error in background task: " + e.getMessage()); |
| 146 | + terminal.writer().flush(); |
| 147 | + } |
| 148 | + }); |
| 149 | + |
| 150 | + try { |
| 151 | + // Main input loop |
| 152 | + while (running.get()) { |
| 153 | + try { |
| 154 | + // Read a line (this will block) |
| 155 | + String line = lineReader.readLine("\nprompt> "); |
| 156 | + |
| 157 | + if ("exit".equalsIgnoreCase(line)) { |
| 158 | + running.set(false); |
| 159 | + } else { |
| 160 | + terminal.writer().println("You entered: " + line); |
| 161 | + terminal.writer().flush(); |
| 162 | + } |
| 163 | + } catch (UserInterruptException e) { |
| 164 | + // Ctrl+C pressed |
| 165 | + running.set(false); |
| 166 | + } |
| 167 | + } |
| 168 | + } finally { |
| 169 | + // Shutdown background task |
| 170 | + executor.shutdownNow(); |
| 171 | + try { |
| 172 | + executor.awaitTermination(1, TimeUnit.SECONDS); |
| 173 | + } catch (InterruptedException e) { |
| 174 | + Thread.currentThread().interrupt(); |
| 175 | + } |
| 176 | + |
| 177 | + terminal.writer().println("\nExiting..."); |
| 178 | + terminal.close(); |
| 179 | + } |
| 180 | + } |
| 181 | +} |
| 182 | +``` |
| 183 | + |
| 184 | +## Asynchronous Input Handling |
| 185 | + |
| 186 | +For more complex scenarios, you can set up asynchronous input handling: |
| 187 | + |
| 188 | +```java title="AsyncInputExample.java" |
| 189 | +import org.jline.terminal.Terminal; |
| 190 | +import org.jline.terminal.TerminalBuilder; |
| 191 | +import org.jline.utils.NonBlockingReader; |
| 192 | + |
| 193 | +import java.io.IOException; |
| 194 | +import java.util.concurrent.ExecutorService; |
| 195 | +import java.util.concurrent.Executors; |
| 196 | +import java.util.concurrent.TimeUnit; |
| 197 | +import java.util.concurrent.atomic.AtomicBoolean; |
| 198 | + |
| 199 | +public class AsyncInputExample { |
| 200 | + public static void main(String[] args) throws IOException { |
| 201 | + Terminal terminal = TerminalBuilder.builder().build(); |
| 202 | + NonBlockingReader reader = terminal.reader(); |
| 203 | + |
| 204 | + // Flag to control input handling |
| 205 | + AtomicBoolean running = new AtomicBoolean(true); |
| 206 | + |
| 207 | + // Start input handling thread |
| 208 | + ExecutorService executor = Executors.newSingleThreadExecutor(); |
| 209 | + executor.submit(() -> { |
| 210 | + try { |
| 211 | + // highlight-start |
| 212 | + // Continuously read input |
| 213 | + while (running.get()) { |
| 214 | + int c = reader.read(100); |
| 215 | + if (c != -1) { |
| 216 | + // Process the input |
| 217 | + terminal.writer().println("\nReceived input: " + (char)c); |
| 218 | + |
| 219 | + if (c == 'q' || c == 'Q') { |
| 220 | + running.set(false); |
| 221 | + } |
| 222 | + |
| 223 | + terminal.writer().flush(); |
| 224 | + } |
| 225 | + } |
| 226 | + // highlight-end |
| 227 | + } catch (IOException e) { |
| 228 | + if (running.get()) { |
| 229 | + terminal.writer().println("Error reading input: " + e.getMessage()); |
| 230 | + terminal.writer().flush(); |
| 231 | + } |
| 232 | + } |
| 233 | + }); |
| 234 | + |
| 235 | + // Main application loop |
| 236 | + try { |
| 237 | + terminal.writer().println("Press keys (q to quit):"); |
| 238 | + terminal.writer().flush(); |
| 239 | + |
| 240 | + int count = 0; |
| 241 | + while (running.get() && count < 30) { |
| 242 | + // Simulate application work |
| 243 | + terminal.writer().print("."); |
| 244 | + terminal.writer().flush(); |
| 245 | + |
| 246 | + TimeUnit.MILLISECONDS.sleep(500); |
| 247 | + count++; |
| 248 | + } |
| 249 | + } catch (InterruptedException e) { |
| 250 | + Thread.currentThread().interrupt(); |
| 251 | + } finally { |
| 252 | + // Shutdown input handling |
| 253 | + running.set(false); |
| 254 | + executor.shutdownNow(); |
| 255 | + try { |
| 256 | + executor.awaitTermination(1, TimeUnit.SECONDS); |
| 257 | + } catch (InterruptedException e) { |
| 258 | + Thread.currentThread().interrupt(); |
| 259 | + } |
| 260 | + |
| 261 | + terminal.writer().println("\nExiting..."); |
| 262 | + terminal.close(); |
| 263 | + } |
| 264 | + } |
| 265 | +} |
| 266 | +``` |
| 267 | + |
| 268 | +## Raw Mode vs. Cooked Mode |
| 269 | + |
| 270 | +Understanding terminal modes is important for non-blocking input: |
| 271 | + |
| 272 | +- **Cooked Mode (Default)**: Input is buffered until Enter is pressed |
| 273 | +- **Raw Mode**: Input is made available immediately, without buffering |
| 274 | + |
| 275 | +```java title="RawModeExample.java" |
| 276 | +import org.jline.terminal.Attributes; |
| 277 | +import org.jline.terminal.Attributes.InputFlag; |
| 278 | +import org.jline.terminal.Terminal; |
| 279 | +import org.jline.terminal.TerminalBuilder; |
| 280 | +import org.jline.utils.NonBlockingReader; |
| 281 | + |
| 282 | +import java.io.IOException; |
| 283 | + |
| 284 | +public class RawModeExample { |
| 285 | + public static void main(String[] args) throws IOException { |
| 286 | + Terminal terminal = TerminalBuilder.builder().build(); |
| 287 | + |
| 288 | + try { |
| 289 | + // Save original terminal attributes |
| 290 | + Attributes originalAttributes = terminal.getAttributes(); |
| 291 | + |
| 292 | + // highlight-start |
| 293 | + // Configure raw mode |
| 294 | + Attributes rawAttributes = new Attributes(originalAttributes); |
| 295 | + rawAttributes.setInputFlag(InputFlag.ICANON, false); // Disable canonical mode |
| 296 | + rawAttributes.setInputFlag(InputFlag.ECHO, false); // Disable echo |
| 297 | + rawAttributes.setControlChar(Attributes.ControlChar.VMIN, 0); // Non-blocking |
| 298 | + rawAttributes.setControlChar(Attributes.ControlChar.VTIME, 0); // No timeout |
| 299 | + |
| 300 | + // Enter raw mode |
| 301 | + terminal.setAttributes(rawAttributes); |
| 302 | + // highlight-end |
| 303 | + |
| 304 | + terminal.writer().println("Terminal is in raw mode. Press keys (q to quit):"); |
| 305 | + terminal.writer().flush(); |
| 306 | + |
| 307 | + NonBlockingReader reader = terminal.reader(); |
| 308 | + |
| 309 | + // Read characters until 'q' is pressed |
| 310 | + while (true) { |
| 311 | + int c = reader.read(100); |
| 312 | + if (c != -1) { |
| 313 | + terminal.writer().println("Read: " + (char)c + " (ASCII: " + c + ")"); |
| 314 | + terminal.writer().flush(); |
| 315 | + |
| 316 | + if (c == 'q' || c == 'Q') { |
| 317 | + break; |
| 318 | + } |
| 319 | + } |
| 320 | + } |
| 321 | + |
| 322 | + // Restore original terminal attributes |
| 323 | + terminal.setAttributes(originalAttributes); |
| 324 | + } finally { |
| 325 | + terminal.close(); |
| 326 | + } |
| 327 | + } |
| 328 | +} |
| 329 | +``` |
| 330 | + |
| 331 | +## Best Practices |
| 332 | + |
| 333 | +When working with non-blocking input in JLine, consider these best practices: |
| 334 | + |
| 335 | +1. **Handle Interruptions**: Always handle interruptions properly to ensure clean shutdown. |
| 336 | + |
| 337 | +2. **Use Separate Threads**: Keep input handling in a separate thread from your main application logic. |
| 338 | + |
| 339 | +3. **Set Appropriate Timeouts**: Choose timeout values that balance responsiveness with CPU usage. |
| 340 | + |
| 341 | +4. **Restore Terminal State**: Always restore the terminal to its original state before exiting. |
| 342 | + |
| 343 | +5. **Check for EOF**: Check for end-of-file conditions (-1 return value) when reading. |
| 344 | + |
| 345 | +6. **Use Atomic Flags**: Use atomic boolean flags for thread coordination. |
| 346 | + |
| 347 | +7. **Provide User Feedback**: Keep users informed about what's happening, especially during long operations. |
| 348 | + |
| 349 | +8. **Graceful Shutdown**: Implement graceful shutdown procedures for all threads. |
| 350 | + |
| 351 | +9. **Error Handling**: Implement robust error handling for I/O exceptions. |
| 352 | + |
| 353 | +10. **Test on Different Platforms**: Test your non-blocking input handling on different platforms and terminal types. |
0 commit comments