fatty 0.99.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/.envrc +2 -0
- data/.simplecov +23 -0
- data/.yardopts +4 -0
- data/CHANGELOG.md +34 -0
- data/CHANGELOG.org +38 -0
- data/LICENSE.txt +21 -0
- data/README.md +31 -0
- data/README.org +166 -0
- data/Rakefile +15 -0
- data/TODO.org +163 -0
- data/examples/markdown/native-markdown.md +370 -0
- data/examples/markdown/ox-gfm-markdown.md +373 -0
- data/examples/markdown/ox-gfm-markdown.org +376 -0
- data/exe/fatty +275 -0
- data/fatty.gemspec +42 -0
- data/lib/fatty/accept_env.rb +32 -0
- data/lib/fatty/action.rb +103 -0
- data/lib/fatty/action_environment.rb +42 -0
- data/lib/fatty/actionable.rb +73 -0
- data/lib/fatty/alert.rb +93 -0
- data/lib/fatty/ansi/renderer.rb +168 -0
- data/lib/fatty/ansi.rb +352 -0
- data/lib/fatty/colors/color.rb +379 -0
- data/lib/fatty/colors/pairs.rb +73 -0
- data/lib/fatty/colors/palette.rb +73 -0
- data/lib/fatty/colors/rgb.txt +788 -0
- data/lib/fatty/colors.rb +5 -0
- data/lib/fatty/config.rb +86 -0
- data/lib/fatty/config_files/config.yml +50 -0
- data/lib/fatty/config_files/help.md +120 -0
- data/lib/fatty/config_files/help.org +124 -0
- data/lib/fatty/config_files/keybindings.yml +49 -0
- data/lib/fatty/config_files/keydefs.yml +23 -0
- data/lib/fatty/config_files/themes/mono.yml +76 -0
- data/lib/fatty/config_files/themes/nordic.yml +77 -0
- data/lib/fatty/config_files/themes/solarized_dark.yml +77 -0
- data/lib/fatty/config_files/themes/terminal.yml +90 -0
- data/lib/fatty/config_files/themes/wordperfect.yml +77 -0
- data/lib/fatty/config_files/themes/wordperfect_light.yml +77 -0
- data/lib/fatty/core_ext/string.rb +21 -0
- data/lib/fatty/core_ext.rb +3 -0
- data/lib/fatty/counter.rb +81 -0
- data/lib/fatty/curses/context.rb +279 -0
- data/lib/fatty/curses/curses_coder.rb +684 -0
- data/lib/fatty/curses/event_source.rb +230 -0
- data/lib/fatty/curses/key_decoder.rb +183 -0
- data/lib/fatty/curses/patch.rb +116 -0
- data/lib/fatty/curses/window_styling.rb +32 -0
- data/lib/fatty/curses.rb +16 -0
- data/lib/fatty/env.rb +100 -0
- data/lib/fatty/help.rb +41 -0
- data/lib/fatty/history/entry.rb +71 -0
- data/lib/fatty/history.rb +289 -0
- data/lib/fatty/input_buffer.rb +998 -0
- data/lib/fatty/input_field.rb +507 -0
- data/lib/fatty/key_event.rb +342 -0
- data/lib/fatty/key_map.rb +392 -0
- data/lib/fatty/keymaps/emacs.rb +189 -0
- data/lib/fatty/log_formats/json.rb +47 -0
- data/lib/fatty/log_formats/text.rb +67 -0
- data/lib/fatty/logger.rb +142 -0
- data/lib/fatty/markdown/ansi_renderer.rb +373 -0
- data/lib/fatty/markdown/render.rb +22 -0
- data/lib/fatty/markdown.rb +4 -0
- data/lib/fatty/menu_env.rb +22 -0
- data/lib/fatty/mouse_event.rb +32 -0
- data/lib/fatty/output_buffer.rb +78 -0
- data/lib/fatty/pager.rb +801 -0
- data/lib/fatty/prompt.rb +40 -0
- data/lib/fatty/renderer/curses.rb +697 -0
- data/lib/fatty/renderer/truecolor.rb +607 -0
- data/lib/fatty/renderer.rb +419 -0
- data/lib/fatty/screen.rb +96 -0
- data/lib/fatty/search.rb +43 -0
- data/lib/fatty/session/alert_session.rb +52 -0
- data/lib/fatty/session/input_session.rb +99 -0
- data/lib/fatty/session/isearch_session.rb +172 -0
- data/lib/fatty/session/keytest_session.rb +236 -0
- data/lib/fatty/session/modal_session.rb +61 -0
- data/lib/fatty/session/output_session.rb +105 -0
- data/lib/fatty/session/popup_session.rb +540 -0
- data/lib/fatty/session/prompt_session.rb +157 -0
- data/lib/fatty/session/search_session.rb +136 -0
- data/lib/fatty/session/shell_session.rb +566 -0
- data/lib/fatty/session.rb +173 -0
- data/lib/fatty/sessions.rb +14 -0
- data/lib/fatty/terminal/popup_owner.rb +26 -0
- data/lib/fatty/terminal/progress.rb +374 -0
- data/lib/fatty/terminal.rb +1067 -0
- data/lib/fatty/themes/loader.rb +136 -0
- data/lib/fatty/themes/manager.rb +71 -0
- data/lib/fatty/themes/registry.rb +64 -0
- data/lib/fatty/themes/resolver.rb +224 -0
- data/lib/fatty/themes/themes.rb +131 -0
- data/lib/fatty/themes.rb +6 -0
- data/lib/fatty/version.rb +5 -0
- data/lib/fatty/view/alert_view.rb +14 -0
- data/lib/fatty/view/cursor_view.rb +18 -0
- data/lib/fatty/view/input_view.rb +9 -0
- data/lib/fatty/view/output_view.rb +9 -0
- data/lib/fatty/view/status_view.rb +14 -0
- data/lib/fatty/view.rb +33 -0
- data/lib/fatty/viewport.rb +90 -0
- data/lib/fatty/views.rb +9 -0
- data/lib/fatty.rb +55 -0
- data/sig/fatty.rbs +4 -0
- metadata +250 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fatty
|
|
4
|
+
# The KeyEvent class is a simple class to store a key with its modifiers.
|
|
5
|
+
# KeyMap will map a KeyEvent to an action (for a given context), and the
|
|
6
|
+
# KeyDecoder class is responsible for turning raw key inputs from curses
|
|
7
|
+
# into a KeyEvent. The CURSES_TO_EVENT constant is an initial map from
|
|
8
|
+
# known curses key codes to KeyEvents. These can be overriden for different
|
|
9
|
+
# terminals in Fatty::Config.keydefs.
|
|
10
|
+
class KeyEvent
|
|
11
|
+
CTRL_CODE_TO_LETTER = (1..26).each_with_object({}) { |n, h|
|
|
12
|
+
# 1->:a, 2->:b, ... 21->:u, 23->:w
|
|
13
|
+
h[n] = (96 + n).chr.to_sym
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
CTRL_PUNCT = {
|
|
17
|
+
27 => :'[', # ESC is also C-[
|
|
18
|
+
28 => :'\\', # C-\
|
|
19
|
+
29 => :']', # C-]
|
|
20
|
+
30 => :'^', # C-^
|
|
21
|
+
31 => :'/', # C-_
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
private_constant :CTRL_CODE_TO_LETTER
|
|
25
|
+
|
|
26
|
+
attr_reader :key, :text, :ctrl, :meta, :shift, :raw
|
|
27
|
+
|
|
28
|
+
def initialize(key:, text: nil, raw: nil, ctrl: false, meta: false, shift: false)
|
|
29
|
+
# If the decoder gives us a control character as an integer (1..26),
|
|
30
|
+
# canonicalize it to ctrl+letter so keymaps and display behave nicely.
|
|
31
|
+
if key.is_a?(Integer)
|
|
32
|
+
if CTRL_CODE_TO_LETTER.key?(key)
|
|
33
|
+
key = CTRL_CODE_TO_LETTER[key]
|
|
34
|
+
ctrl = true
|
|
35
|
+
elsif CTRL_PUNCT.key?(key)
|
|
36
|
+
key = CTRL_PUNCT[key]
|
|
37
|
+
ctrl = true
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
@key = key # Symbol or named key
|
|
41
|
+
@raw = raw # the key (or keys for escape sequences) as returned by Curses
|
|
42
|
+
@ctrl = ctrl
|
|
43
|
+
@meta = meta
|
|
44
|
+
@shift = shift
|
|
45
|
+
# invariant: chorded keys do not self-insert
|
|
46
|
+
@text = ctrl || meta ? nil : text
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.key_to_str(key: "<?>", ctrl: false, meta: false, shift: false)
|
|
50
|
+
mods = []
|
|
51
|
+
mods << "C" if ctrl
|
|
52
|
+
mods << "M" if meta
|
|
53
|
+
mods << "S" if shift
|
|
54
|
+
key_str = key.to_s
|
|
55
|
+
mods.empty? ? key_str : "#{mods.join('-')}-#{key_str}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def to_s
|
|
59
|
+
self.class.key_to_str(key:, ctrl:, meta:, shift:)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def ==(other)
|
|
63
|
+
other.is_a?(KeyEvent) &&
|
|
64
|
+
key == other.key &&
|
|
65
|
+
ctrl == other.ctrl &&
|
|
66
|
+
meta == other.meta &&
|
|
67
|
+
shift == other.shift
|
|
68
|
+
end
|
|
69
|
+
alias_method :eql?, :==
|
|
70
|
+
|
|
71
|
+
def hash
|
|
72
|
+
[key, ctrl, meta, shift].hash
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def printable?
|
|
76
|
+
text && !text.empty? && text != "\n" && text != "\r"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def coded?
|
|
80
|
+
key.is_a?(Symbol)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def uncoded?
|
|
84
|
+
!key.is_a?(Symbol)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def bindings
|
|
88
|
+
Fatty::KeyMap.active.bindings_for(self) || {}
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def unbound?
|
|
92
|
+
coded? && bindings.empty?
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def key_name
|
|
96
|
+
coded? ? to_s : "(none)"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def code
|
|
100
|
+
case raw
|
|
101
|
+
when Integer
|
|
102
|
+
raw
|
|
103
|
+
when Array
|
|
104
|
+
raw.reverse.find { |item| item.is_a?(Integer) }
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def raw_bytes
|
|
109
|
+
bytes_from_raw(raw)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def bytes_from_raw(value)
|
|
113
|
+
case value
|
|
114
|
+
when Array
|
|
115
|
+
value.flat_map { |item| bytes_from_raw(item) }
|
|
116
|
+
when String
|
|
117
|
+
value.bytes
|
|
118
|
+
when Integer
|
|
119
|
+
[value]
|
|
120
|
+
else
|
|
121
|
+
[]
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def modifier_text
|
|
126
|
+
mods = []
|
|
127
|
+
mods << "ctrl" if ctrl?
|
|
128
|
+
mods << "meta" if meta?
|
|
129
|
+
mods << "shift" if shift?
|
|
130
|
+
mods.empty? ? "(none)" : mods.join(", ")
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def bindings_text
|
|
134
|
+
return "(none)" if bindings.empty?
|
|
135
|
+
|
|
136
|
+
text = bindings.map { |context, binding|
|
|
137
|
+
action, _args = binding_action(binding)
|
|
138
|
+
" context: #{context}: #{binding_text(binding)}#{action_target_text(action)}"
|
|
139
|
+
}.join("\n")
|
|
140
|
+
text
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def binding_action(binding)
|
|
144
|
+
case binding
|
|
145
|
+
when Array
|
|
146
|
+
[binding[0], binding[1..] || []]
|
|
147
|
+
else
|
|
148
|
+
[binding, []]
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def binding_text(binding)
|
|
153
|
+
case binding
|
|
154
|
+
when nil
|
|
155
|
+
"(none)"
|
|
156
|
+
when Array
|
|
157
|
+
action = binding[0]
|
|
158
|
+
args = binding[1..] || []
|
|
159
|
+
args.empty? ? action.inspect : "#{action.inspect} #{args.inspect}"
|
|
160
|
+
else
|
|
161
|
+
binding.inspect
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def action_target_text(action)
|
|
166
|
+
return "" unless action
|
|
167
|
+
|
|
168
|
+
defn = Fatty::Actions.lookup(action)
|
|
169
|
+
return " (unregistered action)" unless defn
|
|
170
|
+
|
|
171
|
+
" (#{defn[:on]}: #{action_owner_name(defn[:owner])}##{defn[:method]})"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def action_owner_name(owner)
|
|
175
|
+
name =
|
|
176
|
+
if owner.respond_to?(:name) && owner.name
|
|
177
|
+
owner.name
|
|
178
|
+
else
|
|
179
|
+
owner.to_s
|
|
180
|
+
end
|
|
181
|
+
name.split("::").last
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def color
|
|
185
|
+
if uncoded?
|
|
186
|
+
:oops
|
|
187
|
+
elsif unbound?
|
|
188
|
+
:warn
|
|
189
|
+
else
|
|
190
|
+
:good
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def status_string
|
|
195
|
+
if uncoded?
|
|
196
|
+
"(UNCODED)"
|
|
197
|
+
elsif unbound?
|
|
198
|
+
"(UNBOUND)"
|
|
199
|
+
else
|
|
200
|
+
"(BOUND)"
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def ctrl?
|
|
205
|
+
@ctrl
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def meta?
|
|
209
|
+
@meta
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def shift?
|
|
213
|
+
@shift
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def report
|
|
217
|
+
if uncoded?
|
|
218
|
+
report_for_uncoded
|
|
219
|
+
elsif unbound?
|
|
220
|
+
report_for_unbound
|
|
221
|
+
else
|
|
222
|
+
report_for_bound
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def report_for_uncoded
|
|
227
|
+
rule = colorize("=" * 55) + "\n"
|
|
228
|
+
out = +""
|
|
229
|
+
out << rule
|
|
230
|
+
out << colorize("Key report #{status_string}:\n")
|
|
231
|
+
out << " code: #{code.inspect}\n"
|
|
232
|
+
out << " raw: #{raw.inspect}\n"
|
|
233
|
+
out << " bytes: #{raw_bytes.inspect}\n"
|
|
234
|
+
out << " key: #{key.inspect}\n"
|
|
235
|
+
out << "\n"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def report_for_unbound
|
|
239
|
+
rule = colorize("=" * 55) + "\n"
|
|
240
|
+
out = +""
|
|
241
|
+
out << rule
|
|
242
|
+
out << colorize("Key report #{status_string}:\n")
|
|
243
|
+
# out << " TERM: #{terminal_name}\n"
|
|
244
|
+
out << " code: #{code.inspect}\n"
|
|
245
|
+
out << " raw: #{raw.inspect}\n"
|
|
246
|
+
out << " bytes: #{raw_bytes.inspect}\n"
|
|
247
|
+
out << " key: #{key.inspect}\n"
|
|
248
|
+
out << " name: #{key_name}\n"
|
|
249
|
+
out << " text: #{text.inspect}\n"
|
|
250
|
+
out << " modifiers: #{modifier_text}\n"
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def report_for_bound
|
|
254
|
+
rule = colorize("=" * 55) + "\n"
|
|
255
|
+
out = +""
|
|
256
|
+
out << rule
|
|
257
|
+
out << colorize("Key report #{status_string}:\n")
|
|
258
|
+
# out << " TERM: #{terminal_name}\n"
|
|
259
|
+
out << " code: #{code.inspect}\n"
|
|
260
|
+
out << " raw: #{raw.inspect}\n"
|
|
261
|
+
out << " bytes: #{raw_bytes.inspect}\n"
|
|
262
|
+
out << " key: #{key.inspect}\n"
|
|
263
|
+
out << " name: #{key_name}\n"
|
|
264
|
+
out << " text: #{text.inspect}\n"
|
|
265
|
+
out << " modifiers: #{modifier_text}\n"
|
|
266
|
+
out << " bindings:\n#{bindings_text}\n"
|
|
267
|
+
out << "\n"
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def colorize(text)
|
|
271
|
+
case color
|
|
272
|
+
when :good
|
|
273
|
+
Rainbow(text).green
|
|
274
|
+
when :warn
|
|
275
|
+
Rainbow(text).yellow
|
|
276
|
+
when :oops
|
|
277
|
+
Rainbow(text).red
|
|
278
|
+
else
|
|
279
|
+
text
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def suggested_snippet(terminal_name)
|
|
284
|
+
if uncoded?
|
|
285
|
+
suggested_keydef(terminal_name)
|
|
286
|
+
elsif unbound?
|
|
287
|
+
suggested_keybinding
|
|
288
|
+
else
|
|
289
|
+
''
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def suggested_keydef(terminal_name)
|
|
294
|
+
return "" unless code
|
|
295
|
+
|
|
296
|
+
<<~TEXT
|
|
297
|
+
|
|
298
|
+
No key name is associated with keycode #{code}.
|
|
299
|
+
|
|
300
|
+
Suggested keydefs.yml entry:
|
|
301
|
+
|
|
302
|
+
............>8 snip here 8<....................
|
|
303
|
+
#{terminal_name}:
|
|
304
|
+
map:
|
|
305
|
+
#{code}:
|
|
306
|
+
key: key_name
|
|
307
|
+
shift: <true/false>
|
|
308
|
+
ctrl: <true/false>
|
|
309
|
+
meta: <true/false>
|
|
310
|
+
............>8 snip here 8<....................
|
|
311
|
+
TEXT
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def suggested_keybinding
|
|
315
|
+
out = <<~TEXT
|
|
316
|
+
|
|
317
|
+
No action is bound to #{key_name}.
|
|
318
|
+
|
|
319
|
+
Suggested keybindings.yml entry:
|
|
320
|
+
|
|
321
|
+
............>8 snip here 8<....................
|
|
322
|
+
#{yaml_desc}
|
|
323
|
+
............>8 snip here 8<....................
|
|
324
|
+
TEXT
|
|
325
|
+
|
|
326
|
+
out << <<~TEXT
|
|
327
|
+
|
|
328
|
+
TEXT
|
|
329
|
+
out
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def yaml_desc
|
|
333
|
+
lines = ["# #{key_name}", "- key: #{key}"]
|
|
334
|
+
lines << " shift: true" if shift?
|
|
335
|
+
lines << " ctrl: true" if ctrl?
|
|
336
|
+
lines << " meta: true" if meta?
|
|
337
|
+
lines << " context: <valid_context>"
|
|
338
|
+
lines << " action: <action_name>"
|
|
339
|
+
lines.join("\n")
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|