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.
@@ -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,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../terminal'
4
+ require_relative 'ansi/named_colors'
5
+ require_relative 'text'
6
+ require_relative 'text/char_width'
7
+ require_relative 'detect'
8
+ require_relative 'version'
@@ -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