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.



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/vimamsa/key_binding_tree.rb', line 48

def initialize()
  @next_command_count = nil
  @modes = {}
  @root = State.new("ROOT")
  @cur_state = @root # used for building the tree
  @default_mode = nil
  @default_mode_stack = []
  @mode_history = []
  @state_trail = []
  @last_action = nil
  @cur_action = nil
  @method_handles_repeat = false

  @modifiers = { :ctrl => false, :shift => false, :alt => false } # 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.



46
47
48
# File 'lib/vimamsa/key_binding_tree.rb', line 46

def act_bindings
  @act_bindings
end

#CObject

Returns the value of attribute C.



45
46
47
# File 'lib/vimamsa/key_binding_tree.rb', line 45

def C
  @C
end

#cur_actionObject

Returns the value of attribute cur_action.



45
46
47
# File 'lib/vimamsa/key_binding_tree.rb', line 45

def cur_action
  @cur_action
end

#cur_stateObject

Returns the value of attribute cur_state.



45
46
47
# File 'lib/vimamsa/key_binding_tree.rb', line 45

def cur_state
  @cur_state
end

#default_modeObject

Returns the value of attribute default_mode.



45
46
47
# File 'lib/vimamsa/key_binding_tree.rb', line 45

def default_mode
  @default_mode
end

#default_mode_stackObject (readonly)

Returns the value of attribute default_mode_stack.



46
47
48
# File 'lib/vimamsa/key_binding_tree.rb', line 46

def default_mode_stack
  @default_mode_stack
end

#IObject

Returns the value of attribute I.



45
46
47
# File 'lib/vimamsa/key_binding_tree.rb', line 45

def I
  @I
end

#last_actionObject

Returns the value of attribute last_action.



45
46
47
# File 'lib/vimamsa/key_binding_tree.rb', line 45

def last_action
  @last_action
end

#match_stateObject

Returns the value of attribute match_state.



45
46
47
# File 'lib/vimamsa/key_binding_tree.rb', line 45

def match_state
  @match_state
end

#method_handles_repeatObject

Returns the value of attribute method_handles_repeat.



45
46
47
# File 'lib/vimamsa/key_binding_tree.rb', line 45

def method_handles_repeat
  @method_handles_repeat
end

#mode_root_stateObject (readonly)

Returns the value of attribute mode_root_state.



46
47
48
# File 'lib/vimamsa/key_binding_tree.rb', line 46

def mode_root_state
  @mode_root_state
end

#modifiersObject

Returns the value of attribute modifiers.



45
46
47
# File 'lib/vimamsa/key_binding_tree.rb', line 45

def modifiers
  @modifiers
end

#next_command_countObject

Returns the value of attribute next_command_count.



45
46
47
# File 'lib/vimamsa/key_binding_tree.rb', line 45

def next_command_count
  @next_command_count
end

#rootObject

Returns the value of attribute root.



45
46
47
# File 'lib/vimamsa/key_binding_tree.rb', line 45

def root
  @root
end

#state_trailObject (readonly)

Returns the value of attribute state_trail.



46
47
48
# File 'lib/vimamsa/key_binding_tree.rb', line 46

def state_trail
  @state_trail
end

Instance Method Details

#__set_mode(label) ⇒ Object



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
228
# File 'lib/vimamsa/key_binding_tree.rb', line 203

def __set_mode(label)
  debug "__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
  @cur_mode = label

  if self.get_scope != :editor and !vma.buf.nil?
    vma.buf.mode_stack = @default_mode_stack.clone
  end

  if !vma.gui.view.nil?
    vma.gui.view.draw_cursor()  #TODO: handle outside this class
  end
end

#_bindkey(key, action) ⇒ Object



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
501
502
503
504
505
506
507
508
509
510
511
# File 'lib/vimamsa/key_binding_tree.rb', line 472

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 !vma.actions.include?(action)
    if action.class == String
      reg_act(action, proc { eval(action) }, action)
    end
  end

  m = key.match(/^(\S+)\s(\S.*)$/)
  # Match/split e.g. "VC , , s" to "VC" and ", , s"
  if m
    modetmp = m[1]
    debug [key, modetmp, m].inspect

    # If all of first word are uppercase, e.g. in
    # "VCIX left" => "buf.move(BACKWARD_CHAR)",
    # interpret as each char representing a mode
    modes = modetmp.split("") if modetmp.match(/^\p{Lu}+$/) # Uppercase

    # If all of first word is down case, like in:
    # bindkey "audio space", :audio_stop
    # interpret as whole word representing a mode.
    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

Add keyboard key binding mode based on another mode



135
136
137
138
139
140
141
142
143
144
# File 'lib/vimamsa/key_binding_tree.rb', line 135

def add_minor_mode(id, label, major_mode_label)
  mode = State.new(id)
  @modes[label] = mode
  if @root.nil?
    show_caller
    Ripl.start :binding => binding
  end
  @root.children << mode
  mode.major_modes << major_mode_label
end

#add_mode(id, label, cursortype = :command, name: nil, scope: :buffer) ⇒ Object



124
125
126
127
128
129
130
131
132
# File 'lib/vimamsa/key_binding_tree.rb', line 124

def add_mode(id, label, cursortype = :command, name: nil, scope: :buffer)
  mode = State.new(id, "", cursortype, scope: scope)
  mode.level = 1
  @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
469
470
# File 'lib/vimamsa/key_binding_tree.rb', line 454

def bindkey(key, action)
  if key.class != Array
    # Handle syntax like :
    # "X esc || X ctrl!" => "vma.kbd.to_previous_mode",
    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



146
147
148
# File 'lib/vimamsa/key_binding_tree.rb', line 146

def clear_modifiers()
  # @modifiers = [] #TODO:remove?
end

#cur_mode_strObject



234
235
236
# File 'lib/vimamsa/key_binding_tree.rb', line 234

def cur_mode_str()
  return @mode_root_state.key_name
end

#dump_stateObject



98
99
100
101
102
103
104
105
106
# File 'lib/vimamsa/key_binding_tree.rb', line 98

def dump_state
  debug "dump_state", 2
  pp ["@default_mode_stack", @default_mode_stack]
  pp ["@default_mode", @default_mode]
  pp ["vma.buf.mode_stack", vma.buf.mode_stack]
  pp ["scope", self.get_scope]
  # pp ["@mode_root_state", @mode_root_state]
  # pp ["@match_state", @match_state]
end

#find_state(key_name, eval_rule) ⇒ Object



150
151
152
153
154
155
156
157
158
# File 'lib/vimamsa/key_binding_tree.rb', line 150

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_cursor_typeObject



238
239
240
# File 'lib/vimamsa/key_binding_tree.rb', line 238

def get_cursor_type
  @mode_root_state.cursor_type
end

#get_modeObject



242
243
244
# File 'lib/vimamsa/key_binding_tree.rb', line 242

def get_mode
  return @cur_mode
end

#get_scopeObject



230
231
232
# File 'lib/vimamsa/key_binding_tree.rb', line 230

def get_scope
  @mode_root_state.scope
end

#get_state_trail_strObject



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
# File 'lib/vimamsa/key_binding_tree.rb', line 309

def get_state_trail_str
  s_trail = ""
  last_state = @state_trail.last
  last_state = last_state[0] if last_state.class == Array
  first = true
  for st in @state_trail
    st = st[0] if st.class == Array
    if first
      trailpfx = ""
      if !st.major_modes.empty?
        mmid = st.major_modes.first
        trailpfx = "#{@modes[mmid].to_s}>"
      end
      s_trail << "[#{trailpfx}#{st.to_s}]"
    else
      s_trail << " #{st.to_s}"
    end
    first = false
  end
  children = ""
  for cstate in last_state.children
    act_s = "..."
    act_s = cstate.action.to_s if cstate.action != nil
    children << "  #{cstate.to_s} #{act_s}\n"
  end
  if !@next_command_count.nil?
    s_trail << " #{@next_command_count}"
  end
  return [s_trail, children]
end

#handle_key_bindigs_action(action, c) ⇒ Object



569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
# File 'lib/vimamsa/key_binding_tree.rb', line 569

def handle_key_bindigs_action(action, c)
  # $acth << action #TODO:needed here?
  @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
    debug("COUNT command #{n} times")
  end

  begin
    n.times do
      ret = exec_action(action)

      if vma.macro.is_recording and ret != false
        debug "RECORD ACTION:#{action}", 2
        vma.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.
      # But methods can also handle the number input themselves if vma.kbd.method_handles_repeat=true is set,
    end
    # run_as_idle proc { vma.buf.refresh_cursor; vma.buf.refresh_cursor }, delay: 0.05
  rescue SyntaxError
    message("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

#match(key_name) ⇒ Object



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/vimamsa/key_binding_tree.rb', line 168

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



353
354
355
356
357
358
359
360
361
362
363
364
365
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 353

def match_key_conf(c, translated_c, event_type)
  debug "MATCH KEY CONF: #{[c, translated_c]}"

  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
  end

  if new_state == nil
    debug("NO MATCH")
    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 cnf.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 cnf.debug?
    end

    printf("\n") if cnf.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

  show_state_trail #TODO: check if changed

  return true
end

#mode_bind_key(mode_id, keydef, action) ⇒ Object

Binds a keyboard key combination to an action, for a given keyboard mode like insert (“I”) or command (“C”)



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
540
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
# File 'lib/vimamsa/key_binding_tree.rb', line 515

def mode_bind_key(mode_id, keydef, action)
  # debug "mode_bind_key #{mode_id.inspect}, #{keydef.inspect}, #{action.inspect}", 2
  # Example:
  # bindkey "C , f", :gui_file_finder
  # mode_id = "C", keydef = ", f"
  # and action = :gui_file_finder

  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_with_index { |k, idx|
    # check if key has rules for context like q has in
    # "C q(cntx.recording_macro==true)"
    last_item = false
    if k_arr.size - 1 == idx
      last_item = true
    end
    match = /(.+)\((.*)\)/.match(k)
    eval_rule = ""
    if match
      key_name = match[1]
      eval_rule = match[2]
    else
      key_name = k
    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 or last_item
      new_state = State.new(key_name, eval_rule)
      if last_item
        # Override existing key definition
        @cur_state.children.delete(s1)
      end
      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



164
165
166
# File 'lib/vimamsa/key_binding_tree.rb', line 164

def remove_keyhandling_override()
  @override_keyhandling_callback = nil
end

#set_default_mode(label) ⇒ Object



82
83
84
85
86
87
# File 'lib/vimamsa/key_binding_tree.rb', line 82

def set_default_mode(label)
  @match_state = [@modes[label]]
  @mode_root_state = @modes[label]
  @default_mode = label
  set_mode_stack [label]
end

#set_keyhandling_override(_callback) ⇒ Object



160
161
162
# File 'lib/vimamsa/key_binding_tree.rb', line 160

def set_keyhandling_override(_callback)
  @override_keyhandling_callback = _callback
end

#set_mode(label) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/vimamsa/key_binding_tree.rb', line 69

def set_mode(label)
  return if get_mode == :label
  @match_state = [@modes[label]] # used for matching input
  @mode_root_state = @modes[label]
  # @default_mode = label
  @default_mode_stack << label

  __set_mode(label)
  if !vma.buf.nil?
    # vma.buf.mode_stack = @default_mode_stack.clone
  end
end

#set_mode_stack(ms) ⇒ Object



89
90
91
92
93
94
95
96
# File 'lib/vimamsa/key_binding_tree.rb', line 89

def set_mode_stack(ms)
  debug "set_mode_stack(#{ms})", 2
  show_caller if cnf.debug? # TODO: remove 
  @default_mode_stack = ms
  label = @default_mode_stack[-1]
  @match_state = [@modes[label]]
  @mode_root_state = @modes[label]
end

#set_mode_to_defaultObject



108
109
110
111
112
# File 'lib/vimamsa/key_binding_tree.rb', line 108

def set_mode_to_default()
  # set_mode(@default_mode)
  set_mode_stack [@default_mode_stack[0]]
  __set_mode(@default_mode_stack[0])
end

#set_next_command_count(num) ⇒ Object



340
341
342
343
344
345
346
347
# File 'lib/vimamsa/key_binding_tree.rb', line 340

def set_next_command_count(num)
  if @next_command_count != nil
    @next_command_count = @next_command_count * 10 + num.to_i
  else
    @next_command_count = num.to_i
  end
  debug("NEXT COMMAND COUNT: #{@next_command_count}")
end

#set_state(key_name, eval_rule = "") ⇒ Object



246
247
248
249
250
251
252
253
# File 'lib/vimamsa/key_binding_tree.rb', line 246

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



255
256
257
258
259
260
261
262
263
264
265
# File 'lib/vimamsa/key_binding_tree.rb', line 255

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]
end

#show_state_trailObject



198
199
200
201
# File 'lib/vimamsa/key_binding_tree.rb', line 198

def show_state_trail
  (st, children) = get_state_trail_str()
  vma.gui.statnfo.markup = "<span weight='ultrabold'>#{st}</span>"
end

#to_previous_modeObject



114
115
116
117
118
119
120
121
122
# File 'lib/vimamsa/key_binding_tree.rb', line 114

def to_previous_mode()
  debug "to_previous_mode",2
  debug @default_mode_stack
  if @default_mode_stack.size > 1
    @default_mode_stack.pop
  end
  debug @default_mode_stack
  __set_mode(@default_mode_stack[-1])
end

#to_sObject

Print key bindings to show as documentation or for debugging



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
# File 'lib/vimamsa/key_binding_tree.rb', line 268

def to_s()
  # return self.class.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
          if c.level == 1
            new_p = "#{p} [#{c.key_name}]"
          else
            new_p = "#{p} #{c.key_name}"
          end
        end
        stack << [c, new_p]
      }
      # stack.concat[t.children]
    else
      method_desc = t.action
      if t.action.class == Symbol
        if vma.actions.include?(t.action)
          a = vma.actions[t.action].method_name
          if !a.nil? and !a.empty?
            method_desc = a
          end
        end
      end

      lines << p + " : #{method_desc}"
    end
  end
  s = lines.sort.join("\n")
  return s
end