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
|
@@ -22,7 +22,14 @@ class Buffer < String
|
|
|
22
22
|
else
|
|
23
23
|
@deltas << delta
|
|
24
24
|
end
|
|
25
|
-
|
|
25
|
+
|
|
26
|
+
unless @macro_group_active
|
|
27
|
+
if @last_delta_time && (Time.now - @last_delta_time) > cnf.undo.group_threshold!
|
|
28
|
+
new_undo_group
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
@current_group << delta
|
|
32
|
+
@last_delta_time = Time.now
|
|
26
33
|
if self[-1] != "\n"
|
|
27
34
|
add_delta([self.size, INSERT, 1, "\n"], true)
|
|
28
35
|
end
|
|
@@ -81,19 +88,19 @@ class Buffer < String
|
|
|
81
88
|
@clipboard_paste_running = true
|
|
82
89
|
Thread.new {
|
|
83
90
|
text = `xclip -selection c -o`
|
|
84
|
-
paste_finish(text, at, register)
|
|
91
|
+
GLib::Idle.add { paste_finish(text, at, register); false }
|
|
85
92
|
}
|
|
86
93
|
return nil
|
|
87
94
|
end
|
|
88
95
|
|
|
89
96
|
# Start asynchronous read of system clipboard
|
|
90
|
-
def paste_start(at, register)
|
|
97
|
+
def paste_start(at, register, overwrite: false)
|
|
91
98
|
@clipboard_paste_running = true
|
|
92
99
|
|
|
93
|
-
if true or running_wayland? and !GLib::Version::or_later?(2, 79, 0)
|
|
94
|
-
return paste_start_xclip(at, register)
|
|
95
|
-
end
|
|
96
|
-
|
|
100
|
+
# if true or running_wayland? and !GLib::Version::or_later?(2, 79, 0)
|
|
101
|
+
# return paste_start_xclip(at, register)
|
|
102
|
+
# end
|
|
103
|
+
|
|
97
104
|
clipboard = vma.gui.window.display.clipboard
|
|
98
105
|
clipboard.read_text_async do |_clipboard, result|
|
|
99
106
|
begin
|
|
@@ -101,13 +108,13 @@ class Buffer < String
|
|
|
101
108
|
rescue Gio::IOError::NotSupported
|
|
102
109
|
debug Gio::IOError::NotSupported
|
|
103
110
|
else
|
|
104
|
-
paste_finish(text, at, register)
|
|
111
|
+
paste_finish(text, at, register, overwrite: overwrite)
|
|
105
112
|
end
|
|
106
113
|
end
|
|
107
114
|
return nil
|
|
108
115
|
end
|
|
109
116
|
|
|
110
|
-
def paste_finish(text, at, register)
|
|
117
|
+
def paste_finish(text, at, register, overwrite: false)
|
|
111
118
|
debug "PASTE: #{text}"
|
|
112
119
|
|
|
113
120
|
# If we did not put this text to clipboard
|
|
@@ -121,9 +128,25 @@ class Buffer < String
|
|
|
121
128
|
|
|
122
129
|
return if text == ""
|
|
123
130
|
|
|
124
|
-
|
|
131
|
+
cursor_at_start = cnf.paste.cursor_at_start!
|
|
132
|
+
|
|
133
|
+
if overwrite && !@paste_lines
|
|
134
|
+
# Delete as many chars forward as we are about to insert, stopping before newline
|
|
135
|
+
line_end = current_line_range.last - 1 # position of last char before \n
|
|
136
|
+
n = [text.size, [line_end - @pos + 1, 0].max].min
|
|
137
|
+
add_delta([@pos, DELETE, n], true) if n > 0
|
|
138
|
+
insert_txt_at(text, @pos)
|
|
139
|
+
set_pos(cursor_at_start ? @pos : @pos + text.size - 1)
|
|
140
|
+
elsif @paste_lines
|
|
125
141
|
debug "PASTE LINES"
|
|
126
|
-
|
|
142
|
+
if at == BEFORE
|
|
143
|
+
l = current_line_range()
|
|
144
|
+
insert_txt_at(text, l.begin)
|
|
145
|
+
set_pos(l.begin)
|
|
146
|
+
else
|
|
147
|
+
put_to_new_next_line(text)
|
|
148
|
+
set_pos(@pos) if cursor_at_start
|
|
149
|
+
end
|
|
127
150
|
else
|
|
128
151
|
debug "PASTE !LINES"
|
|
129
152
|
if at_end_of_buffer? or at_end_of_line? or at == BEFORE
|
|
@@ -132,12 +155,23 @@ class Buffer < String
|
|
|
132
155
|
pos = @pos + 1
|
|
133
156
|
end
|
|
134
157
|
insert_txt_at(text, pos)
|
|
135
|
-
set_pos(pos + text.size)
|
|
158
|
+
set_pos(cursor_at_start ? pos : pos + text.size)
|
|
136
159
|
end
|
|
137
160
|
set_pos(@pos)
|
|
161
|
+
vma.buf.view.after_action # redraw
|
|
138
162
|
@clipboard_paste_running = false
|
|
139
163
|
end
|
|
140
164
|
|
|
165
|
+
def paste_over(at = AFTER, register = nil)
|
|
166
|
+
if vma.macro.running_macro
|
|
167
|
+
text = vma.clipboard.get()
|
|
168
|
+
paste_finish(text, at, register, overwrite: true)
|
|
169
|
+
else
|
|
170
|
+
paste_start(at, register, overwrite: true)
|
|
171
|
+
end
|
|
172
|
+
return true
|
|
173
|
+
end
|
|
174
|
+
|
|
141
175
|
def paste(at = AFTER, register = nil)
|
|
142
176
|
# Macro's don't work with asynchronous call using GTK
|
|
143
177
|
# TODO: implement as synchronous?
|
|
@@ -152,6 +186,22 @@ class Buffer < String
|
|
|
152
186
|
return true
|
|
153
187
|
end
|
|
154
188
|
|
|
189
|
+
def increment_current_word()
|
|
190
|
+
debug "increment_current_word", 2
|
|
191
|
+
p = @pos
|
|
192
|
+
return if !is_legal_pos(p)
|
|
193
|
+
(word, range) = get_word_in_pos(p, boundary: :word2)
|
|
194
|
+
if word.match(/(true|false)/i)
|
|
195
|
+
rep = flip_true_false(word)
|
|
196
|
+
else
|
|
197
|
+
num = word.to_i
|
|
198
|
+
num += 1
|
|
199
|
+
rep = num.to_s
|
|
200
|
+
end
|
|
201
|
+
debug [word, range].to_s, 2
|
|
202
|
+
replace_range(range, rep)
|
|
203
|
+
end
|
|
204
|
+
|
|
155
205
|
def complete_current_word(rep)
|
|
156
206
|
debug "complete_current_word", 2
|
|
157
207
|
p = @pos - 1
|
|
@@ -189,7 +239,11 @@ class Buffer < String
|
|
|
189
239
|
(startpos, endpos) = get_visual_mode_range2
|
|
190
240
|
delete_range(startpos, endpos, x)
|
|
191
241
|
@pos = [@pos, @selection_start].min
|
|
192
|
-
|
|
242
|
+
if vma.kbd.get_mode == :visual
|
|
243
|
+
end_visual_mode
|
|
244
|
+
else
|
|
245
|
+
end_selection
|
|
246
|
+
end
|
|
193
247
|
#return
|
|
194
248
|
|
|
195
249
|
# Delete current char
|
|
@@ -217,7 +271,7 @@ class Buffer < String
|
|
|
217
271
|
|
|
218
272
|
def delete_range(startpos, endpos, x = nil)
|
|
219
273
|
s = self[startpos..endpos]
|
|
220
|
-
if startpos
|
|
274
|
+
if startpos > endpos or s == ""
|
|
221
275
|
return
|
|
222
276
|
end
|
|
223
277
|
if x == :append
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# Buffer operations related to cursor position, e.g. moving the cursor (backward, forward, next line etc.)
|
|
2
2
|
class Buffer < String
|
|
3
|
-
|
|
4
3
|
def line(lpos)
|
|
5
4
|
if @line_ends.size == 0
|
|
6
5
|
return self
|
|
@@ -30,15 +29,22 @@ class Buffer < String
|
|
|
30
29
|
def refresh_cursor
|
|
31
30
|
self.view.set_cursor_pos(@pos)
|
|
32
31
|
end
|
|
33
|
-
|
|
32
|
+
|
|
34
33
|
def set_pos(new_pos)
|
|
34
|
+
# If user interacts with the buffer, we consider that the buffer has been accessed
|
|
35
|
+
# And navigation has ended
|
|
36
|
+
if new_pos != @pos
|
|
37
|
+
update_access_time
|
|
38
|
+
bufs.reset_navigation
|
|
39
|
+
end
|
|
35
40
|
if new_pos >= self.size
|
|
36
41
|
@pos = self.size - 1 # TODO:??right side of last char
|
|
37
42
|
elsif new_pos >= 0
|
|
38
43
|
@pos = new_pos
|
|
39
44
|
end
|
|
45
|
+
|
|
40
46
|
self.view.set_cursor_pos(pos)
|
|
41
|
-
|
|
47
|
+
# gui_set_cursor_pos(@id, @pos)
|
|
42
48
|
calculate_line_and_column_pos
|
|
43
49
|
|
|
44
50
|
check_if_modified_outside
|
data/lib/vimamsa/buffer_list.rb
CHANGED
|
@@ -20,12 +20,11 @@ def load_buffer_list()
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
class BufferList
|
|
23
|
-
attr_reader :current_buf, :last_dir, :last_file
|
|
23
|
+
attr_reader :current_buf, :last_dir, :last_file
|
|
24
24
|
attr_accessor :list
|
|
25
25
|
|
|
26
26
|
def initialize()
|
|
27
27
|
@last_dir = File.expand_path(".")
|
|
28
|
-
@buffer_history = []
|
|
29
28
|
super
|
|
30
29
|
@current_buf = 0
|
|
31
30
|
@list = []
|
|
@@ -42,25 +41,14 @@ class BufferList
|
|
|
42
41
|
vma.gui.set_current_buffer(vma.buf.id) #TODO: handle elswhere?
|
|
43
42
|
# vma.buf.view.set_cursor_pos(vma.buf.pos) #TODO: handle elswhere?
|
|
44
43
|
update_last_dir(_buf)
|
|
44
|
+
vma.gui.file_panel_refresh
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def add(_buf)
|
|
48
|
-
@buffer_history << _buf.id
|
|
49
|
-
# @navigation_idx = _buf.id #TODO:??
|
|
50
48
|
@list << _buf
|
|
51
49
|
@h[_buf.id] = _buf
|
|
52
50
|
end
|
|
53
51
|
|
|
54
|
-
#NOTE: unused. enable?
|
|
55
|
-
# def switch()
|
|
56
|
-
# debug "SWITCH BUF. bufsize:#{self.size}, curbuf: #{@current_buf}"
|
|
57
|
-
# @current_buf += 1
|
|
58
|
-
# @current_buf = 0 if @current_buf >= self.size
|
|
59
|
-
# m = method("switch")
|
|
60
|
-
# set_last_command({ method: m, params: [] })
|
|
61
|
-
# set_current_buffer(@current_buf)
|
|
62
|
-
# end
|
|
63
|
-
|
|
64
52
|
def slist
|
|
65
53
|
# TODO: implement using heap/priorityque
|
|
66
54
|
@list.sort_by! { |x| x.access_time }
|
|
@@ -72,6 +60,13 @@ class BufferList
|
|
|
72
60
|
end
|
|
73
61
|
end
|
|
74
62
|
|
|
63
|
+
def print_by_access_time
|
|
64
|
+
slist.reverse.each_with_index do |b, i|
|
|
65
|
+
name = b.fname || "(untitled)"
|
|
66
|
+
puts "#{i + 1}. #{b.access_time.strftime('%H:%M:%S')} #{name}"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
75
70
|
def get_last_visited_id
|
|
76
71
|
last_buf = nil
|
|
77
72
|
for i in 0..(slist.size - 1)
|
|
@@ -83,9 +78,6 @@ class BufferList
|
|
|
83
78
|
|
|
84
79
|
def switch_to_last_buf()
|
|
85
80
|
debug "SWITCH TO LAST BUF:"
|
|
86
|
-
# debug @buffer_history
|
|
87
|
-
# last_buf = @buffer_history[-2]
|
|
88
|
-
|
|
89
81
|
last_buf = slist[-2]
|
|
90
82
|
if !last_buf.nil?
|
|
91
83
|
set_current_buffer(last_buf.id)
|
|
@@ -107,17 +99,6 @@ class BufferList
|
|
|
107
99
|
return @h[id]
|
|
108
100
|
end
|
|
109
101
|
|
|
110
|
-
def add_buf_to_history(buf_idx)
|
|
111
|
-
if @list.include?(buf_idx)
|
|
112
|
-
@buffer_history << @buf_idx
|
|
113
|
-
@navigation_idx = 0
|
|
114
|
-
# compact_buf_history()
|
|
115
|
-
else
|
|
116
|
-
debug "buffer_list, no such id:#{buf_idx}"
|
|
117
|
-
return
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
|
|
121
102
|
def add_current_buf_to_history()
|
|
122
103
|
@h[@current_buf].update_access_time
|
|
123
104
|
end
|
|
@@ -143,7 +124,10 @@ class BufferList
|
|
|
143
124
|
|
|
144
125
|
bu.set_active # TODO
|
|
145
126
|
bu.update_access_time if update_history
|
|
127
|
+
reset_navigation if update_history
|
|
146
128
|
vma.gui.set_current_buffer(idx)
|
|
129
|
+
vma.gui.file_panel_refresh
|
|
130
|
+
vma.gui.func_panel_refresh
|
|
147
131
|
|
|
148
132
|
#TODO: delete?
|
|
149
133
|
# if !vma.buf.mode_stack.nil? and vma.kbd.get_scope != :editor #TODO
|
|
@@ -255,6 +239,8 @@ class BufferList
|
|
|
255
239
|
|
|
256
240
|
@list.delete(@h[idx])
|
|
257
241
|
@h.delete(idx)
|
|
242
|
+
gui_close_buffer(idx)
|
|
243
|
+
vma.gui.file_panel_refresh
|
|
258
244
|
|
|
259
245
|
if auto_open
|
|
260
246
|
@current_buf = get_last_non_active_buffer
|
|
@@ -60,7 +60,7 @@ class BufferManager
|
|
|
60
60
|
@@cur = self
|
|
61
61
|
@header = []
|
|
62
62
|
@header << "Current buffers:"
|
|
63
|
-
@header << "keys: <enter> to select, <c> to close buffer, <x> exit"
|
|
63
|
+
@header << "keys: <enter> (or <double click>) to select, <c> to close buffer, <x> exit"
|
|
64
64
|
@header << "=" * 40
|
|
65
65
|
|
|
66
66
|
s = ""
|
data/lib/vimamsa/conf.rb
CHANGED
|
@@ -13,7 +13,7 @@ $confh = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
|
|
|
13
13
|
# => $confh = {:foo=>{:bar=>{:baz=>3}}}
|
|
14
14
|
def set(_id, val)
|
|
15
15
|
a = $confh
|
|
16
|
-
id = _id.to_a
|
|
16
|
+
id = _id.to_a.dup
|
|
17
17
|
last = id.pop
|
|
18
18
|
for x in id
|
|
19
19
|
a = a[x]
|
|
@@ -103,6 +103,28 @@ def cnf()
|
|
|
103
103
|
return $vimamsa_conf
|
|
104
104
|
end
|
|
105
105
|
|
|
106
|
+
# Read a config value by key path array.
|
|
107
|
+
# Uses the cnf accessor chain so key types match cnf.xxx! reads.
|
|
108
|
+
# Example:
|
|
109
|
+
# cnf_get([:tab, :width]) # => 2
|
|
110
|
+
# cnf_get([:style_scheme]) # => "molokai_edit"
|
|
111
|
+
def cnf_get(key)
|
|
112
|
+
obj = cnf
|
|
113
|
+
key[0..-2].each { |k| obj = obj.public_send(k) }
|
|
114
|
+
obj.public_send(:"#{key.last}!")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Write a config value by key path array.
|
|
118
|
+
# Uses the cnf accessor chain so the value is readable via cnf.xxx!
|
|
119
|
+
# Example:
|
|
120
|
+
# cnf_set([:tab, :width], 4)
|
|
121
|
+
# cnf_set([:style_scheme], "soviet_cockpit")
|
|
122
|
+
def cnf_set(key, val)
|
|
123
|
+
obj = cnf
|
|
124
|
+
key[0..-2].each { |k| obj = obj.public_send(k) }
|
|
125
|
+
obj.public_send(:"#{key.last}=", val)
|
|
126
|
+
end
|
|
127
|
+
|
|
106
128
|
cnf.indent_based_on_last_line = true
|
|
107
129
|
cnf.extensions_to_open = [".txt", ".h", ".c", ".cpp", ".hpp", ".rb", ".inc", ".php", ".sh", ".m", ".gd", ".js", ".py"]
|
|
108
130
|
cnf.default_search_extensions = ["txt", "rb"]
|
|
@@ -112,6 +134,8 @@ cnf.lsp.enabled = false
|
|
|
112
134
|
cnf.fexp.experimental = false
|
|
113
135
|
cnf.experimental = false
|
|
114
136
|
|
|
137
|
+
cnf.kbd.show_prev_action = true
|
|
138
|
+
|
|
115
139
|
cnf.tab.width = 2
|
|
116
140
|
cnf.tab.to_spaces_default = false
|
|
117
141
|
cnf.tab.to_spaces_languages = ["c", "java", "ruby", "hyperplaintext", "php"]
|
|
@@ -129,4 +153,12 @@ cnf.startup_file=false
|
|
|
129
153
|
|
|
130
154
|
cnf.macro.animation_delay = 0.02
|
|
131
155
|
|
|
156
|
+
cnf.undo.group_threshold = 1.8 # seconds of inactivity before starting a new undo group
|
|
157
|
+
|
|
158
|
+
cnf.paste.cursor_at_start = false
|
|
159
|
+
|
|
160
|
+
cnf.style_scheme = "molokai_edit"
|
|
161
|
+
cnf.color_contrast = 1.0
|
|
162
|
+
cnf.color_brightness = 0.0
|
|
163
|
+
|
|
132
164
|
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Map a "line number in a unified diff output" to the corresponding
|
|
2
|
+
# line in the new/changed file (the + side), together with the file it belongs to.
|
|
3
|
+
#
|
|
4
|
+
# Handles multi-file diffs: each --- / +++ pair sets the active file; each @@
|
|
5
|
+
# hunk header resets the line counters. Walking hunk lines:
|
|
6
|
+
# ' ' => old++, new++
|
|
7
|
+
# '-' => old++
|
|
8
|
+
# '+' => new++
|
|
9
|
+
#
|
|
10
|
+
# Returns [new_path, old_path, line_no] or nil for deleted / unmappable lines.
|
|
11
|
+
#
|
|
12
|
+
class DiffLineMapper
|
|
13
|
+
HUNK_RE = /^@@\s+-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s+@@/
|
|
14
|
+
|
|
15
|
+
def initialize(diff_text)
|
|
16
|
+
@lines = diff_text.lines
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Given a 1-based line number in the diff output, return:
|
|
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
|
|
22
|
+
def new_line_for_diff_lineno(diff_lineno)
|
|
23
|
+
raise ArgumentError, "diff line number must be >= 1" if diff_lineno.to_i < 1
|
|
24
|
+
idx = diff_lineno.to_i - 1
|
|
25
|
+
return nil if idx >= @lines.length
|
|
26
|
+
|
|
27
|
+
old_path = nil
|
|
28
|
+
new_path = nil
|
|
29
|
+
old = nil
|
|
30
|
+
new_ = nil
|
|
31
|
+
in_hunk = false
|
|
32
|
+
|
|
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
|
+
|
|
50
|
+
if (m = line.match(HUNK_RE))
|
|
51
|
+
old = m[1].to_i
|
|
52
|
+
new_ = m[3].to_i
|
|
53
|
+
in_hunk = true
|
|
54
|
+
next
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
next unless in_hunk
|
|
58
|
+
|
|
59
|
+
if i == idx
|
|
60
|
+
return nil unless new_
|
|
61
|
+
case line.getbyte(0)
|
|
62
|
+
when '+'.ord then return [new_path, old_path, new_]
|
|
63
|
+
when ' '.ord then return [new_path, old_path, new_]
|
|
64
|
+
when '-'.ord then return nil
|
|
65
|
+
else return nil
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
next unless old && new_
|
|
70
|
+
|
|
71
|
+
case line.getbyte(0)
|
|
72
|
+
when ' '.ord then old += 1; new_ += 1
|
|
73
|
+
when '-'.ord then old += 1
|
|
74
|
+
when '+'.ord then new_ += 1
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
nil
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def diff_buffer_init()
|
|
83
|
+
return if @diff_buffer_init_done
|
|
84
|
+
@diff_buffer_init_done = true
|
|
85
|
+
vma.kbd.add_minor_mode("diffview", :diffview, :command)
|
|
86
|
+
bindkey "diffview enter", :diff_buffer_jump_to_source
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def diff_buffer()
|
|
90
|
+
return if !if_cmd_exists("diff")
|
|
91
|
+
diff_buffer_init
|
|
92
|
+
orig_path = vma.buf.fname
|
|
93
|
+
infile = Tempfile.new("in")
|
|
94
|
+
infile.write(vma.buf.to_s)
|
|
95
|
+
infile.flush
|
|
96
|
+
bufstr = run_cmd("diff -uw '#{orig_path}' #{infile.path}")
|
|
97
|
+
infile.close; infile.unlink
|
|
98
|
+
create_new_file(nil, bufstr)
|
|
99
|
+
gui_set_file_lang(vma.buf.id, "diff")
|
|
100
|
+
vma.kbd.set_mode(:diffview)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def diff_buffer_jump_to_source()
|
|
104
|
+
mapper = DiffLineMapper.new(vma.buf.to_s)
|
|
105
|
+
cur_lpos = vma.buf.lpos + 1
|
|
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
|
+
end
|
|
112
|
+
|
|
113
|
+
new_path, old_path, to_line = result
|
|
114
|
+
orig_path = resolve_diff_path(new_path, old_path)
|
|
115
|
+
|
|
116
|
+
if orig_path.nil? || !File.exist?(orig_path)
|
|
117
|
+
message("Could not find file: #{new_path || old_path}")
|
|
118
|
+
return
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
jump_to_file(orig_path, to_line)
|
|
122
|
+
center_on_current_line
|
|
123
|
+
end
|
|
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
|
+
|
|
169
|
+
def git_diff_buffer()
|
|
170
|
+
return if !if_cmd_exists("git")
|
|
171
|
+
diff_buffer_init
|
|
172
|
+
fname = vma.buf.fname
|
|
173
|
+
if fname.nil?
|
|
174
|
+
message("Buffer has no file")
|
|
175
|
+
return
|
|
176
|
+
end
|
|
177
|
+
bufstr = run_cmd("git diff -w -- #{Shellwords.escape(fname)}")
|
|
178
|
+
if bufstr.strip.empty?
|
|
179
|
+
message("git diff: no changes")
|
|
180
|
+
return
|
|
181
|
+
end
|
|
182
|
+
create_new_file(nil, bufstr)
|
|
183
|
+
gui_set_file_lang(vma.buf.id, "diff")
|
|
184
|
+
vma.kbd.set_mode(:diffview)
|
|
185
|
+
end
|