cless 0.3.20
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/LICENSE +674 -0
- data/Readme.md +1 -0
- data/bin/cless +287 -0
- data/lib/cless/assert.rb +3 -0
- data/lib/cless/cless.rb +816 -0
- data/lib/cless/data.rb +633 -0
- data/lib/cless/display.rb +639 -0
- data/lib/cless/export.rb +92 -0
- data/lib/cless/help.rb +97 -0
- data/lib/cless/optionsdb.rb +29 -0
- data/lib/cless/version.rb +1 -0
- metadata +70 -0
data/lib/cless/data.rb
ADDED
@@ -0,0 +1,633 @@
|
|
1
|
+
require 'fcntl'
|
2
|
+
require 'cless/assert'
|
3
|
+
|
4
|
+
# Including class must respond to #each_line
|
5
|
+
module MappedCommon
|
6
|
+
def parse_header(allowed = [])
|
7
|
+
a = []
|
8
|
+
i = 0
|
9
|
+
each_line do |l|
|
10
|
+
break unless l =~ %r{^\s*#(.*)$}
|
11
|
+
i += 1
|
12
|
+
s = $1
|
13
|
+
case s
|
14
|
+
when /^\s*cless:(.*)$/
|
15
|
+
s = $1.strip
|
16
|
+
a += s.split_with_quotes
|
17
|
+
when /^\s*(\w+):(.*)$/
|
18
|
+
k, v = $1.strip, $2.strip
|
19
|
+
a << "--#{k}" << v if allowed.include?(k)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
return i, a
|
23
|
+
end
|
24
|
+
|
25
|
+
def count_lines_upto(stop_off)
|
26
|
+
cur = 0
|
27
|
+
nb = 0
|
28
|
+
while cur <= stop_off do
|
29
|
+
cur = @ptr.index("\n", cur)
|
30
|
+
if cur
|
31
|
+
cur += 1
|
32
|
+
nb += 1
|
33
|
+
else
|
34
|
+
break
|
35
|
+
end
|
36
|
+
end
|
37
|
+
nb
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Read from a stream. Write data to a temporary file, which is mmap.
|
42
|
+
# Read more data from stream on a need basis, when some index operation fail.
|
43
|
+
class MappedStream
|
44
|
+
include MappedCommon
|
45
|
+
|
46
|
+
DEFAULTS = {
|
47
|
+
:buf_size => 64*1024,
|
48
|
+
:tmp_dir => Dir.tmpdir,
|
49
|
+
}
|
50
|
+
attr_reader :ptr, :more, :fd
|
51
|
+
def initialize(fd, args = {})
|
52
|
+
@fd = fd
|
53
|
+
flags = fd.fcntl(Fcntl::F_GETFL)
|
54
|
+
fd.fcntl(Fcntl::F_SETFL, flags | Fcntl::O_NONBLOCK)
|
55
|
+
@more = true
|
56
|
+
@buf = ""
|
57
|
+
@lines = nil
|
58
|
+
|
59
|
+
DEFAULTS.each { |k, v|
|
60
|
+
instance_variable_set("@#{k}", args[k] || v)
|
61
|
+
}
|
62
|
+
if false
|
63
|
+
# if $have_mmap
|
64
|
+
@tfd = Tempfile.new(Process.pid.to_s, @tmp_dir)
|
65
|
+
@ptr = Mmap.new(@tfd.path, "w")
|
66
|
+
@ptr.extend(10 * @buf_size)
|
67
|
+
else
|
68
|
+
@ptr = ""
|
69
|
+
end
|
70
|
+
|
71
|
+
if block_given?
|
72
|
+
begin
|
73
|
+
yield(self)
|
74
|
+
ensure
|
75
|
+
munmap
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def file_path; @tfd ? @tfd.path : nil; end
|
81
|
+
|
82
|
+
def munmap
|
83
|
+
@ptr.munmap rescue nil
|
84
|
+
@tfd.close! rescue nil
|
85
|
+
end
|
86
|
+
|
87
|
+
def size; @ptr.size; end
|
88
|
+
def rindex(*args); @ptr.rindex(*args); end
|
89
|
+
|
90
|
+
def read_block
|
91
|
+
@fd.read_nonblock(@buf_size, @buf)
|
92
|
+
@ptr << @buf
|
93
|
+
true
|
94
|
+
rescue Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::EINTR => e
|
95
|
+
false
|
96
|
+
rescue EOFError
|
97
|
+
@more = false
|
98
|
+
false
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
def index(substr, off = 0)
|
103
|
+
loop do
|
104
|
+
r = @ptr.index(substr, off) and return r
|
105
|
+
return nil unless @more
|
106
|
+
off = (@ptr.rindex("\n", @ptr.size) || -1) + 1
|
107
|
+
read_block or return nil
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def search_rindex(*args); rindex(*args); end
|
112
|
+
def search_index(substr, off = 0)
|
113
|
+
loop do
|
114
|
+
r = @ptr.index(substr, off) and return r
|
115
|
+
off = (@ptr.rindex("\n", @ptr.size) || -1) + 1
|
116
|
+
select_or_cancel(@fd) or return nil
|
117
|
+
read_block or return nil
|
118
|
+
end
|
119
|
+
end
|
120
|
+
def more_fd
|
121
|
+
@more ? @fd : nil
|
122
|
+
end
|
123
|
+
|
124
|
+
def [](*args); @ptr[*args]; end
|
125
|
+
|
126
|
+
# Get the total number of lines
|
127
|
+
# Stop if line_stop or offset_stop limits are crossed.
|
128
|
+
def lines(line_stop = nil, offset_stop = nil)
|
129
|
+
return @lines unless @more || @lines.nil?
|
130
|
+
lines = @ptr.count("\n")
|
131
|
+
while @more
|
132
|
+
unless read_block
|
133
|
+
select_or_cancel(@fd) or break
|
134
|
+
next
|
135
|
+
end
|
136
|
+
lines += @buf.count("\n")
|
137
|
+
return lines if line_stop && lines >= line_stop
|
138
|
+
return @ptr.size if offset_stop && @ptr.size >= offset_stop
|
139
|
+
end
|
140
|
+
@lines = lines
|
141
|
+
@lines += 1 if @ptr[-1] != ?\n
|
142
|
+
return @lines
|
143
|
+
end
|
144
|
+
|
145
|
+
def each_line
|
146
|
+
off = 0
|
147
|
+
loop do
|
148
|
+
r = @ptr.index("\n", off)
|
149
|
+
if r
|
150
|
+
yield(@ptr[off..r])
|
151
|
+
off = r + 1
|
152
|
+
else
|
153
|
+
read_block or break
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
class MappedFile
|
160
|
+
include MappedCommon
|
161
|
+
|
162
|
+
attr_reader :file_path
|
163
|
+
|
164
|
+
def initialize(fname)
|
165
|
+
@ptr = Mmap.new(fname)
|
166
|
+
@lines = nil
|
167
|
+
@file_path = fname
|
168
|
+
|
169
|
+
if block_given?
|
170
|
+
begin
|
171
|
+
yield(self)
|
172
|
+
ensure
|
173
|
+
munmap
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def size; @ptr.size; end
|
179
|
+
def munmap; @ptr.munmap; end
|
180
|
+
def rindex(*args); @ptr.rindex(*args); end
|
181
|
+
def index(*args); @ptr.index(*args); end
|
182
|
+
def search_rindex(*args); rindex(*args); end
|
183
|
+
def search_index(*args); index(*args); end
|
184
|
+
def [](*args); @ptr[*args]; end
|
185
|
+
def each_line(&b); @ptr.each_line(&b); end
|
186
|
+
def more_fd; nil; end
|
187
|
+
|
188
|
+
def lines
|
189
|
+
return @lines if @lines
|
190
|
+
@lines = @ptr.count("\n")
|
191
|
+
@lines += 1 if @ptr[-1] != ?\n
|
192
|
+
return @lines
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Similar interface to MatchData. Return the entire string as being a match.
|
197
|
+
class FieldMatch
|
198
|
+
def initialize(str); @str = str; end
|
199
|
+
def string; @str; end
|
200
|
+
def pre_match; ""; end
|
201
|
+
def post_match; ""; end
|
202
|
+
def [](i); (i == 0) ? @str : nil; end
|
203
|
+
end
|
204
|
+
|
205
|
+
class Line
|
206
|
+
attr_reader :has_match, :off
|
207
|
+
attr_accessor :highlight
|
208
|
+
alias :highlight? :highlight
|
209
|
+
|
210
|
+
def initialize(a, onl = nil, off = nil)
|
211
|
+
@a, @onl = a, onl
|
212
|
+
@m = []
|
213
|
+
@has_match = false
|
214
|
+
@off = off
|
215
|
+
@highlight = false
|
216
|
+
end
|
217
|
+
|
218
|
+
def ignored; false; end
|
219
|
+
alias :ignored? :ignored
|
220
|
+
|
221
|
+
def values_at(*args); @a.values_at(*args); end
|
222
|
+
def onl_at(*args); (@onl || @a).values_at(*args); end
|
223
|
+
def matches_at(*args); @m.values_at(*args); end
|
224
|
+
|
225
|
+
# onl is the line before any formatting or transformation.
|
226
|
+
# If a field doesn't match pattern but old representation does,
|
227
|
+
# hilight entire field as a match.
|
228
|
+
def match(pattern)
|
229
|
+
does_match = false
|
230
|
+
@a.each_with_index { |f, i|
|
231
|
+
if m = f.match(pattern)
|
232
|
+
does_match = true
|
233
|
+
@m[i] = m
|
234
|
+
elsif @onl && @onl[i].match(pattern)
|
235
|
+
does_match = true
|
236
|
+
@m[i] = FieldMatch.new(f)
|
237
|
+
end
|
238
|
+
}
|
239
|
+
@has_match = does_match
|
240
|
+
end
|
241
|
+
|
242
|
+
def clear_match; @has_match = false; @m.clear; end
|
243
|
+
end
|
244
|
+
|
245
|
+
class IgnoredLine
|
246
|
+
attr_reader :has_match, :str, :off
|
247
|
+
|
248
|
+
def initialize(str, off)
|
249
|
+
@str = str
|
250
|
+
@has_match = false
|
251
|
+
@off = off # Byte offset in file of beginning of line
|
252
|
+
end
|
253
|
+
|
254
|
+
def match(pattern)
|
255
|
+
if m = @str.match(pattern)
|
256
|
+
@m = m
|
257
|
+
@has_match = true
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def matches; @m; end
|
262
|
+
def clear_match; @has_match = false; @m = nil; end
|
263
|
+
|
264
|
+
def ignored; true; end
|
265
|
+
alias :ignored? :ignored
|
266
|
+
|
267
|
+
# An ignored line is never highlighted
|
268
|
+
def highlight; false; end
|
269
|
+
alias :highlight? :highlight
|
270
|
+
|
271
|
+
def highlight=(*args); end
|
272
|
+
end
|
273
|
+
|
274
|
+
class MapData
|
275
|
+
attr_reader :sizes, :line, :line2, :pattern, :split_regexp
|
276
|
+
def initialize(str, split_regexp = nil)
|
277
|
+
@str = str
|
278
|
+
@line = @line2 = 0
|
279
|
+
@off = @off2 = 0 # @off = first character of first line in cache
|
280
|
+
# @off2 = first character of first line past cache
|
281
|
+
@cache = []
|
282
|
+
@sizes = []
|
283
|
+
@pattern = nil # search pattern
|
284
|
+
@formats = nil # formating strings. When not nil, a hash.
|
285
|
+
@ignores = nil # line ignored index or pattern.
|
286
|
+
@split_regexp = split_regexp # split pattern
|
287
|
+
@highlight_regexp = nil # highlight pattern
|
288
|
+
@need_more = false # true if last cache_fill needs data
|
289
|
+
end
|
290
|
+
|
291
|
+
def file_path; @str.file_path; end
|
292
|
+
def write_to(fd)
|
293
|
+
@str.lines # Make sure we have all the data
|
294
|
+
block = 64*1024
|
295
|
+
|
296
|
+
(@str.size / block).times do |i|
|
297
|
+
fd.syswrite(@str[i*block, block])
|
298
|
+
end
|
299
|
+
if (r = @str.size % block) > 0
|
300
|
+
fd.syswrite(@str[@str.size - r, r])
|
301
|
+
end
|
302
|
+
@str.size
|
303
|
+
end
|
304
|
+
|
305
|
+
# Return a file descriptor to listen on (select) if there is less
|
306
|
+
# than n lines in the cache, or nil if not needed, or file is memory
|
307
|
+
# mapped and no listening is necessary.
|
308
|
+
def select_fd(n)
|
309
|
+
@cache.size < n ? @str.more_fd : nil
|
310
|
+
end
|
311
|
+
# if @need_more && @str.respond_to?(:more) && @str.respond_to?(:fd)
|
312
|
+
# @str.more ? @str.fd : nil
|
313
|
+
# else
|
314
|
+
# nil
|
315
|
+
# end
|
316
|
+
# end
|
317
|
+
|
318
|
+
# yield n lines with length len to be displayed
|
319
|
+
def lines(n)
|
320
|
+
@cache.each_with_index { |l, i|
|
321
|
+
break if i >= n
|
322
|
+
yield l
|
323
|
+
}
|
324
|
+
end
|
325
|
+
|
326
|
+
def goto_start
|
327
|
+
return false if @line == 0
|
328
|
+
@line = @line2 = 0
|
329
|
+
@off = @off2 = 0
|
330
|
+
@cache.clear
|
331
|
+
return true
|
332
|
+
end
|
333
|
+
|
334
|
+
def goto_end
|
335
|
+
old_line = @line2
|
336
|
+
@line2 = @str.lines
|
337
|
+
return false if @line2 == old_line
|
338
|
+
@line = @line2
|
339
|
+
@off2 = @off = @str.size
|
340
|
+
cache_size = @cache.size
|
341
|
+
@cache.clear
|
342
|
+
scroll(-cache_size)
|
343
|
+
return true
|
344
|
+
end
|
345
|
+
|
346
|
+
def goto_line(nb)
|
347
|
+
delta = nb - @line - 1
|
348
|
+
scroll(delta)
|
349
|
+
end
|
350
|
+
|
351
|
+
def goto_percent(percent)
|
352
|
+
percent = 0.0 if percent < 0.0
|
353
|
+
percent = 100.0 if percent > 100.0
|
354
|
+
percent = percent.to_f
|
355
|
+
line = (@str.lines * percent / 100).round
|
356
|
+
goto_line(line)
|
357
|
+
end
|
358
|
+
|
359
|
+
def goto_offset(off)
|
360
|
+
@str.lines(nil, off) if off > @str.size
|
361
|
+
off -= 1 if @str[off] == ?\n && off > 0
|
362
|
+
@off = @off2 = (@str.rindex("\n", off) || -1) + 1
|
363
|
+
@line = @line2 = @str.count_lines_upto(@off)
|
364
|
+
@cache.clear
|
365
|
+
end
|
366
|
+
|
367
|
+
# Return true if pattern found, false otherwise
|
368
|
+
def search(pattern, dir = :forward)
|
369
|
+
search_clear if @pattern
|
370
|
+
@pattern = pattern
|
371
|
+
first_line = nil
|
372
|
+
cache = (dir == :forward) ? @cache : @cache.reverse
|
373
|
+
cache.each_with_index { |l, i|
|
374
|
+
l.match(@pattern) and first_line ||= i
|
375
|
+
}
|
376
|
+
if first_line
|
377
|
+
scroll((dir == :forward) ? first_line : -first_line)
|
378
|
+
return true
|
379
|
+
else
|
380
|
+
return search_next(dir)
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
def repeat_search(dir = :forward)
|
385
|
+
first_line = nil
|
386
|
+
cache = (dir == :forward) ? @cache : @cache.reverse
|
387
|
+
cache[1..-1].each_with_index { |l, i|
|
388
|
+
if l.has_match
|
389
|
+
first_line = i
|
390
|
+
break
|
391
|
+
end
|
392
|
+
}
|
393
|
+
if first_line
|
394
|
+
scroll((dir == :forward) ? first_line + 1 : -first_line - 1)
|
395
|
+
return true
|
396
|
+
else
|
397
|
+
return search_next(dir)
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
def search_clear
|
402
|
+
@pattern = nil
|
403
|
+
@cache.each { |l| l.clear_match }
|
404
|
+
end
|
405
|
+
|
406
|
+
# delta > for scrolling down (forward in file)
|
407
|
+
def scroll(delta)
|
408
|
+
return if delta == 0
|
409
|
+
cache_size = @cache.size
|
410
|
+
if delta > 0
|
411
|
+
skipped = skip_forward(delta)
|
412
|
+
@cache.slice!(0, skipped)
|
413
|
+
cache_forward([cache_size, skipped].min)
|
414
|
+
else
|
415
|
+
skipped = skip_backward(-delta)
|
416
|
+
delta = -@cache.size if -delta > @cache.size
|
417
|
+
@cache.slice!((delta..-1))
|
418
|
+
cache_backward([cache_size, skipped].min)
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
def cache_fill(n)
|
423
|
+
@need_more = @cache.size < n
|
424
|
+
cache_forward(n - @cache.size) if @need_more
|
425
|
+
end
|
426
|
+
|
427
|
+
def clear_cache
|
428
|
+
@cache.clear
|
429
|
+
@line2 = @line
|
430
|
+
@off2 = @off
|
431
|
+
@sizes.clear
|
432
|
+
end
|
433
|
+
|
434
|
+
def refresh
|
435
|
+
size = @cache.size
|
436
|
+
clear_cache
|
437
|
+
cache_fill(size)
|
438
|
+
end
|
439
|
+
|
440
|
+
FMT_LETTERS = "bcdEefGgiIosuXxp"
|
441
|
+
FMT_REGEXP = /(^|[^%])(%[ \d#+*.-]*)([#{FMT_LETTERS}])/
|
442
|
+
FLOAT_REGEXP = /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/
|
443
|
+
INT_REGEXP = /^[-+]?[0-9]+$/
|
444
|
+
FLOAT_PROC = proc { |x| x.to_f }
|
445
|
+
INT_PROC = proc { |x| x.to_i }
|
446
|
+
BIGINT_PROC = proc { |x|
|
447
|
+
x = x.to_f.round
|
448
|
+
neg = (x < 0)
|
449
|
+
x = x.abs.to_s.reverse.gsub(/(...)/, '\1,').chomp(",").reverse
|
450
|
+
neg ? "-#{x}" : x
|
451
|
+
}
|
452
|
+
PERCENTAGE_PROC = proc { |x|
|
453
|
+
x.to_f * 100.0
|
454
|
+
}
|
455
|
+
def set_format_column(fmt, *cols)
|
456
|
+
if fmt =~ FMT_REGEXP
|
457
|
+
a = case $3
|
458
|
+
when "b", "c", "d", "i", "o", "u", "X", "x"; [fmt, INT_REGEXP, INT_PROC]
|
459
|
+
when "E", "e", "f", "G", "g"; [fmt, FLOAT_REGEXP, FLOAT_PROC]
|
460
|
+
when "I"; ["#{$`}#{$1}#{$2}s#{$'}", FLOAT_REGEXP, BIGINT_PROC]
|
461
|
+
when "p"; ["#{$`}#{$1}#{$2}f%#{$'}", FLOAT_REGEXP, PERCENTAGE_PROC]
|
462
|
+
end
|
463
|
+
end
|
464
|
+
@formats ||= {}
|
465
|
+
cols.each { |c| @formats[c] = a }
|
466
|
+
true
|
467
|
+
end
|
468
|
+
|
469
|
+
def unset_format_column(col)
|
470
|
+
return nil unless @formats
|
471
|
+
r = @formats.delete(col)
|
472
|
+
@formats = nil if @formats.empty?
|
473
|
+
r
|
474
|
+
end
|
475
|
+
|
476
|
+
def get_format_column(col)
|
477
|
+
return nil unless @formats
|
478
|
+
@formats[col]
|
479
|
+
end
|
480
|
+
|
481
|
+
def formatted_column_list; @formats ? @formats.keys : []; end
|
482
|
+
|
483
|
+
def add_ignore(pattern)
|
484
|
+
return nil unless [Integer, Range, Regexp].any? { |c| c === pattern }
|
485
|
+
(@ignored ||= []) << pattern
|
486
|
+
true
|
487
|
+
end
|
488
|
+
|
489
|
+
def split_regexp=(regexp)
|
490
|
+
@split_regexp = regexp
|
491
|
+
clear_cache
|
492
|
+
end
|
493
|
+
|
494
|
+
def highlight_regexp=(regexp)
|
495
|
+
@highlight_regexp = regexp
|
496
|
+
clear_cache
|
497
|
+
end
|
498
|
+
|
499
|
+
def remove_ignore(pattern)
|
500
|
+
if pattern.nil?
|
501
|
+
r, @ignored = @ignored, nil
|
502
|
+
return r
|
503
|
+
end
|
504
|
+
return nil unless [Integer, Range, Regexp].any? { |c| c === pattern }
|
505
|
+
r = @ignored.delete(pattern)
|
506
|
+
@ignored = nil if @ignored.empty?
|
507
|
+
r
|
508
|
+
end
|
509
|
+
|
510
|
+
def ignore_pattern_list; @ignored ? @ignored : []; end
|
511
|
+
|
512
|
+
# Returns the maximum line offset of all lines in cache
|
513
|
+
def max_offset
|
514
|
+
@cache.collect { |l| l.off }.max
|
515
|
+
end
|
516
|
+
|
517
|
+
private
|
518
|
+
def search_next(dir = :forward)
|
519
|
+
if dir == :forward
|
520
|
+
m = @str.search_index(@pattern, @off2)
|
521
|
+
else
|
522
|
+
m = @str.search_rindex(@pattern, @off)
|
523
|
+
end
|
524
|
+
return false if !m
|
525
|
+
if dir == :forward
|
526
|
+
old_off2, old_line2 = @off2, @line2
|
527
|
+
@off = @off2 = (@str.rindex("\n", m) || -1) + 1
|
528
|
+
@line = @line2 = old_line2 + @str[old_off2..@off2].count("\n")
|
529
|
+
@cache.clear
|
530
|
+
else
|
531
|
+
old_off, old_line = @off, @line
|
532
|
+
@off = @off2 = (@str.rindex("\n", m) || -1) + 1
|
533
|
+
@line = @line2 = old_line - @str[@off..old_off].count("\n")
|
534
|
+
cache_size = @cache.size
|
535
|
+
@cache.clear
|
536
|
+
scroll(-cache_size+1)
|
537
|
+
end
|
538
|
+
return true
|
539
|
+
end
|
540
|
+
|
541
|
+
def reformat(nl)
|
542
|
+
onl = nl.dup
|
543
|
+
@formats.each do |i, f|
|
544
|
+
s = nl[i] or next
|
545
|
+
fmt, cond, proc = *f
|
546
|
+
cond =~ s or next
|
547
|
+
s = proc[s] if proc
|
548
|
+
nl[i] = fmt % s rescue "###"
|
549
|
+
end
|
550
|
+
onl
|
551
|
+
end
|
552
|
+
|
553
|
+
def line_ignore?(str, i)
|
554
|
+
@ignored.any? do |pat|
|
555
|
+
case pat
|
556
|
+
when Range, Integer; pat === i
|
557
|
+
when Regexp; pat === str
|
558
|
+
else false
|
559
|
+
end
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
# str = line
|
564
|
+
# i = line number
|
565
|
+
def line_massage(str, i, off)
|
566
|
+
if @ignored && line_ignore?(str, i)
|
567
|
+
l = IgnoredLine.new(str, off)
|
568
|
+
else
|
569
|
+
onl, nl = nil, str.split(@split_regexp)
|
570
|
+
onl = reformat(nl) if @formats
|
571
|
+
@sizes.max_update(nl.collect { |x| x.size })
|
572
|
+
l = Line.new(nl, onl, off)
|
573
|
+
end
|
574
|
+
l.match(@pattern) if @pattern
|
575
|
+
l.highlight = @highlight_regexp && (@highlight_regexp =~ str)
|
576
|
+
l
|
577
|
+
end
|
578
|
+
|
579
|
+
def cache_forward(n)
|
580
|
+
lnb = @line + @cache.size
|
581
|
+
n.times do |i|
|
582
|
+
noff2 = @str.index("\n", @off2) or break
|
583
|
+
@cache << line_massage(@str[@off2, noff2 - @off2], lnb + i, @off2)
|
584
|
+
@off2 = noff2 + 1
|
585
|
+
end
|
586
|
+
@line2 = @line + @cache.size
|
587
|
+
end
|
588
|
+
|
589
|
+
def cache_backward(n)
|
590
|
+
lnb = @line - 1
|
591
|
+
n.times do |i|
|
592
|
+
break if @off == 0
|
593
|
+
ooff = @off
|
594
|
+
@off = search_backward_to_new_line(@off)
|
595
|
+
@cache.unshift(line_massage(@str[@off, ooff - @off - 1], lnb - i, @off))
|
596
|
+
end
|
597
|
+
@line = @line2 - @cache.size
|
598
|
+
end
|
599
|
+
|
600
|
+
def search_backward_to_new_line(start)
|
601
|
+
return 0 if start < 2
|
602
|
+
npos = @str.rindex("\n", start - 2)
|
603
|
+
return npos ? npos + 1 : 0
|
604
|
+
end
|
605
|
+
|
606
|
+
# Move @off by n lines. Make sure that @off2 >= @off
|
607
|
+
def skip_forward(n)
|
608
|
+
i = 0
|
609
|
+
n.times do
|
610
|
+
noff = @str.search_index("\n", @off) or break
|
611
|
+
@off = noff + 1
|
612
|
+
i += 1
|
613
|
+
end
|
614
|
+
@off2 = @off if @off2 < @off
|
615
|
+
@line += i
|
616
|
+
@line2 = @line if @line > @line2
|
617
|
+
i
|
618
|
+
end
|
619
|
+
|
620
|
+
# Move @off2 back by n lines. Make sure that @off <= @off2
|
621
|
+
def skip_backward(n)
|
622
|
+
i = 0
|
623
|
+
n.times do
|
624
|
+
break if @off2 == 0
|
625
|
+
@off2 = search_backward_to_new_line(@off2)
|
626
|
+
i += 1
|
627
|
+
end
|
628
|
+
@off = @off2 if @off > @off2
|
629
|
+
@line2 -= i
|
630
|
+
@line = @line2 if @line2 < @line
|
631
|
+
i
|
632
|
+
end
|
633
|
+
end
|