feat: support completely disabling mouse with mouse_events=[]; add new cursor_blink to control cursor style of input components (#1139)

This commit is contained in:
三咲雅 · Misaki Masa 2024-06-08 18:29:50 +08:00 committed by GitHub
parent 94628cad9e
commit 1166f86523
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 206 additions and 158 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","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"} {"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":[]}

View file

@ -2,9 +2,9 @@ use std::{io::Write, path::Path, process::Stdio};
use ansi_to_tui::IntoText; use ansi_to_tui::IntoText;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use crossterm::{cursor::MoveTo, queue};
use ratatui::layout::Rect; use ratatui::layout::Rect;
use tokio::process::Command; use tokio::process::Command;
use yazi_shared::term::Term;
use crate::{Adaptor, Emulator}; use crate::{Adaptor, Emulator};
@ -58,7 +58,7 @@ impl Chafa {
Emulator::move_lock((max.x, max.y), |stderr| { Emulator::move_lock((max.x, max.y), |stderr| {
for (i, line) in lines.into_iter().enumerate() { for (i, line) in lines.into_iter().enumerate() {
stderr.write_all(line)?; 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) Ok(area)
}) })
@ -68,7 +68,7 @@ impl Chafa {
let s = " ".repeat(area.width as usize); let s = " ".repeat(area.width as usize);
Emulator::move_lock((0, 0), |stderr| { Emulator::move_lock((0, 0), |stderr| {
for y in area.top()..area.bottom() { for y in area.top()..area.bottom() {
Term::move_to(stderr, area.x, y)?; queue!(stderr, MoveTo(area.x, y))?;
write!(stderr, "{s}")?; write!(stderr, "{s}")?;
} }
Ok(()) Ok(())

View file

@ -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)))
}
}

View file

@ -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 crossterm::{cursor::{RestorePosition, SavePosition}, execute, style::Print, terminal::{disable_raw_mode, enable_raw_mode}};
use scopeguard::defer; use scopeguard::defer;
use tracing::warn; use tokio::{io::{AsyncReadExt, BufReader}, time::timeout};
use yazi_shared::{env_exists, term::Term}; use tracing::{error, warn};
use yazi_shared::env_exists;
use crate::{Adaptor, CLOSE, ESCAPE, START, TMUX}; use crate::{Adaptor, CLOSE, ESCAPE, START, TMUX};
@ -129,7 +130,7 @@ impl Emulator {
RestorePosition RestorePosition
)?; )?;
let resp = futures::executor::block_on(Term::read_until_da1())?; let resp = futures::executor::block_on(Self::read_until_da1())?;
let names = [ let names = [
("kitty", Self::Kitty), ("kitty", Self::Kitty),
("Konsole", Self::Konsole), ("Konsole", Self::Konsole),
@ -187,4 +188,29 @@ impl Emulator {
buf.flush()?; buf.flush()?;
result result
} }
pub async fn read_until_da1() -> Result<String> {
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?
}
} }

View file

@ -5,7 +5,8 @@ use exif::{In, Tag};
use image::{codecs::jpeg::JpegEncoder, imageops::{self, FilterType}, io::Limits, DynamicImage}; use image::{codecs::jpeg::JpegEncoder, imageops::{self, FilterType}, io::Limits, DynamicImage};
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_config::{PREVIEW, TASKS}; use yazi_config::{PREVIEW, TASKS};
use yazi_shared::term::Term;
use crate::Dimension;
pub struct Image; pub struct Image;
@ -77,7 +78,7 @@ impl Image {
} }
pub(super) fn max_pixel(rect: Rect) -> (u32, u32) { pub(super) fn max_pixel(rect: Rect) -> (u32, u32) {
Term::ratio() Dimension::ratio()
.map(|(r1, r2)| { .map(|(r1, r2)| {
let (w, h) = ((rect.width as f64 * r1) as u32, (rect.height as f64 * r2) as u32); 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)) (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 { pub(super) fn pixel_area(size: (u32, u32), rect: Rect) -> Rect {
Term::ratio() Dimension::ratio()
.map(|(r1, r2)| Rect { .map(|(r1, r2)| Rect {
x: rect.x, x: rect.x,
y: rect.y, y: rect.y,

View file

@ -2,9 +2,9 @@ use std::{io::Write, path::Path};
use anyhow::Result; use anyhow::Result;
use base64::{engine::{general_purpose::STANDARD, Config}, Engine}; use base64::{engine::{general_purpose::STANDARD, Config}, Engine};
use crossterm::{cursor::MoveTo, queue};
use image::{codecs::jpeg::JpegEncoder, DynamicImage}; use image::{codecs::jpeg::JpegEncoder, DynamicImage};
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_shared::term::Term;
use super::image::Image; use super::image::Image;
use crate::{adaptor::Adaptor, Emulator, CLOSE, START}; use crate::{adaptor::Adaptor, Emulator, CLOSE, START};
@ -29,7 +29,7 @@ impl Iterm2 {
let s = " ".repeat(area.width as usize); let s = " ".repeat(area.width as usize);
Emulator::move_lock((0, 0), |stderr| { Emulator::move_lock((0, 0), |stderr| {
for y in area.top()..area.bottom() { for y in area.top()..area.bottom() {
Term::move_to(stderr, area.x, y)?; queue!(stderr, MoveTo(area.x, y))?;
write!(stderr, "{s}")?; write!(stderr, "{s}")?;
} }
Ok(()) Ok(())

View file

@ -3,9 +3,9 @@ use std::{io::Write, path::Path};
use anyhow::Result; use anyhow::Result;
use base64::{engine::general_purpose, Engine}; use base64::{engine::general_purpose, Engine};
use crossterm::{cursor::MoveTo, queue};
use image::DynamicImage; use image::DynamicImage;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_shared::term::Term;
use super::image::Image; use super::image::Image;
use crate::{adaptor::Adaptor, Emulator, CLOSE, ESCAPE, START}; use crate::{adaptor::Adaptor, Emulator, CLOSE, ESCAPE, START};
@ -333,7 +333,7 @@ impl Kitty {
let s = " ".repeat(area.width as usize); let s = " ".repeat(area.width as usize);
Emulator::move_lock((0, 0), |stderr| { Emulator::move_lock((0, 0), |stderr| {
for y in area.top()..area.bottom() { for y in area.top()..area.bottom() {
Term::move_to(stderr, area.x, y)?; queue!(stderr, MoveTo(area.x, y))?;
write!(stderr, "{s}")?; write!(stderr, "{s}")?;
} }

View file

@ -2,6 +2,7 @@
mod adaptor; mod adaptor;
mod chafa; mod chafa;
mod dimension;
mod emulator; mod emulator;
mod image; mod image;
mod iterm2; mod iterm2;
@ -12,6 +13,7 @@ mod ueberzug;
pub use adaptor::*; pub use adaptor::*;
use chafa::*; use chafa::*;
pub use dimension::*;
pub use emulator::*; pub use emulator::*;
use iterm2::*; use iterm2::*;
use kitty::*; use kitty::*;

View file

@ -2,10 +2,10 @@ use std::{io::Write, path::Path};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use color_quant::NeuQuant; use color_quant::NeuQuant;
use crossterm::{cursor::MoveTo, queue};
use image::DynamicImage; use image::DynamicImage;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_config::PREVIEW; use yazi_config::PREVIEW;
use yazi_shared::term::Term;
use crate::{adaptor::Adaptor, Emulator, Image, CLOSE, ESCAPE, START}; use crate::{adaptor::Adaptor, Emulator, Image, CLOSE, ESCAPE, START};
@ -29,7 +29,7 @@ impl Sixel {
let s = " ".repeat(area.width as usize); let s = " ".repeat(area.width as usize);
Emulator::move_lock((0, 0), |stderr| { Emulator::move_lock((0, 0), |stderr| {
for y in area.top()..area.bottom() { for y in area.top()..area.bottom() {
Term::move_to(stderr, area.x, y)?; queue!(stderr, MoveTo(area.x, y))?;
write!(stderr, "{s}")?; write!(stderr, "{s}")?;
} }
Ok(()) Ok(())

View file

@ -33,12 +33,12 @@ edit = [
{ run = 'code -w "%*"', block = true, desc = "code (block)", for = "windows" }, { run = 'code -w "%*"', block = true, desc = "code (block)", for = "windows" },
] ]
open = [ open = [
{ run = 'xdg-open "$@"', desc = "Open", for = "linux" }, { run = 'xdg-open "$1"', desc = "Open", for = "linux" },
{ run = 'open "$@"', desc = "Open", for = "macos" }, { run = 'open "$@"', desc = "Open", for = "macos" },
{ run = 'start "" "%1"', orphan = true, desc = "Open", for = "windows" }, { run = 'start "" "%1"', orphan = true, desc = "Open", for = "windows" },
] ]
reveal = [ 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 = 'open -R "$1"', desc = "Reveal", for = "macos" },
{ run = 'explorer /select, "%1"', orphan = true, desc = "Reveal", for = "windows" }, { 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" }, { run = '''exiftool "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show EXIF", for = "unix" },
@ -122,6 +122,8 @@ previewers = [
] ]
[input] [input]
cursor_blink = true
# cd # cd
cd_title = "Change directory:" cd_title = "Change directory:"
cd_origin = "top-center" cd_origin = "top-center"

View file

@ -5,6 +5,8 @@ use crate::MERGED_YAZI;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Input { pub struct Input {
pub cursor_blink: bool,
// cd // cd
pub cd_title: String, pub cd_title: String,
pub cd_origin: Origin, pub cd_origin: Origin,

View file

@ -1,6 +1,5 @@
use crossterm::terminal::WindowSize; use crossterm::terminal::WindowSize;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_shared::term::Term;
use super::{Offset, Origin}; use super::{Offset, Origin};
@ -14,10 +13,9 @@ impl Position {
#[inline] #[inline]
pub fn new(origin: Origin, offset: Offset) -> Self { Self { origin, offset } } 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::*; use Origin::*;
let Offset { x, y, width, height } = self.offset; let Offset { x, y, width, height } = self.offset;
let WindowSize { columns, rows, .. } = Term::size();
let max_x = columns.saturating_sub(width); let max_x = columns.saturating_sub(width);
let new_x = match self.origin { 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 Offset { x, y, width, height } = offset;
let WindowSize { columns, rows, .. } = Term::size();
let above = let above =
base.y.saturating_add(base.height).saturating_add(height).saturating_add_signed(y) > rows; base.y.saturating_add(base.height).saturating_add(height).saturating_add_signed(y) > rows;

View file

@ -1,7 +1,8 @@
use crossterm::event::KeyCode; use crossterm::event::KeyCode;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use yazi_adaptor::Dimension;
use yazi_config::{keymap::{Control, Key}, KEYMAP}; 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 super::HELP_MARGIN;
use crate::input::Input; use crate::input::Input;
@ -22,7 +23,7 @@ pub struct Help {
impl Help { impl Help {
#[inline] #[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) { pub fn toggle(&mut self, layer: Layer) {
self.visible = !self.visible; self.visible = !self.visible;
@ -106,7 +107,7 @@ impl Help {
return None; return None;
} }
if let Some(kw) = self.keyword() { 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 None
} }

View file

@ -6,7 +6,7 @@ use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
use yazi_config::{OPEN, PREVIEW}; use yazi_config::{OPEN, PREVIEW};
use yazi_dds::Pubsub; use yazi_dds::Pubsub;
use yazi_proxy::{AppProxy, TasksProxy, HIDER, WATCHER}; 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; use crate::manager::Manager;
@ -52,7 +52,7 @@ impl Manager {
old: Vec<PathBuf>, old: Vec<PathBuf>,
new: Vec<PathBuf>, new: Vec<PathBuf>,
) -> Result<()> { ) -> Result<()> {
Term::clear(&mut stderr())?; terminal_clear(&mut stderr())?;
if old.len() != new.len() { if old.len() != new.len() {
eprintln!("Number of old and new differ, press ENTER to exit"); eprintln!("Number of old and new differ, press ENTER to exit");
stdin().read_exact(&mut [0]).await?; stdin().read_exact(&mut [0]).await?;
@ -108,7 +108,7 @@ impl Manager {
} }
async fn output_failed(failed: Vec<(PathBuf, PathBuf, anyhow::Error)>) -> Result<()> { 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()); let mut stderr = BufWriter::new(stderr().lock());

View file

@ -4,7 +4,7 @@ use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
use scopeguard::defer; use scopeguard::defer;
use tokio::{io::{stdin, AsyncReadExt}, select, sync::mpsc, time}; use tokio::{io::{stdin, AsyncReadExt}, select, sync::mpsc, time};
use yazi_proxy::{AppProxy, HIDER}; use yazi_proxy::{AppProxy, HIDER};
use yazi_shared::{event::Cmd, term::Term}; use yazi_shared::{event::Cmd, terminal_clear};
use crate::tasks::Tasks; use crate::tasks::Tasks;
@ -30,7 +30,7 @@ impl Tasks {
defer!(AppProxy::resume()); defer!(AppProxy::resume());
AppProxy::stop().await; 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(); BufWriter::new(stderr().lock()).write_all(mem::take(&mut buffered).as_bytes()).ok();
defer! { disable_raw_mode().ok(); } defer! { disable_raw_mode().ok(); }

View file

@ -2,8 +2,9 @@ use std::{sync::Arc, time::Duration};
use parking_lot::Mutex; use parking_lot::Mutex;
use tokio::{task::JoinHandle, time::sleep}; use tokio::{task::JoinHandle, time::sleep};
use yazi_adaptor::Dimension;
use yazi_scheduler::{Ongoing, Scheduler, TaskSummary}; 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}; use super::{TasksProgress, TASKS_BORDER, TASKS_PADDING, TASKS_PERCENT};
@ -53,7 +54,8 @@ impl Tasks {
#[inline] #[inline]
pub fn limit() -> usize { 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<TaskSummary> { pub fn paginate(&self) -> Vec<TaskSummary> {

View file

@ -4,9 +4,9 @@ use anyhow::Result;
use crossterm::event::KeyEvent; use crossterm::event::KeyEvent;
use yazi_config::keymap::Key; use yazi_config::keymap::Key;
use yazi_core::input::InputMode; 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) struct App {
pub(crate) cx: Ctx, pub(crate) cx: Ctx,

View file

@ -1,9 +1,9 @@
use std::ffi::OsString; use std::ffi::OsString;
use yazi_boot::ARGS; 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 { impl App {
pub(crate) fn quit(&mut self, opt: EventQuit) -> ! { pub(crate) fn quit(&mut self, opt: EventQuit) -> ! {

View file

@ -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 { impl App {
pub(crate) fn resume(&mut self, _: Cmd) { pub(crate) fn resume(&mut self, _: Cmd) {

View file

@ -1,12 +1,13 @@
use crossterm::terminal::WindowSize; use crossterm::terminal::WindowSize;
use ratatui::layout::Rect; 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}; use crate::{app::App, notify};
impl App { impl App {
pub(crate) fn update_notify(&mut self, cmd: Cmd) { pub(crate) fn update_notify(&mut self, cmd: Cmd) {
let WindowSize { rows, columns, .. } = Term::size(); let WindowSize { rows, columns, .. } = Dimension::available();
let area = let area =
notify::Layout::available(Rect { x: 0, y: 0, width: columns, height: rows }); notify::Layout::available(Rect { x: 0, y: 0, width: columns, height: rows });

View file

@ -1,6 +1,7 @@
use std::path::MAIN_SEPARATOR; use std::path::MAIN_SEPARATOR;
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, List, ListItem, Widget}}; use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, List, ListItem, Widget}};
use yazi_adaptor::Dimension;
use yazi_config::{popup::{Offset, Position}, THEME}; use yazi_config::{popup::{Offset, Position}, THEME};
use crate::Ctx; use crate::Ctx;
@ -40,7 +41,7 @@ impl<'a> Widget for Completion<'a> {
.collect(); .collect();
let input_area = self.cx.area(&self.cx.input.position); 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, x: 1,
y: 0, y: 0,
width: input_area.width.saturating_sub(2), width: input_area.width.saturating_sub(2),

View file

@ -1,4 +1,5 @@
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_adaptor::Dimension;
use yazi_config::popup::{Origin, Position}; 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}; 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 { pub fn area(&self, position: &Position) -> Rect {
let ws = Dimension::available();
if position.origin != Origin::Hovered { if position.origin != Origin::Hovered {
return position.rect(); return position.rect(ws);
} }
if let Some(r) = if let Some(r) =
self.manager.hovered().and_then(|h| self.manager.current().rect_current(&h.url)) 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 { } else {
Position::new(Origin::TopCenter, position.offset).rect() Position::new(Origin::TopCenter, position.offset).rect(ws)
} }
} }

View file

@ -159,7 +159,6 @@ impl<'a> Executor<'a> {
on!(open_with); on!(open_with);
on!(process_exec); on!(process_exec);
#[allow(clippy::single_match)]
match cmd.name.as_str() { match cmd.name.as_str() {
// Help // Help
"help" => self.app.cx.help.toggle(Layer::Tasks), "help" => self.app.cx.help.toggle(Layer::Tasks),
@ -182,7 +181,6 @@ impl<'a> Executor<'a> {
on!(close); on!(close);
on!(arrow); on!(arrow);
#[allow(clippy::single_match)]
match cmd.name.as_str() { match cmd.name.as_str() {
// Help // Help
"help" => self.app.cx.help.toggle(Layer::Select), "help" => self.app.cx.help.toggle(Layer::Select),
@ -233,7 +231,6 @@ impl<'a> Executor<'a> {
on!(undo); on!(undo);
on!(redo); on!(redo);
#[allow(clippy::single_match)]
match cmd.name.as_str() { match cmd.name.as_str() {
// Help // Help
"help" => self.app.cx.help.toggle(Layer::Input), "help" => self.app.cx.help.toggle(Layer::Input),
@ -262,7 +259,6 @@ impl<'a> Executor<'a> {
on!(arrow); on!(arrow);
on!(filter); on!(filter);
#[allow(clippy::single_match)]
match cmd.name.as_str() { match cmd.name.as_str() {
"close" => self.app.cx.help.toggle(Layer::Help), "close" => self.app.cx.help.toggle(Layer::Help),
// Plugin // Plugin
@ -285,7 +281,6 @@ impl<'a> Executor<'a> {
on!(close); on!(close);
on!(arrow); on!(arrow);
#[allow(clippy::single_match)]
match cmd.name.as_str() { match cmd.name.as_str() {
"close_input" => self.app.cx.input.close(cmd), "close_input" => self.app.cx.input.close(cmd),
// Help // Help

View file

@ -6,9 +6,8 @@ use syntect::easy::HighlightLines;
use yazi_config::THEME; use yazi_config::THEME;
use yazi_core::input::InputMode; use yazi_core::input::InputMode;
use yazi_plugin::external::Highlighter; use yazi_plugin::external::Highlighter;
use yazi_shared::term::Term;
use crate::Ctx; use crate::{Ctx, Term};
pub(crate) struct Input<'a> { pub(crate) struct Input<'a> {
cx: &'a Ctx, cx: &'a Ctx,

View file

@ -21,6 +21,7 @@ mod router;
mod select; mod select;
mod signals; mod signals;
mod tasks; mod tasks;
mod term;
mod which; mod which;
use context::*; use context::*;
@ -31,6 +32,7 @@ use panic::*;
use root::*; use root::*;
use router::*; use router::*;
use signals::*; use signals::*;
use term::*;
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {

View file

@ -1,4 +1,4 @@
use yazi_shared::term::Term; use crate::Term;
pub(super) struct Panic; pub(super) struct Panic;

View file

@ -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 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 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); static CSI_U: AtomicBool = AtomicBool::new(false);
pub struct Term { pub(super) struct Term {
inner: Terminal<CrosstermBackend<BufWriter<Stderr>>>, inner: Terminal<CrosstermBackend<BufWriter<Stderr>>>,
last_area: Rect, last_area: Rect,
last_buffer: Buffer, last_buffer: Buffer,
} }
impl Term { impl Term {
pub fn start() -> Result<Self> { pub(super) fn start() -> Result<Self> {
let mut term = Self { let mut term = Self {
inner: Terminal::new(CrosstermBackend::new(BufWriter::new(stderr())))?, inner: Terminal::new(CrosstermBackend::new(BufWriter::new(stderr())))?,
last_area: Default::default(), last_area: Default::default(),
@ -25,13 +27,13 @@ impl Term {
BufWriter::new(stderr()), BufWriter::new(stderr()),
EnterAlternateScreen, EnterAlternateScreen,
EnableBracketedPaste, EnableBracketedPaste,
EnableMouseCapture, mouse::SetMouse(true),
SavePosition, SavePosition,
Print("\x1b[?u\x1b[c"), Print("\x1b[?u\x1b[c"),
RestorePosition 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")) { if resp.is_ok_and(|s| s.contains("\x1b[?0u")) {
queue!( queue!(
stderr(), stderr(),
@ -56,7 +58,7 @@ impl Term {
execute!( execute!(
stderr(), stderr(),
DisableMouseCapture, mouse::SetMouse(false),
DisableBracketedPaste, DisableBracketedPaste,
LeaveAlternateScreen, LeaveAlternateScreen,
crossterm::cursor::SetCursorStyle::DefaultUserShape crossterm::cursor::SetCursorStyle::DefaultUserShape
@ -66,7 +68,7 @@ impl Term {
Ok(disable_raw_mode()?) 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) { if CSI_U.swap(false, Ordering::Relaxed) {
execute!(stderr(), PopKeyboardEnhancementFlags).ok(); execute!(stderr(), PopKeyboardEnhancementFlags).ok();
} }
@ -74,7 +76,7 @@ impl Term {
execute!( execute!(
stderr(), stderr(),
SetTitle(""), SetTitle(""),
DisableMouseCapture, mouse::SetMouse(false),
DisableBracketedPaste, DisableBracketedPaste,
LeaveAlternateScreen, LeaveAlternateScreen,
crossterm::cursor::SetCursorStyle::DefaultUserShape, crossterm::cursor::SetCursorStyle::DefaultUserShape,
@ -87,7 +89,7 @@ impl Term {
std::process::exit(f() as i32); std::process::exit(f() as i32);
} }
pub fn draw(&mut self, f: impl FnOnce(&mut Frame)) -> io::Result<CompletedFrame> { pub(super) fn draw(&mut self, f: impl FnOnce(&mut Frame)) -> io::Result<CompletedFrame> {
let last = self.inner.draw(f)?; let last = self.inner.draw(f)?;
self.last_area = last.area; self.last_area = last.area;
@ -95,7 +97,7 @@ impl Term {
Ok(last) Ok(last)
} }
pub fn draw_partial(&mut self, f: impl FnOnce(&mut Frame)) -> io::Result<CompletedFrame> { pub(super) fn draw_partial(&mut self, f: impl FnOnce(&mut Frame)) -> io::Result<CompletedFrame> {
self.inner.draw(|frame| { self.inner.draw(|frame| {
let buffer = frame.buffer_mut(); let buffer = frame.buffer_mut();
for y in self.last_area.top()..self.last_area.bottom() { for y in self.last_area.top()..self.last_area.bottom() {
@ -111,43 +113,28 @@ impl Term {
} }
#[inline] #[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() self.inner.autoresize().is_ok() && self.last_area == self.inner.get_frame().size()
} }
pub fn size() -> WindowSize { #[inline]
let mut size = WindowSize { rows: 0, columns: 0, width: 0, height: 0 }; pub(super) fn set_cursor_block() -> Result<()> {
if let Ok(s) = crossterm::terminal::window_size() { use crossterm::cursor::SetCursorStyle;
_ = mem::replace(&mut size, s); Ok(if INPUT.cursor_blink {
} queue!(stderr(), SetCursorStyle::BlinkingBlock)?
} else {
if size.rows == 0 || size.columns == 0 { queue!(stderr(), SetCursorStyle::SteadyBlock)?
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] #[inline]
pub fn ratio() -> Option<(f64, f64)> { pub(super) fn set_cursor_bar() -> Result<()> {
let s = Self::size(); use crossterm::cursor::SetCursorStyle;
if s.width == 0 || s.height == 0 { Ok(if INPUT.cursor_blink {
return None; queue!(stderr(), SetCursorStyle::BlinkingBar)?
} } else {
Some((f64::from(s.width) / f64::from(s.columns), f64::from(s.height) / f64::from(s.rows))) queue!(stderr(), SetCursorStyle::SteadyBar)?
} })
#[inline]
pub fn clear(w: &mut impl Write) -> Result<()> {
queue!(w, Clear(ClearType::All))?;
writeln!(w)?;
Ok(w.flush()?)
} }
} }
@ -164,3 +151,43 @@ impl Deref for Term {
impl DerefMut for Term { impl DerefMut for Term {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } 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()
}
}
}
}

View file

@ -1,5 +1,5 @@
use mlua::{FromLua, UserData}; use mlua::{FromLua, UserData};
use yazi_shared::term::Term; use yazi_adaptor::Dimension;
#[derive(Debug, Clone, Copy, FromLua)] #[derive(Debug, Clone, Copy, FromLua)]
pub struct Window { pub struct Window {
@ -11,7 +11,7 @@ pub struct Window {
impl Default for Window { impl Default for Window {
fn default() -> Self { fn default() -> Self {
let ws = Term::size(); let ws = Dimension::available();
Self { rows: ws.rows, cols: ws.columns, width: ws.width, height: ws.height } Self { rows: ws.rows, cols: ws.columns, width: ws.width, height: ws.height }
} }
} }

View file

@ -12,7 +12,7 @@ mod natsort;
mod number; mod number;
mod os; mod os;
mod ro_cell; mod ro_cell;
pub mod term; mod terminal;
pub mod theme; pub mod theme;
mod throttle; mod throttle;
mod time; mod time;
@ -30,6 +30,7 @@ pub use number::*;
#[cfg(unix)] #[cfg(unix)]
pub use os::*; pub use os::*;
pub use ro_cell::*; pub use ro_cell::*;
pub use terminal::*;
pub use throttle::*; pub use throttle::*;
pub use time::*; pub use time::*;
pub use translit::*; pub use translit::*;

View file

@ -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<String> {
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?
}
}

View file

@ -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)?) }
}

View file

@ -1,7 +0,0 @@
#![allow(clippy::module_inception)]
mod csi_u;
mod cursor;
mod term;
pub use term::*;

View file

@ -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()
}