History

feat: emphasize active tix shortcut prefixes

Expanded shortcut groups previously differed from their collapsed form primarily through parentheses and an underlined key. That cue was easy to miss in the dense history footer, particularly when deciding whether the next key would invoke a direct action or a prefixed one. Reverse every cell belonging to the open view, edit, or information group while preserving its existing colors, dimmed toggles, and underlines. Keep separators and neighboring top-level actions outside the reversed range so the active mode has a strong, theme-independent boundary.

Codex committed 3 months ago

Commit 152ee6b1 parent 1a7f9332 files · +38 −2Browse files

Unified · Split

2 files
  1. gix-tix/spec.md +3 −2
  2. gix-tix/src/ui.rs +35 −0
gix-tix/spec.md+3−2
@@ -219,8 +219,9 @@ for signature verification, alignment, message, and changes actions, but these · 218 unmodified lines
219actions retain their direct shortcuts while the group is closed. While expanded,219actions retain their direct shortcuts while the group is closed. While expanded,
220the group visually contains every following status action through `<enter> diff`,220the group visually contains every following status action through `<enter> diff`,
221including pane switching and navigation; none of those hints is visible while221including pane switching and navigation; none of those hints is visible while
222the group is closed, and quit remains outside it. The footer222the group is closed, and quit remains outside it. The footer underlines the `v`
223underlines the `v` in `view`; while active, `view (` contains223in `view`; an open prefix reverses its complete expanded group for a strong,
224terminal-theme-independent cue. While active, `view (` contains
224every applicable display option and a closing `)` so direct shortcuts remain visibly225every applicable display option and a closing `)` so direct shortcuts remain visibly
225outside the prefix. The history status starts with the history position, then the226outside the prefix. The history status starts with the history position, then the
226`v` prefix and the `e` prefix when it is addressable. Remaining history-level227`v` prefix and the `e` prefix when it is addressable. Remaining history-level
gix-tix/src/ui.rs+35−0
@@ -878,6 +878,7 @@ pub(crate) fn draw_with_worktree( · 877 unmodified lines
878 }878 }
879 }879 }
880 edit_prefix_spans.push(Span::raw(")"));880 edit_prefix_spans.push(Span::raw(")"));
881 emphasize_prefix(&mut edit_prefix_spans[1..]);
881 } else if !app.history_display_expanded {882 } else if !app.history_display_expanded {
882 edit_prefix_spans.push(Span::raw(" · "));883 edit_prefix_spans.push(Span::raw(" · "));
883 edit_prefix_spans.push(Span::styled("e", Style::default().add_modifier(Modifier::UNDERLINED)));884 edit_prefix_spans.push(Span::styled("e", Style::default().add_modifier(Modifier::UNDERLINED)));
@@ -929,6 +930,7 @@ pub(crate) fn draw_with_worktree( · 45 unmodified lines
929 ));930 ));
930 }931 }
931 view_prefix_spans.push(Span::raw(")"));932 view_prefix_spans.push(Span::raw(")"));
933 emphasize_prefix(&mut view_prefix_spans[1..]);
932 } else {934 } else {
933 view_prefix_spans.push(Span::raw(" · "));935 view_prefix_spans.push(Span::raw(" · "));
934 view_prefix_spans.push(Span::styled("v", Style::default().add_modifier(Modifier::UNDERLINED)));936 view_prefix_spans.push(Span::styled("v", Style::default().add_modifier(Modifier::UNDERLINED)));
@@ -946,6 +948,7 @@ pub(crate) fn draw_with_worktree( · 11 unmodified lines
946 ordered.push(Span::raw(" · "));948 ordered.push(Span::raw(" · "));
947 ordered.extend(shortcut("refs", 'r', app.ref_mode != RefMode::None));949 ordered.extend(shortcut("refs", 'r', app.ref_mode != RefMode::None));
948 ordered.push(Span::raw(" · "));950 ordered.push(Span::raw(" · "));
951 let information_prefix_start = app.information_expanded.then_some(ordered.len());
949 ordered.push(Span::styled("?", Style::default().add_modifier(Modifier::UNDERLINED)));952 ordered.push(Span::styled("?", Style::default().add_modifier(Modifier::UNDERLINED)));
950 if app.information_expanded {953 if app.information_expanded {
951 ordered.push(Span::raw(" ("));954 ordered.push(Span::raw(" ("));
@@ -981,6 +984,7 @@ pub(crate) fn draw_with_worktree( · 29 unmodified lines
981 footer_spans.push(Span::raw(" · <enter> diff"));984 footer_spans.push(Span::raw(" · <enter> diff"));
982 }985 }
983 footer_spans.push(Span::raw(")"));986 footer_spans.push(Span::raw(")"));
987 emphasize_prefix(&mut footer_spans[information_prefix_start.expect("the prefix is expanded")..]);
984 }988 }
985 if app.changes_focus.is_none() {989 if app.changes_focus.is_none() {
986 footer_spans.push(Span::raw(" · "));990 footer_spans.push(Span::raw(" · "));
@@ -1623,6 +1627,12 @@ fn shortcut(label: &'static str, key: char, enabled: bool) -> Vec<Span<'static>> · 636 unmodified lines
1623 ]1627 ]
1624}1628}
1625 1629
1630fn emphasize_prefix(spans: &mut [Span<'_>]) {
1631 for span in spans {
1632 span.style = span.style.add_modifier(Modifier::REVERSED);
1633 }
1634}
1635
1626fn notification_discs(spans: Vec<Span<'_>>) -> Vec<Span<'_>> {1636fn notification_discs(spans: Vec<Span<'_>>) -> Vec<Span<'_>> {
1627 let mut out = Vec::with_capacity(spans.len());1637 let mut out = Vec::with_capacity(spans.len());
1628 for span in spans {1638 for span in spans {
@@ -2924,6 +2934,7 @@ mod tests { · 1295 unmodified lines
2924 .contains(Modifier::UNDERLINED),2934 .contains(Modifier::UNDERLINED),
2925 "the prefix key is underlined in its verb"2935 "the prefix key is underlined in its verb"
2926 );2936 );
2937 assert_reversed_group(&terminal, 1, view);
2927 2938
2928 app.history_display_expanded = false;2939 app.history_display_expanded = false;
2929 app.edit_expanded = true;2940 app.edit_expanded = true;
@@ -2932,6 +2943,7 @@ mod tests { · 2 unmodified lines
2932 rendered_line(&terminal, 1).starts_with("0 commits · view · edit (no actions) · copy · refs · ?"),2943 rendered_line(&terminal, 1).starts_with("0 commits · view · edit (no actions) · copy · refs · ?"),
2933 "the edit prefix follows the view prefix even when no action is available"2944 "the edit prefix follows the view prefix even when no action is available"
2934 );2945 );
2946 assert_reversed_group(&terminal, 1, "edit (no actions)");
2935 2947
2936 app.edit_expanded = false;2948 app.edit_expanded = false;
2937 app.information_expanded = true;2949 app.information_expanded = true;
@@ -2943,6 +2955,11 @@ mod tests { · 5 unmodified lines
2943 ),2955 ),
2944 "the information prefix contains every following action except quit: {footer}"2956 "the information prefix contains every following action except quit: {footer}"
2945 );2957 );
2958 assert_reversed_group(
2959 &terminal,
2960 1,
2961 "? ([ align · message · changes · Esc cancel · ↑↓/jk move · h/l pan · <enter> diff)",
2962 );
2946 Ok(())2963 Ok(())
2947 }2964 }
2948 2965
@@ -5108,6 +5125,24 @@ mod tests { · 2159 unmodified lines
5108 })5125 })
5109 }5126 }
5110 5127
5128 fn assert_reversed_group(terminal: &Terminal<TestBackend>, y: u16, group: &str) {
5129 let line = rendered_line(terminal, y);
5130 let start = line[..line.find(group).expect("the active prefix group is visible")]
5131 .chars()
5132 .count() as u16;
5133 let end = start + group.chars().count() as u16;
5134 let buffer = terminal.backend().buffer();
5135 assert!(
5136 (start..end).all(|x| buffer[(x, y)].modifier.contains(Modifier::REVERSED)),
5137 "every cell in {group:?} is reversed"
5138 );
5139 assert!(
5140 !buffer[(start - 1, y)].modifier.contains(Modifier::REVERSED)
5141 && !buffer[(end, y)].modifier.contains(Modifier::REVERSED),
5142 "the active treatment is bounded to {group:?}"
5143 );
5144 }
5145
5111 fn footer_is_dim(terminal: &Terminal<TestBackend>, label: &str) -> bool {5146 fn footer_is_dim(terminal: &Terminal<TestBackend>, label: &str) -> bool {
5112 let y = terminal.backend().buffer().area.height - 1;5147 let y = terminal.backend().buffer().area.height - 1;
5113 let footer = rendered_line(terminal, y);5148 let footer = rendered_line(terminal, y);

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t