Skip to content

Commit 7a77d77

Browse files
committed
chore: Cleanup shortcuts
1 parent 9a5c7ef commit 7a77d77

File tree

2 files changed

+81
-51
lines changed

2 files changed

+81
-51
lines changed

src/ui/mod.rs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use ratatui::widgets::{
1212
Block, BorderType, Borders, Padding, Paragraph, Row, Scrollbar, ScrollbarState, Table,
1313
};
1414
use ratatui::{symbols, Frame};
15+
use widget::Shortcut;
1516

1617
// Helper function to calculate visible rows in the table
1718
fn calculate_visible_table_rows(model: &crate::AppModel) -> usize {
@@ -210,12 +211,11 @@ pub fn render_detail(model: &crate::AppModel, area: Rect, frame: &mut Frame) {
210211
};
211212

212213
let shortcuts = Shortcuts::new(vec![
213-
("q", "quit"),
214-
("space", "✂️snip"),
215-
("w", "write and quit"),
216-
("↑ ↓", "move"),
217-
("/", "search"),
218-
("Esc", "exit search"),
214+
Shortcut::Pair("space", "✂️snip"),
215+
Shortcut::Pair("w", "write and quit"),
216+
Shortcut::Pair("/", "search"),
217+
Shortcut::Trio("▼", "move", "▲"),
218+
Shortcut::Pair("q", "quit"),
219219
])
220220
.with_alignment(Alignment::Right)
221221
.with_label_style(model.default_style.add_modifier(Modifier::BOLD));
@@ -254,6 +254,14 @@ pub fn render_search(model: &mut crate::AppModel, area: Rect, frame: &mut Frame)
254254
..symbols::border::PLAIN
255255
};
256256

257+
let shortcuts = Shortcuts::new(vec![
258+
Shortcut::Pair("🔍", "search"),
259+
Shortcut::Pair("Esc", "exit search"),
260+
Shortcut::Pair("Ctrl+U", "clear search"),
261+
])
262+
.with_alignment(Alignment::Left)
263+
.with_label_style(model.default_style.add_modifier(Modifier::BOLD));
264+
257265
let block = Block::default()
258266
.padding(Padding {
259267
left: 1,
@@ -263,11 +271,14 @@ pub fn render_search(model: &mut crate::AppModel, area: Rect, frame: &mut Frame)
263271
})
264272
.border_set(collapsed_top_border_set)
265273
.borders(Borders::TOP | Borders::LEFT | Borders::RIGHT)
266-
.title(" 🔍 ")
274+
.title(shortcuts.as_line())
267275
.style(model.default_style);
268276

269-
model.search_state.text_input.set_block(block);
270-
frame.render_widget(&model.search_state.text_input, area);
277+
let inner_area = block.inner(area);
278+
279+
frame.render_widget(block, area);
280+
281+
frame.render_widget(&model.search_state.text_input, inner_area);
271282
}
272283

273284
fn styled_method_with_description(method: &Method, padding: usize) -> Line {

src/ui/widget/shortcuts.rs

Lines changed: 61 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,16 @@ use ratatui::prelude::{Line, Modifier, Span, Style, Widget};
55
use ratatui::style::Color;
66
use ratatui::widgets::Clear;
77

8+
/// Enum to define shortcut structures for the constructor
9+
pub enum Shortcut<'a> {
10+
Pair(&'a str, &'a str),
11+
Trio(&'a str, &'a str, &'a str),
12+
}
13+
814
/// A widget to display keyboard shortcuts in the UI
915
#[derive(Clone, Default)]
1016
pub struct Shortcuts {
11-
shortcuts: Vec<(String, String)>,
17+
shortcuts: Vec<(String, String, Option<String>)>,
1218
separator: String,
1319
shortcut_label_style: Style,
1420
shortcut_key_style: Style,
@@ -18,12 +24,17 @@ pub struct Shortcuts {
1824
}
1925

2026
impl Shortcuts {
21-
/// Create a new shortcuts widget from a vector of (key, label) pairs
22-
pub fn from(values: Vec<(&str, &str)>) -> Self {
27+
/// Create a new shortcuts widget from a vector of ShortcutDef enums
28+
pub fn new(values: Vec<Shortcut>) -> Self {
2329
Self {
2430
shortcuts: values
2531
.into_iter()
26-
.map(|(k, l)| (k.to_string(), l.to_string()))
32+
.map(|def| match def {
33+
Shortcut::Pair(k, l) => (k.to_string(), l.to_string(), None),
34+
Shortcut::Trio(k1, l, k2) => {
35+
(k1.to_string(), l.to_string(), Some(k2.to_string()))
36+
}
37+
})
2738
.collect(),
2839
separator: " | ".to_string(),
2940
shortcut_label_style: Style::default().add_modifier(Modifier::BOLD),
@@ -36,65 +47,73 @@ impl Shortcuts {
3647
}
3748
}
3849

39-
/// Create a new shortcuts widget directly (static constructor)
40-
///
41-
/// # Example
42-
/// ```
43-
/// let shortcuts = Shortcuts::new(vec![
44-
/// ("Esc", "exit"),
45-
/// ("↑", "move up"),
46-
/// ]);
47-
/// frame.render_widget(shortcuts, area);
48-
/// ```
49-
pub fn new(values: Vec<(&str, &str)>) -> Self {
50-
Self::from(values)
51-
}
52-
5350
/// Get the line representation of all shortcuts
5451
pub fn as_line(&self) -> Line {
5552
if self.shortcuts.is_empty() {
5653
return Line::default().alignment(self.alignment);
5754
}
5855

59-
let mut spans = Vec::with_capacity(self.shortcuts.len() * 5 + 2);
56+
let mut spans = Vec::with_capacity(self.shortcuts.len() * 5 + 2); // Adjusted capacity estimate
6057

6158
// Add start padding if configured
6259
if !self.padding_start.is_empty() {
6360
spans.push(Span::raw(&self.padding_start));
6461
}
6562

6663
// Process each shortcut
67-
for (i, (key, label)) in self.shortcuts.iter().enumerate() {
64+
for (i, (key1, label, key2_opt)) in self.shortcuts.iter().enumerate() {
6865
// Add separator before shortcut (except for the first one)
6966
if i > 0 {
7067
spans.push(Span::raw(&self.separator));
7168
}
7269

73-
// Render the key-label pair
74-
if label.contains(key) {
75-
// Create mnemonic spans (key is part of the label)
76-
let first_char = key.chars().next().unwrap_or('?');
77-
78-
if let Some(idx) = label.find(first_char) {
79-
// Split the label around the key character
80-
let before = &label[..idx];
81-
let highlight = &label[idx..idx + 1];
82-
let after = &label[idx + 1..];
83-
84-
spans.push(Span::styled(before, self.shortcut_label_style));
85-
spans.push(Span::styled(highlight, self.shortcut_key_style));
86-
spans.push(Span::styled(after, self.shortcut_label_style));
87-
} else {
88-
// Fallback to regular key + label
89-
spans.push(Span::styled(key, self.shortcut_key_style));
70+
match key2_opt {
71+
Some(key2) => {
72+
// Three-part shortcut: key1 label key2
73+
spans.push(Span::styled(key1, self.shortcut_key_style));
9074
spans.push(Span::raw(" "));
9175
spans.push(Span::styled(label, self.shortcut_label_style));
76+
spans.push(Span::raw(" "));
77+
spans.push(Span::styled(key2, self.shortcut_key_style));
78+
}
79+
None => {
80+
// Two-part shortcut: Try mnemonic highlighting first
81+
if label.contains(key1) {
82+
// Create mnemonic spans (key is part of the label)
83+
let first_char = key1.chars().next().unwrap_or('?'); // Use key1
84+
85+
if let Some(idx) = label.find(first_char) {
86+
// Ensure the key is not empty before slicing
87+
let key_len = key1.chars().count();
88+
if key_len > 0 && idx + key_len <= label.chars().count() {
89+
// Split the label around the key
90+
let before = label.chars().take(idx).collect::<String>();
91+
let highlight =
92+
label.chars().skip(idx).take(key_len).collect::<String>();
93+
let after = label.chars().skip(idx + key_len).collect::<String>();
94+
95+
spans.push(Span::styled(before, self.shortcut_label_style));
96+
spans.push(Span::styled(highlight, self.shortcut_key_style));
97+
spans.push(Span::styled(after, self.shortcut_label_style));
98+
} else {
99+
// Fallback if slicing indices are invalid (should be rare)
100+
spans.push(Span::styled(key1, self.shortcut_key_style));
101+
spans.push(Span::raw(" "));
102+
spans.push(Span::styled(label, self.shortcut_label_style));
103+
}
104+
} else {
105+
// Fallback to regular key + label if char not found
106+
spans.push(Span::styled(key1, self.shortcut_key_style));
107+
spans.push(Span::raw(" "));
108+
spans.push(Span::styled(label, self.shortcut_label_style));
109+
}
110+
} else {
111+
// Regular shortcut (key + label)
112+
spans.push(Span::styled(key1, self.shortcut_key_style));
113+
spans.push(Span::raw(" "));
114+
spans.push(Span::styled(label, self.shortcut_label_style));
115+
}
92116
}
93-
} else {
94-
// Regular shortcut (key + label)
95-
spans.push(Span::styled(key, self.shortcut_key_style));
96-
spans.push(Span::raw(" "));
97-
spans.push(Span::styled(label, self.shortcut_label_style));
98117
}
99118
}
100119

0 commit comments

Comments
 (0)