lib/ruby_jard/console.rb in ruby_jard-0.2.0 vs lib/ruby_jard/console.rb in ruby_jard-0.2.1

- old
+ new

@@ -1,7 +1,8 @@ # frozen_string_literal: true +require 'tty-screen' require 'io/console' require 'English' module RubyJard # Wrapper for utilities to control screen @@ -9,78 +10,161 @@ class << self def start_alternative_terminal(output) return unless output.tty? output.print tput('smcup') + rescue RubyJard::Error + # If tput not found, fallback to hard-coded sequence. + output.print "\e[?1049h\e[22;0;0t" end def stop_alternative_terminal(output) return unless output.tty? output.print tput('rmcup') + rescue RubyJard::Error + # If tput not found, fallback to hard-coded sequence. + output.print "\e[?1049l\e[23;0;0t" end def move_to(output, x, y) return unless output.tty? - output.goto(y, x) + output.print format("\e[%<row>d;%<col>dH", row: y + 1, col: x + 1) end def screen_size(output) return [0, 0] unless output.tty? - height, width = output.winsize - [width, height] + [TTY::Screen.width, TTY::Screen.height] end - def hard_clear_screen(output) + def clear_screen(output) return unless output.tty? - output.print tput('clear') + output.print "\e[3J" end - def clear_screen(output) + def clear_screen_to_end(output) return unless output.tty? - output.clear_screen + output.print "\e[0J" end - def hide_cursor(output) + def disable_cursor!(output = STDOUT) return unless output.tty? output.print tput('civis') + rescue RubyJard::Error + # If tput not found, fallback to hard-coded sequence. + output.print "\e[?25l" end - def show_cursor(output) + def enable_cursor!(output = STDOUT) return unless output.tty? - output.print tput('cvvis') + output.print tput('cnorm') + rescue RubyJard::Error + # If tput not found, fallback to hard-coded sequence. + output.print "\e[?12l\e[?25h" end - def cooked!(output) + def getch(input, timeout) + return input.getch(min: 0, time: timeout) if input.respond_to?(:getch) + + raw! + disable_echo! + key = + begin + input.read_nonblock(255) + rescue IO::WaitReadable + io = IO.select([input], nil, nil, timeout) + if io.nil? + nil + else + retry + end + rescue IO::WaitWritable + nil + end + + key + ensure + cooked! + enable_echo! + end + + def raw!(output = STDOUT) return unless output.tty? - output.cooked! + begin + output.raw! + rescue StandardError + stty('raw') + end end - def echo!(output) + def cooked!(output = STDOUT) return unless output.tty? - output.echo = true + begin + output.cooked! + rescue StandardError + # If stty not found, or raise error, nothing I can do + stty('-raw') + end end - def tput(*args) - # TODO: Should implement multiple fallbacks here to support different platforms + def disable_echo!(output = STDOUT) + return unless output.tty? + begin + output.echo = false + rescue StandardError + # If stty not found, or raise error, nothing I can do + stty('-echo') + end + end + + def enable_echo!(output = STDOUT) + return unless output.tty? + + begin + output.echo = true + rescue StandardError + # If stty not found, or raise error, nothing I can do + stty('echo') + end + end + + def cached_tput + @cached_tput ||= {} + end + + def tput(*args) command = "tput #{args.join(' ')}" + return cached_tput[command] unless cached_tput[command].nil? + output = `#{command}` if $CHILD_STATUS.success? + cached_tput[command] = output + else + raise RubyJard::Error, "Fail to call `#{command}`: #{$CHILD_STATUS}" + end + rescue StandardError => e + raise RubyJard::Error, "Fail to call `#{command}`. Error: #{e}" + end + + def stty(*args) + command = "stty #{args.join(' ')}" + output = `#{command}` + if $CHILD_STATUS.success? output else - raise Ruby::Error, "Fail to call `#{command}`: #{$CHILD_STATUS}" + raise RubyJard::Error, "Fail to call `#{command}`: #{$CHILD_STATUS}" end rescue StandardError => e - raise Ruby::Error, "Fail to call `#{command}`. Error: #{e}" + raise RubyJard::Error, "Fail to call `#{command}`. Error: #{e}" end end end end