cless 0.3.20

Sign up to get free protection for your applications and to get access to all the features.
@@ -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