class KeyBindingTree
Attributes
C[RW]
I[RW]
act_bindings[R]
cur_action[RW]
cur_state[RW]
last_action[RW]
match_state[RW]
mode_root_state[R]
root[RW]
state_trail[R]
Public Class Methods
new()
click to toggle source
# File lib/vimamsa/key_binding_tree.rb, line 58 def initialize() @modes = {} @root = State.new("ROOT") @cur_state = @root # used for building the tree @default_mode = nil @mode_history = [] @state_trail = [] @last_action = nil @cur_action = nil @modifiers = [] # TODO: create a queue @last_event = [nil, nil, nil, nil, nil] @override_keyhandling_callback = nil # Allows h["foo"]["faa"]=1 @act_bindings = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) } end
Public Instance Methods
_bindkey(key, action)
click to toggle source
# File lib/vimamsa/key_binding_tree.rb, line 470 def _bindkey(key, action) key.strip! key.gsub!(/\s+/, " ") # if key.class == Array # key.each { |k| bindkey(k, action) } # return # end # $action_list << { :action => action, :key => key } if !$actions.has_key?(action) if action.class == String reg_act(action, proc { eval(action) }, action) end end m = key.match(/^(\S+)\s(\S.*)$/) if m modetmp = m[1] puts [key, modetmp, m].inspect modes = modetmp.split("") if modetmp.match(/^\p{Lu}+$/) # Uppercase modes = [modetmp] if modetmp.match(/^\p{Ll}+$/) # Lowercase keydef = m[2] else fatal_error("Error in keydef #{key.inspect}") end modes.each { |mode_id| mode_bind_key(mode_id, keydef, action) @act_bindings[mode_id][action] = keydef } end
add_minor_mode(id, label, major_mode_label)
click to toggle source
# File lib/vimamsa/key_binding_tree.rb, line 96 def add_minor_mode(id, label, major_mode_label) mode = State.new(id) @modes[label] = mode @root.children << mode mode.major_modes << major_mode_label end
add_mode(id, label)
click to toggle source
$kbd.add_mode(āIā, :insert)
# File lib/vimamsa/key_binding_tree.rb, line 87 def add_mode(id, label) mode = State.new(id) @modes[label] = mode @root.children << mode if @default_mode == nil set_default_mode(label) end end
bindkey(key, action)
click to toggle source
# File lib/vimamsa/key_binding_tree.rb, line 454 def bindkey(key, action) if key.class != Array key = key.split("||") end a = action if action.class == Array label = a[0] a = label proc = action[1] msg = action[2] reg_act(label, proc, msg) end key.each { |k| _bindkey(k, a) } end
clear_modifiers()
click to toggle source
# File lib/vimamsa/key_binding_tree.rb, line 103 def clear_modifiers() @modifiers = [] end
cur_mode_str()
click to toggle source
# File lib/vimamsa/key_binding_tree.rb, line 175 def cur_mode_str() return @mode_root_state.key_name end
find_state(key_name, eval_rule)
click to toggle source
# File lib/vimamsa/key_binding_tree.rb, line 107 def find_state(key_name, eval_rule) @cur_state.children.each { |s| if s.key_name == key_name and s.eval_rule == eval_rule # TODO check eval return s end } return nil end
get_state_trail_str()
click to toggle source
# File lib/vimamsa/key_binding_tree.rb, line 229 def get_state_trail_str s = "" s_trail = "" last_state = @state_trail.last last_state = last_state[0] if last_state.class == Array for st in @state_trail st = st[0] if st.class == Array s_trail << " #{st.to_s}" end s << "CUR STATE: #{s_trail}\n" for cstate in last_state.children act_s = "..." act_s = cstate.action.to_s if cstate.action != nil s << " #{cstate.to_s} #{act_s}\n" end return s end
handle_key_bindigs_action(action, c)
click to toggle source
# File lib/vimamsa/key_binding_tree.rb, line 541 def handle_key_bindigs_action(action, c) $method_handles_repeat = false #TODO:?? n = 1 if $next_command_count and !(action.class == String and action.include?("set_next_command_count")) n = $next_command_count # $next_command_count = nil debug("COUNT command #{n} times") end begin n.times do ret = exec_action(action) if $macro.is_recording and ret != false $macro.record_action(action) end break if $method_handles_repeat # Some methods have specific implementation for repeat, # like '5yy' => copy next five lines. (copy_line()) # By default the same command is just repeated n times # like '20j' => go to next line 20 times. end rescue SyntaxError debug("SYNTAX ERROR with eval cmd #{action}: " + $!.to_s) # rescue NoMethodError # debug("NoMethodError with eval cmd #{action}: " + $!.to_s) # rescue NameError # debug("NameError with eval cmd #{action}: " + $!.to_s) # raise rescue Exception => e puts "BACKTRACE" puts e.backtrace puts e.inspect puts "BACKTRACE END" if $!.class == SystemExit exit else crash("Error with action: #{action}: ", e) end end if action.class == String and !action.include?("set_next_command_count") $next_command_count = nil end end
handle_key_event(event)
click to toggle source
Receive keyboard event from Qt
# File lib/vimamsa/key_binding_tree.rb, line 366 def handle_key_event(event) start_profiler # puts "GOT KEY EVENT: #{key.inspect}" debug "GOT KEY EVENT:: #{event} #{event[2]}" debug "|#{event.inspect}|" $debuginfo["cur_event"] = event t1 = Time.now keycode = event[0] event_type = event[1] modifierinfo = event[4] event[3] = event[2] # String representation of received key key_str = event[2] @modifiers.delete(Qt::Key_Alt) if event[4] & ALTMODIFIER == 0 @modifiers.delete(Qt::Key_Control) if event[4] & CONTROLMODIFIER == 0 @modifiers.delete(Qt::Key_Shift) if event[4] & SHIFTMODIFIER == 0 # Add as modifier if ctrl, alt or shift if modifierinfo & ALTMODIFIER != 0 or modifierinfo & CONTROLMODIFIER != 0 or modifierinfo & SHIFTMODIFIER != 0 # And keypress and not already a modifier if event_type == KEY_PRESS and !@modifiers.include?(keycode) @modifiers << keycode end end # puts "----D------------" # puts @modifiers.inspect # puts event.inspect # puts event[4] & ALTMODIFIER # puts "-----------------" @modifiers.delete(keycode) if event_type == KEY_RELEASE # uval = keyval_to_unicode(event[0]) # event[3] = [uval].pack('c*').force_encoding('UTF-8') #TODO: 32bit? # debug("key_code_to_uval: uval: #{uval} uchar:#{event[3]}") if $event_keysym_translate_table.include?(keycode) key_str = $event_keysym_translate_table[keycode] end # Prefix string representation with modifiers, e.g. ctrl-shift-a key_prefix = "" @modifiers.each { |pressed_key| if $event_keysym_translate_table[pressed_key] key_prefix += $event_keysym_translate_table[pressed_key] + "-" end } # Get char based on keycode # to produce prefixed_key_str "shift-ctrl-a" instead of "shift-ctrl-\x01" key_str2 = key_str if $translate_table.include?(keycode) key_str2 = $translate_table[keycode].downcase end # puts "key_str=|#{key_str}| key_str=|#{key_str.inspect}| key_str2=|#{key_str2}|" prefixed_key_str = key_prefix + key_str2 # Space is only key in $event_keysym_translate_table # which is representable by single char key_str = " " if key_str == "space" # HACK # if keycode == @last_event[0] and event_type == KEY_RELEASE # puts "KEY! key_str=|#{key_str}| prefixed_key_str=|#{prefixed_key_str}|" # end if key_str != "" or prefixed_key_str != "" if keycode == @last_event[0] and event_type == KEY_RELEASE # If key is released immediately after pressed with no other events between match_key_conf(key_str + "!", prefixed_key_str + "!", event_type) elsif event_type == KEY_PRESS match_key_conf(key_str, prefixed_key_str, event_type) end @last_event = event #TODO: outside if? end # gui_refresh_cursor event_handle_time = Time.now - t1 debug "RB key event handle time: #{event_handle_time}" if event_handle_time > 1 / 40.0 render_buffer($buffer) end_profiler end
match(key_name)
click to toggle source
# File lib/vimamsa/key_binding_tree.rb, line 125 def match(key_name) new_state = [] @match_state.each { |parent| parent.children.each { |c| # printf(" KEY MATCH: ") # puts [c.key_name, key_name].inspect if c.key_name == key_name and c.eval_rule == "" new_state << c elsif c.key_name == key_name and c.eval_rule != "" puts "CHECK EVAL: #{c.eval_rule}" if eval(c.eval_rule) new_state << c puts "EVAL TRUE" else puts "EVAL FALSE" end end } } if new_state.any? # Match found @match_state = new_state return new_state # return true else # No match found # @match_state = [@C] #TODO return nil end end
match_key_conf(c, translated_c, event_type)
click to toggle source
Modifies state of key binding tree (move to new state) based on received event Checks child nodes of current state if they match received event if yes, change state to child if no, go back to root
# File lib/vimamsa/key_binding_tree.rb, line 251 def match_key_conf(c, translated_c, event_type) # $cur_key_dict = $key_bind_dict[$context[:mode]] print "MATCH KEY CONF: #{[c, translated_c]}" if $debug if !@override_keyhandling_callback.nil? ret = @override_keyhandling_callback.call(c, event_type) return if ret end eval_s = nil new_state = match(c) # #TODO: # new_state = match(translated_c) # if new_state == nil and translated_c.index("shift") == 0 # new_state = match(c) # end if new_state == nil s1 = match_state[0].children.select { |s| s.key_name.include?("<char>") } # TODO: [0] if s1.any? and (c.size == 1) and event_type == :key_press eval_s = s1.first.action.clone # eval_s.gsub!("<char>","'#{c}'") #TODO: remove new_state = [s1.first] end end if new_state == nil # Child is regexp like /[1-9]/ in: # 'C /[1-9]/'=> 'set_next_command_count(<char>)', # Execute child regexps one until matches s1 = match_state[0].children.select { |s| s.key_name =~ /^\/.*\/$/ } # TODO: [0] if s1.any? and c.size == 1 s1.each { |x| m = /^\/(.*)\/$/.match(x.key_name) if m != nil m2 = Regexp.new(m[1]).match(c) if m2 != nil eval_s = x.action.clone new_state = [x] break end return true end } # eval_s = s1.first.action.clone # eval_s.gsub!("<char>","'#{c}'") # new_state = [s1.first] end end if new_state != nil @state_trail << new_state puts get_state_trail_str() # # puts "CUR STATE: #{@state_trail.collect{|x| x.to_s}.join}" # s_trail = "" # for st in @state_trail # st = st[0] if st.class == Array # s_trail << " #{st.to_s}" # end # puts "CUR STATE: #{s_trail}" # for cstate in new_state[0].children # act_s = "..." # act_s = cstate.action.to_s if cstate.action != nil # puts " #{cstate.to_s} #{act_s}" # end # new_state[0].children.collect{|x|x.to_s} end if new_state == nil printf("NO MATCH") if $debug if event_type == :key_press and c != "shift" # TODO:include other modifiers in addition to shift? set_state_to_root printf(", BACK TO ROOT") if $debug end if event_type == :key_release and c == "shift!" # Pressing a modifier key (shift) puts state back to root # only on key release when no other key has been pressed # after said modifier key (shift). set_state_to_root printf(", BACK TO ROOT") if $debug end printf("\n") if $debug else # Don't execute action if one of the states has children state_with_children = new_state.select { |s| s.children.any? } s_act = new_state.select { |s| s.action != nil } if s_act.any? and !state_with_children.any? eval_s = s_act.first.action if eval_s == nil puts "FOUND MATCH:#{eval_s}" puts "CHAR: #{c}" c.gsub!("\\", %q{\\\\} * 4) # Escape \ -chars c.gsub!("'", "#{'\\' * 4}'") # Escape ' -chars eval_s.gsub!("<char>", "'#{c}'") if eval_s.class == String puts eval_s puts c handle_key_bindigs_action(eval_s, c) set_state_to_root end end return true end
mode_bind_key(mode_id, keydef, action)
click to toggle source
# File lib/vimamsa/key_binding_tree.rb, line 502 def mode_bind_key(mode_id, keydef, action) set_state(mode_id, "") # TODO: check is ok? start_state = @cur_state k_arr = keydef.split prev_state = nil s1 = start_state k_arr.each { |i| # check if key has rules for context like q has in # "C q(cntx.recording_macro==true)" match = /(.+)\((.*)\)/.match(i) eval_rule = "" if match key_name = match[1] eval_rule = match[2] else key_name = i end prev_state = s1 # Create a new state for key if it doesn't exist s1 = find_state(key_name, eval_rule) if s1 == nil new_state = State.new(key_name, eval_rule) s1 = new_state @cur_state.children << new_state end set_state(key_name, eval_rule) # TODO: check is ok? } if action == :delete_state prev_state.children.delete(cur_state) else @cur_state.action = action end @cur_state = @root end
remove_keyhandling_override()
click to toggle source
# File lib/vimamsa/key_binding_tree.rb, line 121 def remove_keyhandling_override() @override_keyhandling_callback = nil end
set_default_mode(label)
click to toggle source
# File lib/vimamsa/key_binding_tree.rb, line 76 def set_default_mode(label) @match_state = [@modes[label]] # used for matching input @mode_root_state = @modes[label] @default_mode = label end
set_keyhandling_override(_callback)
click to toggle source
# File lib/vimamsa/key_binding_tree.rb, line 117 def set_keyhandling_override(_callback) @override_keyhandling_callback = _callback end
set_mode(label)
click to toggle source
# File lib/vimamsa/key_binding_tree.rb, line 156 def set_mode(label) @mode_history << @mode_root_state # Check if label in form :label if @modes.has_key?(label) @mode_root_state = @modes[label] set_state_to_root else # Check if label matches mode name in string format for mode in @root.children if mode.key_name == label @mode_root_state = mode end end end $view.draw_cursor() end
set_mode_to_default()
click to toggle source
# File lib/vimamsa/key_binding_tree.rb, line 82 def set_mode_to_default() set_mode(@default_mode) end
set_state(key_name, eval_rule = "")
click to toggle source
# File lib/vimamsa/key_binding_tree.rb, line 179 def set_state(key_name, eval_rule = "") new_state = find_state(key_name, eval_rule) if new_state != nil @cur_state = new_state else @cur_state = @mode_root_state # TODO end end
set_state_to_root()
click to toggle source
# File lib/vimamsa/key_binding_tree.rb, line 188 def set_state_to_root if @mode_root_state.major_modes.size == 1 modelabel = @mode_root_state.major_modes[0] mmode = @modes[modelabel] @match_state = [@mode_root_state, mmode] else @match_state = [@mode_root_state] end @state_trail = [@mode_root_state] # puts get_state_trail_str() # $next_command_count = nil # TODO: set somewhere else? end
to_s()
click to toggle source
Print key bindings to show as documentation or for debugging
# File lib/vimamsa/key_binding_tree.rb, line 203 def to_s() s = "" # @cur_state = @root stack = [[@root, ""]] lines = [] while stack.any? t, p = *stack.pop # t = current state, p = current path if t.children.any? t.children.reverse.each { |c| if c.eval_rule.size > 0 new_p = "#{p} #{c.key_name}(#{c.eval_rule})" else new_p = "#{p} #{c.key_name}" end stack << [c, new_p] } # stack.concat[t.children] else # s += p + " : #{t.action}\n" lines << p + " : #{t.action}" end end s = lines.sort.join("\n") return s end