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
|
@@ -11,50 +11,44 @@ module Fatty
|
|
|
11
11
|
super
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def render_status(
|
|
15
|
-
state = status_state(
|
|
14
|
+
def render_status(status_session)
|
|
15
|
+
state = status_state(status_session)
|
|
16
16
|
return if state == @last_status_state
|
|
17
17
|
|
|
18
18
|
@last_status_state = state
|
|
19
|
-
|
|
20
|
-
win = context.status_win
|
|
21
|
-
return unless win
|
|
22
|
-
|
|
23
|
-
rows = screen.status_rect.rows
|
|
24
|
-
cols = screen.status_rect.cols
|
|
25
|
-
base_attr = pair_attr(role, fallback: ::Curses::A_REVERSE)
|
|
26
|
-
|
|
27
|
-
win.bkgdset(base_attr) if win.respond_to?(:bkgdset)
|
|
28
|
-
win.erase
|
|
29
|
-
win.attrset(base_attr)
|
|
30
|
-
|
|
31
|
-
status_render_lines(text, width: cols, max_rows: rows).each_with_index do |line, row|
|
|
32
|
-
win.setpos(row, 0)
|
|
33
|
-
rendered = Fatty::Ansi.truncate_visible(line, cols)
|
|
34
|
-
padding = [cols - Fatty::Ansi.visible_length(rendered), 0].max
|
|
35
|
-
win.addstr(rendered + (" " * padding))
|
|
36
|
-
end
|
|
37
|
-
stage_window(win)
|
|
19
|
+
draw_status_lines(status_session)
|
|
38
20
|
end
|
|
39
21
|
|
|
40
|
-
def render_output(
|
|
41
|
-
lines = viewport.slice(
|
|
42
|
-
normalized = normalized_highlights(highlights)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
highlights: normalized,
|
|
48
|
-
)
|
|
22
|
+
def render_output(output_session, viewport: output_session.viewport)
|
|
23
|
+
lines = viewport.slice(output_session.visible_lines)
|
|
24
|
+
normalized = normalized_highlights(output_session.highlights)
|
|
25
|
+
line_number_width =
|
|
26
|
+
if output_session.line_numbers?
|
|
27
|
+
output_session.output.lines.length.to_s.length
|
|
28
|
+
end
|
|
49
29
|
|
|
30
|
+
curr = output_state(output_session, viewport: viewport)
|
|
50
31
|
prev = @last_output_state
|
|
51
32
|
|
|
52
|
-
if prev &&
|
|
53
|
-
|
|
33
|
+
if prev &&
|
|
34
|
+
!output_session.line_numbers? &&
|
|
35
|
+
!ansi_lines?(lines) &&
|
|
36
|
+
output_session.incrementally_scrollable_from?(prev, curr)
|
|
37
|
+
scroll_output_window_delta!(
|
|
38
|
+
delta: output_session.scroll_delta_from(prev, curr),
|
|
39
|
+
lines: lines,
|
|
40
|
+
viewport: viewport,
|
|
41
|
+
highlights: normalized,
|
|
42
|
+
)
|
|
54
43
|
else
|
|
55
|
-
draw_output_lines(
|
|
44
|
+
draw_output_lines(
|
|
45
|
+
lines,
|
|
46
|
+
viewport: viewport,
|
|
47
|
+
highlights: normalized,
|
|
48
|
+
line_number_width: line_number_width,
|
|
49
|
+
)
|
|
56
50
|
end
|
|
57
|
-
|
|
51
|
+
@last_pager_field_state = nil
|
|
58
52
|
@last_output_state = curr
|
|
59
53
|
end
|
|
60
54
|
|
|
@@ -91,8 +85,21 @@ module Fatty
|
|
|
91
85
|
stage_window(win)
|
|
92
86
|
end
|
|
93
87
|
|
|
94
|
-
def
|
|
95
|
-
|
|
88
|
+
def clear_input_field
|
|
89
|
+
@last_input_state = nil
|
|
90
|
+
|
|
91
|
+
win = context.input_win
|
|
92
|
+
return unless win
|
|
93
|
+
|
|
94
|
+
attr = pair_attr(:input, fallback: ::Curses::A_NORMAL)
|
|
95
|
+
win.bkgdset(attr) if win.respond_to?(:bkgdset)
|
|
96
|
+
win.erase
|
|
97
|
+
win.attrset(attr)
|
|
98
|
+
stage_window(win)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def render_alert(alert_session)
|
|
102
|
+
state = alert_state(alert_session)
|
|
96
103
|
return if state == @last_alert_state
|
|
97
104
|
|
|
98
105
|
@last_alert_state = state
|
|
@@ -100,17 +107,32 @@ module Fatty
|
|
|
100
107
|
win = context.alert_win
|
|
101
108
|
return unless win
|
|
102
109
|
|
|
110
|
+
alert = alert_session.current
|
|
103
111
|
text = alert ? alert.format : ""
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
attr = pair_attr(role, fallback: pair_attr(:alert, fallback: ::Curses::A_REVERSE))
|
|
112
|
+
role = alert ? alert_role(alert.role) : :alert_info
|
|
113
|
+
base_attr = pair_attr(role, fallback: pair_attr(:alert, fallback: ::Curses::A_REVERSE))
|
|
107
114
|
cols = win.respond_to?(:maxx) ? win.maxx : screen.alert_rect.cols
|
|
108
115
|
|
|
109
|
-
win.bkgdset(
|
|
116
|
+
win.bkgdset(base_attr) if win.respond_to?(:bkgdset)
|
|
110
117
|
win.erase
|
|
111
|
-
win.attrset(
|
|
118
|
+
win.attrset(base_attr)
|
|
112
119
|
win.setpos(0, 0)
|
|
113
|
-
|
|
120
|
+
|
|
121
|
+
segments = Fatty::Ansi.segment(text).map do |segment_text, style|
|
|
122
|
+
{
|
|
123
|
+
text: segment_text,
|
|
124
|
+
role: role,
|
|
125
|
+
style: style,
|
|
126
|
+
}
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
draw_status_row(
|
|
130
|
+
win,
|
|
131
|
+
row: 0,
|
|
132
|
+
cols: cols,
|
|
133
|
+
segments: segments,
|
|
134
|
+
base_attr: base_attr,
|
|
135
|
+
)
|
|
114
136
|
|
|
115
137
|
stage_window(win)
|
|
116
138
|
end
|
|
@@ -155,12 +177,14 @@ module Fatty
|
|
|
155
177
|
field_win&.close if field_win&.respond_to?(:close)
|
|
156
178
|
end
|
|
157
179
|
|
|
180
|
+
# Modal overlays, render_popup and render_prompt_popu are deliberately
|
|
181
|
+
# not state-cached.
|
|
182
|
+
#
|
|
183
|
+
# They must be restaged on every frame because they are drawn on top of the
|
|
184
|
+
# focused session. Even when the overlay's own state has not changed, the
|
|
185
|
+
# underlying session may have redrawn the area beneath it. Skipping the overlay
|
|
186
|
+
# render would let the underlying frame erase or visually punch through it.
|
|
158
187
|
def render_popup(session:)
|
|
159
|
-
state = popup_state(session)
|
|
160
|
-
return if state == @last_popup_state
|
|
161
|
-
|
|
162
|
-
@last_popup_state = state
|
|
163
|
-
|
|
164
188
|
win = session.win
|
|
165
189
|
return unless win
|
|
166
190
|
|
|
@@ -178,16 +202,7 @@ module Fatty
|
|
|
178
202
|
|
|
179
203
|
win.attrset(frame_attr)
|
|
180
204
|
draw_popup_frame(win, width: width, height: height)
|
|
181
|
-
|
|
182
|
-
if session.title && !session.title.empty?
|
|
183
|
-
title = Fatty::Ansi.strip(session.title.to_s).tr("\r\n", " ")
|
|
184
|
-
title = Fatty::Ansi.truncate_visible(" #{title} ", [width - 4, 0].max)
|
|
185
|
-
|
|
186
|
-
unless title.empty?
|
|
187
|
-
win.setpos(0, 2)
|
|
188
|
-
win.addstr(title)
|
|
189
|
-
end
|
|
190
|
-
end
|
|
205
|
+
render_popup_title(win, session: session, width: width)
|
|
191
206
|
|
|
192
207
|
inner_h = height - 2
|
|
193
208
|
inner_w = width - 2
|
|
@@ -197,72 +212,58 @@ module Fatty
|
|
|
197
212
|
inner.bkgdset(popup_attr) if inner.respond_to?(:bkgdset)
|
|
198
213
|
inner.erase
|
|
199
214
|
|
|
200
|
-
row = 0
|
|
201
|
-
|
|
202
|
-
if session.message && !session.message.empty?
|
|
203
|
-
message = Fatty::Ansi.strip(session.message.to_s).tr("\r\n", " ")
|
|
204
|
-
message = Fatty::Ansi.truncate_visible(message, inner_w)
|
|
215
|
+
row = render_popup_message(inner, session: session, row: 0, width: inner_w, attr: popup_attr)
|
|
205
216
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
inner.addstr(message.ljust(inner_w))
|
|
209
|
-
row += 1
|
|
210
|
-
end
|
|
217
|
+
counts_present = session.counts_present?
|
|
218
|
+
filter_present = session.filter_present?
|
|
211
219
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
220
|
+
input_row = inner_h - 1 if filter_present
|
|
221
|
+
counts_row =
|
|
222
|
+
if counts_present
|
|
223
|
+
filter_present ? input_row - 1 : inner_h - 1
|
|
224
|
+
end
|
|
215
225
|
|
|
216
226
|
list_row = row
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
(0...list_h).each do |offset|
|
|
226
|
-
item_index = start + offset
|
|
227
|
-
item_selected = item_index == selected
|
|
228
|
-
attr = item_selected ? selected_attr : popup_attr
|
|
229
|
-
|
|
230
|
-
line = ""
|
|
231
|
-
if item_index < items.length
|
|
232
|
-
item = items[item_index]
|
|
233
|
-
gutter = session.gutter_for(item: item, selected: item_selected)
|
|
234
|
-
text = Fatty::Ansi.strip(item.to_s).tr("\r\n", " ")
|
|
235
|
-
available = [inner_w - Fatty::Ansi.visible_length(gutter), 0].max
|
|
236
|
-
text = Fatty::Ansi.truncate_visible(text, available)
|
|
237
|
-
line = Fatty::Ansi.truncate_visible(gutter + text, inner_w)
|
|
227
|
+
list_end =
|
|
228
|
+
if counts_present
|
|
229
|
+
counts_row
|
|
230
|
+
elsif filter_present
|
|
231
|
+
input_row
|
|
232
|
+
else
|
|
233
|
+
inner_h
|
|
238
234
|
end
|
|
239
235
|
|
|
240
|
-
|
|
241
|
-
inner.setpos(list_row + offset, 0)
|
|
242
|
-
inner.addstr(line.ljust(inner_w))
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
if counts_present && counts_row && counts_row >= 0
|
|
246
|
-
counts_text = Fatty::Ansi.truncate_visible(popup_counts_text(session), inner_w)
|
|
236
|
+
list_h = [list_end - list_row, 0].max
|
|
247
237
|
|
|
248
|
-
|
|
249
|
-
inner
|
|
250
|
-
|
|
251
|
-
|
|
238
|
+
render_popup_items(
|
|
239
|
+
inner,
|
|
240
|
+
session: session,
|
|
241
|
+
row: list_row,
|
|
242
|
+
height: list_h,
|
|
243
|
+
width: inner_w,
|
|
244
|
+
popup_attr: popup_attr,
|
|
245
|
+
selected_attr: selected_attr,
|
|
246
|
+
)
|
|
252
247
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
row:
|
|
248
|
+
render_popup_counts(
|
|
249
|
+
inner,
|
|
250
|
+
session: session,
|
|
251
|
+
row: counts_row,
|
|
257
252
|
width: inner_w,
|
|
258
|
-
|
|
259
|
-
base_attr: input_attr,
|
|
260
|
-
region_attr: pair_attr(:region, fallback: ::Curses::A_REVERSE),
|
|
261
|
-
suggestion_attr: pair_attr(:input_suggestion, fallback: input_attr),
|
|
253
|
+
attr: counts_attr,
|
|
262
254
|
)
|
|
263
255
|
|
|
264
|
-
|
|
265
|
-
|
|
256
|
+
if filter_present
|
|
257
|
+
render_popup_filter(
|
|
258
|
+
inner,
|
|
259
|
+
session: session,
|
|
260
|
+
row: input_row,
|
|
261
|
+
width: inner_w,
|
|
262
|
+
attr: input_attr,
|
|
263
|
+
)
|
|
264
|
+
cursor_x = session.field.cursor_x.to_i.clamp(0, [inner_w - 1, 0].max)
|
|
265
|
+
win.setpos(1 + input_row, 1 + cursor_x)
|
|
266
|
+
end
|
|
266
267
|
|
|
267
268
|
stage_window(win)
|
|
268
269
|
rescue RuntimeError => e
|
|
@@ -275,11 +276,6 @@ module Fatty
|
|
|
275
276
|
end
|
|
276
277
|
|
|
277
278
|
def render_prompt_popup(session:)
|
|
278
|
-
state = prompt_popup_state(session)
|
|
279
|
-
return if state == @last_prompt_popup_state
|
|
280
|
-
|
|
281
|
-
@last_prompt_popup_state = state
|
|
282
|
-
|
|
283
279
|
win = session.win
|
|
284
280
|
return unless win
|
|
285
281
|
|
|
@@ -331,6 +327,7 @@ module Fatty
|
|
|
331
327
|
)
|
|
332
328
|
|
|
333
329
|
cursor_x = session.field.cursor_x.to_i.clamp(0, [inner_w - 1, 0].max)
|
|
330
|
+
show_cursor
|
|
334
331
|
win.setpos(1 + input_row, 1 + cursor_x)
|
|
335
332
|
|
|
336
333
|
stage_window(inner)
|
|
@@ -382,15 +379,117 @@ module Fatty
|
|
|
382
379
|
stage_window(win)
|
|
383
380
|
end
|
|
384
381
|
|
|
382
|
+
def hide_cursor
|
|
383
|
+
::Curses.curs_set(0)
|
|
384
|
+
self
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def show_cursor
|
|
388
|
+
::Curses.curs_set(1)
|
|
389
|
+
self
|
|
390
|
+
end
|
|
391
|
+
|
|
385
392
|
private
|
|
386
393
|
|
|
394
|
+
# simplecov:disable
|
|
395
|
+
|
|
396
|
+
def render_popup_title(win, session:, width:)
|
|
397
|
+
return unless session.title && !session.title.empty?
|
|
398
|
+
|
|
399
|
+
title = Fatty::Ansi.strip(session.title.to_s).tr("\r\n", " ")
|
|
400
|
+
title = Fatty::Ansi.truncate_visible(" #{title} ", [width - 4, 0].max)
|
|
401
|
+
return if title.empty?
|
|
402
|
+
|
|
403
|
+
win.setpos(0, 2)
|
|
404
|
+
win.addstr(title)
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def render_popup_message(win, session:, row:, width:, attr:)
|
|
408
|
+
return row unless session.message && !session.message.empty?
|
|
409
|
+
|
|
410
|
+
message = Fatty::Ansi.strip(session.message.to_s).tr("\r\n", " ")
|
|
411
|
+
message = Fatty::Ansi.truncate_visible(message, width)
|
|
412
|
+
render_popup_plain_row(win, row: row, width: width, text: message, attr: attr)
|
|
413
|
+
|
|
414
|
+
row + 1
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
def render_popup_items(win, session:, row:, height:, width:, popup_attr:, selected_attr:)
|
|
418
|
+
items = session.displayed
|
|
419
|
+
selected = session.current
|
|
420
|
+
start = session.scroll_start(list_h: height)
|
|
421
|
+
|
|
422
|
+
(0...height).each do |offset|
|
|
423
|
+
item_index = start + offset
|
|
424
|
+
item_selected = item_index == selected
|
|
425
|
+
attr = item_selected ? selected_attr : popup_attr
|
|
426
|
+
text = popup_item_text(session, items: items, item_index: item_index, selected: item_selected, width: width)
|
|
427
|
+
|
|
428
|
+
render_popup_plain_row(
|
|
429
|
+
win,
|
|
430
|
+
row: row + offset,
|
|
431
|
+
width: width,
|
|
432
|
+
text: text,
|
|
433
|
+
attr: attr,
|
|
434
|
+
)
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def popup_item_text(session, items:, item_index:, selected:, width:)
|
|
439
|
+
line = ""
|
|
440
|
+
|
|
441
|
+
if item_index < items.length
|
|
442
|
+
item = items[item_index]
|
|
443
|
+
gutter = session.gutter_for(item: item, selected: selected)
|
|
444
|
+
text = Fatty::Ansi.strip(item.to_s).tr("\r\n", " ")
|
|
445
|
+
available = [width - Fatty::Ansi.visible_length(gutter), 0].max
|
|
446
|
+
text = Fatty::Ansi.truncate_visible(text, available)
|
|
447
|
+
line = Fatty::Ansi.truncate_visible(gutter + text, width)
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
line
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
def render_popup_counts(win, session:, row:, width:, attr:)
|
|
454
|
+
return unless row && row >= 0
|
|
455
|
+
|
|
456
|
+
text = Fatty::Ansi.truncate_visible(popup_counts_text(session), width)
|
|
457
|
+
render_popup_plain_row(win, row: row, width: width, text: text, attr: attr)
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def render_popup_filter(win, session:, row:, width:, attr:)
|
|
461
|
+
render_popup_plain_row(win, row: row, width: width, text: "", attr: attr)
|
|
462
|
+
|
|
463
|
+
render_field_into(
|
|
464
|
+
win: win,
|
|
465
|
+
field: session.field,
|
|
466
|
+
row: row,
|
|
467
|
+
width: width,
|
|
468
|
+
base_role: :popup_input,
|
|
469
|
+
base_attr: attr,
|
|
470
|
+
region_attr: pair_attr(:region, fallback: ::Curses::A_REVERSE),
|
|
471
|
+
suggestion_attr: pair_attr(:input_suggestion, fallback: attr),
|
|
472
|
+
)
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
def render_popup_plain_row(win, row:, width:, text:, attr:)
|
|
476
|
+
line = Fatty::Ansi.truncate_visible(text.to_s, width)
|
|
477
|
+
line = line.ljust(width)
|
|
478
|
+
|
|
479
|
+
win.attrset(attr)
|
|
480
|
+
win.setpos(row, 0)
|
|
481
|
+
win.addstr(line)
|
|
482
|
+
end
|
|
483
|
+
|
|
387
484
|
def available_colors
|
|
388
485
|
::Curses.colors
|
|
389
486
|
end
|
|
390
487
|
|
|
488
|
+
def ansi_lines?(lines)
|
|
489
|
+
lines.any? { |line| line.to_s.include?("\e[") }
|
|
490
|
+
end
|
|
491
|
+
|
|
391
492
|
def after_apply_theme!
|
|
392
|
-
@ansi_pair_cache = {}
|
|
393
|
-
@next_ansi_pair_id = nil
|
|
394
493
|
context.apply_palette(palette)
|
|
395
494
|
end
|
|
396
495
|
|
|
@@ -409,49 +508,105 @@ module Fatty
|
|
|
409
508
|
delta != 0 && delta.abs < curr[:height]
|
|
410
509
|
end
|
|
411
510
|
|
|
412
|
-
def scroll_output_window_delta!(
|
|
511
|
+
def scroll_output_window_delta!(delta:, lines:, viewport:, highlights:)
|
|
413
512
|
Fatty.debug("calling scroll_output_window_delta!", tag: :scrolling)
|
|
414
513
|
win = context.output_win
|
|
415
|
-
|
|
416
|
-
base_attr = pair_attr(:output, fallback: ::Curses::A_NORMAL)
|
|
514
|
+
return unless win
|
|
417
515
|
|
|
516
|
+
height = viewport.height
|
|
517
|
+
base_attr = pair_attr(:output, fallback: ::Curses::A_NORMAL)
|
|
418
518
|
win.attrset(base_attr)
|
|
419
519
|
win.scrl(delta)
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
start_y = curr[:height] - delta
|
|
520
|
+
if delta.positive?
|
|
521
|
+
start_y = height - delta
|
|
423
522
|
start_y = 0 if start_y < 0
|
|
424
523
|
|
|
425
|
-
(start_y...
|
|
426
|
-
line = curr[:lines][y]
|
|
427
|
-
abs_line = curr[:top] + y
|
|
524
|
+
(start_y...height).each do |y|
|
|
428
525
|
draw_output_row(
|
|
429
526
|
win,
|
|
430
|
-
line:
|
|
527
|
+
line: lines[y],
|
|
431
528
|
y: y,
|
|
432
|
-
abs_line:
|
|
433
|
-
highlights:
|
|
529
|
+
abs_line: viewport.top + y,
|
|
530
|
+
highlights: highlights,
|
|
434
531
|
)
|
|
435
532
|
end
|
|
436
533
|
else
|
|
437
534
|
count = -delta
|
|
438
|
-
count =
|
|
535
|
+
count = height if count > height
|
|
536
|
+
|
|
439
537
|
(0...count).each do |y|
|
|
440
|
-
line = curr[:lines][y]
|
|
441
|
-
abs_line = curr[:top] + y
|
|
442
538
|
draw_output_row(
|
|
443
539
|
win,
|
|
444
|
-
line:
|
|
540
|
+
line: lines[y],
|
|
445
541
|
y: y,
|
|
446
|
-
abs_line:
|
|
447
|
-
highlights:
|
|
542
|
+
abs_line: viewport.top + y,
|
|
543
|
+
highlights: highlights,
|
|
448
544
|
)
|
|
449
545
|
end
|
|
450
546
|
end
|
|
451
547
|
stage_window(win)
|
|
452
548
|
end
|
|
453
549
|
|
|
454
|
-
def
|
|
550
|
+
def draw_status_lines(status_session)
|
|
551
|
+
win = context.status_win
|
|
552
|
+
return unless win
|
|
553
|
+
|
|
554
|
+
rect = screen.status_rect
|
|
555
|
+
base_attr = pair_attr(status_session.role, fallback: ::Curses::A_REVERSE)
|
|
556
|
+
lines = status_segment_lines(status_session)
|
|
557
|
+
win.bkgdset(base_attr) if win.respond_to?(:bkgdset)
|
|
558
|
+
win.erase
|
|
559
|
+
rect.rows.times do |row|
|
|
560
|
+
draw_status_row(
|
|
561
|
+
win,
|
|
562
|
+
row: row,
|
|
563
|
+
cols: rect.cols,
|
|
564
|
+
segments: lines[row] || [],
|
|
565
|
+
base_attr: base_attr,
|
|
566
|
+
)
|
|
567
|
+
end
|
|
568
|
+
stage_window(win)
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
def draw_status_row(win, row:, cols:, segments:, base_attr:)
|
|
572
|
+
win.setpos(row, 0)
|
|
573
|
+
remaining = cols
|
|
574
|
+
|
|
575
|
+
segments.each do |segment|
|
|
576
|
+
break if remaining <= 0
|
|
577
|
+
|
|
578
|
+
text = segment[:text].to_s.tr("\r\n", " ")
|
|
579
|
+
text = Fatty::Ansi.truncate_visible(text, remaining)
|
|
580
|
+
next if text.empty?
|
|
581
|
+
|
|
582
|
+
segment_role = segment[:role] || :status
|
|
583
|
+
role_attr = pair_attr(segment_role, fallback: base_attr)
|
|
584
|
+
|
|
585
|
+
attr =
|
|
586
|
+
if segment[:style]
|
|
587
|
+
ansi_style_attr(
|
|
588
|
+
segment[:style],
|
|
589
|
+
fallback: role_attr,
|
|
590
|
+
fallback_role: segment_role,
|
|
591
|
+
)
|
|
592
|
+
else
|
|
593
|
+
role_attr
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
win.attrset(attr)
|
|
597
|
+
win.addstr(text)
|
|
598
|
+
remaining -= Fatty::Ansi.visible_length(text)
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
if remaining.positive?
|
|
602
|
+
win.attrset(base_attr)
|
|
603
|
+
win.addstr(" " * remaining)
|
|
604
|
+
end
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
def draw_output_lines(lines, viewport:, highlights: nil, line_number_width: nil)
|
|
608
|
+
context.reset_ansi_pairs!
|
|
609
|
+
|
|
455
610
|
win = context.output_win
|
|
456
611
|
base_attr = pair_attr(:output, fallback: ::Curses::A_NORMAL)
|
|
457
612
|
|
|
@@ -459,40 +614,48 @@ module Fatty
|
|
|
459
614
|
win.bkgdset(base_attr) if win.respond_to?(:bkgdset)
|
|
460
615
|
win.erase
|
|
461
616
|
|
|
462
|
-
lines.each_with_index do |
|
|
463
|
-
abs_line = viewport.top + y
|
|
617
|
+
lines.each_with_index do |visible_line, y|
|
|
464
618
|
draw_output_row(
|
|
465
619
|
win,
|
|
466
|
-
line:
|
|
620
|
+
line: visible_line.text,
|
|
467
621
|
y: y,
|
|
468
|
-
abs_line:
|
|
622
|
+
abs_line: visible_line.number - 1,
|
|
469
623
|
highlights: highlights,
|
|
624
|
+
line_number: visible_line.number,
|
|
625
|
+
line_number_width: line_number_width,
|
|
470
626
|
)
|
|
471
627
|
end
|
|
472
628
|
stage_window(win)
|
|
473
629
|
end
|
|
474
630
|
|
|
475
|
-
def draw_output_row(win, line:, y:, abs_line:, highlights:)
|
|
631
|
+
def draw_output_row(win, line:, y:, abs_line:, highlights:, line_number: nil, line_number_width: nil)
|
|
476
632
|
base_attr = pair_attr(:output, fallback: ::Curses::A_NORMAL)
|
|
633
|
+
line_number_attr = base_attr | ::Curses::A_DIM
|
|
477
634
|
hi_attr = pair_attr(:match_current, fallback: ::Curses::A_REVERSE)
|
|
478
635
|
hi2_attr = pair_attr(:match_other, fallback: hi_attr)
|
|
479
636
|
|
|
480
637
|
semantic_ranges = highlight_ranges_for_line(highlights, abs_line)
|
|
481
638
|
win.setpos(y, 0)
|
|
482
639
|
win.attrset(base_attr)
|
|
640
|
+
|
|
641
|
+
line_number_attr = pair_attr(:line_number, fallback: base_attr | ::Curses::A_DIM)
|
|
642
|
+
if line_number && line_number_width
|
|
643
|
+
number_text = line_number.to_s.rjust(line_number_width, "0")
|
|
644
|
+
win.attrset(line_number_attr)
|
|
645
|
+
win.addstr("#{number_text}: ")
|
|
646
|
+
win.attrset(base_attr)
|
|
647
|
+
end
|
|
648
|
+
|
|
483
649
|
curses_ranges =
|
|
484
650
|
Array(semantic_ranges).map do |from, to, role|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
# slices = build_line_slices(plain, ranges: curses_ranges) do |_style|
|
|
494
|
-
# base_attr
|
|
495
|
-
# end
|
|
651
|
+
attr =
|
|
652
|
+
case role
|
|
653
|
+
when :secondary then hi2_attr
|
|
654
|
+
else hi_attr
|
|
655
|
+
end
|
|
656
|
+
[from.to_i, to.to_i, attr]
|
|
657
|
+
end
|
|
658
|
+
|
|
496
659
|
slices = build_line_slices(line.to_s, ranges: curses_ranges) do |style|
|
|
497
660
|
ansi_style_attr(style, fallback: base_attr)
|
|
498
661
|
end
|
|
@@ -625,13 +788,16 @@ module Fatty
|
|
|
625
788
|
break if remaining <= 0
|
|
626
789
|
|
|
627
790
|
attr =
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
791
|
+
if segment[:style]
|
|
792
|
+
ansi_style_attr(segment[:style], fallback: base_attr, fallback_role: base_role)
|
|
793
|
+
else
|
|
794
|
+
case segment[:role]
|
|
795
|
+
when :region then region_attr
|
|
796
|
+
when :input_suggestion then suggestion_attr
|
|
797
|
+
else base_attr
|
|
798
|
+
end
|
|
632
799
|
end
|
|
633
|
-
|
|
634
|
-
text = Fatty::Ansi.strip(segment[:text].to_s).tr("\r\n", " ")
|
|
800
|
+
text = segment[:text].to_s.tr("\r\n", " ")
|
|
635
801
|
text = Fatty::Ansi.truncate_visible(text, remaining)
|
|
636
802
|
next if text.empty?
|
|
637
803
|
|
|
@@ -641,18 +807,27 @@ module Fatty
|
|
|
641
807
|
end
|
|
642
808
|
end
|
|
643
809
|
|
|
644
|
-
def ansi_style_attr(style, fallback:)
|
|
810
|
+
def ansi_style_attr(style, fallback:, fallback_role: :output)
|
|
645
811
|
attr = fallback
|
|
812
|
+
|
|
646
813
|
if style.fg || style.bg
|
|
647
814
|
fg = curses_color_for_style(style.fg)
|
|
648
815
|
bg = curses_color_for_style(style.bg)
|
|
649
816
|
|
|
650
|
-
|
|
651
|
-
fg =
|
|
652
|
-
bg =
|
|
817
|
+
role_spec = palette[fallback_role] || palette[:output] || {}
|
|
818
|
+
fg = role_spec[:fg] if fg.nil?
|
|
819
|
+
bg = role_spec[:bg] if bg.nil?
|
|
653
820
|
|
|
654
|
-
|
|
821
|
+
fallback_pair_id = Fatty::Colors::Pairs::ROLE_TO_PAIR.fetch(fallback_role)
|
|
822
|
+
pair_id = context.ansi_pair_for(
|
|
823
|
+
fg,
|
|
824
|
+
bg,
|
|
825
|
+
fallback_pair_id: fallback_pair_id,
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
attr = ::Curses.color_pair(pair_id)
|
|
655
829
|
end
|
|
830
|
+
|
|
656
831
|
attr |= ::Curses::A_BOLD if style.bold
|
|
657
832
|
attr |= ::Curses::A_DIM if style.respond_to?(:dim) && style.dim
|
|
658
833
|
attr |= ::Curses::A_UNDERLINE if style.underline
|
|
@@ -673,25 +848,6 @@ module Fatty
|
|
|
673
848
|
)
|
|
674
849
|
end
|
|
675
850
|
end
|
|
676
|
-
|
|
677
|
-
def ansi_pair_for(fg, bg)
|
|
678
|
-
@ansi_pair_cache ||= {}
|
|
679
|
-
key = [fg.to_i, bg.to_i]
|
|
680
|
-
|
|
681
|
-
@ansi_pair_cache[key] ||=
|
|
682
|
-
begin
|
|
683
|
-
pair_id = next_ansi_pair_id
|
|
684
|
-
::Curses.init_pair(pair_id, fg.to_i, bg.to_i)
|
|
685
|
-
pair_id
|
|
686
|
-
end
|
|
687
|
-
end
|
|
688
|
-
|
|
689
|
-
def next_ansi_pair_id
|
|
690
|
-
@next_ansi_pair_id ||= Fatty::Colors::Pairs::ROLE_TO_PAIR.values.max + 1
|
|
691
|
-
id = @next_ansi_pair_id
|
|
692
|
-
@next_ansi_pair_id += 1
|
|
693
|
-
id
|
|
694
|
-
end
|
|
695
851
|
end
|
|
696
852
|
end
|
|
697
853
|
end
|