vimamsa 0.1.23 → 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/custom_example.rb +37 -11
- data/docker_cmd.sh +7 -0
- data/exe/run_tests.rb +23 -0
- data/lib/vimamsa/actions.rb +8 -0
- data/lib/vimamsa/buffer.rb +38 -47
- data/lib/vimamsa/buffer_changetext.rb +49 -12
- data/lib/vimamsa/buffer_list.rb +2 -28
- data/lib/vimamsa/conf.rb +30 -0
- data/lib/vimamsa/diff_buffer.rb +80 -32
- data/lib/vimamsa/editor.rb +54 -67
- data/lib/vimamsa/file_finder.rb +6 -2
- data/lib/vimamsa/gui.rb +247 -63
- data/lib/vimamsa/gui_file_panel.rb +1 -0
- data/lib/vimamsa/gui_func_panel.rb +127 -0
- data/lib/vimamsa/gui_menu.rb +42 -0
- data/lib/vimamsa/gui_select_window.rb +17 -6
- data/lib/vimamsa/gui_settings.rb +344 -13
- data/lib/vimamsa/gui_sourceview.rb +116 -2
- data/lib/vimamsa/gui_text.rb +0 -22
- data/lib/vimamsa/hyper_plain_text.rb +1 -0
- data/lib/vimamsa/key_actions.rb +30 -29
- data/lib/vimamsa/key_binding_tree.rb +85 -3
- data/lib/vimamsa/key_bindings_vimlike.rb +4 -0
- data/lib/vimamsa/langservp.rb +161 -7
- data/lib/vimamsa/macro.rb +54 -7
- data/lib/vimamsa/rbvma.rb +2 -0
- data/lib/vimamsa/test_framework.rb +137 -0
- 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 +46 -14
data/lib/vimamsa/diff_buffer.rb
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
# Map a "line number in a unified diff output" to the corresponding
|
|
2
|
-
# line
|
|
2
|
+
# line in the new/changed file (the + side), together with the file it belongs to.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# sets starting counters. Then walk each hunk line:
|
|
4
|
+
# Handles multi-file diffs: each --- / +++ pair sets the active file; each @@
|
|
5
|
+
# hunk header resets the line counters. Walking hunk lines:
|
|
7
6
|
# ' ' => old++, new++
|
|
8
7
|
# '-' => old++
|
|
9
8
|
# '+' => new++
|
|
10
9
|
#
|
|
11
|
-
#
|
|
12
|
-
# ' ' or '+' => it corresponds to current new line (before increment)
|
|
13
|
-
# '-' => it has no new-file line (deleted). We return nil.
|
|
10
|
+
# Returns [new_path, old_path, line_no] or nil for deleted / unmappable lines.
|
|
14
11
|
#
|
|
15
12
|
class DiffLineMapper
|
|
16
13
|
HUNK_RE = /^@@\s+-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s+@@/
|
|
@@ -20,18 +17,36 @@ class DiffLineMapper
|
|
|
20
17
|
end
|
|
21
18
|
|
|
22
19
|
# Given a 1-based line number in the diff output, return:
|
|
23
|
-
# - Integer:
|
|
24
|
-
# - nil:
|
|
20
|
+
# - [new_path, old_path, Integer]: raw +++ path, raw --- path, 1-based new-file line
|
|
21
|
+
# - nil: if the diff line is a deletion ('-') or cannot be mapped
|
|
25
22
|
def new_line_for_diff_lineno(diff_lineno)
|
|
26
23
|
raise ArgumentError, "diff line number must be >= 1" if diff_lineno.to_i < 1
|
|
27
24
|
idx = diff_lineno.to_i - 1
|
|
28
25
|
return nil if idx >= @lines.length
|
|
29
26
|
|
|
27
|
+
old_path = nil
|
|
28
|
+
new_path = nil
|
|
30
29
|
old = nil
|
|
31
30
|
new_ = nil
|
|
32
31
|
in_hunk = false
|
|
33
32
|
|
|
34
33
|
@lines.each_with_index do |line, i|
|
|
34
|
+
# File headers reset hunk state and record current file paths.
|
|
35
|
+
# These appear outside hunks, but guard against malformed diffs too.
|
|
36
|
+
if line.start_with?('--- ')
|
|
37
|
+
old_path = line[4..].split("\t").first.strip
|
|
38
|
+
in_hunk = false
|
|
39
|
+
old = new_ = nil
|
|
40
|
+
next
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if line.start_with?('+++ ')
|
|
44
|
+
new_path = line[4..].split("\t").first.strip
|
|
45
|
+
in_hunk = false
|
|
46
|
+
old = new_ = nil
|
|
47
|
+
next
|
|
48
|
+
end
|
|
49
|
+
|
|
35
50
|
if (m = line.match(HUNK_RE))
|
|
36
51
|
old = m[1].to_i
|
|
37
52
|
new_ = m[3].to_i
|
|
@@ -41,17 +56,11 @@ class DiffLineMapper
|
|
|
41
56
|
|
|
42
57
|
next unless in_hunk
|
|
43
58
|
|
|
44
|
-
if line.start_with?('--- ', '+++ ')
|
|
45
|
-
in_hunk = false
|
|
46
|
-
old = new_ = nil
|
|
47
|
-
next
|
|
48
|
-
end
|
|
49
|
-
|
|
50
59
|
if i == idx
|
|
51
60
|
return nil unless new_
|
|
52
61
|
case line.getbyte(0)
|
|
53
|
-
when '+'.ord then return new_
|
|
54
|
-
when ' '.ord then return new_
|
|
62
|
+
when '+'.ord then return [new_path, old_path, new_]
|
|
63
|
+
when ' '.ord then return [new_path, old_path, new_]
|
|
55
64
|
when '-'.ord then return nil
|
|
56
65
|
else return nil
|
|
57
66
|
end
|
|
@@ -94,23 +103,18 @@ end
|
|
|
94
103
|
def diff_buffer_jump_to_source()
|
|
95
104
|
mapper = DiffLineMapper.new(vma.buf.to_s)
|
|
96
105
|
cur_lpos = vma.buf.lpos + 1
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
path = $1.split("\t").first.strip
|
|
103
|
-
# git diff prefixes paths with "a/" — strip it and resolve from git root
|
|
104
|
-
if path.start_with?("a/")
|
|
105
|
-
git_root = `git rev-parse --show-toplevel 2>/dev/null`.strip
|
|
106
|
-
path = File.join(git_root, path[2..]) unless git_root.empty?
|
|
107
|
-
end
|
|
108
|
-
orig_path = path
|
|
109
|
-
break
|
|
110
|
-
end
|
|
106
|
+
result = mapper.new_line_for_diff_lineno(cur_lpos)
|
|
107
|
+
|
|
108
|
+
if result.nil?
|
|
109
|
+
message("No source line for this position")
|
|
110
|
+
return
|
|
111
111
|
end
|
|
112
|
+
|
|
113
|
+
new_path, old_path, to_line = result
|
|
114
|
+
orig_path = resolve_diff_path(new_path, old_path)
|
|
115
|
+
|
|
112
116
|
if orig_path.nil? || !File.exist?(orig_path)
|
|
113
|
-
message("Could not find
|
|
117
|
+
message("Could not find file: #{new_path || old_path}")
|
|
114
118
|
return
|
|
115
119
|
end
|
|
116
120
|
|
|
@@ -118,6 +122,50 @@ def diff_buffer_jump_to_source()
|
|
|
118
122
|
center_on_current_line
|
|
119
123
|
end
|
|
120
124
|
|
|
125
|
+
# Resolve a +++ / --- path pair to an absolute filesystem path.
|
|
126
|
+
# Prefers new_path (the post-change file); falls back to old_path
|
|
127
|
+
# when new_path is /dev/null or a temp file that no longer exists.
|
|
128
|
+
def resolve_diff_path(new_path, old_path)
|
|
129
|
+
git_root = `git rev-parse --show-toplevel 2>/dev/null`.strip
|
|
130
|
+
|
|
131
|
+
expand = lambda do |path|
|
|
132
|
+
return nil if path.nil? || path == "/dev/null"
|
|
133
|
+
# git diff uses "a/" / "b/" prefixes for old/new sides
|
|
134
|
+
if path.start_with?("b/") || path.start_with?("a/")
|
|
135
|
+
rel = path[2..]
|
|
136
|
+
return git_root.empty? ? File.expand_path(rel) : File.join(git_root, rel)
|
|
137
|
+
end
|
|
138
|
+
path.start_with?("/") ? path : File.expand_path(path)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
path = expand.call(new_path)
|
|
142
|
+
return path if path && File.exist?(path)
|
|
143
|
+
|
|
144
|
+
expand.call(old_path)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def git_diff_w()
|
|
148
|
+
return if !if_cmd_exists("git")
|
|
149
|
+
diff_buffer_init
|
|
150
|
+
|
|
151
|
+
dir = vma.buf.fname ? File.dirname(vma.buf.fname) : Dir.pwd
|
|
152
|
+
git_root = `git -C #{Shellwords.escape(dir)} rev-parse --show-toplevel 2>/dev/null`.strip
|
|
153
|
+
if git_root.empty?
|
|
154
|
+
message("Not a git repository")
|
|
155
|
+
return
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
bufstr = run_cmd("git -C #{Shellwords.escape(git_root)} diff -w")
|
|
159
|
+
if bufstr.strip.empty?
|
|
160
|
+
message("git diff -w: no changes")
|
|
161
|
+
return
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
create_new_file(nil, bufstr)
|
|
165
|
+
gui_set_file_lang(vma.buf.id, "diff")
|
|
166
|
+
vma.kbd.set_mode(:diffview)
|
|
167
|
+
end
|
|
168
|
+
|
|
121
169
|
def git_diff_buffer()
|
|
122
170
|
return if !if_cmd_exists("git")
|
|
123
171
|
diff_buffer_init
|
data/lib/vimamsa/editor.rb
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
require "pty"
|
|
2
2
|
|
|
3
|
-
# def handle_drag_and_drop(fname)
|
|
4
|
-
# debug "EDITOR:handle_drag_and_drop"
|
|
5
|
-
# buf.handle_drag_and_drop(fname)
|
|
6
|
-
# end
|
|
7
3
|
|
|
8
4
|
class Editor
|
|
9
5
|
attr_reader :file_content_search_paths, :file_name_search_paths, :gui, :hook, :macro, :actions
|
|
@@ -131,6 +127,22 @@ class Editor
|
|
|
131
127
|
#TODO: if language enabled in config?
|
|
132
128
|
end
|
|
133
129
|
|
|
130
|
+
# Load each module that is enabled in settings.
|
|
131
|
+
# Each module lives in modules/<name>/<name>.rb and may optionally define
|
|
132
|
+
# <name>_init to register actions, key bindings, etc.
|
|
133
|
+
Dir.glob(ppath("modules/*/")).sort.each do |mod_dir|
|
|
134
|
+
mod_name = File.basename(mod_dir)
|
|
135
|
+
if cnf.modules.public_send(mod_name).enabled?
|
|
136
|
+
main_file = File.join(mod_dir, "#{mod_name}.rb")
|
|
137
|
+
if File.exist?(main_file)
|
|
138
|
+
load main_file
|
|
139
|
+
# Call <name>_init if the module defines it.
|
|
140
|
+
init_fn = "#{mod_name}_init"
|
|
141
|
+
send(init_fn) if respond_to?(init_fn, true)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
134
146
|
fname = nil
|
|
135
147
|
if cnf.startup_file?
|
|
136
148
|
fname_ = File.expand_path(cnf.startup_file!)
|
|
@@ -171,6 +183,17 @@ class Editor
|
|
|
171
183
|
check_session_restore unless argv_has_files
|
|
172
184
|
|
|
173
185
|
@hook.call(:after_init)
|
|
186
|
+
|
|
187
|
+
if ARGV.include?("--test")
|
|
188
|
+
test_files = ARGV.select { |a| a.end_with?(".rb") && File.file?(a) }
|
|
189
|
+
run_as_idle proc {
|
|
190
|
+
test_files.each { |f| load f }
|
|
191
|
+
success = run_vma_tests
|
|
192
|
+
# Defer shutdown so GTK can drain all pending idle callbacks from test
|
|
193
|
+
# teardown before widget destruction begins (avoids heap corruption).
|
|
194
|
+
GLib::Timeout.add(300) { shutdown(); false }
|
|
195
|
+
}
|
|
196
|
+
end
|
|
174
197
|
end
|
|
175
198
|
|
|
176
199
|
def register_plugin(name, obj)
|
|
@@ -206,6 +229,7 @@ class Editor
|
|
|
206
229
|
|
|
207
230
|
def save_var_to_file(varname, vardata)
|
|
208
231
|
fn = get_dot_path(varname)
|
|
232
|
+
#TODO: check that save path is safe
|
|
209
233
|
f = File.open(fn, "w")
|
|
210
234
|
File.binwrite(f, vardata)
|
|
211
235
|
f.close
|
|
@@ -253,6 +277,7 @@ class Editor
|
|
|
253
277
|
|
|
254
278
|
fnames = File.read(session_path).lines.map(&:strip).reject(&:empty?)
|
|
255
279
|
fnames.select! { |f| File.exist?(f) }
|
|
280
|
+
fnames.reject! { |f| vma.buffers.get_buffer_by_filename(f) }
|
|
256
281
|
return if fnames.empty?
|
|
257
282
|
|
|
258
283
|
n = fnames.size
|
|
@@ -404,12 +429,6 @@ def set_next_command_count(num)
|
|
|
404
429
|
debug("NEXT COMMAND COUNT: #{$next_command_count}")
|
|
405
430
|
end
|
|
406
431
|
|
|
407
|
-
def start_minibuffer_cmd(bufname, bufstr, cmd)
|
|
408
|
-
vma.kbd.set_mode(:minibuffer)
|
|
409
|
-
$minibuffer = Buffer.new(bufstr, "")
|
|
410
|
-
$minibuffer.call_func = method(cmd)
|
|
411
|
-
end
|
|
412
|
-
|
|
413
432
|
def if_cmd_exists(cmd)
|
|
414
433
|
cmd = cmd.gsub(/[^a-zA-Z0-9_\-]/, '')
|
|
415
434
|
if !system("which #{cmd} > /dev/null 2>&1")
|
|
@@ -419,53 +438,6 @@ def if_cmd_exists(cmd)
|
|
|
419
438
|
return true
|
|
420
439
|
end
|
|
421
440
|
|
|
422
|
-
def invoke_command()
|
|
423
|
-
start_minibuffer_cmd("", "", :execute_command)
|
|
424
|
-
end
|
|
425
|
-
|
|
426
|
-
def execute_command(input_str)
|
|
427
|
-
begin
|
|
428
|
-
out_str = eval(input_str, TOPLEVEL_BINDING) #TODO: Other binding?
|
|
429
|
-
$minibuffer.clear
|
|
430
|
-
$minibuffer << out_str.to_s #TODO: segfaults, why?
|
|
431
|
-
rescue SyntaxError
|
|
432
|
-
debug("SYNTAX ERROR with eval cmd #{action}: " + $!.to_s)
|
|
433
|
-
end
|
|
434
|
-
end
|
|
435
|
-
|
|
436
|
-
def minibuffer_end()
|
|
437
|
-
debug "minibuffer_end"
|
|
438
|
-
vma.kbd.set_mode(:command)
|
|
439
|
-
minibuffer_input = $minibuffer.to_s[0..-2]
|
|
440
|
-
return $minibuffer.call_func.call(minibuffer_input)
|
|
441
|
-
end
|
|
442
|
-
|
|
443
|
-
def minibuffer_cancel()
|
|
444
|
-
debug "minibuffer_cancel"
|
|
445
|
-
vma.kbd.set_mode(:command)
|
|
446
|
-
minibuffer_input = $minibuffer.to_s[0..-2]
|
|
447
|
-
# $minibuffer.call_func.call('')
|
|
448
|
-
end
|
|
449
|
-
|
|
450
|
-
def minibuffer_new_char(c)
|
|
451
|
-
if c == "\r"
|
|
452
|
-
raise "Should not come here"
|
|
453
|
-
debug "MINIBUFFER END"
|
|
454
|
-
else
|
|
455
|
-
$minibuffer.insert_txt(c)
|
|
456
|
-
debug "MINIBUFFER: #{c}"
|
|
457
|
-
end
|
|
458
|
-
#vma.buf = $minibuffer
|
|
459
|
-
end
|
|
460
|
-
|
|
461
|
-
# def readchar_new_char(c)
|
|
462
|
-
# $input_char_call_func.call(c)
|
|
463
|
-
# end
|
|
464
|
-
|
|
465
|
-
def minibuffer_delete()
|
|
466
|
-
$minibuffer.delete(BACKWARD_CHAR)
|
|
467
|
-
end
|
|
468
|
-
|
|
469
441
|
def error(str)
|
|
470
442
|
show_caller
|
|
471
443
|
debug "#{caller[0]} ERROR: #{str}", 2
|
|
@@ -550,10 +522,13 @@ def load_buffer(fname)
|
|
|
550
522
|
# If file already open in existing buffer
|
|
551
523
|
existing_buffer = vma.buffers.get_buffer_by_filename(fname)
|
|
552
524
|
if existing_buffer != nil
|
|
553
|
-
vma.buffers.add_buf_to_history(existing_buffer)
|
|
554
525
|
return
|
|
555
526
|
end
|
|
556
527
|
return if !File.exist?(fname)
|
|
528
|
+
if Encrypt.is_encrypted?(fname)
|
|
529
|
+
decrypt_dialog(filename: fname)
|
|
530
|
+
return nil
|
|
531
|
+
end
|
|
557
532
|
debug("LOAD BUFFER: #{fname}")
|
|
558
533
|
buffer = Buffer.new(read_file("", fname), fname)
|
|
559
534
|
# gui_set_current_buffer(buffer.id)
|
|
@@ -562,7 +537,6 @@ def load_buffer(fname)
|
|
|
562
537
|
#buf = filter_buffer(buffer)
|
|
563
538
|
# debug("END FILTER: #{fname}")
|
|
564
539
|
vma.buffers << buffer
|
|
565
|
-
#$buffer_history << vma.buffers.size - 1
|
|
566
540
|
return buffer
|
|
567
541
|
end
|
|
568
542
|
|
|
@@ -631,14 +605,9 @@ def scan_word_start_marks(search_str)
|
|
|
631
605
|
return wsmarks
|
|
632
606
|
end
|
|
633
607
|
|
|
634
|
-
def hook_draw()
|
|
635
|
-
# TODO: as hook.register
|
|
636
|
-
# easy_jump_draw()
|
|
637
|
-
end
|
|
638
|
-
|
|
639
608
|
def get_dot_path(sfx)
|
|
640
609
|
dot_dir = File.expand_path("~/.config/vimamsa")
|
|
641
|
-
|
|
610
|
+
FileUtils.mkdir_p(dot_dir) unless File.exist?(dot_dir)
|
|
642
611
|
dpath = "#{dot_dir}/#{sfx}"
|
|
643
612
|
return dpath
|
|
644
613
|
end
|
|
@@ -655,6 +624,24 @@ def get_file_line_pointer(s)
|
|
|
655
624
|
return nil
|
|
656
625
|
end
|
|
657
626
|
|
|
627
|
+
# Try to resolve a bare "filename.ext:line" reference (no directory component).
|
|
628
|
+
# Searches the current buffer's directory and the last visited directory.
|
|
629
|
+
def resolve_bare_file_line_pointer(s)
|
|
630
|
+
m = s.match(/\A([\w.\-]+\.[a-zA-Z0-9]+):(c?)(\d+)\z/)
|
|
631
|
+
return nil unless m
|
|
632
|
+
fname = m[1]
|
|
633
|
+
col = m[2]
|
|
634
|
+
line = m[3].to_i
|
|
635
|
+
dirs = []
|
|
636
|
+
dirs << File.dirname(vma.buf.fname) if vma.buf&.fname
|
|
637
|
+
dirs << bufs.last_dir if bufs.last_dir
|
|
638
|
+
dirs.uniq.each do |dir|
|
|
639
|
+
candidate = File.join(dir, fname)
|
|
640
|
+
return [candidate, line, col] if File.exist?(candidate)
|
|
641
|
+
end
|
|
642
|
+
nil
|
|
643
|
+
end
|
|
644
|
+
|
|
658
645
|
def find_project_dir_of_fn(fn)
|
|
659
646
|
pcomp = Pathname.new(fn).each_filename.to_a
|
|
660
647
|
parent_dirs = (0..(pcomp.size - 2)).collect { |x| "/" + pcomp[0..x].join("/") }.reverse
|
|
@@ -693,9 +680,9 @@ end
|
|
|
693
680
|
|
|
694
681
|
def save_settings_to_file
|
|
695
682
|
settings_path = get_dot_path("settings.rb")
|
|
696
|
-
lines =
|
|
683
|
+
lines = all_settings_defs.flat_map { |section| section[:settings] }.map do |s|
|
|
697
684
|
key_str = "cnf." + s[:key].join(".")
|
|
698
|
-
val =
|
|
685
|
+
val = cnf_get(s[:key])
|
|
699
686
|
"#{key_str} = #{val.inspect}"
|
|
700
687
|
end
|
|
701
688
|
IO.write(settings_path, lines.join("\n") + "\n")
|
data/lib/vimamsa/file_finder.rb
CHANGED
|
@@ -39,12 +39,16 @@ class FileFinder
|
|
|
39
39
|
|
|
40
40
|
def initialize()
|
|
41
41
|
vma.hook.register(:shutdown, self.method("save"))
|
|
42
|
-
|
|
42
|
+
# Load saved file list on startup (disabled for now, TODO)
|
|
43
|
+
# @@dir_list = vma.marshal_load("file_index")
|
|
43
44
|
@@dir_list ||= []
|
|
44
|
-
update_search_idx
|
|
45
|
+
# update_search_idx
|
|
46
|
+
FileFinder.recursively_find_files
|
|
47
|
+
message("FileFinder initialized, directories: #{cnf.search_dirs!}")
|
|
45
48
|
end
|
|
46
49
|
|
|
47
50
|
def self.update_search_idx
|
|
51
|
+
|
|
48
52
|
Thread.new {
|
|
49
53
|
sleep 0.1
|
|
50
54
|
@@idx = StringIndex.new
|