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
@@ -1,9 +1,5 @@
1
1
  require "pty"
2
2
 
3
- # def handle_drag_and_drop(fname)
4
- # debug "EDITOR:handle_drag_and_drop"
5
- # buf.handle_drag_and_drop(fname)
6
- # end
7
3
 
8
4
  class Editor
9
5
  attr_reader :file_content_search_paths, :file_name_search_paths, :gui, :hook, :macro, :actions
@@ -110,8 +106,7 @@ class Editor
110
106
 
111
107
  settings_path = get_dot_path("settings.rb")
112
108
  if File.exist?(settings_path)
113
- # = eval(IO.read(settings_path))
114
- #TODO
109
+ eval(IO.read(settings_path))
115
110
  end
116
111
 
117
112
  custom_script = read_file("", custom_fn)
@@ -132,6 +127,22 @@ class Editor
132
127
  #TODO: if language enabled in config?
133
128
  end
134
129
 
130
+ # Load each module that is enabled in settings.
131
+ # Each module lives in modules/<name>/<name>.rb and may optionally define
132
+ # <name>_init to register actions, key bindings, etc.
133
+ Dir.glob(ppath("modules/*/")).sort.each do |mod_dir|
134
+ mod_name = File.basename(mod_dir)
135
+ if cnf.modules.public_send(mod_name).enabled?
136
+ main_file = File.join(mod_dir, "#{mod_name}.rb")
137
+ if File.exist?(main_file)
138
+ load main_file
139
+ # Call <name>_init if the module defines it.
140
+ init_fn = "#{mod_name}_init"
141
+ send(init_fn) if respond_to?(init_fn, true)
142
+ end
143
+ end
144
+ end
145
+
135
146
  fname = nil
136
147
  if cnf.startup_file?
137
148
  fname_ = File.expand_path(cnf.startup_file!)
@@ -151,6 +162,8 @@ class Editor
151
162
  end
152
163
  end
153
164
 
165
+ argv_has_files = ARGV.any? { |a| File.file?(File.expand_path(a)) }
166
+
154
167
  if fname
155
168
  open_new_file(fname)
156
169
  else
@@ -167,7 +180,20 @@ class Editor
167
180
  # To access via vma.FileFinder
168
181
  # self.define_singleton_method(:FileFinder) { @_plugins[:FileFinder] }
169
182
 
183
+ check_session_restore unless argv_has_files
184
+
170
185
  @hook.call(:after_init)
186
+
187
+ if ARGV.include?("--test")
188
+ test_files = ARGV.select { |a| a.end_with?(".rb") && File.file?(a) }
189
+ run_as_idle proc {
190
+ test_files.each { |f| load f }
191
+ success = run_vma_tests
192
+ # Defer shutdown so GTK can drain all pending idle callbacks from test
193
+ # teardown before widget destruction begins (avoids heap corruption).
194
+ GLib::Timeout.add(300) { shutdown(); false }
195
+ }
196
+ end
171
197
  end
172
198
 
173
199
  def register_plugin(name, obj)
@@ -203,6 +229,7 @@ class Editor
203
229
 
204
230
  def save_var_to_file(varname, vardata)
205
231
  fn = get_dot_path(varname)
232
+ #TODO: check that save path is safe
206
233
  f = File.open(fn, "w")
207
234
  File.binwrite(f, vardata)
208
235
  f.close
@@ -225,6 +252,7 @@ class Editor
225
252
  end
226
253
 
227
254
  def shutdown()
255
+ save_session
228
256
  @hook.call(:shutdown)
229
257
  save_state
230
258
  @gui.quit
@@ -233,6 +261,45 @@ class Editor
233
261
  def save_state
234
262
  end
235
263
 
264
+ def save_session
265
+ fnames = vma.buffers.list
266
+ .map { |b| b.fname }
267
+ .compact
268
+ .select { |f| File.exist?(f) }
269
+ IO.write(get_dot_path("session.txt"), fnames.join("\n") + "\n")
270
+ rescue => ex
271
+ debug "save_session failed: #{ex}"
272
+ end
273
+
274
+ def check_session_restore
275
+ session_path = get_dot_path("session.txt")
276
+ return unless File.exist?(session_path)
277
+
278
+ fnames = File.read(session_path).lines.map(&:strip).reject(&:empty?)
279
+ fnames.select! { |f| File.exist?(f) }
280
+ fnames.reject! { |f| vma.buffers.get_buffer_by_filename(f) }
281
+ return if fnames.empty?
282
+
283
+ n = fnames.size
284
+ label = n == 1 ? "1 file" : "#{n} files"
285
+ params = {
286
+ "title" => "Restore previous session? (#{label})",
287
+ "inputs" => {
288
+ "yes_btn" => { :label => "Restore", :type => :button, :default_focus => true },
289
+ "no_btn" => { :label => "No", :type => :button },
290
+ },
291
+ :callback => proc { |x|
292
+ if x["yes_btn"] == "submit"
293
+ initial = vma.buffers.list.find { |b| b.fname.nil? }
294
+ fnames.each { |f| load_buffer(f) }
295
+ initial&.close
296
+ message("Session restored: #{label}")
297
+ end
298
+ },
299
+ }
300
+ PopupFormGenerator.new(params).run
301
+ end
302
+
236
303
  def add_content_search_path(pathstr)
237
304
  p = File.expand_path(pathstr)
238
305
  if !@file_content_search_paths.include?(p)
@@ -362,73 +429,13 @@ def set_next_command_count(num)
362
429
  debug("NEXT COMMAND COUNT: #{$next_command_count}")
363
430
  end
364
431
 
365
- def start_minibuffer_cmd(bufname, bufstr, cmd)
366
- vma.kbd.set_mode(:minibuffer)
367
- $minibuffer = Buffer.new(bufstr, "")
368
- $minibuffer.call_func = method(cmd)
369
- end
370
-
371
-
372
- def diff_buffer()
373
- bufstr = ""
374
- orig_path = vma.buf.fname
375
- infile = Tempfile.new("out")
376
- infile = Tempfile.new("in")
377
- infile.write(vma.buf.to_s)
378
- infile.flush
379
- cmd = "diff -w '#{orig_path}' #{infile.path}"
380
- # debug cmd
381
- bufstr << run_cmd(cmd)
382
- # debug bufstr
383
- infile.close; infile.unlink
384
- create_new_file(nil, bufstr)
385
- end
386
-
387
- def invoke_command()
388
- start_minibuffer_cmd("", "", :execute_command)
389
- end
390
-
391
- def execute_command(input_str)
392
- begin
393
- out_str = eval(input_str, TOPLEVEL_BINDING) #TODO: Other binding?
394
- $minibuffer.clear
395
- $minibuffer << out_str.to_s #TODO: segfaults, why?
396
- rescue SyntaxError
397
- debug("SYNTAX ERROR with eval cmd #{action}: " + $!.to_s)
398
- end
399
- end
400
-
401
- def minibuffer_end()
402
- debug "minibuffer_end"
403
- vma.kbd.set_mode(:command)
404
- minibuffer_input = $minibuffer.to_s[0..-2]
405
- return $minibuffer.call_func.call(minibuffer_input)
406
- end
407
-
408
- def minibuffer_cancel()
409
- debug "minibuffer_cancel"
410
- vma.kbd.set_mode(:command)
411
- minibuffer_input = $minibuffer.to_s[0..-2]
412
- # $minibuffer.call_func.call('')
413
- end
414
-
415
- def minibuffer_new_char(c)
416
- if c == "\r"
417
- raise "Should not come here"
418
- debug "MINIBUFFER END"
419
- else
420
- $minibuffer.insert_txt(c)
421
- debug "MINIBUFFER: #{c}"
432
+ def if_cmd_exists(cmd)
433
+ cmd = cmd.gsub(/[^a-zA-Z0-9_\-]/, '')
434
+ if !system("which #{cmd} > /dev/null 2>&1")
435
+ message("Command \"#{cmd}\" not found!")
436
+ return false
422
437
  end
423
- #vma.buf = $minibuffer
424
- end
425
-
426
- # def readchar_new_char(c)
427
- # $input_char_call_func.call(c)
428
- # end
429
-
430
- def minibuffer_delete()
431
- $minibuffer.delete(BACKWARD_CHAR)
438
+ return true
432
439
  end
433
440
 
434
441
  def error(str)
@@ -515,10 +522,13 @@ def load_buffer(fname)
515
522
  # If file already open in existing buffer
516
523
  existing_buffer = vma.buffers.get_buffer_by_filename(fname)
517
524
  if existing_buffer != nil
518
- vma.buffers.add_buf_to_history(existing_buffer)
519
525
  return
520
526
  end
521
527
  return if !File.exist?(fname)
528
+ if Encrypt.is_encrypted?(fname)
529
+ decrypt_dialog(filename: fname)
530
+ return nil
531
+ end
522
532
  debug("LOAD BUFFER: #{fname}")
523
533
  buffer = Buffer.new(read_file("", fname), fname)
524
534
  # gui_set_current_buffer(buffer.id)
@@ -527,7 +537,6 @@ def load_buffer(fname)
527
537
  #buf = filter_buffer(buffer)
528
538
  # debug("END FILTER: #{fname}")
529
539
  vma.buffers << buffer
530
- #$buffer_history << vma.buffers.size - 1
531
540
  return buffer
532
541
  end
533
542
 
@@ -585,6 +594,7 @@ def open_new_file(filename, file_contents = "")
585
594
  fname = filename
586
595
  bu = load_buffer(fname)
587
596
  vma.buffers.set_current_buffer_by_id(bu.id)
597
+ bu.check_autosave_load
588
598
  end
589
599
  return bu
590
600
  end
@@ -595,14 +605,9 @@ def scan_word_start_marks(search_str)
595
605
  return wsmarks
596
606
  end
597
607
 
598
- def hook_draw()
599
- # TODO: as hook.register
600
- # easy_jump_draw()
601
- end
602
-
603
608
  def get_dot_path(sfx)
604
609
  dot_dir = File.expand_path("~/.config/vimamsa")
605
- Dir.mkdir(dot_dir) unless File.exist?(dot_dir)
610
+ FileUtils.mkdir_p(dot_dir) unless File.exist?(dot_dir)
606
611
  dpath = "#{dot_dir}/#{sfx}"
607
612
  return dpath
608
613
  end
@@ -619,6 +624,24 @@ def get_file_line_pointer(s)
619
624
  return nil
620
625
  end
621
626
 
627
+ # Try to resolve a bare "filename.ext:line" reference (no directory component).
628
+ # Searches the current buffer's directory and the last visited directory.
629
+ def resolve_bare_file_line_pointer(s)
630
+ m = s.match(/\A([\w.\-]+\.[a-zA-Z0-9]+):(c?)(\d+)\z/)
631
+ return nil unless m
632
+ fname = m[1]
633
+ col = m[2]
634
+ line = m[3].to_i
635
+ dirs = []
636
+ dirs << File.dirname(vma.buf.fname) if vma.buf&.fname
637
+ dirs << bufs.last_dir if bufs.last_dir
638
+ dirs.uniq.each do |dir|
639
+ candidate = File.join(dir, fname)
640
+ return [candidate, line, col] if File.exist?(candidate)
641
+ end
642
+ nil
643
+ end
644
+
622
645
  def find_project_dir_of_fn(fn)
623
646
  pcomp = Pathname.new(fn).each_filename.to_a
624
647
  parent_dirs = (0..(pcomp.size - 2)).collect { |x| "/" + pcomp[0..x].join("/") }.reverse
@@ -647,3 +670,49 @@ def find_project_dir_of_cur_buffer()
647
670
  # debug "Proj dir of current file: #{pdir}"
648
671
  return pdir
649
672
  end
673
+
674
+ def reload_customrb
675
+ custom_fn = get_dot_path("custom.rb")
676
+ custom_script = read_file("", custom_fn)
677
+ eval(custom_script) if custom_script
678
+ message("Reloaded #{custom_fn}")
679
+ end
680
+
681
+ def save_settings_to_file
682
+ settings_path = get_dot_path("settings.rb")
683
+ lines = all_settings_defs.flat_map { |section| section[:settings] }.map do |s|
684
+ key_str = "cnf." + s[:key].join(".")
685
+ val = cnf_get(s[:key])
686
+ "#{key_str} = #{val.inspect}"
687
+ end
688
+ IO.write(settings_path, lines.join("\n") + "\n")
689
+ message("Settings saved to #{settings_path}")
690
+ end
691
+
692
+ DEMO_FILES = ["demo.txt", "sheep.jpg", "README.md"]
693
+
694
+ def install_demo_files
695
+ dest_dir = File.expand_path("~/Documents/VimamsaDemo")
696
+ mkdir_if_not_exists dest_dir
697
+ title = "Install demo files to #{dest_dir}?"
698
+ Gui.confirm(title, proc { |x| install_demo_files_callback(x) })
699
+ end
700
+
701
+ def install_demo_files_callback(x)
702
+ return unless x["yes_btn"] == "submit"
703
+ src_dir = File.expand_path("../..", __dir__)
704
+ dest_dir = File.expand_path("~/Documents/VimamsaDemo")
705
+ FileUtils.mkdir_p(dest_dir)
706
+ DEMO_FILES.each do |fname|
707
+ src = File.join(src_dir, fname)
708
+ dst = File.join(dest_dir, fname)
709
+ if File.exist?(src)
710
+ FileUtils.cp(src, dst)
711
+ else
712
+ message("Demo file not found: #{src}")
713
+ end
714
+ end
715
+ open_new_file(File.join(dest_dir, "demo.txt"))
716
+ message("Demo files installed to #{dest_dir}")
717
+ end
718
+
@@ -39,12 +39,16 @@ class FileFinder
39
39
 
40
40
  def initialize()
41
41
  vma.hook.register(:shutdown, self.method("save"))
42
- @@dir_list = vma.marshal_load("file_index")
42
+ # Load saved file list on startup (disabled for now, TODO)
43
+ # @@dir_list = vma.marshal_load("file_index")
43
44
  @@dir_list ||= []
44
- update_search_idx
45
+ # update_search_idx
46
+ FileFinder.recursively_find_files
47
+ message("FileFinder initialized, directories: #{cnf.search_dirs!}")
45
48
  end
46
49
 
47
50
  def self.update_search_idx
51
+
48
52
  Thread.new {
49
53
  sleep 0.1
50
54
  @@idx = StringIndex.new