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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/rcurses/pane.rb +206 -218
  3. data/lib/rcurses.rb +1 -1
  4. metadata +4 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 24a1261f7feb08089088cd39e074ce58d7b31db2577c5d172cc702ca91c99341
4
- data.tar.gz: fe64f92207aad03e6d6b7c0e8cdf61503a0e3c69dc69386f137cb16c1af80956
3
+ metadata.gz: 07bf042dcaa49e65d87441e77e32cb04f6d8738abe081979bc8589966b2ec65f
4
+ data.tar.gz: 234ad19f3e79216d0dd0561e2cfbb43318d7f308c596ab2a847eeda6fefe9563
5
5
  SHA512:
6
- metadata.gz: de7a10ba81e044649267600766fdc49df41969bfdd37db99be7b82883288e3120e51558ad7052cc8d5b766d09c792fc2c8570792e2da2b2f7e6b9de5f5a29cac
7
- data.tar.gz: 2e8fb40766d423138f6bf4fa29c040285c800ca6c9b68e155fc3106cf31eddb585176d622fda17b1327661e9acc452c15434dd56a977f7b6d4eabe711674ecf2
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 = "" # Initialize text variable
17
- @align = "l" # Default alignment
18
- @scroll = true # Initialize scroll indicators to true
19
- @prompt = "" # Initialize prompt for editline
20
- @ix = 0 # Text index (starting text line in pane)
21
- @max_h, @max_w = IO.console.winsize
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(x, y)
25
- @x += x
26
- @y += 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
- # Define the core of the ANSI escape sequence handling
81
- def split_line_with_ansi(line, w)
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
- col(@x); row(@y) # Cursor to start of pane
191
- fmt = [@fg, @bg].compact.join(',') # Format for printing in pane (fg,bg)
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
- @h.times do |i| # Print pane content
197
- l = @ix + i # The current line to be printed
198
- if @txt[l].to_s != "" # Print the text line
199
- # Get padding width and half width
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
- print @txt[l].c(fmt) + " ".c(fmt) * pl
129
+ line_str = @txt[l].c(fmt) + " ".c(fmt) * pl
206
130
  when "r"
207
- print " ".c(fmt) * pl + @txt[l].c(fmt)
131
+ line_str = " ".c(fmt) * pl + @txt[l].c(fmt)
208
132
  when "c"
209
- print " ".c(fmt) * hl + @txt[l].c(fmt) + " ".c(fmt) * (pl - hl)
133
+ line_str = " ".c(fmt) * hl + @txt[l].c(fmt) + " ".c(fmt) * (pl - hl)
210
134
  end
211
135
  else
212
- print " ".c(fmt) * @w
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
- if @ix > 0 and @scroll # Print "more" marker at top
219
- col(@x + @w - 1); row(@y)
220
- print "".c(fmt)
221
- @moreup = true
222
- else
223
- @moreup = false
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 @txt.length - @ix > @h and @scroll # Print bottom "more" marker
227
- col(@x + @w - 1); row(@y + @h - 1)
228
- print "".c(fmt)
229
- @moredown = true
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
- if @border # Print border if @border is set to true
235
- row(@y - 1); col(@x - 1)
236
- print ("┌" + "─" * @w + "┐").c(fmt)
237
- @h.times do |i|
238
- row(@y + i); col(@x - 1)
239
- print "│".c(fmt)
240
- col(@x + @w)
241
- print "│".c(fmt)
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
- row(o_row)
248
- col(o_col)
249
- @txt
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
- def puts(txt)
253
- @text = txt
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!(/\*(.+?)\*/, '\1'.b)
329
- cont.gsub!(/\/(.+?)\//, '\1'.i)
330
- cont.gsub!(/_(.+?)_/, '\1'.u)
331
- cont.gsub!(/#(.+?)#/, '\1'.r)
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
- # Initialize cursor position and indices
347
- @ix = 0 # Starting index of text lines displayed in the pane
348
- @line = 0 # Current line number relative to the pane's visible area
349
- @pos = 0 # Position within the current line (character index)
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' # Continue until ESC is pressed
354
- row(@y + @line) # Move cursor to the correct row
355
- col(@x + @pos) # Move cursor to the correct column
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' # Left justify
290
+ when 'C-L'
360
291
  @align = 'l'
361
- when 'C-R' # Right justify
292
+ when 'C-R'
362
293
  @align = 'r'
363
- when 'C-C' # Center justify
294
+ when 'C-C'
364
295
  @align = 'c'
365
- when 'C-Y' # Copy pane content to clipboard
296
+ when 'C-Y'
366
297
  Clipboard.copy(@text.pure)
367
- when 'C-S' # Save edited text back to @text and exit
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' # Delete character at current position
303
+ when 'DEL'
373
304
  posx = calculate_posx
374
305
  content.slice!(posx)
375
- when 'BACK' # Backspace (delete character before current position)
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' # Word backspace
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' # Kill line (delete from cursor to end of line)
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' # Move cursor up one line
322
+ when 'UP'
392
323
  up
393
- when 'DOWN' # Move cursor down one line
324
+ when 'DOWN'
394
325
  down
395
- when 'RIGHT' # Move cursor right one character
326
+ when 'RIGHT'
396
327
  right
397
- when 'LEFT' # Move cursor left one character
328
+ when 'LEFT'
398
329
  left
399
- when 'HOME' # Move to start of line
330
+ when 'HOME'
400
331
  @pos = 0
401
- when 'END' # Move to end of line
332
+ when 'END'
402
333
  current_line_length = @txt[@ix + @line]&.length || 0
403
334
  @pos = current_line_length
404
- when 'C-HOME' # Move to start of pane
335
+ when 'C-HOME'
405
336
  @ix = 0
406
337
  @line = 0
407
338
  @pos = 0
408
- when 'C-END' # Move to end of pane
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' # Insert newline at current position
345
+ when 'ENTER'
415
346
  posx = calculate_posx
416
347
  content.insert(posx, "¬\n")
417
348
  right
418
- when /^.$/ # Insert character at current position
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) # Refresh the pane with the current 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 = 0
378
+ @ix = 0
457
379
  row(@y)
458
-
459
380
  fmt = [@fg, @bg].compact.join(',')
460
381
  col(@x)
461
- print @prompt.c(fmt) # Print prompt at the pane's starting position
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 # Set initial cursor position at the end of content
386
+ @pos = cont.length
467
387
  chr = ''
468
388
 
469
- while chr != 'ESC' # Continue until ESC is pressed
470
- col(@x + prompt_len) # Set cursor at start of content
471
- cont = cont.slice(0, content_len) # Trim content to max length
472
- print cont.ljust(content_len).c(fmt) # Print content, left-justified
473
- col(@x + prompt_len + @pos) # Set cursor to current position
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
- # Calculates the position in the content string corresponding to the current cursor position
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 the newline character
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
- # Calculates the starting position of the current line in the content string
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 # +1 for the newline character
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: 2.10: Added key captures for S-UP, S-DOWN, S-LEFT and S-RIGHT. added pane properties 'moreup' and 'moredown'
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: '2.10'
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-03-30 00:00:00.000000000 Z
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 2.10: Added key captures for
33
- S-UP, S-DOWN, S-LEFT and S-RIGHT. added pane properties ''moreup'' and ''moredown''.'
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: []