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
data/lib/vimamsa/editor.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
|
data/lib/vimamsa/file_finder.rb
CHANGED
|
@@ -39,12 +39,16 @@ class FileFinder
|
|
|
39
39
|
|
|
40
40
|
def initialize()
|
|
41
41
|
vma.hook.register(:shutdown, self.method("save"))
|
|
42
|
-
|
|
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
|