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
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
- ssm = GtkSource::StyleSchemeManager.new
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)
@@ -148,6 +147,12 @@ def gui_create_buffer(id, bufo)
148
147
  $vmag.buffers[id] = view
149
148
  end
150
149
 
150
+ def gui_close_buffer(id)
151
+ view = vma.gui.buffers.delete(id)
152
+ return if view.nil?
153
+ view.unparent if view.parent
154
+ end
155
+
151
156
  def gui_set_file_lang(id, lname)
152
157
  view = $vmag.buffers[id]
153
158
  lm = GtkSource::LanguageManager.new
@@ -159,9 +164,6 @@ def gui_set_file_lang(id, lname)
159
164
  view.buffer.highlight_syntax = true
160
165
  end
161
166
 
162
- def gui_add_image(imgpath, pos)
163
- end
164
-
165
167
  # TODO:?
166
168
  def gui_select_window_close(arg = nil)
167
169
  end
@@ -183,7 +185,8 @@ def gui_set_current_buffer(id)
183
185
  end
184
186
 
185
187
  def gui_set_window_title(wtitle, subtitle = "")
186
- $vmag.window.title = wtitle
188
+ wtitle = wtitle[0..150]
189
+ $vmag.window.title = "Vimamsa - #{wtitle}"
187
190
  # $vmag.subtitle.markup = "<span weight='ultrabold'>#{subtitle}</span>"
188
191
  $vmag.subtitle.markup = "<span weight='light' size='small'>#{subtitle}</span>"
189
192
  # $vmag.window.titlebar.subtitle = subtitle #TODO:gtk4
@@ -191,7 +194,7 @@ end
191
194
 
192
195
  class VMAgui
193
196
  attr_accessor :buffers, :sw1, :sw2, :view, :buf1, :window, :delex, :statnfo, :overlay, :sws, :two_c
194
- attr_reader :two_column, :windows, :subtitle, :app, :active_window
197
+ attr_reader :two_column, :windows, :subtitle, :app, :active_window, :action_trail_label, :file_panel, :func_panel
195
198
 
196
199
  def initialize()
197
200
  @two_column = false
@@ -205,6 +208,13 @@ class VMAgui
205
208
  @img_resizer_active = false
206
209
  @windows = {}
207
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
208
218
  end
209
219
 
210
220
  def run
@@ -313,123 +323,163 @@ class VMAgui
313
323
  end
314
324
 
315
325
  def add_to_minibuf(msg)
316
- # return #TODO:gtk4
317
- startiter = @minibuf.buffer.get_iter_at(:offset => 0)
318
- @minibuf.buffer.insert(startiter, "#{msg}\n")
319
- @minibuf.signal_emit("move-cursor", Gtk::MovementStep.new(:PAGES), -1, false)
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
320
346
  end
321
347
 
322
- def init_minibuffer()
323
- # Init minibuffer
324
- sw = Gtk::ScrolledWindow.new
325
- sw.set_policy(:automatic, :automatic)
326
- overlay = Gtk::Overlay.new
327
- overlay.set_child(sw)
328
- @vbox.attach(overlay, 0, 3, 2, 1)
329
- sw.set_size_request(-1, 12)
330
-
331
- view = VSourceView.new(nil, nil)
332
- view.set_highlight_current_line(false)
333
- view.set_show_line_numbers(false)
334
- # view.set_buffer(buf1)
335
- ssm = GtkSource::StyleSchemeManager.new
336
- ssm.set_search_path(ssm.search_path << ppath("styles/"))
337
- sty = ssm.get_scheme("molokai_edit")
338
- view.buffer.highlight_matching_brackets = false #TODO
339
- view.buffer.style_scheme = sty
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; }"
340
399
  provider = Gtk::CssProvider.new
341
- # provider.load(data: "textview { font-family: Monospace; font-size: 11pt; }")
342
- provider.load(data: "textview { font-family: Arial; font-size: 10pt; color:#eeeeee}")
343
- view.style_context.add_provider(provider)
344
- view.wrap_mode = :char
345
- @minibuf = view
346
- # startiter = view.buffer.get_iter_at(:offset => 0)
347
- message("STARTUP")
348
- sw.set_child(view)
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
451
+ end
452
+
453
+ def make_header_button(action_id, icon, cb)
454
+ act = Gio::SimpleAction.new(action_id)
455
+ @app.add_action(act)
456
+ act.signal_connect("activate") { |_a, _p| cb.call }
457
+ btn = Gtk::Button.new
458
+ btn.set_child(Gtk::Image.new(icon_name: icon))
459
+ btn.action_name = "app.#{action_id}"
460
+ btn
349
461
  end
350
462
 
351
- #TODO: implement in gtk4
352
463
  def init_header_bar()
353
464
  header = Gtk::HeaderBar.new
354
465
  @header = header
355
- header.show_close_button = true
356
- # header.title = ""#TODO:gtk4
357
- # header.has_subtitle = true#TODO:gtk4
358
- header.subtitle = ""
359
-
360
- # icon = Gio::ThemedIcon.new("mail-send-receive-symbolic")
361
- # icon = Gio::ThemedIcon.new("document-open-symbolic")
362
- # icon = Gio::ThemedIcon.new("dialog-password")
363
-
364
- #edit-redo edit-paste edit-find-replace edit-undo edit-find edit-cut edit-copy
365
- #document-open document-save document-save-as document-properties document-new
366
- # document-revert-symbolic
367
- #
368
-
369
- #TODO:
370
- # button = Gtk::Button.new
371
- # icon = Gio::ThemedIcon.new("open-menu-symbolic")
372
- # image = Gtk::Image.new(:icon => icon, :size => :button)
373
- # button.add(image)
374
- # header.append(button)
375
-
376
- button = Gtk::Button.new
377
- icon = Gio::ThemedIcon.new("document-open-symbolic")
378
- image = Gtk::Image.new(:icon => icon, :size => :button)
379
- button.add(image)
380
- header.append(button)
381
-
382
- button.signal_connect "clicked" do |_widget|
383
- open_file_dialog
384
- end
385
-
386
- button = Gtk::Button.new
387
- icon = Gio::ThemedIcon.new("document-save-symbolic")
388
- image = Gtk::Image.new(:icon => icon, :size => :button)
389
- button.add(image)
390
- header.append(button)
391
- button.signal_connect "clicked" do |_widget|
392
- buf.save
393
- end
394
-
395
- button = Gtk::Button.new
396
- icon = Gio::ThemedIcon.new("document-new-symbolic")
397
- image = Gtk::Image.new(:icon => icon, :size => :button)
398
- button.add(image)
399
- header.append(button)
400
- button.signal_connect "clicked" do |_widget|
401
- create_new_file
402
- end
403
-
404
- box = Gtk::Box.new(:horizontal, 0)
405
- box.style_context.add_class("linked")
406
-
407
- button = Gtk::Button.new
408
- image = Gtk::Image.new(:icon_name => "pan-start-symbolic", :size => :button)
409
- button.add(image)
410
- box.add(button)
411
- button.signal_connect "clicked" do |_widget|
412
- history_switch_backwards
413
- end
414
-
415
- button = Gtk::Button.new
416
- image = Gtk::Image.new(:icon_name => "pan-end-symbolic", :size => :button)
417
- button.add(image)
418
- box.add(button)
419
- button.signal_connect "clicked" do |_widget|
420
- history_switch_forwards
421
- end
422
-
423
- button = Gtk::Button.new
424
- icon = Gio::ThemedIcon.new("window-close-symbolic")
425
- image = Gtk::Image.new(:icon => icon, :size => :button)
426
- button.add(image)
427
- box.add(button)
428
- button.signal_connect "clicked" do |_widget|
429
- bufs.close_current_buffer
430
- end
431
-
432
- header.pack_start(box)
466
+
467
+ file_box = Gtk::Box.new(:horizontal, 0)
468
+ file_box.style_context.add_class("linked")
469
+ file_box.append(make_header_button("hdr-open", "document-open-symbolic", proc { open_file_dialog }))
470
+ file_box.append(make_header_button("hdr-save", "document-save-symbolic", proc { buf.save }))
471
+ file_box.append(make_header_button("hdr-new", "document-new-symbolic", proc { create_new_file }))
472
+ header.pack_start(file_box)
473
+
474
+ nav_box = Gtk::Box.new(:horizontal, 0)
475
+ nav_box.style_context.add_class("linked")
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 }))
479
+ header.pack_start(nav_box)
480
+
481
+ # header.pack_end()
482
+
433
483
  @window.titlebar = header
434
484
  end
435
485
 
@@ -475,6 +525,10 @@ class VMAgui
475
525
  @window.add_controller(press)
476
526
 
477
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
478
532
  name = Gdk::Keyval.to_name(keyval)
479
533
  uki = Gdk::Keyval.to_unicode(keyval)
480
534
  keystr = uki.chr("UTF-8")
@@ -503,6 +557,8 @@ class VMAgui
503
557
  end
504
558
 
505
559
  press.signal_connect "key-released" do |gesture, keyval, keycode, y|
560
+ next false if @entry_has_focus
561
+ next false if @kbd_passthrough
506
562
  name = Gdk::Keyval.to_name(keyval)
507
563
  uki = Gdk::Keyval.to_unicode(keyval)
508
564
  keystr = uki.chr("UTF-8")
@@ -565,11 +621,16 @@ class VMAgui
565
621
  @window = Gtk::ApplicationWindow.new(app)
566
622
  @window.set_application(app)
567
623
 
624
+ @window.signal_connect("close-request") do
625
+ vma.shutdown
626
+ true # prevent default destroy; shutdown->gui.quit handles it
627
+ end
628
+
568
629
  @window.title = "Multiple Views"
569
630
  @vpaned = Gtk::Paned.new(:vertical)
570
631
 
571
632
  @vbox = Gtk::Grid.new()
572
- @window.add(@vbox)
633
+ @window.set_child(@vbox)
573
634
 
574
635
  Thread.new {
575
636
  GLib::Idle.add(proc { debug_idle_func })
@@ -589,9 +650,9 @@ class VMAgui
589
650
  motion_controller.signal_connect("motion") do |controller, x, y|
590
651
  # label.set_text("Mouse at: (%.1f, %.1f)" % [x, y])
591
652
  # puts "MOVE #{x} #{y}"
592
-
653
+
593
654
  # Cursor vanishes when hovering over menubar
594
- draw_cursor_bug_workaround if y < 30
655
+ draw_cursor_bug_workaround if y < 30
595
656
  @last_cursor = [x, y]
596
657
  @cursor_move_time = Time.now
597
658
  end
@@ -628,13 +689,28 @@ class VMAgui
628
689
  init_minibuffer
629
690
 
630
691
  menubar = Gio::Menu.new
631
- app.menubar = menubar
632
- @window.show_menubar = true
633
-
634
692
  @menubar = menubar
635
693
 
694
+ # TODO: Doesn't work, why?:
695
+ # menubar_bar = Gtk::PopoverMenuBar.new(menu_model: menubar)
696
+
697
+ menubar_bar = Gtk::PopoverMenuBar.new()
698
+ menubar_bar.set_menu_model(menubar)
699
+
700
+ menubar_bar.hexpand = true
701
+ @action_trail_label = Gtk::Label.new("")
702
+ @action_trail_label.add_css_class("action-trail")
703
+ menubar_row = Gtk::Box.new(:horizontal, 0)
704
+ menubar_row.append(menubar_bar)
705
+ menubar_row.append(@action_trail_label)
706
+ @vbox.attach(menubar_row, 0, 0, 2, 1)
707
+
636
708
  @active_window = @windows[1]
637
709
 
710
+ init_header_bar
711
+ file_panel_init
712
+ func_panel_init
713
+
638
714
  @window.show
639
715
 
640
716
  surface = @window.native.surface
@@ -670,7 +746,9 @@ class VMAgui
670
746
  min-width: 15px;
671
747
  }
672
748
 
673
- popover background > contents { padding: 8px; border-radius: 20px; }
749
+ popover background > contents { padding: 8px; border-radius: 20px; }
750
+
751
+ label.action-trail { font-family: monospace; font-size: 10pt; margin-right: 8px; color: #aaaaaa; }
674
752
  ")
675
753
  @window.style_context.add_provider(prov)
676
754
 
@@ -700,7 +778,11 @@ class VMAgui
700
778
  end
701
779
 
702
780
  def init_menu
703
- Vimamsa::Menu.new(@menubar, @app)
781
+ @menu = Vimamsa::Menu.new(@menubar, @app)
782
+ end
783
+
784
+ def menu
785
+ @menu
704
786
  end
705
787
 
706
788
  def toggle_two_column
@@ -724,9 +806,13 @@ class VMAgui
724
806
  @pane.set_start_child(nil)
725
807
  @pane.set_end_child(nil)
726
808
 
727
- @vbox.remove(@pane)
728
- @vbox.attach(w1[:overlay], 0, 2, 2, 1)
729
- # @vbox.attach(@statbox, 1, 1, 1, 1)
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
730
816
  @two_column = false
731
817
  end
732
818
 
@@ -776,14 +862,24 @@ class VMAgui
776
862
  w1 = @windows[1]
777
863
  w2 = @windows[2]
778
864
 
779
- # Remove overlay from @vbox and add the Gtk::Paned instead
865
+ # Remove overlay from its current parent and add the Gtk::Paned instead
780
866
  @pane = Gtk::Paned.new(:horizontal)
781
- @vbox.remove(w1[:overlay])
782
- @pane.set_start_child(w2[:overlay])
783
- @pane.set_end_child(w1[:overlay])
784
-
785
- # numbers: left, top, width, height
786
- @vbox.attach(@pane, 0, 2, 2, 1)
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
787
883
 
788
884
  w2[:sw].show
789
885
  @two_column = true
@@ -940,4 +1036,103 @@ class VMAgui
940
1036
  # view.place_cursor_onscreen #TODO: needed?
941
1037
  view.draw_cursor
942
1038
  end
1039
+
1040
+ def file_panel_init
1041
+ @file_panel = FileTreePanel.new
1042
+ @file_panel_shown = false
1043
+ end
1044
+
1045
+ def file_panel_refresh
1046
+ return unless @file_panel_shown
1047
+ @file_panel.refresh
1048
+ end
1049
+
1050
+ def show_file_panel
1051
+ return if @file_panel_shown
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
1061
+ @file_panel_pane = Gtk::Paned.new(:horizontal)
1062
+ @file_panel_pane.hexpand = true
1063
+ @file_panel_pane.vexpand = true
1064
+ @file_panel_pane.set_start_child(@file_panel.widget)
1065
+ @file_panel_pane.set_end_child(inner)
1066
+ @file_panel_pane.set_position(180)
1067
+ set_editor_area(@file_panel_pane)
1068
+ @file_panel_shown = true
1069
+ @file_panel.refresh
1070
+ end
1071
+
1072
+ def hide_file_panel
1073
+ return unless @file_panel_shown
1074
+ inner = @file_panel_pane.end_child
1075
+ @file_panel_pane.set_start_child(nil)
1076
+ @file_panel_pane.set_end_child(nil)
1077
+ set_editor_area(inner)
1078
+ @file_panel_shown = false
1079
+ end
1080
+
1081
+ def toggle_file_panel
1082
+ @file_panel_shown ? hide_file_panel : show_file_panel
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
943
1138
  end
@@ -39,6 +39,8 @@ end
39
39
  class OneInputAction
40
40
  def initialize(main_window, title, field_label, button_title, callback, opt = {})
41
41
  @window = Gtk::Window.new()
42
+ @window.set_transient_for($vmag.window) if $vmag&.window
43
+ @window.modal = true
42
44
  # @window.screen = main_window.screen
43
45
  @window.title = ""
44
46
  # @window.width_request = 800
@@ -0,0 +1,94 @@
1
+ class FileTreePanel
2
+ COL_LABEL = 0
3
+ COL_BUF_ID = 1 # 0 = folder row (not selectable)
4
+
5
+ def initialize
6
+ @store = Gtk::TreeStore.new(String, Integer)
7
+ @tree = Gtk::TreeView.new(@store)
8
+ @tree.headers_visible = false
9
+ @tree.activate_on_single_click = true
10
+ @tree.level_indentation = 7
11
+
12
+ renderer = Gtk::CellRendererText.new
13
+ renderer.ellipsize = Pango::EllipsizeMode::START
14
+ col = Gtk::TreeViewColumn.new("", renderer, text: COL_LABEL)
15
+ col.expand = true
16
+ @tree.append_column(col)
17
+
18
+ @tree.signal_connect("row-activated") do |tv, path, _col|
19
+ iter = @store.get_iter(path)
20
+ next if iter.nil?
21
+ buf_id = iter[COL_BUF_ID]
22
+ next if buf_id.nil? || buf_id == 0
23
+ vma.buffers.set_current_buffer(buf_id)
24
+ end
25
+
26
+ @context_menu = nil
27
+ rightclick = Gtk::GestureClick.new
28
+ rightclick.button = 3
29
+ @tree.add_controller(rightclick)
30
+ rightclick.signal_connect("pressed") do |gesture, n_press, x, y|
31
+ result = @tree.get_path_at_pos(x.to_i, y.to_i)
32
+ next unless result
33
+ iter = @store.get_iter(result[0])
34
+ next unless iter
35
+ buf_id = iter[COL_BUF_ID]
36
+ next unless buf_id && buf_id != 0
37
+ @context_buf_id = buf_id
38
+ show_context_menu(x, y)
39
+ end
40
+
41
+ @sw = Gtk::ScrolledWindow.new
42
+ @sw.set_policy(:never, :automatic)
43
+ @sw.set_child(@tree)
44
+ @sw.set_size_request(180, -1)
45
+ @sw.vexpand = true
46
+ end
47
+
48
+ def widget
49
+ @sw
50
+ end
51
+
52
+ def init_context_menu
53
+ act = Gio::SimpleAction.new("fp_close_buffer")
54
+ vma.gui.app.add_action(act)
55
+ act.signal_connect("activate") { vma.buffers.close_buffer(@context_buf_id) if @context_buf_id }
56
+ @context_menu = Gtk::PopoverMenu.new
57
+ @context_menu.set_parent(@tree)
58
+ @context_menu.has_arrow = false
59
+ end
60
+
61
+ def show_context_menu(x, y)
62
+ init_context_menu if @context_menu.nil?
63
+ menu = Gio::Menu.new
64
+ menu.append("Close file", "app.fp_close_buffer")
65
+ @context_menu.set_menu_model(menu)
66
+ @context_menu.set_pointing_to(Gdk::Rectangle.new(x.to_i, y.to_i, 1, 1))
67
+ @context_menu.popup
68
+ end
69
+
70
+ def refresh
71
+ @store.clear
72
+ bh = {}
73
+ vma.buffers.list.each do |b|
74
+ dname = b.fname ? File.dirname(b.fname) : "*"
75
+ bname = b.fname ? File.basename(b.fname) : (b.list_str || "(untitled)")
76
+ bh[dname] ||= []
77
+ bh[dname] << { bname: bname, buf: b }
78
+ end
79
+
80
+ bh.keys.sort.each do |dname|
81
+ dir_iter = @store.append(nil)
82
+ dir_iter[COL_LABEL] = "📂 #{tilde_path(dname)}"
83
+ dir_iter[COL_BUF_ID] = 0
84
+ bh[dname].sort_by { |x| x[:bname] }.each do |bnfo|
85
+ active_mark = bnfo[:buf].is_active? ? "● " : " "
86
+ file_iter = @store.append(dir_iter)
87
+ file_iter[COL_LABEL] = "#{active_mark}#{bnfo[:bname]}"
88
+ file_iter[COL_BUF_ID] = bnfo[:buf].id
89
+ end
90
+ end
91
+
92
+ @tree.expand_all
93
+ end
94
+ end