textbringer 0.1.0

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,1367 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nkf"
4
+ require "unicode/display_width"
5
+
6
+ module Textbringer
7
+ class Buffer
8
+ extend Enumerable
9
+
10
+ attr_accessor :mode, :keymap
11
+ attr_reader :name, :file_name, :file_encoding, :file_format, :point, :marks
12
+ attr_reader :current_line, :current_column, :visible_mark
13
+
14
+ GAP_SIZE = 256
15
+ UNDO_LIMIT = 1000
16
+
17
+ UTF8_CHAR_LEN = Hash.new(1)
18
+ [
19
+ [0xc0..0xdf, 2],
20
+ [0xe0..0xef, 3],
21
+ [0xf0..0xf4, 4]
22
+ ].each do |range, len|
23
+ range.each do |c|
24
+ UTF8_CHAR_LEN[c.chr] = len
25
+ end
26
+ end
27
+
28
+ @@auto_detect_encodings = [
29
+ Encoding::UTF_8,
30
+ Encoding::EUC_JP,
31
+ Encoding::Windows_31J
32
+ ]
33
+
34
+ DEFAULT_DETECT_ENCODING = ->(s) {
35
+ @@auto_detect_encodings.find { |e|
36
+ s.force_encoding(e)
37
+ s.valid_encoding?
38
+ }
39
+ }
40
+
41
+ NKF_DETECT_ENCODING = ->(s) {
42
+ e = NKF.guess(s)
43
+ e == Encoding::US_ASCII ? Encoding::UTF_8 : e
44
+ }
45
+
46
+ @@detect_encoding_proc = DEFAULT_DETECT_ENCODING
47
+
48
+ @@table = {}
49
+ @@list = []
50
+ @@current = nil
51
+ @@minibuffer = nil
52
+
53
+ def self.auto_detect_encodings
54
+ @@auto_detect_encodings
55
+ end
56
+
57
+ def self.auto_detect_encodings=(encodings)
58
+ @@auto_detect_encodings = encodings
59
+ end
60
+
61
+ def self.detect_encoding_proc
62
+ @@detect_encoding_proc
63
+ end
64
+
65
+ def self.detect_encoding_proc=(f)
66
+ @@detect_encoding_proc = f
67
+ end
68
+
69
+ def self.add(buffer)
70
+ @@table[buffer.name] = buffer
71
+ @@list.unshift(buffer)
72
+ end
73
+
74
+ def self.current
75
+ @@current
76
+ end
77
+
78
+ def self.current=(buffer)
79
+ if buffer && buffer.name && @@table.key?(buffer.name)
80
+ @@list.delete(buffer)
81
+ @@list.push(buffer)
82
+ end
83
+ @@current = buffer
84
+ end
85
+
86
+ def self.minibuffer
87
+ @@minibuffer ||= Buffer.new(name: "*Minibuffer*")
88
+ end
89
+
90
+ def self.last
91
+ if @@list.last == @@current
92
+ @@list[-2]
93
+ else
94
+ @@list.last
95
+ end
96
+ end
97
+
98
+ def self.count
99
+ @@table.size
100
+ end
101
+
102
+ def self.[](name)
103
+ @@table[name]
104
+ end
105
+
106
+ def self.find_or_new(name, **opts)
107
+ @@table[name] ||= new_buffer(name, **opts)
108
+ end
109
+
110
+ def self.names
111
+ @@table.keys
112
+ end
113
+
114
+ def self.kill_em_all
115
+ @@table.clear
116
+ @@list.clear
117
+ @@current = nil
118
+ end
119
+
120
+ def self.find_file(file_name)
121
+ file_name = File.expand_path(file_name)
122
+ buffer = @@table.each_value.find { |buffer|
123
+ buffer.file_name == file_name
124
+ }
125
+ if buffer.nil?
126
+ name = File.basename(file_name)
127
+ begin
128
+ buffer = Buffer.open(file_name, name: new_buffer_name(name))
129
+ add(buffer)
130
+ rescue Errno::ENOENT
131
+ buffer = new_buffer(name, file_name: file_name)
132
+ end
133
+ end
134
+ buffer
135
+ end
136
+
137
+ def self.new_buffer(name, **opts)
138
+ buffer = Buffer.new(**opts.merge(name: new_buffer_name(name)))
139
+ add(buffer)
140
+ buffer
141
+ end
142
+
143
+ def self.new_buffer_name(name)
144
+ if @@table.key?(name)
145
+ (2..Float::INFINITY).lazy.map { |i|
146
+ "#{name}<#{i}>"
147
+ }.find { |i| !@@table.key?(i) }
148
+ else
149
+ name
150
+ end
151
+ end
152
+
153
+ def self.each(&block)
154
+ @@table.each_value(&block)
155
+ end
156
+
157
+ def self.display_width(s)
158
+ # ncurses seems to treat ambiguous east asian characters as narrow.
159
+ Unicode::DisplayWidth.of(s, 1)
160
+ end
161
+
162
+ # s might not be copied.
163
+ def initialize(s = String.new, name: nil,
164
+ file_name: nil, file_encoding: Encoding::UTF_8,
165
+ file_mtime: nil, new_file: true, undo_limit: UNDO_LIMIT,
166
+ read_only: false)
167
+ case s.encoding
168
+ when Encoding::UTF_8, Encoding::ASCII_8BIT
169
+ @contents = s.frozen? ? s.dup : s
170
+ else
171
+ @contents = s.encode(Encoding::UTF_8)
172
+ end
173
+ @contents.force_encoding(Encoding::ASCII_8BIT)
174
+ @name = name
175
+ @file_name = file_name
176
+ self.file_encoding = file_encoding
177
+ @file_mtime = file_mtime
178
+ case @contents
179
+ when /(?<!\r)\n/
180
+ @file_format = :unix
181
+ when /\r(?!\n)/
182
+ @file_format = :mac
183
+ @contents.gsub!(/\r/, "\n")
184
+ when /\r\n/
185
+ @file_format = :dos
186
+ @contents.gsub!(/\r/, "")
187
+ else
188
+ @file_format = :unix
189
+ end
190
+ @new_file = new_file
191
+ @undo_limit = undo_limit
192
+ @point = 0
193
+ @gap_start = 0
194
+ @gap_end = 0
195
+ @marks = []
196
+ @mark = nil
197
+ @current_line = 1
198
+ @current_column = 1 # One-based character count
199
+ @goal_column = nil # Zero-based display width count
200
+ @yank_start = new_mark
201
+ @undo_stack = []
202
+ @redo_stack = []
203
+ @undoing = false
204
+ @version = 0
205
+ @modified = false
206
+ @mode = nil
207
+ @keymap = nil
208
+ @attributes = {}
209
+ @save_point_level = 0
210
+ @match_offsets = []
211
+ @visible_mark = nil
212
+ @read_only = read_only
213
+ end
214
+
215
+ def inspect
216
+ "#<Buffer:#{@name || '0x%x' % object_id}>"
217
+ end
218
+
219
+ def name=(name)
220
+ if @@table[@name] == self
221
+ @@table.delete(@name)
222
+ @name = Buffer.new_buffer_name(name)
223
+ @@table[@name] = self
224
+ else
225
+ @name = name
226
+ end
227
+ end
228
+
229
+ def file_name=(file_name)
230
+ @file_name = file_name
231
+ basename = File.basename(file_name)
232
+ if /\A#{Regexp.quote(basename)}(<\d+>)?\z/ !~ name
233
+ self.name = basename
234
+ end
235
+ end
236
+
237
+ def file_encoding=(enc)
238
+ @file_encoding = enc
239
+ @binary = enc == Encoding::ASCII_8BIT
240
+ end
241
+
242
+ def binary?
243
+ @binary
244
+ end
245
+
246
+ def file_format=(format)
247
+ case format
248
+ when /\Aunix\z/i
249
+ @file_format = :unix
250
+ when /\Ados\z/i
251
+ @file_format = :dos
252
+ when /\Amac\z/i
253
+ @file_format = :mac
254
+ else
255
+ raise ArgumentError, "Unknown file format: #{format}"
256
+ end
257
+ end
258
+
259
+ def read_only?
260
+ @read_only
261
+ end
262
+
263
+ def read_only=(value)
264
+ @read_only = value
265
+ if @read_only
266
+ @modified = false
267
+ end
268
+ end
269
+
270
+ def kill
271
+ @@table.delete(@name)
272
+ @@list.delete(self)
273
+ if @@current == self
274
+ @@current = nil
275
+ end
276
+ end
277
+
278
+ def current?
279
+ @@current == self
280
+ end
281
+
282
+ def modified?
283
+ @modified
284
+ end
285
+
286
+ def [](name)
287
+ if @attributes.key?(name)
288
+ @attributes[name]
289
+ else
290
+ CONFIG[name]
291
+ end
292
+ end
293
+
294
+ def []=(name, value)
295
+ @attributes[name] = value
296
+ end
297
+
298
+ def new_file?
299
+ @new_file
300
+ end
301
+
302
+ def self.open(file_name, name: File.basename(file_name))
303
+ s, mtime = File.open(file_name) { |f|
304
+ f.flock(File::LOCK_SH)
305
+ [f.read, f.mtime]
306
+ }
307
+ enc = @@detect_encoding_proc.call(s) || Encoding::ASCII_8BIT
308
+ s.force_encoding(enc)
309
+ unless s.valid_encoding?
310
+ enc = Encoding::ASCII_8BIT
311
+ s.force_encoding(enc)
312
+ end
313
+ Buffer.new(s, name: name,
314
+ file_name: file_name, file_encoding: enc, file_mtime: mtime,
315
+ new_file: false, read_only: !File.writable?(file_name))
316
+ end
317
+
318
+ def save(file_name = @file_name)
319
+ if file_name.nil?
320
+ raise EditorError, "File name is not set"
321
+ end
322
+ file_name = File.expand_path(file_name)
323
+ begin
324
+ File.open(file_name, "w", external_encoding: @file_encoding) do |f|
325
+ f.flock(File::LOCK_EX)
326
+ write_to_file(f)
327
+ f.flush
328
+ @file_mtime = f.mtime
329
+ end
330
+ rescue Errno::EISDIR
331
+ if @name
332
+ file_name = File.expand_path(@name, file_name)
333
+ retry
334
+ else
335
+ raise
336
+ end
337
+ end
338
+ if file_name != @file_name
339
+ self.file_name = file_name
340
+ end
341
+ @version += 1
342
+ @modified = false
343
+ @new_file = false
344
+ @read_only = false
345
+ end
346
+
347
+ def file_modified?
348
+ !@file_mtime.nil? && File.mtime(@file_name) != @file_mtime
349
+ end
350
+
351
+ def to_s
352
+ result = (@contents[0...@gap_start] + @contents[@gap_end..-1])
353
+ result.force_encoding(Encoding::UTF_8) unless @binary
354
+ result
355
+ end
356
+
357
+ def substring(s, e)
358
+ result =
359
+ if s > @gap_start || e <= @gap_start
360
+ @contents[user_to_gap(s)...user_to_gap(e)]
361
+ else
362
+ len = @gap_start - s
363
+ @contents[user_to_gap(s), len] + @contents[@gap_end, e - s - len]
364
+ end
365
+ result.force_encoding(Encoding::UTF_8) unless @binary
366
+ result
367
+ end
368
+
369
+ def byte_after(location = @point)
370
+ if location < @gap_start
371
+ @contents.byteslice(location)
372
+ else
373
+ @contents.byteslice(location + gap_size)
374
+ end
375
+ end
376
+
377
+ def char_after(location = @point)
378
+ if @binary
379
+ byte_after(location)
380
+ else
381
+ s = substring(location, location + UTF8_CHAR_LEN[byte_after(location)])
382
+ s.empty? ? nil : s
383
+ end
384
+ end
385
+
386
+ def bytesize
387
+ @contents.bytesize - gap_size
388
+ end
389
+ alias size bytesize
390
+
391
+ def point_min
392
+ 0
393
+ end
394
+
395
+ def point_max
396
+ bytesize
397
+ end
398
+
399
+ def get_line_and_column(pos)
400
+ line = 1 + @contents[0...user_to_gap(pos)].count("\n")
401
+ if pos == point_min
402
+ column = 1
403
+ else
404
+ i = @contents.rindex("\n", user_to_gap(pos - 1))
405
+ if i
406
+ i += 1
407
+ else
408
+ i = 0
409
+ end
410
+ column = 1 + substring(gap_to_user(i), pos).size
411
+ end
412
+ [line, column]
413
+ end
414
+
415
+ def goto_char(pos)
416
+ if pos < 0 || pos > size
417
+ raise RangeError, "Out of buffer"
418
+ end
419
+ if !@binary && /[\x80-\xbf]/n =~ byte_after(pos)
420
+ raise ArgumentError, "Position is in the middle of a character"
421
+ end
422
+ @goal_column = nil
423
+ if @save_point_level == 0
424
+ @current_line, @current_column = get_line_and_column(pos)
425
+ end
426
+ @point = pos
427
+ end
428
+
429
+ def goto_line(n)
430
+ pos = point_min
431
+ i = 1
432
+ while i < n && pos < @contents.bytesize
433
+ pos = @contents.index("\n", pos)
434
+ break if pos.nil?
435
+ i += 1
436
+ pos += 1
437
+ end
438
+ @point = gap_to_user(pos)
439
+ @current_line = i
440
+ @current_column = 1
441
+ @goal_column = nil
442
+ end
443
+
444
+ def insert(s, merge_undo = false)
445
+ check_read_only_flag
446
+ pos = @point
447
+ size = s.bytesize
448
+ adjust_gap(size)
449
+ @contents[@point, size] = s.b
450
+ @marks.each do |m|
451
+ if m.location > @point
452
+ m.location += size
453
+ end
454
+ end
455
+ @point = @gap_start += size
456
+ update_line_and_column(pos, @point)
457
+ unless @undoing
458
+ if merge_undo && @undo_stack.last.is_a?(InsertAction)
459
+ @undo_stack.last.merge(s)
460
+ @redo_stack.clear
461
+ else
462
+ push_undo(InsertAction.new(self, pos, s))
463
+ end
464
+ end
465
+ @modified = true
466
+ @goal_column = nil
467
+ end
468
+
469
+ def newline
470
+ indentation = save_point { |saved|
471
+ if /[ \t]/ =~ char_after
472
+ next ""
473
+ end
474
+ beginning_of_line
475
+ s = @point
476
+ while /[ \t]/ =~ char_after
477
+ forward_char
478
+ end
479
+ str = substring(s, @point)
480
+ if end_of_buffer? || char_after == "\n"
481
+ delete_region(s, @point)
482
+ end
483
+ str
484
+ }
485
+ insert("\n" + indentation)
486
+ end
487
+
488
+ def delete_char(n = 1)
489
+ check_read_only_flag
490
+ adjust_gap
491
+ s = @point
492
+ pos = get_pos(@point, n)
493
+ if n > 0
494
+ str = substring(s, pos)
495
+ # fill the gap with NUL to avoid invalid byte sequence in UTF-8
496
+ @contents[@gap_end...user_to_gap(pos)] = "\0" * (pos - @point)
497
+ @gap_end += pos - @point
498
+ @marks.each do |m|
499
+ if m.location > pos
500
+ m.location -= pos - @point
501
+ elsif m.location > @point
502
+ m.location = @point
503
+ end
504
+ end
505
+ push_undo(DeleteAction.new(self, s, s, str))
506
+ @modified = true
507
+ elsif n < 0
508
+ str = substring(pos, s)
509
+ update_line_and_column(@point, pos)
510
+ # fill the gap with NUL to avoid invalid byte sequence in UTF-8
511
+ @contents[user_to_gap(pos)...@gap_start] = "\0" * (@point - pos)
512
+ @marks.each do |m|
513
+ if m.location >= @point
514
+ m.location -= @point - pos
515
+ elsif m.location > pos
516
+ m.location = pos
517
+ end
518
+ end
519
+ @point = @gap_start = pos
520
+ push_undo(DeleteAction.new(self, s, pos, str))
521
+ @modified = true
522
+ end
523
+ @goal_column = nil
524
+ end
525
+
526
+ def backward_delete_char(n = 1)
527
+ delete_char(-n)
528
+ end
529
+
530
+ def forward_char(n = 1)
531
+ pos = get_pos(@point, n)
532
+ update_line_and_column(@point, pos)
533
+ @point = pos
534
+ @goal_column = nil
535
+ end
536
+
537
+ def backward_char(n = 1)
538
+ forward_char(-n)
539
+ end
540
+
541
+ def forward_word(n = 1)
542
+ n.times do
543
+ while !end_of_buffer? && /\p{Letter}|\p{Number}/ !~ char_after
544
+ forward_char
545
+ end
546
+ while !end_of_buffer? && /\p{Letter}|\p{Number}/ =~ char_after
547
+ forward_char
548
+ end
549
+ end
550
+ end
551
+
552
+ def backward_word(n = 1)
553
+ n.times do
554
+ break if beginning_of_buffer?
555
+ backward_char
556
+ while !beginning_of_buffer? && /\p{Letter}|\p{Number}/ !~ char_after
557
+ backward_char
558
+ end
559
+ while !beginning_of_buffer? && /\p{Letter}|\p{Number}/ =~ char_after
560
+ backward_char
561
+ end
562
+ if /\p{Letter}|\p{Number}/ !~ char_after
563
+ forward_char
564
+ end
565
+ end
566
+ end
567
+
568
+ def forward_line(n = 1)
569
+ if n > 0
570
+ n.times do
571
+ end_of_line
572
+ break if end_of_buffer?
573
+ forward_char
574
+ end
575
+ elsif n < 0
576
+ (-n).times do
577
+ beginning_of_line
578
+ break if beginning_of_buffer?
579
+ backward_char
580
+ beginning_of_line
581
+ end
582
+ end
583
+ end
584
+
585
+ def backward_line(n = 1)
586
+ forward_line(-n)
587
+ end
588
+
589
+ def next_line(n = 1)
590
+ if @goal_column
591
+ column = @goal_column
592
+ else
593
+ prev_point = @point
594
+ beginning_of_line
595
+ column = Buffer.display_width(substring(@point, prev_point))
596
+ end
597
+ n.times do
598
+ end_of_line
599
+ forward_char
600
+ s = @point
601
+ while !end_of_buffer? && byte_after != "\n" &&
602
+ Buffer.display_width(substring(s, @point)) < column
603
+ forward_char
604
+ end
605
+ end
606
+ @goal_column = column
607
+ end
608
+
609
+ def previous_line(n = 1)
610
+ if @goal_column
611
+ column = @goal_column
612
+ else
613
+ prev_point = @point
614
+ beginning_of_line
615
+ column = Buffer.display_width(substring(@point, prev_point))
616
+ end
617
+ n.times do
618
+ beginning_of_line
619
+ backward_char
620
+ beginning_of_line
621
+ s = @point
622
+ while !end_of_buffer? && byte_after != "\n" &&
623
+ Buffer.display_width(substring(s, @point)) < column
624
+ forward_char
625
+ end
626
+ end
627
+ @goal_column = column
628
+ end
629
+
630
+ def beginning_of_buffer
631
+ if @save_point_level == 0
632
+ @current_line = 1
633
+ @current_column = 1
634
+ end
635
+ @point = 0
636
+ end
637
+
638
+ def beginning_of_buffer?
639
+ @point == 0
640
+ end
641
+
642
+ def end_of_buffer
643
+ goto_char(bytesize)
644
+ end
645
+
646
+ def end_of_buffer?
647
+ @point == bytesize
648
+ end
649
+
650
+ def beginning_of_line
651
+ while !beginning_of_buffer? &&
652
+ byte_after(@point - 1) != "\n"
653
+ backward_char
654
+ end
655
+ @point
656
+ end
657
+
658
+ def end_of_line
659
+ while !end_of_buffer? &&
660
+ byte_after(@point) != "\n"
661
+ forward_char
662
+ end
663
+ @point
664
+ end
665
+
666
+ def new_mark(location = @point)
667
+ Mark.new(self, location).tap { |m|
668
+ @marks << m
669
+ }
670
+ end
671
+
672
+ def point_to_mark(mark)
673
+ goto_char(mark.location)
674
+ end
675
+
676
+ def mark_to_point(mark)
677
+ mark.location = @point
678
+ end
679
+
680
+ def point_at_mark?(mark)
681
+ @point == mark.location
682
+ end
683
+
684
+ def point_before_mark?(mark)
685
+ @point < mark.location
686
+ end
687
+
688
+ def point_after_mark?(mark)
689
+ @point > mark.location
690
+ end
691
+
692
+ def exchange_point_and_mark(mark = @mark)
693
+ if mark.nil?
694
+ raise EditorError, "The mark is not set"
695
+ end
696
+ update_line_and_column(@point, mark.location)
697
+ @point, mark.location = mark.location, @point
698
+ end
699
+
700
+ # The buffer should not be modified in the given block
701
+ # because current_line/current_column is not updated in save_point.
702
+ def save_point
703
+ saved = new_mark
704
+ column = @goal_column
705
+ @save_point_level += 1
706
+ begin
707
+ yield(saved)
708
+ ensure
709
+ point_to_mark(saved)
710
+ saved.delete
711
+ @goal_column = column
712
+ @save_point_level -= 1
713
+ end
714
+ end
715
+
716
+ # Don't save Buffer.current.
717
+ def save_excursion
718
+ old_point = new_mark
719
+ old_mark = @mark&.dup
720
+ old_column = @goal_column
721
+ begin
722
+ yield
723
+ ensure
724
+ point_to_mark(old_point)
725
+ old_point.delete
726
+ if old_mark
727
+ @mark.location = old_mark.location
728
+ old_mark.delete
729
+ end
730
+ @goal_column = old_column
731
+ end
732
+ end
733
+
734
+ def mark
735
+ if @mark.nil?
736
+ raise EditorError, "The mark is not set"
737
+ end
738
+ @mark.location
739
+ end
740
+
741
+ def set_mark(pos = @point)
742
+ @mark ||= new_mark
743
+ @mark.location = pos
744
+ end
745
+
746
+ def set_visible_mark(pos = @point)
747
+ @visible_mark ||= new_mark
748
+ @visible_mark.location = pos
749
+ end
750
+
751
+ def delete_visible_mark
752
+ if @visible_mark
753
+ @visible_mark.delete
754
+ @visible_mark = nil
755
+ end
756
+ end
757
+
758
+ def copy_region(s = @point, e = mark, append = false)
759
+ str = s <= e ? substring(s, e) : substring(e, s)
760
+ if append && !KILL_RING.empty?
761
+ KILL_RING.current.concat(str)
762
+ else
763
+ KILL_RING.push(str)
764
+ end
765
+ end
766
+
767
+ def kill_region(s = @point, e = mark, append = false)
768
+ copy_region(s, e, append)
769
+ delete_region(s, e)
770
+ end
771
+
772
+ def delete_region(s = @point, e = mark)
773
+ check_read_only_flag
774
+ old_pos = @point
775
+ if s > e
776
+ s, e = e, s
777
+ end
778
+ update_line_and_column(old_pos, s)
779
+ save_point do
780
+ str = substring(s, e)
781
+ @point = s
782
+ adjust_gap
783
+ len = e - s
784
+ # fill the gap with NUL to avoid invalid byte sequence in UTF-8
785
+ @contents[@gap_end, len] = "\0" * len
786
+ @gap_end += len
787
+ @marks.each do |m|
788
+ if m.location > e
789
+ m.location -= len
790
+ elsif m.location > s
791
+ m.location = s
792
+ end
793
+ end
794
+ push_undo(DeleteAction.new(self, old_pos, s, str))
795
+ @modified = true
796
+ end
797
+ end
798
+
799
+ def clear
800
+ check_read_only_flag
801
+ @contents = String.new
802
+ @point = @gap_start = @gap_end = 0
803
+ @marks.each do |m|
804
+ m.location = 0
805
+ end
806
+ @current_line = 1
807
+ @current_column = 1
808
+ @goal_column = nil
809
+ @modified = true
810
+ @undo_stack.clear
811
+ @redo_stack.clear
812
+ end
813
+
814
+ def kill_line(append = false)
815
+ save_point do |saved|
816
+ if end_of_buffer?
817
+ raise RangeError, "End of buffer"
818
+ end
819
+ if char_after == ?\n
820
+ forward_char
821
+ else
822
+ end_of_line
823
+ end
824
+ pos = @point
825
+ point_to_mark(saved)
826
+ kill_region(@point, pos, append)
827
+ end
828
+ end
829
+
830
+ def kill_word(append = false)
831
+ save_point do |saved|
832
+ if end_of_buffer?
833
+ raise RangeError, "End of buffer"
834
+ end
835
+ forward_word
836
+ pos = @point
837
+ point_to_mark(saved)
838
+ kill_region(@point, pos, append)
839
+ end
840
+ end
841
+
842
+ def insert_for_yank(s)
843
+ mark_to_point(@yank_start)
844
+ insert(s)
845
+ end
846
+
847
+ def yank
848
+ insert_for_yank(KILL_RING.current)
849
+ end
850
+
851
+ def yank_pop
852
+ delete_region(@yank_start.location, @point)
853
+ insert_for_yank(KILL_RING.current(1))
854
+ end
855
+
856
+ def undo
857
+ check_read_only_flag
858
+ if @undo_stack.empty?
859
+ raise EditorError, "No further undo information"
860
+ end
861
+ action = @undo_stack.pop
862
+ @undoing = true
863
+ begin
864
+ was_modified = @modified
865
+ action.undo
866
+ if action.version == @version
867
+ @modified = false
868
+ action.version = nil
869
+ elsif !was_modified
870
+ action.version = @version
871
+ end
872
+ @redo_stack.push(action)
873
+ ensure
874
+ @undoing = false
875
+ end
876
+ end
877
+
878
+ def redo
879
+ check_read_only_flag
880
+ if @redo_stack.empty?
881
+ raise EditorError, "No further redo information"
882
+ end
883
+ action = @redo_stack.pop
884
+ @undoing = true
885
+ begin
886
+ was_modified = @modified
887
+ action.redo
888
+ if action.version == @version
889
+ @modified = false
890
+ action.version = nil
891
+ elsif !was_modified
892
+ action.version = @version
893
+ end
894
+ @undo_stack.push(action)
895
+ ensure
896
+ @undoing = false
897
+ end
898
+ end
899
+
900
+ def re_search_forward(s)
901
+ re = new_regexp(s)
902
+ i = byteindex(true, re, @point)
903
+ if i.nil?
904
+ raise SearchError, "Search failed"
905
+ end
906
+ goto_char(match_end(0))
907
+ end
908
+
909
+ def re_search_backward(s)
910
+ re = new_regexp(s)
911
+ pos = @point
912
+ begin
913
+ i = byteindex(false, re, pos)
914
+ if i.nil?
915
+ raise SearchError, "Search failed"
916
+ end
917
+ pos = get_pos(pos, -1)
918
+ rescue RangeError
919
+ raise SearchError, "Search failed"
920
+ end while match_end(0) > @point
921
+ goto_char(match_beginning(0))
922
+ end
923
+
924
+ def looking_at?(re)
925
+ # TODO: optimization
926
+ byteindex(true, re, @point) == @point
927
+ end
928
+
929
+ def byteindex(forward, re, pos)
930
+ @match_offsets = []
931
+ method = forward ? :index : :rindex
932
+ adjust_gap(0, point_max)
933
+ if @binary
934
+ offset = pos
935
+ else
936
+ offset = @contents[0...pos].force_encoding(Encoding::UTF_8).size
937
+ @contents.force_encoding(Encoding::UTF_8)
938
+ end
939
+ begin
940
+ i = @contents.send(method, re, offset)
941
+ if i
942
+ m = Regexp.last_match
943
+ if m.nil?
944
+ # A bug of rindex
945
+ @match_offsets.push([pos, pos])
946
+ pos
947
+ else
948
+ b = m.pre_match.bytesize
949
+ e = b + m.to_s.bytesize
950
+ if e <= bytesize
951
+ @match_offsets.push([b, e])
952
+ match_beg = m.begin(0)
953
+ match_str = m.to_s
954
+ (1 .. m.size - 1).each do |j|
955
+ cb, ce = m.offset(j)
956
+ if cb.nil?
957
+ @match_offsets.push([nil, nil])
958
+ else
959
+ bb = b + match_str[0, cb - match_beg].bytesize
960
+ be = b + match_str[0, ce - match_beg].bytesize
961
+ @match_offsets.push([bb, be])
962
+ end
963
+ end
964
+ b
965
+ else
966
+ nil
967
+ end
968
+ end
969
+ else
970
+ nil
971
+ end
972
+ ensure
973
+ @contents.force_encoding(Encoding::ASCII_8BIT)
974
+ end
975
+ end
976
+
977
+ def match_beginning(n)
978
+ @match_offsets[n]&.first
979
+ end
980
+
981
+ def match_end(n)
982
+ @match_offsets[n]&.last
983
+ end
984
+
985
+ def match_string(n)
986
+ b, e = @match_offsets[n]
987
+ if b.nil?
988
+ nil
989
+ else
990
+ substring(b, e)
991
+ end
992
+ end
993
+
994
+ def replace_match(str)
995
+ new_str = str.gsub(/\\(?:([0-9]+)|(&)|(\\))/) { |s|
996
+ case
997
+ when $1
998
+ match_string($1.to_i)
999
+ when $2
1000
+ match_string(0)
1001
+ when $3
1002
+ "\\"
1003
+ end
1004
+ }
1005
+ b = match_beginning(0)
1006
+ e = match_end(0)
1007
+ goto_char(b)
1008
+ delete_region(b, e)
1009
+ insert(new_str)
1010
+ merge_undo(2)
1011
+ end
1012
+
1013
+ def replace_regexp_forward(regexp, to_str)
1014
+ result = 0
1015
+ rest = substring(point, point_max)
1016
+ delete_region(point, point_max)
1017
+ new_str = rest.gsub(new_regexp(regexp)) {
1018
+ result += 1
1019
+ m = Regexp.last_match
1020
+ to_str.gsub(/\\(?:([0-9]+)|(&)|(\\))/) { |s|
1021
+ case
1022
+ when $1
1023
+ m[$1.to_i]
1024
+ when $2
1025
+ m.to_s
1026
+ when $3
1027
+ "\\"
1028
+ end
1029
+ }
1030
+ }
1031
+ insert(new_str)
1032
+ merge_undo(2)
1033
+ result
1034
+ end
1035
+
1036
+ def transpose_chars
1037
+ if end_of_buffer? || char_after == "\n"
1038
+ backward_char
1039
+ end
1040
+ if beginning_of_buffer?
1041
+ raise RangeError, "Beginning of buffer"
1042
+ end
1043
+ backward_char
1044
+ c = char_after
1045
+ delete_char
1046
+ forward_char
1047
+ insert(c)
1048
+ end
1049
+
1050
+ def gap_filled_with_nul?
1051
+ /\A\0*\z/ =~ @contents[@gap_start...@gap_end] ? true : false
1052
+ end
1053
+
1054
+ def merge_undo(n)
1055
+ return if @undoing || @undo_limit == 0
1056
+ actions = @undo_stack.pop(n)
1057
+ if actions
1058
+ action = CompositeAction.new(self, actions.first.location)
1059
+ actions.each do |i|
1060
+ action.add_action(i)
1061
+ end
1062
+ action.version = actions.first.version
1063
+ @undo_stack.push(action)
1064
+ end
1065
+ end
1066
+
1067
+ def apply_mode(mode_class)
1068
+ @mode = mode_class.new(self)
1069
+ run_hooks(mode_class.hook_name)
1070
+ end
1071
+
1072
+ def indent_to(column)
1073
+ s = if self[:indent_tabs_mode]
1074
+ "\t" * (column / self[:tab_width]) + " " * (column % self[:tab_width])
1075
+ else
1076
+ " " * column
1077
+ end
1078
+ insert(s)
1079
+ end
1080
+
1081
+ private
1082
+
1083
+ def adjust_gap(min_size = 0, pos = @point)
1084
+ if @gap_start < pos
1085
+ len = user_to_gap(pos) - @gap_end
1086
+ @contents[@gap_start, len] = @contents[@gap_end, len]
1087
+ @gap_start += len
1088
+ @gap_end += len
1089
+ elsif @gap_start > pos
1090
+ len = @gap_start - pos
1091
+ @contents[@gap_end - len, len] = @contents[pos, len]
1092
+ @gap_start -= len
1093
+ @gap_end -= len
1094
+ end
1095
+ # fill the gap with NUL to avoid invalid byte sequence in UTF-8
1096
+ @contents[@gap_start...@gap_end] = "\0" * (@gap_end - @gap_start)
1097
+ if gap_size < min_size
1098
+ new_gap_size = GAP_SIZE + min_size
1099
+ extended_size = new_gap_size - gap_size
1100
+ @contents[@gap_end, 0] = "\0" * extended_size
1101
+ @gap_end += extended_size
1102
+ end
1103
+ end
1104
+
1105
+ def gap_size
1106
+ @gap_end - @gap_start
1107
+ end
1108
+
1109
+ def user_to_gap(pos)
1110
+ if pos <= @gap_start
1111
+ pos
1112
+ else
1113
+ gap_size + pos
1114
+ end
1115
+ end
1116
+
1117
+ def gap_to_user(gpos)
1118
+ if gpos <= @gap_start
1119
+ gpos
1120
+ elsif gpos >= @gap_end
1121
+ gpos - gap_size
1122
+ else
1123
+ raise RangeError, "Position is in gap"
1124
+ end
1125
+ end
1126
+
1127
+ def get_pos(pos, offset)
1128
+ if @binary
1129
+ result = pos + offset
1130
+ if result < 0 || result > bytesize
1131
+ raise RangeError, "Out of buffer"
1132
+ end
1133
+ return result
1134
+ end
1135
+ if offset >= 0
1136
+ i = offset
1137
+ while i > 0
1138
+ raise RangeError, "Out of buffer" if end_of_buffer?
1139
+ b = byte_after(pos)
1140
+ pos += UTF8_CHAR_LEN[b]
1141
+ raise RangeError, "Out of buffer" if pos > bytesize
1142
+ i -= 1
1143
+ end
1144
+ else
1145
+ i = -offset
1146
+ while i > 0
1147
+ pos -= 1
1148
+ raise RangeError, "Out of buffer" if pos < 0
1149
+ while /[\x80-\xbf]/n =~ byte_after(pos)
1150
+ pos -= 1
1151
+ raise RangeError, "Out of buffer" if pos < 0
1152
+ end
1153
+ i -= 1
1154
+ end
1155
+ end
1156
+ pos
1157
+ end
1158
+
1159
+ def update_line_and_column(pos, new_pos)
1160
+ return if @save_point_level > 0
1161
+ if pos < new_pos
1162
+ n = @contents[user_to_gap(pos)...user_to_gap(new_pos)].count("\n")
1163
+ if n == 0
1164
+ @current_column += substring(pos, new_pos).size
1165
+ else
1166
+ @current_line += n
1167
+ i = @contents.rindex("\n", user_to_gap(new_pos - 1))
1168
+ if i
1169
+ i += 1
1170
+ else
1171
+ i = 0
1172
+ end
1173
+ @current_column = 1 + substring(gap_to_user(i), new_pos).size
1174
+ end
1175
+ elsif pos > new_pos
1176
+ n = @contents[user_to_gap(new_pos)...user_to_gap(pos)].count("\n")
1177
+ if n == 0
1178
+ @current_column -= substring(new_pos, pos).size
1179
+ else
1180
+ @current_line -= n
1181
+ i = @contents.rindex("\n", user_to_gap(new_pos - 1))
1182
+ if i
1183
+ i += 1
1184
+ else
1185
+ i = 0
1186
+ end
1187
+ @current_column = 1 + substring(gap_to_user(i), new_pos).size
1188
+ end
1189
+ end
1190
+ end
1191
+
1192
+ def write_to_file(f)
1193
+ [@contents[0...@gap_start], @contents[@gap_end..-1]].each do |s|
1194
+ s.force_encoding(Encoding::UTF_8) unless @binary
1195
+ case @file_format
1196
+ when :dos
1197
+ s.gsub!(/\n/, "\r\n")
1198
+ when :mac
1199
+ s.gsub!(/\n/, "\r")
1200
+ end
1201
+ f.write(s)
1202
+ end
1203
+ end
1204
+
1205
+ def push_undo(action)
1206
+ return if @undoing || @undo_limit == 0
1207
+ if @undo_stack.size >= @undo_limit
1208
+ @undo_stack[0, @undo_stack.size + 1 - @undo_limit] = []
1209
+ end
1210
+ if !modified?
1211
+ action.version = @version
1212
+ end
1213
+ @undo_stack.push(action)
1214
+ @redo_stack.clear
1215
+ end
1216
+
1217
+ def new_regexp(s)
1218
+ if s.is_a?(Regexp)
1219
+ s
1220
+ else
1221
+ Regexp.new(s, self[:case_fold_search] ? Regexp::IGNORECASE : 0)
1222
+ end
1223
+ end
1224
+
1225
+ def check_read_only_flag
1226
+ if @read_only
1227
+ raise ReadOnlyError, "Buffer is read only: #{self.inspect}"
1228
+ end
1229
+ end
1230
+ end
1231
+
1232
+ class Mark
1233
+ attr_accessor :location
1234
+
1235
+ def initialize(buffer, location)
1236
+ @buffer = buffer
1237
+ @location = location
1238
+ end
1239
+
1240
+ def delete
1241
+ @buffer.marks.delete(self)
1242
+ end
1243
+
1244
+ def dup
1245
+ mark = @buffer.new_mark
1246
+ mark.location = @location
1247
+ mark
1248
+ end
1249
+ end
1250
+
1251
+ class KillRing
1252
+ def initialize(max = 30)
1253
+ @max = max
1254
+ @ring = []
1255
+ @current = -1
1256
+ end
1257
+
1258
+ def clear
1259
+ @ring.clear
1260
+ @current = -1
1261
+ end
1262
+
1263
+ def push(str)
1264
+ @current += 1
1265
+ if @ring.size < @max
1266
+ @ring.insert(@current, str)
1267
+ else
1268
+ if @current == @max
1269
+ @current = 0
1270
+ end
1271
+ @ring[@current] = str
1272
+ end
1273
+ end
1274
+
1275
+ def current(n = 0)
1276
+ if @ring.empty?
1277
+ raise EditorError, "Kill ring is empty"
1278
+ end
1279
+ @current -= n
1280
+ if @current < 0
1281
+ @current += @ring.size
1282
+ end
1283
+ @ring[@current]
1284
+ end
1285
+
1286
+ def empty?
1287
+ @ring.empty?
1288
+ end
1289
+ end
1290
+
1291
+ KILL_RING = KillRing.new
1292
+
1293
+ class UndoableAction
1294
+ attr_accessor :version
1295
+ attr_reader :location
1296
+
1297
+ def initialize(buffer, location)
1298
+ @version = nil
1299
+ @buffer = buffer
1300
+ @location = location
1301
+ end
1302
+ end
1303
+
1304
+ class InsertAction < UndoableAction
1305
+ def initialize(buffer, location, string)
1306
+ super(buffer, location)
1307
+ @string = string
1308
+ end
1309
+
1310
+ def undo
1311
+ @buffer.goto_char(@location)
1312
+ @buffer.delete_region(@location, @location + @string.bytesize)
1313
+ end
1314
+
1315
+ def redo
1316
+ @buffer.goto_char(@location)
1317
+ @buffer.insert(@string)
1318
+ end
1319
+
1320
+ def merge(s)
1321
+ @string.concat(s)
1322
+ end
1323
+ end
1324
+
1325
+ class DeleteAction < UndoableAction
1326
+ def initialize(buffer, location, insert_location, string)
1327
+ super(buffer, location)
1328
+ @insert_location = insert_location
1329
+ @string = string
1330
+ end
1331
+
1332
+ def undo
1333
+ @buffer.goto_char(@insert_location)
1334
+ @buffer.insert(@string)
1335
+ @buffer.goto_char(@location)
1336
+ end
1337
+
1338
+ def redo
1339
+ @buffer.goto_char(@insert_location)
1340
+ @buffer.delete_region(@insert_location,
1341
+ @insert_location + @string.bytesize)
1342
+ end
1343
+ end
1344
+
1345
+ class CompositeAction < UndoableAction
1346
+ def initialize(buffer, location)
1347
+ super(buffer, location)
1348
+ @actions = []
1349
+ end
1350
+
1351
+ def add_action(action)
1352
+ @actions.push(action)
1353
+ end
1354
+
1355
+ def undo
1356
+ @actions.reverse_each do |action|
1357
+ action.undo
1358
+ end
1359
+ end
1360
+
1361
+ def redo
1362
+ @actions.each do |action|
1363
+ action.redo
1364
+ end
1365
+ end
1366
+ end
1367
+ end