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.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/vimamsa/key_binding_tree.rb', line 59

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.



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

def act_bindings
  @act_bindings
end

#CObject

Returns the value of attribute C.



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

def C
  @C
end

#cur_actionObject

Returns the value of attribute cur_action.



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

def cur_action
  @cur_action
end

#cur_stateObject

Returns the value of attribute cur_state.



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

def cur_state
  @cur_state
end

#IObject

Returns the value of attribute I.



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

def I
  @I
end

#last_actionObject

Returns the value of attribute last_action.



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

def last_action
  @last_action
end

#match_stateObject

Returns the value of attribute match_state.



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

def match_state
  @match_state
end

#mode_root_stateObject (readonly)

Returns the value of attribute mode_root_state.



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

def mode_root_state
  @mode_root_state
end

#rootObject

Returns the value of attribute root.



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

def root
  @root
end

#state_trailObject (readonly)

Returns the value of attribute state_trail.



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

def state_trail
  @state_trail
end

Instance Method Details

#_bindkey(key, action) ⇒ Object



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

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



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

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)



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

def add_mode(id, label)
  mode = State.new(id)
  mode.level = 1
  @modes[label] = mode
  @root.children << mode
  if @default_mode == nil
    set_default_mode(label)
  end
end

#bindkey(key, action) ⇒ Object



378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/vimamsa/key_binding_tree.rb', line 378

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



105
106
107
# File 'lib/vimamsa/key_binding_tree.rb', line 105

def clear_modifiers()
  @modifiers = []
end

#cur_mode_strObject



181
182
183
# File 'lib/vimamsa/key_binding_tree.rb', line 181

def cur_mode_str()
  return @mode_root_state.key_name
end

#find_state(key_name, eval_rule) ⇒ Object



109
110
111
112
113
114
115
116
117
# File 'lib/vimamsa/key_binding_tree.rb', line 109

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



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/vimamsa/key_binding_tree.rb', line 249

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
      s_trail << "[#{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
  return [s_trail, children]
end

#handle_key_bindigs_action(action, c) ⇒ Object



465
466
467
468
469
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
501
502
503
504
505
506
507
508
509
# File 'lib/vimamsa/key_binding_tree.rb', line 465

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



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

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



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
364
365
366
367
368
369
370
371
372
373
374
375
376
# File 'lib/vimamsa/key_binding_tree.rb', line 276

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

  show_state_trail #TODO: check if changed

  return true
end

#mode_bind_key(mode_id, keydef, action) ⇒ Object



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
453
454
455
456
457
458
459
460
461
462
463
# File 'lib/vimamsa/key_binding_tree.rb', line 426

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



123
124
125
# File 'lib/vimamsa/key_binding_tree.rb', line 123

def remove_keyhandling_override()
  @override_keyhandling_callback = nil
end

#set_default_mode(label) ⇒ Object



77
78
79
80
81
# File 'lib/vimamsa/key_binding_tree.rb', line 77

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



119
120
121
# File 'lib/vimamsa/key_binding_tree.rb', line 119

def set_keyhandling_override(_callback)
  @override_keyhandling_callback = _callback
end

#set_mode(label) ⇒ Object



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/vimamsa/key_binding_tree.rb', line 162

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



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

def set_mode_to_default()
  set_mode(@default_mode)
end

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



185
186
187
188
189
190
191
192
# File 'lib/vimamsa/key_binding_tree.rb', line 185

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



194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/vimamsa/key_binding_tree.rb', line 194

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

#show_state_trailObject



157
158
159
160
# File 'lib/vimamsa/key_binding_tree.rb', line 157

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

#to_sObject

Print key bindings to show as documentation or for debugging



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/vimamsa/key_binding_tree.rb', line 209

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
          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 !$actions[t.action].nil?
          a = $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