fatty 0.99.8.7 → 0.99.9.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 +24 -0
- data/README.org +38 -0
- data/lib/fatty/config.rb +1 -1
- data/lib/fatty/curses/event_source.rb +12 -4
- data/lib/fatty/curses/key_decoder.rb +63 -2
- data/lib/fatty/key_event.rb +31 -0
- data/lib/fatty/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cbc5a3073a513f15a71297dcf4220805d46c82e657dd51f81ebfd6ceca7ed3de
|
|
4
|
+
data.tar.gz: f3a3d8bcbe0e7ba2ae026f39df002c21c0b86f377595b9a1f86c227cb0eaf21e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 953bf8c8adccb19c96ab7f332c2fa4b75f945605d1aac599597b480d8b82cb1d8b26e28e4e2607413efe185e27819aeac9fb26af848d224e0b45785f28d81909
|
|
7
|
+
data.tar.gz: 79fa73c37580e337888c86c04aedaf1b5087ba856f6f6f7b06fbfb257de310ff78fdf8e9f9c724bb63db36b4f5efd6803f352153e603822343d1aa5323a68af2
|
data/README.md
CHANGED
|
@@ -1472,6 +1472,30 @@ tags:
|
|
|
1472
1472
|
|
|
1473
1473
|
Most keys are named correctly by curses. Some terminal emulators, multiplexers, or keyboard modes emit numeric keycodes that curses does not name usefully. The `keydefs.yml` file lets you assign names and modifiers to those numeric codes.
|
|
1474
1474
|
|
|
1475
|
+
Fatty also recognizes raw CSI navigation sequences, including modified arrows, Home/End, Insert/Delete, and PageUp/PageDown. An unsupported complete CSI sequence is reported as an undefined key, rather than inserted into the input line. To name one, copy the suggestion from `keytest` into `keydefs.yml`. For example (replace `xterm` with the detected terminal and the example bytes with yours):
|
|
1476
|
+
|
|
1477
|
+
```yaml
|
|
1478
|
+
xterm:
|
|
1479
|
+
sequences:
|
|
1480
|
+
custom_navigation:
|
|
1481
|
+
sequence: "\e[99~"
|
|
1482
|
+
key: custom_navigation
|
|
1483
|
+
meta: true
|
|
1484
|
+
```
|
|
1485
|
+
|
|
1486
|
+
Use double quotes for `sequence` so YAML decodes `\e` as Escape. The label `custom_navigation` identifies the entry for merging configuration layers; use a distinct label for each sequence. Store the bytes in the `sequence` value, not as a YAML key. Numeric definitions may coexist with `sequences` under the same terminal. Sequence definitions override built-in CSI recognition, apply only to that terminal, and leave a standalone Escape unchanged. Paste delimiters cannot be redefined. Invalid sequence entries are ignored with a logged warning. Modifiers default to false; supplied modifiers must be YAML booleans.
|
|
1487
|
+
|
|
1488
|
+
Then assign the named key an action in `keybindings.yml`, for example:
|
|
1489
|
+
|
|
1490
|
+
```yaml
|
|
1491
|
+
- key: custom_navigation
|
|
1492
|
+
meta: true
|
|
1493
|
+
context: input
|
|
1494
|
+
action: move_word_right
|
|
1495
|
+
```
|
|
1496
|
+
|
|
1497
|
+
Use the same key name and modifiers in both files, and restart the application. Alternatively, naming the sequence `right` with `meta: true` uses the existing Emacs-style word-right binding. Already recognized but unbound keys need only an entry in `keybindings.yml`.
|
|
1498
|
+
|
|
1475
1499
|
When Fatty runs inside `tmux`, key definitions are looked up under `tmux`, not under the outer terminal emulator. `tmux` translates the outer terminal's input before Fatty sees it, so keycodes observed inside `tmux` may differ from keycodes observed directly under GNOME Terminal, Kitty, Alacritty, or another terminal. Use `keytest` in the same environment where the application will normally run. The same comment applies to `screen`.
|
|
1476
1500
|
|
|
1477
1501
|
A key definition is terminal-specific. For example:
|
data/README.org
CHANGED
|
@@ -1366,6 +1366,44 @@ or keyboard modes emit numeric keycodes that curses does not name usefully.
|
|
|
1366
1366
|
The =keydefs.yml= file lets you assign names and modifiers to those numeric
|
|
1367
1367
|
codes.
|
|
1368
1368
|
|
|
1369
|
+
Fatty also recognizes raw CSI navigation sequences, including modified arrows,
|
|
1370
|
+
Home/End, Insert/Delete, and PageUp/PageDown. An unsupported complete CSI sequence
|
|
1371
|
+
is reported as an undefined key, rather than inserted into the input line.
|
|
1372
|
+
To name one, copy the suggestion from =keytest= into =keydefs.yml=. For example
|
|
1373
|
+
(replace =xterm= with the detected terminal and the example bytes with yours):
|
|
1374
|
+
|
|
1375
|
+
#+begin_src yaml :tangle no
|
|
1376
|
+
xterm:
|
|
1377
|
+
sequences:
|
|
1378
|
+
custom_navigation:
|
|
1379
|
+
sequence: "\e[99~"
|
|
1380
|
+
key: custom_navigation
|
|
1381
|
+
meta: true
|
|
1382
|
+
#+end_src
|
|
1383
|
+
|
|
1384
|
+
Use double quotes for =sequence= so YAML decodes =\e= as Escape. The label
|
|
1385
|
+
=custom_navigation= identifies the entry for merging configuration layers; use a
|
|
1386
|
+
distinct label for each sequence. Store the bytes in the =sequence= value, not
|
|
1387
|
+
as a YAML key. Numeric definitions may coexist with =sequences= under the same
|
|
1388
|
+
terminal. Sequence definitions override built-in CSI recognition, apply only to
|
|
1389
|
+
that terminal, and leave a standalone Escape unchanged. Paste delimiters cannot
|
|
1390
|
+
be redefined. Invalid sequence entries are ignored with a logged warning.
|
|
1391
|
+
Modifiers default to false; supplied modifiers must be YAML booleans.
|
|
1392
|
+
|
|
1393
|
+
Then assign the named key an action in =keybindings.yml=, for example:
|
|
1394
|
+
|
|
1395
|
+
#+begin_src yaml :tangle no
|
|
1396
|
+
- key: custom_navigation
|
|
1397
|
+
meta: true
|
|
1398
|
+
context: input
|
|
1399
|
+
action: move_word_right
|
|
1400
|
+
#+end_src
|
|
1401
|
+
|
|
1402
|
+
Use the same key name and modifiers in both files, and restart the application.
|
|
1403
|
+
Alternatively, naming the sequence =right= with =meta: true= uses the existing
|
|
1404
|
+
Emacs-style word-right binding. Already recognized but unbound keys need only
|
|
1405
|
+
an entry in =keybindings.yml=.
|
|
1406
|
+
|
|
1369
1407
|
When Fatty runs inside =tmux=, key definitions are looked up under =tmux=, not
|
|
1370
1408
|
under the outer terminal emulator. =tmux= translates the outer terminal's input
|
|
1371
1409
|
before Fatty sees it, so keycodes observed inside =tmux= may differ from
|
data/lib/fatty/config.rb
CHANGED
|
@@ -21,7 +21,7 @@ module Fatty
|
|
|
21
21
|
@config = read_layered("config")
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
# Read in the keydefs.yml config file that maps numeric keycodes returned
|
|
24
|
+
# Read in the keydefs.yml config file that maps numeric keycodes and raw CSI sequences returned
|
|
25
25
|
# by curses but not assigned a key name. This merges in the per-app
|
|
26
26
|
# keydefs.yml as well.
|
|
27
27
|
def self.keydefs
|
|
@@ -143,6 +143,8 @@ module Fatty
|
|
|
143
143
|
seq = "[" + suffix
|
|
144
144
|
if seq == BRACKETED_PASTE_START
|
|
145
145
|
[:paste, read_bracketed_paste]
|
|
146
|
+
elsif suffix.match?(/[\x40-\x7e]\z/)
|
|
147
|
+
[ch, '[', suffix]
|
|
146
148
|
else
|
|
147
149
|
push_pending_raw(*suffix.chars.reverse)
|
|
148
150
|
push_pending_raw(nxt)
|
|
@@ -260,14 +262,20 @@ module Fatty
|
|
|
260
262
|
suffix = +""
|
|
261
263
|
done = false
|
|
262
264
|
|
|
263
|
-
until done
|
|
264
|
-
ch = with_window_timeout(
|
|
265
|
+
until done || suffix.length >= 64
|
|
266
|
+
ch = with_window_timeout(escape_lookahead_ms) { window.getch }
|
|
265
267
|
|
|
266
268
|
if ch == -1 || !ch
|
|
267
269
|
done = true
|
|
268
270
|
else
|
|
269
|
-
|
|
270
|
-
|
|
271
|
+
char = raw_char_to_s(ch)
|
|
272
|
+
if char.bytesize != 1 || !char.ord.between?(0x20, 0x7e)
|
|
273
|
+
push_pending_raw(ch)
|
|
274
|
+
done = true
|
|
275
|
+
else
|
|
276
|
+
suffix << char
|
|
277
|
+
done = char.ord.between?(0x40, 0x7e)
|
|
278
|
+
end
|
|
271
279
|
end
|
|
272
280
|
end
|
|
273
281
|
|
|
@@ -9,6 +9,10 @@ module Fatty
|
|
|
9
9
|
# :ctrl, and :meta, in the KeyEvent so that the KeyMap can assign different
|
|
10
10
|
# actions to the modified keys.
|
|
11
11
|
class KeyDecoder
|
|
12
|
+
CSI_CURSOR_KEYS = { 'A' => :up, 'B' => :down, 'C' => :right, 'D' => :left,
|
|
13
|
+
'H' => :home, 'F' => :end }.freeze
|
|
14
|
+
CSI_TILDE_KEYS = { '1' => :home, '2' => :insert, '3' => :delete, '4' => :end,
|
|
15
|
+
'5' => :page_up, '6' => :page_down, '7' => :home, '8' => :end }.freeze
|
|
12
16
|
SS3_TO_EVENT = {
|
|
13
17
|
"j" => KeyEvent.new(key: :keypad_multiply),
|
|
14
18
|
"k" => KeyEvent.new(key: :keypad_plus),
|
|
@@ -34,6 +38,7 @@ module Fatty
|
|
|
34
38
|
def initialize(env: Env.detect)
|
|
35
39
|
@env = env
|
|
36
40
|
@map = {}
|
|
41
|
+
@sequence_map = {}
|
|
37
42
|
load_builtin_map
|
|
38
43
|
load_user_config
|
|
39
44
|
end
|
|
@@ -46,7 +51,9 @@ module Fatty
|
|
|
46
51
|
result =
|
|
47
52
|
case raw
|
|
48
53
|
when Array
|
|
49
|
-
if raw.length == 3 &&
|
|
54
|
+
if raw.length == 3 && raw[0] == 27 && raw[1] == '['
|
|
55
|
+
decode_csi(raw)
|
|
56
|
+
elsif raw.length == 3 && ss3_sequence?(raw)
|
|
50
57
|
decode_ss3(raw)
|
|
51
58
|
else
|
|
52
59
|
decode_meta(raw)
|
|
@@ -62,6 +69,30 @@ module Fatty
|
|
|
62
69
|
|
|
63
70
|
# simplecov:disable
|
|
64
71
|
|
|
72
|
+
def decode_csi(raw)
|
|
73
|
+
if (spec = @sequence_map["\e[#{raw.last}"])
|
|
74
|
+
return KeyEvent.new(**spec, raw: raw)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
if (match = raw.last.match(/\A(?:1(?:;(\d+))?)?([ABCDHF])\z/))
|
|
78
|
+
key = CSI_CURSOR_KEYS.fetch(match[2])
|
|
79
|
+
modifier = match[1]
|
|
80
|
+
elsif (match = raw.last.match(/\A([1-8])(?:;(\d+))?~\z/))
|
|
81
|
+
key = CSI_TILDE_KEYS.fetch(match[1])
|
|
82
|
+
modifier = match[2]
|
|
83
|
+
end
|
|
84
|
+
modifier = (modifier || '1').to_i
|
|
85
|
+
if key && modifier.between?(1, 16)
|
|
86
|
+
bits = modifier - 1
|
|
87
|
+
KeyEvent.new(key: key, raw: raw,
|
|
88
|
+
shift: (bits & 1).positive?, meta: (bits & 10).positive?,
|
|
89
|
+
ctrl: (bits & 4).positive?)
|
|
90
|
+
else
|
|
91
|
+
# Preserve one nonprinting unknown event for the normal key alert.
|
|
92
|
+
KeyEvent.new(key: "\e[#{raw.last}".inspect, raw: raw)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
65
96
|
def ss3_sequence?(raw)
|
|
66
97
|
raw[0] == 27 && raw[1] == "O"
|
|
67
98
|
end
|
|
@@ -202,7 +233,37 @@ module Fatty
|
|
|
202
233
|
return unless section
|
|
203
234
|
|
|
204
235
|
section.each do |code, spec|
|
|
205
|
-
|
|
236
|
+
if code.to_s == "sequences"
|
|
237
|
+
register_user_sequences(spec)
|
|
238
|
+
else
|
|
239
|
+
register_user_keydef(code, spec)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Labels survive config-key normalization; the sequence stays a value.
|
|
245
|
+
def register_user_sequences(definitions)
|
|
246
|
+
unless definitions.is_a?(Hash)
|
|
247
|
+
Fatty.warn("keydefs sequences must be a mapping of labels to definitions", tag: :keycode)
|
|
248
|
+
return
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
definitions.each do |label, value|
|
|
252
|
+
begin
|
|
253
|
+
spec = normalize_spec(value)
|
|
254
|
+
sequence = spec.delete(:sequence)
|
|
255
|
+
unless sequence.is_a?(String) && sequence.match?(/\A\e\[[0-?]*[ -\/]*[@-~]\z/) &&
|
|
256
|
+
sequence.bytesize <= 66 && !["\e[200~", "\e[201~"].include?(sequence)
|
|
257
|
+
raise ArgumentError, "sequence must be a complete CSI key sequence (not a paste delimiter)"
|
|
258
|
+
end
|
|
259
|
+
unless spec[:key].is_a?(Symbol) && (spec.keys - [:key, :ctrl, :meta, :shift]).empty? &&
|
|
260
|
+
[:ctrl, :meta, :shift].all? { |flag| !spec.key?(flag) || [true, false].include?(spec[flag]) }
|
|
261
|
+
raise ArgumentError, "supply a key name and optional boolean ctrl/meta/shift modifiers"
|
|
262
|
+
end
|
|
263
|
+
@sequence_map[sequence] = spec
|
|
264
|
+
rescue ArgumentError => e
|
|
265
|
+
Fatty.warn("Ignoring keydefs sequence #{label.inspect}: #{e.message}", tag: :keycode)
|
|
266
|
+
end
|
|
206
267
|
end
|
|
207
268
|
end
|
|
208
269
|
|
data/lib/fatty/key_event.rb
CHANGED
|
@@ -89,6 +89,8 @@ module Fatty
|
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def code
|
|
92
|
+
return if csi_sequence
|
|
93
|
+
|
|
92
94
|
case raw
|
|
93
95
|
when Integer
|
|
94
96
|
raw
|
|
@@ -163,6 +165,12 @@ module Fatty
|
|
|
163
165
|
|
|
164
166
|
private
|
|
165
167
|
|
|
168
|
+
def csi_sequence
|
|
169
|
+
return unless raw.is_a?(Array) && raw.length == 3 && raw[0] == 27 && raw[1] == "["
|
|
170
|
+
|
|
171
|
+
"\e[#{raw.last}"
|
|
172
|
+
end
|
|
173
|
+
|
|
166
174
|
def bytes_from_raw(value)
|
|
167
175
|
case value
|
|
168
176
|
when Array
|
|
@@ -293,6 +301,29 @@ module Fatty
|
|
|
293
301
|
end
|
|
294
302
|
|
|
295
303
|
def suggested_keydef(terminal_name)
|
|
304
|
+
if (sequence = csi_sequence)
|
|
305
|
+
return <<~TEXT
|
|
306
|
+
|
|
307
|
+
No key name is associated with sequence #{sequence.inspect}.
|
|
308
|
+
|
|
309
|
+
Suggested keydefs.yml entry (choose a name and modifiers):
|
|
310
|
+
|
|
311
|
+
............>8 snip here 8<....................
|
|
312
|
+
#{terminal_name}:
|
|
313
|
+
sequences:
|
|
314
|
+
custom_key:
|
|
315
|
+
sequence: #{sequence.inspect}
|
|
316
|
+
key: key_name
|
|
317
|
+
shift: false
|
|
318
|
+
ctrl: false
|
|
319
|
+
meta: false
|
|
320
|
+
............>8 snip here 8<....................
|
|
321
|
+
|
|
322
|
+
Use a distinct label for each sequence. Bind the key name in keybindings.yml,
|
|
323
|
+
then restart the application to load your changes.
|
|
324
|
+
TEXT
|
|
325
|
+
end
|
|
326
|
+
|
|
296
327
|
return "" unless code
|
|
297
328
|
|
|
298
329
|
<<~TEXT
|
data/lib/fatty/version.rb
CHANGED