fix: reverse the order of CSI-based and environment-based terminal detection (#2310)

This commit is contained in:
三咲雅 · Misaki Masa 2025-02-09 14:50:19 +08:00 committed by GitHub
parent 17ff1e8812
commit 793c90f021
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 133 additions and 166 deletions

View file

@ -1,6 +1,7 @@
cargo publish -p yazi-macro && sleep 30
cargo publish -p yazi-codegen && sleep 30
cargo publish -p yazi-shared && sleep 30
cargo publish -p yazi-ffi && sleep 30
cargo publish -p yazi-fs && sleep 30
cargo publish -p yazi-config && sleep 30
cargo publish -p yazi-proxy && sleep 30

View file

@ -81,9 +81,9 @@ impl Adapter {
impl Adapter {
pub fn matches(emulator: Emulator) -> Self {
if matches!(emulator.kind.left(), Some(Brand::Microsoft)) {
if emulator.kind.is_left_and(|&b| b == Brand::Microsoft) {
return Self::Sixel;
} else if *WSL && matches!(emulator.kind.left(), Some(Brand::WezTerm)) {
} else if WSL.get() && emulator.kind.is_left_and(|&b| b == Brand::WezTerm) {
return Self::KgpOld;
}
@ -92,7 +92,7 @@ impl Adapter {
protocols.retain(|p| *p == Self::Iip);
if env_exists("ZELLIJ_SESSION_NAME") {
protocols.retain(|p| *p == Self::Sixel);
} else if *TMUX {
} else if TMUX.get() {
protocols.retain(|p| *p != Self::KgpOld);
}
if let Some(p) = protocols.first() {

View file

@ -3,7 +3,7 @@ use yazi_shared::env_exists;
use crate::{Mux, NVIM};
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Brand {
Kitty,
Konsole,
@ -19,6 +19,7 @@ pub enum Brand {
Tabby,
Hyper,
Mintty,
Tmux,
Neovim,
Apple,
Urxvt,
@ -26,10 +27,24 @@ pub enum Brand {
}
impl Brand {
pub(super) fn from_csi(resp: &str) -> Option<Self> {
let names = [
("kitty", Self::Kitty),
("Konsole", Self::Konsole),
("iTerm2", Self::Iterm2),
("WezTerm", Self::WezTerm),
("foot", Self::Foot),
("ghostty", Self::Ghostty),
("tmux ", Self::Tmux),
("Bobcat", Self::Bobcat),
];
names.into_iter().find(|&(n, _)| resp.contains(n)).map(|(_, b)| b)
}
pub fn from_env() -> Option<Self> {
use Brand as B;
if *NVIM {
if NVIM.get() {
return Some(Self::Neovim);
}
@ -76,19 +91,6 @@ impl Brand {
None
}
pub(super) fn from_csi(resp: &str) -> Option<Self> {
let names = [
("kitty", Self::Kitty),
("Konsole", Self::Konsole),
("iTerm2", Self::Iterm2),
("WezTerm", Self::WezTerm),
("foot", Self::Foot),
("ghostty", Self::Ghostty),
("Bobcat", Self::Bobcat),
];
names.into_iter().find(|&(n, _)| resp.contains(n)).map(|(_, b)| b)
}
pub(super) fn adapters(self) -> &'static [crate::Adapter] {
use Brand as B;
@ -109,6 +111,7 @@ impl Brand {
B::Tabby => &[A::Iip, A::Sixel],
B::Hyper => &[A::Iip, A::Sixel],
B::Mintty => &[A::Iip],
B::Tmux => &[],
B::Neovim => &[],
B::Apple => &[],
B::Urxvt => &[],

View file

@ -30,7 +30,8 @@ impl Dimension {
pub fn ratio() -> Option<(f64, f64)> {
let s = Self::available();
Some(if s.width == 0 || s.height == 0 {
(EMULATOR.cell_size?.0 as f64, EMULATOR.cell_size?.1 as f64)
let s = EMULATOR.get().cell_size?;
(s.0 as f64, s.1 as f64)
} else {
(f64::from(s.width) / f64::from(s.columns), f64::from(s.height) / f64::from(s.rows))
})

View file

@ -53,14 +53,11 @@ impl Iip {
write!(
buf,
"{}]1337;File=inline=1;size={};width={}px;height={}px;doNotMoveCursor=1:",
START,
"{START}]1337;File=inline=1;size={};width={w}px;height={h}px;doNotMoveCursor=1:",
b.len(),
w,
h,
)?;
STANDARD.encode_string(b, &mut buf);
write!(buf, "\x07{}", CLOSE)?;
write!(buf, "\x07{CLOSE}")?;
Ok(buf.into_bytes())
})

View file

@ -336,7 +336,7 @@ impl Kgp {
write!(stderr, "{s}")?;
}
write!(stderr, "{}_Gq=2,a=d,d=A{}\\{}", START, ESCAPE, CLOSE)?;
write!(stderr, "{START}_Gq=2,a=d,d=A{ESCAPE}\\{CLOSE}")?;
Ok(())
})
}
@ -350,31 +350,21 @@ impl Kgp {
if let Some(first) = it.next() {
write!(
buf,
"{}_Gq=2,a=T,i=1,C=1,U=1,f={},s={},v={},m={};{}{}\\{}",
START,
format,
"{START}_Gq=2,a=T,i=1,C=1,U=1,f={format},s={},v={},m={};{}{ESCAPE}\\{CLOSE}",
size.0,
size.1,
it.peek().is_some() as u8,
unsafe { str::from_utf8_unchecked(first) },
ESCAPE,
CLOSE
)?;
}
while let Some(chunk) = it.next() {
write!(
buf,
"{}_Gm={};{}{}\\{}",
START,
it.peek().is_some() as u8,
unsafe { str::from_utf8_unchecked(chunk) },
ESCAPE,
CLOSE
)?;
write!(buf, "{START}_Gm={};{}{ESCAPE}\\{CLOSE}", it.peek().is_some() as u8, unsafe {
str::from_utf8_unchecked(chunk)
})?;
}
write!(buf, "{}", CLOSE)?;
write!(buf, "{CLOSE}")?;
Ok(buf)
}

View file

@ -27,7 +27,7 @@ impl KgpOld {
#[inline]
pub(crate) fn image_erase(_: Rect) -> Result<()> {
let mut stderr = LineWriter::new(stderr());
write!(stderr, "{}_Gq=2,a=d,d=A{}\\{}", START, ESCAPE, CLOSE)?;
write!(stderr, "{START}_Gq=2,a=d,d=A{ESCAPE}\\{CLOSE}")?;
stderr.flush()?;
Ok(())
}
@ -41,31 +41,21 @@ impl KgpOld {
if let Some(first) = it.next() {
write!(
buf,
"{}_Gq=2,a=T,z=-1,C=1,f={},s={},v={},m={};{}{}\\{}",
START,
format,
"{START}_Gq=2,a=T,z=-1,C=1,f={format},s={},v={},m={};{}{ESCAPE}\\{CLOSE}",
size.0,
size.1,
it.peek().is_some() as u8,
unsafe { str::from_utf8_unchecked(first) },
ESCAPE,
CLOSE
)?;
}
while let Some(chunk) = it.next() {
write!(
buf,
"{}_Gm={};{}{}\\{}",
START,
it.peek().is_some() as u8,
unsafe { str::from_utf8_unchecked(chunk) },
ESCAPE,
CLOSE
)?;
write!(buf, "{START}_Gm={};{}{ESCAPE}\\{CLOSE}", it.peek().is_some() as u8, unsafe {
str::from_utf8_unchecked(chunk)
})?;
}
write!(buf, "{}", CLOSE)?;
write!(buf, "{CLOSE}")?;
Ok(buf)
}

View file

@ -47,7 +47,7 @@ impl Sixel {
let nq = NeuQuant::new(PREVIEW.sixel_fraction as i32, 256 - alpha as usize, &img);
let mut buf: Vec<u8> = Vec::with_capacity(1 << 16);
write!(buf, "{}P0;1;8q\"1;1;{};{}", START, img.width(), img.height())?;
write!(buf, "{START}P0;1;8q\"1;1;{};{}", img.width(), img.height())?;
// Palette
for (i, c) in nq.color_map_rgba().chunks(4).enumerate() {
@ -96,7 +96,7 @@ impl Sixel {
}
}
write!(buf, "{}\\{}", ESCAPE, CLOSE)?;
write!(buf, "{ESCAPE}\\{CLOSE}")?;
Ok(buf)
})
.await?

View file

@ -17,40 +17,37 @@ pub struct Emulator {
}
impl Default for Emulator {
fn default() -> Self {
Self { kind: Either::Right(Unknown::default()), light: false, cell_size: None }
}
fn default() -> Self { Self::unknown() }
}
impl Emulator {
pub fn detect() -> Self {
if let Some(brand) = Brand::from_env() {
Self { kind: Either::Left(brand), ..Self::detect_base().unwrap_or_default() }
} else {
Self::detect_full().unwrap_or_default()
}
}
pub fn detect_full() -> Result<Self> {
pub fn detect() -> Result<Self> {
defer! { disable_raw_mode().ok(); }
enable_raw_mode()?;
let resort = Brand::from_env();
let kgp_seq = if resort.is_none() {
Mux::csi("\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\")
} else {
"".into()
};
execute!(
LineWriter::new(stderr()),
SavePosition,
Print(Mux::csi("\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\")), // Detect KGP
Print(Mux::csi("\x1b[>q")), // Request terminal version
Print("\x1b[16t"), // Request cell size
Print("\x1b]11;?\x07"), // Request background color
Print(Mux::csi("\x1b[0c")), // Request device attributes
Print(kgp_seq), // Detect KGP
Print(Mux::csi("\x1b[>q")), // Request terminal version
Print("\x1b[16t"), // Request cell size
Print("\x1b]11;?\x07"), // Request background color
Print(Mux::csi("\x1b[0c")), // Request device attributes
RestorePosition
)?;
let resp = futures::executor::block_on(Self::read_until_da1());
Mux::tmux_drain()?;
let kind = if let Some(brand) = Brand::from_csi(&resp) {
Either::Left(brand)
let kind = if let Some(b) = Brand::from_csi(&resp).or(resort) {
Either::Left(b)
} else {
Either::Right(Unknown {
kgp: resp.contains("\x1b_Gi=31;OK"),
@ -65,6 +62,10 @@ impl Emulator {
})
}
pub const fn unknown() -> Self {
Self { kind: Either::Right(Unknown::default()), light: false, cell_size: None }
}
pub fn adapters(self) -> &'static [Adapter] {
match self.kind {
Either::Left(brand) => brand.adapters(),
@ -84,7 +85,7 @@ impl Emulator {
// I really don't want to add this,
// But tmux and ConPTY sometimes cause the cursor position to get out of sync.
if *TMUX || cfg!(windows) {
if TMUX.get() || cfg!(windows) {
execute!(buf, SavePosition, MoveTo(x, y), Show)?;
execute!(buf, MoveTo(x, y), Show)?;
execute!(buf, MoveTo(x, y), Show)?;
@ -94,7 +95,7 @@ impl Emulator {
}
let result = cb(&mut buf);
if *TMUX || cfg!(windows) {
if TMUX.get() || cfg!(windows) {
queue!(buf, Hide, RestorePosition)?;
} else {
queue!(buf, RestorePosition)?;
@ -194,27 +195,6 @@ impl Emulator {
Ok(())
}
fn detect_base() -> Result<Self> {
defer! { disable_raw_mode().ok(); }
enable_raw_mode()?;
execute!(
LineWriter::new(stderr()),
Print("\x1b[16t"), // Request cell size
Print("\x1b]11;?\x07"), // Request background color
Print("\x1b[0c"), // Request device attributes
)?;
let resp = futures::executor::block_on(Self::read_until_da1());
Mux::tmux_drain()?;
Ok(Self {
light: Self::light_bg(&resp).unwrap_or_default(),
cell_size: Self::cell_size(&resp),
..Default::default()
})
}
fn cell_size(resp: &str) -> Option<(u16, u16)> {
let b = resp.split_once("\x1b[6;")?.1.as_bytes();

View file

@ -4,48 +4,49 @@ yazi_macro::mod_pub!(drivers);
yazi_macro::mod_flat!(adapter brand dimension emulator image info mux unknown);
use yazi_shared::{RoCell, SyncCell, env_exists, in_wsl};
use yazi_shared::{SyncCell, env_exists, in_wsl};
pub static EMULATOR: RoCell<Emulator> = RoCell::new();
pub static ADAPTOR: RoCell<Adapter> = RoCell::new();
pub static EMULATOR: SyncCell<Emulator> = SyncCell::new(Emulator::unknown());
pub static ADAPTOR: SyncCell<Adapter> = SyncCell::new(Adapter::Chafa);
// Image state
static SHOWN: SyncCell<Option<ratatui::layout::Rect>> = SyncCell::new(None);
// Tmux support
pub static TMUX: RoCell<bool> = RoCell::new();
static ESCAPE: RoCell<&'static str> = RoCell::new();
static START: RoCell<&'static str> = RoCell::new();
static CLOSE: RoCell<&'static str> = RoCell::new();
// WSL support
pub static WSL: RoCell<bool> = RoCell::new();
pub static WSL: SyncCell<bool> = SyncCell::new(false);
// Neovim support
pub static NVIM: RoCell<bool> = RoCell::new();
pub static NVIM: SyncCell<bool> = SyncCell::new(false);
// Tmux support
pub static TMUX: SyncCell<bool> = SyncCell::new(false);
static ESCAPE: SyncCell<&'static str> = SyncCell::new("\x1b");
static START: SyncCell<&'static str> = SyncCell::new("\x1b");
static CLOSE: SyncCell<&'static str> = SyncCell::new("");
pub fn init() -> anyhow::Result<()> {
init_default();
EMULATOR.init(Emulator::detect());
yazi_config::init_flavor(EMULATOR.light)?;
ADAPTOR.init(Adapter::matches(*EMULATOR));
ADAPTOR.start();
Ok(())
}
pub fn init_default() {
// Tmux support
TMUX.init(Mux::tmux_passthrough());
ESCAPE.init(if *TMUX { "\x1b\x1b" } else { "\x1b" });
START.init(if *TMUX { "\x1bPtmux;\x1b\x1b" } else { "\x1b" });
CLOSE.init(if *TMUX { "\x1b\\" } else { "" });
// WSL support
WSL.init(in_wsl());
WSL.set(in_wsl());
// Neovim support
NVIM.init(env_exists("NVIM_LOG_FILE") && env_exists("NVIM"));
NVIM.set(env_exists("NVIM_LOG_FILE") && env_exists("NVIM"));
// Emulator detection
EMULATOR.set(Emulator::detect().unwrap_or_default());
TMUX.set(EMULATOR.get().kind.is_left_and(|&b| b == Brand::Tmux));
// Tmux support
if TMUX.get() {
ESCAPE.set("\x1b\x1b");
START.set("\x1bPtmux;\x1b\x1b");
CLOSE.set("\x1b\\");
Mux::tmux_passthrough();
EMULATOR.set(Emulator::detect().unwrap_or_default());
}
yazi_config::init_flavor(EMULATOR.get().light)?;
ADAPTOR.set(Adapter::matches(EMULATOR.get()));
ADAPTOR.get().start();
Ok(())
}

View file

@ -7,25 +7,17 @@ pub struct Mux;
impl Mux {
pub fn csi(s: &str) -> std::borrow::Cow<str> {
if *TMUX && !*NVIM {
if TMUX.get() && !NVIM.get() {
std::borrow::Cow::Owned(format!(
"{}{}{}",
*START,
s.trim_start_matches('\x1b').replace('\x1b', *ESCAPE),
*CLOSE
"{START}{}{CLOSE}",
s.trim_start_matches('\x1b').replace('\x1b', ESCAPE.get()),
))
} else {
std::borrow::Cow::Borrowed(s)
}
}
pub fn tmux_passthrough() -> bool {
if !std::env::var("TERM_PROGRAM").is_ok_and(|s| s == "tmux")
&& !std::env::var("TERM").is_ok_and(|s| s.starts_with("tmux"))
{
return false;
}
pub fn tmux_passthrough() {
let child = std::process::Command::new("tmux")
.args(["set", "-p", "allow-passthrough", "on"])
.stdin(std::process::Stdio::null())
@ -46,11 +38,10 @@ impl Mux {
error!("Failed to spawn `tmux set -p allow-passthrough on`: {e}");
}
}
true
}
pub fn tmux_drain() -> Result<()> {
if *TMUX && !*NVIM {
if TMUX.get() && !NVIM.get() {
crossterm::execute!(std::io::stderr(), crossterm::style::Print(Mux::csi("\x1b[5n")))?;
_ = futures::executor::block_on(Emulator::read_until_dsr());
}
@ -74,7 +65,7 @@ impl Mux {
pub(super) fn term_program() -> (Option<String>, Option<String>) {
let (mut term, mut program) = (None, None);
if !*TMUX {
if !TMUX.get() {
return (term, program);
}
let Ok(output) = std::process::Command::new("tmux").arg("show-environment").output() else {

View file

@ -1,12 +1,14 @@
use crate::Adapter;
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Clone, Copy)]
pub struct Unknown {
pub kgp: bool,
pub sixel: bool,
}
impl Unknown {
pub(super) const fn default() -> Self { Self { kgp: false, sixel: false } }
pub(super) fn adapters(self) -> &'static [Adapter] {
use Adapter as A;

View file

@ -17,14 +17,13 @@ impl Actions {
writeln!(s, "\nYa")?;
writeln!(s, " Version: {}", Self::process_output("ya", "--version"))?;
let emulator = yazi_adapter::Emulator::detect();
let emulator = yazi_adapter::Emulator::detect().unwrap_or_default();
writeln!(s, "\nEmulator")?;
writeln!(s, " TERM : {:?}", env::var_os("TERM"))?;
writeln!(s, " TERM_PROGRAM : {:?}", env::var_os("TERM_PROGRAM"))?;
writeln!(s, " TERM_PROGRAM_VERSION: {:?}", env::var_os("TERM_PROGRAM_VERSION"))?;
writeln!(s, " Brand.from_env : {:?}", yazi_adapter::Brand::from_env())?;
writeln!(s, " Emulator.detect : {:?}", emulator)?;
writeln!(s, " Emulator.detect_full: {:?}", yazi_adapter::Emulator::detect_full())?;
writeln!(s, "\nAdapter")?;
writeln!(s, " Adapter.matches: {:?}", yazi_adapter::Adapter::matches(emulator))?;
@ -42,10 +41,10 @@ impl Actions {
writeln!(s, " shared.in_ssh_connection: {:?}", yazi_shared::in_ssh_connection())?;
writeln!(s, "\nWSL")?;
writeln!(s, " WSL: {:?}", *yazi_adapter::WSL)?;
writeln!(s, " WSL: {:?}", yazi_adapter::WSL.get())?;
writeln!(s, "\nNeovim")?;
writeln!(s, " NVIM : {}", *yazi_adapter::NVIM)?;
writeln!(s, " NVIM : {}", yazi_adapter::NVIM.get())?;
writeln!(s, " Neovim version: {}", Self::process_output("nvim", "--version"))?;
writeln!(s, "\nVariables")?;
@ -75,7 +74,7 @@ impl Actions {
)?;
writeln!(s, "\nMultiplexers")?;
writeln!(s, " TMUX : {}", *yazi_adapter::TMUX)?;
writeln!(s, " TMUX : {}", yazi_adapter::TMUX.get())?;
writeln!(s, " tmux version : {}", Self::process_output("tmux", "-V"))?;
writeln!(s, " tmux build flags : enable-sixel={}", Mux::tmux_sixel_flag())?;
writeln!(s, " ZELLIJ_SESSION_NAME: {:?}", env::var_os("ZELLIJ_SESSION_NAME"))?;

View file

@ -16,13 +16,13 @@ However, if you want to customize certain configurations:
- Create a `yazi.toml` in your config directory to override certain settings in [`yazi-default.toml`][yazi-default], so either:
- `~/.config/yazi/yazi.toml` on Unix-like systems
- `C:\Users\USERNAME\AppData\Roaming\yazi\config\yazi.toml` on Windows
- `%AppData%\yazi\config\yazi.toml` on Windows
- Create a `keymap.toml` in your config directory to override certain settings in [`keymap-default.toml`][keymap-default], so either:
- `~/.config/yazi/keymap.toml` on Unix-like systems
- `C:\Users\USERNAME\AppData\Roaming\yazi\config\keymap.toml` on Windows
- `%AppData%\yazi\config\keymap.toml` on Windows
- Create a `theme.toml` in your config directory to override certain settings in [`theme-light.toml`][theme-light] and [`theme-dark.toml`][theme-dark], so either:
- `~/.config/yazi/theme.toml` on Unix-like systems
- `C:\Users\USERNAME\AppData\Roaming\yazi\config\theme.toml` on Windows
- `%AppData%\yazi\config\theme.toml` on Windows
For the user's `theme.toml` file, you can only apply the same color scheme to both the light and dark themes.

View file

@ -36,7 +36,7 @@ impl Watcher {
};
let config = notify::Config::default().with_poll_interval(Duration::from_millis(500));
if *yazi_adapter::WSL {
if yazi_adapter::WSL.get() {
tokio::spawn(Self::fan_in(in_rx, PollWatcher::new(handler, config).unwrap()));
} else {
tokio::spawn(Self::fan_in(in_rx, RecommendedWatcher::new(handler, config).unwrap()));

View file

@ -70,14 +70,14 @@ impl Preview {
#[inline]
pub fn reset(&mut self) {
self.abort();
ADAPTOR.image_hide().ok();
ADAPTOR.get().image_hide().ok();
render!(self.lock.take().is_some())
}
#[inline]
pub fn reset_image(&mut self) {
self.abort();
ADAPTOR.image_hide().ok();
ADAPTOR.get().image_hide().ok();
}
#[inline]

View file

@ -26,7 +26,7 @@ impl Term {
};
enable_raw_mode()?;
if *yazi_adapter::TMUX {
if yazi_adapter::TMUX.get() {
yazi_adapter::Mux::tmux_passthrough();
}
@ -268,12 +268,12 @@ mod screen {
impl crossterm::Command for SetScreen {
fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
if self.0 == *TMUX { Ok(()) } else { EnterAlternateScreen.write_ansi(f) }
if self.0 == TMUX.get() { Ok(()) } else { EnterAlternateScreen.write_ansi(f) }
}
#[cfg(windows)]
fn execute_winapi(&self) -> std::io::Result<()> {
if self.0 == *TMUX { Ok(()) } else { EnterAlternateScreen.execute_winapi() }
if self.0 == TMUX.get() { Ok(()) } else { EnterAlternateScreen.execute_winapi() }
}
#[cfg(windows)]

View file

@ -6,7 +6,7 @@ function M:setup()
ps.sub_remote("extract", function(args)
local noisy = #args == 1 and ' "" --noisy' or ' ""'
for _, arg in ipairs(args) do
ya.manager_emit("plugin", { self._id, args = ya.quote(arg, true) .. noisy })
ya.manager_emit("plugin", { self._id, ya.quote(arg, true) .. noisy })
end
end)
end

View file

@ -38,11 +38,11 @@ impl ratatui::widgets::Widget for Clear {
{
ratatui::widgets::Clear.render(area, buf);
let Some(r) = ADAPTOR.shown_load().and_then(|r| overlap(area, r)) else {
let Some(r) = ADAPTOR.get().shown_load().and_then(|r| overlap(area, r)) else {
return;
};
ADAPTOR.image_erase(r).ok();
ADAPTOR.get().image_erase(r).ok();
COLLISION.store(true, Ordering::Relaxed);
for y in r.top()..r.bottom() {
for x in r.left()..r.right() {

View file

@ -17,7 +17,7 @@ impl Utils {
pub(super) fn image_show(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, (url, rect): (UrlRef, Rect)| async move {
if let Ok(area) = ADAPTOR.image_show(&url, *rect).await {
if let Ok(area) = ADAPTOR.get().image_show(&url, *rect).await {
Rect::from(area).into_lua(&lua)
} else {
Value::Nil.into_lua(&lua)

View file

@ -33,6 +33,14 @@ impl<L, R> Either<L, R> {
}
}
pub fn is_left_and<F: FnOnce(&L) -> bool>(&self, f: F) -> bool {
self.left().map(f).unwrap_or(false)
}
pub fn is_right_and<F: FnOnce(&R) -> bool>(&self, f: F) -> bool {
self.right().map(f).unwrap_or(false)
}
pub fn into_left(self) -> Option<L> {
match self {
Either::Left(l) => Some(l),

View file

@ -1,4 +1,4 @@
use std::{cell::Cell, fmt::{Debug, Formatter}, ops::Deref};
use std::{cell::Cell, fmt::{Debug, Display, Formatter}, ops::Deref};
/// [`SyncCell`], but [`Sync`].
///
@ -34,3 +34,7 @@ impl<T: Copy + Debug> Debug for SyncCell<T> {
f.debug_struct("SyncCell").field("value", &self.get()).finish()
}
}
impl<T: Copy + Display> Display for SyncCell<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { Display::fmt(&self.get(), f) }
}