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.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +2 -0
  3. data/.simplecov +23 -0
  4. data/.yardopts +4 -0
  5. data/CHANGELOG.md +34 -0
  6. data/CHANGELOG.org +38 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +31 -0
  9. data/README.org +166 -0
  10. data/Rakefile +15 -0
  11. data/TODO.org +163 -0
  12. data/examples/markdown/native-markdown.md +370 -0
  13. data/examples/markdown/ox-gfm-markdown.md +373 -0
  14. data/examples/markdown/ox-gfm-markdown.org +376 -0
  15. data/exe/fatty +275 -0
  16. data/fatty.gemspec +42 -0
  17. data/lib/fatty/accept_env.rb +32 -0
  18. data/lib/fatty/action.rb +103 -0
  19. data/lib/fatty/action_environment.rb +42 -0
  20. data/lib/fatty/actionable.rb +73 -0
  21. data/lib/fatty/alert.rb +93 -0
  22. data/lib/fatty/ansi/renderer.rb +168 -0
  23. data/lib/fatty/ansi.rb +352 -0
  24. data/lib/fatty/colors/color.rb +379 -0
  25. data/lib/fatty/colors/pairs.rb +73 -0
  26. data/lib/fatty/colors/palette.rb +73 -0
  27. data/lib/fatty/colors/rgb.txt +788 -0
  28. data/lib/fatty/colors.rb +5 -0
  29. data/lib/fatty/config.rb +86 -0
  30. data/lib/fatty/config_files/config.yml +50 -0
  31. data/lib/fatty/config_files/help.md +120 -0
  32. data/lib/fatty/config_files/help.org +124 -0
  33. data/lib/fatty/config_files/keybindings.yml +49 -0
  34. data/lib/fatty/config_files/keydefs.yml +23 -0
  35. data/lib/fatty/config_files/themes/mono.yml +76 -0
  36. data/lib/fatty/config_files/themes/nordic.yml +77 -0
  37. data/lib/fatty/config_files/themes/solarized_dark.yml +77 -0
  38. data/lib/fatty/config_files/themes/terminal.yml +90 -0
  39. data/lib/fatty/config_files/themes/wordperfect.yml +77 -0
  40. data/lib/fatty/config_files/themes/wordperfect_light.yml +77 -0
  41. data/lib/fatty/core_ext/string.rb +21 -0
  42. data/lib/fatty/core_ext.rb +3 -0
  43. data/lib/fatty/counter.rb +81 -0
  44. data/lib/fatty/curses/context.rb +279 -0
  45. data/lib/fatty/curses/curses_coder.rb +684 -0
  46. data/lib/fatty/curses/event_source.rb +230 -0
  47. data/lib/fatty/curses/key_decoder.rb +183 -0
  48. data/lib/fatty/curses/patch.rb +116 -0
  49. data/lib/fatty/curses/window_styling.rb +32 -0
  50. data/lib/fatty/curses.rb +16 -0
  51. data/lib/fatty/env.rb +100 -0
  52. data/lib/fatty/help.rb +41 -0
  53. data/lib/fatty/history/entry.rb +71 -0
  54. data/lib/fatty/history.rb +289 -0
  55. data/lib/fatty/input_buffer.rb +998 -0
  56. data/lib/fatty/input_field.rb +507 -0
  57. data/lib/fatty/key_event.rb +342 -0
  58. data/lib/fatty/key_map.rb +392 -0
  59. data/lib/fatty/keymaps/emacs.rb +189 -0
  60. data/lib/fatty/log_formats/json.rb +47 -0
  61. data/lib/fatty/log_formats/text.rb +67 -0
  62. data/lib/fatty/logger.rb +142 -0
  63. data/lib/fatty/markdown/ansi_renderer.rb +373 -0
  64. data/lib/fatty/markdown/render.rb +22 -0
  65. data/lib/fatty/markdown.rb +4 -0
  66. data/lib/fatty/menu_env.rb +22 -0
  67. data/lib/fatty/mouse_event.rb +32 -0
  68. data/lib/fatty/output_buffer.rb +78 -0
  69. data/lib/fatty/pager.rb +801 -0
  70. data/lib/fatty/prompt.rb +40 -0
  71. data/lib/fatty/renderer/curses.rb +697 -0
  72. data/lib/fatty/renderer/truecolor.rb +607 -0
  73. data/lib/fatty/renderer.rb +419 -0
  74. data/lib/fatty/screen.rb +96 -0
  75. data/lib/fatty/search.rb +43 -0
  76. data/lib/fatty/session/alert_session.rb +52 -0
  77. data/lib/fatty/session/input_session.rb +99 -0
  78. data/lib/fatty/session/isearch_session.rb +172 -0
  79. data/lib/fatty/session/keytest_session.rb +236 -0
  80. data/lib/fatty/session/modal_session.rb +61 -0
  81. data/lib/fatty/session/output_session.rb +105 -0
  82. data/lib/fatty/session/popup_session.rb +540 -0
  83. data/lib/fatty/session/prompt_session.rb +157 -0
  84. data/lib/fatty/session/search_session.rb +136 -0
  85. data/lib/fatty/session/shell_session.rb +566 -0
  86. data/lib/fatty/session.rb +173 -0
  87. data/lib/fatty/sessions.rb +14 -0
  88. data/lib/fatty/terminal/popup_owner.rb +26 -0
  89. data/lib/fatty/terminal/progress.rb +374 -0
  90. data/lib/fatty/terminal.rb +1067 -0
  91. data/lib/fatty/themes/loader.rb +136 -0
  92. data/lib/fatty/themes/manager.rb +71 -0
  93. data/lib/fatty/themes/registry.rb +64 -0
  94. data/lib/fatty/themes/resolver.rb +224 -0
  95. data/lib/fatty/themes/themes.rb +131 -0
  96. data/lib/fatty/themes.rb +6 -0
  97. data/lib/fatty/version.rb +5 -0
  98. data/lib/fatty/view/alert_view.rb +14 -0
  99. data/lib/fatty/view/cursor_view.rb +18 -0
  100. data/lib/fatty/view/input_view.rb +9 -0
  101. data/lib/fatty/view/output_view.rb +9 -0
  102. data/lib/fatty/view/status_view.rb +14 -0
  103. data/lib/fatty/view.rb +33 -0
  104. data/lib/fatty/viewport.rb +90 -0
  105. data/lib/fatty/views.rb +9 -0
  106. data/lib/fatty.rb +55 -0
  107. data/sig/fatty.rbs +4 -0
  108. 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