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.
- checksums.yaml +4 -4
- data/README.md +237 -565
- data/examples/basic_panes.rb +43 -0
- data/examples/focus_panes.rb +42 -0
- data/lib/rcurses/cursor.rb +48 -0
- data/lib/rcurses/general.rb +99 -0
- data/lib/rcurses/input.rb +127 -0
- data/lib/rcurses/pane.rb +715 -0
- data/lib/rcurses.rb +61 -0
- data/lib/string_extensions.rb +160 -0
- metadata +13 -4
data/lib/rcurses.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# INFORMATION
|
2
|
+
# Name: rcurses - Ruby CURSES
|
3
|
+
# Language: Pure Ruby
|
4
|
+
# Author: Geir Isene <g@isene.com>
|
5
|
+
# Web_site: http://isene.com/
|
6
|
+
# Github: https://github.com/isene/rcurses
|
7
|
+
# License: Public domain
|
8
|
+
# Version: 4.9.3: Reverted to stable 4.8.3 codebase after 4.9.0-4.9.2 color issues
|
9
|
+
|
10
|
+
require 'io/console' # Basic gem for rcurses
|
11
|
+
require 'io/wait' # stdin handling
|
12
|
+
require 'timeout'
|
13
|
+
|
14
|
+
require_relative 'string_extensions'
|
15
|
+
require_relative 'rcurses/general'
|
16
|
+
require_relative 'rcurses/cursor'
|
17
|
+
require_relative 'rcurses/input'
|
18
|
+
require_relative 'rcurses/pane'
|
19
|
+
|
20
|
+
module Rcurses
|
21
|
+
class << self
|
22
|
+
# Public: Initialize Rcurses. Switches terminal into raw/no-echo
|
23
|
+
# and registers cleanup handlers. Idempotent.
|
24
|
+
def init!
|
25
|
+
return if @initialized
|
26
|
+
return unless $stdin.tty?
|
27
|
+
|
28
|
+
# enter raw mode, disable echo
|
29
|
+
$stdin.raw!
|
30
|
+
$stdin.echo = false
|
31
|
+
|
32
|
+
# ensure cleanup on normal exit
|
33
|
+
at_exit { cleanup! }
|
34
|
+
|
35
|
+
# ensure cleanup on signals
|
36
|
+
%w[INT TERM].each do |sig|
|
37
|
+
trap(sig) { cleanup!; exit }
|
38
|
+
end
|
39
|
+
|
40
|
+
@initialized = true
|
41
|
+
end
|
42
|
+
|
43
|
+
# Public: Restore terminal to normal mode, clear screen, show cursor.
|
44
|
+
# Idempotent: subsequent calls do nothing.
|
45
|
+
def cleanup!
|
46
|
+
return if @cleaned_up
|
47
|
+
|
48
|
+
$stdin.cooked!
|
49
|
+
$stdin.echo = true
|
50
|
+
Rcurses.clear_screen
|
51
|
+
Cursor.show
|
52
|
+
|
53
|
+
@cleaned_up = true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Kick off initialization as soon as the library is required.
|
58
|
+
init!
|
59
|
+
end
|
60
|
+
|
61
|
+
# vim: set sw=2 sts=2 et filetype=ruby fdn=2 fcs=fold\:\ :
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# string_extensions.rb
|
2
|
+
|
3
|
+
class String
|
4
|
+
# 256-color or truecolor RGB foregroundbreset only the fg (SGR 39)
|
5
|
+
def fg(color)
|
6
|
+
sp, ep = if color.to_s =~ /\A[0-9A-Fa-f]{6}\z/
|
7
|
+
r, g, b = color.scan(/../).map { |c| c.to_i(16) }
|
8
|
+
["\e[38;2;#{r};#{g};#{b}m", "\e[39m"]
|
9
|
+
else
|
10
|
+
["\e[38;5;#{color}m", "\e[39m"]
|
11
|
+
end
|
12
|
+
color(self, sp, ep)
|
13
|
+
end
|
14
|
+
|
15
|
+
# 256-color or truecolor RGB backgroundbreset only the bg (SGR 49)
|
16
|
+
def bg(color)
|
17
|
+
sp, ep = if color.to_s =~ /\A[0-9A-Fa-f]{6}\z/
|
18
|
+
r, g, b = color.scan(/../).map { |c| c.to_i(16) }
|
19
|
+
["\e[48;2;#{r};#{g};#{b}m", "\e[49m"]
|
20
|
+
else
|
21
|
+
["\e[48;5;#{color}m", "\e[49m"]
|
22
|
+
end
|
23
|
+
color(self, sp, ep)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Both fg and bg in one go
|
27
|
+
def fb(fg_color, bg_color)
|
28
|
+
parts = []
|
29
|
+
if fg_color.to_s =~ /\A[0-9A-Fa-f]{6}\z/
|
30
|
+
r, g, b = fg_color.scan(/../).map { |c| c.to_i(16) }
|
31
|
+
parts << "38;2;#{r};#{g};#{b}"
|
32
|
+
else
|
33
|
+
parts << "38;5;#{fg_color}"
|
34
|
+
end
|
35
|
+
|
36
|
+
if bg_color.to_s =~ /\A[0-9A-Fa-f]{6}\z/
|
37
|
+
r, g, b = bg_color.scan(/../).map { |c| c.to_i(16) }
|
38
|
+
parts << "48;2;#{r};#{g};#{b}"
|
39
|
+
else
|
40
|
+
parts << "48;5;#{bg_color}"
|
41
|
+
end
|
42
|
+
|
43
|
+
sp = "\e[#{parts.join(';')}m"
|
44
|
+
color(self, sp, "\e[39;49m")
|
45
|
+
end
|
46
|
+
|
47
|
+
# bold, italic, underline, blink, reverse
|
48
|
+
def b; color(self, "\e[1m", "\e[22m"); end
|
49
|
+
def i; color(self, "\e[3m", "\e[23m"); end
|
50
|
+
def u; color(self, "\e[4m", "\e[24m"); end
|
51
|
+
def l; color(self, "\e[5m", "\e[25m"); end
|
52
|
+
def r; color(self, "\e[7m", "\e[27m"); end
|
53
|
+
|
54
|
+
# Internal helper - wraps +text+ in start/end sequences,
|
55
|
+
# and re-applies start on every newline.
|
56
|
+
def color(text, sp, ep = "\e[0m")
|
57
|
+
t = text.gsub("\n", "#{ep}\n#{sp}")
|
58
|
+
"#{sp}#{t}#{ep}"
|
59
|
+
end
|
60
|
+
|
61
|
+
# Combined code: "foo".c("FF0000,00FF00,bui")
|
62
|
+
# — 6-hex or decimal for fg, then for bg, then letters b/i/u/l/r
|
63
|
+
def c(code)
|
64
|
+
parts = code.split(',')
|
65
|
+
seq = []
|
66
|
+
|
67
|
+
fg = parts.shift
|
68
|
+
if fg =~ /\A[0-9A-Fa-f]{6}\z/
|
69
|
+
r,g,b = fg.scan(/../).map{|c|c.to_i(16)}
|
70
|
+
seq << "38;2;#{r};#{g};#{b}"
|
71
|
+
elsif fg =~ /\A\d+\z/
|
72
|
+
seq << "38;5;#{fg}"
|
73
|
+
end
|
74
|
+
|
75
|
+
if parts.any?
|
76
|
+
bg = parts.shift
|
77
|
+
if bg =~ /\A[0-9A-Fa-f]{6}\z/
|
78
|
+
r,g,b = bg.scan(/../).map{|c|c.to_i(16)}
|
79
|
+
seq << "48;2;#{r};#{g};#{b}"
|
80
|
+
elsif bg =~ /\A\d+\z/
|
81
|
+
seq << "48;5;#{bg}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
seq << '1' if code.include?('b')
|
86
|
+
seq << '3' if code.include?('i')
|
87
|
+
seq << '4' if code.include?('u')
|
88
|
+
seq << '5' if code.include?('l')
|
89
|
+
seq << '7' if code.include?('r')
|
90
|
+
|
91
|
+
"\e[#{seq.join(';')}m#{self}\e[0m"
|
92
|
+
end
|
93
|
+
|
94
|
+
# Strip all ANSI SGR sequences
|
95
|
+
def pure
|
96
|
+
gsub(/\e\[\d+(?:;\d+)*m/, '')
|
97
|
+
end
|
98
|
+
|
99
|
+
# Remove stray leading/trailing reset if the string has no other styling
|
100
|
+
def clean_ansi
|
101
|
+
gsub(/\A(?:\e\[0m)+/, '').gsub(/\e\[0m\z/, '')
|
102
|
+
end
|
103
|
+
|
104
|
+
# Truncate the *visible* length to n, but preserve embedded ANSI
|
105
|
+
def shorten(n)
|
106
|
+
count = 0
|
107
|
+
out = ''
|
108
|
+
i = 0
|
109
|
+
|
110
|
+
while i < length && count < n
|
111
|
+
if self[i] == "\e" && (m = self[i..-1].match(/\A(\e\[\d+(?:;\d+)*m)/))
|
112
|
+
out << m[1]
|
113
|
+
i += m[1].length
|
114
|
+
else
|
115
|
+
out << self[i]
|
116
|
+
i += 1
|
117
|
+
count += 1
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
out
|
122
|
+
end
|
123
|
+
|
124
|
+
# Insert +insertion+ at visible position +pos+ (negative → end),
|
125
|
+
# respecting and re-inserting existing ANSI sequences.
|
126
|
+
def inject(insertion, pos)
|
127
|
+
pure_txt = pure
|
128
|
+
visible_len = pure_txt.length
|
129
|
+
pos = visible_len if pos < 0
|
130
|
+
|
131
|
+
count, out, i, injected = 0, '', 0, false
|
132
|
+
|
133
|
+
while i < length
|
134
|
+
if self[i] == "\e" && (m = self[i..-1].match(/\A(\e\[\d+(?:;\d+)*m)/))
|
135
|
+
out << m[1]
|
136
|
+
i += m[1].length
|
137
|
+
else
|
138
|
+
if count == pos && !injected
|
139
|
+
out << insertion
|
140
|
+
injected = true
|
141
|
+
end
|
142
|
+
out << self[i]
|
143
|
+
count += 1
|
144
|
+
i += 1
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
unless injected
|
149
|
+
if out =~ /(\e\[\d+(?:;\d+)*m)\z/
|
150
|
+
trailing = $1
|
151
|
+
out = out[0...-trailing.length] + insertion + trailing
|
152
|
+
else
|
153
|
+
out << insertion
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
out
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
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: 5.0.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-07-
|
11
|
+
date: 2025-07-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: clipboard
|
@@ -29,8 +29,9 @@ 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.
|
33
|
-
|
32
|
+
in panes. Cursor movement around the terminal. 5.0.0: Major improvements - memory
|
33
|
+
leak fixes, terminal state protection, Unicode support, and enhanced error handling
|
34
|
+
while maintaining full backward compatibility and 4.8.3 performance.'
|
34
35
|
email: g@isene.com
|
35
36
|
executables: []
|
36
37
|
extensions: []
|
@@ -38,6 +39,14 @@ extra_rdoc_files: []
|
|
38
39
|
files:
|
39
40
|
- LICENSE
|
40
41
|
- README.md
|
42
|
+
- examples/basic_panes.rb
|
43
|
+
- examples/focus_panes.rb
|
44
|
+
- lib/rcurses.rb
|
45
|
+
- lib/rcurses/cursor.rb
|
46
|
+
- lib/rcurses/general.rb
|
47
|
+
- lib/rcurses/input.rb
|
48
|
+
- lib/rcurses/pane.rb
|
49
|
+
- lib/string_extensions.rb
|
41
50
|
homepage: https://isene.com/
|
42
51
|
licenses:
|
43
52
|
- Unlicense
|