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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +32 -0
  3. data/Dockerfile +45 -0
  4. data/README.md +2 -2
  5. data/custom_example.rb +38 -9
  6. data/docker_cmd.sh +7 -0
  7. data/exe/run_tests.rb +23 -0
  8. data/img/screenshot1.png +0 -0
  9. data/img/screenshot2.png +0 -0
  10. data/lib/vimamsa/actions.rb +8 -0
  11. data/lib/vimamsa/buffer.rb +165 -53
  12. data/lib/vimamsa/buffer_changetext.rb +68 -14
  13. data/lib/vimamsa/buffer_cursor.rb +9 -3
  14. data/lib/vimamsa/buffer_list.rb +14 -28
  15. data/lib/vimamsa/buffer_manager.rb +1 -1
  16. data/lib/vimamsa/conf.rb +33 -1
  17. data/lib/vimamsa/diff_buffer.rb +185 -0
  18. data/lib/vimamsa/editor.rb +149 -80
  19. data/lib/vimamsa/file_finder.rb +6 -2
  20. data/lib/vimamsa/gui.rb +330 -135
  21. data/lib/vimamsa/gui_dialog.rb +2 -0
  22. data/lib/vimamsa/gui_file_panel.rb +94 -0
  23. data/lib/vimamsa/gui_form_generator.rb +4 -2
  24. data/lib/vimamsa/gui_func_panel.rb +127 -0
  25. data/lib/vimamsa/gui_image.rb +2 -4
  26. data/lib/vimamsa/gui_menu.rb +54 -1
  27. data/lib/vimamsa/gui_select_window.rb +18 -6
  28. data/lib/vimamsa/gui_settings.rb +486 -0
  29. data/lib/vimamsa/gui_sourceview.rb +196 -8
  30. data/lib/vimamsa/gui_text.rb +0 -22
  31. data/lib/vimamsa/hyper_plain_text.rb +1 -0
  32. data/lib/vimamsa/key_actions.rb +54 -31
  33. data/lib/vimamsa/key_binding_tree.rb +154 -8
  34. data/lib/vimamsa/key_bindings_vimlike.rb +48 -35
  35. data/lib/vimamsa/langservp.rb +161 -7
  36. data/lib/vimamsa/macro.rb +54 -7
  37. data/lib/vimamsa/main.rb +1 -0
  38. data/lib/vimamsa/rbvma.rb +5 -0
  39. data/lib/vimamsa/string_util.rb +56 -0
  40. data/lib/vimamsa/test_framework.rb +137 -0
  41. data/lib/vimamsa/util.rb +3 -36
  42. data/lib/vimamsa/version.rb +1 -1
  43. data/modules/calculator/calculator.rb +318 -0
  44. data/modules/calculator/calculator_info.rb +3 -0
  45. data/modules/terminal/terminal.rb +140 -0
  46. data/modules/terminal/terminal_info.rb +3 -0
  47. data/run_tests.rb +89 -0
  48. data/styles/dark.xml +1 -1
  49. data/styles/molokai_edit.xml +2 -2
  50. data/tests/key_bindings.rb +2 -0
  51. data/tests/test_basic_editing.rb +86 -0
  52. data/tests/test_copy_paste.rb +88 -0
  53. data/tests/test_key_bindings.rb +152 -0
  54. data/tests/test_module_interface.rb +98 -0
  55. data/tests/test_undo.rb +201 -0
  56. data/vimamsa.gemspec +6 -5
  57. metadata +52 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3eba5b3c9a7821e14802295825b32369ff9440046b91b065ca2b1e82e44cc444
4
- data.tar.gz: 16bd4a94226c3b07cb5d40cb6e395a09f8fbbb4e96e164af19b05415048a9812
3
+ metadata.gz: b1c5be10ed2f57f72fe6aa73549680f6617b88149350b2afde434be82bfbe0a8
4
+ data.tar.gz: 941e4c0ddd0359306b91f1f8ebd3d3c91e156847e04c8ffd5ebd4ba7dbf27880
5
5
  SHA512:
6
- metadata.gz: f74f902af54d82002d4b0d814e80cafc5326bfb712c0cd124c39be0bb2bf3f70d1eaabe6c2f05de619ee9e9ac4cf12a10a4350c568ca1b815129904b40797c48
7
- data.tar.gz: a01c59f8358904e1ad511c4608de70bc31ac8bc8ccbbfa240eb897240a68f7673b7ae1bc124dcf312b94a6c7f12834d33dba692e6795713f083a5b2736ac6291
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://samiddhi.net/vimamsa/screenshot1.png" target="_blank"><img src="https://samiddhi.net/vimamsa/screenshot1.png" width="400"/></a>
67
- <a href="https://samiddhi.net/vimamsa/screenshot2.png" target="_blank"><img src="https://samiddhi.net/vimamsa/screenshot2.png" width="400"/></a>
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
- # setcnf :startup_file, "~/Documents/startup.txt"
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
+
Binary file
Binary file
@@ -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
@@ -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 = @dirname.gsub(/^#{userhome}/, "~")
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
- def undo()
527
- debug @edit_history.inspect
528
- return if !@edit_history.any?
529
- last_delta = @edit_history.pop
530
- @redo_stack << last_delta
531
- debug last_delta.inspect
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
- set_pos(last_delta[0])
542
- #recalc_line_ends #TODO: optimize?
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 !@redo_stack.any?
548
- #last_delta = @edit_history[-1].pop
549
- redo_delta = @redo_stack.pop
550
- #printf("==== UNDO ====\n")
551
- debug redo_delta.inspect
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