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