rcurses 2.9 → 3.0
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 +6 -0
- data/lib/rcurses/input.rb +4 -0
- data/lib/rcurses/pane.rb +181 -203
- data/lib/rcurses.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 706550c12ee9965c33b958c6ce4ec5cb0aa12fb37196b4dd623cea43b982528f
|
4
|
+
data.tar.gz: 17e20e12a50a34f2d36069041b3f1dfbedbf27ab7dddf8f3681fd8b289e6fe00
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 68fe3d1c80b12fae7ad09b49efdec40a889bf36f6d269e68ab09971bf9f057874ac122cff4d1c08c9b0673e13e65857c98e9e888977138a1c51a385544520901
|
7
|
+
data.tar.gz: aa6d784ef29afbb0b5e09ada56b2caadf2b474e0bf909eaa92dc161f88d6d1e6c33424d3c1ed2c407148d9ce502bd5563bf1eb1118e3b2a7a15444df18494a89
|
data/README.md
CHANGED
@@ -69,6 +69,8 @@ text | The text/content of the Pane
|
|
69
69
|
ix | "Index" - the line number at the top of the Pane, starts at 0, the first line of text in the Pane
|
70
70
|
align | Text alignment in the Pane: "l" = lefts aligned, "c" = center, "r" = right, with the default "l"
|
71
71
|
prompt | The prompt to print at the beginning of a one-liner Pane used as an input box
|
72
|
+
moreup | Set to true when there is more text above what is shown (top scroll bar i showing)
|
73
|
+
moredown | Set to true when there is more text below what is shown (bottom scroll bar i showing)
|
72
74
|
|
73
75
|
The methods for Pane:
|
74
76
|
|
@@ -150,12 +152,16 @@ Key pressed | string returned
|
|
150
152
|
----------------|----------------------------------------------------------
|
151
153
|
`esc` | "ESC"
|
152
154
|
`up` | "UP"
|
155
|
+
`shift-up` | "S-UP"
|
153
156
|
`ctrl-up` | "C-UP"
|
154
157
|
`down` | "DOWN"
|
158
|
+
`shift-down` | "S-DOWN"
|
155
159
|
`ctrl-down` | "C-DOWN"
|
156
160
|
`right` | "RIGHT"
|
161
|
+
`shift-right` | "S-RIGHT"
|
157
162
|
`ctrl-right` | "C-RIGHT"
|
158
163
|
`left` | "LEFT"
|
164
|
+
`shifth-left` | "S-LEFT"
|
159
165
|
`ctrl-left` | "C-LEFT"
|
160
166
|
`shift-tab` | "S-TAB"
|
161
167
|
`insert` | "INS"
|
data/lib/rcurses/input.rb
CHANGED
@@ -21,9 +21,13 @@ module Rcurses
|
|
21
21
|
third_char = $stdin.getc
|
22
22
|
case third_char
|
23
23
|
when 'A' then chr = "UP"
|
24
|
+
when 'a' then chr = "S-UP"
|
24
25
|
when 'B' then chr = "DOWN"
|
26
|
+
when 'b' then chr = "S-DOWN"
|
25
27
|
when 'C' then chr = "RIGHT"
|
28
|
+
when 'c' then chr = "S-RIGHT"
|
26
29
|
when 'D' then chr = "LEFT"
|
30
|
+
when 'd' then chr = "S-LEFT"
|
27
31
|
when 'Z' then chr = "S-TAB"
|
28
32
|
when '1'
|
29
33
|
fourth_char = $stdin.getc
|
data/lib/rcurses/pane.rb
CHANGED
@@ -5,6 +5,7 @@ module Rcurses
|
|
5
5
|
include Input
|
6
6
|
attr_accessor :x, :y, :w, :h, :fg, :bg
|
7
7
|
attr_accessor :border, :scroll, :text, :ix, :align, :prompt
|
8
|
+
attr_accessor :moreup, :moredown
|
8
9
|
|
9
10
|
def initialize(x = 1, y = 1, w = 1, h = 1, fg = nil, bg = nil)
|
10
11
|
@x = x
|
@@ -14,9 +15,9 @@ module Rcurses
|
|
14
15
|
@fg, @bg = fg, bg
|
15
16
|
@text = "" # Initialize text variable
|
16
17
|
@align = "l" # Default alignment
|
17
|
-
@scroll = true #
|
18
|
-
@prompt = "" #
|
19
|
-
@ix = 0 #
|
18
|
+
@scroll = true # Enable scroll indicators
|
19
|
+
@prompt = "" # Prompt for editline
|
20
|
+
@ix = 0 # Starting text line index
|
20
21
|
@max_h, @max_w = IO.console.winsize
|
21
22
|
end
|
22
23
|
|
@@ -73,108 +74,12 @@ module Rcurses
|
|
73
74
|
refresh
|
74
75
|
end
|
75
76
|
|
77
|
+
# Optimized refresh using double buffering to minimize flicker.
|
76
78
|
def refresh(cont = @text)
|
77
79
|
@max_h, @max_w = IO.console.winsize
|
78
80
|
|
79
|
-
#
|
80
|
-
|
81
|
-
# Define opening and closing sequences
|
82
|
-
open_sequences = {
|
83
|
-
"\e[1m" => "\e[22m",
|
84
|
-
"\e[3m" => "\e[23m",
|
85
|
-
"\e[4m" => "\e[24m",
|
86
|
-
"\e[5m" => "\e[25m",
|
87
|
-
"\e[7m" => "\e[27m" }
|
88
|
-
# All known closing sequences
|
89
|
-
close_sequences = open_sequences.values + ["\e[0m"]
|
90
|
-
# Regex to match ANSI escape sequences
|
91
|
-
ansi_regex = /\e\[[0-9;]*m/
|
92
|
-
result = []
|
93
|
-
# Tokenize the line into ANSI sequences and plain text
|
94
|
-
tokens = line.scan(/(\e\[[0-9;]*m|[^\e]+)/).flatten.compact
|
95
|
-
current_line = ''
|
96
|
-
current_line_length = 0
|
97
|
-
active_sequences = []
|
98
|
-
tokens.each do |token|
|
99
|
-
if token.match?(ansi_regex)
|
100
|
-
# It's an ANSI sequence
|
101
|
-
current_line << token
|
102
|
-
if close_sequences.include?(token)
|
103
|
-
# It's a closing sequence
|
104
|
-
if token == "\e[0m"
|
105
|
-
# Reset all sequences
|
106
|
-
active_sequences.clear
|
107
|
-
else
|
108
|
-
# Remove the corresponding opening sequence
|
109
|
-
corresponding_open = open_sequences.key(token)
|
110
|
-
active_sequences.delete(corresponding_open)
|
111
|
-
end
|
112
|
-
else
|
113
|
-
# It's an opening sequence (or any other ANSI sequence)
|
114
|
-
active_sequences << token
|
115
|
-
end
|
116
|
-
else
|
117
|
-
# It's plain text, split into words and spaces
|
118
|
-
words = token.scan(/\S+\s*/)
|
119
|
-
words.each do |word|
|
120
|
-
word_length = word.gsub(ansi_regex, '').length
|
121
|
-
if current_line_length + word_length <= w
|
122
|
-
# Append word to current line
|
123
|
-
current_line << word
|
124
|
-
current_line_length += word_length
|
125
|
-
else
|
126
|
-
# Word doesn't fit in the current line
|
127
|
-
if current_line_length > 0
|
128
|
-
# Finish the current line and start a new one
|
129
|
-
result << current_line
|
130
|
-
# Start new line with active ANSI sequences
|
131
|
-
current_line = active_sequences.join
|
132
|
-
current_line_length = 0
|
133
|
-
end
|
134
|
-
# Handle long words that might need splitting
|
135
|
-
while word_length > w
|
136
|
-
# Split the word
|
137
|
-
part = word[0, w]
|
138
|
-
current_line << part
|
139
|
-
result << current_line
|
140
|
-
# Update word and lengths
|
141
|
-
word = word[w..-1]
|
142
|
-
word_length = word.gsub(ansi_regex, '').length
|
143
|
-
# Start new line
|
144
|
-
current_line = active_sequences.join
|
145
|
-
current_line_length = 0
|
146
|
-
end
|
147
|
-
# Append any remaining part of the word
|
148
|
-
if word_length > 0
|
149
|
-
current_line << word
|
150
|
-
current_line_length += word_length
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|
156
|
-
# Append any remaining text in the current line
|
157
|
-
result << current_line unless current_line.empty?
|
158
|
-
result
|
159
|
-
end
|
160
|
-
|
161
|
-
# Define the main textformat function
|
162
|
-
def textformat(cont)
|
163
|
-
# Split the content by '\n'
|
164
|
-
lines = cont.split("\n")
|
165
|
-
result = []
|
166
|
-
lines.each do |line|
|
167
|
-
split_lines = split_line_with_ansi(line, @w)
|
168
|
-
result.concat(split_lines)
|
169
|
-
end
|
170
|
-
result
|
171
|
-
end
|
172
|
-
|
173
|
-
# Start the actual refresh
|
174
|
-
o_row, o_col = pos
|
175
|
-
|
176
|
-
# Adjust pane dimensions and positions
|
177
|
-
if @border # Keep panes inside screen
|
81
|
+
# Adjust pane dimensions (unchanged logic)
|
82
|
+
if @border
|
178
83
|
@w = @max_w - 2 if @w > @max_w - 2
|
179
84
|
@h = @max_h - 2 if @h > @max_h - 2
|
180
85
|
@x = 2 if @x < 2; @x = @max_w - @w if @x + @w > @max_w
|
@@ -186,69 +91,81 @@ module Rcurses
|
|
186
91
|
@y = 1 if @y < 1; @y = @max_h - @h + 1 if @y + @h > @max_h + 1
|
187
92
|
end
|
188
93
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
94
|
+
# Save current cursor position.
|
95
|
+
o_row, o_col = pos
|
96
|
+
|
97
|
+
# Hide cursor and switch to alternate screen buffer (if desired). Hide it any case and use Curses.show to bring it back.
|
98
|
+
# Note: The alternate screen buffer means your program’s output won’t mix with the normal terminal content.
|
99
|
+
STDOUT.print "\e[?25l" # hide cursor
|
100
|
+
# Uncomment the next line to use the alternate screen buffer.
|
101
|
+
#STDOUT.print "\e[?1049h"
|
102
|
+
|
103
|
+
buf = ""
|
104
|
+
buf << "\e[#{@y};#{@x}H" # Move cursor to start of pane.
|
105
|
+
fmt = [@fg, @bg].compact.join(',')
|
106
|
+
@txt = cont.split("\n")
|
107
|
+
@txt = textformat(cont) if @txt.any? { |line| line.pure.length >= @w }
|
108
|
+
@ix = @txt.length - 1 if @ix > @txt.length - 1
|
109
|
+
@ix = 0 if @ix < 0
|
194
110
|
|
195
|
-
@h.times do |i|
|
196
|
-
l = @ix + i
|
197
|
-
if @txt[l].to_s != ""
|
198
|
-
# Get padding width and half width
|
111
|
+
@h.times do |i|
|
112
|
+
l = @ix + i
|
113
|
+
if @txt[l].to_s != ""
|
199
114
|
pl = @w - @txt[l].pure.length
|
200
115
|
pl = 0 if pl < 0
|
201
116
|
hl = pl / 2
|
202
117
|
case @align
|
203
118
|
when "l"
|
204
|
-
|
119
|
+
buf << @txt[l].c(fmt) << " ".c(fmt) * pl
|
205
120
|
when "r"
|
206
|
-
|
121
|
+
buf << " ".c(fmt) * pl << @txt[l].c(fmt)
|
207
122
|
when "c"
|
208
|
-
|
123
|
+
buf << " ".c(fmt) * hl << @txt[l].c(fmt) << " ".c(fmt) * (pl - hl)
|
209
124
|
end
|
210
125
|
else
|
211
|
-
|
126
|
+
buf << " ".c(fmt) * @w
|
212
127
|
end
|
213
|
-
|
214
|
-
row(@y + i + 1)
|
128
|
+
buf << "\e[#{@y + i + 1};#{@x}H" # Next line.
|
215
129
|
end
|
216
130
|
|
217
|
-
|
218
|
-
|
219
|
-
|
131
|
+
# Draw scroll markers.
|
132
|
+
if @ix > 0 and @scroll
|
133
|
+
buf << "\e[#{@y};#{@x + @w - 1}H" << "∆".c(fmt)
|
134
|
+
@moreup = true
|
135
|
+
else
|
136
|
+
@moreup = false
|
220
137
|
end
|
221
138
|
|
222
|
-
if @txt.length - @ix > @h and @scroll
|
223
|
-
|
224
|
-
|
139
|
+
if @txt.length - @ix > @h and @scroll
|
140
|
+
buf << "\e[#{@y + @h - 1};#{@x + @w - 1}H" << "∇".c(fmt)
|
141
|
+
@moredown = true
|
142
|
+
else
|
143
|
+
@moredown = false
|
225
144
|
end
|
226
145
|
|
227
|
-
|
228
|
-
|
229
|
-
|
146
|
+
# Draw border if enabled.
|
147
|
+
if @border
|
148
|
+
buf << "\e[#{@y - 1};#{@x - 1}H" << ("┌" + "─" * @w + "┐").c(fmt)
|
230
149
|
@h.times do |i|
|
231
|
-
|
232
|
-
|
233
|
-
col(@x + @w)
|
234
|
-
print "│".c(fmt)
|
150
|
+
buf << "\e[#{@y + i};#{@x - 1}H" << "│".c(fmt)
|
151
|
+
buf << "\e[#{@y + i};#{@x + @w}H" << "│".c(fmt)
|
235
152
|
end
|
236
|
-
|
237
|
-
print ("└" + "─" * @w + "┘").c(fmt)
|
153
|
+
buf << "\e[#{@y + @h};#{@x - 1}H" << ("└" + "─" * @w + "┘").c(fmt)
|
238
154
|
end
|
239
155
|
|
240
|
-
|
241
|
-
|
156
|
+
# Restore original cursor position.
|
157
|
+
buf << "\e[#{o_row};#{o_col}H"
|
158
|
+
|
159
|
+
# Print the whole buffer at once.
|
160
|
+
print buf
|
161
|
+
|
162
|
+
# Uncomment the next line to switched to the alternate screen buffer.
|
163
|
+
#STDOUT.print "\e[?1049l"
|
164
|
+
|
242
165
|
@txt
|
243
166
|
end
|
244
167
|
|
245
|
-
def puts(txt)
|
246
|
-
@text = txt
|
247
|
-
refresh
|
248
|
-
end
|
249
|
-
|
250
168
|
def textformat(cont)
|
251
|
-
# Split the content by '\n'
|
252
169
|
lines = cont.split("\n")
|
253
170
|
result = []
|
254
171
|
lines.each do |line|
|
@@ -258,6 +175,11 @@ module Rcurses
|
|
258
175
|
result
|
259
176
|
end
|
260
177
|
|
178
|
+
def puts(txt)
|
179
|
+
@text = txt
|
180
|
+
refresh
|
181
|
+
end
|
182
|
+
|
261
183
|
def right
|
262
184
|
if @pos < @txt[@ix + @line].length
|
263
185
|
@pos += 1
|
@@ -279,6 +201,7 @@ module Rcurses
|
|
279
201
|
end
|
280
202
|
end
|
281
203
|
end
|
204
|
+
|
282
205
|
def left
|
283
206
|
if @pos == 0
|
284
207
|
if @line == 0
|
@@ -294,6 +217,7 @@ module Rcurses
|
|
294
217
|
@pos -= 1
|
295
218
|
end
|
296
219
|
end
|
220
|
+
|
297
221
|
def up
|
298
222
|
if @line == 0
|
299
223
|
@ix -= 1 unless @ix == 0
|
@@ -305,6 +229,7 @@ module Rcurses
|
|
305
229
|
rescue
|
306
230
|
end
|
307
231
|
end
|
232
|
+
|
308
233
|
def down
|
309
234
|
if @line == @h - 1
|
310
235
|
@ix += 1 unless @ix + @line >= @txt.length - 1
|
@@ -318,10 +243,10 @@ module Rcurses
|
|
318
243
|
end
|
319
244
|
|
320
245
|
def parse(cont)
|
321
|
-
cont.gsub!(/\*(.+?)\*/,
|
322
|
-
cont.gsub!(/\/(.+?)\//,
|
323
|
-
cont.gsub!(/_(.+?)_/,
|
324
|
-
cont.gsub!(/#(.+?)#/,
|
246
|
+
cont.gsub!(/\*(.+?)\*/, '\1'.b)
|
247
|
+
cont.gsub!(/\/(.+?)\//, '\1'.i)
|
248
|
+
cont.gsub!(/_(.+?)_/, '\1'.u)
|
249
|
+
cont.gsub!(/#(.+?)#/, '\1'.r)
|
325
250
|
cont.gsub!(/<([^|]+)\|([^>]+)>/) do
|
326
251
|
text = $2; codes = $1
|
327
252
|
text.c(codes)
|
@@ -331,92 +256,85 @@ module Rcurses
|
|
331
256
|
|
332
257
|
def edit
|
333
258
|
begin
|
334
|
-
# Switch to raw mode without echoing input
|
335
259
|
STDIN.raw!
|
336
260
|
Rcurses::Cursor.show
|
337
|
-
# Prepare content for editing, replacing newlines with a placeholder
|
338
261
|
content = @text.pure.gsub("\n", "¬\n")
|
339
|
-
|
340
|
-
@
|
341
|
-
@
|
342
|
-
@
|
343
|
-
@txt = refresh(content)
|
262
|
+
@ix = 0
|
263
|
+
@line = 0
|
264
|
+
@pos = 0
|
265
|
+
@txt = refresh(content)
|
344
266
|
input_char = ''
|
345
267
|
|
346
|
-
while input_char != 'ESC'
|
347
|
-
row(@y + @line)
|
348
|
-
col(@x + @pos)
|
349
|
-
|
350
|
-
input_char = getchr # Read user input
|
268
|
+
while input_char != 'ESC'
|
269
|
+
row(@y + @line)
|
270
|
+
col(@x + @pos)
|
271
|
+
input_char = getchr
|
351
272
|
case input_char
|
352
|
-
when 'C-L'
|
273
|
+
when 'C-L'
|
353
274
|
@align = 'l'
|
354
|
-
when 'C-R'
|
275
|
+
when 'C-R'
|
355
276
|
@align = 'r'
|
356
|
-
when 'C-C'
|
277
|
+
when 'C-C'
|
357
278
|
@align = 'c'
|
358
|
-
when 'C-Y'
|
279
|
+
when 'C-Y'
|
359
280
|
Clipboard.copy(@text.pure)
|
360
|
-
when 'C-S'
|
281
|
+
when 'C-S'
|
361
282
|
content = content.gsub('¬', "\n")
|
362
283
|
content = parse(content)
|
363
284
|
@text = content
|
364
285
|
input_char = 'ESC'
|
365
|
-
when 'DEL'
|
286
|
+
when 'DEL'
|
366
287
|
posx = calculate_posx
|
367
288
|
content.slice!(posx)
|
368
|
-
when 'BACK'
|
289
|
+
when 'BACK'
|
369
290
|
if @pos > 0
|
370
291
|
left
|
371
292
|
posx = calculate_posx
|
372
293
|
content.slice!(posx)
|
373
294
|
end
|
374
|
-
when 'WBACK'
|
295
|
+
when 'WBACK'
|
375
296
|
while @pos > 0 && content[calculate_posx - 1] != ' '
|
376
297
|
left
|
377
298
|
posx = calculate_posx
|
378
299
|
content.slice!(posx)
|
379
300
|
end
|
380
|
-
when 'C-K'
|
301
|
+
when 'C-K'
|
381
302
|
line_start_pos = calculate_line_start_pos
|
382
303
|
line_length = @txt[@ix + @line]&.length || 0
|
383
304
|
content.slice!(line_start_pos + @pos, line_length - @pos)
|
384
|
-
when 'UP'
|
305
|
+
when 'UP'
|
385
306
|
up
|
386
|
-
when 'DOWN'
|
307
|
+
when 'DOWN'
|
387
308
|
down
|
388
|
-
when 'RIGHT'
|
309
|
+
when 'RIGHT'
|
389
310
|
right
|
390
|
-
when 'LEFT'
|
311
|
+
when 'LEFT'
|
391
312
|
left
|
392
|
-
when 'HOME'
|
313
|
+
when 'HOME'
|
393
314
|
@pos = 0
|
394
|
-
when 'END'
|
315
|
+
when 'END'
|
395
316
|
current_line_length = @txt[@ix + @line]&.length || 0
|
396
317
|
@pos = current_line_length
|
397
|
-
when 'C-HOME'
|
318
|
+
when 'C-HOME'
|
398
319
|
@ix = 0
|
399
320
|
@line = 0
|
400
321
|
@pos = 0
|
401
|
-
when 'C-END'
|
322
|
+
when 'C-END'
|
402
323
|
total_lines = @txt.length
|
403
324
|
@ix = [total_lines - @h, 0].max
|
404
325
|
@line = [@h - 1, total_lines - @ix - 1].min
|
405
326
|
current_line_length = @txt[@ix + @line]&.length || 0
|
406
327
|
@pos = current_line_length
|
407
|
-
when 'ENTER'
|
328
|
+
when 'ENTER'
|
408
329
|
posx = calculate_posx
|
409
330
|
content.insert(posx, "¬\n")
|
410
331
|
right
|
411
|
-
when /^.$/
|
332
|
+
when /^.$/
|
412
333
|
posx = calculate_posx
|
413
334
|
content.insert(posx, input_char)
|
414
335
|
right
|
415
|
-
else
|
416
|
-
# Handle unrecognized input if necessary
|
417
336
|
end
|
418
337
|
|
419
|
-
# Handle pasted input (additional characters in the buffer)
|
420
338
|
while IO.select([$stdin], nil, nil, 0)
|
421
339
|
input_char = $stdin.read_nonblock(1) rescue nil
|
422
340
|
break unless input_char
|
@@ -425,10 +343,9 @@ module Rcurses
|
|
425
343
|
right
|
426
344
|
end
|
427
345
|
|
428
|
-
@txt = refresh(content)
|
346
|
+
@txt = refresh(content)
|
429
347
|
end
|
430
348
|
ensure
|
431
|
-
# Restore terminal mode
|
432
349
|
STDIN.cooked!
|
433
350
|
end
|
434
351
|
Rcurses::Cursor.hide
|
@@ -436,36 +353,28 @@ module Rcurses
|
|
436
353
|
|
437
354
|
def editline
|
438
355
|
begin
|
439
|
-
# Switch to raw mode without echo
|
440
356
|
STDIN.raw!
|
441
357
|
Rcurses::Cursor.show
|
442
|
-
|
443
|
-
# Initialize position and dimensions
|
444
|
-
# Ensure pane is within screen bounds
|
445
358
|
@x = [[@x, 1].max, @max_w - @w + 1].min
|
446
359
|
@y = [[@y, 1].max, @max_h - @h + 1].min
|
447
|
-
|
448
360
|
@scroll = false
|
449
|
-
@ix
|
361
|
+
@ix = 0
|
450
362
|
row(@y)
|
451
|
-
|
452
363
|
fmt = [@fg, @bg].compact.join(',')
|
453
364
|
col(@x)
|
454
|
-
print @prompt.c(fmt)
|
455
|
-
|
365
|
+
print @prompt.c(fmt)
|
456
366
|
prompt_len = @prompt.pure.length
|
457
367
|
content_len = @w - prompt_len
|
458
368
|
cont = @text.pure.slice(0, content_len)
|
459
|
-
@pos = cont.length
|
369
|
+
@pos = cont.length
|
460
370
|
chr = ''
|
461
371
|
|
462
|
-
while chr != 'ESC'
|
463
|
-
col(@x + prompt_len)
|
464
|
-
cont = cont.slice(0, content_len)
|
465
|
-
print cont.ljust(content_len).c(fmt)
|
466
|
-
col(@x + prompt_len + @pos)
|
467
|
-
|
468
|
-
chr = getchr # Read user input
|
372
|
+
while chr != 'ESC'
|
373
|
+
col(@x + prompt_len)
|
374
|
+
cont = cont.slice(0, content_len)
|
375
|
+
print cont.ljust(content_len).c(fmt)
|
376
|
+
col(@x + prompt_len + @pos)
|
377
|
+
chr = getchr
|
469
378
|
case chr
|
470
379
|
when 'LEFT'
|
471
380
|
@pos -= 1 if @pos > 0
|
@@ -500,7 +409,6 @@ module Rcurses
|
|
500
409
|
end
|
501
410
|
end
|
502
411
|
|
503
|
-
# Handle pasted input
|
504
412
|
while IO.select([$stdin], nil, nil, 0)
|
505
413
|
chr = $stdin.read_nonblock(1) rescue nil
|
506
414
|
break unless chr
|
@@ -511,7 +419,6 @@ module Rcurses
|
|
511
419
|
end
|
512
420
|
end
|
513
421
|
ensure
|
514
|
-
# Restore terminal mode
|
515
422
|
STDIN.cooked!
|
516
423
|
end
|
517
424
|
Rcurses::Cursor.hide
|
@@ -519,23 +426,94 @@ module Rcurses
|
|
519
426
|
|
520
427
|
private
|
521
428
|
|
522
|
-
|
429
|
+
def flush_stdin
|
430
|
+
while IO.select([$stdin], nil, nil, 0.005)
|
431
|
+
begin
|
432
|
+
$stdin.read_nonblock(1024)
|
433
|
+
rescue IO::WaitReadable, EOFError
|
434
|
+
break
|
435
|
+
end
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
523
439
|
def calculate_posx
|
524
440
|
total_length = 0
|
525
441
|
(@ix + @line).times do |i|
|
526
|
-
total_length += @txt[i].pure.length + 1 # +1 for the newline
|
442
|
+
total_length += @txt[i].pure.length + 1 # +1 for the newline
|
527
443
|
end
|
528
444
|
total_length += @pos
|
529
445
|
total_length
|
530
446
|
end
|
531
|
-
|
447
|
+
|
532
448
|
def calculate_line_start_pos
|
533
449
|
total_length = 0
|
534
450
|
(@ix + @line).times do |i|
|
535
|
-
total_length += @txt[i].pure.length + 1
|
451
|
+
total_length += @txt[i].pure.length + 1
|
536
452
|
end
|
537
453
|
total_length
|
538
454
|
end
|
455
|
+
|
456
|
+
def split_line_with_ansi(line, w)
|
457
|
+
open_sequences = {
|
458
|
+
"\e[1m" => "\e[22m",
|
459
|
+
"\e[3m" => "\e[23m",
|
460
|
+
"\e[4m" => "\e[24m",
|
461
|
+
"\e[5m" => "\e[25m",
|
462
|
+
"\e[7m" => "\e[27m"
|
463
|
+
}
|
464
|
+
close_sequences = open_sequences.values + ["\e[0m"]
|
465
|
+
ansi_regex = /\e\[[0-9;]*m/
|
466
|
+
result = []
|
467
|
+
tokens = line.scan(/(\e\[[0-9;]*m|[^\e]+)/).flatten.compact
|
468
|
+
current_line = ''
|
469
|
+
current_line_length = 0
|
470
|
+
active_sequences = []
|
471
|
+
tokens.each do |token|
|
472
|
+
if token.match?(ansi_regex)
|
473
|
+
current_line << token
|
474
|
+
if close_sequences.include?(token)
|
475
|
+
if token == "\e[0m"
|
476
|
+
active_sequences.clear
|
477
|
+
else
|
478
|
+
corresponding_open = open_sequences.key(token)
|
479
|
+
active_sequences.delete(corresponding_open)
|
480
|
+
end
|
481
|
+
else
|
482
|
+
active_sequences << token
|
483
|
+
end
|
484
|
+
else
|
485
|
+
words = token.scan(/\s+|\S+/)
|
486
|
+
words.each do |word|
|
487
|
+
word_length = word.gsub(ansi_regex, '').length
|
488
|
+
if current_line_length + word_length <= w
|
489
|
+
current_line << word
|
490
|
+
current_line_length += word_length
|
491
|
+
else
|
492
|
+
if current_line_length > 0
|
493
|
+
result << current_line
|
494
|
+
current_line = active_sequences.join
|
495
|
+
current_line_length = 0
|
496
|
+
end
|
497
|
+
while word_length > w
|
498
|
+
part = word[0, w]
|
499
|
+
current_line << part
|
500
|
+
result << current_line
|
501
|
+
word = word[w..-1]
|
502
|
+
word_length = word.gsub(ansi_regex, '').length
|
503
|
+
current_line = active_sequences.join
|
504
|
+
current_line_length = 0
|
505
|
+
end
|
506
|
+
if word_length > 0
|
507
|
+
current_line << word
|
508
|
+
current_line_length += word_length
|
509
|
+
end
|
510
|
+
end
|
511
|
+
end
|
512
|
+
end
|
513
|
+
end
|
514
|
+
result << current_line unless current_line.empty?
|
515
|
+
result
|
516
|
+
end
|
539
517
|
end
|
540
518
|
end
|
541
519
|
|
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.0: Flicker reduction by double screen buffer
|
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.0'
|
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,7 +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
|
32
|
+
in panes. Cursor movement around the terminal. New in 3.0: Flicker reduction by
|
33
|
+
double screen buffer.'
|
33
34
|
email: g@isene.com
|
34
35
|
executables: []
|
35
36
|
extensions: []
|