rcurses 5.1.0 → 5.1.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 (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/rcurses/pane.rb +121 -30
  3. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1346854c517fefd3324ccc5613ed153a37ef05affd38f0acdaef9c53f26d8f69
4
- data.tar.gz: 045e189341a04b64bdeecf2086ea65c176d7e0712f7e39184f053e1ed64efb36
3
+ metadata.gz: 8be17e6f76652103c068e4299b2c0bb1818ae74512aaff66e09362cdf10fecbf
4
+ data.tar.gz: 13ebe397820e760f3059bd6df1acfea80c1765e8c7ac66bc00caeb09786119f4
5
5
  SHA512:
6
- metadata.gz: 5e95c6ff7ae4e65910e87eec6811276a857ec3e8fcf4a2a459b300ea13a1a47ed37b4e44abdf2e6292059fe110e987ef647c2069f52f0b86d43d2e1994bceb95
7
- data.tar.gz: c4924e43e90ffef0cf575caa05092012cfa597affc8982529e4254ed8013bf18b43eefa087e190be977f514074eb357f082b82687c8641fba0d6761852d5cff9
6
+ metadata.gz: dfb744f2464d2e4529180c15851e71ce5f033a7495f96e1f6f9bdaf1aaf7e4c6b788bd1164cdc7536a4ab740c3ac3e6348721712787ede4f09e021c958f5fffe
7
+ data.tar.gz: b54c0bf4e06484eed793bf07ed779427357b6635a7008e8ac12fe8318715631697cf7ddc8ca18f3aa71a73b8d327df369e5b670aed0582ede49a5ebef36e6dfe
data/lib/rcurses/pane.rb CHANGED
@@ -9,7 +9,7 @@ module Rcurses
9
9
  attr_accessor :record, :history
10
10
 
11
11
  def initialize(x = 1, y = 1, w = 1, h = 1, fg = nil, bg = nil)
12
- @max_h, @max_w = IO.console.winsize
12
+ @max_h, @max_w = IO.console ? IO.console.winsize : [24, 80]
13
13
  @x = x
14
14
  @y = y
15
15
  @w = w
@@ -198,6 +198,10 @@ module Rcurses
198
198
  STDOUT.print "\e[?25l\e[?7l\e[0m\e[r"
199
199
 
200
200
  fmt = [@fg.to_s, @bg.to_s].join(',')
201
+
202
+ # Skip color application if fg and bg are both nil or empty
203
+ @skip_colors = (@fg.nil? && @bg.nil?) || (fmt == ",")
204
+
201
205
 
202
206
  # Lazy evaluation: If the content or pane width has changed, reinitialize the lazy cache.
203
207
  if !defined?(@cached_text) || @cached_text != cont || @cached_w != @w
@@ -251,16 +255,31 @@ module Rcurses
251
255
  pl = @w - Rcurses.display_width(@txt[l].pure)
252
256
  pl = 0 if pl < 0
253
257
  hl = pl / 2
254
- case @align
255
- when "l"
256
- line_str = @txt[l].c(fmt) + " ".c(fmt) * pl
257
- when "r"
258
- line_str = " ".c(fmt) * pl + @txt[l].c(fmt)
259
- when "c"
260
- line_str = " ".c(fmt) * hl + @txt[l].c(fmt) + " ".c(fmt) * (pl - hl)
258
+ # Skip color application if pane has no colors set or text has ANY ANSI codes
259
+ if @skip_colors || @txt[l].include?("\e[")
260
+ # Don't apply pane colors - text already has ANSI sequences
261
+ case @align
262
+ when "l"
263
+ line_str = @txt[l] + " " * pl
264
+ when "r"
265
+ line_str = " " * pl + @txt[l]
266
+ when "c"
267
+ line_str = " " * hl + @txt[l] + " " * (pl - hl)
268
+ end
269
+ else
270
+ # Apply pane colors normally
271
+ case @align
272
+ when "l"
273
+ line_str = @txt[l].c(fmt) + " ".c(fmt) * pl
274
+ when "r"
275
+ line_str = " ".c(fmt) * pl + @txt[l].c(fmt)
276
+ when "c"
277
+ line_str = " ".c(fmt) * hl + @txt[l].c(fmt) + " ".c(fmt) * (pl - hl)
278
+ end
261
279
  end
262
280
  else
263
- line_str = " ".c(fmt) * @w
281
+ # Empty line - only apply colors if pane has them
282
+ line_str = @skip_colors ? " " * @w : " ".c(fmt) * @w
264
283
  end
265
284
 
266
285
  new_frame << line_str
@@ -278,6 +297,16 @@ module Rcurses
278
297
  # restore wrap, then also reset SGR and scroll-region one more time
279
298
  diff_buf << "\e[#{o_row};#{o_col}H\e[?7h\e[0m\e[r"
280
299
  begin
300
+ # Debug: check what's actually being printed
301
+ if diff_buf.include?("Purpose") && diff_buf.include?("[38;5;")
302
+ File.open("/tmp/rcurses_debug.log", "a") do |f|
303
+ f.puts "=== PRINT DEBUG ==="
304
+ f.puts "diff_buf sample: #{diff_buf[0..200].inspect}"
305
+ f.puts "Has escape byte 27: #{diff_buf.bytes.include?(27)}"
306
+ f.puts "Escape count: #{diff_buf.bytes.count(27)}"
307
+ end
308
+ end
309
+
281
310
  print diff_buf
282
311
  rescue => e
283
312
  # If printing fails, at least try to restore terminal state
@@ -640,34 +669,94 @@ module Rcurses
640
669
  begin
641
670
  return [""] if line.nil? || w <= 0
642
671
 
643
- open_sequences = {
644
- "\e[1m" => "\e[22m",
645
- "\e[3m" => "\e[23m",
646
- "\e[4m" => "\e[24m",
647
- "\e[5m" => "\e[25m",
648
- "\e[7m" => "\e[27m"
649
- }
650
- close_sequences = open_sequences.values + ["\e[0m"]
651
672
  ansi_regex = /\e\[[0-9;]*m/
652
673
  result = []
653
674
  tokens = line.scan(/(\e\[[0-9;]*m|[^\e]+)/).flatten.compact
654
675
  current_line = ''
655
676
  current_line_length = 0
656
- active_sequences = []
677
+
678
+ # Track SGR state properly
679
+ sgr_state = {
680
+ bold: false, # 1/22
681
+ italic: false, # 3/23
682
+ underline: false, # 4/24
683
+ blink: false, # 5/25
684
+ reverse: false, # 7/27
685
+ fg_color: nil, # 38/39 (nil means default)
686
+ bg_color: nil # 48/49 (nil means default)
687
+ }
688
+
689
+ # Helper to parse SGR parameters
690
+ parse_sgr = lambda do |sequence|
691
+ return unless sequence =~ /\e\[([0-9;]*)m/
692
+ param_str = $1
693
+ params = param_str.empty? ? [0] : param_str.split(';').map(&:to_i)
694
+ i = 0
695
+ while i < params.length
696
+ case params[i]
697
+ when 0 # Reset all
698
+ sgr_state[:bold] = false
699
+ sgr_state[:italic] = false
700
+ sgr_state[:underline] = false
701
+ sgr_state[:blink] = false
702
+ sgr_state[:reverse] = false
703
+ sgr_state[:fg_color] = nil
704
+ sgr_state[:bg_color] = nil
705
+ when 1 then sgr_state[:bold] = true
706
+ when 3 then sgr_state[:italic] = true
707
+ when 4 then sgr_state[:underline] = true
708
+ when 5 then sgr_state[:blink] = true
709
+ when 7 then sgr_state[:reverse] = true
710
+ when 22 then sgr_state[:bold] = false
711
+ when 23 then sgr_state[:italic] = false
712
+ when 24 then sgr_state[:underline] = false
713
+ when 25 then sgr_state[:blink] = false
714
+ when 27 then sgr_state[:reverse] = false
715
+ when 38 # Foreground color
716
+ if params[i+1] == 5 && params[i+2] # 256 color
717
+ sgr_state[:fg_color] = "38;5;#{params[i+2]}"
718
+ i += 2
719
+ elsif params[i+1] == 2 && params[i+4] # RGB color
720
+ sgr_state[:fg_color] = "38;2;#{params[i+2]};#{params[i+3]};#{params[i+4]}"
721
+ i += 4
722
+ end
723
+ when 39 then sgr_state[:fg_color] = nil # Default foreground
724
+ when 48 # Background color
725
+ if params[i+1] == 5 && params[i+2] # 256 color
726
+ sgr_state[:bg_color] = "48;5;#{params[i+2]}"
727
+ i += 2
728
+ elsif params[i+1] == 2 && params[i+4] # RGB color
729
+ sgr_state[:bg_color] = "48;2;#{params[i+2]};#{params[i+3]};#{params[i+4]}"
730
+ i += 4
731
+ end
732
+ when 49 then sgr_state[:bg_color] = nil # Default background
733
+ # Handle legacy 8-color codes (30-37, 40-47, 90-97, 100-107)
734
+ when 30..37 then sgr_state[:fg_color] = params[i].to_s
735
+ when 40..47 then sgr_state[:bg_color] = params[i].to_s
736
+ when 90..97 then sgr_state[:fg_color] = params[i].to_s
737
+ when 100..107 then sgr_state[:bg_color] = params[i].to_s
738
+ end
739
+ i += 1
740
+ end
741
+ end
742
+
743
+ # Helper to reconstruct SGR sequence from state
744
+ build_sgr = lambda do
745
+ codes = []
746
+ codes << "1" if sgr_state[:bold]
747
+ codes << "3" if sgr_state[:italic]
748
+ codes << "4" if sgr_state[:underline]
749
+ codes << "5" if sgr_state[:blink]
750
+ codes << "7" if sgr_state[:reverse]
751
+ codes << sgr_state[:fg_color] if sgr_state[:fg_color]
752
+ codes << sgr_state[:bg_color] if sgr_state[:bg_color]
753
+ codes.empty? ? "" : "\e[#{codes.join(';')}m"
754
+ end
657
755
 
658
756
  tokens.each do |token|
659
757
  if token.match?(ansi_regex)
660
758
  current_line << token
661
- if close_sequences.include?(token)
662
- if token == "\e[0m"
663
- active_sequences.clear
664
- else
665
- corresponding_open = open_sequences.key(token)
666
- active_sequences.delete(corresponding_open)
667
- end
668
- else
669
- active_sequences << token
670
- end
759
+ parse_sgr.call(token)
671
760
  else
672
761
  words = token.scan(/\s+|\S+/)
673
762
  words.each do |word|
@@ -679,7 +768,8 @@ module Rcurses
679
768
  else
680
769
  if current_line_length > 0
681
770
  result << current_line
682
- current_line = active_sequences.join
771
+ # Start new line with current SGR state
772
+ current_line = build_sgr.call
683
773
  current_line_length = 0
684
774
  end
685
775
  while word_length > w
@@ -688,7 +778,8 @@ module Rcurses
688
778
  result << current_line
689
779
  word = word[[w, word.length].min..-1] || ""
690
780
  word_length = Rcurses.display_width(word.gsub(ansi_regex, ''))
691
- current_line = active_sequences.join
781
+ # Start new line with current SGR state
782
+ current_line = build_sgr.call
692
783
  current_line_length = 0
693
784
  end
694
785
  if word_length > 0
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: 5.1.0
4
+ version: 5.1.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-08-12 00:00:00.000000000 Z
11
+ date: 2025-08-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: clipboard