Skip to content

Commit

Permalink
implement fish like tab completion (ChrisTitusTech#767)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
3 people authored Nov 1, 2024
1 parent 58f3433 commit 5878f4d
Showing 1 changed file with 66 additions and 4 deletions.
70 changes: 66 additions & 4 deletions tui/src/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use ego_tree::NodeId;
use linutil_core::Tab;
use ratatui::{
layout::{Position, Rect},
style::Style,
style::{Color, Style},
text::Span,
widgets::{Block, Borders, Paragraph},
Frame,
Expand All @@ -22,6 +22,7 @@ pub struct Filter {
in_search_mode: bool,
input_position: usize,
items: Vec<ListEntry>,
completion_preview: Option<String>,
}

impl Filter {
Expand All @@ -31,17 +32,23 @@ impl Filter {
in_search_mode: false,
input_position: 0,
items: vec![],
completion_preview: None,
}
}

pub fn item_list(&self) -> &[ListEntry] {
&self.items
}

pub fn activate_search(&mut self) {
self.in_search_mode = true;
}

pub fn deactivate_search(&mut self) {
self.in_search_mode = false;
self.completion_preview = None;
}

pub fn update_items(&mut self, tabs: &[Tab], current_tab: usize, node: NodeId) {
if self.search_input.is_empty() {
let curr = tabs[current_tab].tree.get(node).unwrap();
Expand Down Expand Up @@ -78,13 +85,34 @@ impl Filter {
}
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) {
//Set the search bar text (If empty use the placeholder)
let display_text = if !self.in_search_mode && self.search_input.is_empty() {
Span::raw("Press / to search")
} 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 {
Expand All @@ -110,11 +138,22 @@ impl Filter {
let x = area.x + cursor_position as u16 + 1;
let y = area.y + 1;
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
pub fn handle_key(&mut self, event: &KeyEvent) -> SearchAction {
//Insert user input into the search bar
match event.code {
KeyCode::Char('c') if event.modifiers.contains(KeyModifiers::CONTROL) => {
return self.exit_search()
Expand All @@ -124,10 +163,17 @@ impl Filter {
KeyCode::Delete => self.remove_next(),
KeyCode::Left => return self.cursor_left(),
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::Esc => return self.exit_search(),
_ => return SearchAction::None,
};
self.update_completion_preview();
SearchAction::Update
}

Expand All @@ -141,29 +187,45 @@ impl Filter {
self.input_position = self.input_position.saturating_sub(1);
SearchAction::None
}

fn cursor_right(&mut self) -> SearchAction {
if self.input_position < self.search_input.len() {
self.input_position += 1;
}
SearchAction::None
}

fn insert_char(&mut self, input: char) {
self.search_input.insert(self.input_position, input);
self.cursor_right();
}

fn remove_previous(&mut self) {
let current = self.input_position;
if current > 0 {
self.search_input.remove(current - 1);
self.cursor_left();
}
}

fn remove_next(&mut self) {
let current = self.input_position;
if current < self.search_input.len() {
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) {
self.search_input.clear();
self.input_position = 0;
Expand Down

0 comments on commit 5878f4d

Please sign in to comment.