rcurses 4.9.4 → 5.0.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.
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rcurses'
4
+
5
+ @max_h, @max_w = IO.console.winsize
6
+
7
+ # Start by creating the panes; Format:
8
+ # pane = Rcurses::Pane.new( startx, starty, width, height, fg, bg)
9
+ pane_top = Rcurses::Pane.new( 1, 1, @max_w, 1, 255, 236)
10
+ pane_bottom = Rcurses::Pane.new( 1, @max_h, @max_w, 1, 236, 254)
11
+ pane_left = Rcurses::Pane.new( 2, 3, @max_w/2 - 2, @max_h - 4, 52, nil)
12
+ pane_right = Rcurses::Pane.new(@max_w/2 + 1, 2, @max_w/2, @max_h - 2, 255, 52)
13
+
14
+ pane_left.border = true # Adding a border to the left pane
15
+
16
+ # Add content to the panes
17
+ pane_top.text = Time.now.to_s[0..15].b + " Welcome to the rcurses example program"
18
+ pane_left.text = `ls --color`
19
+ pane_right.text = "Output of free:\n\n" + `free`
20
+ pane_bottom.prompt = "Enter any text and press ENTER: ".b # The prompt text before the user starts writing content
21
+
22
+ pane_top.refresh # This is the order of drawing/refreshing the panes
23
+ pane_left.refresh # ...then the left pane
24
+ pane_right.refresh # ...and the right pane
25
+ pane_bottom.editline # Do not use a refresh before editline
26
+
27
+ # Then create a "pop-up" pane in the middle of the screen
28
+ pane_mid = Rcurses::Pane.new(@max_w/2 - 10, @max_h/2 - 5, 20, 10, 18, 254)
29
+ pane_mid.border = true
30
+ pane_mid.text = "You wrote:" + "\n" + pane_bottom.text.i
31
+ pane_mid.align = "c"
32
+ pane_mid.refresh
33
+
34
+ # Then ask the user to hit ENTER before exiting the program
35
+ pane_bottom.prompt = "Now hit ENTER again "
36
+ pane_bottom.text = ""
37
+ pane_bottom.editline
38
+
39
+ # Reset terminal
40
+ $stdin.cooked!
41
+ $stdin.echo = true
42
+ Rcurses::clear_screen
43
+ Rcurses::Cursor.show
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rcurses'
4
+ include Rcurses::Input
5
+ include Rcurses::Cursor
6
+
7
+ @max_h, @max_w = IO.console.winsize
8
+ @pane = []
9
+ @focused = 3 # Start with pane 3 so that the first keypress focuses back to Pane 0
10
+ Rcurses::Cursor.hide
11
+
12
+ # Start by creating the panes; Format:
13
+ # pane = Rcurses::Pane.new( startx, starty, width, height, fg, bg)
14
+ pane_back = Rcurses::Pane.new( 1, 1, @max_w, @max_h, nil, 236)
15
+ @pane[0] = Rcurses::Pane.new( 4, 4, 20, 10, 236, 254)
16
+ @pane[1] = Rcurses::Pane.new( 30, 10, 16, 12, 232, 132)
17
+ @pane[2] = Rcurses::Pane.new( 8, 20, 30, 20, 136, 54)
18
+ @pane[3] = Rcurses::Pane.new( 50, 30, 24, 10, 206, 24)
19
+
20
+ pane_back.text = "PRESS ANY KEY TO SHIFT FOCUS. PRESS 'ESC' TO QUIT."
21
+ @pane.each_index { |i| @pane[i].text = "This is pane " + i.to_s }
22
+ pane_back.full_refresh
23
+ @pane.each_index { |i| @pane[i].refresh }
24
+
25
+ input = ''
26
+ while input != 'ESC'
27
+ input = getchr
28
+ pane_back.full_refresh
29
+ @focused += 1
30
+ @focused = 0 if @focused == @pane.size
31
+ @pane.each_index {|i| @pane[i].border = false}
32
+ @pane.each_index {|i| @pane[i].full_refresh}
33
+ @pane[@focused].border = true
34
+ @pane[@focused].full_refresh
35
+ end
36
+
37
+ # Always end an application with these lines:
38
+ $stdin.cooked!
39
+ $stdin.echo = true
40
+ Rcurses.clear_screen
41
+ Rcurses::Cursor.show
42
+
@@ -0,0 +1,48 @@
1
+ module Rcurses
2
+ module Cursor
3
+ # Terminal cursor movement ANSI codes (inspired by https://github.com/piotrmurach/tty-cursor)
4
+ module_function
5
+ ESC = "\e".freeze
6
+ CSI = "\e[".freeze
7
+ def save; print(Gem.win_platform? ? CSI + 's' : ESC + '7'); end # Save current position
8
+ def restore; print(Gem.win_platform? ? CSI + 'u' : ESC + '8'); end # Restore cursor position
9
+ def pos # Query cursor current position
10
+ res = ''
11
+ $stdin.raw do |stdin|
12
+ $stdout << CSI + '6n' # The actual ANSI get-position
13
+ $stdout.flush
14
+ while (c = stdin.getc) != 'R'
15
+ res << c if c
16
+ end
17
+ end
18
+ m = res.match(/(?<row>\d+);(?<col>\d+)/)
19
+ return m[:row].to_i, m[:col].to_i
20
+ end
21
+ def rowget
22
+ row, _col = pos
23
+ row
24
+ end
25
+ def colget
26
+ _row, col = pos
27
+ col
28
+ end
29
+ def set(r = 1, c = 1); print(CSI + "#{r}d"); print(CSI + "#{c}G"); end # Set cursor position to Row, Col (y,x)
30
+ def up(n = 1); print(CSI + "#{(n)}A"); end # Move cursor up by n
31
+ def down(n = 1); print(CSI + "#{(n)}B"); end # Move the cursor down by n
32
+ def left(n = 1); print(CSI + "#{n}D"); end # Move the cursor backward by n
33
+ def right(n = 1); print(CSI + "#{n}C"); end # Move the cursor forward by n
34
+ def col(c = 1); print(CSI + "#{c}G"); end # Cursor moves to nth position horizontally in the current line
35
+ def row(r = 1); print(CSI + "#{r}d"); end # Cursor moves to the nth position vertically in the current column
36
+ def next_line; print(CSI + 'E' + CSI + "1G"); end # Move cursor down to beginning of next line
37
+ def prev_line; print(CSI + 'A' + CSI + "1G"); end # Move cursor up to beginning of previous line
38
+ def clear_char(n = 1); print(CSI + "#{n}X"); end # Erase n characters from the current cursor position
39
+ def clear_line; print(CSI + '2K' + CSI + "1G"); end # Erase the entire current line and return to beginning of the line
40
+ def clear_line_before; print(CSI + '1K'); end # Erase from the beginning of the line up to and including the current cursor position.
41
+ def clear_line_after; print(CSI + '0K'); end # Erase from the current position (inclusive) to the end of the line
42
+ def clear_screen_down; print(CSI + 'J'); end # Clear screen down from current row
43
+ def scroll_up; print(ESC + 'M'); end # Scroll display up one line
44
+ def scroll_down; print(ESC + 'D'); end # Scroll display down one line
45
+ def hide; print(CSI + '?25l'); end # Scroll display down one line
46
+ def show; print(CSI + '?25h'); end # Scroll display down one line
47
+ end
48
+ end
@@ -0,0 +1,99 @@
1
+ module Rcurses
2
+ @@terminal_state_saved = false
3
+ @@original_stty_state = nil
4
+
5
+ def self.clear_screen
6
+ # ANSI code \e[2J clears the screen, and \e[H moves the cursor to the top left.
7
+ print "\e[2J\e[H"
8
+ end
9
+
10
+ def self.save_terminal_state(install_handlers = false)
11
+ unless @@terminal_state_saved
12
+ @@original_stty_state = `stty -g 2>/dev/null`.chomp rescue nil
13
+ @@terminal_state_saved = true
14
+ setup_signal_handlers if install_handlers
15
+ end
16
+ end
17
+
18
+ def self.restore_terminal_state
19
+ if @@terminal_state_saved && @@original_stty_state
20
+ begin
21
+ # Restore terminal settings
22
+ system("stty #{@@original_stty_state} 2>/dev/null")
23
+ # Reset terminal
24
+ print "\e[0m\e[?25h\e[?7h\e[r"
25
+ STDOUT.flush
26
+ rescue
27
+ # Fallback restoration
28
+ begin
29
+ STDIN.cooked! rescue nil
30
+ STDIN.echo = true rescue nil
31
+ rescue
32
+ end
33
+ end
34
+ end
35
+ @@terminal_state_saved = false
36
+ end
37
+
38
+ def self.setup_signal_handlers
39
+ ['TERM', 'INT', 'QUIT', 'HUP'].each do |signal|
40
+ Signal.trap(signal) do
41
+ restore_terminal_state
42
+ exit(1)
43
+ end
44
+ end
45
+
46
+ # Handle WINCH (window size change) gracefully
47
+ Signal.trap('WINCH') do
48
+ # Just ignore for now - applications should handle this themselves
49
+ end
50
+ end
51
+
52
+ def self.with_terminal_protection(install_handlers = true)
53
+ save_terminal_state(install_handlers)
54
+ begin
55
+ yield
56
+ ensure
57
+ restore_terminal_state
58
+ end
59
+ end
60
+
61
+ # Simple, fast display_width function (like original 4.8.3)
62
+ def self.display_width(str)
63
+ return 0 if str.nil? || str.empty?
64
+
65
+ width = 0
66
+ str.each_char do |char|
67
+ cp = char.ord
68
+ if cp == 0
69
+ # NUL – no width
70
+ elsif cp < 32 || (cp >= 0x7F && cp < 0xA0)
71
+ # Control characters: no width
72
+ width += 0
73
+ # Approximate common wide ranges:
74
+ elsif (cp >= 0x1100 && cp <= 0x115F) ||
75
+ cp == 0x2329 || cp == 0x232A ||
76
+ (cp >= 0x2E80 && cp <= 0xA4CF) ||
77
+ (cp >= 0xAC00 && cp <= 0xD7A3) ||
78
+ (cp >= 0xF900 && cp <= 0xFAFF) ||
79
+ (cp >= 0xFE10 && cp <= 0xFE19) ||
80
+ (cp >= 0xFE30 && cp <= 0xFE6F) ||
81
+ (cp >= 0xFF00 && cp <= 0xFF60) ||
82
+ (cp >= 0xFFE0 && cp <= 0xFFE6)
83
+ width += 2
84
+ else
85
+ width += 1
86
+ end
87
+ end
88
+ width
89
+ end
90
+
91
+ # Comprehensive Unicode display width (available but not used in performance-critical paths)
92
+ def self.display_width_unicode(str)
93
+ return 0 if str.nil? || str.empty?
94
+
95
+ # ... full Unicode implementation available when needed ...
96
+ # For now, just delegate to the simple version
97
+ display_width(str)
98
+ end
99
+ end
@@ -0,0 +1,127 @@
1
+ module Rcurses
2
+ module Input
3
+ def getchr(t = nil, flush: true)
4
+ begin
5
+ # 1) Read a byte (with optional timeout)
6
+ begin
7
+ c = t ? Timeout.timeout(t) { $stdin.getch } : $stdin.getch
8
+ rescue Timeout::Error
9
+ return nil
10
+ end
11
+
12
+ # 2) If it's ESC, grab any quick trailing bytes
13
+ seq = c
14
+ if c == "\e"
15
+ if IO.select([$stdin], nil, nil, 0.05)
16
+ begin
17
+ seq << $stdin.read_nonblock(16)
18
+ rescue IO::WaitReadable, EOFError
19
+ end
20
+ end
21
+ end
22
+
23
+ # 3) Single ESC alone
24
+ return "ESC" if seq == "\e"
25
+
26
+ # 4) Shift‑TAB
27
+ return "S-TAB" if seq == "\e[Z"
28
+
29
+ # 5) Legacy single‑char shift‑arrows (your old working ones)
30
+ case seq
31
+ when "\e[a" then return "S-UP"
32
+ when "\e[b" then return "S-DOWN"
33
+ when "\e[c" then return "S-RIGHT"
34
+ when "\e[d" then return "S-LEFT"
35
+ end
36
+
37
+ # 6) CSI style shift‑arrows (e.g. ESC [1;2A )
38
+ if m = seq.match(/\A\e\[\d+;2([ABCD])\z/)
39
+ return { 'A' => "S-UP", 'B' => "S-DOWN", 'C' => "S-RIGHT", 'D' => "S-LEFT" }[m[1]]
40
+ end
41
+
42
+ # 7) Plain arrows
43
+ if m = seq.match(/\A\e\[([ABCD])\z/)
44
+ return { 'A' => "UP", 'B' => "DOWN", 'C' => "RIGHT", 'D' => "LEFT" }[m[1]]
45
+ end
46
+
47
+ # 8) CSI + '~' sequences (Ins, Del, Home, End, PgUp, PgDn, F5-F12)
48
+ if seq.start_with?("\e[") && seq.end_with?("~")
49
+ num = seq[/\d+(?=~)/].to_i
50
+ return case num
51
+ when 1, 7 then "HOME"
52
+ when 2 then "INS"
53
+ when 3 then "DEL"
54
+ when 4, 8 then "END"
55
+ when 5 then "PgUP"
56
+ when 6 then "PgDOWN"
57
+ when 15 then "F5"
58
+ when 17 then "F6"
59
+ when 18 then "F7"
60
+ when 19 then "F8"
61
+ when 20 then "F9"
62
+ when 21 then "F10"
63
+ when 23 then "F11"
64
+ when 24 then "F12"
65
+ else ""
66
+ end
67
+ end
68
+
69
+ # 9) SS3 function keys F1-F4
70
+ if seq.start_with?("\eO") && seq.length == 3
71
+ return case seq[2]
72
+ when 'P' then "F1"
73
+ when 'Q' then "F2"
74
+ when 'R' then "F3"
75
+ when 'S' then "F4"
76
+ else ""
77
+ end
78
+ end
79
+
80
+ # 10) Single / Ctrl-char mappings
81
+ return case seq
82
+ when "\r", "\n" then "ENTER"
83
+ when "\t" then "TAB"
84
+ when "\u007F", "\b" then "BACK"
85
+ when "\u0000" then "C-SPACE"
86
+ when "\u0001" then "C-A"
87
+ when "\u0002" then "C-B"
88
+ when "\u0003" then "C-C"
89
+ when "\u0004" then "C-D"
90
+ when "\u0005" then "C-E"
91
+ when "\u0006" then "C-F"
92
+ when "\u0007" then "C-G"
93
+ when "\u0008" then "C-H"
94
+ when "\u000B" then "C-K"
95
+ when "\u000C" then "C-L"
96
+ when "\u000D" then "C-M"
97
+ when "\u000E" then "C-N"
98
+ when "\u000F" then "C-O"
99
+ when "\u0010" then "C-P"
100
+ when "\u0011" then "C-Q"
101
+ when "\u0012" then "C-R"
102
+ when "\u0013" then "C-S"
103
+ when "\u0014" then "C-T"
104
+ when "\u0015" then "C-U"
105
+ when "\u0016" then "C-V"
106
+ when "\u0018" then "C-X"
107
+ when "\u0019" then "C-Y"
108
+ when "\u001A" then "C-Z"
109
+ when "\u0017" then "WBACK"
110
+ when /\A[[:print:]]\z/ then seq
111
+ else ""
112
+ end
113
+ ensure
114
+ if flush
115
+ while IO.select([$stdin], nil, nil, 0)
116
+ begin
117
+ $stdin.read_nonblock(4096)
118
+ rescue IO::WaitReadable, EOFError
119
+ break
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+