.. [WIP] [no ci]

This commit is contained in:
sxyazi 2023-11-01 18:11:47 +08:00
parent 9a67c0150e
commit 802aa86e23
No known key found for this signature in database
11 changed files with 137 additions and 246 deletions

View file

@ -237,3 +237,18 @@ keymap = [
# Filtering
{ on = [ "/" ], exec = "filter", desc = "Apply a filter for the help items" },
]
[completion]
keymap = [
{ on = [ "<C-q>" ], exec = "close", desc = "Cancel completion" },
{ on = [ "<Enter>" ], exec = "close --submit", desc = "Submit the completion" },
{ on = [ "k" ], exec = "arrow -1", desc = "Move cursor up" },
{ on = [ "j" ], exec = "arrow 1", desc = "Move cursor down" },
{ on = [ "<Up>" ], exec = "arrow -1", desc = "Move cursor up" },
{ on = [ "<Down>" ], exec = "arrow 1", desc = "Move cursor down" },
{ on = [ "~" ], exec = "help", desc = "Open help" }
]

View file

@ -7,11 +7,12 @@ use crate::MERGED_KEYMAP;
#[derive(Debug)]
pub struct Keymap {
pub manager: Vec<Control>,
pub tasks: Vec<Control>,
pub select: Vec<Control>,
pub input: Vec<Control>,
pub help: Vec<Control>,
pub manager: Vec<Control>,
pub tasks: Vec<Control>,
pub select: Vec<Control>,
pub input: Vec<Control>,
pub help: Vec<Control>,
pub completion: Vec<Control>,
}
impl<'de> Deserialize<'de> for Keymap {
@ -21,11 +22,12 @@ impl<'de> Deserialize<'de> for Keymap {
{
#[derive(Deserialize)]
struct Shadow {
manager: Inner,
tasks: Inner,
select: Inner,
input: Inner,
help: Inner,
manager: Inner,
tasks: Inner,
select: Inner,
input: Inner,
help: Inner,
completion: Inner,
}
#[derive(Deserialize)]
struct Inner {
@ -34,11 +36,12 @@ impl<'de> Deserialize<'de> for Keymap {
let shadow = Shadow::deserialize(deserializer)?;
Ok(Self {
manager: shadow.manager.keymap,
tasks: shadow.tasks.keymap,
select: shadow.select.keymap,
input: shadow.input.keymap,
help: shadow.help.keymap,
manager: shadow.manager.keymap,
tasks: shadow.tasks.keymap,
select: shadow.select.keymap,
input: shadow.input.keymap,
help: shadow.help.keymap,
completion: shadow.completion.keymap,
})
}
}
@ -56,6 +59,7 @@ impl Keymap {
KeymapLayer::Select => &self.select,
KeymapLayer::Input => &self.input,
KeymapLayer::Help => &self.help,
KeymapLayer::Completion => &self.completion,
KeymapLayer::Which => unreachable!(),
}
}
@ -69,6 +73,7 @@ pub enum KeymapLayer {
Select,
Input,
Help,
Completion,
Which,
}
@ -80,6 +85,7 @@ impl Display for KeymapLayer {
KeymapLayer::Select => write!(f, "select"),
KeymapLayer::Input => write!(f, "input"),
KeymapLayer::Help => write!(f, "help"),
KeymapLayer::Completion => write!(f, "completion"),
KeymapLayer::Which => write!(f, "which"),
}
}

View file

@ -3,26 +3,28 @@ use ratatui::prelude::Rect;
use yazi_config::keymap::KeymapLayer;
use yazi_shared::Term;
use crate::{help::Help, input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which, Position};
use crate::{completion::Completion, help::Help, input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which, Position};
pub struct Ctx {
pub manager: Manager,
pub which: Which,
pub help: Help,
pub input: Input,
pub select: Select,
pub tasks: Tasks,
pub manager: Manager,
pub tasks: Tasks,
pub select: Select,
pub input: Input,
pub help: Help,
pub completion: Completion,
pub which: Which,
}
impl Ctx {
pub fn make() -> Self {
Self {
manager: Manager::make(),
which: Default::default(),
help: Default::default(),
input: Default::default(),
select: Default::default(),
tasks: Tasks::start(),
manager: Manager::make(),
tasks: Tasks::start(),
select: Default::default(),
input: Default::default(),
help: Default::default(),
completion: Default::default(),
which: Default::default(),
}
}
@ -72,6 +74,8 @@ impl Ctx {
pub fn layer(&self) -> KeymapLayer {
if self.which.visible {
KeymapLayer::Which
} else if self.completion.visible {
KeymapLayer::Completion
} else if self.help.visible {
KeymapLayer::Help
} else if self.input.visible {

View file

@ -1,61 +0,0 @@
use crossterm::event::KeyCode;
use yazi_config::keymap::{Exec, Key, KeymapLayer};
use crate::{completion::CompletionOpt, emit, input::Input};
impl Input {
pub fn complete(&mut self) -> bool {
if !self.completion.visible {
if let Some(f) = self.init_completion.as_ref() {
let future = f(self.snaps.current().value.clone());
let id = self.completion.identifier.clone();
tokio::spawn(async move {
let result = future.await;
let mut exec = Exec::call("complete", result);
exec.named.insert("identifier".to_string(), id);
emit!(Call(exec.vec(), KeymapLayer::Input));
});
}
}
false
}
pub fn fill_completion(&mut self, exec: &Exec) -> bool {
if !exec.named.get("identifier").is_some_and(|id| *id == self.completion.identifier) {
return false;
};
match exec.args.len() {
0 => false,
1 => {
if let Some(f) = self.finish_completion.as_ref() {
self
.replace_str(f(self.snaps.current().value.as_str(), exec.args.get(0).unwrap()).as_str())
} else {
false
}
}
_ => {
self.completion.show(CompletionOpt::top().with_items(exec.args.clone()));
true
}
}
}
pub fn navigate_completion(&mut self, key: &Key) -> bool {
match key.code {
KeyCode::Up => self.completion.prev(self.completion.column_cnt as usize),
KeyCode::Down => self.completion.next(self.completion.column_cnt as usize),
KeyCode::Left => self.completion.prev(1),
KeyCode::Right | KeyCode::Tab => self.completion.next(1),
_ => false,
}
}
pub fn finish_completion(&mut self) -> bool {
if let (Some(val), Some(f)) = (self.completion.selected(), &self.finish_completion) {
let final_val = f(self.snaps.current().value.as_str(), val.as_str());
self.replace_str(final_val.as_str());
}
self.completion.close()
}
}

View file

@ -6,8 +6,8 @@ use unicode_width::UnicodeWidthStr;
use yazi_config::keymap::Key;
use yazi_shared::{CharKind, InputError};
use super::{mode::InputMode, op::InputOp, FinishCompletionType, InitCompletionType, InputOpt, InputSnap, InputSnaps};
use crate::{completion::Completion, external, Position};
use super::{mode::InputMode, op::InputOp, InputOpt, InputSnap, InputSnaps};
use crate::{external, Position};
#[derive(Default)]
pub struct Input {
@ -23,10 +23,6 @@ pub struct Input {
// Shell
pub(super) highlight: bool,
pub completion: Completion,
pub(super) init_completion: Option<InitCompletionType>,
pub(super) finish_completion: Option<FinishCompletionType>,
}
impl Input {
@ -44,9 +40,6 @@ impl Input {
// Shell
self.highlight = opt.highlight;
self.init_completion = opt.init_completion;
self.finish_completion = opt.finish_completion;
}
pub fn close(&mut self, submit: bool) -> bool {
@ -56,7 +49,6 @@ impl Input {
}
self.visible = false;
self.completion.close();
true
}
@ -197,25 +189,6 @@ impl Input {
return false;
}
if self.completion.visible {
match key {
Key {
code: KeyCode::Tab | KeyCode::Up | KeyCode::Down | KeyCode::Left | KeyCode::Right,
shift: false,
ctrl: false,
alt: false,
} => return self.navigate_completion(key),
Key { code: KeyCode::Enter, shift: false, ctrl: false, alt: false } => {
return self.finish_completion();
}
_ => {
self.completion.close();
}
}
} else if matches!(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);
}
@ -244,13 +217,6 @@ impl Input {
self.move_(s.chars().count() as isize)
}
pub fn replace_str(&mut self, s: &str) -> bool {
let snap = self.snaps.current_mut();
snap.value = s.to_string();
self.flush_value();
self.move_(s.chars().count() as isize)
}
pub fn backspace(&mut self) -> bool {
let snap = self.snaps.current_mut();
if snap.cursor < 1 {

View file

@ -1,4 +1,3 @@
mod completion;
mod input;
mod mode;
mod op;

View file

@ -1,51 +1,39 @@
use std::{future::Future, pin::Pin};
use ratatui::prelude::Rect;
use crate::Position;
pub type InitCompletionType =
Box<dyn Fn(String) -> Pin<Box<dyn Future<Output = Vec<String>> + Send>> + Send>;
pub type FinishCompletionType = Box<dyn Fn(&str, &str) -> String + Send>;
pub struct InputOpt {
pub title: String,
pub value: String,
pub position: Position,
pub realtime: bool,
pub highlight: bool,
pub init_completion: Option<InitCompletionType>,
pub finish_completion: Option<FinishCompletionType>,
pub title: String,
pub value: String,
pub position: Position,
pub realtime: bool,
pub highlight: bool,
}
impl InputOpt {
pub fn top(title: impl AsRef<str>) -> Self {
Self {
title: title.as_ref().to_owned(),
value: String::new(),
position: Position::Top(
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,
init_completion: None,
finish_completion: None,
realtime: false,
highlight: false,
}
}
pub fn hovered(title: impl AsRef<str>) -> 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,
init_completion: None,
finish_completion: None,
realtime: false,
highlight: false,
}
}
@ -68,16 +56,5 @@ impl InputOpt {
}
#[inline]
pub fn with_completion<
F1: Fn(String) -> Pin<Box<dyn Future<Output = Vec<String>> + Send>> + Send + 'static,
F2: Fn(&str, &str) -> String + Send + 'static,
>(
mut self,
init_completion: F1,
finish_completion: F2,
) -> Self {
self.init_completion = Some(Box::new(init_completion));
self.finish_completion = Some(Box::new(finish_completion));
self
}
pub fn with_completion(mut self) -> Self { todo!() }
}

View file

@ -59,38 +59,8 @@ impl Tab {
pub fn cd_interactive(&mut self, target: Url) -> bool {
tokio::spawn(async move {
let mut result = emit!(Input(
InputOpt::top("Change directory:").with_value(target.to_string_lossy()).with_completion(
|prefix| {
Box::pin(async move {
let mut cmp_prefix = prefix.as_str();
let mut result = vec![];
if let Ok(mut list) = if prefix.contains('/') {
let (old_prefix, old_file_prefix) = prefix.rsplit_once('/').unwrap();
cmp_prefix = old_file_prefix;
tokio::fs::read_dir(old_prefix.to_string() + "/").await
} else {
tokio::fs::read_dir(".").await
} {
while let Ok(Some(f)) = list.next_entry().await {
let name = f.file_name().to_string_lossy().to_string();
if f.metadata().await.is_ok_and(|m| m.is_dir()) && name.starts_with(cmp_prefix) {
result.push(name.clone())
}
}
}
result
})
},
|current, new| {
if let Some((prefix, _)) = current.rsplit_once('/') {
format!("{prefix}/{new}/")
} else {
format!("{new}/")
}
}
)
));
let mut result =
emit!(Input(InputOpt::top("Change directory:").with_value(target.to_string_lossy())));
if let Some(Ok(s)) = result.recv().await {
emit!(Cd(Url::from(s.trim())));

View file

@ -1,7 +1,4 @@
use std::mem;
use ratatui::{buffer::Buffer, layout::{Constraint, Rect}, widgets::{Block, BorderType, Borders, Cell, Clear, Row, Table, Widget}};
use yazi_config::THEME;
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, Borders, Cell, Clear, Row, Table, Widget}};
use yazi_core::Ctx;
pub(crate) struct Completion<'a> {
@ -14,46 +11,47 @@ impl<'a> Completion<'a> {
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);
todo!()
// let completion = &self.cx.input.completion;
// let area = self.cx.area(&completion.position);
let constraint = (0..completion.column_cnt)
.map(|_| Constraint::Percentage(completion.max_width))
.collect::<Vec<Constraint>>();
let table = {
let max_width = completion.max_width as usize;
let mut table = vec![];
let mut cur_row = vec![];
for (idx, s) in completion.items.iter().enumerate() {
if idx != 0 && idx % completion.column_cnt as usize == 0 {
let t = mem::take(&mut cur_row);
table.push(Row::new(t));
}
cur_row.push(
Cell::from(if s.len() < max_width {
s.to_owned()
} else {
s.split_at(max_width - 1).0.to_string() + ""
})
.style(if completion.cursor == idx {
THEME.completion.active.into()
} else {
THEME.completion.inactive.into()
}),
);
}
table.push(Row::new(cur_row));
Table::new(table)
.block(
Block::new()
.borders(Borders::ALL)
.border_type(BorderType::Double)
.border_style(THEME.completion.border.into()),
)
.widths(&constraint)
};
// let constraint = (0..completion.column_cnt)
// .map(|_| Constraint::Percentage(completion.max_width))
// .collect::<Vec<Constraint>>();
// let table = {
// let max_width = completion.max_width as usize;
// let mut table = vec![];
// let mut cur_row = vec![];
// for (idx, s) in completion.items.iter().enumerate() {
// if idx != 0 && idx % completion.column_cnt as usize == 0 {
// let t = mem::take(&mut cur_row);
// table.push(Row::new(t));
// }
// cur_row.push(
// Cell::from(if s.len() < max_width {
// s.to_owned()
// } else {
// s.split_at(max_width - 1).0.to_string() + "…"
// })
// .style(if completion.cursor == idx {
// THEME.completion.active.into()
// } else {
// THEME.completion.inactive.into()
// }),
// );
// }
// table.push(Row::new(cur_row));
// Table::new(table)
// .block(
// Block::new()
// .borders(Borders::ALL)
// .border_type(BorderType::Double)
// .border_style(THEME.completion.border.into()),
// )
// .widths(&constraint)
// };
Clear.render(area, buf);
table.render(area, buf);
// Clear.render(area, buf);
// table.render(area, buf);
}
}

View file

@ -43,6 +43,7 @@ impl Executor {
KeymapLayer::Select => Self::select(cx, e),
KeymapLayer::Input => Self::input(cx, e),
KeymapLayer::Help => Self::help(cx, e),
KeymapLayer::Completion => Self::completion(cx, e),
KeymapLayer::Which => unreachable!(),
};
}
@ -234,8 +235,7 @@ impl Executor {
return if in_operating { cx.input.move_in_operating(step) } else { cx.input.move_(step) };
}
// asynchronized completion
"complete" => return cx.input.fill_completion(exec),
"complete" => todo!(),
_ => {}
}
@ -278,4 +278,21 @@ impl Executor {
_ => false,
}
}
fn completion(cx: &mut Ctx, exec: &Exec) -> bool {
match exec.cmd.as_str() {
"close" => cx.completion.close(),
"arrow" => {
let step: isize = exec.args.get(0).and_then(|s| s.parse().ok()).unwrap_or(0);
if step > 0 {
cx.completion.next(step as usize)
} else {
cx.completion.prev(step.unsigned_abs())
}
}
_ => false,
}
}
}

View file

@ -36,14 +36,14 @@ 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);
}
if self.cx.completion.visible {
completion::Completion::new(self.cx).render(area, buf);
}
if self.cx.which.visible {
which::Which::new(self.cx).render(area, buf);
}