Class: KeyBindingTree

Inherits:
Object
  • Object
show all
Defined in:
lib/vimamsa/key_binding_tree.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeKeyBindingTree

Returns a new instance of KeyBindingTree.



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 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

Instance Attribute Details

#act_bindingsObject (readonly)

Returns the value of attribute act_bindings.



56
57
58
# File 'lib/vimamsa/key_binding_tree.rb', line 56

def act_bindings
  @act_bindings
end

#CObject

Returns the value of attribute C.



55
56
57
# File 'lib/vimamsa/key_binding_tree.rb', line 55

def C
  @C
end

#cur_actionObject

Returns the value of attribute cur_action.



55
56
57
# File 'lib/vimamsa/key_binding_tree.rb', line 55

def cur_action
  @cur_action
end

#cur_stateObject

Returns the value of attribute cur_state.



55
56
57
# File 'lib/vimamsa/key_binding_tree.rb', line 55

def cur_state
  @cur_state
end

#IObject

Returns the value of attribute I.



55
56
57
# File 'lib/vimamsa/key_binding_tree.rb', line 55

def I
  @I
end

#last_actionObject

Returns the value of attribute last_action.



55
56
57
# File 'lib/vimamsa/key_binding_tree.rb', line 55

def last_action
  @last_action
end

#match_stateObject

Returns the value of attribute match_state.



55
56
57
# File 'lib/vimamsa/key_binding_tree.rb', line 55

def match_state
  @match_state
end

#mode_root_stateObject (readonly)

Returns the value of attribute mode_root_state.



56
57
58
# File 'lib/vimamsa/key_binding_tree.rb', line 56

def mode_root_state
  @mode_root_state
end

#rootObject

Returns the value of attribute root.



55
56
57
# File 'lib/vimamsa/key_binding_tree.rb', line 55

def root
  @root
end

#state_trailObject (readonly)

Returns the value of attribute state_trail.



56
57
58
# File 'lib/vimamsa/key_binding_tree.rb', line 56

def state_trail
  @state_trail
end

Instance Method Details

#_bindkey(key, action) ⇒ Object



470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
# 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]
    debug [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) ⇒ Object



96
97
98
99
100
101
# 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) ⇒ Object

$kbd.add_mode(“I”, :insert)



87
88
89
90
91
92
93
94
# 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) ⇒ Object



454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
# 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_modifiersObject



103
104
105
# File 'lib/vimamsa/key_binding_tree.rb', line 103

def clear_modifiers()
  @modifiers = []
end

#cur_mode_strObject



175
176
177
# 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) ⇒ Object



107
108
109
110
111
112
113
114
115
# 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_strObject



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# 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) ⇒ Object



541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
# 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
    debug "BACKTRACE"
    debug e.backtrace
    debug e.inspect
    debug "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) ⇒ Object

Receive keyboard event from Qt



366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
# File 'lib/vimamsa/key_binding_tree.rb', line 366

def handle_key_event(event)
  start_profiler
  # debug "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

  # debug "----D------------"
  # debug @modifiers.inspect
  # debug event.inspect
  # debug event[4] & ALTMODIFIER
  # debug "-----------------"

  @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
  # debug "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
  # debug "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) ⇒ Object



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# 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: ")
      # debug [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 != ""
        debug "CHECK EVAL: #{c.eval_rule}"
        if eval(c.eval_rule)
          new_state << c
          debug "EVAL TRUE"
        else
          debug "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) ⇒ Object

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



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# 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
    debug get_state_trail_str()
    # # debug "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
    # debug "CUR STATE: #{s_trail}"
    # for cstate in new_state[0].children
    # act_s = "..."
    # act_s = cstate.action.to_s if cstate.action != nil
    # debug "  #{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) sets 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
      debug "FOUND MATCH:#{eval_s}"
      debug "CHAR: #{c}"
      c.gsub!("\\", %q{\\\\} * 4) # Escape \ -chars
      c.gsub!("'", "#{'\\' * 4}'") # Escape ' -chars

      eval_s.gsub!("<char>", "'#{c}'") if eval_s.class == String
      debug eval_s
      debug c
      handle_key_bindigs_action(eval_s, c)
      set_state_to_root
    end
  end

  return true
end

#mode_bind_key(mode_id, keydef, action) ⇒ Object



502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
# 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_overrideObject



121
122
123
# File 'lib/vimamsa/key_binding_tree.rb', line 121

def remove_keyhandling_override()
  @override_keyhandling_callback = nil
end

#set_default_mode(label) ⇒ Object



76
77
78
79
80
# 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) ⇒ Object



117
118
119
# File 'lib/vimamsa/key_binding_tree.rb', line 117

def set_keyhandling_override(_callback)
  @override_keyhandling_callback = _callback
end

#set_mode(label) ⇒ Object



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# 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_defaultObject



82
83
84
# 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 = "") ⇒ Object



179
180
181
182
183
184
185
186
# 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_rootObject



188
189
190
191
192
193
194
195
196
197
198
199
200
# 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]
  # debug get_state_trail_str()
  # $next_command_count = nil # TODO: set somewhere else?
end

#to_sObject

Print key bindings to show as documentation or for debugging



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# 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