Skip to content

Commit 8a63427

Browse files
searchable menu
1 parent af946b9 commit 8a63427

File tree

1 file changed

+107
-25
lines changed

1 file changed

+107
-25
lines changed

src/components/menu.rs

Lines changed: 107 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::{
2+
borrow::BorrowMut,
23
collections::HashMap,
34
sync::{Arc, Mutex},
45
time::Duration,
@@ -46,6 +47,8 @@ pub struct Menu {
4647
schema_index: usize,
4748
list_state: ListState,
4849
menu_focus: MenuFocus,
50+
search: Option<String>,
51+
search_focused: bool,
4952
}
5053

5154
impl Menu {
@@ -57,6 +60,8 @@ impl Menu {
5760
schema_index: 0,
5861
list_state: ListState::default(),
5962
menu_focus: MenuFocus::default(),
63+
search: None,
64+
search_focused: false,
6065
}
6166
}
6267

@@ -124,6 +129,12 @@ impl Menu {
124129
MenuFocus::Schema => self.schema_index = 0,
125130
}
126131
}
132+
133+
pub fn reset_search(&mut self) {
134+
self.search = None;
135+
self.search_focused = false;
136+
self.list_state = ListState::default().with_selected(Some(0));
137+
}
127138
}
128139

129140
impl<'a> SettableTableList<'a> for Menu {
@@ -174,18 +185,53 @@ impl Component for Menu {
174185
}
175186
if let Some(Event::Key(key)) = event {
176187
match key.code {
177-
KeyCode::Right | KeyCode::Char('l') => self.change_focus(MenuFocus::Table),
178-
KeyCode::Left | KeyCode::Char('h') => self.change_focus(MenuFocus::Schema),
179-
KeyCode::Down | KeyCode::Char('j') => self.scroll_down(),
180-
KeyCode::Up | KeyCode::Char('k') => self.scroll_up(),
181-
KeyCode::Char('g') => self.scroll_top(),
182-
KeyCode::Char('G') => self.scroll_bottom(),
188+
KeyCode::Right => self.change_focus(MenuFocus::Table),
189+
KeyCode::Left => self.change_focus(MenuFocus::Schema),
190+
KeyCode::Down => self.scroll_down(),
191+
KeyCode::Up => self.scroll_up(),
192+
KeyCode::Char(c) => {
193+
if self.search.is_some() && self.search_focused {
194+
if let Some(search) = self.search.as_mut() {
195+
search.push(c);
196+
self.list_state = ListState::default().with_selected(Some(0));
197+
}
198+
} else {
199+
match key.code {
200+
KeyCode::Char('/') => {
201+
self.search_focused = true;
202+
if self.search.is_none() {
203+
self.search = Some("".to_owned())
204+
}
205+
},
206+
KeyCode::Char('l') => self.change_focus(MenuFocus::Table),
207+
KeyCode::Char('h') => self.change_focus(MenuFocus::Schema),
208+
KeyCode::Char('j') => self.scroll_down(),
209+
KeyCode::Char('k') => self.scroll_up(),
210+
KeyCode::Char('g') => self.scroll_top(),
211+
KeyCode::Char('G') => self.scroll_bottom(),
212+
_ => {},
213+
}
214+
}
215+
},
183216
KeyCode::Enter => {
184-
if let Some(selected) = self.list_state.selected() {
217+
if self.search.is_some() && self.search_focused {
218+
self.search_focused = false;
219+
} else if let Some(selected) = self.list_state.selected() {
185220
let (schema, tables) = self.table_map.get_index(self.schema_index).unwrap();
186221
self.command_tx.as_ref().unwrap().send(Action::MenuSelect(schema.clone(), tables[selected].clone()))?;
187222
}
188223
},
224+
KeyCode::Esc => self.reset_search(),
225+
KeyCode::Backspace => {
226+
if let Some(search) = self.search.as_mut() {
227+
if !search.is_empty() {
228+
search.pop();
229+
self.list_state = ListState::default().with_selected(Some(0));
230+
} else {
231+
self.reset_search();
232+
}
233+
}
234+
},
189235
_ => {},
190236
}
191237
};
@@ -196,34 +242,70 @@ impl Component for Menu {
196242
let focused = app_state.focus == Focus::Menu;
197243
let parent_block = Block::default();
198244
let stable_keys = self.table_map.keys().enumerate();
199-
let constraints = stable_keys.clone().map(|(i, k)| {
200-
match i {
201-
x if x == self.schema_index => Constraint::Min(5),
202-
_ => Constraint::Length(1),
203-
}
204-
});
245+
let mut constraints: Vec<Constraint> = stable_keys
246+
.clone()
247+
.map(|(i, k)| {
248+
match i {
249+
x if x == self.schema_index => Constraint::Min(5),
250+
_ => Constraint::Length(1),
251+
}
252+
})
253+
.collect();
254+
if let Some(search) = self.search.as_ref() {
255+
constraints.insert(0, Constraint::Length(1));
256+
}
205257
let layout =
206258
Layout::default().constraints(constraints).direction(Direction::Vertical).split(parent_block.inner(area));
259+
if let Some(search) = self.search.as_ref() {
260+
f.render_widget(
261+
Text::styled(
262+
"/ ".to_owned() + search.to_owned().as_str(),
263+
if !focused {
264+
Style::new().dim()
265+
} else if self.search_focused {
266+
Style::default().fg(Color::Yellow)
267+
} else {
268+
Style::default()
269+
},
270+
),
271+
layout[0],
272+
)
273+
}
207274
stable_keys.for_each(|(i, k)| {
275+
let layout_index = if self.search.is_some() { i + 1 } else { i };
208276
match i {
209277
x if x == self.schema_index => {
210-
let block = Block::default().title(k.as_str().to_owned() + "(schema)").borders(Borders::ALL).border_style(
211-
if focused && self.menu_focus == MenuFocus::Schema {
278+
let block = Block::default()
279+
.title(k.as_str().to_owned() + "(schema)")
280+
.borders(Borders::ALL)
281+
.border_style(if focused && self.menu_focus == MenuFocus::Schema {
212282
Style::default().fg(Color::Green)
213283
} else if focused {
214284
Style::default()
215285
} else {
216286
Style::new().dim()
217-
},
218-
);
219-
let block_margin = layout[i].inner(Margin { vertical: 1, horizontal: 0 });
287+
})
288+
.padding(Padding { left: 0, right: 1, top: 0, bottom: 0 });
289+
let block_margin = layout[layout_index].inner(Margin { vertical: 1, horizontal: 0 });
220290
let tables = self.table_map.get_key_value(k).unwrap().1.clone();
221-
let table_length = tables.len();
291+
let filtered_tables: Vec<String> = tables
292+
.into_iter()
293+
.filter(|t| {
294+
if let Some(search) = self.search.as_ref() {
295+
t.to_lowercase().contains(search.to_lowercase().trim())
296+
} else {
297+
true
298+
}
299+
})
300+
.collect();
301+
let table_length = filtered_tables.len();
222302
let available_height = block.inner(parent_block.inner(area)).height as usize;
223-
let list = List::default().items(tables).block(block).highlight_style(
224-
Style::default().bg(if focused { Color::Green } else { Color::White }).fg(Color::DarkGray),
303+
let list = List::default().items(filtered_tables).block(block).highlight_style(
304+
Style::default()
305+
.bg(if focused && !self.search_focused { Color::Green } else { Color::White })
306+
.fg(Color::DarkGray),
225307
);
226-
f.render_stateful_widget(list, layout[i], &mut self.list_state);
308+
f.render_stateful_widget(list, layout[layout_index], &mut self.list_state);
227309
let vertical_scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight).symbols(scrollbar::VERTICAL);
228310
let mut vertical_scrollbar_state =
229311
ScrollbarState::new(table_length.saturating_sub(available_height)).position(self.list_state.offset());
@@ -235,7 +317,7 @@ impl Component for Menu {
235317
"└".to_owned() + k.to_owned().as_str() + "(schema)",
236318
if focused { Style::default() } else { Style::new().dim() },
237319
),
238-
layout[i],
320+
layout[layout_index],
239321
);
240322
},
241323
0 => {
@@ -244,7 +326,7 @@ impl Component for Menu {
244326
"┌".to_owned() + k.to_owned().as_str() + "(schema)",
245327
if focused { Style::default() } else { Style::new().dim() },
246328
),
247-
layout[i],
329+
layout[layout_index],
248330
);
249331
},
250332
_ => {
@@ -253,7 +335,7 @@ impl Component for Menu {
253335
"├".to_owned() + k.to_owned().as_str() + "(schema)",
254336
if focused { Style::default() } else { Style::new().dim() },
255337
),
256-
layout[i],
338+
layout[layout_index],
257339
)
258340
},
259341
};

0 commit comments

Comments
 (0)