From 0b85e1fc95591563c7ba4d9898da4e8ee71bd35c Mon Sep 17 00:00:00 2001 From: XOR-op <17672363+XOR-op@users.noreply.github.com> Date: Thu, 26 Oct 2023 16:05:30 -0400 Subject: [PATCH] WIP(completion): code skeleton --- yazi-core/src/completion/completion.rs | 47 ++++++++++++++++++++++++++ yazi-core/src/completion/mod.rs | 5 +++ yazi-core/src/completion/option.rs | 26 ++++++++++++++ yazi-core/src/input/completion.rs | 20 +++++++++++ yazi-core/src/input/input.rs | 14 ++++++-- yazi-core/src/input/mod.rs | 1 + yazi-core/src/input/option.rs | 36 ++++++++++++-------- yazi-core/src/lib.rs | 1 + yazi-fm/src/completion/completion.rs | 43 +++++++++++++++++++++++ yazi-fm/src/completion/mod.rs | 2 ++ yazi-fm/src/main.rs | 1 + yazi-fm/src/root.rs | 6 +++- 12 files changed, 183 insertions(+), 19 deletions(-) create mode 100644 yazi-core/src/completion/completion.rs create mode 100644 yazi-core/src/completion/mod.rs create mode 100644 yazi-core/src/completion/option.rs create mode 100644 yazi-core/src/input/completion.rs create mode 100644 yazi-fm/src/completion/completion.rs create mode 100644 yazi-fm/src/completion/mod.rs diff --git a/yazi-core/src/completion/completion.rs b/yazi-core/src/completion/completion.rs new file mode 100644 index 00000000..49147664 --- /dev/null +++ b/yazi-core/src/completion/completion.rs @@ -0,0 +1,47 @@ +use crate::{completion::CompletionOpt, Position}; + +#[derive(Default)] +pub struct Completion { + items: Vec, + cursor: usize, + + pub position: Position, + pub visible: bool, +} + +impl Completion { + pub fn show(&mut self, opt: CompletionOpt) { + self.close(false); + self.visible = true; + + self.items = opt.items; + self.position = opt.position; + } + + pub fn close(&mut self, submit: bool) -> bool { + self.cursor = 0; + self.visible = false; + true + } + + pub fn next(&mut self, step: usize) -> bool { + let len = self.items.len(); + if len == 0 { + return false; + } + + let old = self.cursor; + self.cursor = (self.cursor + step).min(len - 1); + + old != self.cursor + } + + pub fn prev(&mut self, step: usize) -> bool { + let old = self.cursor; + self.cursor = self.cursor.saturating_sub(step); + + old != self.cursor + } + + pub fn list(&self) -> Vec { self.items.clone() } +} diff --git a/yazi-core/src/completion/mod.rs b/yazi-core/src/completion/mod.rs new file mode 100644 index 00000000..434f45ed --- /dev/null +++ b/yazi-core/src/completion/mod.rs @@ -0,0 +1,5 @@ +mod completion; +mod option; + +pub(super) use completion::Completion; +pub use option::CompletionOpt; diff --git a/yazi-core/src/completion/option.rs b/yazi-core/src/completion/option.rs new file mode 100644 index 00000000..94e16285 --- /dev/null +++ b/yazi-core/src/completion/option.rs @@ -0,0 +1,26 @@ +use ratatui::layout::Rect; + +use crate::Position; + +pub struct CompletionOpt { + pub items: Vec, + pub position: Position, +} + +impl CompletionOpt { + pub fn hovered() -> Self { + Self { + items: vec![], + position: Position::Hovered( + // TODO: hardcode + Rect { x: 0, y: 1, width: 50, height: 3 }, + ), + } + } + + #[inline] + pub fn with_items(mut self, items: Vec) -> Self { + self.items = items; + self + } +} diff --git a/yazi-core/src/input/completion.rs b/yazi-core/src/input/completion.rs new file mode 100644 index 00000000..237c86db --- /dev/null +++ b/yazi-core/src/input/completion.rs @@ -0,0 +1,20 @@ +use crate::{completion::CompletionOpt, input::Input}; + +impl Input { + pub fn complete(&mut self) -> bool { + if !self.completion.visible { + let current = self.snaps.current().value.clone(); + let result = self.completion_callback.as_ref().map(|f| f(current)).unwrap_or_default(); + match result.len() { + 0 => false, + 1 => self.type_str(result.get(0).unwrap()), + _ => { + self.completion.show(CompletionOpt::hovered().with_items(result)); + false + } + } + } else { + self.completion.next(1) + } + } +} diff --git a/yazi-core/src/input/input.rs b/yazi-core/src/input/input.rs index 649218a2..e895fc8a 100644 --- a/yazi-core/src/input/input.rs +++ b/yazi-core/src/input/input.rs @@ -7,12 +7,12 @@ use yazi_config::keymap::Key; use yazi_shared::{CharKind, InputError}; use super::{mode::InputMode, op::InputOp, InputOpt, InputSnap, InputSnaps}; -use crate::{external, Position}; +use crate::{completion::Completion, external, Position}; #[derive(Default)] pub struct Input { - snaps: InputSnaps, - pub visible: bool, + pub(super) snaps: InputSnaps, + pub visible: bool, title: String, pub position: Position, @@ -23,6 +23,9 @@ pub struct Input { // Shell pub(super) highlight: bool, + + pub completion: Completion, + pub(super) completion_callback: Option Vec + Send>>, } impl Input { @@ -189,6 +192,11 @@ impl Input { return false; } + match key { + Key { code: KeyCode::Tab, shift: false, ctrl: false, alt: false } => return self.complete(), + _ => (), + } + if let Some(c) = key.plain() { return self.type_char(c); } diff --git a/yazi-core/src/input/mod.rs b/yazi-core/src/input/mod.rs index 9fa10f1a..457945c6 100644 --- a/yazi-core/src/input/mod.rs +++ b/yazi-core/src/input/mod.rs @@ -1,3 +1,4 @@ +mod completion; mod input; mod mode; mod op; diff --git a/yazi-core/src/input/option.rs b/yazi-core/src/input/option.rs index dd96c068..d615b160 100644 --- a/yazi-core/src/input/option.rs +++ b/yazi-core/src/input/option.rs @@ -3,34 +3,40 @@ use ratatui::prelude::Rect; use crate::Position; pub struct InputOpt { - pub title: String, - pub value: String, - pub position: Position, - pub realtime: bool, - pub highlight: bool, + pub title: String, + pub value: String, + pub position: Position, + pub realtime: bool, + pub highlight: bool, + pub completion_callback: Option Vec + Send>>, } impl InputOpt { pub fn top(title: impl AsRef) -> Self { Self { - title: title.as_ref().to_owned(), - value: String::new(), - position: Position::Top(/* TODO: hardcode */ Rect { x: 0, y: 2, width: 50, height: 3 }), - realtime: false, - highlight: false, + title: title.as_ref().to_owned(), + value: String::new(), + position: Position::Top( + // TODO: hardcode + Rect { x: 0, y: 2, width: 50, height: 3 }, + ), + realtime: false, + highlight: false, + completion_callback: None, } } pub fn hovered(title: impl AsRef) -> Self { Self { - title: title.as_ref().to_owned(), - value: String::new(), - position: Position::Hovered( + title: title.as_ref().to_owned(), + value: String::new(), + position: Position::Hovered( // TODO: hardcode Rect { x: 0, y: 1, width: 50, height: 3 }, ), - realtime: false, - highlight: false, + realtime: false, + highlight: false, + completion_callback: None, } } diff --git a/yazi-core/src/lib.rs b/yazi-core/src/lib.rs index 67db2adf..044a3d2c 100644 --- a/yazi-core/src/lib.rs +++ b/yazi-core/src/lib.rs @@ -7,6 +7,7 @@ )] mod blocker; +pub mod completion; mod context; mod event; pub mod external; diff --git a/yazi-fm/src/completion/completion.rs b/yazi-fm/src/completion/completion.rs new file mode 100644 index 00000000..80838cee --- /dev/null +++ b/yazi-fm/src/completion/completion.rs @@ -0,0 +1,43 @@ +use std::mem; + +use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, Borders, Clear, Row, Table, Widget}}; +use yazi_config::THEME; +use yazi_core::Ctx; + +pub(crate) struct Completion<'a> { + cx: &'a Ctx, +} + +impl<'a> Completion<'a> { + pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } } +} + +impl<'a> Widget for Completion<'a> { + fn render(self, _: Rect, buf: &mut Buffer) { + let completion = &self.cx.input.completion; + let area = self.cx.area(&completion.position); + + let table = { + let mut table = vec![]; + let mut cur_row = vec![]; + const COLUMN_CNT: usize = 4; + for (idx, s) in completion.list().into_iter().enumerate() { + if idx != 0 && idx % COLUMN_CNT == 0 { + let t = mem::take(&mut cur_row); + table.push(Row::new(t)); + } + cur_row.push(s); + } + Table::new(table).block( + Block::new() + .borders(Borders::ALL) + .border_type(BorderType::Rounded) + // todo + .border_style(THEME.select.border.into()), + ) + }; + + Clear.render(area, buf); + table.render(area, buf); + } +} diff --git a/yazi-fm/src/completion/mod.rs b/yazi-fm/src/completion/mod.rs new file mode 100644 index 00000000..d222e39f --- /dev/null +++ b/yazi-fm/src/completion/mod.rs @@ -0,0 +1,2 @@ +mod completion; +pub(super) use completion::*; diff --git a/yazi-fm/src/main.rs b/yazi-fm/src/main.rs index f54e69c9..3690dff0 100644 --- a/yazi-fm/src/main.rs +++ b/yazi-fm/src/main.rs @@ -1,6 +1,7 @@ #![allow(clippy::module_inception)] mod app; +mod completion; mod executor; mod help; mod input; diff --git a/yazi-fm/src/root.rs b/yazi-fm/src/root.rs index bacae721..5bf3021a 100644 --- a/yazi-fm/src/root.rs +++ b/yazi-fm/src/root.rs @@ -2,7 +2,7 @@ use ratatui::{buffer::Buffer, layout::{Constraint, Direction, Layout, Rect}, wid use yazi_core::Ctx; use yazi_plugin::components; -use super::{input, select, tasks, which}; +use super::{completion, input, select, tasks, which}; use crate::help; pub(super) struct Root<'a> { @@ -36,6 +36,10 @@ impl<'a> Widget for Root<'a> { input::Input::new(self.cx).render(area, buf); } + if self.cx.input.completion.visible { + completion::Completion::new(self.cx).render(area, buf); + } + if self.cx.help.visible { help::Layout::new(self.cx).render(area, buf); }