mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
.. [WIP] [no ci]
This commit is contained in:
parent
9a67c0150e
commit
802aa86e23
11 changed files with 137 additions and 246 deletions
|
|
@ -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" }
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
mod completion;
|
||||
mod input;
|
||||
mod mode;
|
||||
mod op;
|
||||
|
|
|
|||
|
|
@ -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!() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue