Class: VSourceView

Inherits:
GtkSource::View
  • Object
show all
Defined in:
lib/vimamsa/gui_sourceview.rb,
lib/vimamsa/gui_sourceview_autocomplete.rb

Overview

class VSourceView < Gtk::TextView

Constant Summary collapse

CONTEXT_MENU_ITEMS =

end draw_cursor

[
  ["Paste", :paste_after_cursor],
  ["Previous buffer", :history_switch_backwards],
]
CONTEXT_MENU_ITEMS_SELECTION =
[
  ["Copy", :copy_selection],
  ["Cut", :cut_selection],
]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(title, bufo) ⇒ VSourceView

def initialize(title = nil,bufo=nil)



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/vimamsa/gui_sourceview.rb', line 6

def initialize(title, bufo)
  # super(:toplevel)
  @highlight_matching_brackets = true
  @idle_func_running = false
  super()
  @bufo = bufo #object of Buffer class (buffer.rb)
  debug "vsource init"
  @last_keyval = nil
  @last_event = [nil, nil]
  @removed_controllers = []
  self.highlight_current_line = true

  @tt = nil

  # Mainly after page-up or page-down

  signal_connect("move-cursor") do |widget, event|
    # if event.name == "GTK_MOVEMENT_PAGES" and (vma.actions.last_action == "page_up" or vma.actions.last_action == "page_down")
    # handle_scrolling()
    # end

    # handle_scrolling()
    # curpos = buffer.cursor_position
    # debug "MOVE CURSOR (sig): #{curpos}"

    # run_as_idle proc {
    # curpos = buffer.cursor_position
    # debug "MOVE CURSOR (sig2): #{curpos}"
    # }

    false
  end

  return

  signal_connect "button-release-event" do |widget, event|
    vma.buf.set_pos(buffer.cursor_position)
    false
  end
  @curpos_mark = nil
end

Instance Attribute Details

#autocp_activeObject

Returns the value of attribute autocp_active.



3
4
5
# File 'lib/vimamsa/gui_sourceview.rb', line 3

def autocp_active
  @autocp_active
end

#bufoObject

Returns the value of attribute bufo.



3
4
5
# File 'lib/vimamsa/gui_sourceview.rb', line 3

def bufo
  @bufo
end

#cpl_listObject

Returns the value of attribute cpl_list.



3
4
5
# File 'lib/vimamsa/gui_sourceview.rb', line 3

def cpl_list
  @cpl_list
end

Instance Method Details

#after_actionObject



674
675
676
677
678
679
680
# File 'lib/vimamsa/gui_sourceview.rb', line 674

def after_action
  iterate_gui_main_loop
  handle_deltas
  iterate_gui_main_loop
  draw_cursor
  iterate_gui_main_loop
end

#autocp_exitObject



84
85
86
87
88
# File 'lib/vimamsa/gui_sourceview_autocomplete.rb', line 84

def autocp_exit
  @autocp_items = []
  @autocp_active = true
  hide_completions
end

#autocp_hilight(id) ⇒ Object



64
65
66
67
68
# File 'lib/vimamsa/gui_sourceview_autocomplete.rb', line 64

def autocp_hilight(id)
  l = @autocp_items[id]
  l.set_text("<span foreground='#00ff00' weight='ultrabold'>#{cpl_list[id]}</span>")
  l.use_markup = true
end

#autocp_selectObject



50
51
52
53
54
# File 'lib/vimamsa/gui_sourceview_autocomplete.rb', line 50

def autocp_select
  return if !@autocp_active
  bufo.complete_current_word(@cpl_list[@autocp_selected])
  autocp_exit
end

#autocp_select_nextObject



76
77
78
79
80
81
82
# File 'lib/vimamsa/gui_sourceview_autocomplete.rb', line 76

def autocp_select_next
  return if @autocp_selected >= cpl_list.size - 1
  debug "autocp_select_next", 2
  autocp_unhilight(@autocp_selected)
  @autocp_selected += 1
  autocp_hilight(@autocp_selected)
end

#autocp_select_previousObject



56
57
58
59
60
61
62
# File 'lib/vimamsa/gui_sourceview_autocomplete.rb', line 56

def autocp_select_previous
  return if @autocp_selected <= 0
  autocp_hilight(@autocp_selected)
  autocp_unhilight(@autocp_selected)
  @autocp_selected -= 1
  autocp_hilight(@autocp_selected)
end

#autocp_unhilight(id) ⇒ Object



70
71
72
73
74
# File 'lib/vimamsa/gui_sourceview_autocomplete.rb', line 70

def autocp_unhilight(id)
  l = @autocp_items[id]
  l.set_text("<span>#{cpl_list[id]}</span>")
  l.use_markup = true
end

#check_controllersObject



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/vimamsa/gui_sourceview.rb', line 148

def check_controllers
  clist = self.observe_controllers
  to_remove = []
  (0..(clist.n_items - 1)).each { |x|
    ctr = clist.get_item(x)
    # Sometimes a GestureClick EventController appears from somewhere
    # not initiated from this file.

    # TODO: Check which of these are needed:
    # puts ctr.class
    # Gtk::DropTarget
    # Gtk::EventControllerFocus
    # Gtk::EventControllerKey
    # Gtk::EventControllerMotion
    # Gtk::EventControllerScroll
    # Gtk::GestureClick
    # Gtk::GestureDrag
    # Gtk::ShortcutController

    if ![@click, @dt].include?(ctr) and [Gtk::DropControllerMotion, Gtk::DropTarget, Gtk::GestureDrag, Gtk::GestureClick, Gtk::EventControllerKey].include?(ctr.class)
      to_remove << ctr
    end
  }
  if to_remove.size > 0
    to_remove.each { |x|
      # To avoid GC. https://github.com/ruby-gnome/ruby-gnome/issues/15790
      @removed_controllers << x
      self.remove_controller(x)
    }
  end
end

#coord_to_iter(xloc, yloc, transform_coord = false) ⇒ Object



367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/vimamsa/gui_sourceview.rb', line 367

def coord_to_iter(xloc, yloc, transform_coord = false)
  if transform_coord
    xloc = (xloc - gutter_width).to_i
    yloc = (yloc + visible_rect.y).to_i
  end

  # Try to get exact character position
  i = get_iter_at_location(xloc, yloc)

  # If doesn't work, at least get the start of correct line
  # TODO: sometimes end of line is better choice
  if i.nil?
    r = get_line_at_y(yloc)
    i = r[0] if !r.nil?
  end

  return i.offset if !i.nil?
  return nil
end

#cur_pos_xyObject



548
549
550
# File 'lib/vimamsa/gui_sourceview.rb', line 548

def cur_pos_xy
  return pos_to_coord(buffer.cursor_position)
end

#cursor_visible_idle_funcObject



617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
# File 'lib/vimamsa/gui_sourceview.rb', line 617

def cursor_visible_idle_func
  # return false
  debug "cursor_visible_idle_func"
  # From https://picheta.me/articles/2013/08/gtk-plus--a-method-to-guarantee-scrolling.html

  # This is not the current buffer
  return false if vma.gui.view != self

  sleep(0.01)
  # intr = iterxy.intersect(vr)
  if is_cursor_visible == false
    # set_cursor_pos(buffer.cursor_position)

    itr = buffer.get_iter_at(:offset => buffer.cursor_position)

    within_margin = 0.075 #margin as a [0.0,0.5) fraction of screen size
    use_align = false
    xalign = 0.5 #0.0=top 1.0=bottom, 0.5=center
    yalign = 0.5

    scroll_to_iter(itr, within_margin, use_align, xalign, yalign)

    return true # Call this func again
  else
    @idle_func_running = false
    return false # Don't call this idle func again
  end
end

#draw_cursorObject



699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
# File 'lib/vimamsa/gui_sourceview.rb', line 699

def draw_cursor
  sv = vma.gui.active_window[:sw].child
  return if sv.nil?
  if sv != self # if we are not the current buffer
    sv.draw_cursor
    return
  end

  mode = vma.kbd.get_mode
  ctype = vma.kbd.get_cursor_type
  ctype = :visual if @bufo.selection_active?

  if [:command, :replace, :browse].include?(ctype)
    set_cursor_color(ctype)

    if !self.overwrite?
      self.overwrite = true

      # (Via trial and error) This combination is needed to make cursor visible:
      # TODO: determine why "self.cursor_visible = true" is not enough
      self.cursor_visible = false
      self.cursor_visible = true
    end
  elsif ctype == :visual
    set_cursor_color(ctype)
    # debug "VISUAL MODE"
    (_start, _end) = @bufo.get_visual_mode_range2
    # debug "#{_start}, #{_end}"
    itr = buffer.get_iter_at(:offset => _start)
    itr2 = buffer.get_iter_at(:offset => _end + 1)
    # Pango-CRITICAL **: pango_layout_get_cursor_pos: assertion 'index >= 0 && index <= layout->length' failed
    buffer.select_range(itr, itr2)
  elsif ctype == :insert
    set_cursor_color(ctype)
    self.overwrite = false
    debug "INSERT MODE"
  else # TODO
  end

  if [:insert, :command, :replace, :browse].include?(ctype)
    # Place cursor where it already is
    # Without this hack, the cursor doesn't always get drawn
    pos = @bufo.pos
    itr = buffer.get_iter_at(:offset => pos)
    buffer.place_cursor(itr)
  end

  # Sometimes we lose focus and the cursor vanishes because of that
  # TODO: determine why&when
  if !self.has_focus?
    self.grab_focus
    self.cursor_visible = false
    self.cursor_visible = true
  end

  update_cursor_unmask
end

#ensure_cursor_visibleObject



663
664
665
666
667
668
669
670
671
672
# File 'lib/vimamsa/gui_sourceview.rb', line 663

def ensure_cursor_visible
  # return
  debug "@idle_func_running=#{@idle_func_running}"
  return if @idle_func_running
  if is_cursor_visible == false
    @idle_func_running = true
    debug "Starting idle func"
    GLib::Idle.add(proc { cursor_visible_idle_func })
  end
end

#focus_inObject



187
188
189
190
191
192
# File 'lib/vimamsa/gui_sourceview.rb', line 187

def focus_in()
  set_cursor_color(@ctype)
  self.cursor_visible = false
  self.cursor_visible = true
  self.grab_focus
end

#focus_outObject



180
181
182
183
184
185
# File 'lib/vimamsa/gui_sourceview.rb', line 180

def focus_out()
  set_cursor_color(:inactive)

  # This does not seem to work: (TODO:why?)
  # self.cursor_visible = false
end

#gutter_widthObject



134
135
136
137
138
# File 'lib/vimamsa/gui_sourceview.rb', line 134

def gutter_width()
  winwidth = width
  view_width = visible_rect.width
  gutter_width = winwidth - view_width
end

#handle_deltasObject



552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
# File 'lib/vimamsa/gui_sourceview.rb', line 552

def handle_deltas()
  any_change = false
  while d = @bufo.deltas.shift
    any_change = true
    pos = d[0]
    op = d[1]
    num = d[2]
    txt = d[3]
    if op == DELETE
      startiter = buffer.get_iter_at(:offset => pos)
      enditer = buffer.get_iter_at(:offset => pos + num)
      buffer.delete(startiter, enditer)
    elsif op == INSERT
      startiter = buffer.get_iter_at(:offset => pos)
      buffer.insert(startiter, txt)
    end
  end
  if any_change
    remask_gtk_buffer
    #TODO: only when necessary
    self.set_cursor_pos(pos)
  end

  # sanity_check #TODO
end

#handle_key_event(keyval, keyname, sig) ⇒ Object

def handle_key_event(event, sig)



419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
# File 'lib/vimamsa/gui_sourceview.rb', line 419

def handle_key_event(keyval, keyname, sig)
  if $update_cursor
    handle_scrolling
  end
  debug $view.visible_rect.inspect

  debug "key event" + [keyval, @last_keyval, keyname, sig].to_s
  # debug event

  # key_name = event.string
  #TODO:??
  # if event.state.control_mask?
  # key_name = Gdk::Keyval.to_name(event.keyval)
  # Gdk::Keyval.to_name()
  # end

  keyval_trans = {}
  keyval_trans[Gdk::Keyval::KEY_Control_L] = "ctrl"
  keyval_trans[Gdk::Keyval::KEY_Control_R] = "ctrl"

  keyval_trans[Gdk::Keyval::KEY_Escape] = "esc"

  keyval_trans[Gdk::Keyval::KEY_Return] = "enter"
  keyval_trans[Gdk::Keyval::KEY_ISO_Enter] = "enter"
  keyval_trans[Gdk::Keyval::KEY_KP_Enter] = "enter"
  keyval_trans[Gdk::Keyval::KEY_Alt_L] = "alt"
  keyval_trans[Gdk::Keyval::KEY_Alt_R] = "alt"
  keyval_trans[Gdk::Keyval::KEY_Caps_Lock] = "caps"

  keyval_trans[Gdk::Keyval::KEY_BackSpace] = "backspace"
  keyval_trans[Gdk::Keyval::KEY_KP_Page_Down] = "pagedown"
  keyval_trans[Gdk::Keyval::KEY_KP_Page_Up] = "pageup"
  keyval_trans[Gdk::Keyval::KEY_Page_Down] = "pagedown"
  keyval_trans[Gdk::Keyval::KEY_Page_Up] = "pageup"
  keyval_trans[Gdk::Keyval::KEY_Left] = "left"
  keyval_trans[Gdk::Keyval::KEY_Right] = "right"
  keyval_trans[Gdk::Keyval::KEY_Down] = "down"
  keyval_trans[Gdk::Keyval::KEY_Up] = "up"
  keyval_trans[Gdk::Keyval::KEY_space] = "space"

  keyval_trans[Gdk::Keyval::KEY_Shift_L] = "shift"
  keyval_trans[Gdk::Keyval::KEY_Shift_R] = "shift"
  keyval_trans[Gdk::Keyval::KEY_Tab] = "tab"
  keyval_trans[Gdk::Keyval::KEY_ISO_Left_Tab] = "tab"

  key_trans = {}
  key_trans["\e"] = "esc"
  tk = keyval_trans[keyval]
  keyname = tk if !tk.nil?

  key_str_parts = []
  key_str_parts << "ctrl" if vma.kbd.modifiers[:ctrl]
  key_str_parts << "alt" if vma.kbd.modifiers[:alt]
  key_str_parts << "shift" if vma.kbd.modifiers[:shift]
  key_str_parts << "meta" if vma.kbd.modifiers[:meta]
  key_str_parts << "super" if vma.kbd.modifiers[:super]
  key_str_parts << keyname

  # After remapping capslock to control in gnome-tweak tool,
  # if pressing and immediately releasing the capslock (control) key,
  # we get first "caps" on keydown and ctrl-"caps" on keyup (keyval 65509, Gdk::Keyval::KEY_Caps_Lock)
  # If mapping capslock to ctrl in hardware, we get keyval 65507 (Gdk::Keyval::KEY_Control_L) instead
  if key_str_parts[0] == "ctrl" and key_str_parts[1] == "caps"
    # Replace ctrl-caps with ctrl
    key_str_parts.delete_at(1)
  end

  if key_str_parts[0] == key_str_parts[1]
    # We don't want "ctrl-ctrl" or "alt-alt"
    # TODO:There should be a better way to do this
    key_str_parts.delete_at(0)
  end

  if key_str_parts[0] == "shift" and key_str_parts.size == 2
    if key_str_parts[1].size == 1 # and key_str_parts[1].match(/^[[:upper:]]$/)
      #"shift-P" to just "P" etc.
      # but keep shift-tab as is
      key_str_parts.delete_at(0)
    end
  end

  key_str = key_str_parts.join("-")
  if key_str == "\u0000"
    key_str = ""
  end

  keynfo = { :key_str => key_str, :key_name => keyname, :keyval => keyval }
  debug keynfo.inspect
  # vma.kbd.match_key_conf(key_str, nil, :key_press)
  # debug "key_str=#{key_str} key_"

  if key_str != "" # or prefixed_key_str != ""
    if sig == :key_release and keyval == @last_keyval
      vma.kbd.match_key_conf(key_str + "!", nil, :key_release)
      @last_event = [keynfo, :key_release]
    elsif sig == :key_press
      vma.kbd.match_key_conf(key_str, nil, :key_press)
      @last_event = [keynfo, key_str, :key_press]
    end
  end
  @last_keyval = keyval

  handle_deltas

  # set_focus(5)
  # false

  draw_cursor #TODO: only when needed
end

#handle_scrollingObject



387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
# File 'lib/vimamsa/gui_sourceview.rb', line 387

def handle_scrolling()
  return # TODO
  # curpos = buffer.cursor_position
  # debug "MOVE CURSOR: #{curpos}"
  return nil if vma.gui.nil?
  return nil if @bufo.nil?
  vma.gui.run_after_scrolling proc {
    debug "START UPDATE POS AFTER SCROLLING", 2
    bc = window_to_buffer_coords(Gtk::TextWindowType::WIDGET, gutter_width + 2, 60)
    if !bc.nil?
      i = coord_to_iter(bc[0], bc[1])
      if !i.nil?
        @bufo.set_pos(i)
      end
    end
    $update_cursor = false
  }
end

#hide_completionsObject



43
44
45
46
47
48
# File 'lib/vimamsa/gui_sourceview_autocomplete.rb', line 43

def hide_completions
  if @acwin.class == Gtk::Popover
    @acwin.hide
  end
  @autocp_active = false
end

#init_context_menuObject



767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
# File 'lib/vimamsa/gui_sourceview.rb', line 767

def init_context_menu
  all_items = CONTEXT_MENU_ITEMS + CONTEXT_MENU_ITEMS_SELECTION
  all_items.each do |label, action_id|
    actkey = "ctx_#{action_id}"
    unless vma.gui.app.lookup_action(actkey)
      act = Gio::SimpleAction.new(actkey)
      vma.gui.app.add_action(act)
      act.signal_connect("activate") do
        call_action(action_id)
        after_action
      end
    end
  end

  unless vma.gui.app.lookup_action("ctx_open_link")
    act = Gio::SimpleAction.new("ctx_open_link")
    vma.gui.app.add_action(act)
    act.signal_connect("activate") do
      next unless @context_link
      if is_url(@context_link)
        open_url(@context_link)
      else
        fn = hpt_check_cur_word(@context_link)
        open_existing_file(fn) if fn
      end
    end
  end

  @context_menu = Gtk::PopoverMenu.new
  @context_menu.set_parent(self)
  @context_menu.has_arrow = false
end

#is_cursor_visibleObject



646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
# File 'lib/vimamsa/gui_sourceview.rb', line 646

def is_cursor_visible
  vr = visible_rect
  iter = buffer.get_iter_at(:offset => buffer.cursor_position)
  iterxy = get_iter_location(iter)
  iterxy.width = 1 if iterxy.width == 0
  iterxy.height = 1 if iterxy.height == 0

  intr = iterxy.intersect(vr)
  if intr.nil?
    debug iterxy.inspect
    debug vr.inspect
    return false
  else
    return true
  end
end


800
801
802
803
804
805
806
807
808
809
# File 'lib/vimamsa/gui_sourceview.rb', line 800

def link_at(x, y)
  bx, by = window_to_buffer_coords(:widget, x.to_i, y.to_i)
  iter = get_iter_at_location(bx, by)
  return nil unless iter
  word, _range = @bufo.get_word_in_pos(iter.offset, boundary: :space)
  return nil unless word
  return word if is_url(word)
  return word if word.match?(/⟦.+⟧/)
  nil
end

#mask_for_display(str) ⇒ Object

Replace each character of every «word with * so the GTK buffer never exposes the real content. Length is preserved → buffer offsets stay valid.



50
51
52
# File 'lib/vimamsa/gui_sourceview.rb', line 50

def mask_for_display(str)
  str.gsub(/(?<=«)\S+/) { |w| "*" * w.length }
end

#masked_word_at_cursor(pos) ⇒ Object

Returns [word, word_start, word_end] if pos (Ruby-buffer offset) is inside a «word region, nil otherwise.



88
89
90
91
92
93
94
95
96
97
98
# File 'lib/vimamsa/gui_sourceview.rb', line 88

def masked_word_at_cursor(pos)
  text = @bufo.to_s
  return nil unless text.include?("«")
  text.scan(/«(\S+)/) do
    word       = $1
    word_start = $~.begin(0) + 1   # first char after «
    word_end   = word_start + word.length
    return [word, word_start, word_end] if pos >= word_start && pos <= word_end
  end
  nil
end

#pos_to_coord(i) ⇒ Object



529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
# File 'lib/vimamsa/gui_sourceview.rb', line 529

def pos_to_coord(i)
  b = buffer
  iter = b.get_iter_at(:offset => i)
  iterxy = get_iter_location(iter)
  winw = width #TODO

  view_width = visible_rect.width
  gutter_width = winw - view_width #TODO

  x = iterxy.x + gutter_width
  y = iterxy.y

  # buffer_to_window_coords(Gtk::TextWindowType::TEXT, iterxy.x, iterxy.y).inspect
  # debug buffer_to_window_coords(Gtk::TextWindowType::TEXT, x, y).inspect
  (x, y) = buffer_to_window_coords(Gtk::TextWindowType::TEXT, x, y)

  return [x, y]
end

#register_signalsObject



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/vimamsa/gui_sourceview.rb', line 194

def register_signals()
  check_controllers

  # TODO: accept GLib::Type::STRING also?
  @dt = Gtk::DropTarget.new(GLib::Type["GFile"], [Gdk::DragAction::COPY, Gdk::DragAction::MOVE])
  # GLib::Type::INVALID

  self.add_controller(@dt)
  @dt.signal_connect "drop" do |obj, v, x, y|
    if v.value.gtype == GLib::Type["GLocalFile"]
      uri = v.value.uri
    elsif v.value.class == String
      uri = v.value.gsub(/\r\n$/, "")
    end
    debug "dt,drop #{v.value},#{x},#{y}", 2
    fp = uri_to_path(uri)
    buf.handle_drag_and_drop(fp) if !fp.nil?
    true
  end

  @dt.signal_connect "enter" do |gesture, x, y, z, m|
    debug "dt,enter", 2
    Gdk::DragAction::COPY
  end

  @dt.signal_connect "motion" do |obj, x, y|
    debug "dt,move", 2

    Gdk::DragAction::COPY
  end

  # dc = Gtk::DropControllerMotion.new
  # self.add_controller(dc)
  # dc.signal_connect "enter" do |gesture, x, y|
  # debug "enter", 2
  # debug [x, y]
  # # Ripl.start :binding => binding
  # true
  # end

  # dc.signal_connect "motion" do |gesture, x, y|
  # debug "move", 2
  # debug [x, y]
  # true
  # end

  # Implement mouse selections using @cnt_mo and @cnt_drag
  @cnt_mo = Gtk::EventControllerMotion.new
  self.add_controller(@cnt_mo)
  @cnt_mo.signal_connect "motion" do |gesture, x, y|
    if !@range_start.nil? and !x.nil? and !y.nil? and buf.visual_mode?
      i = coord_to_iter(x, y, true)
      @bufo.set_pos(i) if !i.nil? and @last_iter != i
      @last_iter = i
    end
  end

  @last_coord = nil
  @cnt_drag = Gtk::GestureDrag.new
  self.add_controller(@cnt_drag)
  @cnt_drag.signal_connect "drag-begin" do |gesture, x, y|
    debug "drag-begin", 2
    i = coord_to_iter(x, y, true)
    pp i
    @range_start = i
    @drag_start_x = x
    @drag_start_y = y
    buf.start_selection
  end

  @cnt_drag.signal_connect "drag-update" do |gesture, offset_x, offset_y|
    next unless @range_start
    cur_x = @drag_start_x + offset_x
    cur_y = @drag_start_y + offset_y
    debug "drag-update 2 #{cur_x} #{cur_y}"
    i = coord_to_iter(cur_x, cur_y, true)
    @bufo.set_pos(i) if !i.nil? and @last_iter != i
    if !i.nil? and (@range_start - i).abs >= 2
    vma.kbd.set_mode(:visual)
    end
    @last_iter = i
  end

  @cnt_drag.signal_connect "drag-end" do |gesture, offsetx, offsety|
    debug "drag-end", 2
    if offsetx.abs < 5 and offsety.abs < 5
      debug "Not enough drag", 2
      buf.end_selection
      # elsif !buf.visual_mode? and vma.kbd.get_scope != :editor
    elsif vma.kbd.get_scope != :editor
      # Can't transition from editor wide mode to buffer specific mode
      vma.kbd.set_mode(:visual)
    else # The normal case
    end
    @range_start = nil
  end

  click = Gtk::GestureClick.new
  click.set_propagation_phase(Gtk::PropagationPhase::CAPTURE)
  self.add_controller(click)
  # Detect mouse click
  @click = click

  @range_start = nil

  # Handle right click
  rightclick = Gtk::GestureClick.new
  rightclick.button = 3
  self.add_controller(rightclick)
  rightclick.signal_connect "pressed" do |gesture, n_press, x, y, z|
    puts "SourceView, GestureClick rightclick released button=#{gesture.button} x=#{x} y=#{y}"
    if gesture.button == 3
      show_context_menu(x, y)
      next true
    end
  end

  click.signal_connect "pressed" do |gesture, n_press, x, y, z|
    debug "SourceView, GestureClick released x=#{x} y=#{y}"
    vma.gui.instance_variable_set(:@kbd_passthrough, false)

    if buf.visual_mode?
      buf.end_visual_mode
    end

    xloc = (x - gutter_width).to_i
    yloc = (y + visible_rect.y).to_i
    debug "xloc=#{xloc} yloc=#{yloc}"

    i = coord_to_iter(xloc, yloc)
    # @range_start = i

    # This needs to happen after xloc calculation, otherwise xloc gets a wrong value (around 200 bigger)
    if vma.gui.current_view != self
      vma.gui.set_current_view(self)
    end

    @bufo.set_pos(i) if !i.nil?
    if n_press == 2
      #TODO: refactor to have one function for all line actions
      if @bufo.module&.respond_to?(:select_line)
        @bufo.module.select_line
      else
        @bufo.cur_nonwhitespace_word_action()
      end
    end
    true
  end

  click.signal_connect "released" do |gesture, n_press, x, y, z|
    debug "SourceView, GestureClick released x=#{x} y=#{y} button=#{gesture.button}"

    xloc = (x - gutter_width).to_i
    yloc = (y + visible_rect.y).to_i
    debug "xloc=#{xloc} yloc=#{yloc}"

    # This needs to happen after xloc calculation, otherwise xloc gets a wrong value (around 200 bigger)
    if vma.gui.current_view != self
      vma.gui.set_current_view(self)
    end

    i = coord_to_iter(xloc, yloc)

    # if i != @range_start
    # debug "RANGE #{[@range_start, i]}", 2
    # end

    @bufo.set_pos(i) if !i.nil?
    # @range_start = nil
    true
  end
end

#remask_gtk_bufferObject

Sync the GTK buffer to mask_for_display(@bufo.to_s). Handles both directions:

- masks unmasked 


66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/vimamsa/gui_sourceview.rb', line 66

def remask_gtk_buffer
  return unless @bufo&.lang == "hyperplaintext"
  ruby_text = @bufo.to_s
  expected  = mask_for_display(ruby_text)
  gtk_text  = buffer.text
  return if expected == gtk_text
  return if expected.length != gtk_text.length  # sanity check

  # Find the smallest enclosing differing range and fix it in one edit.
  i = 0
  i += 1 while i < expected.length && expected[i] == gtk_text[i]
  j = expected.length - 1
  j -= 1 while j >= i && expected[j] == gtk_text[j]

  s_iter = buffer.get_iter_at(:offset => i)
  e_iter = buffer.get_iter_at(:offset => j + 1)
  buffer.delete(s_iter, e_iter)
  buffer.insert(buffer.get_iter_at(:offset => i), expected[i..j])
end

#sanity_checkObject



578
579
580
581
582
583
584
585
586
587
588
589
590
591
# File 'lib/vimamsa/gui_sourceview.rb', line 578

def sanity_check()
  a = buffer.text
  b = buf.to_s
  # debug "===================="
  # debug a.lines[0..10].join()
  # debug "===================="
  # debug b.lines[0..10].join()
  # debug "===================="
  if a == b
    debug "Buffers match"
  else
    debug "ERROR: Buffer's don't match."
  end
end

#set_content(str) ⇒ Object



54
55
56
57
58
59
60
# File 'lib/vimamsa/gui_sourceview.rb', line 54

def set_content(str)
  if @bufo&.lang == "hyperplaintext"
    self.buffer.set_text(mask_for_display(str))
  else
    self.buffer.set_text(str)
  end
end

#set_cursor_color(ctype) ⇒ Object



682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
# File 'lib/vimamsa/gui_sourceview.rb', line 682

def set_cursor_color(ctype)
  if @ctype != ctype
    bg = $confh[:mode][ctype][:cursor][:background]
    if bg.class == String
      if !@cursor_prov.nil?
        self.style_context.remove_provider(@cursor_prov)
      end
      prov = Gtk::CssProvider.new
      # prov.load(data: ".view text selection { background-color: #{bg}; color: #ffffff; }")
      prov.load(data: ".view text selection { background-color: #{bg}; color: #ffffff; } .view { caret-color: #{bg};  }")
      self.style_context.add_provider(prov)
      @cursor_prov = prov
    end
    @ctype = ctype
  end
end

#set_cursor_pos(pos) ⇒ Object



593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
# File 'lib/vimamsa/gui_sourceview.rb', line 593

def set_cursor_pos(pos)
  itr = buffer.get_iter_at(:offset => pos)
  itr2 = buffer.get_iter_at(:offset => pos + 1)
  buffer.place_cursor(itr)

  within_margin = 0.075 #margin as a [0.0,0.5) fraction of screen size
  use_align = false
  xalign = 0.5 #0.0=top 1.0=bottom, 0.5=center
  yalign = 0.5

  if @curpos_mark.nil?
    @curpos_mark = buffer.create_mark("cursor", itr, false)
  else
    buffer.move_mark(@curpos_mark, itr)
  end
  scroll_to_mark(@curpos_mark, within_margin, use_align, xalign, yalign)
  $idle_scroll_to_mark = true
  ensure_cursor_visible

  # draw_cursor

  return true
end

#set_cursor_to_topObject



406
407
408
409
410
411
412
413
414
415
416
# File 'lib/vimamsa/gui_sourceview.rb', line 406

def set_cursor_to_top
  debug "set_cursor_to_top", 2
  bc = window_to_buffer_coords(Gtk::TextWindowType::WIDGET, gutter_width + 2, 60)
  if !bc.nil?
    i = coord_to_iter(bc[0], bc[1])
    if !i.nil?
      @bufo.set_pos(i)
      set_cursor_pos(i)
    end
  end
end

#show_completionsObject



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/vimamsa/gui_sourceview_autocomplete.rb', line 93

def show_completions
  hide_completions
  bu = vma.buf
  (w, range) = bu.get_word_in_pos(bu.pos - 1, boundary: :word)
  debug [w, range].to_s, 2
  matches = Autocomplete.matching_words w
  return if matches.empty?
  @autocp_active = true
  @cpl_list = cpl_list = matches
  win = Gtk::Popover.new()
  win.parent = self
  vbox = Gtk::Grid.new()
  win.set_child(vbox)

  i = 0
  @autocp_items = []
  @autocp_selected = 0
  for x in cpl_list
    l = Gtk::Label.new(x)
    @autocp_items << l
    # numbers: left, top, width, height
    vbox.attach(l, 0, i, 1, 1)
    i += 1
  end
  autocp_hilight(0)
  (x, y) = cur_pos_xy
  rec = Gdk::Rectangle.new(x, y + 8, 10, 10)
  win.has_arrow = false
  win.set_pointing_to(rec)
  win.autohide = false
  win.popup
  gui_remove_controllers(win)
  @acwin = win
end

#show_context_menu(x, y) ⇒ Object



811
812
813
814
815
816
817
818
819
820
821
822
823
# File 'lib/vimamsa/gui_sourceview.rb', line 811

def show_context_menu(x, y)
  init_context_menu if @context_menu.nil?
  menu = Gio::Menu.new
  @context_link = link_at(x, y)
  menu.append("Open link", "app.ctx_open_link") if @context_link
  CONTEXT_MENU_ITEMS.each { |label, action_id| menu.append(label, "app.ctx_#{action_id}") }
  if @bufo.selection_active?
    CONTEXT_MENU_ITEMS_SELECTION.each { |label, action_id| menu.append(label, "app.ctx_#{action_id}") }
  end
  @context_menu.set_menu_model(menu)
  @context_menu.set_pointing_to(Gdk::Rectangle.new(x.to_i, y.to_i, 1, 1))
  @context_menu.popup
end

#show_controllersObject



140
141
142
143
144
145
146
# File 'lib/vimamsa/gui_sourceview.rb', line 140

def show_controllers
  clist = self.observe_controllers
  (0..(clist.n_items - 1)).each { |x|
    ctr = clist.get_item(x)
    pp ctr
  }
end

#start_autocompleteObject



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/vimamsa/gui_sourceview_autocomplete.rb', line 128

def start_autocomplete
  return
  # Roughly following these examples:
  # https://stackoverflow.com/questions/52359721/howto-maintain-gtksourcecompletion-when-changing-buffers-in-a-gtksourceview
  # and gedit-plugins-41.0/plugins/wordcompletion/gedit-word-completion-plugin.c
  # .. but it doesn't work. So implementing using Popover.
  # Keeping this for reference

  cp = self.completion
  prov = GtkSource::CompletionWords.new("Autocomplete") # (name,icon)
  prov.register(self.buffer)
  cp.add_provider(prov)
  pp prov
  self.show_completion
end

#try_autocompleteObject



90
91
# File 'lib/vimamsa/gui_sourceview_autocomplete.rb', line 90

def try_autocomplete
end

#unmask_gtk_region(word, word_start, word_end) ⇒ Object

Replace the masked (***) span with the real word from the Ruby buffer.



101
102
103
104
105
106
# File 'lib/vimamsa/gui_sourceview.rb', line 101

def unmask_gtk_region(word, word_start, word_end)
  s_iter = buffer.get_iter_at(:offset => word_start)
  e_iter = buffer.get_iter_at(:offset => word_end)
  buffer.delete(s_iter, e_iter)
  buffer.insert(buffer.get_iter_at(:offset => word_start), word)
end

#update_cursor_unmaskObject

When in insert mode with cursor on a «word, expose the real text in the GTK buffer so the user can see/edit it. Re-mask when cursor leaves or mode changes.



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/vimamsa/gui_sourceview.rb', line 110

def update_cursor_unmask
  return unless @bufo&.lang == "hyperplaintext"
  ctype = vma.kbd.get_cursor_type
  pos   = @bufo.pos

  if ctype == :insert
    result = masked_word_at_cursor(pos)
    if result
      word, word_start, word_end = result
      return if @unmasked_range == [word_start, word_end]  # already showing this word
      remask_gtk_buffer if @unmasked_range                 # re-mask previous region
      unmask_gtk_region(word, word_start, word_end)
      @unmasked_range = [word_start, word_end]
      return
    end
  end

  # Not insert mode, or cursor is not over a masked word — ensure everything masked
  if @unmasked_range
    @unmasked_range = nil
    remask_gtk_buffer
  end
end