mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: Vim-like notification (#659)
This commit is contained in:
parent
a764e42098
commit
22ecad47ab
22 changed files with 387 additions and 31 deletions
|
|
@ -25,8 +25,8 @@ ueberzug_offset = [ 0, 0, 0, 0 ]
|
|||
|
||||
[opener]
|
||||
edit = [
|
||||
{ exec = '$EDITOR "$@"', block = true, for = "unix" },
|
||||
{ exec = 'code "%*"', orphan = true, for = "windows" },
|
||||
{ exec = '[ -n "$EDITOR" ] && $EDITOR "$@"', desc = "$EDITOR", block = true, for = "unix" },
|
||||
{ exec = 'code "%*"', orphan = true, for = "windows" },
|
||||
]
|
||||
open = [
|
||||
{ exec = 'xdg-open "$@"', desc = "Open", for = "linux" },
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ pub mod folder;
|
|||
pub mod help;
|
||||
pub mod input;
|
||||
pub mod manager;
|
||||
pub mod notify;
|
||||
pub mod select;
|
||||
mod step;
|
||||
pub mod tab;
|
||||
|
|
|
|||
2
yazi-core/src/notify/commands/mod.rs
Normal file
2
yazi-core/src/notify/commands/mod.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
mod push;
|
||||
mod tick;
|
||||
19
yazi-core/src/notify/commands/push.rs
Normal file
19
yazi-core/src/notify/commands/push.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
use std::time::Instant;
|
||||
|
||||
use yazi_shared::{emit, event::Cmd, Layer};
|
||||
|
||||
use crate::notify::{Message, Notify};
|
||||
|
||||
impl Notify {
|
||||
pub fn push(&mut self, msg: impl TryInto<Message>) {
|
||||
let Ok(mut msg) = msg.try_into() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let instant = Instant::now();
|
||||
msg.timeout += instant - self.messages.first().map_or(instant, |m| m.instant);
|
||||
self.messages.push(msg);
|
||||
|
||||
emit!(Call(Cmd::args("update_notify", vec![0.to_string()]), Layer::App));
|
||||
}
|
||||
}
|
||||
67
yazi-core/src/notify/commands/tick.rs
Normal file
67
yazi-core/src/notify/commands/tick.rs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use yazi_shared::{emit, event::Cmd, Layer};
|
||||
|
||||
use crate::notify::Notify;
|
||||
|
||||
pub struct Opt {
|
||||
interval: Duration,
|
||||
}
|
||||
|
||||
impl TryFrom<Cmd> for Opt {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> {
|
||||
let interval = c.take_first().and_then(|s| s.parse::<f64>().ok()).ok_or(())?;
|
||||
if interval < 0.0 {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
Ok(Self { interval: Duration::from_secs_f64(interval) })
|
||||
}
|
||||
}
|
||||
|
||||
impl Notify {
|
||||
pub fn tick(&mut self, opt: impl TryInto<Opt>) {
|
||||
self.tick_handle.take().map(|h| h.abort());
|
||||
let Ok(opt) = opt.try_into() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let limit = self.limit();
|
||||
if limit == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
for m in &mut self.messages[..limit] {
|
||||
if m.timeout.is_zero() {
|
||||
m.percent = m.percent.saturating_sub(20);
|
||||
} else if m.percent < 100 {
|
||||
m.percent = m.percent.saturating_add(20);
|
||||
} else {
|
||||
m.timeout = m.timeout.saturating_sub(opt.interval);
|
||||
}
|
||||
}
|
||||
|
||||
self.messages.retain(|m| m.percent > 0 || !m.timeout.is_zero());
|
||||
let limit = self.limit();
|
||||
let timeouts: Vec<_> = self.messages[..limit]
|
||||
.iter()
|
||||
.filter(|&m| m.percent == 100 && !m.timeout.is_zero())
|
||||
.map(|m| m.timeout)
|
||||
.collect();
|
||||
|
||||
let interval = if timeouts.len() != limit {
|
||||
Duration::from_millis(50)
|
||||
} else if let Some(min) = timeouts.iter().min() {
|
||||
*min
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.tick_handle = Some(tokio::spawn(async move {
|
||||
tokio::time::sleep(interval).await;
|
||||
emit!(Call(Cmd::args("update_notify", vec![interval.as_secs_f64().to_string()]), Layer::App));
|
||||
}));
|
||||
}
|
||||
}
|
||||
20
yazi-core/src/notify/level.rs
Normal file
20
yazi-core/src/notify/level.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
pub enum Level {
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl FromStr for Level {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match s {
|
||||
"info" => Self::Info,
|
||||
"warn" => Self::Warn,
|
||||
"error" => Self::Error,
|
||||
_ => return Err(()),
|
||||
})
|
||||
}
|
||||
}
|
||||
47
yazi-core/src/notify/message.rs
Normal file
47
yazi-core/src/notify/message.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
use yazi_shared::event::Cmd;
|
||||
|
||||
use super::{Level, NOTIFY_BORDER};
|
||||
|
||||
pub struct Message {
|
||||
pub title: String,
|
||||
pub content: String,
|
||||
pub level: Level,
|
||||
|
||||
pub instant: Instant,
|
||||
pub timeout: Duration,
|
||||
|
||||
pub lines: usize,
|
||||
pub percent: u8,
|
||||
}
|
||||
|
||||
impl TryFrom<Cmd> for Message {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> {
|
||||
let timeout = c.take_name("timeout").and_then(|s| s.parse::<f64>().ok()).ok_or(())?;
|
||||
if timeout < 0.0 {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let content = c.take_name("content").ok_or(())?;
|
||||
let lines = content.lines().count();
|
||||
Ok(Self {
|
||||
title: c.take_name("title").ok_or(())?,
|
||||
content,
|
||||
level: c.take_name("level").ok_or(())?.parse()?,
|
||||
|
||||
instant: Instant::now(),
|
||||
timeout: Duration::from_secs_f64(timeout),
|
||||
|
||||
lines,
|
||||
percent: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Message {
|
||||
#[inline]
|
||||
pub fn height(&self) -> usize { self.lines + NOTIFY_BORDER as usize }
|
||||
}
|
||||
11
yazi-core/src/notify/mod.rs
Normal file
11
yazi-core/src/notify/mod.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
mod commands;
|
||||
mod level;
|
||||
mod message;
|
||||
mod notify;
|
||||
|
||||
pub use level::*;
|
||||
pub use message::*;
|
||||
pub use notify::*;
|
||||
|
||||
pub const NOTIFY_BORDER: u16 = 2;
|
||||
pub const NOTIFY_SPACING: u16 = 1;
|
||||
36
yazi-core/src/notify/notify.rs
Normal file
36
yazi-core/src/notify/notify.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use std::ops::ControlFlow;
|
||||
|
||||
use tokio::task::JoinHandle;
|
||||
use yazi_shared::term::Term;
|
||||
|
||||
use super::{Message, NOTIFY_SPACING};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Notify {
|
||||
pub(super) tick_handle: Option<JoinHandle<()>>,
|
||||
pub messages: Vec<Message>,
|
||||
}
|
||||
|
||||
impl Notify {
|
||||
pub fn limit(&self) -> usize {
|
||||
if self.messages.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let mut height = Term::size().height as usize;
|
||||
let flow = (0..self.messages.len().min(3)).try_fold(0, |acc, i| {
|
||||
match height.checked_sub(self.messages[i].height() + NOTIFY_SPACING as usize) {
|
||||
Some(h) => {
|
||||
height = h;
|
||||
ControlFlow::Continue(acc + 1)
|
||||
}
|
||||
None => ControlFlow::Break(acc),
|
||||
}
|
||||
});
|
||||
|
||||
1.max(match flow {
|
||||
ControlFlow::Continue(i) => i,
|
||||
ControlFlow::Break(i) => i,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
mod notify;
|
||||
mod plugin;
|
||||
mod quit;
|
||||
mod render;
|
||||
mod resize;
|
||||
mod resume;
|
||||
mod stop;
|
||||
mod update_notify;
|
||||
mod update_progress;
|
||||
|
|
|
|||
7
yazi-fm/src/app/commands/notify.rs
Normal file
7
yazi-fm/src/app/commands/notify.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
use yazi_core::notify::Message;
|
||||
|
||||
use crate::app::App;
|
||||
|
||||
impl App {
|
||||
pub(crate) fn notify(&mut self, msg: impl TryInto<Message>) { self.cx.notify.push(msg); }
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
use std::sync::atomic::Ordering;
|
||||
|
||||
use ratatui::backend::Backend;
|
||||
use ratatui::{backend::{Backend, CrosstermBackend}, CompletedFrame};
|
||||
|
||||
use crate::{app::App, lives::Lives, root::{Root, COLLISION}};
|
||||
use crate::{app::App, lives::Lives, notify::Notify, root::{Root, COLLISION}};
|
||||
|
||||
impl App {
|
||||
pub(crate) fn render(&mut self) {
|
||||
|
|
@ -14,35 +14,68 @@ impl App {
|
|||
let frame = term
|
||||
.draw(|f| {
|
||||
_ = Lives::scope(&self.cx, |_| Ok(f.render_widget(Root::new(&self.cx), f.size())));
|
||||
|
||||
if let Some((x, y)) = self.cx.cursor() {
|
||||
f.set_cursor(x, y);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
if !COLLISION.load(Ordering::Relaxed) {
|
||||
if collision {
|
||||
// Reload preview if collision is resolved
|
||||
self.cx.manager.peek(true);
|
||||
}
|
||||
return;
|
||||
if COLLISION.load(Ordering::Relaxed) {
|
||||
Self::patch(frame, self.cx.cursor());
|
||||
}
|
||||
if !self.cx.notify.messages.is_empty() {
|
||||
self.render_notify();
|
||||
}
|
||||
|
||||
let mut patch = vec![];
|
||||
for x in frame.area.left()..frame.area.right() {
|
||||
for y in frame.area.top()..frame.area.bottom() {
|
||||
// Reload preview if collision is resolved
|
||||
if collision && !COLLISION.load(Ordering::Relaxed) {
|
||||
self.cx.manager.peek(true);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn render_notify(&mut self) {
|
||||
let Some(term) = &mut self.term else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !term.can_partial() {
|
||||
return self.render();
|
||||
}
|
||||
|
||||
let frame = term
|
||||
.draw_partial(|f| {
|
||||
f.render_widget(Notify::new(&self.cx), f.size());
|
||||
|
||||
if let Some((x, y)) = self.cx.cursor() {
|
||||
f.set_cursor(x, y);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
if COLLISION.load(Ordering::Relaxed) {
|
||||
Self::patch(frame, self.cx.cursor());
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn patch(frame: CompletedFrame, cursor: Option<(u16, u16)>) {
|
||||
let mut patches = vec![];
|
||||
for y in frame.area.top()..frame.area.bottom() {
|
||||
for x in frame.area.left()..frame.area.right() {
|
||||
let cell = frame.buffer.get(x, y);
|
||||
if cell.skip {
|
||||
patch.push((x, y, cell.clone()));
|
||||
patches.push((x, y, cell));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
term.backend_mut().draw(patch.iter().map(|(x, y, cell)| (*x, *y, cell))).ok();
|
||||
if let Some((x, y)) = self.cx.cursor() {
|
||||
term.show_cursor().ok();
|
||||
term.set_cursor(x, y).ok();
|
||||
let mut backend = CrosstermBackend::new(std::io::stdout().lock());
|
||||
backend.draw(patches.into_iter()).ok();
|
||||
if let Some((x, y)) = cursor {
|
||||
backend.show_cursor().ok();
|
||||
backend.set_cursor(x, y).ok();
|
||||
}
|
||||
term.backend_mut().flush().ok();
|
||||
backend.flush().ok();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
15
yazi-fm/src/app/commands/update_notify.rs
Normal file
15
yazi-fm/src/app/commands/update_notify.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
use yazi_shared::event::Cmd;
|
||||
|
||||
use crate::app::App;
|
||||
|
||||
impl App {
|
||||
pub(crate) fn update_notify(&mut self, cmd: Cmd) {
|
||||
self.cx.notify.tick(cmd);
|
||||
|
||||
if self.cx.notify.messages.is_empty() {
|
||||
self.render();
|
||||
} else {
|
||||
self.render_notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,8 +22,8 @@ impl Progress {
|
|||
w.render(buf);
|
||||
|
||||
let mut patch = Vec::with_capacity(area.width as usize * area.height as usize);
|
||||
for x in area.left()..area.right() {
|
||||
for y in area.top()..area.bottom() {
|
||||
for y in area.top()..area.bottom() {
|
||||
for x in area.left()..area.right() {
|
||||
patch.push((x, y, mem::take(buf.get_mut(x, y))));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use ratatui::layout::Rect;
|
||||
use yazi_config::popup::{Origin, Position};
|
||||
use yazi_core::{completion::Completion, help::Help, input::Input, manager::Manager, 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};
|
||||
|
||||
pub struct Ctx {
|
||||
pub manager: Manager,
|
||||
|
|
@ -10,6 +10,7 @@ pub struct Ctx {
|
|||
pub help: Help,
|
||||
pub completion: Completion,
|
||||
pub which: Which,
|
||||
pub notify: Notify,
|
||||
}
|
||||
|
||||
impl Ctx {
|
||||
|
|
@ -22,6 +23,7 @@ impl Ctx {
|
|||
help: Default::default(),
|
||||
completion: Default::default(),
|
||||
which: Default::default(),
|
||||
notify: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,8 +34,10 @@ impl<'a> Executor<'a> {
|
|||
};
|
||||
}
|
||||
|
||||
on!(notify);
|
||||
on!(plugin);
|
||||
on!(plugin_do);
|
||||
on!(update_notify);
|
||||
on!(update_progress);
|
||||
on!(resize);
|
||||
on!(stop);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ mod help;
|
|||
mod input;
|
||||
mod lives;
|
||||
mod logs;
|
||||
mod notify;
|
||||
mod panic;
|
||||
mod root;
|
||||
mod router;
|
||||
|
|
|
|||
3
yazi-fm/src/notify/mod.rs
Normal file
3
yazi-fm/src/notify/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
mod notify;
|
||||
|
||||
pub(super) use notify::*;
|
||||
54
yazi-fm/src/notify/notify.rs
Normal file
54
yazi-fm/src/notify/notify.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use ratatui::{buffer::Buffer, layout::{Constraint, Layout, Offset, Rect}, style::{Style, Stylize}, widgets::{Block, BorderType, Paragraph, Widget}};
|
||||
use yazi_core::notify::{Level, Message};
|
||||
|
||||
use crate::{widgets::Clear, Ctx};
|
||||
|
||||
pub(crate) struct Notify<'a> {
|
||||
cx: &'a Ctx,
|
||||
}
|
||||
|
||||
impl<'a> Notify<'a> {
|
||||
pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } }
|
||||
|
||||
fn chunks(area: Rect, messages: &[Message]) -> Rc<[Rect]> {
|
||||
let chunks = Layout::horizontal([Constraint::Percentage(100), Constraint::Min(40)]).split(area);
|
||||
|
||||
Layout::vertical(messages.iter().map(|m| Constraint::Length(m.height() as u16)))
|
||||
.spacing(1)
|
||||
.split(chunks[1])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Notify<'a> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let notify = &self.cx.notify;
|
||||
|
||||
let limit = notify.limit();
|
||||
let chunks = Self::chunks(area, ¬ify.messages[..limit]);
|
||||
|
||||
for (i, m) in notify.messages.iter().enumerate().take(limit) {
|
||||
let (icon, style) = match m.level {
|
||||
Level::Info => ("", Style::default().green()),
|
||||
Level::Warn => ("", Style::default().yellow()),
|
||||
Level::Error => ("", Style::default().red()),
|
||||
};
|
||||
|
||||
let mut rect = chunks[i]
|
||||
.offset(Offset { x: (100 - m.percent) as i32 * chunks[i].width as i32 / 100, y: 0 });
|
||||
rect.width = area.width.saturating_sub(rect.x);
|
||||
|
||||
Clear.render(rect, buf);
|
||||
Paragraph::new(m.content.as_str())
|
||||
.block(
|
||||
Block::bordered()
|
||||
.border_type(BorderType::Rounded)
|
||||
.title(format!("{} {}", icon, m.title))
|
||||
.title_style(style)
|
||||
.border_style(style),
|
||||
)
|
||||
.render(rect, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,8 +34,8 @@ impl Widget for Clear {
|
|||
|
||||
ADAPTOR.image_erase(r).ok();
|
||||
COLLISION.store(true, Ordering::Relaxed);
|
||||
for x in area.left()..area.right() {
|
||||
for y in area.top()..area.bottom() {
|
||||
for y in area.top()..area.bottom() {
|
||||
for x in area.left()..area.right() {
|
||||
buf.get_mut(x, y).set_skip(true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ pub fn permissions(m: libc::mode_t) -> String {
|
|||
s
|
||||
}
|
||||
|
||||
// Find the max common root of a list of files
|
||||
// Find the max common root in a list of files
|
||||
// e.g. /a/b/c, /a/b/d -> /a/b
|
||||
// /aa/bb/cc, /aa/dd/ee -> /aa
|
||||
pub fn max_common_root(files: &[impl AsRef<Path>]) -> PathBuf {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,24 @@
|
|||
use std::{io::{stdout, Stdout, Write}, mem, ops::{Deref, DerefMut}, sync::atomic::{AtomicBool, Ordering}};
|
||||
use std::{io::{self, stdout, Stdout, Write}, mem, ops::{Deref, DerefMut}, sync::atomic::{AtomicBool, Ordering}};
|
||||
|
||||
use anyhow::Result;
|
||||
use crossterm::{event::{DisableBracketedPaste, DisableFocusChange, EnableBracketedPaste, EnableFocusChange, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, execute, queue, terminal::{disable_raw_mode, enable_raw_mode, supports_keyboard_enhancement, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen, WindowSize}};
|
||||
use ratatui::{backend::CrosstermBackend, Terminal};
|
||||
use ratatui::{backend::CrosstermBackend, buffer::Buffer, layout::Rect, CompletedFrame, Frame, Terminal};
|
||||
|
||||
static CSI_U: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub struct Term(Terminal<CrosstermBackend<Stdout>>);
|
||||
pub struct Term {
|
||||
inner: Terminal<CrosstermBackend<Stdout>>,
|
||||
last_area: Rect,
|
||||
last_buffer: Buffer,
|
||||
}
|
||||
|
||||
impl Term {
|
||||
pub fn start() -> Result<Self> {
|
||||
let mut term = Self(Terminal::new(CrosstermBackend::new(stdout()))?);
|
||||
let mut term = Self {
|
||||
inner: Terminal::new(CrosstermBackend::new(stdout()))?,
|
||||
last_area: Default::default(),
|
||||
last_buffer: Default::default(),
|
||||
};
|
||||
|
||||
enable_raw_mode()?;
|
||||
queue!(stdout(), EnterAlternateScreen, EnableBracketedPaste, EnableFocusChange)?;
|
||||
|
|
@ -69,6 +77,32 @@ impl Term {
|
|||
std::process::exit(f() as i32);
|
||||
}
|
||||
|
||||
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.get(x, y).clone();
|
||||
cell.skip = false;
|
||||
*buffer.get_mut(x, y) = cell;
|
||||
}
|
||||
}
|
||||
|
||||
f(frame);
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn can_partial(&mut self) -> bool { 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() {
|
||||
|
|
@ -112,9 +146,9 @@ impl Drop for Term {
|
|||
impl Deref for Term {
|
||||
type Target = Terminal<CrosstermBackend<Stdout>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
|
||||
impl DerefMut for Term {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue