lib/ruco/keyboard.rb in ruco-0.0.20 vs lib/ruco/keyboard.rb in ruco-0.0.21

- old
+ new

@@ -1,71 +1,127 @@ +require 'curses' + class Keyboard + ENTER = 13 + IS_18 = RUBY_VERSION =~ /^1\.8/ SEQUENCE_TIMEOUT = 0.01 NOTHING = 4294967295 # getch returns this as 'nothing' on 1.8 but nil on 1.9.2 A_TO_Z = ('a'..'z').to_a - def self.listen - loop do - key = Curses.getch || NOTHING + def self.input(&block) + @input = block + end - if @sequence - if sequence_finished? - yield @sequence.pack('c*').force_encoding('utf-8') - @sequence = nil + def self.output + @sequence = nil + @started = Time.now.to_f + + loop do + key = fetch_user_input + if sequence_finished? + if needs_paste_fix?(@sequence) + yield bytes_to_string(@sequence) else - @sequence << key unless key == NOTHING + bytes_to_key_codes(@sequence).each{|c| yield c } end - next + @sequence = nil end - next if key == NOTHING + start_or_append_sequence key + end + end - key = key.ord if key.is_a?(String) # ruby 1.9 fix + private - code = case key + def self.translate_key_to_code(key) + case key - # move - when Curses::Key::UP then :up - when Curses::Key::DOWN then :down - when Curses::Key::RIGHT then :right - when Curses::Key::LEFT then :left - when 554 then :"Ctrl+right" - when 555 then :"Ctrl+Shift+right" - when 539 then :"Ctrl+left" - when 540 then :"Ctrl+Shift+left" - when 560 then :"Ctrl+up" - when 519 then :"Ctrl+down" - when Curses::KEY_END then :end - when Curses::KEY_HOME then :home - when Curses::KEY_NPAGE then :page_down - when Curses::KEY_PPAGE then :page_up - when Curses::KEY_IC then :insert - when Curses::KEY_F0..Curses::KEY_F63 then :"F#{key - Curses::KEY_F0}" + # move + when Curses::Key::UP then :up + when Curses::Key::DOWN then :down + when Curses::Key::RIGHT then :right + when Curses::Key::LEFT then :left + when 554 then :"Ctrl+right" + when 555 then :"Ctrl+Shift+right" + when 539 then :"Ctrl+left" + when 540 then :"Ctrl+Shift+left" + when 560 then :"Ctrl+up" + when 519 then :"Ctrl+down" + when Curses::KEY_END then :end + when Curses::KEY_HOME then :home + when Curses::KEY_NPAGE then :page_down + when Curses::KEY_PPAGE then :page_up + when Curses::KEY_IC then :insert + when Curses::KEY_F0..Curses::KEY_F63 then :"F#{key - Curses::KEY_F0}" - # modify - when 9 then :tab - when 13 then :enter # shadows Ctrl+m - when 263, 127 then :backspace # ubuntu / mac - when Curses::KEY_DC then :delete + # modify + when 9 then :tab + when ENTER then :enter # shadows Ctrl+m + when 263, 127 then :backspace # ubuntu / mac + when Curses::KEY_DC then :delete - # misc - when 0 then :"Ctrl+space" - when 1..26 then :"Ctrl+#{A_TO_Z[key-1]}" - when 27 then :escape - when 195..197 # start of unicode sequence - @sequence = [key] - @sequence_started = Time.now.to_f - next + # misc + when 0 then :"Ctrl+space" + when 1..26 then :"Ctrl+#{A_TO_Z[key-1]}" + when 27 then :escape + when Curses::KEY_RESIZE then :resize + else + key.chr + end + end + + def self.fetch_user_input + key = @input.call || NOTHING + key = key.ord if key.is_a?(String) # ruby 1.9 fix + key + end + + def self.start_or_append_sequence(key) + @started = Time.now.to_f + @sequence ||= [] + @sequence << key + end + + def self.bytes_to_string(bytes) + bytes.pack('c*').gsub("\r","\n").force_encoding('utf-8') + end + + # split a text so fast-typers do not get bugs like ^B^C in output + def self.bytes_to_key_codes(bytes) + result = [] + multi_byte = nil + + bytes.each do |byte| + if multi_byte_part?(byte) + multi_byte ||= [] + multi_byte << byte else - key > 255 ? key : key.chr # output printable chars + if multi_byte + # finish multi-byte char + result << bytes_to_string(multi_byte) + multi_byte = nil + end + result << translate_key_to_code(byte) end + end - yield code + if multi_byte + result << bytes_to_string(multi_byte) end + + result end - private + # not ascii and not control-char + def self.multi_byte_part?(byte) + 127 < byte and byte < 256 + end def self.sequence_finished? - (Time.now.to_f - @sequence_started) > SEQUENCE_TIMEOUT + @sequence and (Time.now.to_f - @started) > SEQUENCE_TIMEOUT + end + + # paste of multiple \n or \n in text would cause weird indentation + def self.needs_paste_fix?(sequence) + sequence.size > 1 and sequence.include?(ENTER) end end \ No newline at end of file