terminal_rb 0.6.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 +7 -0
- data/.yardopts +11 -0
- data/README.md +55 -0
- data/bin/bbcode +46 -0
- data/examples/24bit-colors.rb +20 -0
- data/examples/3bit-colors.rb +18 -0
- data/examples/8bit-colors.rb +32 -0
- data/examples/attributes.rb +14 -0
- data/examples/bbcode.rb +28 -0
- data/examples/info.rb +15 -0
- data/examples/key-codes.rb +22 -0
- data/lib/terminal/ansi/attributes.rb +209 -0
- data/lib/terminal/ansi/named_colors.rb +668 -0
- data/lib/terminal/ansi.rb +593 -0
- data/lib/terminal/detect.rb +151 -0
- data/lib/terminal/input.rb +187 -0
- data/lib/terminal/preload.rb +8 -0
- data/lib/terminal/rspec/helper.rb +33 -0
- data/lib/terminal/text/char_width.rb +2585 -0
- data/lib/terminal/text.rb +542 -0
- data/lib/terminal/version.rb +6 -0
- data/lib/terminal.rb +269 -0
- data/lib/terminal_rb.rb +3 -0
- metadata +68 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Terminal
|
|
4
|
+
module Detect
|
|
5
|
+
class << self
|
|
6
|
+
def application
|
|
7
|
+
return :kitty if ENV.key?('KITTY_PID')
|
|
8
|
+
app_from_term_program || app_from_term
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def colors
|
|
12
|
+
case app_from_term_program
|
|
13
|
+
when :kitty, :ghostty, :vscode, :iterm
|
|
14
|
+
16_777_216
|
|
15
|
+
else
|
|
16
|
+
term = ENV['TERM'] or return 8
|
|
17
|
+
return 16_777_216 if /[+-]direct/.match?(term)
|
|
18
|
+
match = /[-+](\d+)color/.match(term) and return match[1].to_i
|
|
19
|
+
match = /[-+](\d+)bit/.match(term) and return 2**match[1].to_i
|
|
20
|
+
ret = color_by_name[term] and return ret
|
|
21
|
+
if /^(
|
|
22
|
+
iTerm\s?\d*\.app
|
|
23
|
+
|nsterm-build\d+
|
|
24
|
+
|terminology(-[0-9.]+)?
|
|
25
|
+
)$/x.match?(
|
|
26
|
+
term
|
|
27
|
+
)
|
|
28
|
+
return 256
|
|
29
|
+
end
|
|
30
|
+
if /^(dg+ccc|dgunix+ccc|d430.*?[-+](dg|unix).*?[-+]ccc)$/.match?(term)
|
|
31
|
+
return 52
|
|
32
|
+
end
|
|
33
|
+
8
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def app_from_term_program
|
|
40
|
+
value = ENV['TERM_PROGRAM'] or return
|
|
41
|
+
{
|
|
42
|
+
'Apple_Terminal' => :macos,
|
|
43
|
+
'CodeEditApp_Terminal' => :code_edit,
|
|
44
|
+
'FluentTerminal' => :fluent,
|
|
45
|
+
'ghostty' => :ghostty,
|
|
46
|
+
'Hyper' => :hyper,
|
|
47
|
+
'HyperTerm' => :hyper,
|
|
48
|
+
'iTerm.app' => :iterm,
|
|
49
|
+
'Terminus' => :terminus,
|
|
50
|
+
'tmux' => :tmux,
|
|
51
|
+
'vscode' => :vscode,
|
|
52
|
+
'WarpTerminal' => :warp,
|
|
53
|
+
'WezTerm' => :wezterm
|
|
54
|
+
}[
|
|
55
|
+
value
|
|
56
|
+
]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def app_from_term
|
|
60
|
+
value = ENV['TERM'] or return
|
|
61
|
+
{
|
|
62
|
+
'alacritty' => :alacritty,
|
|
63
|
+
'amiga' => :amiga,
|
|
64
|
+
'd430' => :dg_unix,
|
|
65
|
+
'd470' => :dg_unix,
|
|
66
|
+
'dg+' => :dg_unix,
|
|
67
|
+
'dgunix' => :dg_unix,
|
|
68
|
+
'hp+' => :hpterm,
|
|
69
|
+
'hpterm' => :hpterm,
|
|
70
|
+
'mintty' => :mintty,
|
|
71
|
+
'ms-terminal' => :ms_terminal,
|
|
72
|
+
'ncr260' => :ncr260,
|
|
73
|
+
'nsterm' => :nsterm,
|
|
74
|
+
'terminator' => :terminator,
|
|
75
|
+
'terminology' => :terminology,
|
|
76
|
+
'termite' => :termite,
|
|
77
|
+
'vt100' => :vt100,
|
|
78
|
+
'wy350' => :wyse,
|
|
79
|
+
'wy370' => :wyse,
|
|
80
|
+
'xnuppc' => :xnuppc
|
|
81
|
+
}.each_pair { |str, type| return type if value.start_with?(str) }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def color_by_name
|
|
85
|
+
{
|
|
86
|
+
'alacritty' => 256,
|
|
87
|
+
'kitty' => 256,
|
|
88
|
+
'mintty' => 256,
|
|
89
|
+
'ms-terminal' => 256,
|
|
90
|
+
'nsterm' => 256,
|
|
91
|
+
'terminator' => 256,
|
|
92
|
+
'termite' => 256,
|
|
93
|
+
'vscode' => 256,
|
|
94
|
+
'hpterm-color' => 64,
|
|
95
|
+
'wy370-105k' => 64,
|
|
96
|
+
'wy370-EPC' => 64,
|
|
97
|
+
'wy370-nk' => 64,
|
|
98
|
+
'wy370-rv' => 64,
|
|
99
|
+
'wy370-tek' => 64,
|
|
100
|
+
'wy370-vb' => 64,
|
|
101
|
+
'wy370-w' => 64,
|
|
102
|
+
'wy370-wvb' => 64,
|
|
103
|
+
'wy370' => 64,
|
|
104
|
+
'amiga-vnc' => 16,
|
|
105
|
+
'd430-dg' => 16,
|
|
106
|
+
'd430-unix-25' => 16,
|
|
107
|
+
'd430-unix-s' => 16,
|
|
108
|
+
'd430-unix-sr' => 16,
|
|
109
|
+
'd430-unix-w' => 16,
|
|
110
|
+
'd430-unix' => 16,
|
|
111
|
+
'd430c-dg' => 16,
|
|
112
|
+
'd430c-unix-25' => 16,
|
|
113
|
+
'd430c-unix-s' => 16,
|
|
114
|
+
'd430c-unix-sr' => 16,
|
|
115
|
+
'd430c-unix-w' => 16,
|
|
116
|
+
'd430c-unix' => 16,
|
|
117
|
+
'd470-7b' => 16,
|
|
118
|
+
'd470-dg' => 16,
|
|
119
|
+
'd470' => 16,
|
|
120
|
+
'd470c-7b' => 16,
|
|
121
|
+
'd470c-dg|' => 16,
|
|
122
|
+
'd470c' => 16,
|
|
123
|
+
'dg+color' => 16,
|
|
124
|
+
'dg+fixed' => 16,
|
|
125
|
+
'dgmode+color' => 16,
|
|
126
|
+
'dgunix+fixed' => 16,
|
|
127
|
+
'hp+color' => 16,
|
|
128
|
+
'ncr260wy325pp' => 16,
|
|
129
|
+
'ncr260wy325wpp' => 16,
|
|
130
|
+
'ncr260wy350pp' => 16,
|
|
131
|
+
'ncr260wy350wpp' => 16,
|
|
132
|
+
'nsterm-7-c' => 16,
|
|
133
|
+
'nsterm-bce' => 16,
|
|
134
|
+
'nsterm-c-acs' => 16,
|
|
135
|
+
'nsterm-c-s-7' => 16,
|
|
136
|
+
'nsterm-c-s-acs' => 16,
|
|
137
|
+
'nsterm-c-s' => 16,
|
|
138
|
+
'nsterm-c' => 16,
|
|
139
|
+
'nsterm+c' => 16,
|
|
140
|
+
'vt100' => 8,
|
|
141
|
+
'wy350' => 8,
|
|
142
|
+
'xnuppc' => 8,
|
|
143
|
+
'dumb' => 2,
|
|
144
|
+
'dummy' => 2
|
|
145
|
+
}
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
private_constant :Detect
|
|
151
|
+
end
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'io/console'
|
|
4
|
+
|
|
5
|
+
module Terminal
|
|
6
|
+
class << self
|
|
7
|
+
# Read next keyboard input.
|
|
8
|
+
#
|
|
9
|
+
# The input will be returned as named key codes like "Ctrl+C" by default.
|
|
10
|
+
# This can be changed by `mode`.
|
|
11
|
+
#
|
|
12
|
+
# @param [:named, :raw, :both] mode modifies the result
|
|
13
|
+
# @return [String] key code ("as is") in `:raw` mode
|
|
14
|
+
# @return [String] key name in `:named` mode
|
|
15
|
+
# @return [[String, String]] key code and key name in `:both` mode
|
|
16
|
+
def read_key(mode: :named)
|
|
17
|
+
key = _raw_read_key or return
|
|
18
|
+
return key if mode == :raw
|
|
19
|
+
mode == :both ? [key, _key_name(key)] : _key_name(key) || key
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def _raw_read_key
|
|
25
|
+
return unless @in
|
|
26
|
+
return @in.getc unless @in.tty?
|
|
27
|
+
key = @in.getch
|
|
28
|
+
while (nc = @in.read_nonblock(1, exception: false))
|
|
29
|
+
String === nc ? key += nc : break
|
|
30
|
+
end
|
|
31
|
+
key
|
|
32
|
+
rescue Interrupt
|
|
33
|
+
key
|
|
34
|
+
rescue IOError, SystemCallError
|
|
35
|
+
@in = nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def _key_name(key)
|
|
39
|
+
return unless key
|
|
40
|
+
return KEY_MAP[key]&.dup if key.size != 1
|
|
41
|
+
KEY_MAP[key]&.dup if (ord = key.ord) == 127 || (ord > 0 && ord < 28)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
@in = STDIN
|
|
46
|
+
|
|
47
|
+
KEY_MAP =
|
|
48
|
+
Module
|
|
49
|
+
.new do
|
|
50
|
+
def self.add_modifiers(**keys)
|
|
51
|
+
@mods.each_pair do |mod, pref|
|
|
52
|
+
@map.merge!(
|
|
53
|
+
keys.to_h do |name, code|
|
|
54
|
+
["\e[1;#{mod}#{code}", "#{pref}+#{name}"]
|
|
55
|
+
end
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.add_keys(**keys)
|
|
61
|
+
@map.merge!(keys.to_h { |name, code| ["\e[#{code}", name] })
|
|
62
|
+
add_modifiers(**keys)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.add_fkeys(**keys)
|
|
66
|
+
@map.merge!(keys.to_h { |name, code| ["\e[#{code}~", name] })
|
|
67
|
+
@mods.each_pair do |mod, prefix|
|
|
68
|
+
@map.merge!(
|
|
69
|
+
keys.to_h do |name, code|
|
|
70
|
+
["\e[#{code};#{mod}~", "#{prefix}+#{name}"]
|
|
71
|
+
end
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.add_alt_keys(**keys)
|
|
77
|
+
keys.each_pair do |name, code|
|
|
78
|
+
@map[code] = name
|
|
79
|
+
@map["\e#{code}"] = "Alt+#{name}" # kitty
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def self.to_hash
|
|
84
|
+
# control codes
|
|
85
|
+
num = 0
|
|
86
|
+
@map = ('A'..'Z').to_h { [(num += 1).chr, "Ctrl+#{_1}"] }
|
|
87
|
+
|
|
88
|
+
add_keys('F1' => 'P', 'F2' => 'Q', 'F3' => 'R', 'F4' => 'S')
|
|
89
|
+
|
|
90
|
+
add_keys(
|
|
91
|
+
'Up' => 'A',
|
|
92
|
+
'Down' => 'B',
|
|
93
|
+
'Right' => 'C',
|
|
94
|
+
'Left' => 'D',
|
|
95
|
+
'End' => 'F',
|
|
96
|
+
'Home' => 'H'
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
add_fkeys(
|
|
100
|
+
'DEL' => '3',
|
|
101
|
+
'PgUp' => '5',
|
|
102
|
+
'PgDown' => '6',
|
|
103
|
+
# -
|
|
104
|
+
'F1' => 'F1',
|
|
105
|
+
'F2' => 'F2',
|
|
106
|
+
'F3' => 'F3',
|
|
107
|
+
'F4' => 'F4',
|
|
108
|
+
# -
|
|
109
|
+
'F5' => '15',
|
|
110
|
+
'F6' => '17',
|
|
111
|
+
'F7' => '18',
|
|
112
|
+
'F8' => '19',
|
|
113
|
+
'F9' => '20',
|
|
114
|
+
'F10' => '21',
|
|
115
|
+
'F11' => '23',
|
|
116
|
+
'F12' => '24',
|
|
117
|
+
'F13' => '25',
|
|
118
|
+
'F14' => '26',
|
|
119
|
+
'F15' => '28',
|
|
120
|
+
'F16' => '29',
|
|
121
|
+
'F17' => '31',
|
|
122
|
+
'F18' => '32',
|
|
123
|
+
'F19' => '33',
|
|
124
|
+
'F20' => '34'
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
add_fkeys('F3' => '13') # Kitty
|
|
128
|
+
|
|
129
|
+
add_alt_keys(
|
|
130
|
+
'ESC' => "\e",
|
|
131
|
+
'ENTER' => "\r",
|
|
132
|
+
'TAB' => "\t",
|
|
133
|
+
'BACK' => "\u007F",
|
|
134
|
+
'Ctrl+BACK' => "\b",
|
|
135
|
+
'Shift+TAB' => "\e[Z"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# overrides and additional keys
|
|
139
|
+
@map.merge!(
|
|
140
|
+
"\4" => 'DEL',
|
|
141
|
+
"\e[5" => 'PgUp',
|
|
142
|
+
"\e[6" => 'PgDown',
|
|
143
|
+
# SS3 control (VT 100 etc)
|
|
144
|
+
"\eOA" => 'Up',
|
|
145
|
+
"\eOB" => 'Down',
|
|
146
|
+
"\eOC" => 'Right',
|
|
147
|
+
"\eOD" => 'Left',
|
|
148
|
+
"\eOP" => 'F1',
|
|
149
|
+
"\eOQ" => 'F2',
|
|
150
|
+
"\eOR" => 'F3',
|
|
151
|
+
"\eOS" => 'F4',
|
|
152
|
+
"\eO2P" => 'Shift+F1',
|
|
153
|
+
"\eO2Q" => 'Shift+F2',
|
|
154
|
+
"\eO2R" => 'Shift+F3',
|
|
155
|
+
"\eO2S" => 'Shift+F4',
|
|
156
|
+
"\eOt" => 'F5',
|
|
157
|
+
"\eOu" => 'F6',
|
|
158
|
+
"\eOv" => 'F7',
|
|
159
|
+
"\eOw" => 'F8',
|
|
160
|
+
"\eOl" => 'F9',
|
|
161
|
+
"\eOx" => 'F10'
|
|
162
|
+
)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
@mods = {
|
|
166
|
+
2 => 'Shift',
|
|
167
|
+
3 => 'Alt',
|
|
168
|
+
4 => 'Alt-Shift',
|
|
169
|
+
5 => 'Ctrl',
|
|
170
|
+
6 => 'Ctrl-Shift',
|
|
171
|
+
7 => 'Ctrl-Alt',
|
|
172
|
+
8 => 'Ctrl-Alt-Shift',
|
|
173
|
+
9 => 'Meta',
|
|
174
|
+
10 => 'Meta-Shift',
|
|
175
|
+
11 => 'Meta-Alt',
|
|
176
|
+
12 => 'Meta-Alt-Shift',
|
|
177
|
+
13 => 'Ctrl-Meta',
|
|
178
|
+
14 => 'Ctrl-Meta-Shift',
|
|
179
|
+
15 => 'Ctrl-Meta-Alt',
|
|
180
|
+
16 => 'Ctrl-Meta-Alt-Shift'
|
|
181
|
+
}.compare_by_identity
|
|
182
|
+
end
|
|
183
|
+
.to_hash
|
|
184
|
+
.freeze
|
|
185
|
+
|
|
186
|
+
private_constant :KEY_MAP
|
|
187
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.shared_context 'with Terminal' do |mod, ansi: true, winsize: [25, 80]|
|
|
4
|
+
let(:wrapper_module) { Module.new }
|
|
5
|
+
let(:stdout) { double(:STDOUT, winsize: winsize, tty?: ansi) }
|
|
6
|
+
let(:stdoutput) { [] }
|
|
7
|
+
let(:terminal) do
|
|
8
|
+
load('terminal/ansi/attributes.rb', wrapper_module)
|
|
9
|
+
load('terminal/ansi.rb', wrapper_module)
|
|
10
|
+
load('terminal/input.rb', wrapper_module)
|
|
11
|
+
load('terminal/detect.rb', wrapper_module)
|
|
12
|
+
load('terminal.rb', wrapper_module)
|
|
13
|
+
wrapper_module::Terminal
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
before do
|
|
17
|
+
allow(stdout).to receive(:write) do |*args|
|
|
18
|
+
stdoutput.concat(args)
|
|
19
|
+
nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
wrapper_module::STDOUT = stdout unless defined?(wrapper_module::STDOUT)
|
|
23
|
+
|
|
24
|
+
unless ansi
|
|
25
|
+
allow(ENV).to receive(:[]).and_call_original
|
|
26
|
+
allow(ENV).to receive(:[]).with('LINES').and_return winsize.first
|
|
27
|
+
allow(ENV).to receive(:[]).with('COLUMNS').and_return winsize.last
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
mod.__send__(:remove_const, :Terminal) if defined?(mod::Terminal)
|
|
31
|
+
mod.__send__(:const_set, :Terminal, terminal)
|
|
32
|
+
end
|
|
33
|
+
end
|