rcurses 2.10 → 3.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/lib/rcurses/pane.rb +206 -218
- data/lib/rcurses.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 07bf042dcaa49e65d87441e77e32cb04f6d8738abe081979bc8589966b2ec65f
|
4
|
+
data.tar.gz: 234ad19f3e79216d0dd0561e2cfbb43318d7f308c596ab2a847eeda6fefe9563
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d8d764879351431d6057dd1fa573ca2bba3134c207c8c4f1ad4f511d1d12ca93c392e163401a9592c18b6af48f590d7509e510592f4191bf156c89c1c89d644a
|
7
|
+
data.tar.gz: 8ffe0551b49238aa84ef6811d140c398a96736c92f77afd10afd0116431d6856d6472f619345b96827391a9b83d152965704f586119bcd56410b626100da71e1
|
data/lib/rcurses/pane.rb
CHANGED
@@ -8,22 +8,25 @@ module Rcurses
|
|
8
8
|
attr_accessor :moreup, :moredown
|
9
9
|
|
10
10
|
def initialize(x = 1, y = 1, w = 1, h = 1, fg = nil, bg = nil)
|
11
|
+
@max_h, @max_w = IO.console.winsize
|
11
12
|
@x = x
|
12
13
|
@y = y
|
13
14
|
@w = w
|
14
15
|
@h = h
|
15
16
|
@fg, @bg = fg, bg
|
16
|
-
@text = ""
|
17
|
-
@align = "l"
|
18
|
-
@scroll = true
|
19
|
-
@prompt = ""
|
20
|
-
@ix = 0
|
21
|
-
@
|
17
|
+
@text = "" # Initialize text variable
|
18
|
+
@align = "l" # Default alignment
|
19
|
+
@scroll = true # Enable scroll indicators
|
20
|
+
@prompt = "" # Prompt for editline
|
21
|
+
@ix = 0 # Starting text line index
|
22
|
+
@prev_frame = nil # Holds the previously rendered frame (array of lines)
|
23
|
+
@line = 0 # For cursor tracking during editing:
|
24
|
+
@pos = 0 # For cursor tracking during editing:
|
22
25
|
end
|
23
26
|
|
24
|
-
def move(
|
25
|
-
@x +=
|
26
|
-
@y +=
|
27
|
+
def move(dx, dy)
|
28
|
+
@x += dx
|
29
|
+
@y += dy
|
27
30
|
refresh
|
28
31
|
end
|
29
32
|
|
@@ -74,108 +77,14 @@ module Rcurses
|
|
74
77
|
refresh
|
75
78
|
end
|
76
79
|
|
80
|
+
# Diff-based refresh that minimizes flicker.
|
81
|
+
# Building a frame (an array of lines) that includes borders (if enabled).
|
82
|
+
# Content lines are wrapped in vertical border characters when @border is true.
|
77
83
|
def refresh(cont = @text)
|
78
84
|
@max_h, @max_w = IO.console.winsize
|
79
85
|
|
80
|
-
#
|
81
|
-
|
82
|
-
# Define opening and closing sequences
|
83
|
-
open_sequences = {
|
84
|
-
"\e[1m" => "\e[22m",
|
85
|
-
"\e[3m" => "\e[23m",
|
86
|
-
"\e[4m" => "\e[24m",
|
87
|
-
"\e[5m" => "\e[25m",
|
88
|
-
"\e[7m" => "\e[27m" }
|
89
|
-
# All known closing sequences
|
90
|
-
close_sequences = open_sequences.values + ["\e[0m"]
|
91
|
-
# Regex to match ANSI escape sequences
|
92
|
-
ansi_regex = /\e\[[0-9;]*m/
|
93
|
-
result = []
|
94
|
-
# Tokenize the line into ANSI sequences and plain text
|
95
|
-
tokens = line.scan(/(\e\[[0-9;]*m|[^\e]+)/).flatten.compact
|
96
|
-
current_line = ''
|
97
|
-
current_line_length = 0
|
98
|
-
active_sequences = []
|
99
|
-
tokens.each do |token|
|
100
|
-
if token.match?(ansi_regex)
|
101
|
-
# It's an ANSI sequence
|
102
|
-
current_line << token
|
103
|
-
if close_sequences.include?(token)
|
104
|
-
# It's a closing sequence
|
105
|
-
if token == "\e[0m"
|
106
|
-
# Reset all sequences
|
107
|
-
active_sequences.clear
|
108
|
-
else
|
109
|
-
# Remove the corresponding opening sequence
|
110
|
-
corresponding_open = open_sequences.key(token)
|
111
|
-
active_sequences.delete(corresponding_open)
|
112
|
-
end
|
113
|
-
else
|
114
|
-
# It's an opening sequence (or any other ANSI sequence)
|
115
|
-
active_sequences << token
|
116
|
-
end
|
117
|
-
else
|
118
|
-
# It's plain text, split into words and spaces
|
119
|
-
words = token.scan(/\S+\s*/)
|
120
|
-
words.each do |word|
|
121
|
-
word_length = word.gsub(ansi_regex, '').length
|
122
|
-
if current_line_length + word_length <= w
|
123
|
-
# Append word to current line
|
124
|
-
current_line << word
|
125
|
-
current_line_length += word_length
|
126
|
-
else
|
127
|
-
# Word doesn't fit in the current line
|
128
|
-
if current_line_length > 0
|
129
|
-
# Finish the current line and start a new one
|
130
|
-
result << current_line
|
131
|
-
# Start new line with active ANSI sequences
|
132
|
-
current_line = active_sequences.join
|
133
|
-
current_line_length = 0
|
134
|
-
end
|
135
|
-
# Handle long words that might need splitting
|
136
|
-
while word_length > w
|
137
|
-
# Split the word
|
138
|
-
part = word[0, w]
|
139
|
-
current_line << part
|
140
|
-
result << current_line
|
141
|
-
# Update word and lengths
|
142
|
-
word = word[w..-1]
|
143
|
-
word_length = word.gsub(ansi_regex, '').length
|
144
|
-
# Start new line
|
145
|
-
current_line = active_sequences.join
|
146
|
-
current_line_length = 0
|
147
|
-
end
|
148
|
-
# Append any remaining part of the word
|
149
|
-
if word_length > 0
|
150
|
-
current_line << word
|
151
|
-
current_line_length += word_length
|
152
|
-
end
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
# Append any remaining text in the current line
|
158
|
-
result << current_line unless current_line.empty?
|
159
|
-
result
|
160
|
-
end
|
161
|
-
|
162
|
-
# Define the main textformat function
|
163
|
-
def textformat(cont)
|
164
|
-
# Split the content by '\n'
|
165
|
-
lines = cont.split("\n")
|
166
|
-
result = []
|
167
|
-
lines.each do |line|
|
168
|
-
split_lines = split_line_with_ansi(line, @w)
|
169
|
-
result.concat(split_lines)
|
170
|
-
end
|
171
|
-
result
|
172
|
-
end
|
173
|
-
|
174
|
-
# Start the actual refresh
|
175
|
-
o_row, o_col = pos
|
176
|
-
|
177
|
-
# Adjust pane dimensions and positions
|
178
|
-
if @border # Keep panes inside screen
|
86
|
+
# Adjust pane dimensions and positions.
|
87
|
+
if @border
|
179
88
|
@w = @max_w - 2 if @w > @max_w - 2
|
180
89
|
@h = @max_h - 2 if @h > @max_h - 2
|
181
90
|
@x = 2 if @x < 2; @x = @max_w - @w if @x + @w > @max_w
|
@@ -187,75 +96,93 @@ module Rcurses
|
|
187
96
|
@y = 1 if @y < 1; @y = @max_h - @h + 1 if @y + @h > @max_h + 1
|
188
97
|
end
|
189
98
|
|
190
|
-
|
191
|
-
|
192
|
-
@txt = cont.split("\n") # Split content into array
|
193
|
-
@txt = textformat(cont) if @txt.any? { |line| line.pure.length >= @w } # Reformat lines if necessary
|
194
|
-
@ix = @txt.length - 1 if @ix > @txt.length - 1; @ix = 0 if @ix < 0 # Ensure no out-of-bounds
|
99
|
+
# Save current cursor position.
|
100
|
+
o_row, o_col = pos
|
195
101
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
102
|
+
STDOUT.print "\e[?25l" # Hide cursor
|
103
|
+
|
104
|
+
fmt = [@fg, @bg].compact.join(',')
|
105
|
+
@txt = cont.split("\n")
|
106
|
+
@txt = textformat(cont) if @txt.any? { |line| line.pure.length >= @w }
|
107
|
+
@ix = @txt.length - 1 if @ix > @txt.length - 1
|
108
|
+
@ix = 0 if @ix < 0
|
109
|
+
|
110
|
+
# Build the new frame as an array of strings.
|
111
|
+
new_frame = []
|
112
|
+
if @border
|
113
|
+
# Top border spans (@w + 2) characters.
|
114
|
+
top_border = ("┌" + "─" * @w + "┐").c(fmt)
|
115
|
+
new_frame << top_border
|
116
|
+
end
|
117
|
+
|
118
|
+
# Build content lines.
|
119
|
+
content_rows = @h
|
120
|
+
content_rows.times do |i|
|
121
|
+
line_str = ""
|
122
|
+
l = @ix + i
|
123
|
+
if @txt[l].to_s != ""
|
200
124
|
pl = @w - @txt[l].pure.length
|
201
125
|
pl = 0 if pl < 0
|
202
126
|
hl = pl / 2
|
203
127
|
case @align
|
204
128
|
when "l"
|
205
|
-
|
129
|
+
line_str = @txt[l].c(fmt) + " ".c(fmt) * pl
|
206
130
|
when "r"
|
207
|
-
|
131
|
+
line_str = " ".c(fmt) * pl + @txt[l].c(fmt)
|
208
132
|
when "c"
|
209
|
-
|
133
|
+
line_str = " ".c(fmt) * hl + @txt[l].c(fmt) + " ".c(fmt) * (pl - hl)
|
210
134
|
end
|
211
135
|
else
|
212
|
-
|
136
|
+
line_str = " ".c(fmt) * @w
|
213
137
|
end
|
214
|
-
col(@x) # Cursor to start of pane
|
215
|
-
row(@y + i + 1)
|
216
|
-
end
|
217
138
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
139
|
+
# If border is enabled, add vertical border characters.
|
140
|
+
if @border
|
141
|
+
line_str = "│" + line_str + "│"
|
142
|
+
end
|
143
|
+
|
144
|
+
# Add scroll markers (overwrite the last character) if needed.
|
145
|
+
if i == 0 and @ix > 0 and @scroll
|
146
|
+
line_str[-1] = "∆".c(fmt)
|
147
|
+
@moreup = true
|
148
|
+
elsif i == content_rows - 1 and @txt.length - @ix > @h and @scroll
|
149
|
+
line_str[-1] = "∇".c(fmt)
|
150
|
+
@moredown = true
|
151
|
+
else
|
152
|
+
@moreup = false
|
153
|
+
@moredown = false
|
154
|
+
end
|
155
|
+
new_frame << line_str
|
224
156
|
end
|
225
157
|
|
226
|
-
if @
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
else
|
231
|
-
@moredown = false
|
158
|
+
if @border
|
159
|
+
# Bottom border.
|
160
|
+
bottom_border = ("└" + "─" * @w + "┘").c(fmt)
|
161
|
+
new_frame << bottom_border
|
232
162
|
end
|
233
163
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
164
|
+
# Diff-based update: update only lines that changed.
|
165
|
+
diff_buf = ""
|
166
|
+
new_frame.each_with_index do |line, i|
|
167
|
+
# Determine row number:
|
168
|
+
row_num = @border ? (@y - 1 + i) : (@y + i)
|
169
|
+
# When border is enabled, all lines (including content) start at column (@x - 1)
|
170
|
+
col_num = @border ? (@x - 1) : @x
|
171
|
+
if @prev_frame.nil? || @prev_frame[i] != line ||
|
172
|
+
(@border && (i == 0 || i == new_frame.size - 1))
|
173
|
+
diff_buf << "\e[#{row_num};#{col_num}H" << line
|
242
174
|
end
|
243
|
-
row(@y + @h); col(@x - 1)
|
244
|
-
print ("└" + "─" * @w + "┘").c(fmt)
|
245
175
|
end
|
246
176
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
end
|
177
|
+
diff_buf << "\e[#{o_row};#{o_col}H"
|
178
|
+
print diff_buf
|
179
|
+
#STDOUT.print "\e[?25h" # Show cursor - but use Cursor.show instead if needed
|
251
180
|
|
252
|
-
|
253
|
-
|
254
|
-
refresh
|
181
|
+
@prev_frame = new_frame
|
182
|
+
new_frame.join("\n")
|
255
183
|
end
|
256
|
-
|
184
|
+
|
257
185
|
def textformat(cont)
|
258
|
-
# Split the content by '\n'
|
259
186
|
lines = cont.split("\n")
|
260
187
|
result = []
|
261
188
|
lines.each do |line|
|
@@ -265,6 +192,11 @@ module Rcurses
|
|
265
192
|
result
|
266
193
|
end
|
267
194
|
|
195
|
+
def puts(txt)
|
196
|
+
@text = txt
|
197
|
+
refresh
|
198
|
+
end
|
199
|
+
|
268
200
|
def right
|
269
201
|
if @pos < @txt[@ix + @line].length
|
270
202
|
@pos += 1
|
@@ -286,6 +218,7 @@ module Rcurses
|
|
286
218
|
end
|
287
219
|
end
|
288
220
|
end
|
221
|
+
|
289
222
|
def left
|
290
223
|
if @pos == 0
|
291
224
|
if @line == 0
|
@@ -301,6 +234,7 @@ module Rcurses
|
|
301
234
|
@pos -= 1
|
302
235
|
end
|
303
236
|
end
|
237
|
+
|
304
238
|
def up
|
305
239
|
if @line == 0
|
306
240
|
@ix -= 1 unless @ix == 0
|
@@ -312,6 +246,7 @@ module Rcurses
|
|
312
246
|
rescue
|
313
247
|
end
|
314
248
|
end
|
249
|
+
|
315
250
|
def down
|
316
251
|
if @line == @h - 1
|
317
252
|
@ix += 1 unless @ix + @line >= @txt.length - 1
|
@@ -325,10 +260,10 @@ module Rcurses
|
|
325
260
|
end
|
326
261
|
|
327
262
|
def parse(cont)
|
328
|
-
cont.gsub!(/\*(.+?)\*/,
|
329
|
-
cont.gsub!(/\/(.+?)\//,
|
330
|
-
cont.gsub!(/_(.+?)_/,
|
331
|
-
cont.gsub!(/#(.+?)#/,
|
263
|
+
cont.gsub!(/\*(.+?)\*/, '\1'.b)
|
264
|
+
cont.gsub!(/\/(.+?)\//, '\1'.i)
|
265
|
+
cont.gsub!(/_(.+?)_/, '\1'.u)
|
266
|
+
cont.gsub!(/#(.+?)#/, '\1'.r)
|
332
267
|
cont.gsub!(/<([^|]+)\|([^>]+)>/) do
|
333
268
|
text = $2; codes = $1
|
334
269
|
text.c(codes)
|
@@ -338,92 +273,85 @@ module Rcurses
|
|
338
273
|
|
339
274
|
def edit
|
340
275
|
begin
|
341
|
-
# Switch to raw mode without echoing input
|
342
276
|
STDIN.raw!
|
343
277
|
Rcurses::Cursor.show
|
344
|
-
# Prepare content for editing, replacing newlines with a placeholder
|
345
278
|
content = @text.pure.gsub("\n", "¬\n")
|
346
|
-
|
347
|
-
@
|
348
|
-
@
|
349
|
-
@
|
350
|
-
@txt = refresh(content)
|
279
|
+
@ix = 0
|
280
|
+
@line = 0
|
281
|
+
@pos = 0
|
282
|
+
@txt = refresh(content)
|
351
283
|
input_char = ''
|
352
284
|
|
353
|
-
while input_char != 'ESC'
|
354
|
-
row(@y + @line)
|
355
|
-
col(@x + @pos)
|
356
|
-
|
357
|
-
input_char = getchr # Read user input
|
285
|
+
while input_char != 'ESC'
|
286
|
+
row(@y + @line)
|
287
|
+
col(@x + @pos)
|
288
|
+
input_char = getchr
|
358
289
|
case input_char
|
359
|
-
when 'C-L'
|
290
|
+
when 'C-L'
|
360
291
|
@align = 'l'
|
361
|
-
when 'C-R'
|
292
|
+
when 'C-R'
|
362
293
|
@align = 'r'
|
363
|
-
when 'C-C'
|
294
|
+
when 'C-C'
|
364
295
|
@align = 'c'
|
365
|
-
when 'C-Y'
|
296
|
+
when 'C-Y'
|
366
297
|
Clipboard.copy(@text.pure)
|
367
|
-
when 'C-S'
|
298
|
+
when 'C-S'
|
368
299
|
content = content.gsub('¬', "\n")
|
369
300
|
content = parse(content)
|
370
301
|
@text = content
|
371
302
|
input_char = 'ESC'
|
372
|
-
when 'DEL'
|
303
|
+
when 'DEL'
|
373
304
|
posx = calculate_posx
|
374
305
|
content.slice!(posx)
|
375
|
-
when 'BACK'
|
306
|
+
when 'BACK'
|
376
307
|
if @pos > 0
|
377
308
|
left
|
378
309
|
posx = calculate_posx
|
379
310
|
content.slice!(posx)
|
380
311
|
end
|
381
|
-
when 'WBACK'
|
312
|
+
when 'WBACK'
|
382
313
|
while @pos > 0 && content[calculate_posx - 1] != ' '
|
383
314
|
left
|
384
315
|
posx = calculate_posx
|
385
316
|
content.slice!(posx)
|
386
317
|
end
|
387
|
-
when 'C-K'
|
318
|
+
when 'C-K'
|
388
319
|
line_start_pos = calculate_line_start_pos
|
389
320
|
line_length = @txt[@ix + @line]&.length || 0
|
390
321
|
content.slice!(line_start_pos + @pos, line_length - @pos)
|
391
|
-
when 'UP'
|
322
|
+
when 'UP'
|
392
323
|
up
|
393
|
-
when 'DOWN'
|
324
|
+
when 'DOWN'
|
394
325
|
down
|
395
|
-
when 'RIGHT'
|
326
|
+
when 'RIGHT'
|
396
327
|
right
|
397
|
-
when 'LEFT'
|
328
|
+
when 'LEFT'
|
398
329
|
left
|
399
|
-
when 'HOME'
|
330
|
+
when 'HOME'
|
400
331
|
@pos = 0
|
401
|
-
when 'END'
|
332
|
+
when 'END'
|
402
333
|
current_line_length = @txt[@ix + @line]&.length || 0
|
403
334
|
@pos = current_line_length
|
404
|
-
when 'C-HOME'
|
335
|
+
when 'C-HOME'
|
405
336
|
@ix = 0
|
406
337
|
@line = 0
|
407
338
|
@pos = 0
|
408
|
-
when 'C-END'
|
339
|
+
when 'C-END'
|
409
340
|
total_lines = @txt.length
|
410
341
|
@ix = [total_lines - @h, 0].max
|
411
342
|
@line = [@h - 1, total_lines - @ix - 1].min
|
412
343
|
current_line_length = @txt[@ix + @line]&.length || 0
|
413
344
|
@pos = current_line_length
|
414
|
-
when 'ENTER'
|
345
|
+
when 'ENTER'
|
415
346
|
posx = calculate_posx
|
416
347
|
content.insert(posx, "¬\n")
|
417
348
|
right
|
418
|
-
when /^.$/
|
349
|
+
when /^.$/
|
419
350
|
posx = calculate_posx
|
420
351
|
content.insert(posx, input_char)
|
421
352
|
right
|
422
|
-
else
|
423
|
-
# Handle unrecognized input if necessary
|
424
353
|
end
|
425
354
|
|
426
|
-
# Handle pasted input (additional characters in the buffer)
|
427
355
|
while IO.select([$stdin], nil, nil, 0)
|
428
356
|
input_char = $stdin.read_nonblock(1) rescue nil
|
429
357
|
break unless input_char
|
@@ -432,10 +360,9 @@ module Rcurses
|
|
432
360
|
right
|
433
361
|
end
|
434
362
|
|
435
|
-
@txt = refresh(content)
|
363
|
+
@txt = refresh(content)
|
436
364
|
end
|
437
365
|
ensure
|
438
|
-
# Restore terminal mode
|
439
366
|
STDIN.cooked!
|
440
367
|
end
|
441
368
|
Rcurses::Cursor.hide
|
@@ -443,36 +370,28 @@ module Rcurses
|
|
443
370
|
|
444
371
|
def editline
|
445
372
|
begin
|
446
|
-
# Switch to raw mode without echo
|
447
373
|
STDIN.raw!
|
448
374
|
Rcurses::Cursor.show
|
449
|
-
|
450
|
-
# Initialize position and dimensions
|
451
|
-
# Ensure pane is within screen bounds
|
452
375
|
@x = [[@x, 1].max, @max_w - @w + 1].min
|
453
376
|
@y = [[@y, 1].max, @max_h - @h + 1].min
|
454
|
-
|
455
377
|
@scroll = false
|
456
|
-
@ix
|
378
|
+
@ix = 0
|
457
379
|
row(@y)
|
458
|
-
|
459
380
|
fmt = [@fg, @bg].compact.join(',')
|
460
381
|
col(@x)
|
461
|
-
print @prompt.c(fmt)
|
462
|
-
|
382
|
+
print @prompt.c(fmt)
|
463
383
|
prompt_len = @prompt.pure.length
|
464
384
|
content_len = @w - prompt_len
|
465
385
|
cont = @text.pure.slice(0, content_len)
|
466
|
-
@pos = cont.length
|
386
|
+
@pos = cont.length
|
467
387
|
chr = ''
|
468
388
|
|
469
|
-
while chr != 'ESC'
|
470
|
-
col(@x + prompt_len)
|
471
|
-
cont = cont.slice(0, content_len)
|
472
|
-
print cont.ljust(content_len).c(fmt)
|
473
|
-
col(@x + prompt_len + @pos)
|
474
|
-
|
475
|
-
chr = getchr # Read user input
|
389
|
+
while chr != 'ESC'
|
390
|
+
col(@x + prompt_len)
|
391
|
+
cont = cont.slice(0, content_len)
|
392
|
+
print cont.ljust(content_len).c(fmt)
|
393
|
+
col(@x + prompt_len + @pos)
|
394
|
+
chr = getchr
|
476
395
|
case chr
|
477
396
|
when 'LEFT'
|
478
397
|
@pos -= 1 if @pos > 0
|
@@ -507,7 +426,6 @@ module Rcurses
|
|
507
426
|
end
|
508
427
|
end
|
509
428
|
|
510
|
-
# Handle pasted input
|
511
429
|
while IO.select([$stdin], nil, nil, 0)
|
512
430
|
chr = $stdin.read_nonblock(1) rescue nil
|
513
431
|
break unless chr
|
@@ -518,7 +436,6 @@ module Rcurses
|
|
518
436
|
end
|
519
437
|
end
|
520
438
|
ensure
|
521
|
-
# Restore terminal mode
|
522
439
|
STDIN.cooked!
|
523
440
|
end
|
524
441
|
Rcurses::Cursor.hide
|
@@ -526,23 +443,94 @@ module Rcurses
|
|
526
443
|
|
527
444
|
private
|
528
445
|
|
529
|
-
|
446
|
+
def flush_stdin
|
447
|
+
while IO.select([$stdin], nil, nil, 0.005)
|
448
|
+
begin
|
449
|
+
$stdin.read_nonblock(1024)
|
450
|
+
rescue IO::WaitReadable, EOFError
|
451
|
+
break
|
452
|
+
end
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
530
456
|
def calculate_posx
|
531
457
|
total_length = 0
|
532
458
|
(@ix + @line).times do |i|
|
533
|
-
total_length += @txt[i].pure.length + 1 # +1 for
|
459
|
+
total_length += @txt[i].pure.length + 1 # +1 for newline
|
534
460
|
end
|
535
461
|
total_length += @pos
|
536
462
|
total_length
|
537
463
|
end
|
538
|
-
|
464
|
+
|
539
465
|
def calculate_line_start_pos
|
540
466
|
total_length = 0
|
541
467
|
(@ix + @line).times do |i|
|
542
|
-
total_length += @txt[i].pure.length + 1
|
468
|
+
total_length += @txt[i].pure.length + 1
|
543
469
|
end
|
544
470
|
total_length
|
545
471
|
end
|
472
|
+
|
473
|
+
def split_line_with_ansi(line, w)
|
474
|
+
open_sequences = {
|
475
|
+
"\e[1m" => "\e[22m",
|
476
|
+
"\e[3m" => "\e[23m",
|
477
|
+
"\e[4m" => "\e[24m",
|
478
|
+
"\e[5m" => "\e[25m",
|
479
|
+
"\e[7m" => "\e[27m"
|
480
|
+
}
|
481
|
+
close_sequences = open_sequences.values + ["\e[0m"]
|
482
|
+
ansi_regex = /\e\[[0-9;]*m/
|
483
|
+
result = []
|
484
|
+
tokens = line.scan(/(\e\[[0-9;]*m|[^\e]+)/).flatten.compact
|
485
|
+
current_line = ''
|
486
|
+
current_line_length = 0
|
487
|
+
active_sequences = []
|
488
|
+
tokens.each do |token|
|
489
|
+
if token.match?(ansi_regex)
|
490
|
+
current_line << token
|
491
|
+
if close_sequences.include?(token)
|
492
|
+
if token == "\e[0m"
|
493
|
+
active_sequences.clear
|
494
|
+
else
|
495
|
+
corresponding_open = open_sequences.key(token)
|
496
|
+
active_sequences.delete(corresponding_open)
|
497
|
+
end
|
498
|
+
else
|
499
|
+
active_sequences << token
|
500
|
+
end
|
501
|
+
else
|
502
|
+
words = token.scan(/\s+|\S+/)
|
503
|
+
words.each do |word|
|
504
|
+
word_length = word.gsub(ansi_regex, '').length
|
505
|
+
if current_line_length + word_length <= w
|
506
|
+
current_line << word
|
507
|
+
current_line_length += word_length
|
508
|
+
else
|
509
|
+
if current_line_length > 0
|
510
|
+
result << current_line
|
511
|
+
current_line = active_sequences.join
|
512
|
+
current_line_length = 0
|
513
|
+
end
|
514
|
+
while word_length > w
|
515
|
+
part = word[0, w]
|
516
|
+
current_line << part
|
517
|
+
result << current_line
|
518
|
+
word = word[w..-1]
|
519
|
+
word_length = word.gsub(ansi_regex, '').length
|
520
|
+
current_line = active_sequences.join
|
521
|
+
current_line_length = 0
|
522
|
+
end
|
523
|
+
if word_length > 0
|
524
|
+
current_line << word
|
525
|
+
current_line_length += word_length
|
526
|
+
end
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
531
|
+
result << current_line unless current_line.empty?
|
532
|
+
result
|
533
|
+
end
|
546
534
|
end
|
547
535
|
end
|
548
536
|
|
data/lib/rcurses.rb
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
# Web_site: http://isene.com/
|
6
6
|
# Github: https://github.com/isene/rcurses
|
7
7
|
# License: Public domain
|
8
|
-
# Version:
|
8
|
+
# Version: 3.1: Flicker reduction by diff rendering
|
9
9
|
|
10
10
|
require 'io/console' # Basic gem for rcurses
|
11
11
|
require 'io/wait' # stdin handling
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rcurses
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '
|
4
|
+
version: '3.1'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Geir Isene
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-04-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: clipboard
|
@@ -29,8 +29,8 @@ description: 'Create curses applications for the terminal easier than ever. Crea
|
|
29
29
|
up text (in panes or anywhere in the terminal) in bold, italic, underline, reverse
|
30
30
|
color, blink and in any 256 terminal colors for foreground and background. Use a
|
31
31
|
simple editor to let users edit text in panes. Left, right or center align text
|
32
|
-
in panes. Cursor movement around the terminal. New in
|
33
|
-
|
32
|
+
in panes. Cursor movement around the terminal. New in 3.1: Flicker reduction by
|
33
|
+
diff rendering.'
|
34
34
|
email: g@isene.com
|
35
35
|
executables: []
|
36
36
|
extensions: []
|