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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 519b734dc4d7f0897a42e766e58732f13458b7f8f1bfac59f0582b1862006805
4
- data.tar.gz: 003edc0102fda74f0b9d530fb2682f9b810a5bd9756eec6464f0e0443a3efb9f
3
+ metadata.gz: cbc5a3073a513f15a71297dcf4220805d46c82e657dd51f81ebfd6ceca7ed3de
4
+ data.tar.gz: f3a3d8bcbe0e7ba2ae026f39df002c21c0b86f377595b9a1f86c227cb0eaf21e
5
5
  SHA512:
6
- metadata.gz: a9691e1dafdd5c714b9e490d91be490bb5d05c591d74ffb8b7421f9092d2b083c7d035d9afeebb933ed096783c9214638059d738caff2441541ead719aa03d8d
7
- data.tar.gz: a77da76a84bdbf1a8806fa9f921fbf3c13aa509b7ccfa0a476a4bfc92111d29687b382a10e4d311a4ab59693de1064a8874da804a9ee19077d7bf66e8a7a56f8
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(0) { window.getch }
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
- suffix << raw_char_to_s(ch)
270
- done = true if suffix.end_with?("~")
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 && ss3_sequence?(raw)
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
- register_user_keydef(code.to_i, spec)
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
 
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fatty
4
- VERSION = "0.99.8.7"
4
+ VERSION = "0.99.9.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fatty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.99.8.7
4
+ version: 0.99.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel E. Doherty