rcurses 4.8.3 → 4.9.1
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 +4 -4
- data/README.md +565 -237
- metadata +4 -13
- data/examples/basic_panes.rb +0 -43
- data/examples/focus_panes.rb +0 -42
- data/lib/rcurses/cursor.rb +0 -48
- data/lib/rcurses/general.rb +0 -6
- data/lib/rcurses/input.rb +0 -127
- data/lib/rcurses/pane.rb +0 -662
- data/lib/rcurses.rb +0 -61
- data/lib/string_extensions.rb +0 -160
data/lib/rcurses/pane.rb
DELETED
@@ -1,662 +0,0 @@
|
|
1
|
-
module Rcurses
|
2
|
-
# A simple display_width function that approximates how many columns a string occupies.
|
3
|
-
# This is a simplified version that may need adjustments for full Unicode support.
|
4
|
-
def self.display_width(str)
|
5
|
-
width = 0
|
6
|
-
str.each_char do |char|
|
7
|
-
cp = char.ord
|
8
|
-
if cp == 0
|
9
|
-
# NUL – no width
|
10
|
-
elsif cp < 32 || (cp >= 0x7F && cp < 0xA0)
|
11
|
-
# Control characters: no width
|
12
|
-
width += 0
|
13
|
-
# Approximate common wide ranges:
|
14
|
-
elsif (cp >= 0x1100 && cp <= 0x115F) ||
|
15
|
-
cp == 0x2329 || cp == 0x232A ||
|
16
|
-
(cp >= 0x2E80 && cp <= 0xA4CF) ||
|
17
|
-
(cp >= 0xAC00 && cp <= 0xD7A3) ||
|
18
|
-
(cp >= 0xF900 && cp <= 0xFAFF) ||
|
19
|
-
(cp >= 0xFE10 && cp <= 0xFE19) ||
|
20
|
-
(cp >= 0xFE30 && cp <= 0xFE6F) ||
|
21
|
-
(cp >= 0xFF00 && cp <= 0xFF60) ||
|
22
|
-
(cp >= 0xFFE0 && cp <= 0xFFE6)
|
23
|
-
width += 2
|
24
|
-
else
|
25
|
-
width += 1
|
26
|
-
end
|
27
|
-
end
|
28
|
-
width
|
29
|
-
end
|
30
|
-
|
31
|
-
class Pane
|
32
|
-
require 'clipboard' # Ensure the 'clipboard' gem is installed
|
33
|
-
include Cursor
|
34
|
-
include Input
|
35
|
-
attr_accessor :x, :y, :w, :h, :fg, :bg
|
36
|
-
attr_accessor :border, :scroll, :text, :ix, :index, :align, :prompt
|
37
|
-
attr_accessor :moreup, :moredown
|
38
|
-
attr_accessor :record, :history
|
39
|
-
|
40
|
-
def initialize(x = 1, y = 1, w = 1, h = 1, fg = nil, bg = nil)
|
41
|
-
@max_h, @max_w = IO.console.winsize
|
42
|
-
@x = x
|
43
|
-
@y = y
|
44
|
-
@w = w
|
45
|
-
@h = h
|
46
|
-
@fg, @bg = fg, bg
|
47
|
-
@text = "" # Initialize text variable
|
48
|
-
@align = "l" # Default alignment
|
49
|
-
@scroll = true # Enable scroll indicators
|
50
|
-
@prompt = "" # Prompt for editline
|
51
|
-
@ix = 0 # Starting text line index
|
52
|
-
@prev_frame = nil # Holds the previously rendered frame (array of lines)
|
53
|
-
@line = 0 # For cursor tracking during editing:
|
54
|
-
@pos = 0 # For cursor tracking during editing:
|
55
|
-
@record = false # Don't record history unless explicitly set to true
|
56
|
-
@history = [] # History array
|
57
|
-
end
|
58
|
-
|
59
|
-
def text=(new_text)
|
60
|
-
(@history << @text) if @record && @text
|
61
|
-
@text = new_text
|
62
|
-
end
|
63
|
-
|
64
|
-
def ask(prompt, text)
|
65
|
-
@prompt = prompt
|
66
|
-
@text = text
|
67
|
-
editline
|
68
|
-
(@history << @text) if @record && !@text.empty?
|
69
|
-
@text
|
70
|
-
end
|
71
|
-
|
72
|
-
def say(text)
|
73
|
-
(@history << text) if @record && !text.empty?
|
74
|
-
@text = text
|
75
|
-
@ix = 0
|
76
|
-
refresh
|
77
|
-
end
|
78
|
-
|
79
|
-
def clear
|
80
|
-
@text = ""
|
81
|
-
@ix = 0
|
82
|
-
full_refresh
|
83
|
-
end
|
84
|
-
|
85
|
-
def move(dx, dy)
|
86
|
-
@x += dx
|
87
|
-
@y += dy
|
88
|
-
refresh
|
89
|
-
end
|
90
|
-
|
91
|
-
def linedown
|
92
|
-
@ix += 1
|
93
|
-
@ix = @text.split("\n").length if @ix > @text.split("\n").length - 1
|
94
|
-
refresh
|
95
|
-
end
|
96
|
-
|
97
|
-
def lineup
|
98
|
-
@ix -= 1
|
99
|
-
@ix = 0 if @ix < 0
|
100
|
-
refresh
|
101
|
-
end
|
102
|
-
|
103
|
-
def pagedown
|
104
|
-
@ix = @ix + @h - 1
|
105
|
-
@ix = @text.split("\n").length - @h if @ix > @text.split("\n").length - @h
|
106
|
-
refresh
|
107
|
-
end
|
108
|
-
|
109
|
-
def pageup
|
110
|
-
@ix = @ix - @h + 1
|
111
|
-
@ix = 0 if @ix < 0
|
112
|
-
refresh
|
113
|
-
end
|
114
|
-
|
115
|
-
def bottom
|
116
|
-
@ix = @text.split("\n").length - @h
|
117
|
-
refresh
|
118
|
-
end
|
119
|
-
|
120
|
-
def top
|
121
|
-
@ix = 0
|
122
|
-
refresh
|
123
|
-
end
|
124
|
-
|
125
|
-
# full_refresh forces a complete repaint.
|
126
|
-
def full_refresh(cont = @text)
|
127
|
-
@prev_frame = nil
|
128
|
-
refresh(cont)
|
129
|
-
end
|
130
|
-
|
131
|
-
# Refresh only the border
|
132
|
-
def border_refresh
|
133
|
-
left_col = @x - 1
|
134
|
-
right_col = @x + @w
|
135
|
-
top_row = @y - 1
|
136
|
-
bottom_row = @y + @h
|
137
|
-
|
138
|
-
if @border
|
139
|
-
fmt = [@fg.to_s, @bg.to_s].join(',')
|
140
|
-
top = ("┌" + "─" * @w + "┐").c(fmt)
|
141
|
-
STDOUT.print "\e[#{top_row};#{left_col}H" + top
|
142
|
-
(0...@h).each do |i|
|
143
|
-
row = @y + i
|
144
|
-
STDOUT.print "\e[#{row};#{left_col}H" + "│".c(fmt)
|
145
|
-
STDOUT.print "\e[#{row};#{right_col}H" + "│".c(fmt)
|
146
|
-
end
|
147
|
-
bottom = ("└" + "─" * @w + "┘").c(fmt)
|
148
|
-
STDOUT.print "\e[#{bottom_row};#{left_col}H" + bottom
|
149
|
-
else
|
150
|
-
STDOUT.print "\e[#{top_row};#{left_col}H" + " " * (@w + 2)
|
151
|
-
(0...@h).each do |i|
|
152
|
-
row = @y + i
|
153
|
-
STDOUT.print "\e[#{row};#{left_col}H" + " "
|
154
|
-
STDOUT.print "\e[#{row};#{right_col}H" + " "
|
155
|
-
end
|
156
|
-
STDOUT.print "\e[#{bottom_row};#{left_col}H" + " " * (@w + 2)
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
# Diff-based refresh that minimizes flicker.
|
161
|
-
# In this updated version we lazily process only the raw lines required to fill the pane.
|
162
|
-
def refresh(cont = @text)
|
163
|
-
@max_h, @max_w = IO.console.winsize
|
164
|
-
|
165
|
-
if @border
|
166
|
-
@w = @max_w - 2 if @w > @max_w - 2
|
167
|
-
@h = @max_h - 2 if @h > @max_h - 2
|
168
|
-
@x = 2 if @x < 2; @x = @max_w - @w if @x + @w > @max_w
|
169
|
-
@y = 2 if @y < 2; @y = @max_h - @h if @y + @h > @max_h
|
170
|
-
else
|
171
|
-
@w = @max_w if @w > @max_w
|
172
|
-
@h = @max_h if @h > @max_h
|
173
|
-
@x = 1 if @x < 1; @x = @max_w - @w + 1 if @x + @w > @max_w + 1
|
174
|
-
@y = 1 if @y < 1; @y = @max_h - @h + 1 if @y + @h > @max_h + 1
|
175
|
-
end
|
176
|
-
|
177
|
-
o_row, o_col = pos
|
178
|
-
|
179
|
-
# Hide cursor, disable auto-wrap, reset all SGR and scroll margins
|
180
|
-
# (so stray underline, scroll regions, etc. can’t leak out)
|
181
|
-
STDOUT.print "\e[?25l\e[?7l\e[0m\e[r"
|
182
|
-
|
183
|
-
fmt = [@fg.to_s, @bg.to_s].join(',')
|
184
|
-
|
185
|
-
# Lazy evaluation: If the content or pane width has changed, reinitialize the lazy cache.
|
186
|
-
if !defined?(@cached_text) || @cached_text != cont || @cached_w != @w
|
187
|
-
@raw_txt = cont.split("\n").map { |line| line.chomp("\r") }
|
188
|
-
@lazy_txt = [] # This will hold the processed (wrapped) lines as needed.
|
189
|
-
@lazy_index = 0 # Pointer to the next raw line to process.
|
190
|
-
@cached_text = cont.dup
|
191
|
-
@cached_w = @w
|
192
|
-
end
|
193
|
-
|
194
|
-
content_rows = @h
|
195
|
-
# Ensure we have processed enough lines for the current scroll position + visible area.
|
196
|
-
required_lines = @ix + content_rows
|
197
|
-
while @lazy_txt.size < required_lines && @lazy_index < @raw_txt.size
|
198
|
-
raw_line = @raw_txt[@lazy_index]
|
199
|
-
# If the raw line is short, no wrapping is needed.
|
200
|
-
if raw_line.respond_to?(:pure) && Rcurses.display_width(raw_line.pure) < @w
|
201
|
-
processed = [raw_line]
|
202
|
-
else
|
203
|
-
processed = split_line_with_ansi(raw_line, @w)
|
204
|
-
end
|
205
|
-
@lazy_txt.concat(processed)
|
206
|
-
@lazy_index += 1
|
207
|
-
end
|
208
|
-
@txt = @lazy_txt
|
209
|
-
|
210
|
-
@ix = @txt.length - 1 if @ix > @txt.length - 1
|
211
|
-
@ix = 0 if @ix < 0
|
212
|
-
|
213
|
-
new_frame = []
|
214
|
-
|
215
|
-
content_rows.times do |i|
|
216
|
-
line_str = ""
|
217
|
-
l = @ix + i
|
218
|
-
if @txt[l].to_s != ""
|
219
|
-
pl = @w - Rcurses.display_width(@txt[l].pure)
|
220
|
-
pl = 0 if pl < 0
|
221
|
-
hl = pl / 2
|
222
|
-
case @align
|
223
|
-
when "l"
|
224
|
-
line_str = @txt[l].c(fmt) + " ".c(fmt) * pl
|
225
|
-
when "r"
|
226
|
-
line_str = " ".c(fmt) * pl + @txt[l].c(fmt)
|
227
|
-
when "c"
|
228
|
-
line_str = " ".c(fmt) * hl + @txt[l].c(fmt) + " ".c(fmt) * (pl - hl)
|
229
|
-
end
|
230
|
-
else
|
231
|
-
line_str = " ".c(fmt) * @w
|
232
|
-
end
|
233
|
-
|
234
|
-
new_frame << line_str
|
235
|
-
end
|
236
|
-
|
237
|
-
diff_buf = ""
|
238
|
-
new_frame.each_with_index do |line, i|
|
239
|
-
row_num = @y + i
|
240
|
-
col_num = @x
|
241
|
-
if @prev_frame.nil? || @prev_frame[i] != line
|
242
|
-
diff_buf << "\e[#{row_num};#{col_num}H" << line
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
# restore wrap, then also reset SGR and scroll-region one more time
|
247
|
-
diff_buf << "\e[#{o_row};#{o_col}H\e[?7h\e[0m\e[r"
|
248
|
-
print diff_buf
|
249
|
-
@prev_frame = new_frame
|
250
|
-
|
251
|
-
# Draw scroll markers after printing the frame.
|
252
|
-
if @scroll
|
253
|
-
marker_col = @x + @w - 1
|
254
|
-
if @ix > 0
|
255
|
-
print "\e[#{@y};#{marker_col}H" + "∆".c(fmt)
|
256
|
-
end
|
257
|
-
# If there are more processed lines than fit in the pane
|
258
|
-
# OR there remain raw lines to process, show the down marker.
|
259
|
-
if (@txt.length - @ix) > @h || (@lazy_index < @raw_txt.size)
|
260
|
-
print "\e[#{@y + @h - 1};#{marker_col}H" + "∇".c(fmt)
|
261
|
-
end
|
262
|
-
end
|
263
|
-
|
264
|
-
if @border
|
265
|
-
# top
|
266
|
-
print "\e[#{@y - 1};#{@x - 1}H" + ("┌" + "─" * @w + "┐").c(fmt)
|
267
|
-
# sides
|
268
|
-
(0...@h).each do |i|
|
269
|
-
print "\e[#{@y + i};#{@x - 1}H" + "│".c(fmt)
|
270
|
-
print "\e[#{@y + i};#{@x + @w}H" + "│".c(fmt)
|
271
|
-
end
|
272
|
-
# bottom
|
273
|
-
print "\e[#{@y + @h};#{@x - 1}H" + ("└" + "─" * @w + "┘").c(fmt)
|
274
|
-
end
|
275
|
-
|
276
|
-
new_frame.join("\n")
|
277
|
-
end
|
278
|
-
|
279
|
-
def textformat(cont)
|
280
|
-
# This method is no longer used in refresh since we process lazily,
|
281
|
-
# but is kept here if needed elsewhere.
|
282
|
-
lines = cont.split("\n")
|
283
|
-
result = []
|
284
|
-
lines.each do |line|
|
285
|
-
split_lines = split_line_with_ansi(line, @w)
|
286
|
-
result.concat(split_lines)
|
287
|
-
end
|
288
|
-
result
|
289
|
-
end
|
290
|
-
|
291
|
-
def right
|
292
|
-
if @pos < @txt[@ix + @line].length
|
293
|
-
@pos += 1
|
294
|
-
if @pos == @w
|
295
|
-
@pos = 0
|
296
|
-
if @line == @h - 1
|
297
|
-
@ix += 1
|
298
|
-
else
|
299
|
-
@line += 1
|
300
|
-
end
|
301
|
-
end
|
302
|
-
else
|
303
|
-
if @line == @h - 1
|
304
|
-
@ix += 1 unless @ix >= @txt.length - @h
|
305
|
-
@pos = 0
|
306
|
-
elsif @line + @ix + 1 < @txt.length
|
307
|
-
@line += 1
|
308
|
-
@pos = 0
|
309
|
-
end
|
310
|
-
end
|
311
|
-
end
|
312
|
-
|
313
|
-
def left
|
314
|
-
if @pos == 0
|
315
|
-
if @line == 0
|
316
|
-
unless @ix == 0
|
317
|
-
@ix -= 1
|
318
|
-
@pos = @txt[@ix + @line].length
|
319
|
-
end
|
320
|
-
else
|
321
|
-
@line -= 1
|
322
|
-
@pos = @txt[@ix + @line].length
|
323
|
-
end
|
324
|
-
else
|
325
|
-
@pos -= 1
|
326
|
-
end
|
327
|
-
end
|
328
|
-
|
329
|
-
def up
|
330
|
-
if @line == 0
|
331
|
-
@ix -= 1 unless @ix == 0
|
332
|
-
else
|
333
|
-
@line -= 1
|
334
|
-
end
|
335
|
-
begin
|
336
|
-
@pos = [@pos, @txt[@ix + @line].length].min
|
337
|
-
rescue
|
338
|
-
end
|
339
|
-
end
|
340
|
-
|
341
|
-
def down
|
342
|
-
if @line == @h - 1
|
343
|
-
@ix += 1 unless @ix + @line >= @txt.length - 1
|
344
|
-
elsif @line + @ix + 1 < @txt.length
|
345
|
-
@line += 1
|
346
|
-
end
|
347
|
-
begin
|
348
|
-
@pos = [@pos, @txt[@ix + @line].length].min
|
349
|
-
rescue
|
350
|
-
end
|
351
|
-
end
|
352
|
-
|
353
|
-
def parse(cont)
|
354
|
-
cont.gsub!(/\*(.+?)\*/, '\1'.b)
|
355
|
-
cont.gsub!(/\/(.+?)\//, '\1'.i)
|
356
|
-
cont.gsub!(/_(.+?)_/, '\1'.u)
|
357
|
-
cont.gsub!(/#(.+?)#/, '\1'.r)
|
358
|
-
cont.gsub!(/<([^|]+)\|([^>]+)>/) do
|
359
|
-
text = $2; codes = $1
|
360
|
-
text.c(codes)
|
361
|
-
end
|
362
|
-
cont
|
363
|
-
end
|
364
|
-
|
365
|
-
def edit
|
366
|
-
begin
|
367
|
-
STDIN.cooked! rescue nil
|
368
|
-
STDIN.echo = true rescue nil
|
369
|
-
# Prepare content with visible newline markers
|
370
|
-
content = @text.pure.gsub("\n", "¬\n")
|
371
|
-
# Reset editing cursor state
|
372
|
-
@ix = 0
|
373
|
-
@line = 0
|
374
|
-
@pos = 0
|
375
|
-
# Initial render sets @txt internally for display and cursor math
|
376
|
-
refresh(content)
|
377
|
-
Rcurses::Cursor.show
|
378
|
-
input_char = ''
|
379
|
-
|
380
|
-
while input_char != 'ESC'
|
381
|
-
# Move the terminal cursor to the logical text cursor
|
382
|
-
row(@y + @line)
|
383
|
-
col(@x + @pos)
|
384
|
-
input_char = getchr(flush: false)
|
385
|
-
case input_char
|
386
|
-
when 'C-L'
|
387
|
-
@align = 'l'
|
388
|
-
when 'C-R'
|
389
|
-
@align = 'r'
|
390
|
-
when 'C-C'
|
391
|
-
@align = 'c'
|
392
|
-
when 'C-Y'
|
393
|
-
Clipboard.copy(@text.pure)
|
394
|
-
when 'C-S'
|
395
|
-
content = content.gsub('¬', "\n")
|
396
|
-
content = parse(content)
|
397
|
-
@text = content
|
398
|
-
input_char = 'ESC'
|
399
|
-
when 'DEL'
|
400
|
-
posx = calculate_posx
|
401
|
-
content.slice!(posx)
|
402
|
-
when 'BACK'
|
403
|
-
if @pos > 0
|
404
|
-
left
|
405
|
-
posx = calculate_posx
|
406
|
-
content.slice!(posx)
|
407
|
-
end
|
408
|
-
when 'WBACK'
|
409
|
-
while @pos > 0 && content[calculate_posx - 1] != ' '
|
410
|
-
left
|
411
|
-
posx = calculate_posx
|
412
|
-
content.slice!(posx)
|
413
|
-
end
|
414
|
-
when 'C-K'
|
415
|
-
line_start_pos = calculate_line_start_pos
|
416
|
-
line_length = @txt[@ix + @line]&.length || 0
|
417
|
-
content.slice!(line_start_pos + @pos, line_length - @pos)
|
418
|
-
when 'UP'
|
419
|
-
up
|
420
|
-
when 'DOWN'
|
421
|
-
down
|
422
|
-
when 'RIGHT'
|
423
|
-
right
|
424
|
-
when 'LEFT'
|
425
|
-
left
|
426
|
-
when 'HOME'
|
427
|
-
@pos = 0
|
428
|
-
when 'END'
|
429
|
-
current_line_length = @txt[@ix + @line]&.length || 0
|
430
|
-
@pos = current_line_length
|
431
|
-
when 'C-HOME'
|
432
|
-
@ix = 0; @line = 0; @pos = 0
|
433
|
-
when 'C-END'
|
434
|
-
total_lines = @txt.length
|
435
|
-
@ix = [total_lines - @h, 0].max
|
436
|
-
@line = [@h - 1, total_lines - @ix - 1].min
|
437
|
-
current_line_length = @txt[@ix + @line]&.length || 0
|
438
|
-
@pos = current_line_length
|
439
|
-
when 'ENTER'
|
440
|
-
posx = calculate_posx
|
441
|
-
content.insert(posx, "¬\n")
|
442
|
-
right
|
443
|
-
when /^.$/
|
444
|
-
posx = calculate_posx
|
445
|
-
content.insert(posx, input_char)
|
446
|
-
right
|
447
|
-
end
|
448
|
-
|
449
|
-
# Handle any buffered input
|
450
|
-
while IO.select([$stdin], nil, nil, 0)
|
451
|
-
input_char = $stdin.read_nonblock(1) rescue nil
|
452
|
-
break unless input_char
|
453
|
-
posx = calculate_posx
|
454
|
-
content.insert(posx, input_char)
|
455
|
-
right
|
456
|
-
end
|
457
|
-
|
458
|
-
# Re-render without overwriting the internal @txt
|
459
|
-
refresh(content)
|
460
|
-
Rcurses::Cursor.show
|
461
|
-
end
|
462
|
-
ensure
|
463
|
-
STDIN.raw! rescue nil
|
464
|
-
STDIN.echo = false rescue nil
|
465
|
-
while IO.select([$stdin], nil, nil, 0)
|
466
|
-
$stdin.read_nonblock(4096) rescue break
|
467
|
-
end
|
468
|
-
end
|
469
|
-
Rcurses::Cursor.hide
|
470
|
-
end
|
471
|
-
|
472
|
-
def editline
|
473
|
-
begin
|
474
|
-
STDIN.cooked! rescue nil
|
475
|
-
STDIN.echo = true rescue nil
|
476
|
-
Rcurses::Cursor.show
|
477
|
-
@x = [[@x, 1].max, @max_w - @w + 1].min
|
478
|
-
@y = [[@y, 1].max, @max_h - @h + 1].min
|
479
|
-
@scroll = false
|
480
|
-
@ix = 0
|
481
|
-
row(@y)
|
482
|
-
fmt = [@fg.to_s, @bg.to_s].join(',')
|
483
|
-
col(@x)
|
484
|
-
print @prompt.c(fmt)
|
485
|
-
prompt_len = @prompt.pure.length
|
486
|
-
content_len = @w - prompt_len
|
487
|
-
cont = @text.pure.slice(0, content_len)
|
488
|
-
@pos = cont.length
|
489
|
-
chr = ''
|
490
|
-
history_index = @history.size
|
491
|
-
|
492
|
-
while chr != 'ESC'
|
493
|
-
col(@x + prompt_len)
|
494
|
-
cont = cont.slice(0, content_len)
|
495
|
-
print cont.ljust(content_len).c(fmt)
|
496
|
-
col(@x + prompt_len + @pos)
|
497
|
-
chr = getchr(flush: false)
|
498
|
-
case chr
|
499
|
-
when 'LEFT'
|
500
|
-
@pos -= 1 if @pos > 0
|
501
|
-
when 'RIGHT'
|
502
|
-
@pos += 1 if @pos < cont.length
|
503
|
-
when 'HOME'
|
504
|
-
@pos = 0
|
505
|
-
when 'END'
|
506
|
-
@pos = cont.length
|
507
|
-
when 'DEL'
|
508
|
-
cont[@pos] = '' if @pos < cont.length
|
509
|
-
when 'BACK'
|
510
|
-
if @pos > 0
|
511
|
-
@pos -= 1
|
512
|
-
cont[@pos] = ''
|
513
|
-
end
|
514
|
-
when 'WBACK'
|
515
|
-
while @pos > 0 && cont[@pos - 1] != ' '
|
516
|
-
@pos -= 1
|
517
|
-
cont[@pos] = ''
|
518
|
-
end
|
519
|
-
when 'C-K'
|
520
|
-
cont = ''
|
521
|
-
@pos = 0
|
522
|
-
when 'ENTER'
|
523
|
-
@text = cont
|
524
|
-
chr = 'ESC'
|
525
|
-
when 'UP'
|
526
|
-
if @history.any? && history_index > 0
|
527
|
-
history_index -= 1
|
528
|
-
cont = @history[history_index].pure.slice(0, content_len)
|
529
|
-
@pos = cont.length
|
530
|
-
end
|
531
|
-
when 'DOWN'
|
532
|
-
if history_index < @history.size - 1
|
533
|
-
history_index += 1
|
534
|
-
cont = @history[history_index].pure.slice(0, content_len)
|
535
|
-
@pos = cont.length
|
536
|
-
elsif history_index == @history.size - 1
|
537
|
-
history_index += 1
|
538
|
-
cont = ""
|
539
|
-
@pos = 0
|
540
|
-
end
|
541
|
-
when /^.$/
|
542
|
-
if @pos < content_len
|
543
|
-
cont.insert(@pos, chr)
|
544
|
-
@pos += 1
|
545
|
-
end
|
546
|
-
end
|
547
|
-
|
548
|
-
while IO.select([$stdin], nil, nil, 0)
|
549
|
-
chr = $stdin.read_nonblock(1) rescue nil
|
550
|
-
break unless chr
|
551
|
-
if @pos < content_len
|
552
|
-
cont.insert(@pos, chr)
|
553
|
-
@pos += 1
|
554
|
-
end
|
555
|
-
end
|
556
|
-
end
|
557
|
-
ensure
|
558
|
-
STDIN.raw! rescue nil
|
559
|
-
STDIN.echo = false rescue nil
|
560
|
-
while IO.select([$stdin], nil, nil, 0)
|
561
|
-
$stdin.read_nonblock(4096) rescue break
|
562
|
-
end
|
563
|
-
end
|
564
|
-
prompt_len = @prompt.pure.length
|
565
|
-
new_col = @x + prompt_len + (@pos > 0 ? @pos - 1 : 0)
|
566
|
-
col(new_col)
|
567
|
-
Rcurses::Cursor.hide
|
568
|
-
end
|
569
|
-
|
570
|
-
private
|
571
|
-
|
572
|
-
def flush_stdin
|
573
|
-
while IO.select([$stdin], nil, nil, 0.005)
|
574
|
-
begin
|
575
|
-
$stdin.read_nonblock(1024)
|
576
|
-
rescue IO::WaitReadable, EOFError
|
577
|
-
break
|
578
|
-
end
|
579
|
-
end
|
580
|
-
end
|
581
|
-
|
582
|
-
def calculate_posx
|
583
|
-
total_length = 0
|
584
|
-
(@ix + @line).times do |i|
|
585
|
-
total_length += Rcurses.display_width(@txt[i].pure) + 1 # +1 for newline
|
586
|
-
end
|
587
|
-
total_length += @pos
|
588
|
-
total_length
|
589
|
-
end
|
590
|
-
|
591
|
-
def calculate_line_start_pos
|
592
|
-
total_length = 0
|
593
|
-
(@ix + @line).times do |i|
|
594
|
-
total_length += Rcurses.display_width(@txt[i].pure) + 1
|
595
|
-
end
|
596
|
-
total_length
|
597
|
-
end
|
598
|
-
|
599
|
-
def split_line_with_ansi(line, w)
|
600
|
-
open_sequences = {
|
601
|
-
"\e[1m" => "\e[22m",
|
602
|
-
"\e[3m" => "\e[23m",
|
603
|
-
"\e[4m" => "\e[24m",
|
604
|
-
"\e[5m" => "\e[25m",
|
605
|
-
"\e[7m" => "\e[27m"
|
606
|
-
}
|
607
|
-
close_sequences = open_sequences.values + ["\e[0m"]
|
608
|
-
ansi_regex = /\e\[[0-9;]*m/
|
609
|
-
result = []
|
610
|
-
tokens = line.scan(/(\e\[[0-9;]*m|[^\e]+)/).flatten.compact
|
611
|
-
current_line = ''
|
612
|
-
current_line_length = 0
|
613
|
-
active_sequences = []
|
614
|
-
tokens.each do |token|
|
615
|
-
if token.match?(ansi_regex)
|
616
|
-
current_line << token
|
617
|
-
if close_sequences.include?(token)
|
618
|
-
if token == "\e[0m"
|
619
|
-
active_sequences.clear
|
620
|
-
else
|
621
|
-
corresponding_open = open_sequences.key(token)
|
622
|
-
active_sequences.delete(corresponding_open)
|
623
|
-
end
|
624
|
-
else
|
625
|
-
active_sequences << token
|
626
|
-
end
|
627
|
-
else
|
628
|
-
words = token.scan(/\s+|\S+/)
|
629
|
-
words.each do |word|
|
630
|
-
word_length = Rcurses.display_width(word.gsub(ansi_regex, ''))
|
631
|
-
if current_line_length + word_length <= w
|
632
|
-
current_line << word
|
633
|
-
current_line_length += word_length
|
634
|
-
else
|
635
|
-
if current_line_length > 0
|
636
|
-
result << current_line
|
637
|
-
current_line = active_sequences.join
|
638
|
-
current_line_length = 0
|
639
|
-
end
|
640
|
-
while word_length > w
|
641
|
-
part = word[0, w]
|
642
|
-
current_line << part
|
643
|
-
result << current_line
|
644
|
-
word = word[w..-1]
|
645
|
-
word_length = Rcurses.display_width(word.gsub(ansi_regex, ''))
|
646
|
-
current_line = active_sequences.join
|
647
|
-
current_line_length = 0
|
648
|
-
end
|
649
|
-
if word_length > 0
|
650
|
-
current_line << word
|
651
|
-
current_line_length += word_length
|
652
|
-
end
|
653
|
-
end
|
654
|
-
end
|
655
|
-
end
|
656
|
-
end
|
657
|
-
result << current_line unless current_line.empty?
|
658
|
-
result
|
659
|
-
end
|
660
|
-
end
|
661
|
-
end
|
662
|
-
|
data/lib/rcurses.rb
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
# INFORMATION
|
2
|
-
# Name: rcurses - Ruby CURSES
|
3
|
-
# Language: Pure Ruby
|
4
|
-
# Author: Geir Isene <g@isene.com>
|
5
|
-
# Web_site: http://isene.com/
|
6
|
-
# Github: https://github.com/isene/rcurses
|
7
|
-
# License: Public domain
|
8
|
-
# Version: 4.8.3: Bugfix: Text carriege return corner case fix
|
9
|
-
|
10
|
-
require 'io/console' # Basic gem for rcurses
|
11
|
-
require 'io/wait' # stdin handling
|
12
|
-
require 'timeout'
|
13
|
-
|
14
|
-
require_relative 'string_extensions'
|
15
|
-
require_relative 'rcurses/general'
|
16
|
-
require_relative 'rcurses/cursor'
|
17
|
-
require_relative 'rcurses/input'
|
18
|
-
require_relative 'rcurses/pane'
|
19
|
-
|
20
|
-
module Rcurses
|
21
|
-
class << self
|
22
|
-
# Public: Initialize Rcurses. Switches terminal into raw/no-echo
|
23
|
-
# and registers cleanup handlers. Idempotent.
|
24
|
-
def init!
|
25
|
-
return if @initialized
|
26
|
-
return unless $stdin.tty?
|
27
|
-
|
28
|
-
# enter raw mode, disable echo
|
29
|
-
$stdin.raw!
|
30
|
-
$stdin.echo = false
|
31
|
-
|
32
|
-
# ensure cleanup on normal exit
|
33
|
-
at_exit { cleanup! }
|
34
|
-
|
35
|
-
# ensure cleanup on signals
|
36
|
-
%w[INT TERM].each do |sig|
|
37
|
-
trap(sig) { cleanup!; exit }
|
38
|
-
end
|
39
|
-
|
40
|
-
@initialized = true
|
41
|
-
end
|
42
|
-
|
43
|
-
# Public: Restore terminal to normal mode, clear screen, show cursor.
|
44
|
-
# Idempotent: subsequent calls do nothing.
|
45
|
-
def cleanup!
|
46
|
-
return if @cleaned_up
|
47
|
-
|
48
|
-
$stdin.cooked!
|
49
|
-
$stdin.echo = true
|
50
|
-
Rcurses.clear_screen
|
51
|
-
Cursor.show
|
52
|
-
|
53
|
-
@cleaned_up = true
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
# Kick off initialization as soon as the library is required.
|
58
|
-
init!
|
59
|
-
end
|
60
|
-
|
61
|
-
# vim: set sw=2 sts=2 et filetype=ruby fdn=2 fcs=fold\:\ :
|