vimamsa 0.1.0 → 0.1.5

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.
@@ -0,0 +1,1760 @@
1
+ require "digest"
2
+ require "tempfile"
3
+ require "pathname"
4
+ require "openssl"
5
+ require "ripl/multi_line"
6
+
7
+ $paste_lines = false
8
+ $buffer_history = [0]
9
+
10
+ $update_highlight = false
11
+
12
+ class Buffer < String
13
+
14
+ #attr_reader (:pos, :cpos, :lpos)
15
+
16
+ attr_reader :pos, :lpos, :cpos, :deltas, :edit_history, :fname, :call_func, :pathname, :basename, :update_highlight, :marks, :is_highlighted, :syntax_detect_failed, :id, :lang
17
+ attr_writer :call_func, :update_highlight
18
+ attr_accessor :qt_update_highlight, :update_hl_startpos, :update_hl_endpos, :hl_queue, :syntax_parser, :highlights, :qt_reset_highlight, :is_parsing_syntax, :line_ends, :bt, :line_action_handler, :module, :active_kbd_mode, :title, :subtitle
19
+
20
+ @@num_buffers = 0
21
+
22
+ def initialize(str = "\n", fname = nil)
23
+ debug "Buffer.rb: def initialize"
24
+ super(str)
25
+
26
+ @lang = nil
27
+ @id = @@num_buffers
28
+ @@num_buffers += 1
29
+ qt_create_buffer(@id)
30
+ puts "NEW BUFFER fn=#{fname} ID:#{@id}"
31
+
32
+ @module = nil
33
+
34
+ @crypt = nil
35
+ @update_highlight = true
36
+ @syntax_detect_failed = false
37
+ @is_parsing_syntax = false
38
+ @last_update = Time.now - 100
39
+ @highlights = {}
40
+ if fname != nil
41
+ @fname = File.expand_path(fname)
42
+ detect_file_language()
43
+ else
44
+ @fname = fname
45
+ end
46
+ @hl_queue = []
47
+ @line_action_handler = nil
48
+
49
+ @dirname = nil
50
+ @title = "*buf-#{@id}*"
51
+ @subtitle = ""
52
+
53
+ if @fname
54
+ @title = File.basename(@fname)
55
+ @dirname = File.dirname(@fname)
56
+ userhome = File.expand_path("~")
57
+ @subtitle = @dirname.gsub(/^#{userhome}/, "~")
58
+ end
59
+
60
+ t1 = Time.now
61
+ qt_set_current_buffer(@id)
62
+ gui_set_window_title(@title, @subtitle)
63
+
64
+ set_content(str)
65
+ debug "init time:#{Time.now - t1}"
66
+
67
+ # TODO: add \n when chars are added after last \n
68
+ self << "\n" if self[-1] != "\n"
69
+ @current_word = nil
70
+ @active_kbd_mode = nil
71
+ end
72
+
73
+ def set_active
74
+ if !@active_kbd_mode.nil?
75
+ $kbd.set_mode(@active_kbd_mode)
76
+ else
77
+ $kbd.set_mode_to_default
78
+ end
79
+ # qt_set_current_buffer(@id)
80
+ end
81
+
82
+ def detect_file_language
83
+ @lang = nil
84
+ @lang = "c" if @fname.match(/\.(c|h|cpp)$/)
85
+ @lang = "java" if @fname.match(/\.(java)$/)
86
+ @lang = "ruby" if @fname.match(/\.(rb)$/)
87
+ @lang = "hyperplaintext" if @fname.match(/\.(txt)$/)
88
+ @lang = "php" if @fname.match(/\.(php)$/)
89
+
90
+ lm = GtkSource::LanguageManager.new
91
+
92
+ lm.set_search_path(lm.search_path << ppath("lang/"))
93
+ lang = lm.guess_language(@fname)
94
+ # lang.get_metadata("line-comment-start")
95
+ # lang.get_metadata("block-comment-start")
96
+ # lang.get_metadata("block-comment-end")
97
+ @lang_nfo = lang
98
+ if !lang.nil? and !lang.id.nil?
99
+ puts "Guessed LANG: #{lang.id}"
100
+ @lang = lang.id
101
+ end
102
+
103
+ if @lang
104
+ gui_set_file_lang(@id, @lang)
105
+ end
106
+ end
107
+
108
+ def add_image(imgpath, pos)
109
+ return if !is_legal_pos(pos)
110
+ # insert_txt_at(" ", pos)
111
+ qt_process_deltas
112
+ qt_add_image(imgpath, pos)
113
+ end
114
+
115
+ def is_legal_pos(pos, op = :read)
116
+ return false if pos < 0
117
+ if op == :add
118
+ return false if pos > self.size
119
+ elsif op == :read
120
+ return false if pos >= self.size
121
+ end
122
+ return true
123
+ end
124
+
125
+ def set_encrypted(password)
126
+ @crypt = Encrypt.new(password)
127
+ message("Set buffer encrypted")
128
+ end
129
+
130
+ def set_unencrypted()
131
+ @crypt = nil
132
+ end
133
+
134
+ def add_new_line(txt)
135
+ # buf.jump(END_OF_LINE);buf.insert_txt("\n");
136
+ end
137
+
138
+ def insert_image_after_current_line(fname)
139
+ lr = current_line_range()
140
+ a = "⟦img:#{fname}⟧\n"
141
+ b = " \n"
142
+ txt = a + b
143
+ insert_txt_at(txt, lr.end + 1)
144
+ qt_process_deltas
145
+ imgpos = lr.end + 1 + a.size
146
+ add_image(fname, imgpos)
147
+ end
148
+
149
+ def handle_drag_and_drop(fname)
150
+ debug "[buffer] Dropped file: #{fname}"
151
+ if is_image_file(fname)
152
+ debug "Dropped image file"
153
+ insert_image_after_current_line(fname)
154
+ elsif vma.can_open_extension?(fname)
155
+ debug "Dropped text file"
156
+ open_new_file(fname)
157
+ else
158
+ debug "Dropped unknown file format"
159
+ end
160
+ # add_image(imgpath, pos)
161
+ end
162
+
163
+ def get_file_type()
164
+ return @lang
165
+ end
166
+
167
+ def revert()
168
+ return if !@fname
169
+ return if !File.exists?(@fname)
170
+ message("Revert buffer #{@fname}")
171
+ str = read_file("", @fname)
172
+ self.set_content(str)
173
+ end
174
+
175
+ def decrypt(password)
176
+ begin
177
+ @crypt = Encrypt.new(password)
178
+ str = @crypt.decrypt(@encrypted_str)
179
+ rescue OpenSSL::Cipher::CipherError => e
180
+ str = "incorrect password"
181
+ end
182
+ self.set_content(str)
183
+ end
184
+
185
+ def sanitycheck_btree()
186
+ # lines = self.split("\n")
187
+
188
+ lines = self.scan(/([^\n]*\n)/).flatten
189
+
190
+ i = 0
191
+ ok = true
192
+ @bt.each_line { |r|
193
+ if lines[i] != r #or true
194
+ puts "NO MATCH FOR LINE:"
195
+ puts "i=#{i}["
196
+ # puts "[orig]pos=#{leaf.pos} |#{leaf.data}|"
197
+ # puts "spos=#{spos} nchar=#{leaf.nchar} epos=#{epos} a[]=\nr=|#{r}|"
198
+ puts "fromtree:|#{r}|"
199
+ puts "frombuf:|#{lines[i]}"
200
+ puts "]"
201
+ ok = false
202
+ end
203
+ i += 1
204
+ }
205
+
206
+ puts "BT: NO ERRORS" if ok
207
+ puts "BT: ERRORS" if !ok
208
+ end
209
+
210
+ def set_content(str)
211
+ @encrypted_str = nil
212
+ @qt_update_highlight = true
213
+ @ftype = nil
214
+ if str[0..10] == "VMACRYPT001"
215
+ @encrypted_str = str[11..-1]
216
+ callback = proc { |x| decrypt_cur_buffer(x) }
217
+ gui_one_input_action("Decrypt", "Password:", "decrypt", callback)
218
+ str = "ENCRYPTED"
219
+ else
220
+ # @crypt = nil
221
+ end
222
+
223
+ if (str[-1] != "\n")
224
+ str << "\n"
225
+ end
226
+
227
+ self.replace(str)
228
+ @line_ends = scan_indexes(self, /\n/)
229
+
230
+ # @bt = BufferTree.new(str)
231
+ if $experimental
232
+ @bt = BufferTree.new(self)
233
+ if $debug
234
+ sanitycheck_btree()
235
+ end
236
+ end
237
+
238
+ @last_update = Time.now - 10
239
+ debug("line_ends")
240
+ @marks = Hash.new
241
+ @basename = ""
242
+ @pathname = Pathname.new(fname) if @fname
243
+ @basename = @pathname.basename if @fname
244
+ @pos = 0 # Position in whole file
245
+ @cpos = 0 # Column position on current line
246
+ @lpos = 0 # Number of current line
247
+ @edit_version = 0 # +1 for every buffer modification
248
+ @larger_cpos = 0 # Store @cpos when move up/down to shorter line than @cpos
249
+ @need_redraw = 1
250
+ @call_func = nil
251
+ @deltas = []
252
+ @edit_history = []
253
+ @redo_stack = []
254
+ @edit_pos_history = []
255
+ @edit_pos_history_i = 0
256
+ # @highlights = {}
257
+ @highlights.delete_if { |x| true }
258
+
259
+ @syntax_parser = nil
260
+
261
+ @is_highlighted = false
262
+ @update_highlight = true
263
+ @update_hl_startpos = 0 #TODO
264
+ @update_hl_endpos = self.size - 1
265
+
266
+ qt_set_buffer_contents(@id, self.to_s)
267
+
268
+ # add_hl_update(@update_hl_startpos, @update_hl_endpos)
269
+ end
270
+
271
+ def set_filename(filename)
272
+ @fname = filename
273
+ @pathname = Pathname.new(fname) if @fname
274
+ @basename = @pathname.basename if @fname
275
+ detect_file_language
276
+ end
277
+
278
+ def get_short_path()
279
+ fpath = self.fname
280
+ if fpath.size > 50
281
+ fpath = fpath[-50..-1]
282
+ end
283
+ return fpath
284
+ end
285
+
286
+ def line(lpos)
287
+ if @line_ends.size == 0
288
+ return self
289
+ end
290
+
291
+ #TODO: implement using line_range()
292
+ if lpos >= @line_ends.size
293
+ debug("lpos too large") #TODO
294
+ return ""
295
+ elsif lpos == @line_ends.size
296
+ end
297
+ start = @line_ends[lpos - 1] + 1 if lpos > 0
298
+ start = 0 if lpos == 0
299
+ _end = @line_ends[lpos]
300
+ debug "start: _#{start}, end: #{_end}"
301
+ return self[start.._end]
302
+ end
303
+
304
+ def is_delta_ok(delta)
305
+ ret = true
306
+ pos = delta[0]
307
+ if pos < 0
308
+ ret = false
309
+ debug "pos=#{pos} < 0"
310
+ elsif pos > self.size
311
+ debug "pos=#{pos} > self.size=#{self.size}"
312
+ ret = false
313
+ end
314
+ if ret == false
315
+ # crash("DELTA OK=#{ret}")
316
+ end
317
+ return ret
318
+ end
319
+
320
+ #TODO: change to apply=true as default
321
+ def add_delta(delta, apply = false, auto_update_cpos = false)
322
+ return if !is_delta_ok(delta)
323
+ if delta[1] == DELETE
324
+ return if delta[0] >= self.size
325
+ # If go over length of buffer
326
+ if delta[0] + delta[2] >= self.size
327
+ delta[2] = self.size - delta[0]
328
+ end
329
+ end
330
+
331
+ @edit_version += 1
332
+ @redo_stack = []
333
+ if apply
334
+ delta = run_delta(delta, auto_update_cpos)
335
+ else
336
+ @deltas << delta
337
+ end
338
+ @edit_history << delta
339
+ if self[-1] != "\n"
340
+ add_delta([self.size, INSERT, 1, "\n"], true)
341
+ end
342
+ reset_larger_cpos #TODO: correct here?
343
+ end
344
+
345
+ def add_hl_update(startpos, endpos)
346
+ return if @is_highlighted == false
347
+
348
+ debug "@update_hl_endpos = #{endpos}"
349
+ @hl_queue << [startpos, endpos]
350
+ end
351
+
352
+ def run_delta(delta, auto_update_cpos = false)
353
+ # 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.
354
+
355
+ if $experimental
356
+ @bt.handle_delta(Delta.new(delta[0], delta[1], delta[2], delta[3]))
357
+ end
358
+ pos = delta[0]
359
+ if @edit_pos_history.any? and (@edit_pos_history.last - pos).abs <= 2
360
+ @edit_pos_history.pop
361
+ end
362
+
363
+ lsp = get_line_start(pos)
364
+
365
+ if @edit_pos_history[-1] != lsp
366
+ @edit_pos_history << lsp
367
+ end
368
+ @edit_pos_history_i = 0
369
+
370
+ if delta[1] == DELETE
371
+ delta[3] = self.slice!(delta[0], delta[2])
372
+ @deltas << delta
373
+ update_index(pos, -delta[2])
374
+ update_line_ends(pos, -delta[2], delta[3])
375
+ update_highlights(pos, -delta[2], delta[3])
376
+ update_cursor_pos(pos, -delta[2]) if auto_update_cpos
377
+
378
+ @update_hl_startpos = pos - delta[2]
379
+ @update_hl_endpos = pos
380
+ add_hl_update(@update_hl_startpos, @update_hl_endpos)
381
+ elsif delta[1] == INSERT
382
+ self.insert(delta[0], delta[3])
383
+ @deltas << delta
384
+ debug [pos, +delta[2]].inspect
385
+ update_index(pos, +delta[2])
386
+ update_cursor_pos(pos, +delta[2]) if auto_update_cpos
387
+ update_line_ends(pos, +delta[2], delta[3])
388
+ update_highlights(pos, +delta[2], delta[3])
389
+
390
+ @update_hl_startpos = pos
391
+ @update_hl_endpos = pos + delta[2]
392
+ add_hl_update(@update_hl_startpos, @update_hl_endpos)
393
+ end
394
+ debug "DELTA=#{delta.inspect}"
395
+ # sanity_check_line_ends #TODO: enable with debug mode
396
+ #highlight_c()
397
+
398
+ $update_highlight = true
399
+ @update_highlight = true
400
+
401
+ return delta
402
+ end
403
+
404
+ # Update cursor position after change in buffer contents.
405
+ # e.g. after processing with external command like indenter
406
+ def update_cursor_pos(pos, changeamount)
407
+ if @pos > pos + 1 && changeamount > 0
408
+ # @pos is after addition
409
+ set_pos(@pos + changeamount)
410
+ elsif @pos > pos && changeamount < 0 && @pos < pos - changeamount
411
+ # @pos is between removal
412
+ set_pos(pos)
413
+ elsif @pos > pos && changeamount < 0 && @pos >= pos - changeamount
414
+ # @pos is after removal
415
+ set_pos(@pos + changeamount)
416
+ end
417
+ end
418
+
419
+ def update_index(pos, changeamount)
420
+ # puts "pos #{pos}, changeamount #{changeamount}, @pos #{@pos}"
421
+ @edit_pos_history.collect! { |x| r = x if x <= pos; r = x + changeamount if x > pos; r }
422
+ # TODO: handle between removal case
423
+ for k in @marks.keys
424
+ # puts "change(?): pos=#{pos}, k=#{k}, #{@marks[k]}, #{changeamount}"
425
+ if @marks[k] > pos
426
+ @marks[k] = @marks[k] + changeamount
427
+ end
428
+ end
429
+ end
430
+
431
+ def jump_to_last_edit()
432
+ return if @edit_pos_history.empty?
433
+ @edit_pos_history_i += 1
434
+
435
+ if @edit_pos_history_i > @edit_pos_history.size
436
+ @edit_pos_history_i = 0
437
+ end
438
+
439
+ # if @edit_pos_history.size >= @edit_pos_history_i
440
+ set_pos(@edit_pos_history[-@edit_pos_history_i])
441
+ center_on_current_line
442
+ return true
443
+ # end
444
+ end
445
+
446
+ def jump_to_next_edit()
447
+ return if @edit_pos_history.empty?
448
+ @edit_pos_history_i -= 1
449
+ @edit_pos_history_i = @edit_pos_history.size - 1 if @edit_pos_history_i < 0
450
+ # Ripl.start :binding => binding
451
+ debug "@edit_pos_history_i=#{@edit_pos_history_i}"
452
+ set_pos(@edit_pos_history[-@edit_pos_history_i])
453
+ center_on_current_line
454
+ return true
455
+ end
456
+
457
+ def jump_to_random_pos()
458
+ set_pos(rand(self.size))
459
+ end
460
+
461
+ def undo()
462
+ debug @edit_history.inspect
463
+ return if !@edit_history.any?
464
+ last_delta = @edit_history.pop
465
+ @redo_stack << last_delta
466
+ debug last_delta.inspect
467
+ if last_delta[1] == DELETE
468
+ d = [last_delta[0], INSERT, 0, last_delta[3]]
469
+ run_delta(d)
470
+ elsif last_delta[1] == INSERT
471
+ d = [last_delta[0], DELETE, last_delta[3].size]
472
+ run_delta(d)
473
+ else
474
+ return #TODO: assert?
475
+ end
476
+ set_pos(last_delta[0])
477
+ #recalc_line_ends #TODO: optimize?
478
+ calculate_line_and_column_pos
479
+ end
480
+
481
+ def redo()
482
+ return if !@redo_stack.any?
483
+ #last_delta = @edit_history[-1].pop
484
+ redo_delta = @redo_stack.pop
485
+ #printf("==== UNDO ====\n")
486
+ debug redo_delta.inspect
487
+ run_delta(redo_delta)
488
+ @edit_history << redo_delta
489
+ set_pos(redo_delta[0])
490
+ #recalc_line_ends #TODO: optimize?
491
+ calculate_line_and_column_pos
492
+ end
493
+
494
+ def current_char()
495
+ return self[@pos]
496
+ end
497
+
498
+ def current_line()
499
+ range = line_range(@lpos, 1)
500
+ return self[range]
501
+ end
502
+
503
+ def get_com_str()
504
+ # return nil if @syntax_detect_failed
505
+
506
+ com_str = nil
507
+ # if get_file_type() == "c" or get_file_type() == "java"
508
+ # com_str = "//"
509
+ # elsif get_file_type() == "ruby"
510
+ # com_str = "#"
511
+ # else
512
+ # com_str = "//"
513
+ # end
514
+
515
+ if !@lang_nfo.nil?
516
+ com_str = @lang_nfo.get_metadata("line-comment-start")
517
+ end
518
+
519
+ # lang.get_metadata("block-comment-start")
520
+ # lang.get_metadata("block-comment-end")
521
+
522
+ com_str = "//" if com_str.nil?
523
+
524
+ return com_str
525
+ end
526
+
527
+ def comment_linerange(r)
528
+ com_str = get_com_str()
529
+ #lines = $buffer[r].split(/(\n)/).each_slice(2).map { |x| x[0] }
530
+ lines = $buffer[r].lines
531
+ mod = ""
532
+ lines.each { |line|
533
+ m = line.match(/^(\s*)(\S.*)/)
534
+ if m == nil or m[2].size == 0
535
+ ret = line
536
+ elsif m[2].size > 0
537
+ ret = "#{m[1]}#{com_str} #{m[2]}\n"
538
+ end
539
+ mod << ret
540
+ }
541
+ replace_range(r, mod)
542
+ end
543
+
544
+ def get_line_start(pos)
545
+
546
+ # Bsearch: https://www.rubydoc.info/stdlib/core/Array#bsearch-instance_method
547
+ # In find-minimum mode (this is a good choice for typical use case), the block must return true or false, and there must be an index i (0 <= i <= ary.size) so that:
548
+ # the block returns false for any element whose index is less than i, and
549
+ # the block returns true for any element whose index is greater than or equal to i.
550
+ # This method returns the i-th element. If i is equal to ary.size, it returns nil.
551
+
552
+ # (OLD) slower version:
553
+ # ls = @line_ends.select { |x| x < pos }.max
554
+ a = @line_ends.bsearch_index { |x| x >= pos }
555
+
556
+ a = @line_ends[-1] if a == nil
557
+ a = 0 if a == nil
558
+ if a > 0
559
+ a = a - 1
560
+ else
561
+ a = 0
562
+ end
563
+ ls = nil
564
+ ls = @line_ends[a] if a != nil
565
+ # if a != nil and ls != @line_ends[a]
566
+ # puts "NO MATCH @line_ends[a]"
567
+ # Ripl.start :binding => binding
568
+ # end
569
+
570
+ if ls == nil
571
+ ls = 0
572
+ else
573
+ ls = ls + 1
574
+ end
575
+ return ls
576
+ end
577
+
578
+ def get_line_end(pos)
579
+ #Ripl.start :binding => binding
580
+ return @line_ends.select { |x| x > pos }.min
581
+ end
582
+
583
+ def comment_selection(op = :comment)
584
+ if visual_mode?
585
+ (startpos, endpos) = get_visual_mode_range2
586
+ first = get_line_start(startpos)
587
+ # last = get_line_end(endpos)
588
+ last = get_line_end(endpos - 1)
589
+ if op == :comment
590
+ comment_linerange(first..last)
591
+ elsif op == :uncomment
592
+ uncomment_linerange(first..last)
593
+ end
594
+ $buffer.end_visual_mode
595
+ end
596
+ end
597
+
598
+ def uncomment_linerange(r)
599
+ com_str = get_com_str()
600
+ #r=$buffer.line_range($buffer.lpos, 2)
601
+ lines = $buffer[r].split(/(\n)/).each_slice(2).map { |x| x[0] }
602
+ mod = lines.collect { |x| x.sub(/^(\s*)(#{com_str}\s?)/, '\1') + "\n" }.join()
603
+ replace_range(r, mod)
604
+ end
605
+
606
+ def get_repeat_num()
607
+ $method_handles_repeat = true
608
+ repeat_num = 1
609
+ if !$next_command_count.nil? and $next_command_count > 0
610
+ repeat_num = $next_command_count
611
+ end
612
+ return repeat_num
613
+ end
614
+
615
+ def comment_line(op = :comment)
616
+ num_lines = get_repeat_num()
617
+ lrange = line_range(@lpos, num_lines)
618
+ if op == :comment
619
+ comment_linerange(lrange)
620
+ elsif op == :uncomment
621
+ uncomment_linerange(lrange)
622
+ end
623
+ end
624
+
625
+ def replace_range(range, text)
626
+ delete_range(range.first, range.last)
627
+ insert_txt_at(text, range.begin)
628
+ end
629
+
630
+ def current_line_range()
631
+ range = line_range(@lpos, 1)
632
+ return range
633
+ end
634
+
635
+ def line_range(start_line, num_lines, include_last_nl = true)
636
+ end_line = start_line + num_lines - 1
637
+ if end_line >= @line_ends.size
638
+ debug("lpos too large") #TODO
639
+ end_line = @line_ends.size - 1
640
+ end
641
+ start = @line_ends[start_line - 1] + 1 if start_line > 0
642
+ start = 0 if start_line == 0
643
+ if include_last_nl
644
+ _End = @line_ends[end_line]
645
+ else
646
+ _End = @line_ends[end_line] - 1
647
+ end
648
+ _End = start if _End < start
649
+ debug "line range: start=#{start}, end=#{_End}"
650
+ return start.._End
651
+ end
652
+
653
+ def copy(range_id)
654
+ $paste_lines = false
655
+ debug "range_id: #{range_id}"
656
+ debug range_id.inspect
657
+ range = get_range(range_id)
658
+ debug range.inspect
659
+ set_clipboard(self[range])
660
+ end
661
+
662
+ def recalc_line_ends()
663
+ t1 = Time.now
664
+ leo = @line_ends.clone
665
+ @line_ends = scan_indexes(self, /\n/)
666
+ if @line_ends == leo
667
+ debug "No change to line ends"
668
+ else
669
+ debug "CHANGES to line ends"
670
+ end
671
+
672
+ debug "Scan line_end time: #{Time.now - t1}"
673
+ #puts @line_ends
674
+ end
675
+
676
+ def sanity_check_line_ends()
677
+ leo = @line_ends.clone
678
+ @line_ends = scan_indexes(self, /\n/)
679
+ if @line_ends == leo
680
+ debug "No change to line ends"
681
+ else
682
+ debug "CHANGES to line ends"
683
+ debug leo.inspect
684
+ debug @line_ends.inspect
685
+ crash("CHANGES to line ends")
686
+ end
687
+ end
688
+
689
+ def update_bufpos_on_change(positions, xpos, changeamount)
690
+ # puts "xpos=#{xpos} changeamount=#{changeamount}"
691
+ positions.collect { |x|
692
+ r = nil
693
+ r = x if x < xpos
694
+ r = x + changeamount if changeamount < 0 && x + changeamount >= xpos
695
+ r = x + changeamount if changeamount > 0 && x >= xpos
696
+ r
697
+ }
698
+ end
699
+
700
+ def update_highlights(pos, changeamount, changestr)
701
+ return if !self.is_highlighted # $cnf[:syntax_highlight]
702
+ lpos, cpos = get_line_and_col_pos(pos)
703
+ if @highlights and @highlights[lpos]
704
+ hl = @highlights[lpos]
705
+ hls = hl.collect { |x| x[0] } # highlight range start
706
+ hle = hl.collect { |x| x[1] } # highlight range end
707
+ hls2 = update_bufpos_on_change(hls, cpos, changeamount)
708
+ hle2 = update_bufpos_on_change(hle, cpos, changeamount)
709
+ hlnew = []
710
+ for i in hle.size.times
711
+ if hls2[i] != nil and hle2[i] != nil
712
+ hlnew << [hls2[i], hle2[i], hl[i][2]]
713
+ end
714
+ end
715
+ @highlights[lpos] = hlnew
716
+ end
717
+ end
718
+
719
+ def update_line_ends(pos, changeamount, changestr)
720
+ if changeamount > -1
721
+ changeamount = changestr.size
722
+ i_nl = scan_indexes(changestr, /\n/)
723
+ i_nl.collect! { |x| x + pos }
724
+ end
725
+ # puts "change:#{changeamount}"
726
+ #TODO: this is the bottle neck in insert_txt action
727
+ @line_ends.collect! { |x|
728
+ r = nil
729
+ r = x if x < pos
730
+ r = x + changeamount if changeamount < 0 && x + changeamount >= pos
731
+ r = x + changeamount if changeamount > 0 && x >= pos
732
+ r
733
+ }.compact!
734
+
735
+ if changeamount > -1 && i_nl.size > 0
736
+ @line_ends.concat(i_nl)
737
+ @line_ends.sort!
738
+ end
739
+ end
740
+
741
+ def at_end_of_line?()
742
+ return (self[@pos] == "\n" or at_end_of_buffer?)
743
+ end
744
+
745
+ def at_end_of_buffer?()
746
+ return @pos == self.size
747
+ end
748
+
749
+ def set_pos(new_pos)
750
+ if new_pos >= self.size
751
+ @pos = self.size - 1 # TODO:??right side of last char
752
+ elsif new_pos >= 0
753
+ @pos = new_pos
754
+ end
755
+ qt_set_cursor_pos(@id, @pos)
756
+ calculate_line_and_column_pos
757
+ end
758
+
759
+ # Get the line number of character position
760
+ def get_line_pos(pos)
761
+ lpos = @line_ends.bsearch_index { |x, _| x >= pos }
762
+ return lpos
763
+ end
764
+
765
+ # Calculate the two dimensional column and line positions based on
766
+ # (one dimensional) position in the buffer.
767
+ def get_line_and_col_pos(pos)
768
+ pos = self.size if pos > self.size
769
+ pos = 0 if pos < 0
770
+
771
+ lpos = get_line_pos(pos)
772
+
773
+ lpos = @line_ends.size if lpos == nil
774
+ cpos = pos
775
+ cpos -= @line_ends[lpos - 1] + 1 if lpos > 0
776
+
777
+ return [lpos, cpos]
778
+ end
779
+
780
+ def calculate_line_and_column_pos(reset = true)
781
+ @lpos, @cpos = get_line_and_col_pos(@pos)
782
+ reset_larger_cpos if reset
783
+ end
784
+
785
+ def set_line_and_column_pos(lpos, cpos, _reset_larger_cpos = true)
786
+ @lpos = lpos if !lpos.nil?
787
+ @cpos = cpos if !cpos.nil?
788
+ if @lpos > 0
789
+ new_pos = @line_ends[@lpos - 1] + 1
790
+ else
791
+ new_pos = 0
792
+ end
793
+
794
+ if @cpos > (line(@lpos).size - 1)
795
+ debug("$cpos too large: #{@cpos} #{@lpos}")
796
+ if @larger_cpos < @cpos
797
+ @larger_cpos = @cpos
798
+ end
799
+ @cpos = line(@lpos).size - 1
800
+ end
801
+ new_pos += @cpos
802
+ set_pos(new_pos)
803
+ reset_larger_cpos if _reset_larger_cpos
804
+ end
805
+
806
+ # Calculate the one dimensional array index based on column and line positions
807
+ def calculate_pos_from_cpos_lpos(reset = true)
808
+ set_line_and_column_pos(nil, nil)
809
+ end
810
+
811
+ def delete2(range_id)
812
+ $paste_lines = false
813
+ range = get_range(range_id)
814
+ return if range == nil
815
+ debug "RANGE"
816
+ debug range.inspect
817
+ debug range.inspect
818
+ debug "------"
819
+ delete_range(range.first, range.last)
820
+ pos = [range.first, @pos].min
821
+ set_pos(pos)
822
+ end
823
+
824
+ def delete(op)
825
+ $paste_lines = false
826
+ # Delete selection
827
+ if op == SELECTION && visual_mode?
828
+ (startpos, endpos) = get_visual_mode_range2
829
+ delete_range(startpos, endpos)
830
+ @pos = [@pos, @selection_start].min
831
+ end_visual_mode
832
+ #return
833
+
834
+ # Delete current char
835
+ elsif op == CURRENT_CHAR_FORWARD
836
+ return if @pos >= self.size - 1 # May not delete last '\n'
837
+ add_delta([@pos, DELETE, 1], true)
838
+
839
+ # Delete current char and then move backward
840
+ elsif op == CURRENT_CHAR_BACKWARD
841
+ add_delta([@pos, DELETE, 1], true)
842
+ @pos -= 1
843
+
844
+ # Delete the char before current char and move backward
845
+ elsif op == BACKWARD_CHAR and @pos > 0
846
+ add_delta([@pos - 1, DELETE, 1], true)
847
+ @pos -= 1
848
+ elsif op == FORWARD_CHAR #TODO: ok?
849
+ add_delta([@pos + 1, DELETE, 1], true)
850
+ end
851
+ set_pos(@pos)
852
+ #recalc_line_ends
853
+ calculate_line_and_column_pos
854
+ #need_redraw!
855
+ end
856
+
857
+ def delete_range(startpos, endpos)
858
+ #s = self.slice!(startpos..endpos)
859
+ set_clipboard(self[startpos..endpos])
860
+ add_delta([startpos, DELETE, (endpos - startpos + 1)], true)
861
+ #recalc_line_ends
862
+ calculate_line_and_column_pos
863
+ end
864
+
865
+ def get_range(range_id)
866
+ range = nil
867
+ if range_id == :to_word_end
868
+ wmarks = get_word_end_marks(@pos, @pos + 150)
869
+ if wmarks.any?
870
+ range = @pos..wmarks[0]
871
+ end
872
+ elsif range_id == :to_line_end
873
+ puts "TO LINE END"
874
+ range = @pos..(@line_ends[@lpos] - 1)
875
+ elsif range_id == :to_line_start
876
+ puts "TO LINE START: #{@lpos}"
877
+
878
+ if @cpos == 0
879
+ range = nil
880
+ else
881
+ if @lpos == 0
882
+ startpos = 0
883
+ else
884
+ startpos = @line_ends[@lpos - 1] + 1
885
+ end
886
+ endpos = @pos - 1
887
+ range = startpos..endpos
888
+ end
889
+ # range = startpos..(@pos - 1)
890
+ else
891
+ crash("INVALID RANGE")
892
+ end
893
+ return range if range == nil
894
+ if range.last < range.first
895
+ range.last = range.first
896
+ end
897
+ if range.first < 0
898
+ range.first = 0
899
+ end
900
+ if range.last >= self.size
901
+ range.last = self.size - 1
902
+ end
903
+ #TODO: sanity check
904
+ return range
905
+ end
906
+
907
+ def reset_larger_cpos()
908
+ @larger_cpos = @cpos
909
+ end
910
+
911
+ def move(direction)
912
+ puts "cpos:#{@cpos} lpos:#{@lpos} @larger_cpos:#{@larger_cpos}"
913
+ if direction == :forward_page
914
+ puts "FORWARD PAGE"
915
+ visible_range = get_visible_area()
916
+ set_pos(visible_range[1])
917
+ top_where_cursor()
918
+ end
919
+ if direction == :backward_page
920
+ puts "backward PAGE"
921
+ visible_range = get_visible_area()
922
+ set_pos(visible_range[0])
923
+ bottom_where_cursor()
924
+ end
925
+
926
+ if direction == FORWARD_CHAR
927
+ return if @pos >= self.size - 1
928
+ set_pos(@pos + 1)
929
+ end
930
+ if direction == BACKWARD_CHAR
931
+ set_pos(@pos - 1)
932
+ end
933
+ if direction == FORWARD_LINE
934
+ if @lpos >= @line_ends.size - 1 # Cursor is on last line
935
+ debug("ON LAST LINE")
936
+ return
937
+ else
938
+ @lpos += 1
939
+ end
940
+ end
941
+ if direction == BACKWARD_LINE
942
+ if @lpos == 0 # Cursor is on first line
943
+ return
944
+ else
945
+ @lpos -= 1
946
+ end
947
+ end
948
+
949
+ if direction == FORWARD_CHAR or direction == BACKWARD_CHAR
950
+ reset_larger_cpos
951
+ end
952
+
953
+ if direction == BACKWARD_LINE or direction == FORWARD_LINE
954
+ if @lpos > 0
955
+ new_pos = @line_ends[@lpos - 1] - 1
956
+ else
957
+ new_pos = 0
958
+ end
959
+
960
+ _line = self.line(@lpos)
961
+ if @cpos > (_line.size - 1)
962
+ debug("$cpos too large: #{@cpos} #{@lpos}")
963
+ if @larger_cpos < @cpos
964
+ @larger_cpos = @cpos
965
+ end
966
+ @cpos = line(@lpos).size - 1
967
+ end
968
+
969
+ if @larger_cpos > @cpos and @larger_cpos < (_line.size)
970
+ @cpos = @larger_cpos
971
+ elsif @larger_cpos > @cpos and @larger_cpos >= (_line.size)
972
+ @cpos = line(@lpos).size - 1
973
+ end
974
+
975
+ #new_pos += @cpos
976
+ #@pos = new_pos
977
+ calculate_pos_from_cpos_lpos(false)
978
+ end
979
+ end
980
+
981
+ def mark_current_position(mark_char)
982
+ @marks[mark_char] = @pos
983
+ end
984
+
985
+ # Get positions of last characters in words
986
+ def get_word_end_marks(startpos, endpos)
987
+ startpos = 0 if startpos < 0
988
+ endpos = self.size if endpos > self.size
989
+ search_str = self[(startpos)..(endpos)]
990
+ return if search_str == nil
991
+ wsmarks = scan_indexes(search_str, /(?<=\p{Word})[^\p{Word}]/)
992
+ wsmarks = wsmarks.collect { |x| x + startpos - 1 }
993
+ return wsmarks
994
+ end
995
+
996
+ # Get positions of first characters in words
997
+ def get_word_start_marks(startpos, endpos)
998
+ startpos = 0 if startpos < 0
999
+ endpos = self.size if endpos > self.size
1000
+ search_str = self[(startpos)..(endpos)]
1001
+ return if search_str == nil
1002
+ wsmarks = scan_indexes(search_str, /(?<=[^\p{Word}])\p{Word}/)
1003
+ wsmarks = wsmarks.collect { |x| x + startpos }
1004
+ return wsmarks
1005
+ end
1006
+
1007
+ def scan_marks(startpos, endpos, regstr, offset = 0)
1008
+ startpos = 0 if startpos < 0
1009
+ endpos = self.size if endpos > self.size
1010
+ search_str = self[(startpos)..(endpos)]
1011
+ return if search_str == nil
1012
+ marks = scan_indexes(search_str, regstr)
1013
+ marks = marks.collect { |x| x + startpos + offset }
1014
+ return marks
1015
+ end
1016
+
1017
+ def handle_word(wnfo)
1018
+ word = wnfo[0]
1019
+ wtype = wnfo[1]
1020
+ if wtype == :url
1021
+ open_url(word)
1022
+ elsif wtype == :linepointer
1023
+ puts word.inspect
1024
+ jump_to_file(word[0], word[1])
1025
+ elsif wtype == :textfile
1026
+ open_existing_file(word)
1027
+ elsif wtype == :file
1028
+ open_with_default_program(word)
1029
+ elsif wtype == :hpt_link
1030
+ open_existing_file(word)
1031
+ else
1032
+ #TODO
1033
+ end
1034
+ end
1035
+
1036
+ def context_menu_items()
1037
+ m = []
1038
+ if @visual_mode
1039
+ seltxt = get_current_selection
1040
+ m << ["Copy", self.method("copy_active_selection"), nil]
1041
+ m << ["Join lines", self.method("convert_selected_text"), :joinlines]
1042
+ # m << ["Sort", self.method("convert_selected_text"), :sortlines]
1043
+ m << ["Sort", method("call"), :sortlines]
1044
+ m << ["Filter: get numbers", method("call"), :getnums_on_lines]
1045
+ m << ["Delete selection", method("call"), :delete_selection]
1046
+
1047
+ # m << ["Search in dictionary", self.method("handle_word"), nil]
1048
+ # m << ["Search in google", self.method("handle_word"), nil]
1049
+ m << ["Execute in terminal", method("exec_in_terminal"), seltxt]
1050
+ else
1051
+ (word, wtype) = get_cur_nonwhitespace_word()
1052
+ if wtype == :url
1053
+ m << ["Open url", self.method("open_url"), word]
1054
+ elsif wtype == :linepointer
1055
+ m << ["Jump to line", self.method("handle_word"), [word, wtype]]
1056
+ elsif wtype == :textfile
1057
+ m << ["Open text file", self.method("handle_word"), [word, wtype]]
1058
+ elsif wtype == :file
1059
+ m << ["Open file (xdg-open)", self.method("handle_word"), [word, wtype]]
1060
+ elsif wtype == :hpt_link
1061
+ m << ["Jump to file", self.method("handle_word"), [word, wtype]]
1062
+ else
1063
+ # m << ["TODO", self.method("handle_word"), word]
1064
+ m << ["Paste", method("call"), :paste_after]
1065
+ end
1066
+ end
1067
+ return m
1068
+ end
1069
+
1070
+ # Activated when enter/return pressed
1071
+ def handle_line_action()
1072
+ if line_action_handler.class == Proc
1073
+ # Custom handler
1074
+ line_action_handler.call(lpos)
1075
+ else
1076
+ # Generic default action
1077
+ cur_nonwhitespace_word_action()
1078
+ end
1079
+ end
1080
+
1081
+ def get_cur_nonwhitespace_word()
1082
+ wem = scan_marks(@pos, @pos + 200, /(?<=\S)\s/, -1)
1083
+ wsm = scan_marks(@pos - 200, @pos, /((?<=\s)\S)|^\S/)
1084
+
1085
+ word_start = wsm[-1]
1086
+ word_end = wem[0]
1087
+ word_start = pos if word_start == nil
1088
+ word_end = pos if word_end == nil
1089
+ word = self[word_start..word_end]
1090
+ puts "'WORD: #{word}'"
1091
+ message("'#{word}'")
1092
+ linep = get_file_line_pointer(word)
1093
+ puts "linep'#{linep}'"
1094
+ path = File.expand_path(word)
1095
+ wtype = nil
1096
+ if is_url(word)
1097
+ wtype = :url
1098
+ elsif is_existing_file(path)
1099
+ message("PATH:'#{word}'")
1100
+ if vma.can_open_extension?(path)
1101
+ wtype = :textfile
1102
+ else
1103
+ wtype = :file
1104
+ end
1105
+ # elsif hpt_check_cur_word(word) #TODO: check only
1106
+ # puts word
1107
+ elsif linep != nil
1108
+ wtype = :linepointer
1109
+ word = linep
1110
+ else
1111
+ fn = hpt_check_cur_word(word)
1112
+ if !fn.nil?
1113
+ return [fn, :hpt_link]
1114
+ end
1115
+ end
1116
+ return [word, wtype]
1117
+ end
1118
+
1119
+ def cur_nonwhitespace_word_action()
1120
+
1121
+ # (word, wtype) = get_cur_nonwhitespace_word()
1122
+ wnfo = get_cur_nonwhitespace_word()
1123
+ handle_word(wnfo)
1124
+ end
1125
+
1126
+ def get_cur_word()
1127
+ wem = get_word_end_marks(@pos, @pos + 200)
1128
+ wsm = get_word_start_marks(@pos - 200, @pos)
1129
+ word_start = wsm[-1]
1130
+ word_end = wem[0]
1131
+ word_start = pos if word_start == nil
1132
+ word_end = pos if word_end == nil
1133
+ word = self[word_start..word_end]
1134
+ puts "'#{word}'"
1135
+ message("'#{word}'")
1136
+ #puts wm
1137
+ end
1138
+
1139
+ def jump_to_next_instance_of_word()
1140
+ if $kbd.last_action == $kbd.cur_action and @current_word != nil
1141
+ # puts "REPEATING *"
1142
+ else
1143
+ start_search = [@pos - 150, 0].max
1144
+
1145
+ search_str1 = self[start_search..(@pos)]
1146
+ wsmarks = scan_indexes(search_str1, /(?<=[^\p{Word}])\p{Word}/)
1147
+ a = wsmarks[-1]
1148
+ a = 0 if a == nil
1149
+
1150
+ search_str2 = self[(@pos)..(@pos + 150)]
1151
+ wemarks = scan_indexes(search_str2, /(?<=\p{Word})[^\p{Word}]/)
1152
+ b = wemarks[0]
1153
+ word_start = (@pos - search_str1.size + a + 1)
1154
+ word_start = 0 if !(word_start >= 0)
1155
+ @current_word = self[word_start..(@pos + b - 1)]
1156
+ end
1157
+
1158
+ #TODO: search for /[^\p{Word}]WORD[^\p{Word}]/
1159
+ position_of_next_word = self.index(@current_word, @pos + 1)
1160
+ if position_of_next_word != nil
1161
+ set_pos(position_of_next_word)
1162
+ else #Search from beginning
1163
+ position_of_next_word = self.index(@current_word)
1164
+ set_pos(position_of_next_word) if position_of_next_word != nil
1165
+ end
1166
+ center_on_current_line
1167
+ return true
1168
+ end
1169
+
1170
+ def jump_word(direction, wordpos)
1171
+ offset = 0
1172
+ if direction == FORWARD
1173
+ debug "POS: #{@pos},"
1174
+ search_str = self[(@pos)..(@pos + 250)]
1175
+ return if search_str == nil
1176
+ if wordpos == WORD_START # vim 'w'
1177
+ wsmarks = scan_indexes(search_str, /(?<=[^\p{Word}])\p{Word}|\Z/) # \Z = end of string, just before last newline.
1178
+ wsmarks2 = scan_indexes(search_str, /\n[ \t]*\n/) # "empty" lines that have whitespace
1179
+ wsmarks2 = wsmarks2.collect { |x| x + 1 }
1180
+ wsmarks = (wsmarks2 + wsmarks).sort.uniq
1181
+ offset = 0
1182
+ if wsmarks.any?
1183
+ next_pos = @pos + wsmarks[0] + offset
1184
+ set_pos(next_pos)
1185
+ end
1186
+ elsif wordpos == WORD_END
1187
+ search_str = self[(@pos + 1)..(@pos + 150)]
1188
+ wsmarks = scan_indexes(search_str, /(?<=\p{Word})[^\p{Word}]/)
1189
+ offset = -1
1190
+ if wsmarks.any?
1191
+ next_pos = @pos + 1 + wsmarks[0] + offset
1192
+ set_pos(next_pos)
1193
+ end
1194
+ end
1195
+ end
1196
+ if direction == BACKWARD # vim 'b'
1197
+ start_search = @pos - 150 #TODO 150 length limit
1198
+ start_search = 0 if start_search < 0
1199
+ search_str = self[start_search..(@pos - 1)]
1200
+ return if search_str == nil
1201
+ wsmarks = scan_indexes(search_str,
1202
+ #/(^|(\W)\w|\n)/) #TODO 150 length limit
1203
+ #/^|(?<=[^\p{Word}])\p{Word}|(?<=\n)\n/) #include empty lines?
1204
+ /\A|(?<=[^\p{Word}])\p{Word}/) # Start of string or nonword,word.
1205
+
1206
+ offset = 0
1207
+
1208
+ if wsmarks.any?
1209
+ next_pos = start_search + wsmarks.last + offset
1210
+ set_pos(next_pos)
1211
+ end
1212
+ end
1213
+ end
1214
+
1215
+ def jump_to_mark(mark_char)
1216
+ p = @marks[mark_char]
1217
+ set_pos(p) if p
1218
+ center_on_current_line
1219
+ return true
1220
+ end
1221
+
1222
+ def jump(target)
1223
+ if target == START_OF_BUFFER
1224
+ set_pos(0)
1225
+ end
1226
+ if target == END_OF_BUFFER
1227
+ set_pos(self.size - 1)
1228
+ end
1229
+ if target == BEGINNING_OF_LINE
1230
+ @cpos = 0
1231
+ calculate_pos_from_cpos_lpos
1232
+ end
1233
+ if target == END_OF_LINE
1234
+ @cpos = line(@lpos).size - 1
1235
+ calculate_pos_from_cpos_lpos
1236
+ end
1237
+
1238
+ if target == FIRST_NON_WHITESPACE
1239
+ l = current_line()
1240
+ puts l.inspect
1241
+ @cpos = line(@lpos).size - 1
1242
+ a = scan_indexes(l, /\S/)
1243
+ puts a.inspect
1244
+ if a.any?
1245
+ @cpos = a[0]
1246
+ else
1247
+ @cpos = 0
1248
+ end
1249
+ calculate_pos_from_cpos_lpos
1250
+ end
1251
+ end
1252
+
1253
+ def jump_to_line(line_n = 1)
1254
+
1255
+ # $method_handles_repeat = true
1256
+ # if !$next_command_count.nil? and $next_command_count > 0
1257
+ # line_n = $next_command_count
1258
+ # debug "jump to line:#{line_n}"
1259
+ # end
1260
+ debug "jump to line:#{line_n}"
1261
+ line_n = get_repeat_num() if line_n == 1
1262
+
1263
+ if line_n > @line_ends.size
1264
+ debug("lpos too large") #TODO
1265
+ return
1266
+ end
1267
+ if line_n == 1
1268
+ set_pos(0)
1269
+ else
1270
+ set_pos(@line_ends[line_n - 2] + 1)
1271
+ end
1272
+ end
1273
+
1274
+ def join_lines()
1275
+ if @lpos >= @line_ends.size - 1 # Cursor is on last line
1276
+ debug("ON LAST LINE")
1277
+ return
1278
+ else
1279
+ # TODO: replace all whitespace between lines with ' '
1280
+ jump(END_OF_LINE)
1281
+ delete(CURRENT_CHAR_FORWARD)
1282
+ #insert_txt(' ',AFTER)
1283
+ insert_txt(" ", BEFORE)
1284
+ end
1285
+ end
1286
+
1287
+ def jump_to_next_instance_of_char(char, direction = FORWARD)
1288
+
1289
+ #return if at_end_of_line?
1290
+ if direction == FORWARD
1291
+ position_of_next_char = self.index(char, @pos + 1)
1292
+ if position_of_next_char != nil
1293
+ @pos = position_of_next_char
1294
+ end
1295
+ elsif direction == BACKWARD
1296
+ start_search = @pos - 250
1297
+ start_search = 0 if start_search < 0
1298
+ search_substr = self[start_search..(@pos - 1)]
1299
+ _pos = search_substr.reverse.index(char)
1300
+ if _pos != nil
1301
+ @pos -= (_pos + 1)
1302
+ end
1303
+ end
1304
+ m = method("jump_to_next_instance_of_char")
1305
+ set_last_command({ method: m, params: [char, direction] })
1306
+ $last_find_command = { char: char, direction: direction }
1307
+ set_pos(@pos)
1308
+ end
1309
+
1310
+ def replace_with_char(char)
1311
+ debug "self_pos:'#{self[@pos]}'"
1312
+ return if self[@pos] == "\n"
1313
+ d1 = [@pos, DELETE, 1]
1314
+ d2 = [@pos, INSERT, 1, char]
1315
+ add_delta(d1, true)
1316
+ add_delta(d2, true)
1317
+ debug "DELTAS:#{$buffer.deltas.inspect} "
1318
+ end
1319
+
1320
+ def insert_txt_at(c, pos)
1321
+ c = c.force_encoding("UTF-8"); #TODO:correct?
1322
+ c = "\n" if c == "\r"
1323
+ add_delta([pos, INSERT, c.size, c], true)
1324
+ calculate_line_and_column_pos
1325
+ end
1326
+
1327
+ def execute_current_line_in_terminal()
1328
+ s = get_current_line
1329
+ exec_in_terminal(s)
1330
+ end
1331
+
1332
+ def insert_new_line()
1333
+ s = get_current_line
1334
+ $hook.call(:insert_new_line, s)
1335
+ insert_txt("\n")
1336
+ # message("foo")
1337
+ end
1338
+
1339
+ def insert_txt(c, mode = BEFORE)
1340
+ # start_profiler
1341
+ #Sometimes we get ASCII-8BIT although actually UTF-8 "incompatible character encodings: UTF-8 and ASCII-8BIT (Encoding::CompatibilityError)"
1342
+ c = c.force_encoding("UTF-8"); #TODO:correct?
1343
+
1344
+ c = "\n" if c == "\r"
1345
+ if $cnf[:indent_based_on_last_line] and c == "\n" and @lpos > 0
1346
+ # Indent start of new line based on last line
1347
+ last_line = line(@lpos)
1348
+ m = /^( +)([^ ]+|$)/.match(last_line)
1349
+ debug m.inspect
1350
+ c = c + " " * m[1].size if m
1351
+ end
1352
+ if mode == BEFORE
1353
+ insert_pos = @pos
1354
+ @pos += c.size
1355
+ elsif mode == AFTER
1356
+ insert_pos = @pos + 1
1357
+ else
1358
+ return
1359
+ end
1360
+
1361
+ #self.insert(insert_pos,c)
1362
+ add_delta([insert_pos, INSERT, c.size, c], true)
1363
+ #puts("encoding: #{c.encoding}")
1364
+ #puts "c.size: #{c.size}"
1365
+ #recalc_line_ends #TODO: optimize?
1366
+ calculate_line_and_column_pos
1367
+ #need_redraw!
1368
+ #@pos += c.size
1369
+ # end_profiler
1370
+ end
1371
+
1372
+ # Update buffer contents to newstr
1373
+ # Change by taking diff of old/new content
1374
+ def update_content(newstr)
1375
+ diff = Differ.diff_by_char(newstr, self.to_s)
1376
+
1377
+ da = diff.get_raw_array
1378
+
1379
+ pos = 0
1380
+ posA = 0
1381
+ posB = 0
1382
+ deltas = []
1383
+
1384
+ for x in da
1385
+ if x.class == String
1386
+ posA += x.size
1387
+ posB += x.size
1388
+ elsif x.class == Differ::Change
1389
+ if x.insert?
1390
+ deltas << [posB, INSERT, x.insert.size, x.insert.clone]
1391
+ posB += x.insert.size
1392
+ elsif x.delete?
1393
+ posA += x.delete.size
1394
+ deltas << [posB, DELETE, x.delete.size]
1395
+ end
1396
+ end
1397
+ end
1398
+ for d in deltas
1399
+ add_delta(d, true, true)
1400
+ end
1401
+ # $buffer.update_content(IO.read('test.txt'))
1402
+ end
1403
+
1404
+ def need_redraw!
1405
+ @need_redraw = true
1406
+ end
1407
+
1408
+ def need_redraw?
1409
+ return @need_redraw
1410
+ end
1411
+
1412
+ def set_redrawed
1413
+ @need_redraw = false
1414
+ end
1415
+
1416
+ # Create a new line after current line and insert text on that line
1417
+ def put_to_new_next_line(txt)
1418
+ l = current_line_range()
1419
+ insert_txt_at(txt, l.end + 1)
1420
+ set_pos(l.end + 1)
1421
+ end
1422
+
1423
+ def paste(at = AFTER, register = nil)
1424
+ # Paste after current char. Except if at end of line, paste before end of line.
1425
+ text = ""
1426
+ if register.nil?
1427
+ text = paste_system_clipboard
1428
+ end
1429
+
1430
+ if text == ""
1431
+ return if !$clipboard.any?
1432
+ if register == nil
1433
+ text = $clipboard[-1]
1434
+ else
1435
+ text = $register[register]
1436
+ end
1437
+ end
1438
+ puts "PASTE: #{text}"
1439
+
1440
+ return if text == ""
1441
+
1442
+ if $paste_lines
1443
+ debug "PASTE LINES"
1444
+ put_to_new_next_line(text)
1445
+ else
1446
+ if at_end_of_buffer? or at_end_of_line? or at == BEFORE
1447
+ pos = @pos
1448
+ else
1449
+ pos = @pos + 1
1450
+ end
1451
+ insert_txt_at(text, pos)
1452
+ set_pos(pos + text.size)
1453
+ end
1454
+ set_pos(@pos)
1455
+ #TODO: AFTER does not work
1456
+ #insert_txt($clipboard[-1],AFTER)
1457
+ #recalc_line_ends #TODO: bug when run twice?
1458
+ end
1459
+
1460
+ def delete_line()
1461
+ $method_handles_repeat = true
1462
+ num_lines = 1
1463
+ if !$next_command_count.nil? and $next_command_count > 0
1464
+ num_lines = $next_command_count
1465
+ debug "copy num_lines:#{num_lines}"
1466
+ end
1467
+ lrange = line_range(@lpos, num_lines)
1468
+ s = self[lrange]
1469
+ add_delta([lrange.begin, DELETE, lrange.end - lrange.begin + 1], true)
1470
+ set_clipboard(s)
1471
+ update_pos(lrange.begin)
1472
+ $paste_lines = true
1473
+ #recalc_line_ends
1474
+ end
1475
+
1476
+ def update_pos(pos)
1477
+ @pos = pos
1478
+ calculate_line_and_column_pos
1479
+ end
1480
+
1481
+ def start_visual_mode()
1482
+ @visual_mode = true
1483
+ @selection_start = @pos
1484
+ qt_set_selection_start(@id, selection_start)
1485
+ $kbd.set_mode(:visual)
1486
+ end
1487
+
1488
+ def copy_active_selection(x = nil)
1489
+ debug "!COPY SELECTION"
1490
+ $paste_lines = false
1491
+ return if !@visual_mode
1492
+
1493
+ debug "COPY SELECTION"
1494
+ set_clipboard(self[get_visual_mode_range])
1495
+ end_visual_mode
1496
+ return true
1497
+ end
1498
+
1499
+ def transform_selection(op)
1500
+ return if !@visual_mode
1501
+ r = get_visual_mode_range
1502
+ txt = self[r]
1503
+ txt.upcase! if op == :upcase
1504
+ txt.downcase! if op == :downcase
1505
+ txt.gsub!(/\w+/, &:capitalize) if op == :capitalize
1506
+ txt.swapcase! if op == :swapcase
1507
+ txt.reverse! if op == :reverse
1508
+
1509
+ replace_range(r, txt)
1510
+ end_visual_mode
1511
+ end
1512
+
1513
+ def convert_selected_text(converter_id)
1514
+ return if !@visual_mode
1515
+ r = get_visual_mode_range
1516
+ txt = self[r]
1517
+ txt = $vma.apply_conv(converter_id, txt)
1518
+ #TODO: Detect if changed?
1519
+ replace_range(r, txt)
1520
+ end_visual_mode
1521
+ end
1522
+
1523
+ def style_transform(op)
1524
+ return if !@visual_mode
1525
+ r = get_visual_mode_range
1526
+ #TODO: if txt[-1]=="\n"
1527
+ txt = self[r]
1528
+ txt = "⦁" + txt + "⦁" if op == :bold
1529
+ txt = "⟦" + txt + "⟧" if op == :link
1530
+ txt = "❙" + txt + "❙" if op == :title
1531
+ txt.gsub!(/[❙◼⟦⟧⦁]/, "") if op == :clear
1532
+
1533
+ replace_range(r, txt)
1534
+ end_visual_mode
1535
+ end
1536
+
1537
+ def set_line_style(op)
1538
+ lrange = line_range(@lpos, 1, false)
1539
+ txt = self[lrange]
1540
+ # txt = "◼ " + txt if op == :heading
1541
+ txt = "⦁" + txt + "⦁" if op == :bold
1542
+ txt = "❙" + txt + "❙" if op == :title
1543
+ txt.gsub!(/◼ /, "") if op == :clear
1544
+ txt.gsub!(/[❙◼⟦⟧⦁]/, "") if op == :clear or [:h1, :h2, :h3, :h4].include?(op)
1545
+
1546
+ if [:h1, :h2, :h3, :h4].include?(op)
1547
+ txt.strip!
1548
+ txt = "◼ " + txt if op == :h1
1549
+ txt = "◼◼ " + txt if op == :h2
1550
+ txt = "◼◼◼ " + txt if op == :h3
1551
+ txt = "◼◼◼◼ " + txt if op == :h4
1552
+ end
1553
+ replace_range(lrange, txt)
1554
+ end
1555
+
1556
+ def copy_line()
1557
+ $method_handles_repeat = true
1558
+ num_lines = 1
1559
+ if !$next_command_count.nil? and $next_command_count > 0
1560
+ num_lines = $next_command_count
1561
+ debug "copy num_lines:#{num_lines}"
1562
+ end
1563
+ set_clipboard(self[line_range(@lpos, num_lines)])
1564
+ $paste_lines = true
1565
+ end
1566
+
1567
+ def put_file_path_to_clipboard
1568
+ set_clipboard(self.fname)
1569
+ end
1570
+
1571
+ def delete_active_selection() #TODO: remove this function
1572
+ return if !@visual_mode #TODO: this should not happen
1573
+
1574
+ _start, _end = get_visual_mode_range
1575
+ set_clipboard(self[_start, _end])
1576
+ end_visual_mode
1577
+ end
1578
+
1579
+ def end_visual_mode()
1580
+ debug "End visual mode"
1581
+ #TODO:take previous mode (insert|command) from stack?
1582
+ $kbd.set_mode(:command)
1583
+ @visual_mode = false
1584
+ return true
1585
+ end
1586
+
1587
+ def get_visual_mode_range2()
1588
+ r = get_visual_mode_range
1589
+ return [r.begin, r.end]
1590
+ end
1591
+
1592
+ def get_current_line
1593
+ s = self[line_range(@lpos, 1)]
1594
+ return s
1595
+ end
1596
+
1597
+ def get_current_selection()
1598
+ return "" if !@visual_mode
1599
+ return self[get_visual_mode_range]
1600
+ end
1601
+
1602
+ def get_visual_mode_range()
1603
+ _start = @selection_start
1604
+ _end = @pos
1605
+
1606
+ _start, _end = _end, _start if _start > _end
1607
+ _end = _end + 1 if _start < _end
1608
+
1609
+ return _start..(_end - 1)
1610
+ end
1611
+
1612
+ def selection_start()
1613
+ return -1 if !@visual_mode
1614
+ return @selection_start if @visual_mode
1615
+ end
1616
+
1617
+ def visual_mode?()
1618
+ return @visual_mode
1619
+ end
1620
+
1621
+ def value()
1622
+ return self.to_s
1623
+ end
1624
+
1625
+ def save_as()
1626
+ debug "save_as"
1627
+ savepath = ""
1628
+
1629
+ # If current file has fname, save to that fname
1630
+ # Else search for previously open files and save to the directory of
1631
+ # the last viewed file that has a filename
1632
+ # $buffers[$buffer_history.reverse[1]].fname
1633
+
1634
+ if @fname
1635
+ savepath = File.dirname(@fname)
1636
+ else
1637
+ savepath = buflist.get_last_dir
1638
+ end
1639
+ # Ripl.start :binding => binding
1640
+ qt_file_saveas(savepath)
1641
+ # calls back to file_saveas
1642
+ # TODO:?
1643
+ end
1644
+
1645
+ def write_contents_to_file(fpath)
1646
+ if @crypt != nil
1647
+ mode = "wb+"
1648
+ contents = "VMACRYPT001" + @crypt.encrypt(self.to_s)
1649
+ else
1650
+ mode = "w+"
1651
+ contents = self.to_s
1652
+ end
1653
+
1654
+ Thread.new {
1655
+ File.open(fpath, mode) do |io|
1656
+ #io.set_encoding(self.encoding)
1657
+
1658
+ begin
1659
+ io.write(contents)
1660
+ rescue Encoding::UndefinedConversionError => ex
1661
+ # this might happen when trying to save UTF-8 as US-ASCII
1662
+ # so just warn, try to save as UTF-8 instead.
1663
+ warn("Saving as UTF-8 because of: #{ex.class}: #{ex}")
1664
+ io.rewind
1665
+
1666
+ io.set_encoding(Encoding::UTF_8)
1667
+ io.write(contents)
1668
+ #self.encoding = Encoding::UTF_8
1669
+ end
1670
+ end
1671
+ sleep 3
1672
+ }
1673
+ end
1674
+
1675
+ def save()
1676
+ if !@fname
1677
+ save_as()
1678
+ return
1679
+ end
1680
+ message("Saving file #{@fname}")
1681
+ write_contents_to_file(@fname)
1682
+ end
1683
+
1684
+ # Indents whole buffer using external program
1685
+ def indent()
1686
+ file = Tempfile.new("out")
1687
+ infile = Tempfile.new("in")
1688
+ file.write($buffer.to_s)
1689
+ file.flush
1690
+ bufc = "FOO"
1691
+
1692
+ tmppos = @pos
1693
+
1694
+ message("Auto format #{@fname}")
1695
+
1696
+ if ["chdr", "c", "cpp"].include?(get_file_type())
1697
+
1698
+ #C/C++/Java/JavaScript/Objective-C/Protobuf code
1699
+ system("clang-format -style='{BasedOnStyle: LLVM, ColumnLimit: 100, SortIncludes: false}' #{file.path} > #{infile.path}")
1700
+ bufc = IO.read(infile.path)
1701
+ elsif get_file_type() == "Javascript"
1702
+ cmd = "clang-format #{file.path} > #{infile.path}'"
1703
+ debug cmd
1704
+ system(cmd)
1705
+ bufc = IO.read(infile.path)
1706
+ elsif get_file_type() == "ruby"
1707
+ cmd = "rufo #{file.path}"
1708
+ debug cmd
1709
+ system(cmd)
1710
+ bufc = IO.read(file.path)
1711
+ else
1712
+ return
1713
+ end
1714
+ $buffer.update_content(bufc)
1715
+ center_on_current_line #TODO: needed?
1716
+ file.close; file.unlink
1717
+ infile.close; infile.unlink
1718
+ end
1719
+
1720
+ def backup()
1721
+ fname = @fname
1722
+ return if !@fname
1723
+ message("Backup buffer #{fname}")
1724
+ spfx = fname.gsub("=", "==").gsub("/", "=:")
1725
+ spath = File.expand_path("~/autosave")
1726
+ return false if !can_save_to_directory?(spath)
1727
+ datetime = DateTime.now().strftime("%d%m%Y:%H%M%S")
1728
+ savepath = "#{spath}/#{spfx}_#{datetime}"
1729
+ if is_path_writable(savepath)
1730
+ debug "BACKUP BUFFER TO: #{savepath}"
1731
+ write_contents_to_file(savepath)
1732
+ else
1733
+ message("PATH NOT WRITABLE: #{savepath}")
1734
+ end
1735
+ end
1736
+ end
1737
+
1738
+ #TODO
1739
+ def write_to_file(savepath, s)
1740
+ if is_path_writable(savepath)
1741
+ IO.write(savepath, $buffer.to_s)
1742
+ else
1743
+ message("PATH NOT WRITABLE: #{savepath}")
1744
+ end
1745
+ end
1746
+
1747
+ def is_path_writable(fpath)
1748
+ r = false
1749
+ if fpath.class == String
1750
+ r = true if File.writable?(Pathname.new(fpath).dirname)
1751
+ end
1752
+ return r
1753
+ end
1754
+
1755
+ def backup_all_buffers()
1756
+ for buf in $buffers
1757
+ buf.backup
1758
+ end
1759
+ message("Backup all buffers")
1760
+ end