vimamsa 0.1.22 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.dockerignore +32 -0
- data/Dockerfile +45 -0
- data/README.md +2 -2
- data/custom_example.rb +38 -9
- data/docker_cmd.sh +7 -0
- data/exe/run_tests.rb +23 -0
- data/img/screenshot1.png +0 -0
- data/img/screenshot2.png +0 -0
- data/lib/vimamsa/actions.rb +8 -0
- data/lib/vimamsa/buffer.rb +165 -53
- data/lib/vimamsa/buffer_changetext.rb +68 -14
- data/lib/vimamsa/buffer_cursor.rb +9 -3
- data/lib/vimamsa/buffer_list.rb +14 -28
- data/lib/vimamsa/buffer_manager.rb +1 -1
- data/lib/vimamsa/conf.rb +33 -1
- data/lib/vimamsa/diff_buffer.rb +185 -0
- data/lib/vimamsa/editor.rb +149 -80
- data/lib/vimamsa/file_finder.rb +6 -2
- data/lib/vimamsa/gui.rb +330 -135
- data/lib/vimamsa/gui_dialog.rb +2 -0
- data/lib/vimamsa/gui_file_panel.rb +94 -0
- data/lib/vimamsa/gui_form_generator.rb +4 -2
- data/lib/vimamsa/gui_func_panel.rb +127 -0
- data/lib/vimamsa/gui_image.rb +2 -4
- data/lib/vimamsa/gui_menu.rb +54 -1
- data/lib/vimamsa/gui_select_window.rb +18 -6
- data/lib/vimamsa/gui_settings.rb +486 -0
- data/lib/vimamsa/gui_sourceview.rb +196 -8
- data/lib/vimamsa/gui_text.rb +0 -22
- data/lib/vimamsa/hyper_plain_text.rb +1 -0
- data/lib/vimamsa/key_actions.rb +54 -31
- data/lib/vimamsa/key_binding_tree.rb +154 -8
- data/lib/vimamsa/key_bindings_vimlike.rb +48 -35
- data/lib/vimamsa/langservp.rb +161 -7
- data/lib/vimamsa/macro.rb +54 -7
- data/lib/vimamsa/main.rb +1 -0
- data/lib/vimamsa/rbvma.rb +5 -0
- data/lib/vimamsa/string_util.rb +56 -0
- data/lib/vimamsa/test_framework.rb +137 -0
- data/lib/vimamsa/util.rb +3 -36
- data/lib/vimamsa/version.rb +1 -1
- data/modules/calculator/calculator.rb +318 -0
- data/modules/calculator/calculator_info.rb +3 -0
- data/modules/terminal/terminal.rb +140 -0
- data/modules/terminal/terminal_info.rb +3 -0
- data/run_tests.rb +89 -0
- data/styles/dark.xml +1 -1
- data/styles/molokai_edit.xml +2 -2
- data/tests/key_bindings.rb +2 -0
- data/tests/test_basic_editing.rb +86 -0
- data/tests/test_copy_paste.rb +88 -0
- data/tests/test_key_bindings.rb +152 -0
- data/tests/test_module_interface.rb +98 -0
- data/tests/test_undo.rb +201 -0
- data/vimamsa.gemspec +6 -5
- metadata +52 -14
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
#
|
|
20
20
|
|
|
21
21
|
class State
|
|
22
|
-
attr_accessor :key_name, :eval_rule, :children, :action, :label, :major_modes, :level, :cursor_type, :keywords
|
|
22
|
+
attr_accessor :key_name, :eval_rule, :children, :action, :label, :major_modes, :level, :cursor_type, :keywords, :root, :parent
|
|
23
23
|
attr_reader :cur_mode, :scope
|
|
24
24
|
|
|
25
25
|
def initialize(key_name, eval_rule = "", ctype = :command, scope: :buffer)
|
|
@@ -31,6 +31,8 @@ class State
|
|
|
31
31
|
@keywords = []
|
|
32
32
|
@action = nil
|
|
33
33
|
@level = 0
|
|
34
|
+
@root = self # parent of a parent ... until mode root
|
|
35
|
+
@parent = nil
|
|
34
36
|
@cursor_type = ctype
|
|
35
37
|
end
|
|
36
38
|
|
|
@@ -55,6 +57,7 @@ class KeyBindingTree
|
|
|
55
57
|
@last_action = nil
|
|
56
58
|
@cur_action = nil
|
|
57
59
|
@method_handles_repeat = false
|
|
60
|
+
@overwriting_state = nil # A branch which has priority over other branches
|
|
58
61
|
|
|
59
62
|
@modifiers = { :ctrl => false, :shift => false, :alt => false } # TODO: create a queue
|
|
60
63
|
@last_event = [nil, nil, nil, nil, nil]
|
|
@@ -66,10 +69,12 @@ class KeyBindingTree
|
|
|
66
69
|
|
|
67
70
|
def set_mode(label)
|
|
68
71
|
return if get_mode == :label
|
|
72
|
+
vma.buf&.new_undo_group
|
|
69
73
|
@match_state = [@modes[label]] # used for matching input
|
|
70
74
|
@mode_root_state = @modes[label]
|
|
71
|
-
|
|
72
|
-
@default_mode_stack
|
|
75
|
+
|
|
76
|
+
#TODO: should not happen? @default_mode_stack[-1] should be always the same as get_mode ?
|
|
77
|
+
@default_mode_stack << label if label != @default_mode_stack[-1]
|
|
73
78
|
|
|
74
79
|
__set_mode(label)
|
|
75
80
|
if !vma.buf.nil?
|
|
@@ -258,17 +263,78 @@ class KeyBindingTree
|
|
|
258
263
|
end
|
|
259
264
|
|
|
260
265
|
def set_state_to_root
|
|
266
|
+
# if root state is a minor mode
|
|
261
267
|
if @mode_root_state.major_modes.size == 1
|
|
262
|
-
modelabel = @mode_root_state.major_modes[0]
|
|
263
|
-
|
|
264
|
-
|
|
268
|
+
modelabel = @mode_root_state.major_modes[0] #TODO: support multiple inheritance?
|
|
269
|
+
parent_mode = @modes[modelabel]
|
|
270
|
+
# Have two branches for the matching (both major and minor modes)
|
|
271
|
+
# @mode_root_state = minor, parent_mode = major
|
|
272
|
+
@match_state = [@mode_root_state, parent_mode]
|
|
273
|
+
@overwriting_state = @mode_root_state # States from this branch have priority over others.
|
|
265
274
|
else
|
|
275
|
+
# if root state is a major mode
|
|
276
|
+
@overwriting_state = nil
|
|
266
277
|
@match_state = [@mode_root_state]
|
|
267
278
|
end
|
|
268
279
|
|
|
269
280
|
@state_trail = [@mode_root_state]
|
|
270
281
|
end
|
|
271
282
|
|
|
283
|
+
# Returns a list of key positions that are free (no action bound) under each
|
|
284
|
+
# existing node in the tree. For each intermediate node (chord prefix),
|
|
285
|
+
# shows which common keys are not yet bound as its children.
|
|
286
|
+
# Does NOT recurse into free positions — only checks children of existing nodes.
|
|
287
|
+
def get_free_bindings(modes: [])
|
|
288
|
+
common_keys = ("a".."z").to_a + %w[
|
|
289
|
+
space return esc tab backspace
|
|
290
|
+
, . / ; ' [ ] \\ `
|
|
291
|
+
]
|
|
292
|
+
# delete ! @ # $ % ^ & * ( ) : " < > ?
|
|
293
|
+
# left right up down home end pageup pagedown
|
|
294
|
+
# 0 1 2 3 4 5 6 7 8 9
|
|
295
|
+
# ctrl-a ctrl-b ctrl-d ctrl-e ctrl-f ctrl-g ctrl-h
|
|
296
|
+
# ctrl-j ctrl-k ctrl-l ctrl-n ctrl-o ctrl-p ctrl-q ctrl-r
|
|
297
|
+
# ctrl-s ctrl-t ctrl-u ctrl-v ctrl-w ctrl-x ctrl-y ctrl-z
|
|
298
|
+
# shift-left shift-right shift-up shift-down
|
|
299
|
+
# F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12
|
|
300
|
+
|
|
301
|
+
lines = []
|
|
302
|
+
stack = [[@root, ""]]
|
|
303
|
+
|
|
304
|
+
while stack.any?
|
|
305
|
+
t, p = *stack.pop
|
|
306
|
+
next unless t.children.any?
|
|
307
|
+
|
|
308
|
+
t.children.each { |c|
|
|
309
|
+
if c.level == 1 && !modes.empty?
|
|
310
|
+
next unless modes.include?(c.key_name)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
if c.level == 1
|
|
314
|
+
new_p = "[#{c.key_name}]"
|
|
315
|
+
elsif c.eval_rule.size > 0
|
|
316
|
+
new_p = "#{p} #{c.key_name}(#{c.eval_rule})"
|
|
317
|
+
else
|
|
318
|
+
new_p = p.empty? ? c.key_name.to_s : "#{p} #{c.key_name}"
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
next unless c.children.any?
|
|
322
|
+
next if c.children.any? { |gc| gc.key_name == "<char>" }
|
|
323
|
+
|
|
324
|
+
bound = c.children.map(&:key_name).to_set
|
|
325
|
+
free = common_keys.reject { |k| bound.include?(k) }
|
|
326
|
+
lines << "#{new_p} : #{free.join(" ")}" if free.any?
|
|
327
|
+
|
|
328
|
+
stack << [c, new_p]
|
|
329
|
+
}
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
lines.sort_by { |l|
|
|
333
|
+
prefix = l.split(" : ").first.split
|
|
334
|
+
[prefix.first, prefix.size, l]
|
|
335
|
+
}.join("\n")
|
|
336
|
+
end
|
|
337
|
+
|
|
272
338
|
def get_by_keywords(modes: [], keywords: [])
|
|
273
339
|
s = ""
|
|
274
340
|
stack = [[@root, ""]]
|
|
@@ -378,7 +444,13 @@ class KeyBindingTree
|
|
|
378
444
|
mmid = st.major_modes.first
|
|
379
445
|
trailpfx = "#{@modes[mmid].to_s}>"
|
|
380
446
|
end
|
|
381
|
-
|
|
447
|
+
mode_str = st.to_s
|
|
448
|
+
mode_str = "COMMAND" if mode_str == "C"
|
|
449
|
+
mode_str = "INSERT" if mode_str == "I"
|
|
450
|
+
mode_str = "VISUAL" if mode_str == "V"
|
|
451
|
+
mode_str = "BROWSE" if mode_str == "B"
|
|
452
|
+
|
|
453
|
+
s_trail << "[#{trailpfx}#{mode_str}]"
|
|
382
454
|
else
|
|
383
455
|
s_trail << " #{st.to_s}"
|
|
384
456
|
end
|
|
@@ -491,7 +563,31 @@ class KeyBindingTree
|
|
|
491
563
|
state_with_children = new_state.select { |s| s.children.any? }
|
|
492
564
|
s_act = new_state.select { |s| s.action != nil }
|
|
493
565
|
|
|
494
|
-
|
|
566
|
+
# Multiple matching states/modes (search has forked)
|
|
567
|
+
if new_state.size > 1
|
|
568
|
+
# puts "AAA"
|
|
569
|
+
# Conflict: One of them has actions (matching should stop),
|
|
570
|
+
# another has children (matching should continue)
|
|
571
|
+
if s_act.any? and state_with_children.any?
|
|
572
|
+
# puts "AAA1"
|
|
573
|
+
a = s_act[0]
|
|
574
|
+
b = state_with_children[0]
|
|
575
|
+
# Running major+minor mode. Minor mode overwriting the major mode
|
|
576
|
+
if a.root == @overwriting_state or b.root == @overwriting_state
|
|
577
|
+
# puts "AAA3:del"
|
|
578
|
+
# Remove those states not belonging to the overwriting branch (minor mode)
|
|
579
|
+
[s_act, state_with_children, new_state].each { |z| z.delete_if { |x| x.root != @overwriting_state } }
|
|
580
|
+
end
|
|
581
|
+
end
|
|
582
|
+
end
|
|
583
|
+
# new_state[0].root.key_name
|
|
584
|
+
|
|
585
|
+
if s_act.any? and state_with_children.any?
|
|
586
|
+
# debug "Conflict: s_act.any? and state_with_children.any?"
|
|
587
|
+
# require "pry"; binding.pry
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
if s_act.any? and !state_with_children.any?
|
|
495
591
|
eval_s = s_act.first.action if eval_s == nil
|
|
496
592
|
# if eval_s.to_s.match(/end_recording/)
|
|
497
593
|
# require "pry"; binding.pry
|
|
@@ -537,6 +633,14 @@ class KeyBindingTree
|
|
|
537
633
|
key.each { |k| _bindkey(k, a, keywords: keywords) }
|
|
538
634
|
end
|
|
539
635
|
|
|
636
|
+
def unbindkey(key)
|
|
637
|
+
if key.class != Array
|
|
638
|
+
key = key.split("||")
|
|
639
|
+
end
|
|
640
|
+
#TODO: test
|
|
641
|
+
key.each { |k| _bindkey(k.strip, :delete_state) }
|
|
642
|
+
end
|
|
643
|
+
|
|
540
644
|
def _bindkey(key, action, keywords: [])
|
|
541
645
|
key.strip!
|
|
542
646
|
key.gsub!(/\s+/, " ")
|
|
@@ -625,6 +729,8 @@ class KeyBindingTree
|
|
|
625
729
|
end
|
|
626
730
|
s1 = new_state
|
|
627
731
|
@cur_state.children << new_state
|
|
732
|
+
new_state.root = @cur_state.root
|
|
733
|
+
new_state.parent = @cur_state
|
|
628
734
|
end
|
|
629
735
|
|
|
630
736
|
set_state(key_name, eval_rule) # TODO: check is ok?
|
|
@@ -639,6 +745,7 @@ class KeyBindingTree
|
|
|
639
745
|
end
|
|
640
746
|
|
|
641
747
|
def handle_key_bindigs_action(action, c)
|
|
748
|
+
trail_str = get_state_trail_str[0]
|
|
642
749
|
# $acth << action #TODO:needed here?
|
|
643
750
|
@method_handles_repeat = false #TODO:??
|
|
644
751
|
n = 1
|
|
@@ -685,6 +792,20 @@ class KeyBindingTree
|
|
|
685
792
|
if !(action.class == String and action.include?("set_next_command_count"))
|
|
686
793
|
@next_command_count = nil
|
|
687
794
|
end
|
|
795
|
+
|
|
796
|
+
if cnf.kbd.show_prev_action? and trail_str.class == String
|
|
797
|
+
len_limit = 35
|
|
798
|
+
action_desc = "UNK"
|
|
799
|
+
if action.class == String && (m = action.match(/\Abuf\.insert_txt\((.+)\)\z/))
|
|
800
|
+
char_part = m[1].gsub("&", "&").gsub("<", "<").gsub(">", ">")
|
|
801
|
+
action_desc = "insert #{char_part}"
|
|
802
|
+
else
|
|
803
|
+
action_desc = vma.actions[action]&.method_name || action.to_s
|
|
804
|
+
action_desc = action_desc[0..len_limit] if action_desc.size > len_limit
|
|
805
|
+
end
|
|
806
|
+
trail_str = trail_str.gsub("&", "&").gsub("<", "<").gsub(">", ">")
|
|
807
|
+
vma.gui.action_trail_label.markup = "<span weight='bold'>#{action_desc}|#{trail_str}</span>"
|
|
808
|
+
end
|
|
688
809
|
end
|
|
689
810
|
end
|
|
690
811
|
|
|
@@ -692,6 +813,10 @@ def bindkey(key, action, keywords: "")
|
|
|
692
813
|
vma.kbd.bindkey(key, action, keywords: keywords)
|
|
693
814
|
end
|
|
694
815
|
|
|
816
|
+
def unbindkey(key)
|
|
817
|
+
vma.kbd.unbindkey(key)
|
|
818
|
+
end
|
|
819
|
+
|
|
695
820
|
def add_keys(keywords, to_add)
|
|
696
821
|
to_add.each { |key, value|
|
|
697
822
|
bindkey(key, value, keywords: keywords)
|
|
@@ -705,11 +830,26 @@ def exec_action(action)
|
|
|
705
830
|
return call_action(action)
|
|
706
831
|
elsif action.class == Proc
|
|
707
832
|
return action.call
|
|
833
|
+
elsif action.class == String && vma.actions.include?(action.to_sym)
|
|
834
|
+
# Symbols serialised through JSON become plain strings; dispatch them as actions.
|
|
835
|
+
return call_action(action.to_sym)
|
|
708
836
|
else
|
|
709
837
|
return eval(action)
|
|
710
838
|
end
|
|
711
839
|
end
|
|
712
840
|
|
|
841
|
+
def show_free_key_bindings()
|
|
842
|
+
kbd_s = "❙Free key binding slots❙\n"
|
|
843
|
+
kbd_s << "\n⦁[Mode] <prefix> : <free keys>⦁\n"
|
|
844
|
+
kbd_s << "[B]=Browse, [C]=Command, [I]=Insert, [V]=Visual\n"
|
|
845
|
+
kbd_s << "Free = not yet bound under that prefix\n"
|
|
846
|
+
kbd_s << "===============================================\n"
|
|
847
|
+
kbd_s << vma.kbd.get_free_bindings
|
|
848
|
+
kbd_s << "\n"
|
|
849
|
+
b = create_new_buffer(kbd_s, "free-key-bindings")
|
|
850
|
+
gui_set_file_lang(b.id, "hyperplaintext")
|
|
851
|
+
end
|
|
852
|
+
|
|
713
853
|
def show_key_bindings()
|
|
714
854
|
kbd_s = "❙Key bindings❙\n"
|
|
715
855
|
kbd_s << "\n⦁[Mode] <keys> : <action>⦁\n"
|
|
@@ -734,6 +874,12 @@ def show_key_bindings()
|
|
|
734
874
|
done.concat(x.lines); kbd_s << x
|
|
735
875
|
kbd_s << "\n"
|
|
736
876
|
|
|
877
|
+
kbd_s << "◼ Hyper Plaintext\n"
|
|
878
|
+
x = vma.kbd.get_by_keywords(modes: ["C"], keywords: ["hyperplaintext"])
|
|
879
|
+
x2 = vma.kbd.get_by_keywords(modes: ["V"], keywords: ["hyperplaintext"])
|
|
880
|
+
done.concat(x.lines); kbd_s << x << "\n" << x2
|
|
881
|
+
kbd_s << "\n"
|
|
882
|
+
|
|
737
883
|
kbd_s << "◼ Core\n"
|
|
738
884
|
x = vma.kbd.get_by_keywords(modes: [], keywords: ["core"])
|
|
739
885
|
x << vma.kbd.get_by_keywords(modes: ["X"], keywords: ["intro"])
|
|
@@ -42,6 +42,14 @@ def insert_move(op)
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
add_keys "intro", {
|
|
45
|
+
"VCX up" => "buf.move(BACKWARD_LINE)",
|
|
46
|
+
"VCX down" => "buf.move(FORWARD_LINE)",
|
|
47
|
+
"VCX right" => "buf.move(FORWARD_CHAR)",
|
|
48
|
+
"VCX left" => "buf.move(BACKWARD_CHAR)",
|
|
49
|
+
"C , b" => :start_buf_manager,
|
|
50
|
+
"VC l" => "buf.move(FORWARD_CHAR)",
|
|
51
|
+
"VC , , s" => :search_actions,
|
|
52
|
+
"C , n b" => :buf_new,
|
|
45
53
|
"C y y" => :copy_cur_line,
|
|
46
54
|
"C P" => :paste_before_cursor,
|
|
47
55
|
"C p" => :paste_after_cursor,
|
|
@@ -70,6 +78,20 @@ add_keys "intro", {
|
|
|
70
78
|
"IX ctrl-n" => :move_next_line,
|
|
71
79
|
"IX ctrl-b" => :move_backward_char,
|
|
72
80
|
"IX ctrl-a" => :jump_beginning_of_line,
|
|
81
|
+
|
|
82
|
+
"CI ctrl-v" => :paste_before_cursor,
|
|
83
|
+
"I ctrl-c" => :copy_selection, #TODO: in control mode also?
|
|
84
|
+
"CI ctrl-x" => :cut_selection,
|
|
85
|
+
"CI ctrl-z" => :undo,
|
|
86
|
+
"CI ctrl-w" => :close_current_buffer,
|
|
87
|
+
"I ctrl-y" => :redo,
|
|
88
|
+
"CI backspace" => :insert_backspace,
|
|
89
|
+
"CI ctrl-o" => :open_file_dialog,
|
|
90
|
+
"I ctrl-l" => :find_in_buffer,
|
|
91
|
+
"I ctrl-g" => :find_next,
|
|
92
|
+
|
|
93
|
+
"I shift-up" => :insert_select_up,
|
|
94
|
+
"I shift-down" => :insert_select_down,
|
|
73
95
|
}
|
|
74
96
|
|
|
75
97
|
add_keys "intro delete", {
|
|
@@ -83,21 +105,16 @@ add_keys "core", {
|
|
|
83
105
|
# File handling
|
|
84
106
|
"C ctrl-s" => :buf_save,
|
|
85
107
|
|
|
108
|
+
"V J" => :V_join_lines,
|
|
86
109
|
# Buffer handling
|
|
87
110
|
# "C B" => "bufs.switch",
|
|
88
111
|
"C tab" => "bufs.switch_to_last_buf",
|
|
89
112
|
# 'C , s'=> 'gui_select_buffer',
|
|
90
113
|
"C , r v b" => :buf_revert,
|
|
91
114
|
"C , c b" => "bufs.close_current_buffer",
|
|
92
|
-
"C , n b" => :buf_new,
|
|
93
115
|
# "C , , ." => "backup_all_buffers()",
|
|
94
|
-
"VC , , s" => :search_actions,
|
|
95
116
|
|
|
96
117
|
# MOVING
|
|
97
|
-
# 'VC h' => 'buf.move(BACKWARD_CHAR)',
|
|
98
|
-
"VC l" => "buf.move(FORWARD_CHAR)",
|
|
99
|
-
# "VC j" => "buf.move(FORWARD_LINE)",
|
|
100
|
-
# "VC k" => "buf.move(BACKWARD_LINE)",
|
|
101
118
|
|
|
102
119
|
"VCI pagedown" => :page_down,
|
|
103
120
|
"VCI pageup" => :page_up,
|
|
@@ -113,10 +130,10 @@ add_keys "core", {
|
|
|
113
130
|
|
|
114
131
|
"I enter" => :insert_new_line,
|
|
115
132
|
|
|
116
|
-
|
|
133
|
+
|
|
134
|
+
"I shift-left" => "insert_select_move(BACKWARD_CHAR)",
|
|
117
135
|
"I shift-right" => "insert_select_move(FORWARD_CHAR)",
|
|
118
136
|
"I shift-down" => "insert_select_move(FORWARD_LINE)",
|
|
119
|
-
"I shift-up" => "insert_select_move(BACKWARD_LINE)",
|
|
120
137
|
"I shift-pagedown" => "insert_select_move(:pagedown)",
|
|
121
138
|
"I shift-pageup" => "insert_select_move(:pageup)",
|
|
122
139
|
|
|
@@ -127,14 +144,6 @@ add_keys "core", {
|
|
|
127
144
|
"I pagedown" => "insert_move(:pagedown)",
|
|
128
145
|
"I pageup" => "insert_move(:pageup)",
|
|
129
146
|
|
|
130
|
-
#TODO:
|
|
131
|
-
"I @shift-click" => "insert_mode_shift_click(charpos)",
|
|
132
|
-
|
|
133
|
-
"VCX left" => "buf.move(BACKWARD_CHAR)",
|
|
134
|
-
"VCX right" => "buf.move(FORWARD_CHAR)",
|
|
135
|
-
"VCX down" => "buf.move(FORWARD_LINE)",
|
|
136
|
-
"VCX up" => "buf.move(BACKWARD_LINE)",
|
|
137
|
-
|
|
138
147
|
# 'C '=> 'buf.jump_word(BACKWARD,END)',#TODO
|
|
139
148
|
"VC f <char>" => "buf.jump_to_next_instance_of_char(<char>)",
|
|
140
149
|
"VC F <char>" => "buf.jump_to_next_instance_of_char(<char>,BACKWARD)",
|
|
@@ -159,7 +168,7 @@ add_keys "core", {
|
|
|
159
168
|
|
|
160
169
|
"R <char>" => "readchar_new_char(<char>)",
|
|
161
170
|
|
|
162
|
-
"C n" =>
|
|
171
|
+
"C n" => :find_next,
|
|
163
172
|
"C N" => "$search.jump_to_previous()",
|
|
164
173
|
|
|
165
174
|
"C C" => :content_search,
|
|
@@ -224,8 +233,6 @@ add_keys "core", {
|
|
|
224
233
|
# "V ctrl-c" => "buf.comment_selection",
|
|
225
234
|
"V ctrl-x" => "buf.comment_selection(:uncomment)",
|
|
226
235
|
|
|
227
|
-
"CI ctrl-v" => "buf.paste(BEFORE)",
|
|
228
|
-
"CI backspace" => "buf.delete(BACKWARD_CHAR)",
|
|
229
236
|
|
|
230
237
|
# Marks
|
|
231
238
|
"CV m <char>" => "buf.mark_current_position(<char>)",
|
|
@@ -237,6 +244,8 @@ add_keys "core", {
|
|
|
237
244
|
|
|
238
245
|
# Replace mode
|
|
239
246
|
"X esc || X ctrl!" => "vma.kbd.to_previous_mode",
|
|
247
|
+
# "X p" => :paste_over_after, #TODO
|
|
248
|
+
"X ctrl-v" => :paste_over_before,
|
|
240
249
|
"X <char>" => "buf.replace_with_char(<char>);buf.move(FORWARD_CHAR)",
|
|
241
250
|
|
|
242
251
|
# Macros
|
|
@@ -244,19 +253,22 @@ add_keys "core", {
|
|
|
244
253
|
# "C q a" => 'vma.macro.start_recording("a")',
|
|
245
254
|
|
|
246
255
|
"macro q" => "vma.kbd.to_previous_mode; vma.macro.end_recording",
|
|
247
|
-
"macro q z" => "vma.kbd.to_previous_mode; vma.macro.end_recording",
|
|
256
|
+
# "macro q z" => "vma.kbd.to_previous_mode; vma.macro.end_recording",
|
|
248
257
|
|
|
249
258
|
# "VC q(vma.macro.is_recording==true)" => "vma.macro.end_recording", # TODO: does not work
|
|
250
259
|
# "VC o(vma.macro.is_recording==true)" => "vma.macro.end_recording", # TODO: does not work
|
|
251
260
|
# "VC q q(vma.macro.is_recording==true)" => "vma.macro.end_recording",
|
|
252
261
|
"VC q <char>" => "vma.kbd.set_mode(:macro);vma.macro.start_recording(<char>)",
|
|
253
262
|
# 'C q'=> 'vma.macro.end_recording', #TODO
|
|
254
|
-
|
|
263
|
+
|
|
264
|
+
# "C q v" => "vma.kbd.to_previous_mode; vma.macro.end_recording", #TODO
|
|
265
|
+
|
|
255
266
|
# 'C v'=> 'vma.macro.end_recording',
|
|
256
267
|
# "C M" => 'vma.macro.run_last_macro',
|
|
257
268
|
"C @ <char>" => "vma.macro.run_macro(<char>)",
|
|
258
269
|
"C , m S" => 'vma.macro.save_macro("a")',
|
|
259
270
|
"C , m s" => "vma.macro.save",
|
|
271
|
+
"C , m h" => :show_message_history,
|
|
260
272
|
|
|
261
273
|
# "C ." => "repeat_last_action", # TODO
|
|
262
274
|
"VC ;" => "repeat_last_find",
|
|
@@ -277,7 +289,6 @@ add_keys "core", {
|
|
|
277
289
|
"I space" => 'buf.insert_txt(" ")',
|
|
278
290
|
# "I return" => 'buf.insert_new_line()',
|
|
279
291
|
|
|
280
|
-
"CI ctrl-o" => :open_file_dialog,
|
|
281
292
|
"C , a" => :ack_search,
|
|
282
293
|
"C d w" => :delete_to_next_word_start,
|
|
283
294
|
|
|
@@ -305,19 +316,6 @@ add_keys "core", {
|
|
|
305
316
|
"C , , u" => :update_file_index,
|
|
306
317
|
"C , s a" => :buf_save_as,
|
|
307
318
|
"VC , r r" => :gui_search_replace,
|
|
308
|
-
"V , t b" => :set_style_bold,
|
|
309
|
-
"V , t l" => :set_style_link,
|
|
310
|
-
"V J" => :V_join_lines,
|
|
311
|
-
"V , t c" => :clear_formats,
|
|
312
|
-
"C , t h" => :set_line_style_heading,
|
|
313
|
-
"C , t 1" => :set_line_style_h1,
|
|
314
|
-
"C , t 2" => :set_line_style_h2,
|
|
315
|
-
"C , t 3" => :set_line_style_h3,
|
|
316
|
-
"C , t 4" => :set_line_style_h4,
|
|
317
|
-
"C , t b" => :set_line_style_bold,
|
|
318
|
-
"C , t t" => :set_line_style_title,
|
|
319
|
-
"C , t c" => :clear_line_styles,
|
|
320
|
-
"C , b" => :start_buf_manager,
|
|
321
319
|
"C , w" => :toggle_active_window,
|
|
322
320
|
"C , , w" => :toggle_two_column,
|
|
323
321
|
|
|
@@ -341,6 +339,20 @@ add_keys "core", {
|
|
|
341
339
|
"V a d" => [:delete_append_selection, proc { buf.delete(SELECTION, :append) }, "Delete and append selection"]
|
|
342
340
|
}
|
|
343
341
|
|
|
342
|
+
add_keys "hyperplaintext", {
|
|
343
|
+
"V , t b" => :set_style_bold,
|
|
344
|
+
"V , t l" => :set_style_link,
|
|
345
|
+
"V , t c" => :clear_formats,
|
|
346
|
+
"C , t h" => :set_line_style_heading,
|
|
347
|
+
"C , t 1" => :set_line_style_h1,
|
|
348
|
+
"C , t 2" => :set_line_style_h2,
|
|
349
|
+
"C , t 3" => :set_line_style_h3,
|
|
350
|
+
"C , t 4" => :set_line_style_h4,
|
|
351
|
+
"C , t b" => :set_line_style_bold,
|
|
352
|
+
"C , t t" => :set_line_style_title,
|
|
353
|
+
"C , t c" => :clear_line_styles,
|
|
354
|
+
}
|
|
355
|
+
|
|
344
356
|
bindkey ["VCB M", "B m"], :run_last_macro
|
|
345
357
|
|
|
346
358
|
add_keys "experimental", {
|
|
@@ -352,6 +364,7 @@ add_keys "experimental", {
|
|
|
352
364
|
# "CV , R" => "restart_application", #TODO: does not work
|
|
353
365
|
"I ctrl-h" => :show_autocomplete, #TODO: does not work
|
|
354
366
|
"C , d m" => :kbd_dump_state,
|
|
367
|
+
"C , ; ." => :increment_word,
|
|
355
368
|
"C , d d" => "debug_dump_deltas",
|
|
356
369
|
"C , d c" => "debug_dump_clipboard",
|
|
357
370
|
"C , d b" => "debug_print_buffer",
|
data/lib/vimamsa/langservp.rb
CHANGED
|
@@ -90,7 +90,7 @@ class LangSrv
|
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
def handle_delta(delta, fpath, version)
|
|
93
|
-
fpuri =
|
|
93
|
+
fpuri = file_uri(fpath)
|
|
94
94
|
|
|
95
95
|
# delta[0]: char position
|
|
96
96
|
# delta[1]: INSERT or DELETE
|
|
@@ -147,7 +147,8 @@ class LangSrv
|
|
|
147
147
|
# r = @resp.delete_at(0)
|
|
148
148
|
end
|
|
149
149
|
|
|
150
|
-
def get_definition(
|
|
150
|
+
def get_definition(fpath_or_uri, lpos, cpos)
|
|
151
|
+
fpuri = fpath_or_uri.start_with?("file://") ? fpath_or_uri : file_uri(fpath_or_uri)
|
|
151
152
|
a = LSP::Interface::DefinitionParams.new(
|
|
152
153
|
position: LSP::Interface::Position.new(line: lpos, character: cpos),
|
|
153
154
|
text_document: LSP::Interface::TextDocumentIdentifier.new(uri: fpuri),
|
|
@@ -172,22 +173,175 @@ class LangSrv
|
|
|
172
173
|
return nil
|
|
173
174
|
end
|
|
174
175
|
|
|
175
|
-
|
|
176
|
+
# LSP SymbolKind values for callable things
|
|
177
|
+
FUNCTION_KINDS = [6, 9, 12].freeze # Function, Constructor, Method
|
|
178
|
+
CLASS_KINDS = [5, 10, 11, 23].freeze # Class, Module, Interface, Namespace
|
|
179
|
+
|
|
180
|
+
# Recursively collect symbols of function kinds.
|
|
181
|
+
# Handles both flat SymbolInformation[] and nested DocumentSymbol[] (with :children).
|
|
182
|
+
def collect_functions(symbols)
|
|
183
|
+
result = []
|
|
184
|
+
symbols.each do |s|
|
|
185
|
+
result << s if FUNCTION_KINDS.include?(s[:kind])
|
|
186
|
+
result.concat(collect_functions(s[:children])) if s[:children].is_a?(Array)
|
|
187
|
+
end
|
|
188
|
+
result
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Flatten all FUNCTION_KINDS from a symbol subtree into [{name:, line:}, ...].
|
|
192
|
+
def flatten_functions(symbols)
|
|
193
|
+
result = []
|
|
194
|
+
symbols.each do |s|
|
|
195
|
+
if FUNCTION_KINDS.include?(s[:kind])
|
|
196
|
+
line = s.dig(:range, :start, :line)
|
|
197
|
+
result << { name: s[:name], line: line ? line + 1 : 0 }
|
|
198
|
+
end
|
|
199
|
+
result.concat(flatten_functions(s[:children])) if s[:children].is_a?(Array)
|
|
200
|
+
end
|
|
201
|
+
result
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Build groups from a nested DocumentSymbol[].
|
|
205
|
+
# Returns [{name:, line:, functions: [{name:, line:}, ...]}, ...]
|
|
206
|
+
# name: nil = top-level (ungrouped) functions.
|
|
207
|
+
def collect_groups_nested(symbols)
|
|
208
|
+
groups = []
|
|
209
|
+
top_funcs = []
|
|
210
|
+
symbols.each do |s|
|
|
211
|
+
if CLASS_KINDS.include?(s[:kind])
|
|
212
|
+
line = s.dig(:range, :start, :line)
|
|
213
|
+
funcs = s[:children].is_a?(Array) ? flatten_functions(s[:children]) : []
|
|
214
|
+
groups << { name: s[:name], line: line ? line + 1 : 0, functions: funcs }
|
|
215
|
+
elsif FUNCTION_KINDS.include?(s[:kind])
|
|
216
|
+
line = s.dig(:range, :start, :line)
|
|
217
|
+
top_funcs << { name: s[:name], line: line ? line + 1 : 0 }
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
groups.unshift({ name: nil, line: nil, functions: top_funcs }) unless top_funcs.empty?
|
|
221
|
+
groups
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Build groups from a flat SymbolInformation[] using :containerName.
|
|
225
|
+
def collect_groups_flat(symbols)
|
|
226
|
+
|
|
227
|
+
by_container = {}
|
|
228
|
+
symbols.each do |s|
|
|
229
|
+
next unless FUNCTION_KINDS.include?(s[:kind])
|
|
230
|
+
container = s[:containerName] || ""
|
|
231
|
+
line = s.dig(:location, :range, :start, :line)
|
|
232
|
+
by_container[container] ||= []
|
|
233
|
+
by_container[container] << { name: s[:name], line: line ? line + 1 : 0 }
|
|
234
|
+
end
|
|
235
|
+
groups = []
|
|
236
|
+
top_funcs = by_container.delete("") || []
|
|
237
|
+
by_container.keys.sort.each do |container|
|
|
238
|
+
groups << { name: container, line: 0, functions: by_container[container] }
|
|
239
|
+
end
|
|
240
|
+
groups << { name: nil, line: nil, functions: top_funcs } unless top_funcs.empty?
|
|
241
|
+
|
|
242
|
+
groups
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Send textDocument/documentSymbol and return [{name:, line:}, ...] for functions/methods.
|
|
246
|
+
# Returns nil on error, empty array if no functions found.
|
|
247
|
+
def document_functions(fpath)
|
|
248
|
+
ensure_file_open(fpath)
|
|
249
|
+
fpuri = file_uri(fpath)
|
|
250
|
+
a = LSP::Interface::DocumentSymbolParams.new(
|
|
251
|
+
text_document: LSP::Interface::TextDocumentIdentifier.new(uri: fpuri),
|
|
252
|
+
)
|
|
253
|
+
id = new_id
|
|
254
|
+
@writer.write(id: id, params: a, method: "textDocument/documentSymbol")
|
|
255
|
+
r = wait_for_response(id)
|
|
256
|
+
return nil if r.nil?
|
|
257
|
+
|
|
258
|
+
symbols = r[:result]
|
|
259
|
+
return nil if !symbols.is_a?(Array)
|
|
260
|
+
|
|
261
|
+
functions = collect_functions(symbols)
|
|
262
|
+
functions.map do |s|
|
|
263
|
+
line = s.dig(:range, :start, :line) || s.dig(:location, :range, :start, :line)
|
|
264
|
+
{ name: s[:name], line: line ? line + 1 : 0 }
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Like document_functions but returns functions grouped by class/module.
|
|
269
|
+
# Returns [{name:, line:, functions: [{name:, line:}, ...]}, ...]
|
|
270
|
+
# name: nil means top-level (ungrouped) functions.
|
|
271
|
+
def document_functions_grouped(fpath)
|
|
272
|
+
ensure_file_open(fpath)
|
|
273
|
+
fpuri = file_uri(fpath)
|
|
274
|
+
a = LSP::Interface::DocumentSymbolParams.new(
|
|
275
|
+
text_document: LSP::Interface::TextDocumentIdentifier.new(uri: fpuri),
|
|
276
|
+
)
|
|
277
|
+
id = new_id
|
|
278
|
+
@writer.write(id: id, params: a, method: "textDocument/documentSymbol")
|
|
279
|
+
r = wait_for_response(id)
|
|
280
|
+
return nil if r.nil?
|
|
281
|
+
|
|
282
|
+
symbols = r[:result]
|
|
283
|
+
return nil unless symbols.is_a?(Array)
|
|
284
|
+
|
|
285
|
+
# Detect nested (DocumentSymbol) vs flat (SymbolInformation) format
|
|
286
|
+
if symbols.any? { |s| s.key?(:children) }
|
|
287
|
+
collect_groups_nested(symbols)
|
|
288
|
+
else
|
|
289
|
+
collect_groups_flat(symbols)
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Send textDocument/documentSymbol, filter to functions/methods, and puts them.
|
|
294
|
+
def print_functions(fpath)
|
|
295
|
+
funcs = document_functions(fpath)
|
|
296
|
+
if funcs.nil? || funcs.empty?
|
|
297
|
+
puts "(no functions found in #{File.basename(fpath)})"
|
|
298
|
+
return
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
puts "=== Functions in #{File.basename(fpath)} ==="
|
|
302
|
+
funcs.each do |f|
|
|
303
|
+
line_str = f[:line] > 0 ? ":#{f[:line]}" : ""
|
|
304
|
+
puts " #{f[:name]}#{line_str}"
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def file_uri(fp)
|
|
309
|
+
URI.join("file:///", fp).to_s
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def open_file(fp, fc = nil, lang: nil)
|
|
176
313
|
debug "open_file", 2
|
|
314
|
+
@opened_files ||= {}
|
|
315
|
+
fpuri = file_uri(fp)
|
|
177
316
|
fc = IO.read(fp) if fc.nil?
|
|
178
|
-
|
|
179
|
-
encoded_filepath = URI.encode_www_form_component(fp)
|
|
180
|
-
fpuri = URI.parse("file://#{encoded_filepath}")
|
|
317
|
+
lang ||= @lang
|
|
181
318
|
|
|
182
319
|
a = LSP::Interface::DidOpenTextDocumentParams.new(
|
|
183
320
|
text_document: LSP::Interface::TextDocumentItem.new(
|
|
184
321
|
uri: fpuri,
|
|
185
322
|
text: fc,
|
|
186
|
-
language_id:
|
|
323
|
+
language_id: lang,
|
|
187
324
|
version: 1,
|
|
188
325
|
),
|
|
189
326
|
)
|
|
190
327
|
|
|
191
328
|
@writer.write(method: "textDocument/didOpen", params: a)
|
|
329
|
+
@opened_files[fpuri] = true
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def ensure_file_open(fp, fc = nil)
|
|
333
|
+
@opened_files ||= {}
|
|
334
|
+
fpuri = file_uri(fp)
|
|
335
|
+
open_file(fp, fc) unless @opened_files[fpuri]
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def lsp_print_functions
|
|
340
|
+
return unless vma.buf&.fname
|
|
341
|
+
lsp = LangSrv.get(vma.buf.lang)
|
|
342
|
+
if lsp.nil?
|
|
343
|
+
message("No LSP server available for #{vma.buf.lang}")
|
|
344
|
+
return
|
|
192
345
|
end
|
|
346
|
+
lsp.print_functions(vma.buf.fname)
|
|
193
347
|
end
|