vimamsa 0.1.10 → 0.1.11

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