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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +13 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +41 -0
- data/LICENSE.txt +21 -0
- data/README.md +58 -0
- data/Rakefile +9 -0
- data/bin/console +9 -0
- data/exe/tb +40 -0
- data/lib/textbringer/buffer.rb +1367 -0
- data/lib/textbringer/commands.rb +690 -0
- data/lib/textbringer/config.rb +9 -0
- data/lib/textbringer/controller.rb +129 -0
- data/lib/textbringer/errors.rb +12 -0
- data/lib/textbringer/keymap.rb +141 -0
- data/lib/textbringer/mode.rb +64 -0
- data/lib/textbringer/modes/backtrace_mode.rb +38 -0
- data/lib/textbringer/modes/fundamental_mode.rb +6 -0
- data/lib/textbringer/modes/programming_mode.rb +43 -0
- data/lib/textbringer/modes/ruby_mode.rb +192 -0
- data/lib/textbringer/modes.rb +7 -0
- data/lib/textbringer/utils.rb +277 -0
- data/lib/textbringer/version.rb +3 -0
- data/lib/textbringer/window.rb +646 -0
- data/lib/textbringer.rb +10 -0
- data/textbringer.gemspec +30 -0
- metadata +170 -0
@@ -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
|