class Dispel::Keyboard

Constants

A_TO_Z
DEFAULT_INPUT
ENTER
ESCAPE
IS_18
MAX_CHAR
NOTHING
SEQUENCE_TIMEOUT

Public Class Methods

input(&block) click to toggle source
# File lib/dispel/keyboard.rb, line 14
def self.input(&block)
  @input = block
end
output(options={}) { |:timeout| ... } click to toggle source
# File lib/dispel/keyboard.rb, line 18
def self.output(options={}, &block)
  @sequence = []
  @started = Time.now.to_f
  timeout = options[:timeout]

  if timeout && SEQUENCE_TIMEOUT > timeout
    raise "Timeout must be higher then SEQUENCE_TIMEOUT (#{SEQUENCE_TIMEOUT})"
  end

  loop do
    @now = Time.now.to_f
    @elapsed = @now - @started

    key = fetch_user_input

    # finish previous sequence
    if @sequence.any? and @elapsed > SEQUENCE_TIMEOUT
      sequence_to_keys(@sequence).each(&block)
      @sequence = []
    end

    if key # start new sequence
      @started = @now
      @sequence << key
    elsif timeout and @elapsed > timeout
      @started = @now
      yield :timeout
    else
      sleep SEQUENCE_TIMEOUT # nothing happening -> sleep a bit to save cpu
    end
  end
end

Private Class Methods

bytes_to_key_codes(bytes) click to toggle source

split a text so fast-typers do not get bugs like ^B^C in output

# File lib/dispel/keyboard.rb, line 157
def self.bytes_to_key_codes(bytes)
  result = []
  multi_byte = []

  append_multibyte = lambda{
    unless multi_byte.empty?
      result << bytes_to_string(multi_byte)
      multi_byte = []
    end
  }

  bytes.each do |byte|
    if multi_byte_part?(byte)
      multi_byte << byte
    else
      append_multibyte.call
      result << translate_key_to_code(byte)
    end
  end

  append_multibyte.call
  result
end
bytes_to_string(bytes) click to toggle source
# File lib/dispel/keyboard.rb, line 152
def self.bytes_to_string(bytes)
  bytes.pack('c*').gsub("\r","\n").force_encoding('utf-8')
end
escape_sequence?(sequence) click to toggle source
# File lib/dispel/keyboard.rb, line 205
def self.escape_sequence?(sequence)
  sequence[0] == ESCAPE and sequence.size.between?(2,7)
end
fetch_user_input() click to toggle source
# File lib/dispel/keyboard.rb, line 146
def self.fetch_user_input
  key = (@input || DEFAULT_INPUT).call || NOTHING
  key = key.ord unless IS_18
  key if key < NOTHING
end
is_alt_key_code?(sequence) click to toggle source
# File lib/dispel/keyboard.rb, line 209
def self.is_alt_key_code?(sequence)
  sequence.slice(0,1) == "^" and sequence.size == 2
end
multi_byte_part?(byte) click to toggle source

not ascii and not control-char

# File lib/dispel/keyboard.rb, line 182
def self.multi_byte_part?(byte)
  127 < byte and byte < 256
end
needs_paste_fix?(sequence) click to toggle source

paste of multiple n or n in text would cause weird indentation

# File lib/dispel/keyboard.rb, line 187
def self.needs_paste_fix?(sequence)
  sequence.size > 1 and sequence.include?(ENTER)
end
sequence_to_keys(sequence) click to toggle source
# File lib/dispel/keyboard.rb, line 191
def self.sequence_to_keys(sequence)
  if needs_paste_fix?(sequence)
    [bytes_to_string(sequence)]
  else
    # when connected via ssh escape sequences are used
    if escape_sequence?(sequence)
      stringified = bytes_to_string(sequence).sub(/\e+/,'^').sub('[[','[')
      [translate_key_to_code(stringified)]
    else
      bytes_to_key_codes(sequence)
    end
  end
end
translate_key_to_code(key) click to toggle source
# File lib/dispel/keyboard.rb, line 53
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

  # code, unix, iTerm
  when 337, '^[1;2A', "^[A" then :"Shift+up"
  when 336, '^[1;2B', "^[B" then :"Shift+down"
  when 402, '^[1;2C' then :"Shift+right"
  when 393, '^[1;2D' then :"Shift+left"

  when 558, '^[1;3A' then :"Alt+up"
  when 517, '^[1;3B' then :"Alt+down"
  when 552, '^[1;3C' then :"Alt+right"
  when 537, '^[1;3D' then :"Alt+left"

  when 560, '^[1;5A' then :"Ctrl+up"
  when 519, '^[1;5B' then :"Ctrl+down"
  when 554, '^[1;5C' then :"Ctrl+right"
  when 539, '^[1;5D' then :"Ctrl+left"

  when 561, '^[1;6A' then :"Ctrl+Shift+up"
  when 520, '^[1;6B' then :"Ctrl+Shift+down"
  when 555, '^[1;6C', "^[C" then :"Ctrl+Shift+right"
  when 540, '^[1;6D', "^[D" then :"Ctrl+Shift+left"

  when 562, '^[1;7A' then :"Alt+Ctrl+up"
  when 521, '^[1;7B' then :"Alt+Ctrl+down"
  when 556, '^[1;7C' then :"Alt+Ctrl+right"
  when 541, '^[1;7D' then :"Alt+Ctrl+left"

  when      '^[1;8A' then :"Alt+Ctrl+Shift+up"
  when      '^[1;8B' then :"Alt+Ctrl+Shift+down"
  when      '^[1;8C' then :"Alt+Ctrl+Shift+right"
  when      '^[1;8D' then :"Alt+Ctrl+Shift+left"

  when      '^[1;10A' then :"Alt+Shift+up"
  when      '^[1;10B' then :"Alt+Shift+down"
  when      '^[1;10C' then :"Alt+Shift+right"
  when      '^[1;10D' then :"Alt+Shift+left"

  when      '^[F'     then :"Shift+end"
  when      '^[H'     then :"Shift+home"

  when      '^[1;9F'  then :"Alt+end"
  when      '^[1;9H'  then :"Alt+home"

  when      '^[1;10F' then :"Alt+Shift+end"
  when      '^[1;10H' then :"Alt+Shift+home"

  when      '^[1;13F' then :"Alt+Ctrl+end"
  when      '^[1;13H' then :"Alt+Ctrl+home"

  when      '^[1;14F' then :"Alt+Ctrl+Shift+end"
  when      '^[1;14H' then :"Alt+Ctrl+Shift+home"

  when 527            then :"Ctrl+Shift+end"
  when 532            then :"Ctrl+Shift+home"

  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 353 then :"Shift+tab"
  when ENTER then :enter # shadows Ctrl+m
  when 263, 127 then :backspace
  when '^[3~', Curses::KEY_DC then :delete

  # misc
  when 0 then :"Ctrl+space"
  when 1..26 then :"Ctrl+#{A_TO_Z[key-1]}"
  when ESCAPE then :escape
  when Curses::KEY_RESIZE then :resize
  else
    if key.is_a? Integer
      key > MAX_CHAR ? key : key.chr
    elsif is_alt_key_code?(key)
      :"Alt+#{key.slice(1,1)}"
    else
      key
    end
  end
end