When developing terminal-based applications in Ruby, managing user input and output efficiently is crucial. The code snippet below demonstrates how to use Ruby’s Async
gem alongside getch
and cooked
mode to create a responsive and user-friendly application that handles various user inputs, including special control characters.
require 'async'
counter = 0
pause = false
Async do |task|
task.async do
loop do
case input = $stdin.getch
when 'p'
pause = true
when 'r'
pause = false
when 'q', "\u0003", "\u0004"
exit 0
end
end
end
task.async do
while true
unless pause
$stdout.cooked { puts counter }
counter += 1
end
sleep(0.5)
end
end
end
Understanding the Code
getch
and Input Handling
The getch
method from Ruby’s io/console
library is a simple way to capture single-character input without requiring the user to press “Enter.” This is particularly useful in terminal applications where immediate response to user actions is needed.
In this code, getch
listens for user input in an infinite loop, responding to specific keys:
- Pressing
'p'
pauses the counter. - Pressing
'r'
resumes the counter. - Pressing
'q'
, or specific control characters (\u0003
forCTRL+C
and\u0004
forCTRL+D
), gracefully exits the program.
Special Control Characters: \u0003
and \u0004
Control characters like \u0003
and \u0004
represent CTRL+C
and CTRL+D
, respectively:
CTRL+C
(\u0003
): This sends an interrupt signal to the program, often used to stop it.CTRL+D
(\u0004
): This sends an end-of-transmission (EOF) signal, which can also be used to terminate input or exit a program.
By including these in the case
statement, the code gracefully exits when either of these combinations is pressed, ensuring that the program stops in a controlled manner.
cooked
Mode and Output Handling
The $stdout.cooked
block temporarily switches the terminal to “cooked” mode, where standard terminal input and output behavior is enabled. This means line buffering is restored, and special characters (like backspace) work as expected. Inside this block, the program outputs the current value of counter
, ensuring that it prints correctly in the terminal.
The use of cooked
mode here is significant because it ensures that output remains well-formatted and readable, even as the program alternates between receiving input and printing output.
Leveraging Async
for Non-Blocking Operations
The Async
gem is used to handle these operations concurrently:
- One task listens for user input.
- Another task updates and prints the counter unless it’s paused.
This approach ensures that the program remains responsive, efficiently handling both tasks without blocking one another.
Conclusion
This example showcases how to build a responsive terminal application in Ruby using Async
, getch
, and cooked
mode. The handling of special control characters like \u0003
(CTRL+C
) and \u0004
(CTRL+D
) ensures that the program can exit gracefully, while the use of Async
allows for smooth, non-blocking operations, making the application both user-friendly and robust.
Thanks to Ohm user in Ruby discord channel for some insights!