vimamsa 0.1.22 → 0.1.23
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/README.md +2 -2
- data/custom_example.rb +4 -1
- data/img/screenshot1.png +0 -0
- data/img/screenshot2.png +0 -0
- data/lib/vimamsa/buffer.rb +129 -8
- data/lib/vimamsa/buffer_changetext.rb +19 -2
- data/lib/vimamsa/buffer_cursor.rb +9 -3
- data/lib/vimamsa/buffer_list.rb +12 -0
- data/lib/vimamsa/buffer_manager.rb +1 -1
- data/lib/vimamsa/conf.rb +3 -1
- data/lib/vimamsa/diff_buffer.rb +137 -0
- data/lib/vimamsa/editor.rb +98 -16
- data/lib/vimamsa/gui.rb +93 -82
- data/lib/vimamsa/gui_dialog.rb +2 -0
- data/lib/vimamsa/gui_file_panel.rb +93 -0
- data/lib/vimamsa/gui_form_generator.rb +4 -2
- data/lib/vimamsa/gui_image.rb +2 -4
- data/lib/vimamsa/gui_menu.rb +12 -1
- data/lib/vimamsa/gui_select_window.rb +1 -0
- data/lib/vimamsa/gui_settings.rb +155 -0
- data/lib/vimamsa/gui_sourceview.rb +80 -6
- data/lib/vimamsa/key_actions.rb +32 -10
- data/lib/vimamsa/key_binding_tree.rb +72 -8
- data/lib/vimamsa/key_bindings_vimlike.rb +44 -35
- data/lib/vimamsa/main.rb +1 -0
- data/lib/vimamsa/rbvma.rb +3 -0
- data/lib/vimamsa/string_util.rb +56 -0
- data/lib/vimamsa/util.rb +3 -36
- data/lib/vimamsa/version.rb +1 -1
- metadata +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0cde9df6e9ba0c6ac7bdbc894726e0fe0941ab50867abfdc150314e2d3a2c3de
|
|
4
|
+
data.tar.gz: b2ca3e49f7b7da7f3f135b12780dc5b2e34e90d5d90bf35ec56ac8efa5f46600
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 25de014aaef8785f1e169f9afe8242143ff5e70d1c3a71d13aa6556188161d361a7c9248d00f8a530550e488c1dabea4104461011bcb17f135c157e6d9c229e6
|
|
7
|
+
data.tar.gz: 87023b352b98dcceb2e2d388cfe18409bb899522629357bdd62dcb62856738ce5af4f3f5ae4091bac399f251ea4848f524318127e8ad7aa05d8bd49a437b7d48
|
data/README.md
CHANGED
|
@@ -63,8 +63,8 @@ For customization, edit ~/.vimamsa/custom.rb
|
|
|
63
63
|
|
|
64
64
|
## Screenshots
|
|
65
65
|
|
|
66
|
-
<a href="https://
|
|
67
|
-
<a href="https://
|
|
66
|
+
<a href="https://raw.githubusercontent.com/SamiSieranoja/vimamsa/refs/heads/master/img/screenshot1.png" target="_blank"><img src="https://raw.githubusercontent.com/SamiSieranoja/vimamsa/refs/heads/master/img/screenshot1.png" width="400"/></a>
|
|
67
|
+
<a href="https://raw.githubusercontent.com/SamiSieranoja/vimamsa/refs/heads/master/img/screenshot2.png" target="_blank"><img src="https://raw.githubusercontent.com/SamiSieranoja/vimamsa/refs/heads/master/img/screenshot2.png" width="400"/></a>
|
|
68
68
|
|
|
69
69
|
## Key bindings
|
|
70
70
|
|
data/custom_example.rb
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
# Extract unique words
|
|
4
4
|
# c = Converter.new(lambda { |x| h = {}; x.split(/\s+/).each { |y| h[y] = 1 }; h.keys.join(" ") }, :lambda, :uniqwords)
|
|
5
5
|
|
|
6
|
-
# Eval selected text as ruby code (e.g. use as calculator)
|
|
7
6
|
|
|
7
|
+
# Eval selected text as ruby code (e.g. use as calculator):
|
|
8
8
|
# bindkey "V , e", "vma.buf.convert_selected_text(:eval)"
|
|
9
9
|
# syntax: bindkey "mode key1 key2 ..."
|
|
10
10
|
|
|
@@ -24,6 +24,9 @@
|
|
|
24
24
|
# cnf.lsp.server.clangd = { name: "clangd", command: "clangd-12 --offset-encoding=utf-8", type: "stdio" }
|
|
25
25
|
# cnf.lsp.server.clangd.languages = ["c", "cpp"]
|
|
26
26
|
|
|
27
|
+
# Uncomment this if you don't want to see the state trail of previous action
|
|
28
|
+
# on top right corner:
|
|
29
|
+
# cnf.kbd.show_prev_action = false
|
|
27
30
|
|
|
28
31
|
|
|
29
32
|
|
data/img/screenshot1.png
ADDED
|
Binary file
|
data/img/screenshot2.png
ADDED
|
Binary file
|
data/lib/vimamsa/buffer.rb
CHANGED
|
@@ -42,6 +42,8 @@ class Buffer < String
|
|
|
42
42
|
|
|
43
43
|
@last_save = @last_asked_from_user = @file_last_cheked = Time.now
|
|
44
44
|
@t_modified = @last_save
|
|
45
|
+
@last_autosave = @last_save
|
|
46
|
+
@autosave_thread = nil
|
|
45
47
|
|
|
46
48
|
@crypt = nil
|
|
47
49
|
@update_highlight = true
|
|
@@ -65,6 +67,7 @@ class Buffer < String
|
|
|
65
67
|
if cnf.lsp.enabled?
|
|
66
68
|
init_lsp
|
|
67
69
|
end
|
|
70
|
+
start_autosave_timer
|
|
68
71
|
return self
|
|
69
72
|
end
|
|
70
73
|
|
|
@@ -237,11 +240,40 @@ class Buffer < String
|
|
|
237
240
|
return words
|
|
238
241
|
end
|
|
239
242
|
|
|
243
|
+
# TODO: review
|
|
244
|
+
def cleanup_deleted_images(deleted_text)
|
|
245
|
+
return if @images.empty?
|
|
246
|
+
return unless deleted_text&.include?("⟦img:")
|
|
247
|
+
deleted_text.scan(/⟦img:(.+?)⟧/) do |match|
|
|
248
|
+
tag_path = match[0]
|
|
249
|
+
abs_path = translate_path(tag_path, self)
|
|
250
|
+
@images.delete_if do |img|
|
|
251
|
+
next false unless img[:path] == abs_path || img[:path] == tag_path
|
|
252
|
+
# img[:obj].destroy unless img[:obj].destroyed?
|
|
253
|
+
# Schedule anchor character deletion after the current delta is done.
|
|
254
|
+
# Using the anchor object lets us find the correct position even if
|
|
255
|
+
# prior edits shifted it.
|
|
256
|
+
anchor = img[:anchor]
|
|
257
|
+
buf_ref = self
|
|
258
|
+
run_as_idle proc {
|
|
259
|
+
next if anchor.nil? || anchor.deleted?
|
|
260
|
+
itr = buf_ref.view.buffer.get_iter_at_child_anchor(anchor)
|
|
261
|
+
next if itr.nil?
|
|
262
|
+
pos = itr.offset
|
|
263
|
+
buf_ref.add_delta([pos, DELETE, 1], true)
|
|
264
|
+
buf_ref.calculate_line_and_column_pos
|
|
265
|
+
}
|
|
266
|
+
true
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
240
271
|
def add_image(imgpath, pos)
|
|
241
272
|
return if !is_legal_pos(pos)
|
|
242
273
|
|
|
243
274
|
vbuf = view.buffer
|
|
244
275
|
itr = vbuf.get_iter_at(:offset => pos)
|
|
276
|
+
return if itr&.child_anchor # anchor already exists here, image already rendered
|
|
245
277
|
itr2 = vbuf.get_iter_at(:offset => pos + 1)
|
|
246
278
|
vbuf.delete(itr, itr2)
|
|
247
279
|
anchor = vbuf.create_child_anchor(itr)
|
|
@@ -256,7 +288,7 @@ class Buffer < String
|
|
|
256
288
|
da.scale_image
|
|
257
289
|
|
|
258
290
|
# vma.gui.handle_image_resize #TODO:gtk4
|
|
259
|
-
@images << { :path => imgpath, :obj => da }
|
|
291
|
+
@images << { :path => imgpath, :obj => da, :anchor => anchor }
|
|
260
292
|
|
|
261
293
|
gui_set_current_buffer(@id) #TODO: needed?
|
|
262
294
|
end
|
|
@@ -308,6 +340,8 @@ class Buffer < String
|
|
|
308
340
|
message("Revert buffer #{@fname}")
|
|
309
341
|
str = read_file("", @fname)
|
|
310
342
|
self.set_content(str)
|
|
343
|
+
@last_save = Time.now
|
|
344
|
+
refresh_title
|
|
311
345
|
end
|
|
312
346
|
|
|
313
347
|
def init_encrypted(crypt:, filename:, encrypted:)
|
|
@@ -408,7 +442,7 @@ class Buffer < String
|
|
|
408
442
|
@title = File.basename(@fname)
|
|
409
443
|
@dirname = File.dirname(@fname)
|
|
410
444
|
userhome = File.expand_path("~")
|
|
411
|
-
@subtitle = @
|
|
445
|
+
@subtitle = @fname.gsub(/^#{userhome}/, "~")
|
|
412
446
|
vma.buffers.last_dir = @dirname
|
|
413
447
|
|
|
414
448
|
detect_file_language
|
|
@@ -464,6 +498,7 @@ class Buffer < String
|
|
|
464
498
|
|
|
465
499
|
if delta[1] == DELETE
|
|
466
500
|
delta[3] = self.slice!(delta[0], delta[2])
|
|
501
|
+
cleanup_deleted_images(delta[3])
|
|
467
502
|
@deltas << delta
|
|
468
503
|
update_index(pos, -delta[2])
|
|
469
504
|
update_line_ends(pos, -delta[2], delta[3])
|
|
@@ -485,6 +520,11 @@ class Buffer < String
|
|
|
485
520
|
@update_hl_startpos = pos
|
|
486
521
|
@update_hl_endpos = pos + delta[2]
|
|
487
522
|
add_hl_update(@update_hl_startpos, @update_hl_endpos)
|
|
523
|
+
|
|
524
|
+
if delta[3]&.include?("⟦img:")
|
|
525
|
+
buf_ref = self
|
|
526
|
+
run_as_idle proc { hpt_scan_images(buf_ref) }
|
|
527
|
+
end
|
|
488
528
|
end
|
|
489
529
|
debug("DELTA=#{delta.inspect}", 2)
|
|
490
530
|
# sanity_check_line_ends #TODO: enable with debug mode
|
|
@@ -1045,7 +1085,14 @@ class Buffer < String
|
|
|
1045
1085
|
wsm = scan_marks(p - maxws, p, /((?<=\s)\S)|^\S/)
|
|
1046
1086
|
word_start = wsm[-1]
|
|
1047
1087
|
word_end = wem[0]
|
|
1088
|
+
elsif boundary == :word2
|
|
1089
|
+
wem = scan_marks(p, p + maxws, /\b/,-1)
|
|
1090
|
+
wsm = scan_marks(p - maxws, p, /\b\w/)
|
|
1091
|
+
word_start = wsm[-1]
|
|
1092
|
+
word_end = wem.select{|x|x>=word_start}[0]
|
|
1093
|
+
|
|
1048
1094
|
elsif boundary == :word
|
|
1095
|
+
#TODO: change name :word. This works only with autocomplete
|
|
1049
1096
|
wsm = scan_marks(p - maxws, p, /\b\w/)
|
|
1050
1097
|
word_start = wsm[-1]
|
|
1051
1098
|
word_end = p
|
|
@@ -1247,10 +1294,12 @@ class Buffer < String
|
|
|
1247
1294
|
end
|
|
1248
1295
|
|
|
1249
1296
|
def end_selection()
|
|
1250
|
-
|
|
1297
|
+
puts("END SELECTION")
|
|
1298
|
+
# @selection_start = nil
|
|
1251
1299
|
@selection_active = false
|
|
1252
1300
|
@visual_mode = false
|
|
1253
1301
|
#TODO: remove @visual_mode
|
|
1302
|
+
# print(caller())
|
|
1254
1303
|
end
|
|
1255
1304
|
|
|
1256
1305
|
def start_selection()
|
|
@@ -1265,7 +1314,7 @@ class Buffer < String
|
|
|
1265
1314
|
end
|
|
1266
1315
|
|
|
1267
1316
|
def copy_active_selection(x = nil)
|
|
1268
|
-
debug "!COPY SELECTION"
|
|
1317
|
+
debug "!COPY SELECTION vm=#{@visual_mode}"
|
|
1269
1318
|
@paste_lines = false
|
|
1270
1319
|
return if !@visual_mode
|
|
1271
1320
|
|
|
@@ -1394,7 +1443,7 @@ class Buffer < String
|
|
|
1394
1443
|
r = get_visual_mode_range
|
|
1395
1444
|
if r.begin > r.end
|
|
1396
1445
|
debug "r.begin > r.end"
|
|
1397
|
-
|
|
1446
|
+
require "pry";binding.pry
|
|
1398
1447
|
end
|
|
1399
1448
|
return [r.begin, r.end]
|
|
1400
1449
|
end
|
|
@@ -1412,10 +1461,7 @@ class Buffer < String
|
|
|
1412
1461
|
def get_visual_mode_range()
|
|
1413
1462
|
_start = @selection_start
|
|
1414
1463
|
_end = @pos
|
|
1415
|
-
|
|
1416
1464
|
_start, _end = _end, _start if _start > _end
|
|
1417
|
-
# _end = _end + 1 if _start < _end #TODO:verify if correct
|
|
1418
|
-
# return _start..(_end - 1)
|
|
1419
1465
|
return _start..(_end)
|
|
1420
1466
|
end
|
|
1421
1467
|
|
|
@@ -1567,6 +1613,80 @@ class Buffer < String
|
|
|
1567
1613
|
return false
|
|
1568
1614
|
end
|
|
1569
1615
|
|
|
1616
|
+
AUTOSAVE_INTERVAL = 5 # seconds between autosave checks
|
|
1617
|
+
|
|
1618
|
+
def autosave_path
|
|
1619
|
+
return nil if @fname.nil?
|
|
1620
|
+
dir = File.dirname(@fname)
|
|
1621
|
+
base = File.basename(@fname)
|
|
1622
|
+
File.join(dir, ".#{base}_vma_autosave")
|
|
1623
|
+
end
|
|
1624
|
+
|
|
1625
|
+
def autosave
|
|
1626
|
+
return if @fname.nil?
|
|
1627
|
+
return if @t_modified <= @last_autosave
|
|
1628
|
+
apath = autosave_path
|
|
1629
|
+
contents = self.to_s
|
|
1630
|
+
Thread.new {
|
|
1631
|
+
begin
|
|
1632
|
+
File.open(apath, "w+") { |io| io.set_encoding(self.encoding); io.write(contents) }
|
|
1633
|
+
@last_autosave = Time.now
|
|
1634
|
+
debug "autosaved to #{apath}"
|
|
1635
|
+
rescue => ex
|
|
1636
|
+
debug "autosave failed: #{ex}"
|
|
1637
|
+
end
|
|
1638
|
+
}
|
|
1639
|
+
end
|
|
1640
|
+
|
|
1641
|
+
def start_autosave_timer
|
|
1642
|
+
@autosave_thread = Thread.new {
|
|
1643
|
+
loop do
|
|
1644
|
+
sleep AUTOSAVE_INTERVAL
|
|
1645
|
+
autosave
|
|
1646
|
+
end
|
|
1647
|
+
}
|
|
1648
|
+
end
|
|
1649
|
+
|
|
1650
|
+
def delete_autosave_file
|
|
1651
|
+
apath = autosave_path
|
|
1652
|
+
return if apath.nil?
|
|
1653
|
+
File.delete(apath) if File.exist?(apath)
|
|
1654
|
+
rescue => ex
|
|
1655
|
+
debug "delete autosave failed: #{ex}"
|
|
1656
|
+
end
|
|
1657
|
+
|
|
1658
|
+
def check_autosave_load
|
|
1659
|
+
apath = autosave_path
|
|
1660
|
+
return if apath.nil? || !File.exist?(apath)
|
|
1661
|
+
if File.read(apath) == self.to_s
|
|
1662
|
+
delete_autosave_file
|
|
1663
|
+
return
|
|
1664
|
+
end
|
|
1665
|
+
title = "An autosave file was found for <b>#{File.basename(@fname)}</b>. Load autosave?"
|
|
1666
|
+
params = {
|
|
1667
|
+
"title" => title,
|
|
1668
|
+
"inputs" => {
|
|
1669
|
+
"yes_btn" => { :label => "Load", :type => :button, :default_focus => true },
|
|
1670
|
+
"delete_btn" => { :label => "Delete autosave", :type => :button },
|
|
1671
|
+
},
|
|
1672
|
+
:callback => proc { |x| load_autosave_callback(x) },
|
|
1673
|
+
}
|
|
1674
|
+
PopupFormGenerator.new(params).run
|
|
1675
|
+
end
|
|
1676
|
+
|
|
1677
|
+
def load_autosave_callback(x)
|
|
1678
|
+
if x["yes_btn"] == "submit"
|
|
1679
|
+
str = read_file("", autosave_path)
|
|
1680
|
+
set_content(str)
|
|
1681
|
+
@t_modified = Time.now
|
|
1682
|
+
refresh_title
|
|
1683
|
+
message("Loaded autosave for #{@fname}")
|
|
1684
|
+
elsif x["delete_btn"] == "submit"
|
|
1685
|
+
delete_autosave_file
|
|
1686
|
+
message("Deleted autosave for #{@fname}")
|
|
1687
|
+
end
|
|
1688
|
+
end
|
|
1689
|
+
|
|
1570
1690
|
def save()
|
|
1571
1691
|
check_if_modified_outside #TODO
|
|
1572
1692
|
if !@fname
|
|
@@ -1575,6 +1695,7 @@ class Buffer < String
|
|
|
1575
1695
|
end
|
|
1576
1696
|
message("Saving file #{@fname}")
|
|
1577
1697
|
write_contents_to_file(@fname)
|
|
1698
|
+
delete_autosave_file
|
|
1578
1699
|
hook.call(:file_saved, self)
|
|
1579
1700
|
end
|
|
1580
1701
|
|
|
@@ -93,7 +93,7 @@ class Buffer < String
|
|
|
93
93
|
if true or running_wayland? and !GLib::Version::or_later?(2, 79, 0)
|
|
94
94
|
return paste_start_xclip(at, register)
|
|
95
95
|
end
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
clipboard = vma.gui.window.display.clipboard
|
|
98
98
|
clipboard.read_text_async do |_clipboard, result|
|
|
99
99
|
begin
|
|
@@ -135,6 +135,7 @@ class Buffer < String
|
|
|
135
135
|
set_pos(pos + text.size)
|
|
136
136
|
end
|
|
137
137
|
set_pos(@pos)
|
|
138
|
+
vma.buf.view.after_action # redraw
|
|
138
139
|
@clipboard_paste_running = false
|
|
139
140
|
end
|
|
140
141
|
|
|
@@ -152,6 +153,22 @@ class Buffer < String
|
|
|
152
153
|
return true
|
|
153
154
|
end
|
|
154
155
|
|
|
156
|
+
def increment_current_word()
|
|
157
|
+
debug "increment_current_word", 2
|
|
158
|
+
p = @pos
|
|
159
|
+
return if !is_legal_pos(p)
|
|
160
|
+
(word, range) = get_word_in_pos(p, boundary: :word2)
|
|
161
|
+
if word.match(/(true|false)/i)
|
|
162
|
+
rep = flip_true_false(word)
|
|
163
|
+
else
|
|
164
|
+
num = word.to_i
|
|
165
|
+
num += 1
|
|
166
|
+
rep = num.to_s
|
|
167
|
+
end
|
|
168
|
+
debug [word, range].to_s, 2
|
|
169
|
+
replace_range(range, rep)
|
|
170
|
+
end
|
|
171
|
+
|
|
155
172
|
def complete_current_word(rep)
|
|
156
173
|
debug "complete_current_word", 2
|
|
157
174
|
p = @pos - 1
|
|
@@ -217,7 +234,7 @@ class Buffer < String
|
|
|
217
234
|
|
|
218
235
|
def delete_range(startpos, endpos, x = nil)
|
|
219
236
|
s = self[startpos..endpos]
|
|
220
|
-
if startpos
|
|
237
|
+
if startpos > endpos or s == ""
|
|
221
238
|
return
|
|
222
239
|
end
|
|
223
240
|
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
|
@@ -42,6 +42,7 @@ class BufferList
|
|
|
42
42
|
vma.gui.set_current_buffer(vma.buf.id) #TODO: handle elswhere?
|
|
43
43
|
# vma.buf.view.set_cursor_pos(vma.buf.pos) #TODO: handle elswhere?
|
|
44
44
|
update_last_dir(_buf)
|
|
45
|
+
vma.gui.file_panel_refresh
|
|
45
46
|
end
|
|
46
47
|
|
|
47
48
|
def add(_buf)
|
|
@@ -72,6 +73,13 @@ class BufferList
|
|
|
72
73
|
end
|
|
73
74
|
end
|
|
74
75
|
|
|
76
|
+
def print_by_access_time
|
|
77
|
+
slist.reverse.each_with_index do |b, i|
|
|
78
|
+
name = b.fname || "(untitled)"
|
|
79
|
+
puts "#{i + 1}. #{b.access_time.strftime('%H:%M:%S')} #{name}"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
75
83
|
def get_last_visited_id
|
|
76
84
|
last_buf = nil
|
|
77
85
|
for i in 0..(slist.size - 1)
|
|
@@ -143,7 +151,9 @@ class BufferList
|
|
|
143
151
|
|
|
144
152
|
bu.set_active # TODO
|
|
145
153
|
bu.update_access_time if update_history
|
|
154
|
+
reset_navigation if update_history
|
|
146
155
|
vma.gui.set_current_buffer(idx)
|
|
156
|
+
vma.gui.file_panel_refresh
|
|
147
157
|
|
|
148
158
|
#TODO: delete?
|
|
149
159
|
# if !vma.buf.mode_stack.nil? and vma.kbd.get_scope != :editor #TODO
|
|
@@ -255,6 +265,8 @@ class BufferList
|
|
|
255
265
|
|
|
256
266
|
@list.delete(@h[idx])
|
|
257
267
|
@h.delete(idx)
|
|
268
|
+
gui_close_buffer(idx)
|
|
269
|
+
vma.gui.file_panel_refresh
|
|
258
270
|
|
|
259
271
|
if auto_open
|
|
260
272
|
@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]
|
|
@@ -112,6 +112,8 @@ cnf.lsp.enabled = false
|
|
|
112
112
|
cnf.fexp.experimental = false
|
|
113
113
|
cnf.experimental = false
|
|
114
114
|
|
|
115
|
+
cnf.kbd.show_prev_action = true
|
|
116
|
+
|
|
115
117
|
cnf.tab.width = 2
|
|
116
118
|
cnf.tab.to_spaces_default = false
|
|
117
119
|
cnf.tab.to_spaces_languages = ["c", "java", "ruby", "hyperplaintext", "php"]
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Map a "line number in a unified diff output" to the corresponding
|
|
2
|
+
# line number in the *new/changed file* (the + side).
|
|
3
|
+
#
|
|
4
|
+
# Key idea:
|
|
5
|
+
# @@ -old_start,old_count +new_start,new_count @@
|
|
6
|
+
# sets starting counters. Then walk each hunk line:
|
|
7
|
+
# ' ' => old++, new++
|
|
8
|
+
# '-' => old++
|
|
9
|
+
# '+' => new++
|
|
10
|
+
#
|
|
11
|
+
# If the target diff line is:
|
|
12
|
+
# ' ' or '+' => it corresponds to current new line (before increment)
|
|
13
|
+
# '-' => it has no new-file line (deleted). We return nil.
|
|
14
|
+
#
|
|
15
|
+
class DiffLineMapper
|
|
16
|
+
HUNK_RE = /^@@\s+-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s+@@/
|
|
17
|
+
|
|
18
|
+
def initialize(diff_text)
|
|
19
|
+
@lines = diff_text.lines
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Given a 1-based line number in the diff output, return:
|
|
23
|
+
# - Integer: 1-based line number in the new file
|
|
24
|
+
# - nil: if the diff line is a deletion ('-') or cannot be mapped
|
|
25
|
+
def new_line_for_diff_lineno(diff_lineno)
|
|
26
|
+
raise ArgumentError, "diff line number must be >= 1" if diff_lineno.to_i < 1
|
|
27
|
+
idx = diff_lineno.to_i - 1
|
|
28
|
+
return nil if idx >= @lines.length
|
|
29
|
+
|
|
30
|
+
old = nil
|
|
31
|
+
new_ = nil
|
|
32
|
+
in_hunk = false
|
|
33
|
+
|
|
34
|
+
@lines.each_with_index do |line, i|
|
|
35
|
+
if (m = line.match(HUNK_RE))
|
|
36
|
+
old = m[1].to_i
|
|
37
|
+
new_ = m[3].to_i
|
|
38
|
+
in_hunk = true
|
|
39
|
+
next
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
next unless in_hunk
|
|
43
|
+
|
|
44
|
+
if line.start_with?('--- ', '+++ ')
|
|
45
|
+
in_hunk = false
|
|
46
|
+
old = new_ = nil
|
|
47
|
+
next
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if i == idx
|
|
51
|
+
return nil unless new_
|
|
52
|
+
case line.getbyte(0)
|
|
53
|
+
when '+'.ord then return new_
|
|
54
|
+
when ' '.ord then return new_
|
|
55
|
+
when '-'.ord then return nil
|
|
56
|
+
else return nil
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
next unless old && new_
|
|
61
|
+
|
|
62
|
+
case line.getbyte(0)
|
|
63
|
+
when ' '.ord then old += 1; new_ += 1
|
|
64
|
+
when '-'.ord then old += 1
|
|
65
|
+
when '+'.ord then new_ += 1
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
nil
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def diff_buffer_init()
|
|
74
|
+
return if @diff_buffer_init_done
|
|
75
|
+
@diff_buffer_init_done = true
|
|
76
|
+
vma.kbd.add_minor_mode("diffview", :diffview, :command)
|
|
77
|
+
bindkey "diffview enter", :diff_buffer_jump_to_source
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def diff_buffer()
|
|
81
|
+
return if !if_cmd_exists("diff")
|
|
82
|
+
diff_buffer_init
|
|
83
|
+
orig_path = vma.buf.fname
|
|
84
|
+
infile = Tempfile.new("in")
|
|
85
|
+
infile.write(vma.buf.to_s)
|
|
86
|
+
infile.flush
|
|
87
|
+
bufstr = run_cmd("diff -uw '#{orig_path}' #{infile.path}")
|
|
88
|
+
infile.close; infile.unlink
|
|
89
|
+
create_new_file(nil, bufstr)
|
|
90
|
+
gui_set_file_lang(vma.buf.id, "diff")
|
|
91
|
+
vma.kbd.set_mode(:diffview)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def diff_buffer_jump_to_source()
|
|
95
|
+
mapper = DiffLineMapper.new(vma.buf.to_s)
|
|
96
|
+
cur_lpos = vma.buf.lpos + 1
|
|
97
|
+
to_line = mapper.new_line_for_diff_lineno(cur_lpos)
|
|
98
|
+
|
|
99
|
+
orig_path = nil
|
|
100
|
+
vma.buf.to_s.each_line do |l|
|
|
101
|
+
if l =~ /^--- (.+)/
|
|
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
|
|
111
|
+
end
|
|
112
|
+
if orig_path.nil? || !File.exist?(orig_path)
|
|
113
|
+
message("Could not find original file in diff")
|
|
114
|
+
return
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
jump_to_file(orig_path, to_line)
|
|
118
|
+
center_on_current_line
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def git_diff_buffer()
|
|
122
|
+
return if !if_cmd_exists("git")
|
|
123
|
+
diff_buffer_init
|
|
124
|
+
fname = vma.buf.fname
|
|
125
|
+
if fname.nil?
|
|
126
|
+
message("Buffer has no file")
|
|
127
|
+
return
|
|
128
|
+
end
|
|
129
|
+
bufstr = run_cmd("git diff -w -- #{Shellwords.escape(fname)}")
|
|
130
|
+
if bufstr.strip.empty?
|
|
131
|
+
message("git diff: no changes")
|
|
132
|
+
return
|
|
133
|
+
end
|
|
134
|
+
create_new_file(nil, bufstr)
|
|
135
|
+
gui_set_file_lang(vma.buf.id, "diff")
|
|
136
|
+
vma.kbd.set_mode(:diffview)
|
|
137
|
+
end
|