Responsive Terminal with Async and getch

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 for CTRL+C and \u0004 for CTRL+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!

Leave a Reply

Your email address will not be published. Required fields are marked *