vimamsa 0.1.10 → 0.1.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,35 +6,36 @@ require "openssl"
6
6
  require "ripl/multi_line"
7
7
 
8
8
  $paste_lines = false
9
- $buffer_history = [0]
9
+ $buffer_history = []
10
10
 
11
11
  $update_highlight = false
12
12
 
13
13
  $ifuncon = false
14
14
 
15
15
  class Buffer < String
16
-
17
- #attr_reader (:pos, :cpos, :lpos)
18
-
19
- attr_reader :pos, :lpos, :cpos, :deltas, :edit_history, :fname, :call_func, :pathname, :basename, :dirname, :update_highlight, :marks, :is_highlighted, :syntax_detect_failed, :id, :lang, :images
16
+ attr_reader :pos, :lpos, :cpos, :deltas, :edit_history, :fname, :call_func, :pathname, :basename, :dirname, :update_highlight, :marks, :is_highlighted, :syntax_detect_failed, :id, :lang, :images, :last_save
20
17
  attr_writer :call_func, :update_highlight
21
18
  attr_accessor :gui_update_highlight, :update_hl_startpos, :update_hl_endpos, :hl_queue, :syntax_parser, :highlights, :gui_reset_highlight, :is_parsing_syntax, :line_ends, :bt, :line_action_handler, :module, :active_kbd_mode, :title, :subtitle
22
19
 
23
20
  @@num_buffers = 0
24
21
 
25
- def initialize(str = "\n", fname = nil)
22
+ def initialize(str = "\n", fname = nil, prefix = "buf")
26
23
  debug "Buffer.rb: def initialize"
27
24
  super(str)
28
25
 
29
26
  @images = []
27
+ @audiofiles = []
30
28
  @lang = nil
31
29
  @id = @@num_buffers
32
30
  @@num_buffers += 1
31
+ @version = 0
33
32
  gui_create_buffer(@id, self)
34
33
  debug "NEW BUFFER fn=#{fname} ID:#{@id}"
35
34
 
36
35
  @module = nil
37
36
 
37
+ @last_save = @last_asked_from_user = @file_last_cheked = Time.now
38
+
38
39
  @crypt = nil
39
40
  @update_highlight = true
40
41
  @syntax_detect_failed = false
@@ -51,19 +52,18 @@ class Buffer < String
51
52
  @line_action_handler = nil
52
53
 
53
54
  @dirname = nil
54
- @title = "*buf-#{@id}*"
55
+ @title = "*#{prefix}-#{@id}*"
55
56
  @subtitle = ""
56
57
 
57
58
  if @fname
58
59
  @title = File.basename(@fname)
59
60
  @dirname = File.dirname(@fname)
60
61
  userhome = File.expand_path("~")
61
- @subtitle = @dirname.gsub(/^#{userhome}/, "~")
62
+ # @subtitle = @dirname.gsub(/^#{userhome}/, "~")
63
+ @subtitle = @fname.gsub(/^#{userhome}/, "~")
62
64
  end
63
65
 
64
66
  t1 = Time.now
65
- gui_set_current_buffer(@id)
66
- gui_set_window_title(@title, @subtitle)
67
67
 
68
68
  set_content(str)
69
69
  debug "init time:#{Time.now - t1}"
@@ -72,6 +72,10 @@ class Buffer < String
72
72
  self << "\n" if self[-1] != "\n"
73
73
  @current_word = nil
74
74
  @active_kbd_mode = nil
75
+ if conf(:enable_lsp)
76
+ init_lsp
77
+ end
78
+ return self
75
79
  end
76
80
 
77
81
  def list_str()
@@ -99,6 +103,36 @@ class Buffer < String
99
103
  end
100
104
  end
101
105
 
106
+ def lsp_get_def()
107
+ if !@lsp.nil?
108
+ fpuri = URI.join("file:///", @fname).to_s
109
+ @lsp.get_definition(fpuri, @lpos, @cpos)
110
+ end
111
+ end
112
+
113
+ def lsp_jump_to_def()
114
+ if !@lsp.nil?
115
+ fpuri = URI.join("file:///", @fname).to_s
116
+ r = @lsp.get_definition(fpuri, @lpos, @cpos)
117
+ if !r.nil?
118
+ jump_to_file(r[0], r[1])
119
+ end
120
+ end
121
+ end
122
+
123
+ def init_lsp()
124
+ if conf(:enable_lsp) and !@lang.nil?
125
+ @lsp = LangSrv.get(@lang.to_sym)
126
+ if @lang == "php"
127
+ # Ripl.start :binding => binding
128
+ end
129
+ end
130
+
131
+ if !@lsp.nil?
132
+ @lsp.open_file(@fname, self.to_s)
133
+ end
134
+ end
135
+
102
136
  def detect_file_language
103
137
  @lang = nil
104
138
  @lang = "c" if @fname.match(/\.(c|h|cpp)$/)
@@ -106,6 +140,7 @@ class Buffer < String
106
140
  @lang = "ruby" if @fname.match(/\.(rb)$/)
107
141
  @lang = "hyperplaintext" if @fname.match(/\.(txt)$/)
108
142
  @lang = "php" if @fname.match(/\.(php)$/)
143
+ @lsp = nil
109
144
 
110
145
  lm = GtkSource::LanguageManager.new
111
146
 
@@ -119,10 +154,12 @@ class Buffer < String
119
154
  debug "Guessed LANG: #{lang.id}"
120
155
  @lang = lang.id
121
156
  end
157
+ puts @lang.inspect
122
158
 
123
159
  if @lang
124
160
  gui_set_file_lang(@id, @lang)
125
161
  end
162
+ return @lang
126
163
  end
127
164
 
128
165
  def view()
@@ -130,6 +167,59 @@ class Buffer < String
130
167
  return vma.gui.buffers[@id]
131
168
  end
132
169
 
170
+ # Replace char at pos with audio widget for
171
+ def add_audio(afpath, pos)
172
+ return if !is_legal_pos(pos)
173
+ afpath = File.expand_path(afpath)
174
+ return if !File.exist?(afpath)
175
+
176
+ vbuf = view.buffer
177
+ itr = vbuf.get_iter_at(:offset => pos)
178
+ itr2 = vbuf.get_iter_at(:offset => pos + 1)
179
+ vbuf.delete(itr, itr2)
180
+ anchor = vbuf.create_child_anchor(itr)
181
+
182
+ mf = Gtk::MediaFile.new(afpath)
183
+ mc = Gtk::MediaControls.new(mf)
184
+ # mc = Gtk::MediaControls.new(Gtk::MediaFile.new)
185
+ # Thread.new{mf.play;sleep 0.01; mf.pause}
186
+ @audiofiles << mc
187
+
188
+ view.add_child_at_anchor(mc, anchor)
189
+ mc.set_size_request(500, 20)
190
+ mc.set_margin_start(view.gutter_width + 10)
191
+
192
+ provider = Gtk::CssProvider.new
193
+ mc.add_css_class("medctr")
194
+
195
+ provider.load(data: ".medctr { background-color:#353535; }")
196
+ mc.style_context.add_provider(provider)
197
+
198
+ pp mf.set_prepared(true)
199
+ # pp mf.pause
200
+ pp mf.duration
201
+ pp mf.has_audio?
202
+
203
+ # >> Gtk::MediaControls.signals
204
+ # => ["direction-changed", "destroy", "show", "hide", "map", "unmap", "realize", "unrealize", "state-flags-changed", "mnemonic-activate", "move-focus", "keynav-failed", "query-tooltip", "notify"]
205
+
206
+ # If this is done too early, the gutter is not yet drawn which
207
+ # will result in wrong position
208
+ if @audiofiles.size == 1
209
+ run_as_idle proc { self.reset_audio_widget_positions }
210
+ end
211
+ $audiof = mf
212
+ end
213
+
214
+ def reset_audio_widget_positions
215
+ debug "reset_audio_widget_positions", 2
216
+ for mc in @audiofiles
217
+ mc.set_size_request(500, 20)
218
+ mc.set_margin_start(view.gutter_width + 10)
219
+ end
220
+ return false
221
+ end
222
+
133
223
  def add_image(imgpath, pos)
134
224
  return if !is_legal_pos(pos)
135
225
 
@@ -141,16 +231,17 @@ class Buffer < String
141
231
 
142
232
  da = ResizableImage.new(imgpath, view)
143
233
  view.add_child_at_anchor(da, anchor)
144
- da.signal_connect "draw" do |widget, cr|
234
+
235
+ da.set_draw_func do |widget, cr|
145
236
  da.do_draw(widget, cr)
146
237
  end
147
238
 
148
239
  da.scale_image
149
240
 
150
- vma.gui.handle_image_resize
241
+ # vma.gui.handle_image_resize #TODO:gtk4
151
242
  @images << { :path => imgpath, :obj => da }
152
243
 
153
- gui_set_current_buffer(@id)
244
+ gui_set_current_buffer(@id) #TODO: needed?
154
245
  end
155
246
 
156
247
  def is_legal_pos(pos, op = :read)
@@ -307,6 +398,7 @@ class Buffer < String
307
398
  gui_set_buffer_contents(@id, self.to_s)
308
399
  @images = [] #TODO: if reload
309
400
  hpt_scan_images(self)
401
+ hpt_scan_audio(self)
310
402
 
311
403
  # add_hl_update(@update_hl_startpos, @update_hl_endpos)
312
404
  end
@@ -402,9 +494,23 @@ class Buffer < String
402
494
  def run_delta(delta, auto_update_cpos = false)
403
495
  # auto_update_cpos: In some cases position of cursor should be updated automatically based on change to buffer (delta). In other cases this is handled by the action that creates the delta.
404
496
 
497
+ # delta[0]: char position
498
+ # delta[1]: INSERT or DELETE
499
+ # delta[2]: number of chars affected
500
+ # delta[3]: text to add in case of insert
501
+
502
+ @version += 1
405
503
  if $experimental
406
504
  @bt.handle_delta(Delta.new(delta[0], delta[1], delta[2], delta[3]))
407
505
  end
506
+
507
+ if !@lsp.nil?
508
+ dc = delta.clone
509
+ dc[3] = "" if dc[3].nil?
510
+ dc[4] = get_line_and_col_pos(delta[0])
511
+ dc[5] = get_line_and_col_pos(delta[0] + delta[2])
512
+ @lsp.handle_delta(dc, @fname, @version)
513
+ end
408
514
  pos = delta[0]
409
515
  if @edit_pos_history.any? and (@edit_pos_history.last - pos).abs <= 2
410
516
  @edit_pos_history.pop
@@ -441,7 +547,7 @@ class Buffer < String
441
547
  @update_hl_endpos = pos + delta[2]
442
548
  add_hl_update(@update_hl_startpos, @update_hl_endpos)
443
549
  end
444
- debug "DELTA=#{delta.inspect}"
550
+ debug("DELTA=#{delta.inspect}", 2)
445
551
  # sanity_check_line_ends #TODO: enable with debug mode
446
552
  #highlight_c()
447
553
 
@@ -804,6 +910,8 @@ class Buffer < String
804
910
  end
805
911
  gui_set_cursor_pos(@id, @pos)
806
912
  calculate_line_and_column_pos
913
+
914
+ check_if_modified_outside
807
915
  end
808
916
 
809
917
  # Get the line number of character position
@@ -871,12 +979,12 @@ class Buffer < String
871
979
  set_pos(pos)
872
980
  end
873
981
 
874
- def delete(op)
982
+ def delete(op, x = nil)
875
983
  $paste_lines = false
876
984
  # Delete selection
877
985
  if op == SELECTION && visual_mode?
878
986
  (startpos, endpos) = get_visual_mode_range2
879
- delete_range(startpos, endpos)
987
+ delete_range(startpos, endpos, x)
880
988
  @pos = [@pos, @selection_start].min
881
989
  end_visual_mode
882
990
  #return
@@ -904,21 +1012,39 @@ class Buffer < String
904
1012
  #need_redraw!
905
1013
  end
906
1014
 
907
- def delete_range(startpos, endpos)
908
- #s = self.slice!(startpos..endpos)
909
- set_clipboard(self[startpos..endpos])
1015
+ def delete_range(startpos, endpos, x = nil)
1016
+ s = self[startpos..endpos]
1017
+ if startpos == endpos or s == ""
1018
+ return
1019
+ end
1020
+ if x == :append
1021
+ debug "APPEND"
1022
+ s += "\n" + get_clipboard()
1023
+ end
1024
+ set_clipboard(s)
910
1025
  add_delta([startpos, DELETE, (endpos - startpos + 1)], true)
911
1026
  #recalc_line_ends
912
1027
  calculate_line_and_column_pos
913
1028
  end
914
1029
 
1030
+ # Ranges to use in delete or copy operations
915
1031
  def get_range(range_id)
916
1032
  range = nil
917
1033
  if range_id == :to_word_end
1034
+ # TODO: better way to make the search than + 150 from current position
918
1035
  wmarks = get_word_end_marks(@pos, @pos + 150)
919
1036
  if wmarks.any?
920
1037
  range = @pos..wmarks[0]
921
1038
  end
1039
+ elsif range_id == :to_next_word # start of
1040
+ wmarks = get_word_start_marks(@pos, @pos + 150)
1041
+ if !wmarks[0].nil?
1042
+ range = @pos..(wmarks[0] - 1)
1043
+ debug range, 2
1044
+ else
1045
+ range = get_range(:to_word_end)
1046
+ end
1047
+ # Ripl.start :binding => binding
922
1048
  elsif range_id == :to_line_end
923
1049
  debug "TO LINE END"
924
1050
  range = @pos..(@line_ends[@lpos] - 1)
@@ -1150,7 +1276,8 @@ class Buffer < String
1150
1276
  wtype = :url
1151
1277
  elsif is_existing_file(path)
1152
1278
  message("PATH:'#{word}'")
1153
- if vma.can_open_extension?(path)
1279
+ # if vma.can_open_extension?(path)
1280
+ if file_is_text_file(path)
1154
1281
  wtype = :textfile
1155
1282
  else
1156
1283
  wtype = :file
@@ -1366,6 +1493,12 @@ class Buffer < String
1366
1493
  calculate_line_and_column_pos
1367
1494
  end
1368
1495
 
1496
+ def append(c)
1497
+ pos = self.size - 1
1498
+ add_delta([pos, INSERT, c.size, c], true)
1499
+ calculate_line_and_column_pos
1500
+ end
1501
+
1369
1502
  def execute_current_line_in_terminal(autoclose = false)
1370
1503
  s = get_current_line
1371
1504
  exec_in_terminal(s, autoclose)
@@ -1470,22 +1603,27 @@ class Buffer < String
1470
1603
  set_pos(l.end + 1)
1471
1604
  end
1472
1605
 
1473
- def paste(at = AFTER, register = nil)
1474
- # Paste after current char. Except if at end of line, paste before end of line.
1475
- text = ""
1476
- if register.nil?
1477
- text = paste_system_clipboard
1606
+ # Start asynchronous read of system clipboard
1607
+ def paste_start(at, register)
1608
+ @clipboard_paste_running = true
1609
+ clipboard = vma.gui.window.display.clipboard
1610
+ clipboard.read_text_async do |_clipboard, result|
1611
+ text = clipboard.read_text_finish(result)
1612
+ paste_finish(text, at, register)
1478
1613
  end
1614
+ end
1479
1615
 
1480
- if text == ""
1481
- return if !$clipboard.any?
1482
- if register == nil
1483
- text = $clipboard[-1]
1484
- else
1485
- text = $register[register]
1486
- end
1487
- end
1616
+ def paste_finish(text, at, register)
1617
+ # if text == ""
1618
+ # return if !$clipboard.any?
1619
+ # if register == nil
1620
+ # text = $clipboard[-1]
1621
+ # else
1622
+ # text = $register[register]
1623
+ # end
1624
+ # end
1488
1625
  debug "PASTE: #{text}"
1626
+ $clipboard << text
1489
1627
 
1490
1628
  return if text == ""
1491
1629
 
@@ -1502,9 +1640,21 @@ class Buffer < String
1502
1640
  set_pos(pos + text.size)
1503
1641
  end
1504
1642
  set_pos(@pos)
1505
- #TODO: AFTER does not work
1506
- #insert_txt($clipboard[-1],AFTER)
1507
- #recalc_line_ends #TODO: bug when run twice?
1643
+ @clipboard_paste_running = false
1644
+ end
1645
+
1646
+ def paste(at = AFTER, register = nil)
1647
+ # Macro's don't work with asynchronous call using GTK
1648
+ # TODO: implement as synchronous?
1649
+ # Use internal clipboard
1650
+ if vma.macro.running_macro
1651
+ text = get_clipboard()
1652
+ paste_finish(text, at, register)
1653
+ else
1654
+ # Get clipboard using GUI
1655
+ paste_start(at, register)
1656
+ end
1657
+ return true
1508
1658
  end
1509
1659
 
1510
1660
  def delete_line()
@@ -1623,6 +1773,10 @@ class Buffer < String
1623
1773
  set_clipboard(self.fname)
1624
1774
  end
1625
1775
 
1776
+ def put_file_ref_to_clipboard
1777
+ set_clipboard(self.fname + ":#{@lpos}")
1778
+ end
1779
+
1626
1780
  def delete_active_selection() #TODO: remove this function
1627
1781
  return if !@visual_mode #TODO: this should not happen
1628
1782
 
@@ -1641,6 +1795,10 @@ class Buffer < String
1641
1795
 
1642
1796
  def get_visual_mode_range2()
1643
1797
  r = get_visual_mode_range
1798
+ if r.begin > r.end
1799
+ debug "r.begin > r.end"
1800
+ Ripl.start :binding => binding
1801
+ end
1644
1802
  return [r.begin, r.end]
1645
1803
  end
1646
1804
 
@@ -1659,9 +1817,9 @@ class Buffer < String
1659
1817
  _end = @pos
1660
1818
 
1661
1819
  _start, _end = _end, _start if _start > _end
1662
- _end = _end + 1 if _start < _end
1663
-
1664
- return _start..(_end - 1)
1820
+ # _end = _end + 1 if _start < _end #TODO:verify if correct
1821
+ # return _start..(_end - 1)
1822
+ return _start..(_end)
1665
1823
  end
1666
1824
 
1667
1825
  def selection_start()
@@ -1695,7 +1853,36 @@ class Buffer < String
1695
1853
  # calls back to file_saveas
1696
1854
  end
1697
1855
 
1698
- def save_as_callback(fpath)
1856
+ def save_as_check_callback(vals)
1857
+ if vals["yes_btn"] == "submit"
1858
+ save_as_callback(@unconfirmed_path, true)
1859
+ end
1860
+ end
1861
+
1862
+ def save_as_callback(fpath, confirmed = false)
1863
+ # The check if file exists is handled by Gtk::FileChooserDialog in GTK4
1864
+ # Keeping this code from GTK3 in case want to do this manually at some point
1865
+ # if !confirmed
1866
+ # @unconfirmed_path = fpath
1867
+ # if File.exists?(fpath) and File.file?(fpath)
1868
+ # params = {}
1869
+ # params["title"] = "The file already exists, overwrite? \r #{fpath}"
1870
+ # params["inputs"] = {}
1871
+ # params["inputs"]["yes_btn"] = { :label => "Yes", :type => :button, :default_focus => true }
1872
+ # callback = proc { |x| save_as_check_callback(x) }
1873
+ # params[:callback] = callback
1874
+ # PopupFormGenerator.new(params).run
1875
+ # return
1876
+ # elsif File.exists?(fpath) #and File.directory?(fpath)
1877
+ # params = {}
1878
+ # params["title"] = "Can't write to the destination.\r #{fpath}"
1879
+ # params["inputs"] = {}
1880
+ # params["inputs"]["ok_btn"] = { :label => "Ok", :type => :button, :default_focus => true }
1881
+ # PopupFormGenerator.new(params).run
1882
+ # return
1883
+ # end
1884
+ # end
1885
+
1699
1886
  set_filename(fpath)
1700
1887
  save()
1701
1888
  gui_set_window_title(@title, @subtitle) #TODO: if not active buffer?
@@ -1729,11 +1916,47 @@ class Buffer < String
1729
1916
  message("File #{fpath} not writeable")
1730
1917
  #TODO: show message box
1731
1918
  end
1919
+ @last_save = Time.now
1920
+ puts "file saved on #{@last_save}"
1732
1921
  sleep 3
1733
1922
  }
1734
1923
  end
1735
1924
 
1925
+ def check_if_modified_outside_callback(x)
1926
+ debug "check_if_modified_outside_callback"
1927
+ if x["yes_btn"] == "submit"
1928
+ revert()
1929
+ end
1930
+ end
1931
+
1932
+ def check_if_modified_outside
1933
+ # Don't check if less than 8 seconds since last checked
1934
+ return false if @fname.nil?
1935
+ return false if Time.now - 8 < @file_last_cheked
1936
+ @file_last_cheked = Time.now
1937
+ return false if !File.exist?(@fname)
1938
+
1939
+ file_stat = File.stat(@fname)
1940
+ modification_time = file_stat.mtime
1941
+
1942
+ if modification_time > @last_save and @last_asked_from_user < modification_time
1943
+ @last_asked_from_user = Time.now
1944
+ debug "File modified outside this program."
1945
+ params = {}
1946
+ params["title"] = "The file has been modified outside this program. Reload from disk? \r #{@fname}"
1947
+ params["inputs"] = {}
1948
+ params["inputs"]["yes_btn"] = { :label => "Yes", :type => :button, :default_focus => true }
1949
+ callback = proc { |x| check_if_modified_outside_callback(x) }
1950
+ params[:callback] = callback
1951
+ PopupFormGenerator.new(params).run
1952
+ return true
1953
+ end
1954
+
1955
+ return false
1956
+ end
1957
+
1736
1958
  def save()
1959
+ check_if_modified_outside
1737
1960
  if !@fname
1738
1961
  save_as()
1739
1962
  return
@@ -1754,22 +1977,24 @@ class Buffer < String
1754
1977
 
1755
1978
  message("Auto format #{@fname}")
1756
1979
 
1757
- if ["chdr", "c", "cpp"].include?(get_file_type())
1980
+ ftype = get_file_type()
1981
+ if ["chdr", "c", "cpp", "cpphdr"].include?(ftype)
1758
1982
 
1759
1983
  #C/C++/Java/JavaScript/Objective-C/Protobuf code
1760
1984
  system("clang-format -style='{BasedOnStyle: LLVM, ColumnLimit: 100, SortIncludes: false}' #{file.path} > #{infile.path}")
1761
1985
  bufc = IO.read(infile.path)
1762
- elsif get_file_type() == "Javascript"
1986
+ elsif ftype == "Javascript"
1763
1987
  cmd = "clang-format #{file.path} > #{infile.path}'"
1764
1988
  debug cmd
1765
1989
  system(cmd)
1766
1990
  bufc = IO.read(infile.path)
1767
- elsif get_file_type() == "ruby"
1991
+ elsif ftype == "ruby"
1768
1992
  cmd = "rufo #{file.path}"
1769
1993
  debug cmd
1770
1994
  system(cmd)
1771
1995
  bufc = IO.read(file.path)
1772
1996
  else
1997
+ message("No auto-format handler for file of type: #{ftype}")
1773
1998
  return
1774
1999
  end
1775
2000
  self.update_content(bufc)
@@ -1818,8 +2043,8 @@ def is_path_writable(fpath)
1818
2043
  end
1819
2044
 
1820
2045
  def backup_all_buffers()
1821
- for buf in selffers
1822
- buf.backup
2046
+ for b in bufs
2047
+ b.backup
1823
2048
  end
1824
2049
  message("Backup all buffers")
1825
2050
  end