fatty 0.99.0 → 0.99.3
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/CHANGELOG.org +11 -29
- data/README.md +2207 -18
- data/README.org +2066 -104
- data/exe/fatty +236 -93
- data/help/help.md +233 -0
- data/lib/fatty/action.rb +3 -7
- data/lib/fatty/actionable.rb +2 -0
- data/lib/fatty/alert.rb +65 -39
- data/lib/fatty/ansi/renderer.rb +26 -6
- data/lib/fatty/ansi.rb +1 -0
- data/lib/fatty/api/alert.rb +15 -0
- data/lib/fatty/api/choose.rb +145 -0
- data/lib/fatty/api/environment.rb +12 -0
- data/lib/fatty/api/keytest.rb +11 -0
- data/lib/fatty/api/menu.rb +104 -0
- data/lib/fatty/api/output.rb +55 -0
- data/lib/fatty/api/progress.rb +24 -0
- data/lib/fatty/api/prompt.rb +77 -0
- data/lib/fatty/api/status.rb +55 -0
- data/lib/fatty/api.rb +38 -0
- data/lib/fatty/callback_environment.rb +74 -0
- data/lib/fatty/colors/color.rb +366 -232
- data/lib/fatty/colors/pairs.rb +2 -0
- data/lib/fatty/command.rb +78 -0
- data/lib/fatty/config.rb +227 -39
- data/lib/fatty/config_files/config.yml +4 -12
- data/lib/fatty/config_files/keydefs.yml +13 -23
- data/lib/fatty/config_files/themes/capuchin_monk.yml +140 -0
- data/lib/fatty/config_files/themes/catppuccin_latte.yml +140 -0
- data/lib/fatty/config_files/themes/catppuccin_mocha.yml +140 -0
- data/lib/fatty/config_files/themes/cyberpunk.yml +141 -0
- data/lib/fatty/config_files/themes/dracula.yml +140 -0
- data/lib/fatty/config_files/themes/everforest_dark.yml +140 -0
- data/lib/fatty/config_files/themes/gruvbox_dark.yml +140 -0
- data/lib/fatty/config_files/themes/gruvbox_light.yml +140 -0
- data/lib/fatty/config_files/themes/mono.yml +119 -47
- data/lib/fatty/config_files/themes/monokai.yml +140 -0
- data/lib/fatty/config_files/themes/nordic.yml +1 -0
- data/lib/fatty/config_files/themes/onedark.yml +140 -0
- data/lib/fatty/config_files/themes/solarized_dark.yml +1 -0
- data/lib/fatty/config_files/themes/solarized_light.yml +140 -0
- data/lib/fatty/config_files/themes/terminal.yml +67 -0
- data/lib/fatty/config_files/themes/tokyo_night.yml +140 -0
- data/lib/fatty/config_files/themes/wordperfect.yml +80 -9
- data/lib/fatty/core_ext/hash.rb +19 -0
- data/lib/fatty/core_ext/string.rb +19 -12
- data/lib/fatty/core_ext.rb +1 -0
- data/lib/fatty/curses/context.rb +86 -84
- data/lib/fatty/curses/event_source.rb +128 -40
- data/lib/fatty/curses/key_decoder.rb +77 -11
- data/lib/fatty/env.rb +21 -20
- data/lib/fatty/help.rb +1 -29
- data/lib/fatty/history/entry.rb +2 -0
- data/lib/fatty/history.rb +17 -14
- data/lib/fatty/input_buffer.rb +71 -47
- data/lib/fatty/input_field.rb +216 -149
- data/lib/fatty/key_event.rb +67 -66
- data/lib/fatty/key_map.rb +54 -16
- data/lib/fatty/keymaps/emacs.rb +40 -6
- data/lib/fatty/log_formats/json.rb +2 -0
- data/lib/fatty/log_formats/text.rb +2 -0
- data/lib/fatty/logger.rb +7 -2
- data/lib/fatty/markdown/ansi_renderer.rb +75 -6
- data/lib/fatty/markdown/render.rb +2 -3
- data/lib/fatty/mouse_event.rb +142 -2
- data/lib/fatty/output_buffer.rb +2 -0
- data/lib/fatty/pager.rb +158 -47
- data/lib/fatty/progress.rb +417 -0
- data/lib/fatty/renderer/curses.rb +340 -184
- data/lib/fatty/renderer/truecolor.rb +266 -163
- data/lib/fatty/renderer.rb +115 -97
- data/lib/fatty/screen.rb +2 -0
- data/lib/fatty/session/alert_session.rb +39 -21
- data/lib/fatty/session/isearch_session.rb +92 -38
- data/lib/fatty/session/keytest_session.rb +105 -85
- data/lib/fatty/session/modal_session.rb +38 -1
- data/lib/fatty/session/output_session.rb +472 -21
- data/lib/fatty/session/popup_session.rb +251 -134
- data/lib/fatty/session/prompt_session.rb +68 -26
- data/lib/fatty/session/search_session.rb +79 -31
- data/lib/fatty/session/shell_session.rb +383 -368
- data/lib/fatty/session/status_session.rb +167 -0
- data/lib/fatty/session.rb +95 -95
- data/lib/fatty/terminal/popup_owner.rb +14 -11
- data/lib/fatty/terminal.rb +515 -813
- data/lib/fatty/themes/loader.rb +4 -2
- data/lib/fatty/themes/manager.rb +4 -0
- data/lib/fatty/themes/registry.rb +2 -0
- data/lib/fatty/themes/resolver.rb +175 -162
- data/lib/fatty/version.rb +1 -1
- data/lib/fatty/viewport.rb +8 -1
- data/lib/fatty.rb +35 -7
- metadata +42 -40
- data/.envrc +0 -2
- data/.simplecov +0 -23
- data/.yardopts +0 -4
- data/CHANGELOG.md +0 -34
- data/Rakefile +0 -15
- data/TODO.org +0 -163
- data/examples/markdown/native-markdown.md +0 -370
- data/examples/markdown/ox-gfm-markdown.md +0 -373
- data/examples/markdown/ox-gfm-markdown.org +0 -376
- data/fatty.gemspec +0 -42
- data/lib/fatty/accept_env.rb +0 -32
- data/lib/fatty/config_files/help.md +0 -120
- data/lib/fatty/config_files/help.org +0 -124
- data/lib/fatty/config_files/themes/wordperfect_light.yml +0 -77
- data/lib/fatty/menu_env.rb +0 -22
- data/lib/fatty/session/input_session.rb +0 -99
- data/lib/fatty/sessions.rb +0 -14
- data/lib/fatty/terminal/progress.rb +0 -374
- data/lib/fatty/view/alert_view.rb +0 -14
- data/lib/fatty/view/cursor_view.rb +0 -18
- data/lib/fatty/view/input_view.rb +0 -9
- data/lib/fatty/view/output_view.rb +0 -9
- data/lib/fatty/view/status_view.rb +0 -14
- data/lib/fatty/view.rb +0 -33
- data/lib/fatty/views.rb +0 -9
- data/sig/fatty.rbs +0 -4
data/lib/fatty/key_map.rb
CHANGED
|
@@ -12,7 +12,7 @@ module Fatty
|
|
|
12
12
|
class KeyGesture
|
|
13
13
|
attr_reader :key, :ctrl, :meta, :shift
|
|
14
14
|
|
|
15
|
-
def initialize(key:, ctrl
|
|
15
|
+
def initialize(key:, ctrl: false, meta: false, shift: false)
|
|
16
16
|
@key = key
|
|
17
17
|
@ctrl = ctrl
|
|
18
18
|
@meta = meta
|
|
@@ -64,7 +64,7 @@ module Fatty
|
|
|
64
64
|
class MouseGesture
|
|
65
65
|
attr_reader :button, :ctrl, :meta, :shift
|
|
66
66
|
|
|
67
|
-
def initialize(button:, ctrl
|
|
67
|
+
def initialize(button:, ctrl: false, meta: false, shift: false)
|
|
68
68
|
@button = button
|
|
69
69
|
@ctrl = ctrl
|
|
70
70
|
@meta = meta
|
|
@@ -152,15 +152,13 @@ module Fatty
|
|
|
152
152
|
|
|
153
153
|
# Bind a KeyEvent to an action in the given context.
|
|
154
154
|
def bind(context: DEFAULT_CONTEXT, key:, ctrl: false, meta: false, shift: false, action: nil)
|
|
155
|
+
context = normalize_binding_symbol(context, field: :context)
|
|
156
|
+
key = normalize_binding_symbol(key, field: :key)
|
|
157
|
+
action = normalize_action(action)
|
|
158
|
+
|
|
155
159
|
bind_str = "#{KeyEvent.key_to_str(key:, ctrl:, meta:, shift:)} -> #{action} in context: #{context}"
|
|
156
160
|
Fatty.info("KeyMap#bind: (#{bind_str})", tag: :keybinding)
|
|
157
161
|
|
|
158
|
-
raise ArgumentError, "context must be a Symbol" unless context.is_a?(Symbol)
|
|
159
|
-
raise ArgumentError, "key must be a Symbol" unless key.is_a?(Symbol)
|
|
160
|
-
unless action.is_a?(Symbol) || (action.is_a?(Array) && action.first.is_a?(Symbol))
|
|
161
|
-
raise ArgumentError, "action must be a Symbol or [Symbol, *args]"
|
|
162
|
-
end
|
|
163
|
-
|
|
164
162
|
self.class.register_context(context)
|
|
165
163
|
|
|
166
164
|
evt = KeyEvent.new(
|
|
@@ -175,19 +173,17 @@ module Fatty
|
|
|
175
173
|
end
|
|
176
174
|
|
|
177
175
|
def bind_mouse(context: DEFAULT_CONTEXT, button:, ctrl: false, meta: false, shift: false, action: nil)
|
|
176
|
+
context = normalize_binding_symbol(context, field: :context)
|
|
177
|
+
button = normalize_binding_symbol(button, field: :button)
|
|
178
|
+
action = normalize_action(action)
|
|
179
|
+
|
|
178
180
|
bind_str = "#{KeyEvent.key_to_str(key: button, ctrl:, meta:, shift:)} -> #{action} in context: #{context}"
|
|
179
181
|
Fatty.info("KeyMap#bind_mouse(#{bind_str})", tag: :keybinding)
|
|
180
182
|
|
|
181
|
-
raise ArgumentError, "context must be a Symbol" unless context.is_a?(Symbol)
|
|
182
|
-
raise ArgumentError, "button must be a Symbol" unless button.is_a?(Symbol)
|
|
183
|
-
unless action.is_a?(Symbol) || (action.is_a?(Array) && action.first.is_a?(Symbol))
|
|
184
|
-
raise ArgumentError, "action must be a Symbol or [Symbol, *args]"
|
|
185
|
-
end
|
|
186
|
-
|
|
187
183
|
self.class.register_context(context)
|
|
188
184
|
|
|
189
185
|
gest = MouseGesture.new(
|
|
190
|
-
button
|
|
186
|
+
button:,
|
|
191
187
|
ctrl: truthy?(ctrl),
|
|
192
188
|
meta: truthy?(meta),
|
|
193
189
|
shift: truthy?(shift),
|
|
@@ -291,16 +287,58 @@ module Fatty
|
|
|
291
287
|
return result unless event
|
|
292
288
|
|
|
293
289
|
gest = gesture_from_event(event)
|
|
294
|
-
|
|
295
290
|
@bindings.each do |context, map|
|
|
296
291
|
binding = map[gest]
|
|
297
292
|
result[context] = binding if binding
|
|
298
293
|
end
|
|
294
|
+
if event.printable?
|
|
295
|
+
self_insert_contexts.each do |context|
|
|
296
|
+
result[context] ||= [:self_insert, event.text]
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
299
|
result
|
|
300
300
|
end
|
|
301
301
|
|
|
302
302
|
private
|
|
303
303
|
|
|
304
|
+
# simplecov:disable
|
|
305
|
+
|
|
306
|
+
def self_insert_contexts
|
|
307
|
+
[:text, :pager_input].select { |context| self.class.registered_contexts.include?(context) }
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def normalize_binding_symbol(value, field:)
|
|
311
|
+
case value
|
|
312
|
+
when Symbol
|
|
313
|
+
value
|
|
314
|
+
when String
|
|
315
|
+
normalize_symbol_string(value, field:)
|
|
316
|
+
else
|
|
317
|
+
raise ArgumentError, "#{field} must be a Symbol or String"
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def normalize_action(action)
|
|
322
|
+
case action
|
|
323
|
+
when Symbol, String
|
|
324
|
+
normalize_binding_symbol(action, field: :action)
|
|
325
|
+
when Array
|
|
326
|
+
raise ArgumentError, "action must be a Symbol, String, or [Symbol/String, *args]" if action.empty?
|
|
327
|
+
|
|
328
|
+
[normalize_binding_symbol(action.first, field: :action), *action.drop(1)]
|
|
329
|
+
else
|
|
330
|
+
raise ArgumentError, "action must be a Symbol, String, or [Symbol/String, *args]"
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def normalize_symbol_string(value, field:)
|
|
335
|
+
str = value.strip
|
|
336
|
+
str = str.delete_prefix(":")
|
|
337
|
+
raise ArgumentError, "#{field} must not be empty" if str.empty?
|
|
338
|
+
|
|
339
|
+
str.to_sym
|
|
340
|
+
end
|
|
341
|
+
|
|
304
342
|
def gesture_from_event(event)
|
|
305
343
|
case event
|
|
306
344
|
when Fatty::MouseEvent
|
data/lib/fatty/keymaps/emacs.rb
CHANGED
|
@@ -54,8 +54,8 @@ module Fatty
|
|
|
54
54
|
# Counts (prefix arg)
|
|
55
55
|
map.bind(key: :u, ctrl: true, action: :universal_argument)
|
|
56
56
|
map.bind_digits(context: :text, meta: true)
|
|
57
|
-
map.bind_digits(context: :popup)
|
|
58
57
|
map.bind_digits(context: :paging)
|
|
58
|
+
map.bind_digits(context: :paging, meta: true)
|
|
59
59
|
# ShellSession uses contexts [:paging, :terminal] while the pager is
|
|
60
60
|
# active. Bind C-u in :terminal so it works as a prefix during paging
|
|
61
61
|
# (e.g. C-u / to start a regex search).
|
|
@@ -70,6 +70,7 @@ module Fatty
|
|
|
70
70
|
|
|
71
71
|
# Completion on input
|
|
72
72
|
map.bind(context: :input, key: :tab, action: :complete)
|
|
73
|
+
map.bind(context: :input, key: :tab, shift: true, action: :complete_previous)
|
|
73
74
|
map.bind(context: :input, key: :tab, meta: true, action: :completion_popup)
|
|
74
75
|
|
|
75
76
|
# Popup
|
|
@@ -77,12 +78,13 @@ module Fatty
|
|
|
77
78
|
map.bind(context: :popup, key: :g, ctrl: true, action: :popup_cancel)
|
|
78
79
|
map.bind(context: :popup, key: :escape, action: :popup_cancel)
|
|
79
80
|
map.bind(context: :popup, key: :enter, action: :popup_accept)
|
|
80
|
-
map.bind(context: :popup, key: :tab, action: :popup_accept)
|
|
81
81
|
map.bind(context: :popup, key: :return, action: :popup_accept)
|
|
82
82
|
map.bind(context: :popup, key: :up, action: :popup_prev)
|
|
83
83
|
map.bind(context: :popup, key: :down, action: :popup_next)
|
|
84
84
|
map.bind(context: :popup, key: :p, ctrl: true, action: :popup_prev)
|
|
85
85
|
map.bind(context: :popup, key: :n, ctrl: true, action: :popup_next)
|
|
86
|
+
map.bind(context: :popup, key: :p, meta: true, action: :history_prev)
|
|
87
|
+
map.bind(context: :popup, key: :n, meta: true, action: :history_next)
|
|
86
88
|
map.bind(context: :popup, key: :page_up, action: :popup_page_up)
|
|
87
89
|
map.bind(context: :popup, key: :page_down, action: :popup_page_down)
|
|
88
90
|
map.bind(context: :popup, key: :v, meta: true, action: :popup_page_up)
|
|
@@ -92,6 +94,10 @@ module Fatty
|
|
|
92
94
|
map.bind(context: :popup, key: :'<', meta: true, action: :popup_top)
|
|
93
95
|
map.bind(context: :popup, key: :'>', meta: true, action: :popup_bottom)
|
|
94
96
|
map.bind(context: :popup, key: :l, ctrl: true, action: :popup_recenter)
|
|
97
|
+
map.bind(context: :popup, key: :tab, action: :popup_tab)
|
|
98
|
+
map.bind(context: :popup, key: :tab, shift: true, action: :popup_backtab)
|
|
99
|
+
map.bind(context: :popup, key: :right, action: :popup_tab)
|
|
100
|
+
map.bind(context: :popup, key: :left, action: :popup_backtab)
|
|
95
101
|
|
|
96
102
|
# Popups that present multiple items for selection have a special
|
|
97
103
|
# context that steals the SPACE key for toggling selection
|
|
@@ -100,10 +106,16 @@ module Fatty
|
|
|
100
106
|
# Prompt
|
|
101
107
|
map.bind(context: :prompt, key: :c, ctrl: true, action: :prompt_cancel)
|
|
102
108
|
map.bind(context: :prompt, key: :g, ctrl: true, action: :prompt_cancel)
|
|
109
|
+
map.bind(context: :prompt, key: :escape, action: :prompt_cancel)
|
|
103
110
|
map.bind(context: :prompt, key: :enter, action: :prompt_accept)
|
|
104
111
|
map.bind(context: :prompt, key: :return, action: :prompt_accept)
|
|
105
112
|
map.bind(context: :prompt, key: :j, ctrl: true, action: :prompt_accept)
|
|
106
113
|
|
|
114
|
+
# Prompt history
|
|
115
|
+
map.bind(context: :prompt, key: :p, ctrl: true, action: :history_prev)
|
|
116
|
+
map.bind(context: :prompt, key: :n, ctrl: true, action: :history_next)
|
|
117
|
+
map.bind(context: :prompt, key: :up, action: :history_prev)
|
|
118
|
+
map.bind(context: :prompt, key: :down, action: :history_next)
|
|
107
119
|
#
|
|
108
120
|
# Themes
|
|
109
121
|
map.bind(context: :terminal, key: :t, meta: true, ctrl: true, action: :cycle_theme)
|
|
@@ -144,14 +156,30 @@ module Fatty
|
|
|
144
156
|
map.bind(context: :paging, key: :c, ctrl: true, action: :quit_paging)
|
|
145
157
|
map.bind(context: :paging, key: :g, ctrl: true, action: :quit_paging)
|
|
146
158
|
map.bind(context: :paging, key: :q, action: :quit_paging)
|
|
159
|
+
map.bind(context: :paging, key: :escape, action: :quit_paging)
|
|
147
160
|
|
|
148
161
|
# Paging search (opens SearchSession via ShellSession actions)
|
|
149
162
|
map.bind(context: :paging, key: :/, action: :pager_search_forward)
|
|
150
163
|
map.bind(context: :paging, key: :'?', action: :pager_search_backward)
|
|
164
|
+
map.bind(context: :paging, key: :/, ctrl: true, action: :pager_regex_search_forward)
|
|
165
|
+
map.bind(context: :paging, key: :'?', ctrl: true, action: :pager_regex_search_backward)
|
|
151
166
|
# Incremental (string-only) search like Emacs isearch.
|
|
152
167
|
# Regex is intentionally non-incremental (use C-u / or / then toggle).
|
|
153
168
|
map.bind(context: :paging, key: :s, ctrl: true, action: :pager_isearch_forward)
|
|
154
169
|
map.bind(context: :paging, key: :r, ctrl: true, action: :pager_isearch_backward)
|
|
170
|
+
map.bind(context: :paging, key: :s, meta: true, ctrl: true, action: :pager_regex_search_forward)
|
|
171
|
+
map.bind(context: :paging, key: :r, meta: true, ctrl: true, action: :pager_regex_search_backward)
|
|
172
|
+
|
|
173
|
+
# Repeat last search (no minibuffer)
|
|
174
|
+
map.bind(context: :paging, key: :n, action: :pager_search_next)
|
|
175
|
+
map.bind(context: :paging, key: :N, action: :pager_search_prev)
|
|
176
|
+
|
|
177
|
+
# Line numbers
|
|
178
|
+
map.bind(context: :paging, key: :'=', action: :toggle_line_numbers)
|
|
179
|
+
|
|
180
|
+
# Narrowing
|
|
181
|
+
map.bind(context: :paging, key: :'<', action: :narrow_output)
|
|
182
|
+
map.bind(context: :paging, key: :'>', action: :widen_output)
|
|
155
183
|
|
|
156
184
|
# I-search controls (within ISearchSession)
|
|
157
185
|
map.bind(context: :isearch, key: :enter, action: :isearch_accept)
|
|
@@ -159,14 +187,14 @@ module Fatty
|
|
|
159
187
|
map.bind(context: :isearch, key: :j, ctrl: true, action: :isearch_accept)
|
|
160
188
|
map.bind(context: :isearch, key: :s, ctrl: true, action: :isearch_next)
|
|
161
189
|
map.bind(context: :isearch, key: :r, ctrl: true, action: :isearch_prev)
|
|
190
|
+
map.bind(context: :isearch, key: :p, ctrl: true, action: :history_prev)
|
|
191
|
+
map.bind(context: :isearch, key: :n, ctrl: true, action: :history_next)
|
|
192
|
+
map.bind(context: :isearch, key: :up, action: :history_prev)
|
|
193
|
+
map.bind(context: :isearch, key: :down, action: :history_next)
|
|
162
194
|
map.bind(context: :isearch, key: :c, ctrl: true, action: :isearch_cancel)
|
|
163
195
|
map.bind(context: :isearch, key: :g, ctrl: true, action: :isearch_cancel)
|
|
164
196
|
map.bind(context: :isearch, key: :escape, action: :isearch_cancel)
|
|
165
197
|
|
|
166
|
-
# Repeat last search (no minibuffer)
|
|
167
|
-
map.bind(context: :paging, key: :n, action: :pager_search_next)
|
|
168
|
-
map.bind(context: :paging, key: :N, action: :pager_search_prev)
|
|
169
|
-
|
|
170
198
|
# Search controls (within SearchSession)
|
|
171
199
|
map.bind(context: :search, key: :enter, action: :search_accept)
|
|
172
200
|
map.bind(context: :search, key: :return, action: :search_accept)
|
|
@@ -174,6 +202,10 @@ module Fatty
|
|
|
174
202
|
map.bind(context: :search, key: :s, ctrl: true, action: :search_step_forward)
|
|
175
203
|
map.bind(context: :search, key: :r, ctrl: true, action: :search_step_backward)
|
|
176
204
|
map.bind(context: :search, key: :r, meta: true, action: :search_toggle_regex)
|
|
205
|
+
map.bind(context: :search, key: :p, ctrl: true, action: :history_prev)
|
|
206
|
+
map.bind(context: :search, key: :n, ctrl: true, action: :history_next)
|
|
207
|
+
map.bind(context: :search, key: :up, action: :history_prev)
|
|
208
|
+
map.bind(context: :search, key: :down, action: :history_next)
|
|
177
209
|
map.bind(context: :search, key: :c, ctrl: true, action: :search_cancel)
|
|
178
210
|
map.bind(context: :search, key: :g, ctrl: true, action: :search_cancel)
|
|
179
211
|
map.bind(context: :search, key: :escape, action: :search_cancel)
|
|
@@ -181,6 +213,8 @@ module Fatty
|
|
|
181
213
|
# Mouse scrolling in paging
|
|
182
214
|
map.bind_mouse(context: :paging, button: :scroll_up, action: :scroll_up)
|
|
183
215
|
map.bind_mouse(context: :paging, button: :scroll_down, action: :scroll_down)
|
|
216
|
+
map.bind_mouse(context: :paging, button: :scroll_up, ctrl: true, action: :page_up)
|
|
217
|
+
map.bind_mouse(context: :paging, button: :scroll_down, ctrl: true, action: :page_down)
|
|
184
218
|
|
|
185
219
|
# Load the user's keybindings
|
|
186
220
|
map.load_user_config.activate!
|
data/lib/fatty/logger.rb
CHANGED
|
@@ -66,17 +66,22 @@ module Fatty
|
|
|
66
66
|
# - command:: Terminal.apply_command / :send dispatches
|
|
67
67
|
# - session:: session update calls, mode/context stack changes
|
|
68
68
|
# - render:: viewports, layout sizes, redraw triggers
|
|
69
|
-
# - perf:: timings, frame time, slow paths
|
|
70
69
|
# - all:: All of the above
|
|
70
|
+
# - perf:: timings, frame time, slow paths
|
|
71
71
|
def self.log(event = nil, level: :debug, tag: nil, **data)
|
|
72
72
|
return unless logger
|
|
73
73
|
|
|
74
74
|
tags = active_tags
|
|
75
|
+
|
|
76
|
+
# The :perf tag has to be requested explicitly: it is not implied by
|
|
77
|
+
# :all.
|
|
78
|
+
return if tag == :perf && !tags.include?(:perf)
|
|
79
|
+
|
|
75
80
|
if tag && !tags.include?(:all)
|
|
76
81
|
return unless tags.include?(tag.to_sym)
|
|
77
82
|
end
|
|
78
83
|
|
|
79
|
-
payload = {
|
|
84
|
+
payload = { tag: tag, event: event }
|
|
80
85
|
payload.merge!(data.reject { |k, _| k == :event || k == :tag })
|
|
81
86
|
|
|
82
87
|
logger.add(severity(level), payload)
|
|
@@ -7,15 +7,17 @@ module Fatty
|
|
|
7
7
|
class AnsiRenderer < Redcarpet::Render::Base
|
|
8
8
|
HARD_BREAK = "\uE000"
|
|
9
9
|
|
|
10
|
-
def initialize(width: 80, palette: nil)
|
|
10
|
+
def initialize(width: 80, palette: nil, theme: nil, truecolor: false)
|
|
11
11
|
super()
|
|
12
12
|
@width = width.to_i
|
|
13
13
|
@palette = palette || {}
|
|
14
|
+
@theme = theme || {}
|
|
15
|
+
@truecolor = truecolor
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
def block_code(code, language)
|
|
17
19
|
lexer = rouge_lexer(language.to_s, code.to_s)
|
|
18
|
-
formatter =
|
|
20
|
+
formatter = rouge_formatter
|
|
19
21
|
gutter = md("│ ", :markdown_code_gutter)
|
|
20
22
|
|
|
21
23
|
highlighted = formatter.format(lexer.lex(code.to_s))
|
|
@@ -271,6 +273,8 @@ module Fatty
|
|
|
271
273
|
|
|
272
274
|
private
|
|
273
275
|
|
|
276
|
+
# simplecov:disable
|
|
277
|
+
|
|
274
278
|
def wrap(text, first_prefix: "", rest_prefix: first_prefix)
|
|
275
279
|
hard_lines = text.to_s.split(HARD_BREAK, -1)
|
|
276
280
|
|
|
@@ -336,20 +340,30 @@ module Fatty
|
|
|
336
340
|
codes << 4 if attrs.include?(:underline)
|
|
337
341
|
codes << 7 if attrs.include?(:reverse)
|
|
338
342
|
|
|
339
|
-
|
|
343
|
+
fg_rgb = spec[:fg_rgb] || spec["fg_rgb"]
|
|
344
|
+
bg_rgb = spec[:bg_rgb] || spec["bg_rgb"]
|
|
345
|
+
fg = spec[:fg] || spec["fg"]
|
|
346
|
+
bg = spec[:bg] || spec["bg"]
|
|
347
|
+
|
|
348
|
+
if fg_rgb
|
|
340
349
|
codes.push(38, 2, *fg_rgb)
|
|
341
|
-
elsif (fg
|
|
350
|
+
elsif !default_color?(fg)
|
|
342
351
|
codes.concat(color_codes(fg, foreground: true))
|
|
343
352
|
end
|
|
344
353
|
|
|
345
|
-
if
|
|
354
|
+
if bg_rgb
|
|
346
355
|
codes.push(48, 2, *bg_rgb)
|
|
347
|
-
elsif (bg
|
|
356
|
+
elsif !default_color?(bg)
|
|
348
357
|
codes.concat(color_codes(bg, foreground: false))
|
|
349
358
|
end
|
|
359
|
+
|
|
350
360
|
codes
|
|
351
361
|
end
|
|
352
362
|
|
|
363
|
+
def default_color?(color)
|
|
364
|
+
color.nil? || color.to_i == Fatty::Color::DEFAULT_INDEX
|
|
365
|
+
end
|
|
366
|
+
|
|
353
367
|
def color_codes(color, foreground:)
|
|
354
368
|
color = color.to_i
|
|
355
369
|
|
|
@@ -369,5 +383,60 @@ module Fatty
|
|
|
369
383
|
[48, 5, color]
|
|
370
384
|
end
|
|
371
385
|
end
|
|
386
|
+
|
|
387
|
+
def rouge_theme
|
|
388
|
+
case markdown_code_theme
|
|
389
|
+
when :solarized_light
|
|
390
|
+
Rouge::Themes::Base16::Solarized.mode(:light).new
|
|
391
|
+
when :solarized_dark
|
|
392
|
+
Rouge::Themes::Base16::Solarized.mode(:dark).new
|
|
393
|
+
when :base16
|
|
394
|
+
Rouge::Themes::Base16.new
|
|
395
|
+
when :base16_monokai
|
|
396
|
+
Rouge::Themes::Base16::Monokai.mode(:dark).new
|
|
397
|
+
when :monokai
|
|
398
|
+
Rouge::Themes::Monokai.new
|
|
399
|
+
when :monokai_sublime
|
|
400
|
+
Rouge::Themes::MonokaiSublime.new
|
|
401
|
+
when :gruvbox_light
|
|
402
|
+
Rouge::Themes::Gruvbox.mode(:light).new
|
|
403
|
+
when :gruvbox_dark
|
|
404
|
+
Rouge::Themes::Gruvbox.mode(:dark).new
|
|
405
|
+
when :github
|
|
406
|
+
Rouge::Themes::Github.mode(:light).new
|
|
407
|
+
when :github_dark
|
|
408
|
+
Rouge::Themes::Github.mode(:dark).new
|
|
409
|
+
when :molokai
|
|
410
|
+
Rouge::Themes::Molokai.new
|
|
411
|
+
when :colorful
|
|
412
|
+
Rouge::Themes::Colorful.new
|
|
413
|
+
when :bw
|
|
414
|
+
Rouge::Themes::BlackWhiteTheme.new
|
|
415
|
+
when :pastie
|
|
416
|
+
Rouge::Themes::Pastie.new
|
|
417
|
+
when :tulip
|
|
418
|
+
Rouge::Themes::Tulip.new
|
|
419
|
+
when :igor_pro
|
|
420
|
+
Rouge::Themes::IgorPro.new
|
|
421
|
+
when :magritte
|
|
422
|
+
Rouge::Themes::Magritte.new
|
|
423
|
+
else
|
|
424
|
+
Rouge::Themes::ThankfulEyes.new
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def markdown_code_theme
|
|
429
|
+
value = @theme[:markdown_code_theme] if @theme.respond_to?(:[])
|
|
430
|
+
value.to_s.tr("-", "_").to_sym
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def rouge_formatter
|
|
434
|
+
formatter_class = truecolor? ? Rouge::Formatters::TerminalTruecolor : Rouge::Formatters::Terminal256
|
|
435
|
+
formatter_class.new(rouge_theme)
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def truecolor?
|
|
439
|
+
@truecolor
|
|
440
|
+
end
|
|
372
441
|
end
|
|
373
442
|
end
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Fatty
|
|
4
4
|
module Markdown
|
|
5
|
-
def self.render(text, width: 80, palette: nil)
|
|
5
|
+
def self.render(text, width: 80, palette: nil, theme: nil, truecolor: false) #
|
|
6
6
|
markdown = Redcarpet::Markdown.new(
|
|
7
|
-
Fatty::AnsiRenderer.new(width: width, palette: palette),
|
|
8
|
-
no_intra_emphasis: true,
|
|
7
|
+
Fatty::AnsiRenderer.new(width: width, palette: palette, theme: theme, truecolor: truecolor),
|
|
9
8
|
tables: true,
|
|
10
9
|
fenced_code_blocks: true,
|
|
11
10
|
autolink: true,
|
data/lib/fatty/mouse_event.rb
CHANGED
|
@@ -6,7 +6,7 @@ module Fatty
|
|
|
6
6
|
class MouseEvent
|
|
7
7
|
attr_reader :button, :x, :y, :ctrl, :meta, :shift, :raw
|
|
8
8
|
|
|
9
|
-
def initialize(button:, x:
|
|
9
|
+
def initialize(button:, x: 0, y: 0, raw: nil, ctrl: false, meta: false, shift: false)
|
|
10
10
|
@button = button
|
|
11
11
|
@x = x
|
|
12
12
|
@y = y
|
|
@@ -17,8 +17,21 @@ module Fatty
|
|
|
17
17
|
Fatty.debug("#{self.class}#new(#{self})", tag: :event)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
def key
|
|
21
|
+
:mouse
|
|
22
|
+
end
|
|
23
|
+
|
|
20
24
|
def to_s
|
|
21
|
-
|
|
25
|
+
self.class.button_to_str(button:, ctrl:, meta:, shift:)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.button_to_str(button:, ctrl: false, meta: false, shift: false)
|
|
29
|
+
mods = []
|
|
30
|
+
mods << "C" if ctrl
|
|
31
|
+
mods << "M" if meta
|
|
32
|
+
mods << "S" if shift
|
|
33
|
+
button_str = button.to_s
|
|
34
|
+
mods.empty? ? button_str : "#{mods.join('-')}-#{button_str}"
|
|
22
35
|
end
|
|
23
36
|
|
|
24
37
|
def printable?
|
|
@@ -28,5 +41,132 @@ module Fatty
|
|
|
28
41
|
def mouse?
|
|
29
42
|
true
|
|
30
43
|
end
|
|
44
|
+
|
|
45
|
+
def ctrl?
|
|
46
|
+
@ctrl
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def meta?
|
|
50
|
+
@meta
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def shift?
|
|
54
|
+
@shift
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def bindings
|
|
58
|
+
Fatty::KeyMap.active.bindings_for(self) || {}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def report
|
|
62
|
+
rule = "=" * 55 + "\n"
|
|
63
|
+
out = +""
|
|
64
|
+
out << rule
|
|
65
|
+
out << "Mouse report #{status_string}:\n"
|
|
66
|
+
out << " raw: #{raw.inspect}\n"
|
|
67
|
+
out << " button: #{button.inspect}\n"
|
|
68
|
+
out << " name: #{to_s}\n"
|
|
69
|
+
out << " x: #{x.inspect}\n"
|
|
70
|
+
out << " y: #{y.inspect}\n"
|
|
71
|
+
out << " modifiers: #{modifier_text}\n"
|
|
72
|
+
out << " bindings:\n#{bindings_text}\n"
|
|
73
|
+
out << "\n"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def suggested_snippet(_terminal_name)
|
|
77
|
+
bindings.empty? ? suggested_mouse_binding : ""
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def status_string
|
|
83
|
+
bindings.empty? ? "(UNBOUND)" : "(BOUND)"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def modifier_text
|
|
87
|
+
mods = []
|
|
88
|
+
mods << "ctrl" if ctrl?
|
|
89
|
+
mods << "meta" if meta?
|
|
90
|
+
mods << "shift" if shift?
|
|
91
|
+
mods.empty? ? "(none)" : mods.join(", ")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def bindings_text
|
|
95
|
+
return "(none)" if bindings.empty?
|
|
96
|
+
|
|
97
|
+
bindings.map { |context, binding|
|
|
98
|
+
action, _args = binding_action(binding)
|
|
99
|
+
" context: #{context}: #{binding_text(binding)}#{action_target_text(action)}"
|
|
100
|
+
}.join("\n")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def binding_action(binding)
|
|
104
|
+
case binding
|
|
105
|
+
when Array
|
|
106
|
+
[binding[0], binding[1..] || []]
|
|
107
|
+
else
|
|
108
|
+
[binding, []]
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def binding_text(binding)
|
|
113
|
+
case binding
|
|
114
|
+
when nil
|
|
115
|
+
"(none)"
|
|
116
|
+
when Array
|
|
117
|
+
action = binding[0]
|
|
118
|
+
args = binding[1..] || []
|
|
119
|
+
args.empty? ? action.inspect : "#{action.inspect} #{args.inspect}"
|
|
120
|
+
else
|
|
121
|
+
binding.inspect
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def action_target_text(action)
|
|
126
|
+
return "" unless action
|
|
127
|
+
|
|
128
|
+
defn = Fatty::Actions.lookup(action)
|
|
129
|
+
return " (unregistered action)" unless defn
|
|
130
|
+
|
|
131
|
+
" (#{defn[:on]}: #{action_owner_name(defn[:owner])}##{defn[:method]})"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def action_owner_name(owner)
|
|
135
|
+
name =
|
|
136
|
+
if owner.respond_to?(:name) && owner.name
|
|
137
|
+
owner.name
|
|
138
|
+
else
|
|
139
|
+
owner.to_s
|
|
140
|
+
end
|
|
141
|
+
name.split("::").last
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def suggested_mouse_binding
|
|
145
|
+
out = <<~TEXT
|
|
146
|
+
|
|
147
|
+
No action is bound to #{to_s}.
|
|
148
|
+
|
|
149
|
+
Suggested keybindings.yml entry:
|
|
150
|
+
|
|
151
|
+
............>8 snip here 8<....................
|
|
152
|
+
#{yaml_desc}
|
|
153
|
+
............>8 snip here 8<....................
|
|
154
|
+
TEXT
|
|
155
|
+
|
|
156
|
+
out << <<~TEXT
|
|
157
|
+
|
|
158
|
+
TEXT
|
|
159
|
+
out
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def yaml_desc
|
|
163
|
+
lines = ["# #{to_s}", "- button: #{button}"]
|
|
164
|
+
lines << " shift: true" if shift?
|
|
165
|
+
lines << " ctrl: true" if ctrl?
|
|
166
|
+
lines << " meta: true" if meta?
|
|
167
|
+
lines << " context: <valid_context>"
|
|
168
|
+
lines << " action: <action_name>"
|
|
169
|
+
lines.join("\n")
|
|
170
|
+
end
|
|
31
171
|
end
|
|
32
172
|
end
|