vimamsa 0.1.23 → 0.1.25
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/custom_example.rb +37 -11
- data/docker_cmd.sh +7 -0
- data/exe/run_tests.rb +23 -0
- data/lib/vimamsa/actions.rb +8 -0
- data/lib/vimamsa/buffer.rb +38 -47
- data/lib/vimamsa/buffer_changetext.rb +49 -12
- data/lib/vimamsa/buffer_list.rb +2 -28
- data/lib/vimamsa/conf.rb +30 -0
- data/lib/vimamsa/diff_buffer.rb +80 -32
- data/lib/vimamsa/editor.rb +54 -67
- data/lib/vimamsa/file_finder.rb +6 -2
- data/lib/vimamsa/gui.rb +247 -63
- data/lib/vimamsa/gui_file_panel.rb +1 -0
- data/lib/vimamsa/gui_func_panel.rb +127 -0
- data/lib/vimamsa/gui_menu.rb +42 -0
- data/lib/vimamsa/gui_select_window.rb +17 -6
- data/lib/vimamsa/gui_settings.rb +347 -13
- data/lib/vimamsa/gui_sourceview.rb +116 -2
- data/lib/vimamsa/gui_text.rb +0 -22
- data/lib/vimamsa/hyper_plain_text.rb +1 -0
- data/lib/vimamsa/key_actions.rb +30 -29
- data/lib/vimamsa/key_binding_tree.rb +85 -3
- data/lib/vimamsa/key_bindings_vimlike.rb +4 -0
- data/lib/vimamsa/langservp.rb +161 -7
- data/lib/vimamsa/macro.rb +54 -7
- data/lib/vimamsa/rbvma.rb +2 -0
- data/lib/vimamsa/test_framework.rb +137 -0
- 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 +46 -14
data/lib/vimamsa/gui.rb
CHANGED
|
@@ -126,12 +126,11 @@ def gui_create_buffer(id, bufo)
|
|
|
126
126
|
|
|
127
127
|
view.register_signals()
|
|
128
128
|
|
|
129
|
-
|
|
130
|
-
ssm.set_search_path(ssm.search_path << ppath("styles/"))
|
|
131
|
-
sty = ssm.get_scheme("molokai_edit")
|
|
129
|
+
sty = load_vimamsa_scheme
|
|
132
130
|
|
|
133
131
|
buf1.highlight_matching_brackets = true
|
|
134
132
|
buf1.style_scheme = sty
|
|
133
|
+
gui_apply_color_mode(sty) if sty
|
|
135
134
|
|
|
136
135
|
view.set_highlight_current_line(true)
|
|
137
136
|
view.set_show_line_numbers(true)
|
|
@@ -165,9 +164,6 @@ def gui_set_file_lang(id, lname)
|
|
|
165
164
|
view.buffer.highlight_syntax = true
|
|
166
165
|
end
|
|
167
166
|
|
|
168
|
-
def gui_add_image(imgpath, pos)
|
|
169
|
-
end
|
|
170
|
-
|
|
171
167
|
# TODO:?
|
|
172
168
|
def gui_select_window_close(arg = nil)
|
|
173
169
|
end
|
|
@@ -198,7 +194,7 @@ end
|
|
|
198
194
|
|
|
199
195
|
class VMAgui
|
|
200
196
|
attr_accessor :buffers, :sw1, :sw2, :view, :buf1, :window, :delex, :statnfo, :overlay, :sws, :two_c
|
|
201
|
-
attr_reader :two_column, :windows, :subtitle, :app, :active_window, :action_trail_label, :file_panel
|
|
197
|
+
attr_reader :two_column, :windows, :subtitle, :app, :active_window, :action_trail_label, :file_panel, :func_panel
|
|
202
198
|
|
|
203
199
|
def initialize()
|
|
204
200
|
@two_column = false
|
|
@@ -212,6 +208,13 @@ class VMAgui
|
|
|
212
208
|
@img_resizer_active = false
|
|
213
209
|
@windows = {}
|
|
214
210
|
@app = nil
|
|
211
|
+
@entry_has_focus = false
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Called by embedded widgets (e.g. calculator entries) when a text entry
|
|
215
|
+
# gains or loses focus, so the vimamsa key handler knows to step aside.
|
|
216
|
+
def notify_entry_focus(active)
|
|
217
|
+
@entry_has_focus = active
|
|
215
218
|
end
|
|
216
219
|
|
|
217
220
|
def run
|
|
@@ -320,39 +323,131 @@ class VMAgui
|
|
|
320
323
|
end
|
|
321
324
|
|
|
322
325
|
def add_to_minibuf(msg)
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
@
|
|
326
|
-
|
|
326
|
+
@minibuf_messages ||= []
|
|
327
|
+
@minibuf_messages.unshift(msg)
|
|
328
|
+
@minibuf_messages = @minibuf_messages.first(50)
|
|
329
|
+
|
|
330
|
+
@minibuf_label.label = msg
|
|
331
|
+
@minibuf_textview.buffer.text = @minibuf_messages.join("\n")
|
|
332
|
+
|
|
333
|
+
return if @minibuf_expanded
|
|
334
|
+
|
|
335
|
+
@minibuf_stack.visible_child_name = "label"
|
|
336
|
+
@minibuf_content.visible = true
|
|
337
|
+
|
|
338
|
+
@minibuf_vpane.position = @minibuf_vpane.height - 26
|
|
339
|
+
|
|
340
|
+
GLib::Source.remove(@minibuf_hide_source) if @minibuf_hide_source
|
|
341
|
+
@minibuf_hide_source = GLib::Timeout.add(7000) do
|
|
342
|
+
@minibuf_content.visible = false
|
|
343
|
+
@minibuf_hide_source = nil
|
|
344
|
+
false
|
|
345
|
+
end
|
|
327
346
|
end
|
|
328
347
|
|
|
329
|
-
def
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
348
|
+
def minibuf_toggle_expanded
|
|
349
|
+
@minibuf_expanded = !@minibuf_expanded
|
|
350
|
+
if @minibuf_expanded
|
|
351
|
+
GLib::Source.remove(@minibuf_hide_source) if @minibuf_hide_source
|
|
352
|
+
@minibuf_hide_source = nil
|
|
353
|
+
@minibuf_content.visible = true
|
|
354
|
+
@minibuf_stack.visible_child_name = "history"
|
|
355
|
+
run_as_idle proc {
|
|
356
|
+
pos = @minibuf_vpane.max_position - (@minibuf_history_height || 120)
|
|
357
|
+
@minibuf_vpane.position = [pos, 0].max
|
|
358
|
+
}
|
|
359
|
+
else
|
|
360
|
+
# Save height before collapsing so next open restores it
|
|
361
|
+
@minibuf_history_height = @minibuf_vpane.max_position - @minibuf_vpane.position
|
|
362
|
+
@minibuf_stack.visible_child_name = "label"
|
|
363
|
+
run_as_idle proc {
|
|
364
|
+
#TODO: automatic way of resizing doesn't work:
|
|
365
|
+
# h = [@minibuf_label.height, 24].max + 2
|
|
366
|
+
# @minibuf_vpane.position = @minibuf_vpane.max_position - h
|
|
367
|
+
@minibuf_vpane.position = @minibuf_vpane.height - 26
|
|
368
|
+
}
|
|
369
|
+
@minibuf_hide_source = GLib::Timeout.add(7000) do
|
|
370
|
+
@minibuf_content.visible = false
|
|
371
|
+
@minibuf_expanded = false
|
|
372
|
+
@minibuf_stack.visible_child_name = "label"
|
|
373
|
+
@minibuf_hide_source = nil
|
|
374
|
+
false
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def show_message_history
|
|
380
|
+
minibuf_toggle_expanded
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# ── editor-area helpers (delegates to @minibuf_vpane start child) ─────────
|
|
384
|
+
|
|
385
|
+
def editor_area
|
|
386
|
+
@minibuf_vpane.start_child
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def set_editor_area(w)
|
|
390
|
+
@minibuf_vpane.set_start_child(w)
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def init_minibuffer
|
|
394
|
+
@minibuf_messages = []
|
|
395
|
+
@minibuf_expanded = false
|
|
396
|
+
@minibuf_history_height = 120
|
|
397
|
+
|
|
398
|
+
css = "label.minibuf, textview.minibuf { color: #cdd6f4; font-family: Monospace; font-size: 10pt; padding: 3px 8px; background-color: #1e1e2e; }"
|
|
347
399
|
provider = Gtk::CssProvider.new
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
view
|
|
351
|
-
|
|
352
|
-
@
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
400
|
+
provider.load(data: css)
|
|
401
|
+
|
|
402
|
+
# Collapsed view: single line, latest message
|
|
403
|
+
@minibuf_label = Gtk::Label.new("")
|
|
404
|
+
@minibuf_label.xalign = 0.0
|
|
405
|
+
@minibuf_label.hexpand = true
|
|
406
|
+
@minibuf_label.ellipsize = Pango::EllipsizeMode::END
|
|
407
|
+
@minibuf_label.add_css_class("minibuf")
|
|
408
|
+
@minibuf_label.style_context.add_provider(provider)
|
|
409
|
+
|
|
410
|
+
# Expanded view: scrollable history (fills whatever height the pane gives)
|
|
411
|
+
@minibuf_textview = Gtk::TextView.new
|
|
412
|
+
@minibuf_textview.editable = false
|
|
413
|
+
@minibuf_textview.cursor_visible = false
|
|
414
|
+
@minibuf_textview.wrap_mode = :word_char
|
|
415
|
+
@minibuf_textview.add_css_class("minibuf")
|
|
416
|
+
@minibuf_textview.style_context.add_provider(provider)
|
|
417
|
+
@minibuf_textview.vexpand = true
|
|
418
|
+
|
|
419
|
+
scroll = Gtk::ScrolledWindow.new
|
|
420
|
+
scroll.set_policy(:never, :automatic)
|
|
421
|
+
scroll.set_child(@minibuf_textview)
|
|
422
|
+
scroll.vexpand = true
|
|
423
|
+
|
|
424
|
+
@minibuf_stack = Gtk::Stack.new
|
|
425
|
+
@minibuf_stack.vhomogeneous = false
|
|
426
|
+
@minibuf_stack.transition_type = :crossfade
|
|
427
|
+
@minibuf_stack.transition_duration = 100
|
|
428
|
+
@minibuf_stack.add_named(@minibuf_label, "label")
|
|
429
|
+
@minibuf_stack.add_named(scroll, "history")
|
|
430
|
+
|
|
431
|
+
@minibuf_content = Gtk::Box.new(:vertical, 0)
|
|
432
|
+
@minibuf_content.append(Gtk::Separator.new(:horizontal))
|
|
433
|
+
@minibuf_content.append(@minibuf_stack)
|
|
434
|
+
@minibuf_content.visible = false # hidden until first message
|
|
435
|
+
|
|
436
|
+
# Vertical pane: editor fills top, minibuffer sits at bottom, draggable
|
|
437
|
+
editor_w = @windows[1][:overlay]
|
|
438
|
+
@vbox.remove(editor_w)
|
|
439
|
+
|
|
440
|
+
@minibuf_vpane = Gtk::Paned.new(:vertical)
|
|
441
|
+
@minibuf_vpane.vexpand = true
|
|
442
|
+
@minibuf_vpane.hexpand = true
|
|
443
|
+
@minibuf_vpane.resize_start_child = true # editor absorbs window resize
|
|
444
|
+
@minibuf_vpane.resize_end_child = false # minibuffer keeps its height
|
|
445
|
+
@minibuf_vpane.shrink_end_child = true # allow fully collapsing minibuffer
|
|
446
|
+
@minibuf_vpane.set_start_child(editor_w)
|
|
447
|
+
@minibuf_vpane.set_end_child(@minibuf_content)
|
|
448
|
+
|
|
449
|
+
@vbox.attach(@minibuf_vpane, 0, 2, 2, 1)
|
|
450
|
+
@minibuf_hide_source = nil
|
|
356
451
|
end
|
|
357
452
|
|
|
358
453
|
def make_header_button(action_id, icon, cb)
|
|
@@ -373,16 +468,17 @@ class VMAgui
|
|
|
373
468
|
file_box.style_context.add_class("linked")
|
|
374
469
|
file_box.append(make_header_button("hdr-open", "document-open-symbolic", proc { open_file_dialog }))
|
|
375
470
|
file_box.append(make_header_button("hdr-save", "document-save-symbolic", proc { buf.save }))
|
|
376
|
-
file_box.append(make_header_button("hdr-new",
|
|
471
|
+
file_box.append(make_header_button("hdr-new", "document-new-symbolic", proc { create_new_file }))
|
|
377
472
|
header.pack_start(file_box)
|
|
378
473
|
|
|
379
474
|
nav_box = Gtk::Box.new(:horizontal, 0)
|
|
380
475
|
nav_box.style_context.add_class("linked")
|
|
381
|
-
nav_box.append(make_header_button("hdr-prev",
|
|
382
|
-
nav_box.append(make_header_button("hdr-next",
|
|
476
|
+
nav_box.append(make_header_button("hdr-prev", "pan-start-symbolic", proc { history_switch_backwards }))
|
|
477
|
+
nav_box.append(make_header_button("hdr-next", "pan-end-symbolic", proc { history_switch_forwards }))
|
|
478
|
+
nav_box.append(make_header_button("hdr-close", "window-close-symbolic", proc { bufs.close_current_buffer }))
|
|
383
479
|
header.pack_start(nav_box)
|
|
384
480
|
|
|
385
|
-
header.pack_end(
|
|
481
|
+
# header.pack_end()
|
|
386
482
|
|
|
387
483
|
@window.titlebar = header
|
|
388
484
|
end
|
|
@@ -429,6 +525,10 @@ class VMAgui
|
|
|
429
525
|
@window.add_controller(press)
|
|
430
526
|
|
|
431
527
|
press.signal_connect "key-pressed" do |gesture, keyval, keycode, y|
|
|
528
|
+
# Step aside when a text entry widget (e.g. calculator var field) has focus.
|
|
529
|
+
# @entry_has_focus is set explicitly via notify_entry_focus by those widgets.
|
|
530
|
+
next false if @entry_has_focus
|
|
531
|
+
next false if @kbd_passthrough
|
|
432
532
|
name = Gdk::Keyval.to_name(keyval)
|
|
433
533
|
uki = Gdk::Keyval.to_unicode(keyval)
|
|
434
534
|
keystr = uki.chr("UTF-8")
|
|
@@ -457,6 +557,8 @@ class VMAgui
|
|
|
457
557
|
end
|
|
458
558
|
|
|
459
559
|
press.signal_connect "key-released" do |gesture, keyval, keycode, y|
|
|
560
|
+
next false if @entry_has_focus
|
|
561
|
+
next false if @kbd_passthrough
|
|
460
562
|
name = Gdk::Keyval.to_name(keyval)
|
|
461
563
|
uki = Gdk::Keyval.to_unicode(keyval)
|
|
462
564
|
keystr = uki.chr("UTF-8")
|
|
@@ -509,8 +611,6 @@ class VMAgui
|
|
|
509
611
|
@last_debug_idle = Time.now
|
|
510
612
|
app = Gtk::Application.new("net.samiddhi.vimamsa.r#{rand(1000)}", :flags_none)
|
|
511
613
|
@app = app
|
|
512
|
-
|
|
513
|
-
|
|
514
614
|
|
|
515
615
|
Gtk::Settings.default.gtk_application_prefer_dark_theme = true
|
|
516
616
|
Gtk::Settings.default.gtk_theme_name = "Adwaita"
|
|
@@ -521,6 +621,11 @@ class VMAgui
|
|
|
521
621
|
@window = Gtk::ApplicationWindow.new(app)
|
|
522
622
|
@window.set_application(app)
|
|
523
623
|
|
|
624
|
+
@window.signal_connect("close-request") do
|
|
625
|
+
vma.shutdown
|
|
626
|
+
true # prevent default destroy; shutdown->gui.quit handles it
|
|
627
|
+
end
|
|
628
|
+
|
|
524
629
|
@window.title = "Multiple Views"
|
|
525
630
|
@vpaned = Gtk::Paned.new(:vertical)
|
|
526
631
|
|
|
@@ -545,9 +650,9 @@ class VMAgui
|
|
|
545
650
|
motion_controller.signal_connect("motion") do |controller, x, y|
|
|
546
651
|
# label.set_text("Mouse at: (%.1f, %.1f)" % [x, y])
|
|
547
652
|
# puts "MOVE #{x} #{y}"
|
|
548
|
-
|
|
653
|
+
|
|
549
654
|
# Cursor vanishes when hovering over menubar
|
|
550
|
-
draw_cursor_bug_workaround if y < 30
|
|
655
|
+
draw_cursor_bug_workaround if y < 30
|
|
551
656
|
@last_cursor = [x, y]
|
|
552
657
|
@cursor_move_time = Time.now
|
|
553
658
|
end
|
|
@@ -588,10 +693,10 @@ class VMAgui
|
|
|
588
693
|
|
|
589
694
|
# TODO: Doesn't work, why?:
|
|
590
695
|
# menubar_bar = Gtk::PopoverMenuBar.new(menu_model: menubar)
|
|
591
|
-
|
|
696
|
+
|
|
592
697
|
menubar_bar = Gtk::PopoverMenuBar.new()
|
|
593
698
|
menubar_bar.set_menu_model(menubar)
|
|
594
|
-
|
|
699
|
+
|
|
595
700
|
menubar_bar.hexpand = true
|
|
596
701
|
@action_trail_label = Gtk::Label.new("")
|
|
597
702
|
@action_trail_label.add_css_class("action-trail")
|
|
@@ -604,6 +709,7 @@ class VMAgui
|
|
|
604
709
|
|
|
605
710
|
init_header_bar
|
|
606
711
|
file_panel_init
|
|
712
|
+
func_panel_init
|
|
607
713
|
|
|
608
714
|
@window.show
|
|
609
715
|
|
|
@@ -672,7 +778,11 @@ class VMAgui
|
|
|
672
778
|
end
|
|
673
779
|
|
|
674
780
|
def init_menu
|
|
675
|
-
Vimamsa::Menu.new(@menubar, @app)
|
|
781
|
+
@menu = Vimamsa::Menu.new(@menubar, @app)
|
|
782
|
+
end
|
|
783
|
+
|
|
784
|
+
def menu
|
|
785
|
+
@menu
|
|
676
786
|
end
|
|
677
787
|
|
|
678
788
|
def toggle_two_column
|
|
@@ -696,9 +806,13 @@ class VMAgui
|
|
|
696
806
|
@pane.set_start_child(nil)
|
|
697
807
|
@pane.set_end_child(nil)
|
|
698
808
|
|
|
699
|
-
@
|
|
700
|
-
|
|
701
|
-
|
|
809
|
+
if @func_panel_shown
|
|
810
|
+
@func_panel_pane.set_end_child(w1[:overlay])
|
|
811
|
+
elsif @file_panel_shown
|
|
812
|
+
@file_panel_pane.set_end_child(w1[:overlay])
|
|
813
|
+
else
|
|
814
|
+
set_editor_area(w1[:overlay])
|
|
815
|
+
end
|
|
702
816
|
@two_column = false
|
|
703
817
|
end
|
|
704
818
|
|
|
@@ -748,14 +862,24 @@ class VMAgui
|
|
|
748
862
|
w1 = @windows[1]
|
|
749
863
|
w2 = @windows[2]
|
|
750
864
|
|
|
751
|
-
# Remove overlay from
|
|
865
|
+
# Remove overlay from its current parent and add the Gtk::Paned instead
|
|
752
866
|
@pane = Gtk::Paned.new(:horizontal)
|
|
753
|
-
@
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
@
|
|
867
|
+
if @func_panel_shown
|
|
868
|
+
@func_panel_pane.set_end_child(nil)
|
|
869
|
+
@pane.set_start_child(w2[:overlay])
|
|
870
|
+
@pane.set_end_child(w1[:overlay])
|
|
871
|
+
@func_panel_pane.set_end_child(@pane)
|
|
872
|
+
elsif @file_panel_shown
|
|
873
|
+
@file_panel_pane.set_end_child(nil)
|
|
874
|
+
@pane.set_start_child(w2[:overlay])
|
|
875
|
+
@pane.set_end_child(w1[:overlay])
|
|
876
|
+
@file_panel_pane.set_end_child(@pane)
|
|
877
|
+
else
|
|
878
|
+
@minibuf_vpane.set_start_child(nil) # unparent w1[:overlay] before re-parenting
|
|
879
|
+
@pane.set_start_child(w2[:overlay])
|
|
880
|
+
@pane.set_end_child(w1[:overlay])
|
|
881
|
+
set_editor_area(@pane)
|
|
882
|
+
end
|
|
759
883
|
|
|
760
884
|
w2[:sw].show
|
|
761
885
|
@two_column = true
|
|
@@ -925,15 +1049,22 @@ class VMAgui
|
|
|
925
1049
|
|
|
926
1050
|
def show_file_panel
|
|
927
1051
|
return if @file_panel_shown
|
|
928
|
-
|
|
929
|
-
@
|
|
1052
|
+
# If func panel is shown it sits in minibuf_vpane; wrap it too
|
|
1053
|
+
inner = if @func_panel_shown
|
|
1054
|
+
@func_panel_pane
|
|
1055
|
+
elsif @two_column
|
|
1056
|
+
@pane
|
|
1057
|
+
else
|
|
1058
|
+
@windows[1][:overlay]
|
|
1059
|
+
end
|
|
1060
|
+
@minibuf_vpane.set_start_child(nil) # unparent inner before re-parenting
|
|
930
1061
|
@file_panel_pane = Gtk::Paned.new(:horizontal)
|
|
931
1062
|
@file_panel_pane.hexpand = true
|
|
932
1063
|
@file_panel_pane.vexpand = true
|
|
933
1064
|
@file_panel_pane.set_start_child(@file_panel.widget)
|
|
934
1065
|
@file_panel_pane.set_end_child(inner)
|
|
935
1066
|
@file_panel_pane.set_position(180)
|
|
936
|
-
|
|
1067
|
+
set_editor_area(@file_panel_pane)
|
|
937
1068
|
@file_panel_shown = true
|
|
938
1069
|
@file_panel.refresh
|
|
939
1070
|
end
|
|
@@ -943,12 +1074,65 @@ class VMAgui
|
|
|
943
1074
|
inner = @file_panel_pane.end_child
|
|
944
1075
|
@file_panel_pane.set_start_child(nil)
|
|
945
1076
|
@file_panel_pane.set_end_child(nil)
|
|
946
|
-
|
|
947
|
-
@vbox.attach(inner, 0, 2, 2, 1)
|
|
1077
|
+
set_editor_area(inner)
|
|
948
1078
|
@file_panel_shown = false
|
|
949
1079
|
end
|
|
950
1080
|
|
|
951
1081
|
def toggle_file_panel
|
|
952
1082
|
@file_panel_shown ? hide_file_panel : show_file_panel
|
|
953
1083
|
end
|
|
1084
|
+
|
|
1085
|
+
def func_panel_init
|
|
1086
|
+
@func_panel = FuncPanel.new
|
|
1087
|
+
@func_panel_shown = false
|
|
1088
|
+
end
|
|
1089
|
+
|
|
1090
|
+
def func_panel_refresh
|
|
1091
|
+
return unless @func_panel_shown
|
|
1092
|
+
@func_panel.refresh
|
|
1093
|
+
end
|
|
1094
|
+
|
|
1095
|
+
def show_func_panel
|
|
1096
|
+
return if @func_panel_shown
|
|
1097
|
+
if @file_panel_shown
|
|
1098
|
+
inner = @file_panel_pane.end_child
|
|
1099
|
+
@file_panel_pane.set_end_child(nil)
|
|
1100
|
+
elsif @two_column
|
|
1101
|
+
inner = @pane
|
|
1102
|
+
@minibuf_vpane.set_start_child(nil)
|
|
1103
|
+
else
|
|
1104
|
+
inner = @windows[1][:overlay]
|
|
1105
|
+
@minibuf_vpane.set_start_child(nil)
|
|
1106
|
+
end
|
|
1107
|
+
@func_panel_pane = Gtk::Paned.new(:horizontal)
|
|
1108
|
+
@func_panel_pane.hexpand = true
|
|
1109
|
+
@func_panel_pane.vexpand = true
|
|
1110
|
+
@func_panel_pane.set_start_child(@func_panel.widget)
|
|
1111
|
+
@func_panel_pane.set_end_child(inner)
|
|
1112
|
+
@func_panel_pane.set_position(160)
|
|
1113
|
+
if @file_panel_shown
|
|
1114
|
+
@file_panel_pane.set_end_child(@func_panel_pane)
|
|
1115
|
+
else
|
|
1116
|
+
set_editor_area(@func_panel_pane)
|
|
1117
|
+
end
|
|
1118
|
+
@func_panel_shown = true
|
|
1119
|
+
@func_panel.refresh
|
|
1120
|
+
end
|
|
1121
|
+
|
|
1122
|
+
def hide_func_panel
|
|
1123
|
+
return unless @func_panel_shown
|
|
1124
|
+
inner = @func_panel_pane.end_child
|
|
1125
|
+
@func_panel_pane.set_start_child(nil)
|
|
1126
|
+
@func_panel_pane.set_end_child(nil)
|
|
1127
|
+
if @file_panel_shown
|
|
1128
|
+
@file_panel_pane.set_end_child(inner)
|
|
1129
|
+
else
|
|
1130
|
+
set_editor_area(inner)
|
|
1131
|
+
end
|
|
1132
|
+
@func_panel_shown = false
|
|
1133
|
+
end
|
|
1134
|
+
|
|
1135
|
+
def toggle_func_panel
|
|
1136
|
+
@func_panel_shown ? hide_func_panel : show_func_panel
|
|
1137
|
+
end
|
|
954
1138
|
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Left-side panel that displays LSP-provided functions/methods for the current buffer,
|
|
2
|
+
# grouped by the class or module they belong to.
|
|
3
|
+
class FuncPanel
|
|
4
|
+
COL_NAME = 0 # Tree column index: display name (class name or function name)
|
|
5
|
+
COL_LINE = 1 # Tree column index: 1-based line number to jump to; 0 = not a jump target
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
# @store is the data model: a tree (not flat list) so we can nest functions
|
|
9
|
+
# under their parent class. Two columns: the display string and the line number.
|
|
10
|
+
@store = Gtk::TreeStore.new(String, Integer)
|
|
11
|
+
|
|
12
|
+
# @tree is the GTK widget that renders @store. It holds expand/collapse state
|
|
13
|
+
# and handles row selection. Backed by @store — clearing @store clears the view.
|
|
14
|
+
@tree = Gtk::TreeView.new(@store)
|
|
15
|
+
@tree.headers_visible = false
|
|
16
|
+
@tree.activate_on_single_click = true
|
|
17
|
+
|
|
18
|
+
# Single text column; ellipsize at end so long names don't overflow the panel width.
|
|
19
|
+
renderer = Gtk::CellRendererText.new
|
|
20
|
+
renderer.ellipsize = Pango::EllipsizeMode::END
|
|
21
|
+
col = Gtk::TreeViewColumn.new("", renderer, text: COL_NAME)
|
|
22
|
+
col.expand = true
|
|
23
|
+
@tree.append_column(col)
|
|
24
|
+
|
|
25
|
+
# Jump to the function's line when a row is clicked.
|
|
26
|
+
# Class header rows have COL_LINE = 0 and are skipped (no jump).
|
|
27
|
+
@tree.signal_connect("row-activated") do |_tv, path, _col|
|
|
28
|
+
iter = @store.get_iter(path)
|
|
29
|
+
next if iter.nil?
|
|
30
|
+
line = iter[COL_LINE]
|
|
31
|
+
next if line <= 0
|
|
32
|
+
vma.buf.jump_to_line(line)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
sw = Gtk::ScrolledWindow.new
|
|
36
|
+
sw.set_policy(:never, :automatic)
|
|
37
|
+
sw.set_child(@tree)
|
|
38
|
+
sw.vexpand = true
|
|
39
|
+
|
|
40
|
+
header = Gtk::Label.new("<span weight='ultrabold'>Functions</span> (click to jump)")
|
|
41
|
+
header.use_markup = true
|
|
42
|
+
|
|
43
|
+
header.xalign = 0.0
|
|
44
|
+
header.margin_start = 6
|
|
45
|
+
header.margin_top = 4
|
|
46
|
+
header.margin_bottom = 2
|
|
47
|
+
|
|
48
|
+
# @box is the outermost widget: header label on top, scrollable tree below.
|
|
49
|
+
@box = Gtk::Box.new(:vertical, 0)
|
|
50
|
+
@box.set_size_request(160, -1)
|
|
51
|
+
@box.append(header)
|
|
52
|
+
@box.append(sw)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Returns the outermost widget to embed in the paned layout.
|
|
56
|
+
def widget
|
|
57
|
+
@box
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Asynchronously fetch functions from the LSP server and repopulate @store.
|
|
61
|
+
# The LSP call runs in a background thread; the GTK update is marshalled back
|
|
62
|
+
# to the main thread via GLib::Idle.add to avoid threading issues.
|
|
63
|
+
def refresh
|
|
64
|
+
buf = vma.buf
|
|
65
|
+
unless buf&.fname
|
|
66
|
+
set_placeholder("(no file)")
|
|
67
|
+
return
|
|
68
|
+
end
|
|
69
|
+
lsp = LangSrv.get(buf.lang)
|
|
70
|
+
unless lsp
|
|
71
|
+
set_placeholder("(no LSP)")
|
|
72
|
+
return
|
|
73
|
+
end
|
|
74
|
+
fpath = buf.fname
|
|
75
|
+
Thread.new {
|
|
76
|
+
# groups: [{name:, line:, functions: [{name:, line:}, ...]}, ...]
|
|
77
|
+
# name: nil means top-level functions with no enclosing class.
|
|
78
|
+
groups = lsp.document_functions_grouped(fpath)
|
|
79
|
+
GLib::Idle.add {
|
|
80
|
+
@store.clear
|
|
81
|
+
if groups.nil? || groups.empty?
|
|
82
|
+
set_placeholder("(no functions)")
|
|
83
|
+
else
|
|
84
|
+
populate(groups)
|
|
85
|
+
end
|
|
86
|
+
false # returning false removes this idle callback after one run
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
# Fill @store from the grouped function list.
|
|
94
|
+
# Named groups become collapsible parent rows; top-level functions are root rows.
|
|
95
|
+
def populate(groups)
|
|
96
|
+
groups.each do |g|
|
|
97
|
+
if g[:name]
|
|
98
|
+
# Class/module header: parent row with the class name (and its own line number
|
|
99
|
+
# so clicking it jumps to the class definition when line > 0).
|
|
100
|
+
header = @store.append(nil)
|
|
101
|
+
header[COL_NAME] = g[:name]
|
|
102
|
+
header[COL_LINE] = g[:line] || 0
|
|
103
|
+
g[:functions].each do |f|
|
|
104
|
+
child = @store.append(header) # appended under the class header
|
|
105
|
+
child[COL_NAME] = f[:name]
|
|
106
|
+
child[COL_LINE] = f[:line]
|
|
107
|
+
end
|
|
108
|
+
else
|
|
109
|
+
# Top-level functions (not inside any class): added as root rows directly.
|
|
110
|
+
g[:functions].each do |f|
|
|
111
|
+
row = @store.append(nil)
|
|
112
|
+
row[COL_NAME] = f[:name]
|
|
113
|
+
row[COL_LINE] = f[:line]
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
@tree.expand_all # show all classes expanded by default
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Replace the entire store contents with a single non-clickable status message.
|
|
121
|
+
def set_placeholder(text)
|
|
122
|
+
@store.clear
|
|
123
|
+
iter = @store.append(nil)
|
|
124
|
+
iter[COL_NAME] = text
|
|
125
|
+
iter[COL_LINE] = 0
|
|
126
|
+
end
|
|
127
|
+
end
|
data/lib/vimamsa/gui_menu.rb
CHANGED
|
@@ -53,6 +53,7 @@ module Vimamsa
|
|
|
53
53
|
|
|
54
54
|
add_to_menu "Actions.experimental.Diff", { :label => "Show Diff of\nunsaved changes", :action => :diff_buffer }
|
|
55
55
|
add_to_menu "Actions.experimental.GitDiff", { :label => "Show git diff", :action => :git_diff_buffer }
|
|
56
|
+
add_to_menu "Actions.experimental.GitDiffW", { :label => "Show git diff -w (repo)", :action => :git_diff_w }
|
|
56
57
|
|
|
57
58
|
add_to_menu "Actions.experimental.PrintBufferAccessList", { :label => "Print buffers by access time", :action => :print_buffer_access_list }
|
|
58
59
|
add_to_menu "Actions.experimental.EnableDebug", { :label => "Enable debug", :action => :enable_debug }
|
|
@@ -63,10 +64,13 @@ module Vimamsa
|
|
|
63
64
|
|
|
64
65
|
|
|
65
66
|
add_to_menu "Actions.debug.dumpkbd", { :label => "Dump kbd state", :action => :kbd_dump_state }
|
|
67
|
+
add_to_menu "Actions.debug.ToggleKbdPassthrough", { :label => "Toggle kbd event passthrough", :action => :toggle_kbd_passthrough }
|
|
66
68
|
|
|
67
69
|
add_to_menu "View.BufferManager", { :label => "Show open files", :action => :start_buf_manager }
|
|
68
70
|
add_to_menu "View.TwoColumn", { :label => "Toggle two column mode", :action => :toggle_two_column }
|
|
69
71
|
add_to_menu "View.FilePanel", { :label => "Toggle file panel", :action => :toggle_file_panel }
|
|
72
|
+
add_to_menu "View.FuncPanel", { :label => "Toggle function panel (LSP)", :action => :toggle_func_panel }
|
|
73
|
+
add_to_menu "View.MsgHistory", { :label => "Message history", :action => :show_message_history }
|
|
70
74
|
|
|
71
75
|
add_to_menu "Actions.EncryptFile", { :label => "Encrypt file", :action => :encrypt_file }
|
|
72
76
|
add_to_menu "Help.KeyBindings", { :label => "Show key bindings", :action => :show_key_bindings }
|
|
@@ -81,6 +85,11 @@ module Vimamsa
|
|
|
81
85
|
def initialize(menubar, _app)
|
|
82
86
|
@app = _app
|
|
83
87
|
@nfo = {}
|
|
88
|
+
@menubar = menubar
|
|
89
|
+
# Gio::Menu for the "Modules" top-level menu, created on first use.
|
|
90
|
+
@module_menu = nil
|
|
91
|
+
# Ordered list of action symbols in @module_menu, used to find removal positions.
|
|
92
|
+
@module_actions = []
|
|
84
93
|
|
|
85
94
|
add_menu_items
|
|
86
95
|
|
|
@@ -89,6 +98,39 @@ module Vimamsa
|
|
|
89
98
|
end
|
|
90
99
|
end
|
|
91
100
|
|
|
101
|
+
# Add a menu item under the top-level "Modules" menu.
|
|
102
|
+
# Creates the Modules menu the first time it is called.
|
|
103
|
+
def add_module_action(action, label)
|
|
104
|
+
if @module_menu.nil?
|
|
105
|
+
@module_menu = Gio::Menu.new
|
|
106
|
+
modules_item = Gio::MenuItem.new("Modules", nil)
|
|
107
|
+
modules_item.submenu = @module_menu
|
|
108
|
+
@menubar.append_item(modules_item)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
act = Gio::SimpleAction.new(action.to_s)
|
|
112
|
+
@app.add_action(act)
|
|
113
|
+
act.signal_connect("activate") { call_action(action) }
|
|
114
|
+
|
|
115
|
+
item = Gio::MenuItem.new(label, "app.#{action}")
|
|
116
|
+
@module_menu.append_item(item)
|
|
117
|
+
@module_actions << action
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Remove a previously added module menu item.
|
|
121
|
+
def remove_module_action(action)
|
|
122
|
+
idx = @module_actions.index(action)
|
|
123
|
+
return if idx.nil?
|
|
124
|
+
@module_menu.remove(idx)
|
|
125
|
+
@module_actions.delete_at(idx)
|
|
126
|
+
@app.remove_action(action.to_s)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Return true if action was added via add_module_action and not yet removed.
|
|
130
|
+
def module_action?(action)
|
|
131
|
+
@module_actions.include?(action)
|
|
132
|
+
end
|
|
133
|
+
|
|
92
134
|
def build_menu(nfo, parent)
|
|
93
135
|
menu = Gio::Menu.new
|
|
94
136
|
if nfo[:action]
|