mirror of
https://github.com/sxyazi/yazi.git
synced 2026-05-13 08:16:40 +00:00
refactor: move Term to its own yazi-term crate (#3629)
This commit is contained in:
parent
24c60419bb
commit
583345296f
27 changed files with 197 additions and 124 deletions
|
|
@ -12,10 +12,17 @@ repository = "https://github.com/sxyazi/yazi"
|
|||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
yazi-macro = { path = "../yazi-macro", version = "26.1.22" }
|
||||
yazi-config = { path = "../yazi-config", version = "26.1.22" }
|
||||
yazi-emulator = { path = "../yazi-emulator", version = "26.1.22" }
|
||||
yazi-macro = { path = "../yazi-macro", version = "26.1.22" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "26.1.22" }
|
||||
yazi-shim = { path = "../yazi-shim", version = "26.1.22" }
|
||||
yazi-tty = { path = "../yazi-tty", version = "26.1.22" }
|
||||
|
||||
# External dependencies
|
||||
crossterm = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
crossterm = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
libc = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
pub struct SetBackground(pub bool, pub String);
|
||||
|
||||
impl crossterm::Command for SetBackground {
|
||||
fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
|
||||
if self.1.is_empty() {
|
||||
Ok(())
|
||||
} else if self.0 {
|
||||
write!(f, "\x1b]11;{}\x1b\\", self.1)
|
||||
} else {
|
||||
write!(f, "\x1b]111\x1b\\")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn execute_winapi(&self) -> std::io::Result<()> { Ok(()) }
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
|
||||
|
||||
use crossterm::cursor::SetCursorStyle;
|
||||
|
||||
static SHAPE: AtomicU8 = AtomicU8::new(0);
|
||||
static BLINK: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub struct RestoreCursor;
|
||||
|
||||
impl RestoreCursor {
|
||||
pub fn store(resp: &str) {
|
||||
SHAPE.store(
|
||||
resp
|
||||
.split_once("\x1bP1$r")
|
||||
.and_then(|(_, s)| s.bytes().next())
|
||||
.filter(|&b| matches!(b, b'0'..=b'6'))
|
||||
.map_or(u8::MAX, |b| b - b'0'),
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
BLINK.store(resp.contains("\x1b[?12;1$y"), Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
impl crossterm::Command for RestoreCursor {
|
||||
fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
|
||||
let (shape, shape_blink) = match SHAPE.load(Ordering::Relaxed) {
|
||||
u8::MAX => (0, None),
|
||||
n => (n.max(1).div_ceil(2), Some(n.max(1) & 1 == 1)),
|
||||
};
|
||||
|
||||
let blink = shape_blink.unwrap_or(BLINK.load(Ordering::Relaxed));
|
||||
Ok(match shape {
|
||||
2 if blink => SetCursorStyle::BlinkingUnderScore.write_ansi(f)?,
|
||||
2 if !blink => SetCursorStyle::SteadyUnderScore.write_ansi(f)?,
|
||||
3 if blink => SetCursorStyle::BlinkingBar.write_ansi(f)?,
|
||||
3 if !blink => SetCursorStyle::SteadyBar.write_ansi(f)?,
|
||||
_ if blink => SetCursorStyle::DefaultUserShape.write_ansi(f)?,
|
||||
_ if !blink => SetCursorStyle::SteadyBlock.write_ansi(f)?,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn execute_winapi(&self) -> std::io::Result<()> { Ok(()) }
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
pub struct If<T: crossterm::Command>(pub bool, pub T);
|
||||
|
||||
impl<T: crossterm::Command> crossterm::Command for If<T> {
|
||||
fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
|
||||
if self.0 { self.1.write_ansi(f) } else { Ok(()) }
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn execute_winapi(&self) -> std::io::Result<()> {
|
||||
if self.0 { self.1.execute_winapi() } else { Ok(()) }
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_ansi_code_supported(&self) -> bool { self.1.is_ansi_code_supported() }
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(background cursor r#if);
|
||||
yazi_macro::mod_flat!(option state term);
|
||||
|
|
|
|||
17
yazi-term/src/option.rs
Normal file
17
yazi-term/src/option.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
use yazi_config::{THEME, YAZI};
|
||||
|
||||
pub(super) struct TermOption {
|
||||
pub bg: String,
|
||||
pub mouse: bool,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for TermOption {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bg: THEME.app.bg_color(),
|
||||
mouse: !YAZI.mgr.mouse_events.get().is_empty(),
|
||||
title: YAZI.mgr.title(),
|
||||
}
|
||||
}
|
||||
}
|
||||
45
yazi-term/src/state.rs
Normal file
45
yazi-term/src/state.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use crate::TermOption;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) struct TermState {
|
||||
pub(super) bg: bool,
|
||||
pub(super) csi_u: bool,
|
||||
pub(super) mouse: bool,
|
||||
pub(super) title: bool,
|
||||
pub(super) cursor_shape: u8,
|
||||
pub(super) cursor_blink: bool,
|
||||
}
|
||||
|
||||
impl TermState {
|
||||
pub(super) const fn default() -> Self {
|
||||
Self {
|
||||
bg: false,
|
||||
csi_u: false,
|
||||
mouse: false,
|
||||
title: false,
|
||||
cursor_shape: 0,
|
||||
cursor_blink: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn new(resp: &str, opt: &TermOption) -> Self {
|
||||
let csi_u = resp.contains("\x1b[?0u");
|
||||
|
||||
let cursor_shape = resp
|
||||
.split_once("\x1bP1$r")
|
||||
.and_then(|(_, s)| s.bytes().next())
|
||||
.filter(|&b| matches!(b, b'0'..=b'6'))
|
||||
.map_or(u8::MAX, |b| b - b'0');
|
||||
|
||||
let cursor_blink = resp.contains("\x1b[?12;1$y");
|
||||
|
||||
Self {
|
||||
bg: !opt.bg.is_empty(),
|
||||
csi_u,
|
||||
mouse: opt.mouse,
|
||||
title: opt.title.is_some(),
|
||||
cursor_shape,
|
||||
cursor_blink,
|
||||
}
|
||||
}
|
||||
}
|
||||
151
yazi-term/src/term.rs
Normal file
151
yazi-term/src/term.rs
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
use std::{io, ops::Deref};
|
||||
|
||||
use anyhow::Result;
|
||||
use crossterm::{event::{DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, EnableFocusChange, EnableMouseCapture, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, execute, queue, style::Print, terminal::{EnterAlternateScreen, LeaveAlternateScreen, SetTitle, disable_raw_mode, enable_raw_mode}};
|
||||
use ratatui::{CompletedFrame, Frame, Terminal, backend::CrosstermBackend, buffer::Buffer, layout::Rect};
|
||||
use yazi_emulator::{Emulator, Mux, TMUX};
|
||||
use yazi_shared::SyncCell;
|
||||
use yazi_shim::crossterm::{If, RestoreBackground, RestoreCursor, SetBackground};
|
||||
use yazi_tty::{TTY, TtyWriter};
|
||||
|
||||
use crate::{TermOption, TermState};
|
||||
|
||||
static STATE: SyncCell<TermState> = SyncCell::new(TermState::default());
|
||||
|
||||
pub struct Term {
|
||||
inner: Terminal<CrosstermBackend<TtyWriter<'static>>>,
|
||||
last_area: Rect,
|
||||
last_buffer: Buffer,
|
||||
}
|
||||
|
||||
impl Term {
|
||||
pub fn start() -> Result<Self> {
|
||||
let opt = TermOption::default();
|
||||
let mut term = Self {
|
||||
inner: Terminal::new(CrosstermBackend::new(TTY.writer()))?,
|
||||
last_area: Default::default(),
|
||||
last_buffer: Default::default(),
|
||||
};
|
||||
|
||||
enable_raw_mode()?;
|
||||
static FIRST: SyncCell<bool> = SyncCell::new(false);
|
||||
if FIRST.replace(true) && yazi_emulator::TMUX.get() {
|
||||
yazi_emulator::Mux::tmux_passthrough();
|
||||
}
|
||||
|
||||
execute!(
|
||||
TTY.writer(),
|
||||
If(!TMUX.get(), EnterAlternateScreen),
|
||||
Print("\x1bP$q q\x1b\\"), // Request cursor shape (DECRQSS query for DECSCUSR)
|
||||
Print("\x1b[?12$p"), // Request cursor blink status (DECRQM query for DECSET 12)
|
||||
Print("\x1b[?u"), // Request keyboard enhancement flags (CSI u)
|
||||
Print("\x1b[0c"), // Request device attributes
|
||||
If(TMUX.get(), EnterAlternateScreen),
|
||||
SetBackground(&opt.bg), // Set app background
|
||||
EnableBracketedPaste,
|
||||
EnableFocusChange,
|
||||
If(opt.mouse, EnableMouseCapture),
|
||||
)?;
|
||||
|
||||
let resp = Emulator::read_until_da1();
|
||||
Mux::tmux_drain()?;
|
||||
|
||||
STATE.set(TermState::new(&resp, &opt));
|
||||
if STATE.get().csi_u {
|
||||
_ = queue!(
|
||||
TTY.writer(),
|
||||
PushKeyboardEnhancementFlags(
|
||||
KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
|
||||
| KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(s) = opt.title {
|
||||
queue!(TTY.writer(), SetTitle(s)).ok();
|
||||
}
|
||||
|
||||
term.inner.hide_cursor()?;
|
||||
term.inner.clear()?;
|
||||
term.inner.flush()?;
|
||||
Ok(term)
|
||||
}
|
||||
|
||||
fn stop(&mut self) -> Result<()> {
|
||||
let state = STATE.get();
|
||||
|
||||
execute!(
|
||||
TTY.writer(),
|
||||
If(state.mouse, DisableMouseCapture),
|
||||
If(state.bg, RestoreBackground),
|
||||
If(state.csi_u, PopKeyboardEnhancementFlags),
|
||||
RestoreCursor { shape: state.cursor_shape, blink: state.cursor_blink },
|
||||
If(state.title, SetTitle("")),
|
||||
DisableFocusChange,
|
||||
DisableBracketedPaste,
|
||||
LeaveAlternateScreen,
|
||||
)?;
|
||||
|
||||
self.inner.show_cursor()?;
|
||||
Ok(disable_raw_mode()?)
|
||||
}
|
||||
|
||||
pub fn goodbye(f: impl FnOnce() -> i32) -> ! {
|
||||
let state = STATE.get();
|
||||
|
||||
execute!(
|
||||
TTY.writer(),
|
||||
If(state.mouse, DisableMouseCapture),
|
||||
If(state.bg, RestoreBackground),
|
||||
If(state.csi_u, PopKeyboardEnhancementFlags),
|
||||
RestoreCursor { shape: state.cursor_shape, blink: state.cursor_blink },
|
||||
If(state.title, SetTitle("")),
|
||||
DisableFocusChange,
|
||||
DisableBracketedPaste,
|
||||
LeaveAlternateScreen,
|
||||
crossterm::cursor::Show
|
||||
)
|
||||
.ok();
|
||||
|
||||
disable_raw_mode().ok();
|
||||
|
||||
std::process::exit(f());
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, f: impl FnOnce(&mut Frame)) -> io::Result<CompletedFrame<'_>> {
|
||||
let last = self.inner.draw(f)?;
|
||||
|
||||
self.last_area = last.area;
|
||||
self.last_buffer = last.buffer.clone();
|
||||
Ok(last)
|
||||
}
|
||||
|
||||
pub 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() {
|
||||
for x in self.last_area.left()..self.last_area.right() {
|
||||
let mut cell = self.last_buffer[(x, y)].clone();
|
||||
cell.skip = false;
|
||||
buffer[(x, y)] = cell;
|
||||
}
|
||||
}
|
||||
|
||||
f(frame);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn can_partial(&mut self) -> bool {
|
||||
self.inner.autoresize().is_ok() && self.last_area == self.inner.get_frame().area()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Term {
|
||||
fn drop(&mut self) { self.stop().ok(); }
|
||||
}
|
||||
|
||||
impl Deref for Term {
|
||||
type Target = Terminal<CrosstermBackend<TtyWriter<'static>>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue