mirror of
https://github.com/ChrisTitusTech/linutil.git
synced 2024-11-05 13:15:21 +00:00
implement fish like tab completion (#767)
* implement fish like tab completion * grey the pv out and convert to lowercase * run fmt * do not tabcomplete if the user hits enter * fix * fix lints * run docgen --------- Co-authored-by: nyx <nnyyxxxx@users.noreply.github.com> Co-authored-by: Adam Perkowski <adas1per@protonmail.com>
This commit is contained in:
parent
58f3433de6
commit
5878f4dbf0
|
@ -4,7 +4,7 @@ use ego_tree::NodeId;
|
||||||
use linutil_core::Tab;
|
use linutil_core::Tab;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Position, Rect},
|
layout::{Position, Rect},
|
||||||
style::Style,
|
style::{Color, Style},
|
||||||
text::Span,
|
text::Span,
|
||||||
widgets::{Block, Borders, Paragraph},
|
widgets::{Block, Borders, Paragraph},
|
||||||
Frame,
|
Frame,
|
||||||
|
@ -22,6 +22,7 @@ pub struct Filter {
|
||||||
in_search_mode: bool,
|
in_search_mode: bool,
|
||||||
input_position: usize,
|
input_position: usize,
|
||||||
items: Vec<ListEntry>,
|
items: Vec<ListEntry>,
|
||||||
|
completion_preview: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Filter {
|
impl Filter {
|
||||||
|
@ -31,17 +32,23 @@ impl Filter {
|
||||||
in_search_mode: false,
|
in_search_mode: false,
|
||||||
input_position: 0,
|
input_position: 0,
|
||||||
items: vec![],
|
items: vec![],
|
||||||
|
completion_preview: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn item_list(&self) -> &[ListEntry] {
|
pub fn item_list(&self) -> &[ListEntry] {
|
||||||
&self.items
|
&self.items
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn activate_search(&mut self) {
|
pub fn activate_search(&mut self) {
|
||||||
self.in_search_mode = true;
|
self.in_search_mode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deactivate_search(&mut self) {
|
pub fn deactivate_search(&mut self) {
|
||||||
self.in_search_mode = false;
|
self.in_search_mode = false;
|
||||||
|
self.completion_preview = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_items(&mut self, tabs: &[Tab], current_tab: usize, node: NodeId) {
|
pub fn update_items(&mut self, tabs: &[Tab], current_tab: usize, node: NodeId) {
|
||||||
if self.search_input.is_empty() {
|
if self.search_input.is_empty() {
|
||||||
let curr = tabs[current_tab].tree.get(node).unwrap();
|
let curr = tabs[current_tab].tree.get(node).unwrap();
|
||||||
|
@ -78,13 +85,34 @@ impl Filter {
|
||||||
}
|
}
|
||||||
self.items.sort_by(|a, b| a.node.name.cmp(&b.node.name));
|
self.items.sort_by(|a, b| a.node.name.cmp(&b.node.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.update_completion_preview();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_completion_preview(&mut self) {
|
||||||
|
if self.search_input.is_empty() {
|
||||||
|
self.completion_preview = None;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let input = self.search_input.iter().collect::<String>().to_lowercase();
|
||||||
|
self.completion_preview = self.items.iter().find_map(|item| {
|
||||||
|
let item_name_lower = item.node.name.to_lowercase();
|
||||||
|
if item_name_lower.starts_with(&input) {
|
||||||
|
Some(item_name_lower[input.len()..].to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn draw_searchbar(&self, frame: &mut Frame, area: Rect, theme: &Theme) {
|
pub fn draw_searchbar(&self, frame: &mut Frame, area: Rect, theme: &Theme) {
|
||||||
//Set the search bar text (If empty use the placeholder)
|
//Set the search bar text (If empty use the placeholder)
|
||||||
let display_text = if !self.in_search_mode && self.search_input.is_empty() {
|
let display_text = if !self.in_search_mode && self.search_input.is_empty() {
|
||||||
Span::raw("Press / to search")
|
Span::raw("Press / to search")
|
||||||
} else {
|
} else {
|
||||||
Span::raw(self.search_input.iter().collect::<String>())
|
let input_text = self.search_input.iter().collect::<String>();
|
||||||
|
Span::styled(input_text, Style::default().fg(theme.focused_color()))
|
||||||
};
|
};
|
||||||
|
|
||||||
let search_color = if self.in_search_mode {
|
let search_color = if self.in_search_mode {
|
||||||
|
@ -110,11 +138,22 @@ impl Filter {
|
||||||
let x = area.x + cursor_position as u16 + 1;
|
let x = area.x + cursor_position as u16 + 1;
|
||||||
let y = area.y + 1;
|
let y = area.y + 1;
|
||||||
frame.set_cursor_position(Position::new(x, y));
|
frame.set_cursor_position(Position::new(x, y));
|
||||||
|
|
||||||
|
if let Some(preview) = &self.completion_preview {
|
||||||
|
let preview_span = Span::styled(preview, Style::default().fg(Color::DarkGray));
|
||||||
|
let preview_paragraph = Paragraph::new(preview_span).style(Style::default());
|
||||||
|
let preview_area = Rect::new(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
(preview.len() as u16).min(area.width - cursor_position as u16 - 1),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
frame.render_widget(preview_paragraph, preview_area);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Handles key events. Returns true if search must be exited
|
// Handles key events. Returns true if search must be exited
|
||||||
pub fn handle_key(&mut self, event: &KeyEvent) -> SearchAction {
|
pub fn handle_key(&mut self, event: &KeyEvent) -> SearchAction {
|
||||||
//Insert user input into the search bar
|
|
||||||
match event.code {
|
match event.code {
|
||||||
KeyCode::Char('c') if event.modifiers.contains(KeyModifiers::CONTROL) => {
|
KeyCode::Char('c') if event.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||||
return self.exit_search()
|
return self.exit_search()
|
||||||
|
@ -124,10 +163,17 @@ impl Filter {
|
||||||
KeyCode::Delete => self.remove_next(),
|
KeyCode::Delete => self.remove_next(),
|
||||||
KeyCode::Left => return self.cursor_left(),
|
KeyCode::Left => return self.cursor_left(),
|
||||||
KeyCode::Right => return self.cursor_right(),
|
KeyCode::Right => return self.cursor_right(),
|
||||||
|
KeyCode::Tab => return self.complete_search(),
|
||||||
|
KeyCode::Esc => {
|
||||||
|
self.input_position = 0;
|
||||||
|
self.search_input.clear();
|
||||||
|
self.completion_preview = None;
|
||||||
|
return SearchAction::Exit;
|
||||||
|
}
|
||||||
KeyCode::Enter => return SearchAction::Exit,
|
KeyCode::Enter => return SearchAction::Exit,
|
||||||
KeyCode::Esc => return self.exit_search(),
|
|
||||||
_ => return SearchAction::None,
|
_ => return SearchAction::None,
|
||||||
};
|
};
|
||||||
|
self.update_completion_preview();
|
||||||
SearchAction::Update
|
SearchAction::Update
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,16 +187,19 @@ impl Filter {
|
||||||
self.input_position = self.input_position.saturating_sub(1);
|
self.input_position = self.input_position.saturating_sub(1);
|
||||||
SearchAction::None
|
SearchAction::None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cursor_right(&mut self) -> SearchAction {
|
fn cursor_right(&mut self) -> SearchAction {
|
||||||
if self.input_position < self.search_input.len() {
|
if self.input_position < self.search_input.len() {
|
||||||
self.input_position += 1;
|
self.input_position += 1;
|
||||||
}
|
}
|
||||||
SearchAction::None
|
SearchAction::None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_char(&mut self, input: char) {
|
fn insert_char(&mut self, input: char) {
|
||||||
self.search_input.insert(self.input_position, input);
|
self.search_input.insert(self.input_position, input);
|
||||||
self.cursor_right();
|
self.cursor_right();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_previous(&mut self) {
|
fn remove_previous(&mut self) {
|
||||||
let current = self.input_position;
|
let current = self.input_position;
|
||||||
if current > 0 {
|
if current > 0 {
|
||||||
|
@ -158,12 +207,25 @@ impl Filter {
|
||||||
self.cursor_left();
|
self.cursor_left();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_next(&mut self) {
|
fn remove_next(&mut self) {
|
||||||
let current = self.input_position;
|
let current = self.input_position;
|
||||||
if current < self.search_input.len() {
|
if current < self.search_input.len() {
|
||||||
self.search_input.remove(current);
|
self.search_input.remove(current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn complete_search(&mut self) -> SearchAction {
|
||||||
|
if let Some(completion) = self.completion_preview.take() {
|
||||||
|
self.search_input.extend(completion.chars());
|
||||||
|
self.input_position = self.search_input.len();
|
||||||
|
self.update_completion_preview();
|
||||||
|
SearchAction::Update
|
||||||
|
} else {
|
||||||
|
SearchAction::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clear_search(&mut self) {
|
pub fn clear_search(&mut self) {
|
||||||
self.search_input.clear();
|
self.search_input.clear();
|
||||||
self.input_position = 0;
|
self.input_position = 0;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user