From 1166f86523950a2279033a70679712a3dc5b2049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Sat, 8 Jun 2024 18:29:50 +0800 Subject: [PATCH] feat: support completely disabling mouse with `mouse_events=[]`; add new `cursor_blink` to control cursor style of input components (#1139) --- cspell.json | 2 +- yazi-adaptor/src/chafa.rs | 6 +- yazi-adaptor/src/dimension.rs | 35 ++++++ yazi-adaptor/src/emulator.rs | 36 +++++- yazi-adaptor/src/image.rs | 7 +- yazi-adaptor/src/iterm2.rs | 4 +- yazi-adaptor/src/kitty.rs | 4 +- yazi-adaptor/src/lib.rs | 2 + yazi-adaptor/src/sixel.rs | 4 +- yazi-config/preset/yazi.toml | 6 +- yazi-config/src/popup/input.rs | 2 + yazi-config/src/popup/position.rs | 7 +- yazi-core/src/help/help.rs | 7 +- yazi-core/src/manager/commands/bulk_rename.rs | 6 +- yazi-core/src/tasks/commands/inspect.rs | 4 +- yazi-core/src/tasks/tasks.rs | 6 +- yazi-fm/src/app/app.rs | 4 +- yazi-fm/src/app/commands/quit.rs | 4 +- yazi-fm/src/app/commands/resume.rs | 4 +- yazi-fm/src/app/commands/update_notify.rs | 5 +- yazi-fm/src/completion/completion.rs | 3 +- yazi-fm/src/context.rs | 8 +- yazi-fm/src/executor.rs | 5 - yazi-fm/src/input/input.rs | 3 +- yazi-fm/src/main.rs | 2 + yazi-fm/src/panic.rs | 2 +- {yazi-shared/src/term => yazi-fm/src}/term.rs | 109 +++++++++++------- yazi-plugin/src/bindings/window.rs | 4 +- yazi-shared/src/lib.rs | 3 +- yazi-shared/src/term/csi_u.rs | 34 ------ yazi-shared/src/term/cursor.rs | 17 --- yazi-shared/src/term/mod.rs | 7 -- yazi-shared/src/terminal.rs | 10 ++ 33 files changed, 205 insertions(+), 157 deletions(-) create mode 100644 yazi-adaptor/src/dimension.rs rename {yazi-shared/src/term => yazi-fm/src}/term.rs (54%) delete mode 100644 yazi-shared/src/term/csi_u.rs delete mode 100644 yazi-shared/src/term/cursor.rs delete mode 100644 yazi-shared/src/term/mod.rs create mode 100644 yazi-shared/src/terminal.rs diff --git a/cspell.json b/cspell.json index 2bbe9f407..db858584d 100644 --- a/cspell.json +++ b/cspell.json @@ -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","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit"],"version":"0.2","flagWords":[],"language":"en"} \ No newline at end of file +{"version":"0.2","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","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt"],"language":"en","flagWords":[]} \ No newline at end of file diff --git a/yazi-adaptor/src/chafa.rs b/yazi-adaptor/src/chafa.rs index 1e3a74283..88ac5d64a 100644 --- a/yazi-adaptor/src/chafa.rs +++ b/yazi-adaptor/src/chafa.rs @@ -2,9 +2,9 @@ use std::{io::Write, path::Path, process::Stdio}; use ansi_to_tui::IntoText; use anyhow::{bail, Result}; +use crossterm::{cursor::MoveTo, queue}; use ratatui::layout::Rect; use tokio::process::Command; -use yazi_shared::term::Term; use crate::{Adaptor, Emulator}; @@ -58,7 +58,7 @@ impl Chafa { Emulator::move_lock((max.x, max.y), |stderr| { for (i, line) in lines.into_iter().enumerate() { stderr.write_all(line)?; - Term::move_to(stderr, max.x, max.y + i as u16 + 1)?; + queue!(stderr, MoveTo(max.x, max.y + i as u16 + 1))?; } Ok(area) }) @@ -68,7 +68,7 @@ impl Chafa { let s = " ".repeat(area.width as usize); Emulator::move_lock((0, 0), |stderr| { for y in area.top()..area.bottom() { - Term::move_to(stderr, area.x, y)?; + queue!(stderr, MoveTo(area.x, y))?; write!(stderr, "{s}")?; } Ok(()) diff --git a/yazi-adaptor/src/dimension.rs b/yazi-adaptor/src/dimension.rs new file mode 100644 index 000000000..dfa0f464d --- /dev/null +++ b/yazi-adaptor/src/dimension.rs @@ -0,0 +1,35 @@ +use std::mem; + +use crossterm::terminal::WindowSize; + +pub struct Dimension; + +impl Dimension { + pub fn available() -> WindowSize { + let mut size = WindowSize { rows: 0, columns: 0, width: 0, height: 0 }; + if let Ok(s) = crossterm::terminal::window_size() { + _ = mem::replace(&mut size, s); + } + + if size.rows == 0 || size.columns == 0 { + if let Ok(s) = crossterm::terminal::size() { + size.columns = s.0; + size.rows = s.1; + } + } + + // TODO: Use `CSI 14 t` to get the actual size of the terminal + // if size.width == 0 || size.height == 0 {} + + size + } + + #[inline] + pub fn ratio() -> Option<(f64, f64)> { + let s = Self::available(); + if s.width == 0 || s.height == 0 { + return None; + } + Some((f64::from(s.width) / f64::from(s.columns), f64::from(s.height) / f64::from(s.rows))) + } +} diff --git a/yazi-adaptor/src/emulator.rs b/yazi-adaptor/src/emulator.rs index c366e0c48..298ea18c1 100644 --- a/yazi-adaptor/src/emulator.rs +++ b/yazi-adaptor/src/emulator.rs @@ -1,10 +1,11 @@ -use std::{env, io::{stderr, LineWriter}}; +use std::{env, io::{stderr, LineWriter}, time::Duration}; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, bail, Result}; use crossterm::{cursor::{RestorePosition, SavePosition}, execute, style::Print, terminal::{disable_raw_mode, enable_raw_mode}}; use scopeguard::defer; -use tracing::warn; -use yazi_shared::{env_exists, term::Term}; +use tokio::{io::{AsyncReadExt, BufReader}, time::timeout}; +use tracing::{error, warn}; +use yazi_shared::env_exists; use crate::{Adaptor, CLOSE, ESCAPE, START, TMUX}; @@ -129,7 +130,7 @@ impl Emulator { RestorePosition )?; - let resp = futures::executor::block_on(Term::read_until_da1())?; + let resp = futures::executor::block_on(Self::read_until_da1())?; let names = [ ("kitty", Self::Kitty), ("Konsole", Self::Konsole), @@ -187,4 +188,29 @@ impl Emulator { buf.flush()?; result } + + pub async fn read_until_da1() -> Result { + let read = async { + let mut stdin = BufReader::new(tokio::io::stdin()); + let mut buf = String::with_capacity(200); + loop { + let mut c = [0; 1]; + if stdin.read(&mut c).await? == 0 { + bail!("unexpected EOF"); + } + buf.push(c[0] as char); + if c[0] == b'c' && buf.contains("\x1b[?") { + break; + } + } + Ok(buf) + }; + + let timeout = timeout(Duration::from_secs(10), read).await; + if let Err(ref e) = timeout { + error!("read_until_da1: {e:?}"); + } + + timeout? + } } diff --git a/yazi-adaptor/src/image.rs b/yazi-adaptor/src/image.rs index d21575dfb..d5d39ca39 100644 --- a/yazi-adaptor/src/image.rs +++ b/yazi-adaptor/src/image.rs @@ -5,7 +5,8 @@ use exif::{In, Tag}; use image::{codecs::jpeg::JpegEncoder, imageops::{self, FilterType}, io::Limits, DynamicImage}; use ratatui::layout::Rect; use yazi_config::{PREVIEW, TASKS}; -use yazi_shared::term::Term; + +use crate::Dimension; pub struct Image; @@ -77,7 +78,7 @@ impl Image { } pub(super) fn max_pixel(rect: Rect) -> (u32, u32) { - Term::ratio() + Dimension::ratio() .map(|(r1, r2)| { let (w, h) = ((rect.width as f64 * r1) as u32, (rect.height as f64 * r2) as u32); (w.min(PREVIEW.max_width), h.min(PREVIEW.max_height)) @@ -86,7 +87,7 @@ impl Image { } pub(super) fn pixel_area(size: (u32, u32), rect: Rect) -> Rect { - Term::ratio() + Dimension::ratio() .map(|(r1, r2)| Rect { x: rect.x, y: rect.y, diff --git a/yazi-adaptor/src/iterm2.rs b/yazi-adaptor/src/iterm2.rs index 97c0262f3..fc47e3d3c 100644 --- a/yazi-adaptor/src/iterm2.rs +++ b/yazi-adaptor/src/iterm2.rs @@ -2,9 +2,9 @@ use std::{io::Write, path::Path}; use anyhow::Result; use base64::{engine::{general_purpose::STANDARD, Config}, Engine}; +use crossterm::{cursor::MoveTo, queue}; use image::{codecs::jpeg::JpegEncoder, DynamicImage}; use ratatui::layout::Rect; -use yazi_shared::term::Term; use super::image::Image; use crate::{adaptor::Adaptor, Emulator, CLOSE, START}; @@ -29,7 +29,7 @@ impl Iterm2 { let s = " ".repeat(area.width as usize); Emulator::move_lock((0, 0), |stderr| { for y in area.top()..area.bottom() { - Term::move_to(stderr, area.x, y)?; + queue!(stderr, MoveTo(area.x, y))?; write!(stderr, "{s}")?; } Ok(()) diff --git a/yazi-adaptor/src/kitty.rs b/yazi-adaptor/src/kitty.rs index 35518e53a..fdc93d153 100644 --- a/yazi-adaptor/src/kitty.rs +++ b/yazi-adaptor/src/kitty.rs @@ -3,9 +3,9 @@ use std::{io::Write, path::Path}; use anyhow::Result; use base64::{engine::general_purpose, Engine}; +use crossterm::{cursor::MoveTo, queue}; use image::DynamicImage; use ratatui::layout::Rect; -use yazi_shared::term::Term; use super::image::Image; use crate::{adaptor::Adaptor, Emulator, CLOSE, ESCAPE, START}; @@ -333,7 +333,7 @@ impl Kitty { let s = " ".repeat(area.width as usize); Emulator::move_lock((0, 0), |stderr| { for y in area.top()..area.bottom() { - Term::move_to(stderr, area.x, y)?; + queue!(stderr, MoveTo(area.x, y))?; write!(stderr, "{s}")?; } diff --git a/yazi-adaptor/src/lib.rs b/yazi-adaptor/src/lib.rs index 65675fc70..48baab5e6 100644 --- a/yazi-adaptor/src/lib.rs +++ b/yazi-adaptor/src/lib.rs @@ -2,6 +2,7 @@ mod adaptor; mod chafa; +mod dimension; mod emulator; mod image; mod iterm2; @@ -12,6 +13,7 @@ mod ueberzug; pub use adaptor::*; use chafa::*; +pub use dimension::*; pub use emulator::*; use iterm2::*; use kitty::*; diff --git a/yazi-adaptor/src/sixel.rs b/yazi-adaptor/src/sixel.rs index 993a4e7ad..3a6ef153e 100644 --- a/yazi-adaptor/src/sixel.rs +++ b/yazi-adaptor/src/sixel.rs @@ -2,10 +2,10 @@ use std::{io::Write, path::Path}; use anyhow::{bail, Result}; use color_quant::NeuQuant; +use crossterm::{cursor::MoveTo, queue}; use image::DynamicImage; use ratatui::layout::Rect; use yazi_config::PREVIEW; -use yazi_shared::term::Term; use crate::{adaptor::Adaptor, Emulator, Image, CLOSE, ESCAPE, START}; @@ -29,7 +29,7 @@ impl Sixel { let s = " ".repeat(area.width as usize); Emulator::move_lock((0, 0), |stderr| { for y in area.top()..area.bottom() { - Term::move_to(stderr, area.x, y)?; + queue!(stderr, MoveTo(area.x, y))?; write!(stderr, "{s}")?; } Ok(()) diff --git a/yazi-config/preset/yazi.toml b/yazi-config/preset/yazi.toml index 77f39a9f7..47b37cf23 100644 --- a/yazi-config/preset/yazi.toml +++ b/yazi-config/preset/yazi.toml @@ -33,12 +33,12 @@ edit = [ { run = 'code -w "%*"', block = true, desc = "code (block)", for = "windows" }, ] open = [ - { run = 'xdg-open "$@"', desc = "Open", for = "linux" }, + { run = 'xdg-open "$1"', desc = "Open", for = "linux" }, { run = 'open "$@"', desc = "Open", for = "macos" }, { run = 'start "" "%1"', orphan = true, desc = "Open", for = "windows" }, ] reveal = [ - { run = 'xdg-open "$(dirname "$0")"', desc = "Reveal", for = "linux" }, + { run = 'xdg-open "$(dirname "$1")"', desc = "Reveal", for = "linux" }, { run = 'open -R "$1"', desc = "Reveal", for = "macos" }, { run = 'explorer /select, "%1"', orphan = true, desc = "Reveal", for = "windows" }, { run = '''exiftool "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show EXIF", for = "unix" }, @@ -122,6 +122,8 @@ previewers = [ ] [input] +cursor_blink = true + # cd cd_title = "Change directory:" cd_origin = "top-center" diff --git a/yazi-config/src/popup/input.rs b/yazi-config/src/popup/input.rs index 4b1f9d526..5db33ef7f 100644 --- a/yazi-config/src/popup/input.rs +++ b/yazi-config/src/popup/input.rs @@ -5,6 +5,8 @@ use crate::MERGED_YAZI; #[derive(Deserialize)] pub struct Input { + pub cursor_blink: bool, + // cd pub cd_title: String, pub cd_origin: Origin, diff --git a/yazi-config/src/popup/position.rs b/yazi-config/src/popup/position.rs index a67e44dae..d89749476 100644 --- a/yazi-config/src/popup/position.rs +++ b/yazi-config/src/popup/position.rs @@ -1,6 +1,5 @@ use crossterm::terminal::WindowSize; use ratatui::layout::Rect; -use yazi_shared::term::Term; use super::{Offset, Origin}; @@ -14,10 +13,9 @@ impl Position { #[inline] pub fn new(origin: Origin, offset: Offset) -> Self { Self { origin, offset } } - pub fn rect(&self) -> Rect { + pub fn rect(&self, WindowSize { columns, rows, .. }: WindowSize) -> Rect { use Origin::*; let Offset { x, y, width, height } = self.offset; - let WindowSize { columns, rows, .. } = Term::size(); let max_x = columns.saturating_sub(width); let new_x = match self.origin { @@ -45,9 +43,8 @@ impl Position { } } - pub fn sticky(base: Rect, offset: Offset) -> Rect { + pub fn sticky(WindowSize { columns, rows, .. }: WindowSize, base: Rect, offset: Offset) -> Rect { let Offset { x, y, width, height } = offset; - let WindowSize { columns, rows, .. } = Term::size(); let above = base.y.saturating_add(base.height).saturating_add(height).saturating_add_signed(y) > rows; diff --git a/yazi-core/src/help/help.rs b/yazi-core/src/help/help.rs index af13964da..f5fc72c0b 100644 --- a/yazi-core/src/help/help.rs +++ b/yazi-core/src/help/help.rs @@ -1,7 +1,8 @@ use crossterm::event::KeyCode; use unicode_width::UnicodeWidthStr; +use yazi_adaptor::Dimension; use yazi_config::{keymap::{Control, Key}, KEYMAP}; -use yazi_shared::{render, render_and, term::Term, Layer}; +use yazi_shared::{render, render_and, Layer}; use super::HELP_MARGIN; use crate::input::Input; @@ -22,7 +23,7 @@ pub struct Help { impl Help { #[inline] - pub fn limit() -> usize { Term::size().rows.saturating_sub(HELP_MARGIN) as usize } + pub fn limit() -> usize { Dimension::available().rows.saturating_sub(HELP_MARGIN) as usize } pub fn toggle(&mut self, layer: Layer) { self.visible = !self.visible; @@ -106,7 +107,7 @@ impl Help { return None; } if let Some(kw) = self.keyword() { - return Some((kw.width() as u16, Term::size().rows)); + return Some((kw.width() as u16, Dimension::available().rows)); } None } diff --git a/yazi-core/src/manager/commands/bulk_rename.rs b/yazi-core/src/manager/commands/bulk_rename.rs index e57bd2b4c..6a22fe3dd 100644 --- a/yazi-core/src/manager/commands/bulk_rename.rs +++ b/yazi-core/src/manager/commands/bulk_rename.rs @@ -6,7 +6,7 @@ use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}}; use yazi_config::{OPEN, PREVIEW}; use yazi_dds::Pubsub; use yazi_proxy::{AppProxy, TasksProxy, HIDER, WATCHER}; -use yazi_shared::{fs::{max_common_root, maybe_exists, File, FilesOp, Url}, term::Term}; +use yazi_shared::{fs::{max_common_root, maybe_exists, File, FilesOp, Url}, terminal_clear}; use crate::manager::Manager; @@ -52,7 +52,7 @@ impl Manager { old: Vec, new: Vec, ) -> Result<()> { - Term::clear(&mut stderr())?; + terminal_clear(&mut stderr())?; if old.len() != new.len() { eprintln!("Number of old and new differ, press ENTER to exit"); stdin().read_exact(&mut [0]).await?; @@ -108,7 +108,7 @@ impl Manager { } async fn output_failed(failed: Vec<(PathBuf, PathBuf, anyhow::Error)>) -> Result<()> { - Term::clear(&mut stderr())?; + terminal_clear(&mut stderr())?; { let mut stderr = BufWriter::new(stderr().lock()); diff --git a/yazi-core/src/tasks/commands/inspect.rs b/yazi-core/src/tasks/commands/inspect.rs index 9eea78313..4828b913a 100644 --- a/yazi-core/src/tasks/commands/inspect.rs +++ b/yazi-core/src/tasks/commands/inspect.rs @@ -4,7 +4,7 @@ use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; use scopeguard::defer; use tokio::{io::{stdin, AsyncReadExt}, select, sync::mpsc, time}; use yazi_proxy::{AppProxy, HIDER}; -use yazi_shared::{event::Cmd, term::Term}; +use yazi_shared::{event::Cmd, terminal_clear}; use crate::tasks::Tasks; @@ -30,7 +30,7 @@ impl Tasks { defer!(AppProxy::resume()); AppProxy::stop().await; - Term::clear(&mut stderr()).ok(); + terminal_clear(&mut stderr()).ok(); BufWriter::new(stderr().lock()).write_all(mem::take(&mut buffered).as_bytes()).ok(); defer! { disable_raw_mode().ok(); } diff --git a/yazi-core/src/tasks/tasks.rs b/yazi-core/src/tasks/tasks.rs index 0f4acff90..7858504fb 100644 --- a/yazi-core/src/tasks/tasks.rs +++ b/yazi-core/src/tasks/tasks.rs @@ -2,8 +2,9 @@ use std::{sync::Arc, time::Duration}; use parking_lot::Mutex; use tokio::{task::JoinHandle, time::sleep}; +use yazi_adaptor::Dimension; use yazi_scheduler::{Ongoing, Scheduler, TaskSummary}; -use yazi_shared::{emit, event::Cmd, term::Term, Layer}; +use yazi_shared::{emit, event::Cmd, Layer}; use super::{TasksProgress, TASKS_BORDER, TASKS_PADDING, TASKS_PERCENT}; @@ -53,7 +54,8 @@ impl Tasks { #[inline] pub fn limit() -> usize { - (Term::size().rows * TASKS_PERCENT / 100).saturating_sub(TASKS_BORDER + TASKS_PADDING) as usize + (Dimension::available().rows * TASKS_PERCENT / 100).saturating_sub(TASKS_BORDER + TASKS_PADDING) + as usize } pub fn paginate(&self) -> Vec { diff --git a/yazi-fm/src/app/app.rs b/yazi-fm/src/app/app.rs index 95ed35b50..f28912bc9 100644 --- a/yazi-fm/src/app/app.rs +++ b/yazi-fm/src/app/app.rs @@ -4,9 +4,9 @@ use anyhow::Result; use crossterm::event::KeyEvent; use yazi_config::keymap::Key; use yazi_core::input::InputMode; -use yazi_shared::{emit, event::{Cmd, Event, NEED_RENDER}, term::Term, Layer}; +use yazi_shared::{emit, event::{Cmd, Event, NEED_RENDER}, Layer}; -use crate::{lives::Lives, Ctx, Executor, Router, Signals}; +use crate::{lives::Lives, Ctx, Executor, Router, Signals, Term}; pub(crate) struct App { pub(crate) cx: Ctx, diff --git a/yazi-fm/src/app/commands/quit.rs b/yazi-fm/src/app/commands/quit.rs index f7b7ec3fe..f83bc2643 100644 --- a/yazi-fm/src/app/commands/quit.rs +++ b/yazi-fm/src/app/commands/quit.rs @@ -1,9 +1,9 @@ use std::ffi::OsString; use yazi_boot::ARGS; -use yazi_shared::{event::EventQuit, term::Term}; +use yazi_shared::event::EventQuit; -use crate::app::App; +use crate::{app::App, Term}; impl App { pub(crate) fn quit(&mut self, opt: EventQuit) -> ! { diff --git a/yazi-fm/src/app/commands/resume.rs b/yazi-fm/src/app/commands/resume.rs index fe6ebf681..4f9ca1a22 100644 --- a/yazi-fm/src/app/commands/resume.rs +++ b/yazi-fm/src/app/commands/resume.rs @@ -1,6 +1,6 @@ -use yazi_shared::{event::Cmd, term::Term}; +use yazi_shared::event::Cmd; -use crate::app::App; +use crate::{app::App, Term}; impl App { pub(crate) fn resume(&mut self, _: Cmd) { diff --git a/yazi-fm/src/app/commands/update_notify.rs b/yazi-fm/src/app/commands/update_notify.rs index 4da856c98..bb89b32c4 100644 --- a/yazi-fm/src/app/commands/update_notify.rs +++ b/yazi-fm/src/app/commands/update_notify.rs @@ -1,12 +1,13 @@ use crossterm::terminal::WindowSize; use ratatui::layout::Rect; -use yazi_shared::{event::Cmd, term::Term}; +use yazi_adaptor::Dimension; +use yazi_shared::event::Cmd; use crate::{app::App, notify}; impl App { pub(crate) fn update_notify(&mut self, cmd: Cmd) { - let WindowSize { rows, columns, .. } = Term::size(); + let WindowSize { rows, columns, .. } = Dimension::available(); let area = notify::Layout::available(Rect { x: 0, y: 0, width: columns, height: rows }); diff --git a/yazi-fm/src/completion/completion.rs b/yazi-fm/src/completion/completion.rs index 7dee9d63c..3d43e0b14 100644 --- a/yazi-fm/src/completion/completion.rs +++ b/yazi-fm/src/completion/completion.rs @@ -1,6 +1,7 @@ use std::path::MAIN_SEPARATOR; use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, List, ListItem, Widget}}; +use yazi_adaptor::Dimension; use yazi_config::{popup::{Offset, Position}, THEME}; use crate::Ctx; @@ -40,7 +41,7 @@ impl<'a> Widget for Completion<'a> { .collect(); let input_area = self.cx.area(&self.cx.input.position); - let mut area = Position::sticky(input_area, Offset { + let mut area = Position::sticky(Dimension::available(), input_area, Offset { x: 1, y: 0, width: input_area.width.saturating_sub(2), diff --git a/yazi-fm/src/context.rs b/yazi-fm/src/context.rs index 3c12b7d57..6b9b6fe80 100644 --- a/yazi-fm/src/context.rs +++ b/yazi-fm/src/context.rs @@ -1,4 +1,5 @@ use ratatui::layout::Rect; +use yazi_adaptor::Dimension; use yazi_config::popup::{Origin, Position}; use yazi_core::{completion::Completion, help::Help, input::Input, manager::Manager, notify::Notify, select::Select, tasks::Tasks, which::Which}; @@ -28,16 +29,17 @@ impl Ctx { } pub fn area(&self, position: &Position) -> Rect { + let ws = Dimension::available(); if position.origin != Origin::Hovered { - return position.rect(); + return position.rect(ws); } if let Some(r) = self.manager.hovered().and_then(|h| self.manager.current().rect_current(&h.url)) { - Position::sticky(r, position.offset) + Position::sticky(ws, r, position.offset) } else { - Position::new(Origin::TopCenter, position.offset).rect() + Position::new(Origin::TopCenter, position.offset).rect(ws) } } diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index e16c6f727..e34d4a71e 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -159,7 +159,6 @@ impl<'a> Executor<'a> { on!(open_with); on!(process_exec); - #[allow(clippy::single_match)] match cmd.name.as_str() { // Help "help" => self.app.cx.help.toggle(Layer::Tasks), @@ -182,7 +181,6 @@ impl<'a> Executor<'a> { on!(close); on!(arrow); - #[allow(clippy::single_match)] match cmd.name.as_str() { // Help "help" => self.app.cx.help.toggle(Layer::Select), @@ -233,7 +231,6 @@ impl<'a> Executor<'a> { on!(undo); on!(redo); - #[allow(clippy::single_match)] match cmd.name.as_str() { // Help "help" => self.app.cx.help.toggle(Layer::Input), @@ -262,7 +259,6 @@ impl<'a> Executor<'a> { on!(arrow); on!(filter); - #[allow(clippy::single_match)] match cmd.name.as_str() { "close" => self.app.cx.help.toggle(Layer::Help), // Plugin @@ -285,7 +281,6 @@ impl<'a> Executor<'a> { on!(close); on!(arrow); - #[allow(clippy::single_match)] match cmd.name.as_str() { "close_input" => self.app.cx.input.close(cmd), // Help diff --git a/yazi-fm/src/input/input.rs b/yazi-fm/src/input/input.rs index a0863347f..f4ad39cf4 100644 --- a/yazi-fm/src/input/input.rs +++ b/yazi-fm/src/input/input.rs @@ -6,9 +6,8 @@ use syntect::easy::HighlightLines; use yazi_config::THEME; use yazi_core::input::InputMode; use yazi_plugin::external::Highlighter; -use yazi_shared::term::Term; -use crate::Ctx; +use crate::{Ctx, Term}; pub(crate) struct Input<'a> { cx: &'a Ctx, diff --git a/yazi-fm/src/main.rs b/yazi-fm/src/main.rs index def019fb2..bcfed8749 100644 --- a/yazi-fm/src/main.rs +++ b/yazi-fm/src/main.rs @@ -21,6 +21,7 @@ mod router; mod select; mod signals; mod tasks; +mod term; mod which; use context::*; @@ -31,6 +32,7 @@ use panic::*; use root::*; use router::*; use signals::*; +use term::*; #[tokio::main] async fn main() -> anyhow::Result<()> { diff --git a/yazi-fm/src/panic.rs b/yazi-fm/src/panic.rs index f0773886d..ca8cedfe9 100644 --- a/yazi-fm/src/panic.rs +++ b/yazi-fm/src/panic.rs @@ -1,4 +1,4 @@ -use yazi_shared::term::Term; +use crate::Term; pub(super) struct Panic; diff --git a/yazi-shared/src/term/term.rs b/yazi-fm/src/term.rs similarity index 54% rename from yazi-shared/src/term/term.rs rename to yazi-fm/src/term.rs index cc4b90ffe..2b3267430 100644 --- a/yazi-shared/src/term/term.rs +++ b/yazi-fm/src/term.rs @@ -1,19 +1,21 @@ -use std::{io::{self, stderr, BufWriter, Stderr, Write}, mem, ops::{Deref, DerefMut}, sync::atomic::{AtomicBool, Ordering}}; +use std::{io::{self, stderr, BufWriter, Stderr}, ops::{Deref, DerefMut}, sync::atomic::{AtomicBool, Ordering}}; use anyhow::Result; -use crossterm::{cursor::{RestorePosition, SavePosition}, event::{DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, execute, queue, style::Print, terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen, SetTitle, WindowSize}}; +use crossterm::{cursor::{RestorePosition, SavePosition}, event::{DisableBracketedPaste, EnableBracketedPaste, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, execute, queue, style::Print, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, SetTitle}}; use ratatui::{backend::CrosstermBackend, buffer::Buffer, layout::Rect, CompletedFrame, Frame, Terminal}; +use yazi_adaptor::Emulator; +use yazi_config::INPUT; static CSI_U: AtomicBool = AtomicBool::new(false); -pub struct Term { +pub(super) struct Term { inner: Terminal>>, last_area: Rect, last_buffer: Buffer, } impl Term { - pub fn start() -> Result { + pub(super) fn start() -> Result { let mut term = Self { inner: Terminal::new(CrosstermBackend::new(BufWriter::new(stderr())))?, last_area: Default::default(), @@ -25,13 +27,13 @@ impl Term { BufWriter::new(stderr()), EnterAlternateScreen, EnableBracketedPaste, - EnableMouseCapture, + mouse::SetMouse(true), SavePosition, Print("\x1b[?u\x1b[c"), RestorePosition )?; - let resp = futures::executor::block_on(Self::read_until_da1()); + let resp = futures::executor::block_on(Emulator::read_until_da1()); if resp.is_ok_and(|s| s.contains("\x1b[?0u")) { queue!( stderr(), @@ -56,7 +58,7 @@ impl Term { execute!( stderr(), - DisableMouseCapture, + mouse::SetMouse(false), DisableBracketedPaste, LeaveAlternateScreen, crossterm::cursor::SetCursorStyle::DefaultUserShape @@ -66,7 +68,7 @@ impl Term { Ok(disable_raw_mode()?) } - pub fn goodbye(f: impl FnOnce() -> bool) -> ! { + pub(super) fn goodbye(f: impl FnOnce() -> bool) -> ! { if CSI_U.swap(false, Ordering::Relaxed) { execute!(stderr(), PopKeyboardEnhancementFlags).ok(); } @@ -74,7 +76,7 @@ impl Term { execute!( stderr(), SetTitle(""), - DisableMouseCapture, + mouse::SetMouse(false), DisableBracketedPaste, LeaveAlternateScreen, crossterm::cursor::SetCursorStyle::DefaultUserShape, @@ -87,7 +89,7 @@ impl Term { std::process::exit(f() as i32); } - pub fn draw(&mut self, f: impl FnOnce(&mut Frame)) -> io::Result { + pub(super) fn draw(&mut self, f: impl FnOnce(&mut Frame)) -> io::Result { let last = self.inner.draw(f)?; self.last_area = last.area; @@ -95,7 +97,7 @@ impl Term { Ok(last) } - pub fn draw_partial(&mut self, f: impl FnOnce(&mut Frame)) -> io::Result { + pub(super) fn draw_partial(&mut self, f: impl FnOnce(&mut Frame)) -> io::Result { self.inner.draw(|frame| { let buffer = frame.buffer_mut(); for y in self.last_area.top()..self.last_area.bottom() { @@ -111,43 +113,28 @@ impl Term { } #[inline] - pub fn can_partial(&mut self) -> bool { + pub(super) fn can_partial(&mut self) -> bool { self.inner.autoresize().is_ok() && self.last_area == self.inner.get_frame().size() } - pub fn size() -> WindowSize { - let mut size = WindowSize { rows: 0, columns: 0, width: 0, height: 0 }; - if let Ok(s) = crossterm::terminal::window_size() { - _ = mem::replace(&mut size, s); - } - - if size.rows == 0 || size.columns == 0 { - if let Ok(s) = crossterm::terminal::size() { - size.columns = s.0; - size.rows = s.1; - } - } - - // TODO: Use `CSI 14 t` to get the actual size of the terminal - // if size.width == 0 || size.height == 0 {} - - size - } - #[inline] - pub fn ratio() -> Option<(f64, f64)> { - let s = Self::size(); - if s.width == 0 || s.height == 0 { - return None; - } - Some((f64::from(s.width) / f64::from(s.columns), f64::from(s.height) / f64::from(s.rows))) + pub(super) fn set_cursor_block() -> Result<()> { + use crossterm::cursor::SetCursorStyle; + Ok(if INPUT.cursor_blink { + queue!(stderr(), SetCursorStyle::BlinkingBlock)? + } else { + queue!(stderr(), SetCursorStyle::SteadyBlock)? + }) } #[inline] - pub fn clear(w: &mut impl Write) -> Result<()> { - queue!(w, Clear(ClearType::All))?; - writeln!(w)?; - Ok(w.flush()?) + pub(super) fn set_cursor_bar() -> Result<()> { + use crossterm::cursor::SetCursorStyle; + Ok(if INPUT.cursor_blink { + queue!(stderr(), SetCursorStyle::BlinkingBar)? + } else { + queue!(stderr(), SetCursorStyle::SteadyBar)? + }) } } @@ -164,3 +151,43 @@ impl Deref for Term { impl DerefMut for Term { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } + +// --- Mouse support +mod mouse { + use crossterm::event::{DisableMouseCapture, EnableMouseCapture}; + use yazi_config::MANAGER; + + pub struct SetMouse(pub bool); + + impl crossterm::Command for SetMouse { + fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result { + if MANAGER.mouse_events.is_empty() { + Ok(()) + } else if self.0 { + EnableMouseCapture.write_ansi(f) + } else { + DisableMouseCapture.write_ansi(f) + } + } + + #[cfg(windows)] + fn execute_winapi(&self) -> std::io::Result<()> { + if MANAGER.mouse_events.is_empty() { + Ok(()) + } else if self.0 { + EnableMouseCapture.execute_winapi() + } else { + DisableMouseCapture.execute_winapi() + } + } + + #[cfg(windows)] + fn is_ansi_code_supported(&self) -> bool { + if self.0 { + EnableMouseCapture.is_ansi_code_supported() + } else { + DisableMouseCapture.is_ansi_code_supported() + } + } + } +} diff --git a/yazi-plugin/src/bindings/window.rs b/yazi-plugin/src/bindings/window.rs index c8da6bde7..32b40a45e 100644 --- a/yazi-plugin/src/bindings/window.rs +++ b/yazi-plugin/src/bindings/window.rs @@ -1,5 +1,5 @@ use mlua::{FromLua, UserData}; -use yazi_shared::term::Term; +use yazi_adaptor::Dimension; #[derive(Debug, Clone, Copy, FromLua)] pub struct Window { @@ -11,7 +11,7 @@ pub struct Window { impl Default for Window { fn default() -> Self { - let ws = Term::size(); + let ws = Dimension::available(); Self { rows: ws.rows, cols: ws.columns, width: ws.width, height: ws.height } } } diff --git a/yazi-shared/src/lib.rs b/yazi-shared/src/lib.rs index 1cef2c7ad..dc11a9b3d 100644 --- a/yazi-shared/src/lib.rs +++ b/yazi-shared/src/lib.rs @@ -12,7 +12,7 @@ mod natsort; mod number; mod os; mod ro_cell; -pub mod term; +mod terminal; pub mod theme; mod throttle; mod time; @@ -30,6 +30,7 @@ pub use number::*; #[cfg(unix)] pub use os::*; pub use ro_cell::*; +pub use terminal::*; pub use throttle::*; pub use time::*; pub use translit::*; diff --git a/yazi-shared/src/term/csi_u.rs b/yazi-shared/src/term/csi_u.rs deleted file mode 100644 index 04a3e90dc..000000000 --- a/yazi-shared/src/term/csi_u.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::time::Duration; - -use anyhow::{bail, Result}; -use tokio::{io::{stdin, AsyncReadExt, BufReader}, time::timeout}; -use tracing::error; - -use super::Term; - -impl Term { - pub async fn read_until_da1() -> Result { - let read = async { - let mut stdin = BufReader::new(stdin()); - let mut buf = String::with_capacity(200); - loop { - let mut c = [0; 1]; - if stdin.read(&mut c).await? == 0 { - bail!("unexpected EOF"); - } - buf.push(c[0] as char); - if c[0] == b'c' && buf.contains("\x1b[?") { - break; - } - } - Ok(buf) - }; - - let timeout = timeout(Duration::from_secs(10), read).await; - if let Err(ref e) = timeout { - error!("read_until_da1: {e:?}"); - } - - timeout? - } -} diff --git a/yazi-shared/src/term/cursor.rs b/yazi-shared/src/term/cursor.rs deleted file mode 100644 index c381f2750..000000000 --- a/yazi-shared/src/term/cursor.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::io::{stderr, Write}; - -use anyhow::Result; -use crossterm::{cursor::{MoveTo, SetCursorStyle}, queue}; - -use super::Term; - -impl Term { - #[inline] - pub fn move_to(w: &mut impl Write, x: u16, y: u16) -> Result<()> { Ok(queue!(w, MoveTo(x, y))?) } - - #[inline] - pub fn set_cursor_block() -> Result<()> { Ok(queue!(stderr(), SetCursorStyle::BlinkingBlock)?) } - - #[inline] - pub fn set_cursor_bar() -> Result<()> { Ok(queue!(stderr(), SetCursorStyle::BlinkingBar)?) } -} diff --git a/yazi-shared/src/term/mod.rs b/yazi-shared/src/term/mod.rs deleted file mode 100644 index 91aab0b9b..000000000 --- a/yazi-shared/src/term/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![allow(clippy::module_inception)] - -mod csi_u; -mod cursor; -mod term; - -pub use term::*; diff --git a/yazi-shared/src/terminal.rs b/yazi-shared/src/terminal.rs new file mode 100644 index 000000000..ed66d0ccd --- /dev/null +++ b/yazi-shared/src/terminal.rs @@ -0,0 +1,10 @@ +use std::io::Write; + +use crossterm::queue; + +#[inline] +pub fn terminal_clear(w: &mut impl Write) -> std::io::Result<()> { + queue!(w, crossterm::terminal::Clear(crossterm::terminal::ClearType::All))?; + writeln!(w)?; + w.flush() +}