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
|