use std::{borrow::Cow, ops::Range}; use anyhow::Result; use ratatui_core::layout::{Rect, Size}; use yazi_macro::act; use yazi_shared::id::Ids; use yazi_shim::path::CROSS_SEPARATOR; use yazi_tty::sequence::SetCursorStyle; use super::{InputSnap, InputSnaps, mode::InputMode, op::InputOp}; use crate::{CLIPBOARD, input::{InputCallback, InputEvent, InputOpt, InputStyles}}; #[derive(Debug, Default)] pub struct Input { pub size: Size, pub snaps: InputSnaps, pub styles: InputStyles, pub obscure: bool, pub blinking: bool, pub realtime: bool, pub completion: bool, pub cb: Option>, pub ticket: Ids, } impl Input { pub fn new(opt: InputOpt) -> Result { let mut input = Self { snaps: InputSnaps::new(opt.value, opt.obscure), styles: opt.styles, obscure: opt.obscure, blinking: opt.blinking, realtime: opt.realtime, completion: opt.completion, cb: opt.cb, ..Default::default() }; if let Some(cursor) = opt.cursor { input.snap_mut().cursor = cursor; act!(r#move, input)?; } Ok(input) } pub fn repos(&mut self, area: Rect) { let size = area.into(); if self.size != size { self.size = size; self.snap_mut().resize(size.width as usize); } } pub(super) fn handle_op(&mut self, cursor: usize, include: bool) -> bool { let old = self.snap().clone(); let snap = self.snap_mut(); match snap.op { InputOp::None | InputOp::Select(_) => { snap.cursor = cursor; } InputOp::Delete(cut, insert, _) => { let range = snap.op.range(cursor, include).unwrap(); let Range { start, end } = snap.idx(range.start)..snap.idx(range.end); let drain = snap.value.drain(start.unwrap()..end.unwrap()).collect::(); if cut { futures::executor::block_on(CLIPBOARD.set(&drain)); } snap.op = InputOp::None; snap.mode = if insert { InputMode::Insert } else { InputMode::Normal }; snap.cursor = range.start; } InputOp::Yank(_) => { let range = snap.op.range(cursor, include).unwrap(); let Range { start, end } = snap.idx(range.start)..snap.idx(range.end); let yanked = &snap.value[start.unwrap()..end.unwrap()]; snap.op = InputOp::None; futures::executor::block_on(CLIPBOARD.set(yanked)); } }; snap.cursor = snap.count().saturating_sub(snap.mode.delta()).min(snap.cursor); if snap == &old { return false; } if !matches!(old.op, InputOp::None | InputOp::Select(_)) { self.snaps.tag(self.size.width as usize).then(|| self.flush_type()); } true } pub(super) fn flush_type(&mut self) { self.ticket.next(); if let Some(cb) = self.cb.as_ref().filter(|_| self.realtime) { cb(InputEvent::Type(self.value().to_owned())); } self.flush_trigger(true); } pub(super) fn flush_trigger(&self, force: bool) { if let Some(cb) = self.cb.as_ref().filter(|_| self.completion) { cb(InputEvent::Trigger( self.partition().0.to_owned(), (!force).then_some(self.ticket.current()), )); } } } impl Input { pub fn value(&self) -> &str { &self.snap().value } pub fn display(&self) -> Cow<'_, str> { if self.obscure { "•".repeat(self.snap().window(self.size.width as usize).len()).into() } else { self.snap().slice(self.snap().window(self.size.width as usize)).into() } } pub fn mode(&self) -> InputMode { self.snap().mode } pub fn cursor(&self) -> u16 { self.snap().width(self.snap().offset..self.snap().cursor) } pub fn cursor_shape(&self) -> SetCursorStyle { use InputMode as M; match self.mode() { M::Normal if self.blinking => SetCursorStyle::BlinkingBlock, M::Normal if !self.blinking => SetCursorStyle::SteadyBlock, M::Insert if self.blinking => SetCursorStyle::BlinkingBar, M::Insert if !self.blinking => SetCursorStyle::SteadyBar, M::Replace if self.blinking => SetCursorStyle::BlinkingUnderline, M::Replace if !self.blinking => SetCursorStyle::SteadyUnderline, M::Normal | M::Insert | M::Replace => unreachable!(), } } pub fn selected(&self) -> Option> { let snap = self.snap(); let start = snap.op.start()?; let (start, end) = if start < snap.cursor { (start, snap.cursor) } else { (snap.cursor + 1, start + 1) }; let win = snap.window(self.size.width as usize); let Range { start, end } = start.max(win.start)..end.min(win.end); let s = snap.width(snap.offset..start); Some(s..s + snap.width(start..end)) } pub fn partition(&self) -> (&str, &str) { let snap = self.snap(); let idx = snap.idx(snap.cursor).unwrap(); if let Some(sep) = snap.value[idx..].find(CROSS_SEPARATOR).map(|i| idx + i) { (&snap.value[..sep], &snap.value[sep + 1..]) } else { (&snap.value, "") } } pub fn snap(&self) -> &InputSnap { self.snaps.current() } pub fn snap_mut(&mut self) -> &mut InputSnap { self.snaps.current_mut() } }