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
|
@@ -4,11 +4,11 @@ module Fatty
|
|
|
4
4
|
class PopUpSession < ModalSession
|
|
5
5
|
action_on :session
|
|
6
6
|
|
|
7
|
-
attr_reader :field, :filtered, :displayed, :
|
|
7
|
+
attr_reader :prompt, :items, :field, :filtered, :displayed, :title, :message
|
|
8
8
|
|
|
9
9
|
MAX_WIDTH = 120
|
|
10
10
|
DEFAULT_HEIGHT = 12
|
|
11
|
-
MIN_LIST_H =
|
|
11
|
+
MIN_LIST_H = 1
|
|
12
12
|
MAX_LIST_H = 20
|
|
13
13
|
MARGIN = 2
|
|
14
14
|
|
|
@@ -19,21 +19,27 @@ module Fatty
|
|
|
19
19
|
# - source: Proc that returns the candidate list. May accept (query) or be arity 0.
|
|
20
20
|
# - matcher: Proc (item, query) -> truthy. Defaults to substring match.
|
|
21
21
|
# - order: :as_given (default) or :reverse (presentation order).
|
|
22
|
-
# -
|
|
22
|
+
# - current: :preserve (default), :top, :bottom (how the current behaves after refresh).
|
|
23
23
|
def initialize(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
24
|
+
source:,
|
|
25
|
+
title: nil,
|
|
26
|
+
message: nil,
|
|
27
|
+
prompt: "Narrow: ",
|
|
28
|
+
keymap: Keymaps.emacs,
|
|
29
|
+
matcher: nil,
|
|
30
|
+
order: :as_given,
|
|
31
|
+
kind: nil,
|
|
32
|
+
current: :preserve,
|
|
33
|
+
initial_query: nil,
|
|
34
|
+
show_counts: true,
|
|
35
|
+
show_filter: true,
|
|
36
|
+
selection_mode: :single,
|
|
37
|
+
history: nil,
|
|
38
|
+
history_kind: :popup_filter,
|
|
39
|
+
history_ctx: nil,
|
|
40
|
+
save_history: true,
|
|
41
|
+
validate_unique_labels: false
|
|
42
|
+
)
|
|
37
43
|
super(keymap: keymap)
|
|
38
44
|
@source = source
|
|
39
45
|
@title = title&.to_s
|
|
@@ -42,64 +48,155 @@ module Fatty
|
|
|
42
48
|
@matcher = matcher || method(:default_matcher)
|
|
43
49
|
@order = order.to_sym
|
|
44
50
|
@kind = kind&.to_sym
|
|
45
|
-
@
|
|
51
|
+
@current_policy = current.to_sym
|
|
52
|
+
@current = nil
|
|
46
53
|
@selection_mode = selection_mode.to_sym
|
|
54
|
+
@show_counts = !!show_counts
|
|
55
|
+
@history = history
|
|
56
|
+
@save_history = !!save_history
|
|
57
|
+
@field = InputField.new(
|
|
58
|
+
prompt: @prompt,
|
|
59
|
+
history: @history,
|
|
60
|
+
history_kind: history_kind,
|
|
61
|
+
history_ctx: history_ctx,
|
|
62
|
+
)
|
|
63
|
+
@show_filter = !!show_filter
|
|
47
64
|
@validate_unique_labels = !!validate_unique_labels
|
|
48
|
-
|
|
49
|
-
@field = InputField.new(prompt: @prompt)
|
|
50
65
|
text = initial_query.to_s
|
|
51
66
|
@field.buffer.replace(text) unless text.empty?
|
|
52
67
|
|
|
53
68
|
@items = []
|
|
54
69
|
@filtered = []
|
|
55
70
|
@displayed = []
|
|
56
|
-
@selected = 0
|
|
57
71
|
@selected_labels = {}
|
|
58
72
|
|
|
59
73
|
@last_query = nil
|
|
60
74
|
@scroll_start = 0
|
|
75
|
+
refresh_items
|
|
61
76
|
end
|
|
62
77
|
|
|
63
78
|
#########################################################################################
|
|
64
|
-
#
|
|
79
|
+
# Session Protocol
|
|
65
80
|
#########################################################################################
|
|
66
81
|
|
|
67
82
|
def init(terminal:)
|
|
83
|
+
commands = super
|
|
68
84
|
refresh_items
|
|
69
85
|
rebuild_windows!
|
|
70
|
-
notify_owner(:popup_changed)
|
|
86
|
+
commands.concat(notify_owner(:popup_changed))
|
|
71
87
|
end
|
|
72
88
|
|
|
73
|
-
def
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
89
|
+
def update(command)
|
|
90
|
+
commands =
|
|
91
|
+
case command.action
|
|
92
|
+
when :key
|
|
93
|
+
ev = command.payload.fetch(:event)
|
|
94
|
+
action, args = resolve_action(ev)
|
|
95
|
+
|
|
96
|
+
if action
|
|
97
|
+
normalize_action_result(apply_action(action, args, event: ev))
|
|
98
|
+
else
|
|
99
|
+
[]
|
|
100
|
+
end
|
|
101
|
+
when :terminal_paste
|
|
102
|
+
text = command.payload.fetch(:text, "").to_s
|
|
103
|
+
env = action_env(event: nil)
|
|
104
|
+
@field.act_on(:paste, text, env: env)
|
|
105
|
+
refresh_items
|
|
106
|
+
ensure_scroll_visible
|
|
107
|
+
[]
|
|
108
|
+
else
|
|
109
|
+
[]
|
|
110
|
+
end
|
|
111
|
+
Array(commands)
|
|
77
112
|
end
|
|
78
113
|
|
|
79
|
-
def view
|
|
114
|
+
def view
|
|
80
115
|
Fatty.debug("PopupSession#view: object_id=#{object_id} win_nil=#{@win.nil?}", tag: :session)
|
|
81
116
|
return unless @win
|
|
82
117
|
|
|
83
118
|
renderer.render_popup(session: self)
|
|
84
119
|
end
|
|
85
120
|
|
|
86
|
-
|
|
87
|
-
#
|
|
88
|
-
|
|
89
|
-
max_w = max_width(cols: cols, margin: MARGIN, min_width: 10)
|
|
90
|
-
max_h = max_height(rows: rows, margin: MARGIN, min_height: 5)
|
|
121
|
+
#########################################################################################
|
|
122
|
+
# Other public API methods
|
|
123
|
+
#########################################################################################
|
|
91
124
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
125
|
+
def state
|
|
126
|
+
[
|
|
127
|
+
title.to_s.dup.freeze,
|
|
128
|
+
message.to_s.dup.freeze,
|
|
129
|
+
displayed.map { |item| item.to_s.dup.freeze }.freeze,
|
|
130
|
+
current,
|
|
131
|
+
field.buffer.text.to_s.dup.freeze,
|
|
132
|
+
field.buffer.cursor,
|
|
133
|
+
field.buffer.virtual_suffix.to_s.dup.freeze,
|
|
134
|
+
selected_labels.map { |label| label.to_s.dup.freeze }.sort.freeze,
|
|
135
|
+
counts&.dup&.freeze,
|
|
136
|
+
renderer.screen.rows,
|
|
137
|
+
renderer.screen.cols,
|
|
138
|
+
]
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def current
|
|
142
|
+
current_index
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def counts
|
|
146
|
+
{
|
|
147
|
+
total: total_count,
|
|
148
|
+
selected: selected_count,
|
|
149
|
+
matching: matching_count,
|
|
150
|
+
showing: showing_count
|
|
151
|
+
}
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def counts_present?
|
|
155
|
+
@show_counts
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def filter_present?
|
|
159
|
+
@show_filter
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Renderer calls this to determine which slice of items to display.
|
|
163
|
+
def scroll_start(list_h:)
|
|
164
|
+
max_start = @displayed.length - list_h
|
|
165
|
+
max_start = 0 if max_start < 0
|
|
166
|
+
|
|
167
|
+
@scroll_start = 0 if @scroll_start < 0
|
|
168
|
+
@scroll_start = max_start if @scroll_start > max_start
|
|
169
|
+
@scroll_start
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def gutter_for(item:, selected:)
|
|
173
|
+
if multi_select?
|
|
174
|
+
selected_item_label?(item) ? SELECTED_GUTTER : UNSELECTED_GUTTER
|
|
175
|
+
else
|
|
176
|
+
' '
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# During path completion update the popup's candidates.
|
|
181
|
+
def replace_query(text)
|
|
182
|
+
@field.buffer.replace(text.to_s)
|
|
183
|
+
refresh_items
|
|
184
|
+
ensure_scroll_visible
|
|
185
|
+
notify_owner(:popup_changed)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def replace_source(source:, title: nil, message: nil, query: "", current: :top)
|
|
189
|
+
@source = source
|
|
190
|
+
@title = title.to_s if title
|
|
191
|
+
@message = message.to_s if message
|
|
192
|
+
@current_policy = current.to_sym
|
|
193
|
+
@field.buffer.replace(query.to_s)
|
|
194
|
+
@last_query = nil
|
|
195
|
+
@scroll_start = 0
|
|
196
|
+
|
|
197
|
+
refresh_items
|
|
198
|
+
ensure_scroll_visible
|
|
199
|
+
notify_owner(:popup_changed)
|
|
103
200
|
end
|
|
104
201
|
|
|
105
202
|
############################################################################################
|
|
@@ -107,7 +204,7 @@ module Fatty
|
|
|
107
204
|
############################################################################################
|
|
108
205
|
|
|
109
206
|
action :popup_cancel do
|
|
110
|
-
notify_owner(:popup_cancelled) + [
|
|
207
|
+
notify_owner(:popup_cancelled) + [Command.terminal(:pop_modal)]
|
|
111
208
|
end
|
|
112
209
|
|
|
113
210
|
action :popup_accept do
|
|
@@ -115,32 +212,32 @@ module Fatty
|
|
|
115
212
|
end
|
|
116
213
|
|
|
117
214
|
action :popup_next do
|
|
118
|
-
|
|
215
|
+
move_current_by(1)
|
|
119
216
|
ensure_scroll_visible
|
|
120
217
|
notify_owner(:popup_changed)
|
|
121
218
|
end
|
|
122
219
|
|
|
123
220
|
action :popup_prev do
|
|
124
|
-
|
|
221
|
+
move_current_by(-1)
|
|
125
222
|
ensure_scroll_visible
|
|
126
223
|
notify_owner(:popup_changed)
|
|
127
224
|
end
|
|
128
225
|
|
|
129
226
|
action :popup_page_down do
|
|
130
|
-
|
|
227
|
+
move_current_by(popup_list_height)
|
|
131
228
|
ensure_scroll_visible
|
|
132
229
|
notify_owner(:popup_changed)
|
|
133
230
|
end
|
|
134
231
|
|
|
135
232
|
action :popup_page_up do
|
|
136
|
-
|
|
233
|
+
move_current_by(-popup_list_height)
|
|
137
234
|
ensure_scroll_visible
|
|
138
235
|
notify_owner(:popup_changed)
|
|
139
236
|
end
|
|
140
237
|
|
|
141
238
|
action :popup_top do
|
|
142
239
|
unless @filtered.empty?
|
|
143
|
-
@
|
|
240
|
+
@current = 0
|
|
144
241
|
recenter_scroll
|
|
145
242
|
end
|
|
146
243
|
notify_owner(:popup_changed)
|
|
@@ -148,7 +245,7 @@ module Fatty
|
|
|
148
245
|
|
|
149
246
|
action :popup_bottom do
|
|
150
247
|
unless @filtered.empty?
|
|
151
|
-
@
|
|
248
|
+
@current = [@filtered.length - 1, 0].max
|
|
152
249
|
recenter_scroll
|
|
153
250
|
end
|
|
154
251
|
notify_owner(:popup_changed)
|
|
@@ -169,7 +266,56 @@ module Fatty
|
|
|
169
266
|
end
|
|
170
267
|
end
|
|
171
268
|
|
|
172
|
-
|
|
269
|
+
action :popup_tab do
|
|
270
|
+
if @kind == :completion
|
|
271
|
+
notify_owner(:popup_tab)
|
|
272
|
+
else
|
|
273
|
+
accept_selection
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
action :popup_backtab do
|
|
278
|
+
if @kind == :completion
|
|
279
|
+
notify_owner(:popup_backtab)
|
|
280
|
+
else
|
|
281
|
+
[]
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
private
|
|
286
|
+
|
|
287
|
+
# simplecov:disable
|
|
288
|
+
|
|
289
|
+
def selected_labels
|
|
290
|
+
@selected_labels.keys
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def keymap_contexts
|
|
294
|
+
contexts = [:popup, :text, :terminal]
|
|
295
|
+
contexts.unshift(:popup_multi) if multi_select?
|
|
296
|
+
contexts
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Return the outer width and height of the window for this modal,
|
|
300
|
+
# including any padding and borders.
|
|
301
|
+
def geometry(cols:, rows:)
|
|
302
|
+
max_w = max_width(cols: cols, margin: MARGIN, min_width: 10)
|
|
303
|
+
max_h = max_height(rows: rows, margin: MARGIN, min_height: 5)
|
|
304
|
+
|
|
305
|
+
desired_list_h = @filtered.length.clamp(MIN_LIST_H, MAX_LIST_H)
|
|
306
|
+
height = clamp_height(
|
|
307
|
+
desired_list_h + popup_extra_rows,
|
|
308
|
+
max_height: max_h,
|
|
309
|
+
min_height: 6,
|
|
310
|
+
)
|
|
311
|
+
width = clamp_width(
|
|
312
|
+
MAX_WIDTH,
|
|
313
|
+
max_width: max_w,
|
|
314
|
+
)
|
|
315
|
+
[width, height]
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def apply_action(action, args, event:)
|
|
173
319
|
env = action_env(event: event)
|
|
174
320
|
|
|
175
321
|
if Fatty::Actions.lookup(action)&.fetch(:on) == :session
|
|
@@ -181,53 +327,52 @@ module Fatty
|
|
|
181
327
|
notify_owner(:popup_changed)
|
|
182
328
|
end
|
|
183
329
|
rescue ActionError => e
|
|
184
|
-
Fatty.error("PopUpSession#
|
|
330
|
+
Fatty.error("PopUpSession#apply_action: ActionError #{e.message}", tag: :session)
|
|
185
331
|
[]
|
|
186
332
|
end
|
|
187
333
|
|
|
188
|
-
def
|
|
334
|
+
def move_current_by(delta)
|
|
189
335
|
return if @displayed.empty?
|
|
190
336
|
|
|
191
|
-
msg = "PopUpSession#
|
|
337
|
+
msg = "PopUpSession#move_current_by before: current=#{@current.inspect} delta=#{delta} len=#{@displayed.length}"
|
|
192
338
|
Fatty.debug(msg)
|
|
193
|
-
@
|
|
194
|
-
Fatty.debug("PopUpSession#
|
|
339
|
+
@current = ((@current || 0) + delta) % @displayed.length
|
|
340
|
+
Fatty.debug("PopUpSession#move_current_by after: selected=#{@current.inspect}")
|
|
195
341
|
end
|
|
196
342
|
|
|
197
343
|
def accept_selection
|
|
344
|
+
query = @field.buffer.text.to_s
|
|
345
|
+
@field.accept_line(save_history: @save_history) unless query.empty?
|
|
198
346
|
if multi_select?
|
|
199
|
-
|
|
347
|
+
items = selected_result_hash
|
|
348
|
+
if items.empty? && selected_item
|
|
349
|
+
items[item_label(selected_item)] = item_value(selected_item)
|
|
350
|
+
end
|
|
351
|
+
payload = popup_payload(items)
|
|
200
352
|
else
|
|
201
353
|
item = selected_item
|
|
202
|
-
query = @field.buffer.text.to_s
|
|
203
|
-
|
|
204
354
|
return [] if item.nil? && query.empty?
|
|
205
355
|
|
|
206
356
|
item = query if item.nil?
|
|
207
357
|
payload = popup_payload(item)
|
|
208
358
|
end
|
|
209
359
|
[
|
|
210
|
-
|
|
211
|
-
|
|
360
|
+
Command.terminal(
|
|
361
|
+
:send_modal_owner,
|
|
362
|
+
command: Command.session(:self, :popup_result, **payload),
|
|
363
|
+
),
|
|
364
|
+
Command.terminal(:pop_modal),
|
|
212
365
|
]
|
|
213
366
|
end
|
|
214
367
|
|
|
215
368
|
def selected_item
|
|
216
|
-
@displayed[@
|
|
369
|
+
@displayed[@current]
|
|
217
370
|
end
|
|
218
371
|
|
|
219
372
|
def selected_item_label?(item)
|
|
220
373
|
selected_label?(item_label(item))
|
|
221
374
|
end
|
|
222
375
|
|
|
223
|
-
def gutter_for(item:, selected:)
|
|
224
|
-
if multi_select?
|
|
225
|
-
selected_item_label?(item) ? SELECTED_GUTTER : UNSELECTED_GUTTER
|
|
226
|
-
else
|
|
227
|
-
' '
|
|
228
|
-
end
|
|
229
|
-
end
|
|
230
|
-
|
|
231
376
|
def refresh_items_if_query_changed
|
|
232
377
|
q = @field.buffer.text.to_s
|
|
233
378
|
return if q == @last_query
|
|
@@ -258,28 +403,16 @@ module Fatty
|
|
|
258
403
|
validate_unique_labels!(@items) if @validate_unique_labels
|
|
259
404
|
|
|
260
405
|
matcher = @matcher || method(:default_matcher)
|
|
261
|
-
|
|
262
406
|
@filtered =
|
|
263
407
|
if q.empty?
|
|
264
408
|
@items
|
|
265
409
|
else
|
|
266
410
|
@items.select { |e| matcher.call(e, q) }
|
|
267
411
|
end
|
|
268
|
-
|
|
269
412
|
refresh_displayed_items
|
|
270
413
|
apply_selection_policy!
|
|
271
414
|
end
|
|
272
415
|
|
|
273
|
-
# Renderer calls this to determine which slice of items to display.
|
|
274
|
-
def scroll_start(list_h:)
|
|
275
|
-
max_start = @displayed.length - list_h
|
|
276
|
-
max_start = 0 if max_start < 0
|
|
277
|
-
|
|
278
|
-
@scroll_start = 0 if @scroll_start < 0
|
|
279
|
-
@scroll_start = max_start if @scroll_start > max_start
|
|
280
|
-
@scroll_start
|
|
281
|
-
end
|
|
282
|
-
|
|
283
416
|
def toggle_selected_current!
|
|
284
417
|
item = selected_item
|
|
285
418
|
return unless item
|
|
@@ -295,7 +428,6 @@ module Fatty
|
|
|
295
428
|
else
|
|
296
429
|
@selected_labels[label] = true
|
|
297
430
|
end
|
|
298
|
-
|
|
299
431
|
refresh_displayed_items
|
|
300
432
|
item
|
|
301
433
|
end
|
|
@@ -322,21 +454,6 @@ module Fatty
|
|
|
322
454
|
[@displayed.length - scroll_start(list_h: popup_list_height), popup_list_height].min
|
|
323
455
|
end
|
|
324
456
|
|
|
325
|
-
def counts
|
|
326
|
-
{
|
|
327
|
-
total: total_count,
|
|
328
|
-
selected: selected_count,
|
|
329
|
-
matching: matching_count,
|
|
330
|
-
showing: showing_count
|
|
331
|
-
}
|
|
332
|
-
end
|
|
333
|
-
|
|
334
|
-
def selected_labels
|
|
335
|
-
@selected_labels.keys
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
private
|
|
339
|
-
|
|
340
457
|
def validate_unique_labels!(items)
|
|
341
458
|
counts = Hash.new(0)
|
|
342
459
|
items.each do |item|
|
|
@@ -350,29 +467,20 @@ module Fatty
|
|
|
350
467
|
raise ArgumentError, "duplicate chooser labels: #{shown}"
|
|
351
468
|
end
|
|
352
469
|
|
|
353
|
-
def popup_payload(item = selected_item)
|
|
354
|
-
{
|
|
355
|
-
kind: @kind,
|
|
356
|
-
item: item,
|
|
357
|
-
query: @field.buffer.text.to_s,
|
|
358
|
-
index: @selected
|
|
359
|
-
}
|
|
360
|
-
end
|
|
361
|
-
|
|
362
470
|
def popup_payload(item = selected_item)
|
|
363
471
|
if multi_select?
|
|
364
472
|
{
|
|
365
473
|
kind: @kind,
|
|
366
474
|
items: item,
|
|
367
475
|
query: @field.buffer.text.to_s,
|
|
368
|
-
index: @
|
|
476
|
+
index: @current
|
|
369
477
|
}
|
|
370
478
|
else
|
|
371
479
|
{
|
|
372
480
|
kind: @kind,
|
|
373
481
|
item: item,
|
|
374
482
|
query: @field.buffer.text.to_s,
|
|
375
|
-
index: @
|
|
483
|
+
index: @current
|
|
376
484
|
}
|
|
377
485
|
end
|
|
378
486
|
end
|
|
@@ -382,15 +490,20 @@ module Fatty
|
|
|
382
490
|
end
|
|
383
491
|
|
|
384
492
|
def popup_extra_rows
|
|
385
|
-
rows =
|
|
493
|
+
rows = 2 # border rows
|
|
386
494
|
rows += 1 if popup_has_message?
|
|
495
|
+
rows += 1 if counts_present?
|
|
496
|
+
rows += 1 if filter_present?
|
|
387
497
|
rows
|
|
388
498
|
end
|
|
389
499
|
|
|
390
500
|
def notify_owner(name)
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
501
|
+
[
|
|
502
|
+
Command.terminal(
|
|
503
|
+
:send_modal_owner,
|
|
504
|
+
command: Command.session(:self, name, **popup_payload, session: self),
|
|
505
|
+
),
|
|
506
|
+
]
|
|
394
507
|
end
|
|
395
508
|
|
|
396
509
|
def popup_list_height
|
|
@@ -422,10 +535,10 @@ module Fatty
|
|
|
422
535
|
top_zone = @scroll_start + band
|
|
423
536
|
bot_zone = (@scroll_start + list_h - 1) - band
|
|
424
537
|
|
|
425
|
-
if @
|
|
426
|
-
@scroll_start = @
|
|
427
|
-
elsif @
|
|
428
|
-
@scroll_start = @
|
|
538
|
+
if @current < top_zone
|
|
539
|
+
@scroll_start = @current - band
|
|
540
|
+
elsif @current > bot_zone
|
|
541
|
+
@scroll_start = @current - (list_h - 1) + band
|
|
429
542
|
end
|
|
430
543
|
|
|
431
544
|
max_start = @displayed.length - list_h
|
|
@@ -437,7 +550,7 @@ module Fatty
|
|
|
437
550
|
|
|
438
551
|
def recenter_scroll
|
|
439
552
|
list_h = popup_list_height
|
|
440
|
-
@scroll_start = @
|
|
553
|
+
@scroll_start = @current - (list_h / 2)
|
|
441
554
|
|
|
442
555
|
max_start = @displayed.length - list_h
|
|
443
556
|
max_start = 0 if max_start < 0
|
|
@@ -469,32 +582,36 @@ module Fatty
|
|
|
469
582
|
end
|
|
470
583
|
end
|
|
471
584
|
|
|
585
|
+
def current_index
|
|
586
|
+
return 0 if @displayed.empty?
|
|
587
|
+
|
|
588
|
+
@current = 0 unless @current.is_a?(Integer)
|
|
589
|
+
@current.clamp(0, @displayed.length - 1)
|
|
590
|
+
end
|
|
591
|
+
|
|
472
592
|
def apply_selection_policy!
|
|
473
|
-
prior = @
|
|
593
|
+
prior = @current
|
|
474
594
|
last_idx = [@displayed.length - 1, 0].max
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
0
|
|
478
|
-
else
|
|
479
|
-
case @selection
|
|
480
|
-
when :top
|
|
595
|
+
@current =
|
|
596
|
+
if @displayed.empty?
|
|
481
597
|
0
|
|
482
|
-
when :bottom
|
|
483
|
-
last_idx
|
|
484
598
|
else
|
|
485
|
-
@
|
|
599
|
+
case @current_policy
|
|
600
|
+
when :top
|
|
601
|
+
0
|
|
602
|
+
when :bottom
|
|
603
|
+
last_idx
|
|
604
|
+
else
|
|
605
|
+
@current.is_a?(Integer) ? @current.clamp(0, last_idx) : 0
|
|
606
|
+
end
|
|
486
607
|
end
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
if @selection == :top || @selection == :bottom
|
|
490
|
-
recenter_scroll
|
|
491
|
-
elsif @selected != prior
|
|
608
|
+
if @current != prior
|
|
492
609
|
ensure_scroll_visible
|
|
493
610
|
end
|
|
494
611
|
end
|
|
495
612
|
|
|
496
613
|
def default_matcher(item, query)
|
|
497
|
-
|
|
614
|
+
Fatty::Search.match_all_terms?(item, query)
|
|
498
615
|
end
|
|
499
616
|
|
|
500
617
|
def action_env(event:)
|