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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b1c5be10ed2f57f72fe6aa73549680f6617b88149350b2afde434be82bfbe0a8
|
|
4
|
+
data.tar.gz: 941e4c0ddd0359306b91f1f8ebd3d3c91e156847e04c8ffd5ebd4ba7dbf27880
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b161ee9c95585324ee772fb6c28bd148d8846bde5aec0a70117af51895ca82f7e179968d1b7dd05269b24f8e4a78f7a61de09db48c75531e252e6a07bb412bbd
|
|
7
|
+
data.tar.gz: f5b553a83eef33c833ca5fc0ef01da4b3c5004f2c44df8d1298940d61dd28ddc8863828249568c88feac0d611538a533d90ee914a61e4ddf499538eacae228ef
|
data/.dockerignore
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Let the container generate its own lockfile during bundle install
|
|
2
|
+
Gemfile.lock
|
|
3
|
+
|
|
4
|
+
# Compiled artifacts — rebuilt by bundle install
|
|
5
|
+
*.gem
|
|
6
|
+
*.o
|
|
7
|
+
*.so
|
|
8
|
+
ext/vmaext/Makefile
|
|
9
|
+
ext/vmaext/mkmf.log
|
|
10
|
+
|
|
11
|
+
# Autosave files
|
|
12
|
+
*_vma_autosave
|
|
13
|
+
|
|
14
|
+
# Reports and logs
|
|
15
|
+
test_report.txt
|
|
16
|
+
llm_report.txt
|
|
17
|
+
lsp-log.txt
|
|
18
|
+
|
|
19
|
+
# Bug/scratch notes
|
|
20
|
+
bug*.txt
|
|
21
|
+
*.txtf
|
|
22
|
+
|
|
23
|
+
# Media
|
|
24
|
+
*.mp3
|
|
25
|
+
*.wav
|
|
26
|
+
*.png
|
|
27
|
+
|
|
28
|
+
# Performance traces
|
|
29
|
+
perf*.svg
|
|
30
|
+
perf.data*
|
|
31
|
+
|
|
32
|
+
|
data/Dockerfile
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Vimamsa test container
|
|
2
|
+
#
|
|
3
|
+
# Runs the full test suite headlessly via xvfb.
|
|
4
|
+
#
|
|
5
|
+
# Build: docker build -t vimamsa-test .
|
|
6
|
+
# Run: docker run --rm vimamsa-test
|
|
7
|
+
#
|
|
8
|
+
# Ruby version matches development environment (see Makefile in project root).
|
|
9
|
+
|
|
10
|
+
FROM ruby:3.1-slim
|
|
11
|
+
|
|
12
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
|
13
|
+
|
|
14
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
15
|
+
build-essential \
|
|
16
|
+
ruby-dev \
|
|
17
|
+
git \
|
|
18
|
+
pkg-config \
|
|
19
|
+
libgtk-4-dev \
|
|
20
|
+
libgtksourceview-5-dev \
|
|
21
|
+
libgstreamer1.0-dev \
|
|
22
|
+
libgstreamer-plugins-base1.0-dev \
|
|
23
|
+
libvte-2.91-gtk4-dev \
|
|
24
|
+
xvfb \
|
|
25
|
+
xauth \
|
|
26
|
+
libssl-dev
|
|
27
|
+
|
|
28
|
+
WORKDIR /app
|
|
29
|
+
|
|
30
|
+
# COPY .git/ .git/
|
|
31
|
+
# COPY vimamsa.gemspec Gemfile ./
|
|
32
|
+
# COPY lib/vimamsa/version.rb lib/vimamsa/
|
|
33
|
+
# COPY ext/vmaext/ ext/vmaext/
|
|
34
|
+
|
|
35
|
+
RUN gem install bundler -v '~> 2.4'
|
|
36
|
+
|
|
37
|
+
RUN echo "alias ll='ls -ltrh'" >> ~/.bashrc
|
|
38
|
+
ARG CACHE_BUST=1
|
|
39
|
+
RUN git clone https://github.com/SamiSieranoja/vimamsa.git && cd vimamsa && gem build vimamsa.gemspec && gem install vimamsa-0.1.*.gem
|
|
40
|
+
RUN cp /usr/local/bundle/gems/vimamsa-0.1.23/ext/vmaext/vmaext.so vimamsa/lib/
|
|
41
|
+
|
|
42
|
+
WORKDIR /app/vimamsa
|
|
43
|
+
CMD ["bash", "-c", "xvfb-run -a ruby run_tests.rb 2>&1"]
|
|
44
|
+
|
|
45
|
+
|
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
|
|
|
@@ -14,15 +14,8 @@
|
|
|
14
14
|
# setcnf :tab_width, 4
|
|
15
15
|
|
|
16
16
|
# Open this file every time the program starts
|
|
17
|
-
|
|
17
|
+
cnf.startup_file = "~/Documents/startup.txt"
|
|
18
18
|
|
|
19
|
-
# To enable LSP:
|
|
20
|
-
# cnf.lsp.enabled = true
|
|
21
|
-
# cnf.lsp.server.solargraph = { name: "solargraph", command: "solargraph stdio", type: "stdio" }
|
|
22
|
-
# cnf.lsp.server.solargraph.languages = ["ruby"]
|
|
23
|
-
|
|
24
|
-
# cnf.lsp.server.clangd = { name: "clangd", command: "clangd-12 --offset-encoding=utf-8", type: "stdio" }
|
|
25
|
-
# cnf.lsp.server.clangd.languages = ["c", "cpp"]
|
|
26
19
|
|
|
27
20
|
|
|
28
21
|
|
|
@@ -32,6 +25,9 @@ def insert_date()
|
|
|
32
25
|
vma.buf.insert_txt("#{DateTime.now().strftime("%Y-%m-%d")}\n")
|
|
33
26
|
end
|
|
34
27
|
|
|
28
|
+
bindkey "C , i d", proc { insert_date }
|
|
29
|
+
|
|
30
|
+
|
|
35
31
|
def collect_c_header()
|
|
36
32
|
# Matches e.g.:
|
|
37
33
|
# static void funcname(parameters)
|
|
@@ -66,4 +62,37 @@ end
|
|
|
66
62
|
# Find with action search ([C] , , s)
|
|
67
63
|
reg_act(:insert_lorem_ipsum, proc { insert_lorem_ipsum }, "Insert lorem ipsum")
|
|
68
64
|
|
|
65
|
+
def open_mtg(urlpref = nil)
|
|
66
|
+
require "uri"
|
|
67
|
+
l = vma.buf.get_current_line.strip
|
|
68
|
+
m = l.match(/[^;!]+/)
|
|
69
|
+
if m
|
|
70
|
+
cardname = m[0].strip
|
|
71
|
+
# cardname = "Omnath, locus of mana"
|
|
72
|
+
u = URI.encode_www_form_component(cardname)
|
|
73
|
+
url = "https://gatherer.wizards.com/Pages/Card/Details.aspx?name=#{u}"
|
|
74
|
+
if !urlpref.nil?
|
|
75
|
+
url = urlpref + u
|
|
76
|
+
end
|
|
77
|
+
open_url(url)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# reg_act(:open_mtg, proc { open_mtg }, "open mtg card info")
|
|
82
|
+
# bindkey "C , , m", :open_mtg
|
|
83
|
+
# Restless Cottage
|
|
84
|
+
# https://duckduckgo.com/?ia=web&origin=funnel_home_website&t=h_&q=
|
|
85
|
+
|
|
86
|
+
# Primitive support for LSP (not well tested)
|
|
87
|
+
# To enable LSP:
|
|
88
|
+
# cnf.lsp.enabled = true
|
|
89
|
+
# cnf.lsp.server.solargraph = { name: "solargraph", command: "solargraph stdio", type: "stdio" }
|
|
90
|
+
# cnf.lsp.server.solargraph.languages = ["ruby"]
|
|
91
|
+
|
|
92
|
+
# cnf.lsp.server.clangd = { name: "clangd", command: "clangd-12 --offset-encoding=utf-8", type: "stdio" }
|
|
93
|
+
# cnf.lsp.server.clangd.languages = ["c", "cpp"]
|
|
94
|
+
|
|
95
|
+
# Uncomment this if you don't want to see the state trail of previous action
|
|
96
|
+
# on top right corner:
|
|
97
|
+
# cnf.kbd.show_prev_action = false
|
|
69
98
|
|
data/docker_cmd.sh
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
gem build vimamsa.gemspec
|
|
2
|
+
sudo gem install --local vimamsa-0.1.*.gem
|
|
3
|
+
cp /usr/local/bundle/gems/vimamsa-0.1.23/ext/vmaext/vmaext.so .
|
|
4
|
+
cp /usr/local/bundle/gems/vimamsa-0.1.23/ext/vmaext/vmaext.so lib/
|
|
5
|
+
# xvfb-run -a bundle exec ruby exe/run_tests.rb --test tests/test_basic_editing.rb
|
|
6
|
+
xvfb-run -a bundle exec ruby run_tests.rb
|
|
7
|
+
|
data/exe/run_tests.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/ruby
|
|
2
|
+
require "ripl/multi_line"
|
|
3
|
+
require "tempfile"
|
|
4
|
+
require "pathname"
|
|
5
|
+
|
|
6
|
+
ENV["GTK_THEME"] = "Adwaita:light"
|
|
7
|
+
|
|
8
|
+
selfpath = __FILE__
|
|
9
|
+
selfpath = File.readlink(selfpath) if File.lstat(selfpath).symlink?
|
|
10
|
+
scriptdir = File.expand_path(File.dirname(selfpath) + "/..")
|
|
11
|
+
|
|
12
|
+
$LOAD_PATH.unshift(File.expand_path("lib"))
|
|
13
|
+
$LOAD_PATH.unshift(File.expand_path("ext"))
|
|
14
|
+
begin
|
|
15
|
+
gem_spec = Gem::Specification.find_by_name("vimamsa")
|
|
16
|
+
$LOAD_PATH.unshift(File.join(gem_spec.gem_dir, "ext", "vmaext")) if gem_spec
|
|
17
|
+
rescue Gem::MissingSpecError
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
require "vimamsa"
|
|
21
|
+
$vmag = VMAgui.new()
|
|
22
|
+
$vmag.run
|
|
23
|
+
|
data/img/screenshot1.png
ADDED
|
Binary file
|
data/img/screenshot2.png
ADDED
|
Binary file
|
data/lib/vimamsa/actions.rb
CHANGED
|
@@ -10,6 +10,10 @@ class Action
|
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
def unreg_act(id)
|
|
14
|
+
vma.actions.unregister(id)
|
|
15
|
+
end
|
|
16
|
+
|
|
13
17
|
def reg_act(id, callfunc, name = "", opt = {})
|
|
14
18
|
if callfunc.class == Proc
|
|
15
19
|
a = Action.new(id, name, callfunc, opt)
|
|
@@ -43,6 +47,10 @@ class ActionList
|
|
|
43
47
|
@actions[id] = obj
|
|
44
48
|
end
|
|
45
49
|
|
|
50
|
+
def unregister(id)
|
|
51
|
+
@actions.delete(id)
|
|
52
|
+
end
|
|
53
|
+
|
|
46
54
|
def include?(act)
|
|
47
55
|
return @actions.has_key?(act)
|
|
48
56
|
end
|
data/lib/vimamsa/buffer.rb
CHANGED
|
@@ -5,10 +5,6 @@ require "pathname"
|
|
|
5
5
|
require "openssl"
|
|
6
6
|
require "ripl/multi_line"
|
|
7
7
|
|
|
8
|
-
$buffer_history = []
|
|
9
|
-
|
|
10
|
-
$update_highlight = false
|
|
11
|
-
|
|
12
8
|
$ifuncon = false
|
|
13
9
|
|
|
14
10
|
class Buffer < String
|
|
@@ -42,6 +38,8 @@ class Buffer < String
|
|
|
42
38
|
|
|
43
39
|
@last_save = @last_asked_from_user = @file_last_cheked = Time.now
|
|
44
40
|
@t_modified = @last_save
|
|
41
|
+
@last_autosave = @last_save
|
|
42
|
+
@autosave_thread = nil
|
|
45
43
|
|
|
46
44
|
@crypt = nil
|
|
47
45
|
@update_highlight = true
|
|
@@ -65,6 +63,7 @@ class Buffer < String
|
|
|
65
63
|
if cnf.lsp.enabled?
|
|
66
64
|
init_lsp
|
|
67
65
|
end
|
|
66
|
+
start_autosave_timer
|
|
68
67
|
return self
|
|
69
68
|
end
|
|
70
69
|
|
|
@@ -237,11 +236,40 @@ class Buffer < String
|
|
|
237
236
|
return words
|
|
238
237
|
end
|
|
239
238
|
|
|
239
|
+
# TODO: review
|
|
240
|
+
def cleanup_deleted_images(deleted_text)
|
|
241
|
+
return if @images.empty?
|
|
242
|
+
return unless deleted_text&.include?("⟦img:")
|
|
243
|
+
deleted_text.scan(/⟦img:(.+?)⟧/) do |match|
|
|
244
|
+
tag_path = match[0]
|
|
245
|
+
abs_path = translate_path(tag_path, self)
|
|
246
|
+
@images.delete_if do |img|
|
|
247
|
+
next false unless img[:path] == abs_path || img[:path] == tag_path
|
|
248
|
+
# img[:obj].destroy unless img[:obj].destroyed?
|
|
249
|
+
# Schedule anchor character deletion after the current delta is done.
|
|
250
|
+
# Using the anchor object lets us find the correct position even if
|
|
251
|
+
# prior edits shifted it.
|
|
252
|
+
anchor = img[:anchor]
|
|
253
|
+
buf_ref = self
|
|
254
|
+
run_as_idle proc {
|
|
255
|
+
next if anchor.nil? || anchor.deleted?
|
|
256
|
+
itr = buf_ref.view.buffer.get_iter_at_child_anchor(anchor)
|
|
257
|
+
next if itr.nil?
|
|
258
|
+
pos = itr.offset
|
|
259
|
+
buf_ref.add_delta([pos, DELETE, 1], true)
|
|
260
|
+
buf_ref.calculate_line_and_column_pos
|
|
261
|
+
}
|
|
262
|
+
true
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
240
267
|
def add_image(imgpath, pos)
|
|
241
268
|
return if !is_legal_pos(pos)
|
|
242
269
|
|
|
243
270
|
vbuf = view.buffer
|
|
244
271
|
itr = vbuf.get_iter_at(:offset => pos)
|
|
272
|
+
return if itr&.child_anchor # anchor already exists here, image already rendered
|
|
245
273
|
itr2 = vbuf.get_iter_at(:offset => pos + 1)
|
|
246
274
|
vbuf.delete(itr, itr2)
|
|
247
275
|
anchor = vbuf.create_child_anchor(itr)
|
|
@@ -256,7 +284,7 @@ class Buffer < String
|
|
|
256
284
|
da.scale_image
|
|
257
285
|
|
|
258
286
|
# vma.gui.handle_image_resize #TODO:gtk4
|
|
259
|
-
@images << { :path => imgpath, :obj => da }
|
|
287
|
+
@images << { :path => imgpath, :obj => da, :anchor => anchor }
|
|
260
288
|
|
|
261
289
|
gui_set_current_buffer(@id) #TODO: needed?
|
|
262
290
|
end
|
|
@@ -308,12 +336,15 @@ class Buffer < String
|
|
|
308
336
|
message("Revert buffer #{@fname}")
|
|
309
337
|
str = read_file("", @fname)
|
|
310
338
|
self.set_content(str)
|
|
339
|
+
@last_save = Time.now
|
|
340
|
+
refresh_title
|
|
311
341
|
end
|
|
312
342
|
|
|
313
343
|
def init_encrypted(crypt:, filename:, encrypted:)
|
|
314
344
|
@crypt = crypt
|
|
315
345
|
@encrypted_str = encrypted
|
|
316
346
|
set_filename(filename)
|
|
347
|
+
gui_set_buffer_contents(@id, self.to_s)
|
|
317
348
|
end
|
|
318
349
|
|
|
319
350
|
def sanitycheck_btree()
|
|
@@ -376,7 +407,10 @@ class Buffer < String
|
|
|
376
407
|
@need_redraw = 1
|
|
377
408
|
@call_func = nil
|
|
378
409
|
@deltas = []
|
|
379
|
-
@edit_history = []
|
|
410
|
+
@edit_history = [] # Array of groups: [[delta, ...], ...]
|
|
411
|
+
@current_group = [] # In-progress undo group
|
|
412
|
+
@last_delta_time = nil
|
|
413
|
+
@macro_group_active = false
|
|
380
414
|
@redo_stack = []
|
|
381
415
|
@edit_pos_history = []
|
|
382
416
|
@edit_pos_history_i = 0
|
|
@@ -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,12 +520,16 @@ 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
|
|
491
531
|
#highlight_c()
|
|
492
532
|
|
|
493
|
-
$update_highlight = true
|
|
494
533
|
@update_highlight = true
|
|
495
534
|
|
|
496
535
|
return delta
|
|
@@ -523,36 +562,39 @@ class Buffer < String
|
|
|
523
562
|
end
|
|
524
563
|
end
|
|
525
564
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
if last_delta[1] == DELETE
|
|
533
|
-
d = [last_delta[0], INSERT, 0, last_delta[3]]
|
|
534
|
-
run_delta(d)
|
|
535
|
-
elsif last_delta[1] == INSERT
|
|
536
|
-
d = [last_delta[0], DELETE, last_delta[3].size]
|
|
537
|
-
run_delta(d)
|
|
538
|
-
else
|
|
539
|
-
return #TODO: assert?
|
|
565
|
+
# Flush @current_group into @edit_history and start a new group.
|
|
566
|
+
# Called on mode changes, macro boundaries, and time threshold.
|
|
567
|
+
def new_undo_group
|
|
568
|
+
if @current_group.any?
|
|
569
|
+
@edit_history << @current_group
|
|
570
|
+
@current_group = []
|
|
540
571
|
end
|
|
541
|
-
|
|
542
|
-
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
def apply_inverse_delta(d)
|
|
575
|
+
if d[1] == DELETE
|
|
576
|
+
run_delta([d[0], INSERT, 0, d[3]])
|
|
577
|
+
elsif d[1] == INSERT
|
|
578
|
+
run_delta([d[0], DELETE, d[3].size])
|
|
579
|
+
end
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
def undo()
|
|
583
|
+
new_undo_group # flush any in-progress group first
|
|
584
|
+
return if @edit_history.empty?
|
|
585
|
+
group = @edit_history.pop
|
|
586
|
+
@redo_stack << group
|
|
587
|
+
group.reverse_each { |d| apply_inverse_delta(d) }
|
|
588
|
+
set_pos(group.first[0])
|
|
543
589
|
calculate_line_and_column_pos
|
|
544
590
|
end
|
|
545
591
|
|
|
546
592
|
def redo()
|
|
547
|
-
return if
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
run_delta(redo_delta)
|
|
553
|
-
@edit_history << redo_delta
|
|
554
|
-
set_pos(redo_delta[0])
|
|
555
|
-
#recalc_line_ends #TODO: optimize?
|
|
593
|
+
return if @redo_stack.empty?
|
|
594
|
+
group = @redo_stack.pop
|
|
595
|
+
group.each { |d| run_delta(d) }
|
|
596
|
+
@edit_history << group
|
|
597
|
+
set_pos(group.last[0])
|
|
556
598
|
calculate_line_and_column_pos
|
|
557
599
|
end
|
|
558
600
|
|
|
@@ -1045,7 +1087,14 @@ class Buffer < String
|
|
|
1045
1087
|
wsm = scan_marks(p - maxws, p, /((?<=\s)\S)|^\S/)
|
|
1046
1088
|
word_start = wsm[-1]
|
|
1047
1089
|
word_end = wem[0]
|
|
1090
|
+
elsif boundary == :word2
|
|
1091
|
+
wem = scan_marks(p, p + maxws, /\b/,-1)
|
|
1092
|
+
wsm = scan_marks(p - maxws, p, /\b\w/)
|
|
1093
|
+
word_start = wsm[-1]
|
|
1094
|
+
word_end = wem.select{|x|x>=word_start}[0]
|
|
1095
|
+
|
|
1048
1096
|
elsif boundary == :word
|
|
1097
|
+
#TODO: change name :word. This works only with autocomplete
|
|
1049
1098
|
wsm = scan_marks(p - maxws, p, /\b\w/)
|
|
1050
1099
|
word_start = wsm[-1]
|
|
1051
1100
|
word_end = p
|
|
@@ -1086,6 +1135,9 @@ class Buffer < String
|
|
|
1086
1135
|
elsif linep != nil
|
|
1087
1136
|
wtype = :linepointer
|
|
1088
1137
|
word = linep
|
|
1138
|
+
elsif (bare_linep = resolve_bare_file_line_pointer(word))
|
|
1139
|
+
wtype = :linepointer
|
|
1140
|
+
word = bare_linep
|
|
1089
1141
|
elsif m = word.match(/⟦help:(.*)⟧/)
|
|
1090
1142
|
return [m[1], :help]
|
|
1091
1143
|
else
|
|
@@ -1247,16 +1299,17 @@ class Buffer < String
|
|
|
1247
1299
|
end
|
|
1248
1300
|
|
|
1249
1301
|
def end_selection()
|
|
1250
|
-
@selection_start = nil
|
|
1302
|
+
# @selection_start = nil
|
|
1251
1303
|
@selection_active = false
|
|
1252
1304
|
@visual_mode = false
|
|
1253
1305
|
#TODO: remove @visual_mode
|
|
1306
|
+
# print(caller())
|
|
1254
1307
|
end
|
|
1255
1308
|
|
|
1256
1309
|
def start_selection()
|
|
1257
1310
|
@selection_start = @pos
|
|
1258
1311
|
@selection_active = true
|
|
1259
|
-
@visual_mode = true
|
|
1312
|
+
@visual_mode = true #TODO: change use of @visual_mode into @selection_active
|
|
1260
1313
|
end
|
|
1261
1314
|
|
|
1262
1315
|
# Start selection if not already started
|
|
@@ -1265,7 +1318,7 @@ class Buffer < String
|
|
|
1265
1318
|
end
|
|
1266
1319
|
|
|
1267
1320
|
def copy_active_selection(x = nil)
|
|
1268
|
-
debug "!COPY SELECTION"
|
|
1321
|
+
debug "!COPY SELECTION vm=#{@visual_mode}"
|
|
1269
1322
|
@paste_lines = false
|
|
1270
1323
|
return if !@visual_mode
|
|
1271
1324
|
|
|
@@ -1392,10 +1445,6 @@ class Buffer < String
|
|
|
1392
1445
|
|
|
1393
1446
|
def get_visual_mode_range2()
|
|
1394
1447
|
r = get_visual_mode_range
|
|
1395
|
-
if r.begin > r.end
|
|
1396
|
-
debug "r.begin > r.end"
|
|
1397
|
-
Ripl.start :binding => binding
|
|
1398
|
-
end
|
|
1399
1448
|
return [r.begin, r.end]
|
|
1400
1449
|
end
|
|
1401
1450
|
|
|
@@ -1410,12 +1459,9 @@ class Buffer < String
|
|
|
1410
1459
|
end
|
|
1411
1460
|
|
|
1412
1461
|
def get_visual_mode_range()
|
|
1413
|
-
_start = @selection_start
|
|
1462
|
+
_start = @selection_start || @pos
|
|
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
|
|
|
@@ -1600,15 +1721,6 @@ class Buffer < String
|
|
|
1600
1721
|
end
|
|
1601
1722
|
end
|
|
1602
1723
|
|
|
1603
|
-
#TODO: function not used
|
|
1604
|
-
def write_to_file(savepath, s)
|
|
1605
|
-
if is_path_writable(savepath)
|
|
1606
|
-
IO.write(savepath, self.to_s)
|
|
1607
|
-
else
|
|
1608
|
-
message("PATH NOT WRITABLE: #{savepath}")
|
|
1609
|
-
end
|
|
1610
|
-
end
|
|
1611
|
-
|
|
1612
1724
|
def is_path_writable(fpath)
|
|
1613
1725
|
r = false
|
|
1614
1726
|
if fpath.class == String
|