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
@@ -0,0 +1,639 @@
|
|
1
|
+
class Array
|
2
|
+
def max_update(a)
|
3
|
+
a.each_with_index { |x, i|
|
4
|
+
self[i] = x if !(v = self[i]) || v < x
|
5
|
+
}
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class Attr
|
10
|
+
NAME2COLORS = {
|
11
|
+
"none" => -1,
|
12
|
+
"black" => Ncurses::COLOR_BLACK,
|
13
|
+
"red" => Ncurses::COLOR_RED,
|
14
|
+
"green" => Ncurses::COLOR_GREEN,
|
15
|
+
"yellow" => Ncurses::COLOR_YELLOW,
|
16
|
+
"blue" => Ncurses::COLOR_BLUE,
|
17
|
+
"magenta" => Ncurses::COLOR_MAGENTA,
|
18
|
+
"white" => Ncurses::COLOR_WHITE,
|
19
|
+
}
|
20
|
+
COLORS = NAME2COLORS.values
|
21
|
+
NAME2ATTR = {
|
22
|
+
"normal" => Ncurses::A_NORMAL,
|
23
|
+
"standout" => Ncurses::A_STANDOUT,
|
24
|
+
"underline" => Ncurses::A_UNDERLINE,
|
25
|
+
"dim" => Ncurses::A_DIM,
|
26
|
+
"bold" => Ncurses::A_BOLD,
|
27
|
+
}
|
28
|
+
ATTRS = NAME2ATTR.values
|
29
|
+
|
30
|
+
DEFAULTS = {
|
31
|
+
:background => NAME2COLORS["none"],
|
32
|
+
:foreground => NAME2COLORS["none"],
|
33
|
+
:attribute => NAME2ATTR["bold"],
|
34
|
+
}
|
35
|
+
|
36
|
+
def initialize(args = {}) # background, foreground, attribute
|
37
|
+
# Sanitize
|
38
|
+
DEFAULTS.each { |k, v|
|
39
|
+
instance_variable_set("@#{k}", args[k].nil? ? v : args[k])
|
40
|
+
}
|
41
|
+
@background = check_color(@background)
|
42
|
+
@foreground = check_color(@foreground)
|
43
|
+
@attribute = check_attribute(@attribute)
|
44
|
+
update_pair
|
45
|
+
end
|
46
|
+
|
47
|
+
def next_background; @background = inc(@background, COLORS); update_pair; end
|
48
|
+
def next_foreground; @foreground = inc(@foreground, COLORS); update_pair; end
|
49
|
+
def next_attribute; @attribute = inc(@attribute, ATTRS); end
|
50
|
+
|
51
|
+
def set; Ncurses.attrset(@attribute | @pair); end
|
52
|
+
def reset; Ncurses.attrset(Ncurses::A_NORMAL); end
|
53
|
+
|
54
|
+
def on; Ncurses.attron(@attribute | @pair); end
|
55
|
+
def off; Ncurses.attroff(@attribute | @pair); end
|
56
|
+
|
57
|
+
def names
|
58
|
+
r = {}
|
59
|
+
r[:foreground] = (NAME2COLORS.find { |n, v| v == @foreground } || ["black"])[0]
|
60
|
+
r[:background] = (NAME2COLORS.find { |n, v| v == @background } || ["white"])[0]
|
61
|
+
r[:attribute] = (NAME2ATTR.find { |n, v| v == @attribute } || ["normal"])[0]
|
62
|
+
r
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
def check(c, hash, ary)
|
67
|
+
case c
|
68
|
+
when Integer
|
69
|
+
ary.include?(c) ? c : ary.first
|
70
|
+
when String
|
71
|
+
(v = hash[c.downcase.strip]) ? v : ary.first
|
72
|
+
else
|
73
|
+
ary.first
|
74
|
+
end
|
75
|
+
end
|
76
|
+
def check_color(c); check(c, NAME2COLORS, COLORS); end
|
77
|
+
def check_attribute(a); check(a, NAME2ATTR, ATTRS); end
|
78
|
+
|
79
|
+
def inc(c, ary); ary[((ary.index(c) || 0)+1) % ary.size]; end
|
80
|
+
|
81
|
+
def update_pair
|
82
|
+
Ncurses.use_default_colors
|
83
|
+
Ncurses.init_pair(1, @foreground, @background)
|
84
|
+
@pair = Ncurses.COLOR_PAIR(1)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class Curses
|
89
|
+
((?a.ord)..(?z.ord)).each { |c|
|
90
|
+
const_set("CTRL_#{c.chr.upcase}", c - ?a.ord + 1)
|
91
|
+
}
|
92
|
+
ESC = ?\e.ord
|
93
|
+
SPACE = " "[0].ord # [0] useless for 1.9 but necessary for 1.8
|
94
|
+
|
95
|
+
def initialize(args = {})
|
96
|
+
Ncurses.initscr
|
97
|
+
@started = true
|
98
|
+
begin
|
99
|
+
Ncurses.start_color
|
100
|
+
Ncurses.cbreak
|
101
|
+
Ncurses.noecho
|
102
|
+
Ncurses.nonl
|
103
|
+
Ncurses.stdscr.intrflush(false)
|
104
|
+
Ncurses.stdscr.immedok(false)
|
105
|
+
Ncurses.keypad(Ncurses.stdscr, true)
|
106
|
+
|
107
|
+
|
108
|
+
yield self
|
109
|
+
ensure
|
110
|
+
@started && Ncurses.endwin
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class LineDisplay
|
116
|
+
ISNUM = /^[+-]?[\d,]*\.?\d*(?:[eE][+-]?\d+)?$/
|
117
|
+
DEFAULTS = {
|
118
|
+
:line_highlight => true, # Wether to hilight every other line
|
119
|
+
:line_highlight_period => 2,
|
120
|
+
:line_highlight_shift => 0,
|
121
|
+
:col_highlight => true, # Wether to hilight every other column
|
122
|
+
:col_highlight_period => 2,
|
123
|
+
:col_highlight_shift => 1,
|
124
|
+
:column => false, # Wether to display column number
|
125
|
+
:col_start => 1, # 1-based column numbering by default
|
126
|
+
:col_width => 50, # Maximum column width
|
127
|
+
:line => false, # Wether to display line number
|
128
|
+
:line_offset => false, # Display line offset instead of number
|
129
|
+
:col_names => false, # Wether to display column names
|
130
|
+
:col_space => 1, # Width of separator between columns
|
131
|
+
:separator => " ", # Separator caracter
|
132
|
+
:padding => " ", # Padding caracter
|
133
|
+
:right_align_re => ISNUM, # Matching columns are right align by default
|
134
|
+
:hide_ignore => false # Hide lines that are ignored
|
135
|
+
}
|
136
|
+
attr_accessor *DEFAULTS.keys
|
137
|
+
attr_accessor :col_offsets, :widths
|
138
|
+
attr_reader :prompt_line, :sizes
|
139
|
+
|
140
|
+
|
141
|
+
def separator=(s)
|
142
|
+
@separator = (!s || s.empty?) ? " " : s
|
143
|
+
end
|
144
|
+
|
145
|
+
def padding=(s)
|
146
|
+
@padding = (!s || s.empty?) ? " " : s
|
147
|
+
end
|
148
|
+
|
149
|
+
attr_accessor :col_headers, :hide_ignored
|
150
|
+
def initialize(data, args = {})
|
151
|
+
DEFAULTS.each { |k, v|
|
152
|
+
self.send("#{k}=", args[k].nil? ? v : args[k])
|
153
|
+
}
|
154
|
+
@data = data
|
155
|
+
@col_hide = []
|
156
|
+
@align = [] # column alignment: nil (i.e. auto), :left, :right, :center
|
157
|
+
@widths = [] # max column widths
|
158
|
+
@col_offsets = [] # offsets for large columns
|
159
|
+
@col_headers = nil # Actual names
|
160
|
+
@col_off = 0
|
161
|
+
@st_col = 0
|
162
|
+
@args = args
|
163
|
+
end
|
164
|
+
|
165
|
+
def initialize_curses
|
166
|
+
@attr = Attr.new(@args)
|
167
|
+
@col_names &= @col_headers # Disable col_names if no headers
|
168
|
+
@args = nil
|
169
|
+
end
|
170
|
+
|
171
|
+
def nb_lines
|
172
|
+
Ncurses.stdscr.getmaxy - 1 - (@column ? 1 : 0) - (@col_names ? 1 : 0)
|
173
|
+
end
|
174
|
+
|
175
|
+
def next_foreground; @attr.next_foreground; end
|
176
|
+
def next_background; @attr.next_background; end
|
177
|
+
def next_attribute; @attr.next_attribute; end
|
178
|
+
def attr_names; @attr.names; end
|
179
|
+
|
180
|
+
# @col_hide always store the 0 based indices of the
|
181
|
+
# columns to show or hide. @col_start is taken into account when
|
182
|
+
# setting and getting the @col_hide variables, and for display.
|
183
|
+
def col_hide_clear; @col_hide = []; end
|
184
|
+
def col_hide(*args)
|
185
|
+
args = args.collect { |x| x - @col_start }
|
186
|
+
@col_hide.push(*args)
|
187
|
+
@col_hide.uniq!
|
188
|
+
@col_hide.sort!
|
189
|
+
end
|
190
|
+
|
191
|
+
def col_hidden(with_start = true)
|
192
|
+
if with_start
|
193
|
+
@col_hide.collect { |x| x + @col_start }
|
194
|
+
else
|
195
|
+
@col_hide
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def col_show(*args)
|
200
|
+
@col_hide -= args.collect { |x| x - @col_start }
|
201
|
+
end
|
202
|
+
|
203
|
+
def col_align(align, cols)
|
204
|
+
cols.each { |x|
|
205
|
+
x -= @col_start
|
206
|
+
next if x < 0
|
207
|
+
@align[x] = align
|
208
|
+
}
|
209
|
+
end
|
210
|
+
|
211
|
+
def st_col; @st_col; end
|
212
|
+
|
213
|
+
def st_col=(n)
|
214
|
+
n = 0 if n < 0
|
215
|
+
n = @data.sizes.size if n > @data.sizes.size
|
216
|
+
return @st_col if n == @st_col
|
217
|
+
|
218
|
+
range, sign = (n > @st_col) ? [@st_col...n, 1] : [n...@st_col, -1]
|
219
|
+
@col_off += sign * @data.sizes[range].inject(0) { |acc, x|
|
220
|
+
acc + x + @sep.size
|
221
|
+
}
|
222
|
+
@col_off = [@col_off, 0].max
|
223
|
+
@col_off = 0 if n == 0
|
224
|
+
@st_col = n
|
225
|
+
end
|
226
|
+
|
227
|
+
def col_space=(n)
|
228
|
+
@col_space = [1, n.to_i].max
|
229
|
+
end
|
230
|
+
|
231
|
+
def refresh
|
232
|
+
Ncurses.move(0, 0)
|
233
|
+
Ncurses.attrset(Ncurses.COLOR_PAIR(0))
|
234
|
+
lines = refresh_prepare
|
235
|
+
|
236
|
+
i = 0
|
237
|
+
line_i = @data.line + 1
|
238
|
+
|
239
|
+
sline = refresh_column_headers
|
240
|
+
|
241
|
+
@data.lines(lines) { |l|
|
242
|
+
highlighted = @line_highlight && ((line_i - @line_highlight_shift) %
|
243
|
+
@line_highlight_period == 0)
|
244
|
+
highlighted ||= l.highlight?
|
245
|
+
highlighted ? @attr.set : @attr.reset
|
246
|
+
display_line(l, line_i, sline, highlighted)
|
247
|
+
i += 1
|
248
|
+
line_i += 1
|
249
|
+
sline += 1
|
250
|
+
}
|
251
|
+
Ncurses.clrtobot
|
252
|
+
ensure
|
253
|
+
Ncurses.refresh
|
254
|
+
end
|
255
|
+
|
256
|
+
def display_line(l, line_i, sline, highlighted, sift = true, force_center = false)
|
257
|
+
if @line
|
258
|
+
Ncurses.attron(Ncurses::A_REVERSE) if l.has_match
|
259
|
+
Ncurses.attron(Ncurses::A_UNDERLINE) if IgnoredLine === l
|
260
|
+
s = @line_offset ? l.off : line_i
|
261
|
+
Ncurses.mvaddstr(sline, 0, @col_fmt % [@linec, s])
|
262
|
+
Ncurses.attroff(Ncurses::A_REVERSE) if l.has_match
|
263
|
+
Ncurses.attroff(Ncurses::A_UNDERLINE) if IgnoredLine === l
|
264
|
+
end
|
265
|
+
|
266
|
+
if Line === l
|
267
|
+
a = sift ? l.values_at(*@col_show) : l.values_at(0..-1)
|
268
|
+
# Now always display one field at a time
|
269
|
+
ms = sift ? l.matches_at(*@col_show) : l.matches_at(0..-1)
|
270
|
+
clen = @len
|
271
|
+
@sizes.zip(ms).each_with_index { |sm, i|
|
272
|
+
chilighted = !highlighted && @col_highlight
|
273
|
+
chilighted &&= ((@st_col - @col_highlight_shift + i) % @col_highlight_period == 0)
|
274
|
+
@attr.on if chilighted
|
275
|
+
|
276
|
+
s, m = *sm
|
277
|
+
|
278
|
+
align = force_center ? :center : @align[i]
|
279
|
+
align = (@right_align_re && a[i] =~ @right_align_re) ? :right : :left if align.nil?
|
280
|
+
|
281
|
+
# Handle max column width
|
282
|
+
cwidth = [s, clen, @widths_show[i]].min # Actual width of column
|
283
|
+
lcwidth = cwidth # lcwdith is width left in column
|
284
|
+
|
285
|
+
if m
|
286
|
+
string_length = m.string.length
|
287
|
+
large = string_length > cwidth
|
288
|
+
if !large
|
289
|
+
if align == :right
|
290
|
+
Ncurses.addstr(str = (" " * (cwidth - string_length)))
|
291
|
+
lcwidth -= str.length
|
292
|
+
elsif align == :center
|
293
|
+
Ncurses.addstr(str = (" " * ((cwidth - string_length) / 2)))
|
294
|
+
lcwidth -= str.length
|
295
|
+
end
|
296
|
+
Ncurses.addstr(str = m.pre_match[0, lcwidth])
|
297
|
+
lcwidth -= str.length
|
298
|
+
Ncurses.attron(Ncurses::A_REVERSE)
|
299
|
+
Ncurses.addstr(str = m[0][0, lcwidth])
|
300
|
+
Ncurses.attroff(Ncurses::A_REVERSE)
|
301
|
+
lcwidth -= str.length
|
302
|
+
Ncurses.addstr(str = m.post_match[0, lcwidth])
|
303
|
+
lcwidth -= str.length
|
304
|
+
if align == :left
|
305
|
+
Ncurses.addstr(str = (" " * (cwidth - string_length)))
|
306
|
+
lcwidth -= str.length
|
307
|
+
elsif align == :center
|
308
|
+
space = cwidth - string_length
|
309
|
+
Ncurses.addstr(str = (" " * (space / 2 + space % 2)))
|
310
|
+
lcwidth -= str.length
|
311
|
+
end
|
312
|
+
else # large
|
313
|
+
loff = @offsets_show[i] || 0 # Offset left to skip
|
314
|
+
cap_str = proc { |x|
|
315
|
+
if x.length > loff
|
316
|
+
Ncurses.addstr(str = x[loff..-1][0, lcwidth])
|
317
|
+
lcwidth -= str.length
|
318
|
+
loff = 0
|
319
|
+
else
|
320
|
+
loff -= x.length
|
321
|
+
end
|
322
|
+
}
|
323
|
+
cap_str[m.pre_match]
|
324
|
+
Ncurses.attron(Ncurses::A_REVERSE)
|
325
|
+
cap_str[m[0]]
|
326
|
+
Ncurses.attroff(Ncurses::A_REVERSE)
|
327
|
+
cap_str[m.post_match]
|
328
|
+
Ncurses.addstr(" " * lcwidth) if lcwidth > 0
|
329
|
+
end
|
330
|
+
else # No match
|
331
|
+
col_string = a[i] || ""
|
332
|
+
if col_string.length <= cwidth
|
333
|
+
case align
|
334
|
+
when :left
|
335
|
+
str = col_string.ljust(cwidth)
|
336
|
+
when :right
|
337
|
+
str = col_string.rjust(cwidth)
|
338
|
+
when :center
|
339
|
+
str = col_string.center(cwidth)
|
340
|
+
end
|
341
|
+
else
|
342
|
+
str = (col_string[@offsets_show[i], cwidth] || "").ljust(cwidth)
|
343
|
+
end
|
344
|
+
Ncurses.addstr(str[0, cwidth])
|
345
|
+
end
|
346
|
+
clen -= cwidth; break if clen <= 0
|
347
|
+
@attr.off if chilighted
|
348
|
+
Ncurses.addstr(str = @sep[0, clen])
|
349
|
+
clen -= str.length; break if clen <= 0
|
350
|
+
}
|
351
|
+
@attr.reset if @col_highlight
|
352
|
+
Ncurses.addstr(" " * clen) if clen > 0
|
353
|
+
# else
|
354
|
+
# # No match, display all at once
|
355
|
+
# str = (@format % @sizes.zip(a).flatten).ljust(@len)[0, @len]
|
356
|
+
# Ncurses.addstr(str)
|
357
|
+
# end
|
358
|
+
else # l is an ignored line
|
359
|
+
off = @col_off
|
360
|
+
clen = @len
|
361
|
+
if @hide_ignored
|
362
|
+
Ncurses.addstr(" " * clen) if clen > 0
|
363
|
+
elsif l.has_match
|
364
|
+
m = l.matches
|
365
|
+
s = m.pre_match
|
366
|
+
if s.length > off && clen > 0
|
367
|
+
Ncurses.addstr(str = s[off, clen])
|
368
|
+
clen -= str.length
|
369
|
+
off = 0
|
370
|
+
else
|
371
|
+
off -= s.length
|
372
|
+
end
|
373
|
+
s = m[0]
|
374
|
+
if s.length > off && clen > 0
|
375
|
+
Ncurses.attron(Ncurses::A_REVERSE)
|
376
|
+
Ncurses.addstr(str = s[off, clen])
|
377
|
+
Ncurses.attroff(Ncurses::A_REVERSE)
|
378
|
+
clen -= str.length
|
379
|
+
off = 0
|
380
|
+
else
|
381
|
+
off -= s.length
|
382
|
+
end
|
383
|
+
s = m.post_match
|
384
|
+
if s.length > off && clen > 0
|
385
|
+
Ncurses.addstr(str = s[off, clen])
|
386
|
+
clen -= str.length
|
387
|
+
end
|
388
|
+
Ncurses.addstr(" " * clen) if clen > 0
|
389
|
+
else # l.has_match
|
390
|
+
s = l.str
|
391
|
+
if s.length > off && clen > 0
|
392
|
+
Ncurses.addstr(str = s[off, @len].ljust(clen)[0, clen])
|
393
|
+
clen -= str.length
|
394
|
+
end
|
395
|
+
Ncurses.addstr(" " * clen) if clen > 0
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
# Modifies @sizes
|
401
|
+
def refresh_column_headers
|
402
|
+
@col_names &= @col_headers # Disable col_names if no headers
|
403
|
+
if @column
|
404
|
+
cnumber = @col_show.map { |x| (x + @col_start).to_s }
|
405
|
+
cnumber.each_with_index { |s, i| s.replace("< #{s} >") if @sizes[i] > @widths_show[i] }
|
406
|
+
@sizes.max_update(cnumber.collect { |x| x.size })
|
407
|
+
end
|
408
|
+
if @col_names
|
409
|
+
column_headers = @col_headers.values_at(*@col_show).map { |x| x || "" }
|
410
|
+
hs = col_headers.map { |s| s.size }
|
411
|
+
@sizes.max_update(hs)
|
412
|
+
end
|
413
|
+
|
414
|
+
sline = 0
|
415
|
+
if @column
|
416
|
+
Ncurses.attron(Ncurses::A_UNDERLINE) if !@col_names
|
417
|
+
display_line(Line.new(cnumber), "", sline, false, false, true)
|
418
|
+
Ncurses.attroff(Ncurses::A_UNDERLINE) if !@col_names
|
419
|
+
sline += 1
|
420
|
+
end
|
421
|
+
if @col_names
|
422
|
+
Ncurses.attron(Ncurses::A_UNDERLINE)
|
423
|
+
display_line(Line.new(column_headers), "", sline, false, false)
|
424
|
+
Ncurses.attroff(Ncurses::A_UNDERLINE)
|
425
|
+
sline += 1
|
426
|
+
end
|
427
|
+
sline
|
428
|
+
end
|
429
|
+
|
430
|
+
def refresh_prepare
|
431
|
+
lines = nb_lines
|
432
|
+
@len = Ncurses.stdscr.getmaxx
|
433
|
+
@col_show = (@st_col..(@st_col + @col_hide.size + @len / 2)).to_a
|
434
|
+
@col_show -= @col_hide
|
435
|
+
@sizes = @data.sizes.values_at(*@col_show).map { |x| x || 0 }
|
436
|
+
@widths_show = @widths.values_at(*@col_show).map { |x| x || @col_width }
|
437
|
+
@offsets_show = @col_offsets.values_at(*@col_show).map { |x| x || 0 }
|
438
|
+
if @line
|
439
|
+
@linec = @line_offset ? @data.max_offset : (@data.line + lines)
|
440
|
+
@linec = @linec.to_s.size
|
441
|
+
end
|
442
|
+
@len -= @linec + @col_space if @line
|
443
|
+
@sep = @separator.center(@col_space, @padding)
|
444
|
+
@col_fmt = "%*s#{@sep}"
|
445
|
+
@format = @col_fmt * @col_show.size
|
446
|
+
return lines
|
447
|
+
end
|
448
|
+
|
449
|
+
def wait_status(status, wprompt)
|
450
|
+
len = Ncurses.stdscr.getmaxx
|
451
|
+
aprompt = wprompt[0, len - 1]
|
452
|
+
Ncurses.attrset(Ncurses::A_NORMAL)
|
453
|
+
Ncurses.mvaddstr(Ncurses.stdscr.getmaxy-1, 0, aprompt)
|
454
|
+
|
455
|
+
nlen = len - aprompt.length
|
456
|
+
Ncurses.attrset(Ncurses::A_BOLD)
|
457
|
+
Ncurses.addstr(status.rjust(nlen)[0, nlen])
|
458
|
+
Ncurses.attrset(Ncurses::A_NORMAL)
|
459
|
+
end
|
460
|
+
|
461
|
+
def start_active_status(status)
|
462
|
+
len = Ncurses.stdscr.getmaxx
|
463
|
+
astatus = status[0, len - 2]
|
464
|
+
Ncurses.attrset(Ncurses::A_NORMAL)
|
465
|
+
Ncurses.mvaddstr(Ncurses.stdscr.getmaxy-1, 0, status[0, len - 2])
|
466
|
+
|
467
|
+
nlen = len - astatus.length
|
468
|
+
Ncurses.addstr('|'.rjust(nlen)[0, nlen])
|
469
|
+
Ncurses.refresh
|
470
|
+
|
471
|
+
@active_th = Thread.new {
|
472
|
+
loop {
|
473
|
+
['/', '-', '\\', '|'].each { |c|
|
474
|
+
sleep(0.5)
|
475
|
+
Ncurses.mvaddstr(Ncurses.stdscr.getmaxy-1, len - 1, c)
|
476
|
+
Ncurses.refresh
|
477
|
+
}
|
478
|
+
}
|
479
|
+
}
|
480
|
+
end
|
481
|
+
|
482
|
+
def end_active_status
|
483
|
+
@active_th.kill if @active_th
|
484
|
+
@active_th = nil
|
485
|
+
end
|
486
|
+
|
487
|
+
def flush
|
488
|
+
Ncurses.refresh
|
489
|
+
end
|
490
|
+
|
491
|
+
def prompt(ps, opts = {})
|
492
|
+
stdscr = Ncurses.stdscr
|
493
|
+
len = stdscr.getmaxx
|
494
|
+
Ncurses.attrset(Ncurses.COLOR_PAIR(0))
|
495
|
+
Ncurses.mvaddstr(stdscr.getmaxy-1, 0, ps.ljust(len)[0, len])
|
496
|
+
s, pos, key = read_line(stdscr.getmaxy-1, ps.length, opts)
|
497
|
+
Ncurses.mvaddstr(stdscr.getmaxy-1, 0, " " * len)
|
498
|
+
return (key == ?\e.ord) ? nil : s
|
499
|
+
rescue KeyboardInterrupt
|
500
|
+
return nil
|
501
|
+
end
|
502
|
+
|
503
|
+
# read_line returns an array
|
504
|
+
# [string, last_cursor_position_in_string, keycode_of_terminating_enter_key].
|
505
|
+
# options recognize:
|
506
|
+
# :window What window to work with
|
507
|
+
# :max_len Width of window
|
508
|
+
# :string Initial value
|
509
|
+
# :cursor_pos Initial cursor position
|
510
|
+
# :history Array containing the history to use with up and down arrows
|
511
|
+
def read_line(y, x, opts = {})
|
512
|
+
window = opts[:window] || Ncurses.stdscr
|
513
|
+
max_len = opts[:max_len] || (window.getmaxx - x - 1)
|
514
|
+
@prompt_line = opts[:init] || ""
|
515
|
+
cursor_pos = opts[:cursor_pos] || @prompt_line.size
|
516
|
+
other = opts[:other]
|
517
|
+
extra = opts[:extra]
|
518
|
+
history = opts[:history] || []
|
519
|
+
history_pos = 0
|
520
|
+
save_line = ""
|
521
|
+
|
522
|
+
loop do
|
523
|
+
window.mvaddstr(y,x,@prompt_line)
|
524
|
+
window.move(y,x+cursor_pos)
|
525
|
+
|
526
|
+
extra.call if extra
|
527
|
+
|
528
|
+
ch = window.getch
|
529
|
+
case ch
|
530
|
+
when Ncurses::KEY_LEFT, Curses::CTRL_B
|
531
|
+
cursor_pos = [0, cursor_pos-1].max
|
532
|
+
when Ncurses::KEY_RIGHT, Curses::CTRL_F
|
533
|
+
cursor_pos = [@prompt_line.length, cursor_pos+1].min
|
534
|
+
when Ncurses::KEY_ENTER, ?\n.ord, ?\r.ord
|
535
|
+
return @prompt_line, cursor_pos, ch # Which return key has been used?
|
536
|
+
when Ncurses::KEY_HOME, Curses::CTRL_A
|
537
|
+
cursor_pos = 0
|
538
|
+
when Ncurses::KEY_END, Curses::CTRL_E
|
539
|
+
cursor_pos = [max_len, @prompt_line.length].min
|
540
|
+
when Ncurses::KEY_DC, Curses::CTRL_D
|
541
|
+
@prompt_line.slice!(cursor_pos)
|
542
|
+
window.mvaddstr(y, x+@prompt_line.length, " ")
|
543
|
+
when Curses::CTRL_K
|
544
|
+
window.mvaddstr(y, x+cursor_pos, " " * (@prompt_line.length - cursor_pos))
|
545
|
+
@prompt_line = @prompt_line[0, cursor_pos]
|
546
|
+
when Ncurses::KEY_BACKSPACE, ?\b.ord
|
547
|
+
if cursor_pos > 0
|
548
|
+
cursor_pos -= 1
|
549
|
+
@prompt_line.slice!(cursor_pos)
|
550
|
+
window.mvaddstr(y, x+@prompt_line.length, " ")
|
551
|
+
else
|
552
|
+
return "", 0, ?\e.ord
|
553
|
+
end
|
554
|
+
when ?\e.ord # ESCAPE
|
555
|
+
return "", 0, ch
|
556
|
+
when Ncurses::KEY_UP
|
557
|
+
if history_pos < history.size
|
558
|
+
save_line = @prompt_line if history_pos == 0
|
559
|
+
window.mvaddstr(y, x, " " * @prompt_line.length)
|
560
|
+
@prompt_line = history[history_pos].dup
|
561
|
+
history_pos += 1
|
562
|
+
cursor_pos = @prompt_line.size
|
563
|
+
end
|
564
|
+
when Ncurses::KEY_DOWN
|
565
|
+
if history_pos > 0
|
566
|
+
window.mvaddstr(y, x, " " * @prompt_line.length)
|
567
|
+
history_pos -= 1
|
568
|
+
@prompt_line = history_pos > 0 ? history[history_pos - 1].dup : save_line
|
569
|
+
cursor_pos = @prompt_line.size
|
570
|
+
end
|
571
|
+
when C::SPACE..255 # remaining printables
|
572
|
+
if (cursor_pos < max_len)
|
573
|
+
@prompt_line[cursor_pos,0] = ch.chr
|
574
|
+
cursor_pos += 1
|
575
|
+
else
|
576
|
+
Ncurses.beep
|
577
|
+
end
|
578
|
+
end
|
579
|
+
other[ch] if other
|
580
|
+
end
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
class CommandSubWindow
|
585
|
+
def initialize(width = 0, height = 15, bordery = 5, borderx = 10)
|
586
|
+
maxy, maxx = Ncurses.stdscr.getmaxy, Ncurses.stdscr.getmaxx
|
587
|
+
@nlines = [height + 2, maxy - 2 * bordery].min
|
588
|
+
@ncols = [width + 2, maxx - 2 * borderx].min
|
589
|
+
@win = Ncurses.stdscr.subwin(@nlines, @ncols, bordery, borderx)
|
590
|
+
new_list([])
|
591
|
+
end
|
592
|
+
|
593
|
+
def destroy
|
594
|
+
@win.delwin if @win
|
595
|
+
end
|
596
|
+
|
597
|
+
def new_list(list)
|
598
|
+
@list = list
|
599
|
+
@top_item = 0
|
600
|
+
@cur_item = 0
|
601
|
+
display_list
|
602
|
+
end
|
603
|
+
|
604
|
+
def display_list
|
605
|
+
return unless @win
|
606
|
+
@win.box(0, 0)
|
607
|
+
len = @ncols - 2
|
608
|
+
height = @nlines - 2
|
609
|
+
str = " Commands "
|
610
|
+
@win.mvaddstr(0, (len - str.size) / 2, str) if len > str.size
|
611
|
+
i = 1
|
612
|
+
@list[@top_item..-1].each { |s|
|
613
|
+
break if i > height
|
614
|
+
@win.attron(Ncurses::A_REVERSE) if @cur_item + 1 == i + @top_item
|
615
|
+
@win.mvaddstr(i, 1, s.ljust(len)[0, len])
|
616
|
+
@win.attroff(Ncurses::A_REVERSE) if @cur_item + 1 == i + @top_item
|
617
|
+
i += 1
|
618
|
+
}
|
619
|
+
empty = " " * len
|
620
|
+
i.upto(height) { |j|
|
621
|
+
@win.mvaddstr(j, 1, empty)
|
622
|
+
}
|
623
|
+
@win.wsyncup
|
624
|
+
end
|
625
|
+
|
626
|
+
def next_item
|
627
|
+
@cur_item += 1 if @cur_item < @list.size - 1
|
628
|
+
@top_item += 1 if (@cur_item - @top_item).abs >= @nlines - 2
|
629
|
+
display_list
|
630
|
+
end
|
631
|
+
|
632
|
+
def previous_item
|
633
|
+
@cur_item -= 1 if @cur_item > 0
|
634
|
+
@top_item -= 1 if @cur_item < @top_item
|
635
|
+
display_list
|
636
|
+
end
|
637
|
+
|
638
|
+
def item; @list[@cur_item]; end
|
639
|
+
end
|