WIP [no ci]

This commit is contained in:
sxyazi 2023-11-03 16:53:15 +08:00
parent d59e52c39b
commit 146606e034
No known key found for this signature in database
16 changed files with 219 additions and 66 deletions

View file

@ -1 +1 @@
{"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp"," Überzug"," Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi"],"language":"en","version":"0.2","flagWords":[]}
{"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp"," Überzug"," Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit"],"flagWords":[],"version":"0.2","language":"en"}

View file

@ -244,8 +244,8 @@ 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 = [ "<A-k>" ], exec = "arrow -1", desc = "Move cursor up" },
{ on = [ "<A-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" },

View file

@ -84,13 +84,19 @@ inactive = {}
# : }}}
# : Completion {{{
[completion]
border = { fg = "blue" }
active = { fg = "lightcyan" }
active = { bg = "darkgray" }
inactive = {}
# Icons
icon_file = ""
icon_folder = ""
icon_command = ""
# : }}}

View file

@ -86,6 +86,10 @@ pub struct Completion {
pub border: Style,
pub active: Style,
pub inactive: Style,
pub icon_file: String,
pub icon_folder: String,
pub icon_command: String,
}
#[derive(Deserialize, Serialize)]

View file

@ -0,0 +1,44 @@
use yazi_config::keymap::Exec;
use crate::completion::Completion;
pub struct Opt(isize);
impl From<&Exec> for Opt {
fn from(e: &Exec) -> Self { Self(e.args.first().and_then(|s| s.parse().ok()).unwrap_or(0)) }
}
impl Completion {
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);
let limit = self.limit();
if self.cursor >= len.min(self.offset + limit) {
self.offset = len.saturating_sub(limit).min(self.offset + self.cursor - old);
}
old != self.cursor
}
fn prev(&mut self, step: usize) -> bool {
let old = self.cursor;
self.cursor = self.cursor.saturating_sub(step);
if self.cursor < self.offset {
self.offset = self.offset.saturating_sub(old - self.cursor);
}
old != self.cursor
}
pub fn arrow(&mut self, opt: impl Into<Opt>) -> bool {
let step = opt.into().0;
if step > 0 { self.next(step as usize) } else { self.prev(step.unsigned_abs()) }
}
}

View file

@ -0,0 +1,32 @@
use yazi_config::keymap::{Exec, KeymapLayer};
use crate::{completion::Completion, emit};
pub struct Opt(bool);
impl From<&Exec> for Opt {
fn from(e: &Exec) -> Self { Self(e.named.contains_key("submit")) }
}
impl From<bool> for Opt {
fn from(b: bool) -> Self { Self(b) }
}
impl Completion {
pub fn close(&mut self, opt: impl Into<Opt>) -> bool {
let submit = opt.into().0;
if submit {
emit!(Call(
Exec::call("complete", vec![self.items[self.cursor].to_owned()])
.with_bool("apply", true)
.with("ticket", self.ticket)
.vec(),
KeymapLayer::Input
));
}
self.cursor = 0;
self.visible = false;
true
}
}

View file

@ -1,2 +1,4 @@
mod arrow;
mod close;
mod show;
mod trigger;

View file

@ -1,39 +1,20 @@
#[derive(Default)]
pub struct Completion {
pub items: Vec<String>,
pub cursor: usize,
pub(super) items: Vec<String>,
pub(super) offset: usize,
pub cursor: usize,
pub ticket: usize,
pub visible: bool,
}
impl Completion {
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
#[inline]
pub fn window(&self) -> &[String] {
let end = (self.offset + self.limit()).min(self.items.len());
&self.items[self.offset..end]
}
#[inline]
pub fn selected(&self) -> Option<&String> { self.items.get(self.cursor) }
pub fn limit(&self) -> usize { self.items.len().min(5) }
}

View file

@ -0,0 +1,45 @@
use yazi_config::keymap::Exec;
use crate::input::Input;
pub struct Opt<'a> {
word: &'a str,
ticket: usize,
}
impl<'a> From<&'a Exec> for Opt<'a> {
fn from(e: &'a Exec) -> Self {
Self {
word: e.args.first().map(|w| w.as_str()).unwrap_or_default(),
ticket: e.named.get("ticket").and_then(|v| v.parse().ok()).unwrap_or(0),
}
}
}
impl Input {
pub fn complete<'a>(&mut self, opt: impl Into<Opt<'a>>) -> bool {
let opt = opt.into();
if self.ticket != opt.ticket {
return false;
}
let [before, after] = self.partition();
let new = if let Some((prefix, _)) = before.rsplit_once('/') {
format!("{prefix}/{}{after}", opt.word)
} else {
format!("{}{after}", opt.word)
};
let snap = self.snaps.current_mut();
if new == snap.value {
return false;
}
let delta = new.chars().count() as isize - snap.value.chars().count() as isize;
snap.value = new;
self.move_(delta);
self.flush_value(true);
true
}
}

View file

@ -0,0 +1 @@
mod complete;

View file

@ -3,17 +3,17 @@ use std::ops::Range;
use crossterm::event::KeyCode;
use tokio::sync::mpsc::UnboundedSender;
use unicode_width::UnicodeWidthStr;
use yazi_config::keymap::{Exec, Key, KeymapLayer};
use yazi_config::keymap::Key;
use yazi_shared::{CharKind, InputError};
use super::{mode::InputMode, op::InputOp, InputOpt, InputSnap, InputSnaps};
use crate::{emit, external, Position};
use crate::{external, Position};
#[derive(Default)]
pub struct Input {
snaps: InputSnaps,
pub ticket: usize,
pub visible: bool,
pub(super) snaps: InputSnaps,
pub ticket: usize,
pub visible: bool,
pub title: String,
pub position: Position,
@ -31,7 +31,6 @@ impl Input {
pub fn show(&mut self, opt: InputOpt, tx: UnboundedSender<Result<String, InputError>>) {
self.close(false);
self.snaps.reset(opt.value);
self.ticket = self.ticket.wrapping_add(1);
self.visible = true;
self.title = opt.title;
@ -52,6 +51,7 @@ impl Input {
_ = cb.send(if submit { Ok(value) } else { Err(InputError::Canceled(value)) });
}
self.ticket = self.ticket.wrapping_add(1);
self.visible = false;
true
}
@ -213,7 +213,7 @@ impl Input {
}
self.move_(s.chars().count() as isize);
self.flush_value();
self.flush_value(false);
true
}
@ -226,7 +226,7 @@ impl Input {
}
self.move_(-1);
self.flush_value();
self.flush_value(false);
true
}
@ -320,26 +320,23 @@ impl Input {
return false;
}
if !matches!(old.op, InputOp::None | InputOp::Select(_)) {
self.snaps.tag().then(|| self.flush_value());
self.snaps.tag().then(|| self.flush_value(false));
}
true
}
#[inline]
fn flush_value(&mut self) {
pub(super) fn flush_value(&mut self, no_complete: bool) {
self.ticket = self.ticket.wrapping_add(1);
if self.realtime {
let value = self.snap().value.clone();
self.callback.as_ref().unwrap().send(Err(InputError::Typed(value))).ok();
}
if self.completion {
emit!(Call(
Exec::call("complete", vec![self.partition()[0].to_owned()])
.with("ticket", self.ticket)
.vec(),
KeymapLayer::Input
));
if self.completion && !no_complete {
let before = self.partition()[0].to_owned();
self.callback.as_ref().unwrap().send(Err(InputError::Completed(before, self.ticket))).ok();
}
}
}

View file

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

View file

@ -1,6 +1,9 @@
use std::mem;
use std::{mem, time::Duration};
use yazi_shared::Url;
use tokio::pin;
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
use yazi_config::keymap::{Exec, KeymapLayer};
use yazi_shared::{Debounce, InputError, Url};
use crate::{emit, files::{File, FilesOp}, input::InputOpt, tab::Tab};
@ -59,12 +62,26 @@ impl Tab {
pub fn cd_interactive(&mut self, target: Url) -> bool {
tokio::spawn(async move {
let mut result = emit!(Input(
let rx = emit!(Input(
InputOpt::top("Change directory:").with_value(target.to_string_lossy()).with_completion()
));
if let Some(Ok(s)) = result.recv().await {
emit!(Cd(Url::from(s.trim())));
let rx = Debounce::new(UnboundedReceiverStream::new(rx), Duration::from_millis(50));
pin!(rx);
while let Some(result) = rx.next().await {
match result {
Ok(s) => {
emit!(Cd(Url::from(s.trim())));
}
Err(InputError::Completed(before, ticket)) => {
emit!(Call(
Exec::call("complete", vec![before]).with("ticket", ticket).vec(),
KeymapLayer::Input
));
}
_ => break,
}
}
});
false

View file

@ -1,4 +1,5 @@
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, Borders, Clear, List, ListItem, Widget}};
use yazi_config::THEME;
use yazi_core::{Ctx, Position};
pub(crate) struct Completion<'a> {
@ -11,8 +12,24 @@ impl<'a> Completion<'a> {
impl<'a> Widget for Completion<'a> {
fn render(self, rect: Rect, buf: &mut Buffer) {
let items =
self.cx.completion.items.iter().map(|x| ListItem::new(x.as_str())).collect::<Vec<_>>();
let items = self
.cx
.completion
.window()
.iter()
.enumerate()
.map(|(i, x)| {
let mut item = ListItem::new(format!(" {} {}", THEME.completion.icon_file, x));
if i == self.cx.completion.cursor {
item = item.style(THEME.completion.active.into());
} else {
item = item.style(THEME.completion.inactive.into());
}
item
})
.collect::<Vec<_>>();
let input_area = self.cx.area(&self.cx.input.position);
let mut area =
@ -27,7 +44,12 @@ impl<'a> Widget for Completion<'a> {
Clear.render(area, buf);
List::new(items)
.block(Block::new().borders(Borders::ALL).border_type(BorderType::Rounded))
.block(
Block::new()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(THEME.completion.border.into()),
)
.render(area, buf);
}
}

View file

@ -267,7 +267,13 @@ impl<'a> Executor<'a> {
};
}
"complete" => return self.cx.completion.trigger(exec),
"complete" => {
return if exec.named.contains_key("apply") {
self.cx.input.complete(exec)
} else {
self.cx.completion.trigger(exec)
};
}
_ => {}
}
@ -315,16 +321,9 @@ impl<'a> Executor<'a> {
match exec.cmd.as_str() {
"trigger" => self.cx.completion.trigger(exec),
"show" => self.cx.completion.show(exec),
"close" => self.cx.completion.close(exec.named.contains_key("submit")),
"close" => self.cx.completion.close(exec),
"arrow" => {
let step: isize = exec.args.first().and_then(|s| s.parse().ok()).unwrap_or(0);
if step > 0 {
self.cx.completion.next(step as usize)
} else {
self.cx.completion.prev(step.unsigned_abs())
}
}
"arrow" => self.cx.completion.arrow(exec),
"help" => self.cx.help.toggle(KeymapLayer::Completion),
_ => false,

View file

@ -3,6 +3,7 @@ use std::{error::Error, fmt::{self, Display}};
#[derive(Debug)]
pub enum InputError {
Typed(String),
Completed(String, usize),
Canceled(String),
}
@ -10,6 +11,7 @@ impl Display for InputError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Typed(text) => write!(f, "Typed error: {text}"),
Self::Completed(text, _) => write!(f, "Completed error: {text}"),
Self::Canceled(text) => write!(f, "Canceled error: {text}"),
}
}