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

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 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<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 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,

View File

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

View File

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

View File

@ -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::*;

View File

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

View File

@ -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"

View File

@ -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,

View File

@ -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;

View File

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

View File

@ -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<PathBuf>,
new: Vec<PathBuf>,
) -> 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());

View File

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

View File

@ -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<TaskSummary> {

View File

@ -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,

View File

@ -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) -> ! {

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,

View File

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

View File

@ -1,4 +1,4 @@
use yazi_shared::term::Term;
use crate::Term;
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 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<CrosstermBackend<BufWriter<Stderr>>>,
last_area: Rect,
last_buffer: Buffer,
}
impl Term {
pub fn start() -> Result<Self> {
pub(super) fn start() -> Result<Self> {
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<CompletedFrame> {
pub(super) fn draw(&mut self, f: impl FnOnce(&mut Frame)) -> io::Result<CompletedFrame> {
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<CompletedFrame> {
pub(super) fn draw_partial(&mut self, f: impl FnOnce(&mut Frame)) -> io::Result<CompletedFrame> {
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(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 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)))
}
#[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()
}
}
}
}

View File

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

View File

@ -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::*;

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