#![forbid(unsafe_code)]
use crate::terminal_capabilities::TerminalCapabilities;
use std::cell::RefCell;
#[derive(Debug, Clone, Default)]
pub struct CapabilityOverride {
pub true_color: Option<bool>,
pub colors_256: Option<bool>,
pub unicode_box_drawing: Option<bool>,
pub unicode_emoji: Option<bool>,
pub double_width: Option<bool>,
pub sync_output: Option<bool>,
pub osc8_hyperlinks: Option<bool>,
pub scroll_region: Option<bool>,
pub in_tmux: Option<bool>,
pub in_screen: Option<bool>,
pub in_zellij: Option<bool>,
pub kitty_keyboard: Option<bool>,
pub focus_events: Option<bool>,
pub bracketed_paste: Option<bool>,
pub mouse_sgr: Option<bool>,
pub osc52_clipboard: Option<bool>,
}
impl CapabilityOverride {
#[must_use]
pub const fn new() -> Self {
Self {
true_color: None,
colors_256: None,
unicode_box_drawing: None,
unicode_emoji: None,
double_width: None,
sync_output: None,
osc8_hyperlinks: None,
scroll_region: None,
in_tmux: None,
in_screen: None,
in_zellij: None,
kitty_keyboard: None,
focus_events: None,
bracketed_paste: None,
mouse_sgr: None,
osc52_clipboard: None,
}
}
#[must_use]
pub const fn dumb() -> Self {
Self {
true_color: Some(false),
colors_256: Some(false),
unicode_box_drawing: Some(false),
unicode_emoji: Some(false),
double_width: Some(false),
sync_output: Some(false),
osc8_hyperlinks: Some(false),
scroll_region: Some(false),
in_tmux: Some(false),
in_screen: Some(false),
in_zellij: Some(false),
kitty_keyboard: Some(false),
focus_events: Some(false),
bracketed_paste: Some(false),
mouse_sgr: Some(false),
osc52_clipboard: Some(false),
}
}
#[must_use]
pub const fn modern() -> Self {
Self {
true_color: Some(true),
colors_256: Some(true),
unicode_box_drawing: Some(true),
unicode_emoji: Some(true),
double_width: Some(true),
sync_output: Some(true),
osc8_hyperlinks: Some(true),
scroll_region: Some(true),
in_tmux: Some(false),
in_screen: Some(false),
in_zellij: Some(false),
kitty_keyboard: Some(true),
focus_events: Some(true),
bracketed_paste: Some(true),
mouse_sgr: Some(true),
osc52_clipboard: Some(true),
}
}
#[must_use]
pub const fn tmux() -> Self {
Self {
true_color: None,
colors_256: Some(true),
unicode_box_drawing: None,
unicode_emoji: None,
double_width: None,
sync_output: Some(false),
osc8_hyperlinks: Some(false),
scroll_region: Some(true),
in_tmux: Some(true),
in_screen: Some(false),
in_zellij: Some(false),
kitty_keyboard: Some(false),
focus_events: Some(false),
bracketed_paste: Some(true),
mouse_sgr: Some(true),
osc52_clipboard: Some(false),
}
}
#[must_use]
pub const fn true_color(mut self, value: Option<bool>) -> Self {
self.true_color = value;
self
}
#[must_use]
pub const fn colors_256(mut self, value: Option<bool>) -> Self {
self.colors_256 = value;
self
}
#[must_use]
pub const fn unicode_box_drawing(mut self, value: Option<bool>) -> Self {
self.unicode_box_drawing = value;
self
}
#[must_use]
pub const fn unicode_emoji(mut self, value: Option<bool>) -> Self {
self.unicode_emoji = value;
self
}
#[must_use]
pub const fn double_width(mut self, value: Option<bool>) -> Self {
self.double_width = value;
self
}
#[must_use]
pub const fn sync_output(mut self, value: Option<bool>) -> Self {
self.sync_output = value;
self
}
#[must_use]
pub const fn osc8_hyperlinks(mut self, value: Option<bool>) -> Self {
self.osc8_hyperlinks = value;
self
}
#[must_use]
pub const fn scroll_region(mut self, value: Option<bool>) -> Self {
self.scroll_region = value;
self
}
#[must_use]
pub const fn in_tmux(mut self, value: Option<bool>) -> Self {
self.in_tmux = value;
self
}
#[must_use]
pub const fn in_screen(mut self, value: Option<bool>) -> Self {
self.in_screen = value;
self
}
#[must_use]
pub const fn in_zellij(mut self, value: Option<bool>) -> Self {
self.in_zellij = value;
self
}
#[must_use]
pub const fn kitty_keyboard(mut self, value: Option<bool>) -> Self {
self.kitty_keyboard = value;
self
}
#[must_use]
pub const fn focus_events(mut self, value: Option<bool>) -> Self {
self.focus_events = value;
self
}
#[must_use]
pub const fn bracketed_paste(mut self, value: Option<bool>) -> Self {
self.bracketed_paste = value;
self
}
#[must_use]
pub const fn mouse_sgr(mut self, value: Option<bool>) -> Self {
self.mouse_sgr = value;
self
}
#[must_use]
pub const fn osc52_clipboard(mut self, value: Option<bool>) -> Self {
self.osc52_clipboard = value;
self
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.true_color.is_none()
&& self.colors_256.is_none()
&& self.unicode_box_drawing.is_none()
&& self.unicode_emoji.is_none()
&& self.double_width.is_none()
&& self.sync_output.is_none()
&& self.osc8_hyperlinks.is_none()
&& self.scroll_region.is_none()
&& self.in_tmux.is_none()
&& self.in_screen.is_none()
&& self.in_zellij.is_none()
&& self.kitty_keyboard.is_none()
&& self.focus_events.is_none()
&& self.bracketed_paste.is_none()
&& self.mouse_sgr.is_none()
&& self.osc52_clipboard.is_none()
}
#[must_use]
pub fn apply_to(&self, mut caps: TerminalCapabilities) -> TerminalCapabilities {
if let Some(v) = self.true_color {
caps.true_color = v;
}
if let Some(v) = self.colors_256 {
caps.colors_256 = v;
}
if let Some(v) = self.unicode_box_drawing {
caps.unicode_box_drawing = v;
}
if let Some(v) = self.unicode_emoji {
caps.unicode_emoji = v;
}
if let Some(v) = self.double_width {
caps.double_width = v;
}
if let Some(v) = self.sync_output {
caps.sync_output = v;
}
if let Some(v) = self.osc8_hyperlinks {
caps.osc8_hyperlinks = v;
}
if let Some(v) = self.scroll_region {
caps.scroll_region = v;
}
if let Some(v) = self.in_tmux {
caps.in_tmux = v;
}
if let Some(v) = self.in_screen {
caps.in_screen = v;
}
if let Some(v) = self.in_zellij {
caps.in_zellij = v;
}
if let Some(v) = self.kitty_keyboard {
caps.kitty_keyboard = v;
}
if let Some(v) = self.focus_events {
caps.focus_events = v;
}
if let Some(v) = self.bracketed_paste {
caps.bracketed_paste = v;
}
if let Some(v) = self.mouse_sgr {
caps.mouse_sgr = v;
}
if let Some(v) = self.osc52_clipboard {
caps.osc52_clipboard = v;
}
caps
}
}
thread_local! {
static OVERRIDE_STACK: RefCell<Vec<CapabilityOverride>> = const { RefCell::new(Vec::new()) };
}
#[must_use]
pub struct OverrideGuard {
_marker: std::marker::PhantomData<*const ()>,
}
impl Drop for OverrideGuard {
fn drop(&mut self) {
OVERRIDE_STACK.with(|stack| {
stack.borrow_mut().pop();
});
}
}
#[must_use = "the override is removed when the guard is dropped"]
pub fn push_override(over: CapabilityOverride) -> OverrideGuard {
OVERRIDE_STACK.with(|stack| {
stack.borrow_mut().push(over);
});
OverrideGuard {
_marker: std::marker::PhantomData,
}
}
pub fn with_capability_override<F, R>(over: CapabilityOverride, f: F) -> R
where
F: FnOnce() -> R,
{
let _guard = push_override(over);
f()
}
#[must_use]
pub fn current_capabilities() -> TerminalCapabilities {
let base = TerminalCapabilities::detect();
current_capabilities_with_base(base)
}
#[must_use]
pub fn current_capabilities_with_base(base: TerminalCapabilities) -> TerminalCapabilities {
OVERRIDE_STACK.with(|stack| {
let stack = stack.borrow();
stack.iter().fold(base, |caps, over| over.apply_to(caps))
})
}
#[must_use]
pub fn has_active_overrides() -> bool {
OVERRIDE_STACK.with(|stack| !stack.borrow().is_empty())
}
#[must_use]
pub fn override_depth() -> usize {
OVERRIDE_STACK.with(|stack| stack.borrow().len())
}
pub fn clear_all_overrides() {
OVERRIDE_STACK.with(|stack| {
stack.borrow_mut().clear();
});
}
impl TerminalCapabilities {
#[must_use]
pub fn with_overrides() -> Self {
current_capabilities()
}
#[must_use]
pub fn with_overrides_from(self, base: Self) -> Self {
current_capabilities_with_base(base)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn override_new_is_empty() {
let over = CapabilityOverride::new();
assert!(over.is_empty());
}
#[test]
fn override_dumb_disables_all() {
let over = CapabilityOverride::dumb();
assert!(!over.is_empty());
assert_eq!(over.true_color, Some(false));
assert_eq!(over.colors_256, Some(false));
assert_eq!(over.sync_output, Some(false));
assert_eq!(over.mouse_sgr, Some(false));
}
#[test]
fn override_modern_enables_all() {
let over = CapabilityOverride::modern();
assert_eq!(over.true_color, Some(true));
assert_eq!(over.colors_256, Some(true));
assert_eq!(over.sync_output, Some(true));
assert_eq!(over.kitty_keyboard, Some(true));
assert_eq!(over.in_tmux, Some(false));
}
#[test]
fn override_tmux_sets_mux() {
let over = CapabilityOverride::tmux();
assert_eq!(over.in_tmux, Some(true));
assert_eq!(over.sync_output, Some(false));
assert_eq!(over.osc52_clipboard, Some(false));
}
#[test]
fn override_builder_chain() {
let over = CapabilityOverride::new()
.true_color(Some(true))
.colors_256(Some(true))
.unicode_box_drawing(Some(false))
.mouse_sgr(Some(false));
assert_eq!(over.true_color, Some(true));
assert_eq!(over.colors_256, Some(true));
assert_eq!(over.unicode_box_drawing, Some(false));
assert_eq!(over.mouse_sgr, Some(false));
assert!(over.sync_output.is_none());
}
#[test]
fn apply_to_overrides_caps() {
let base = TerminalCapabilities::dumb();
let over = CapabilityOverride::new()
.true_color(Some(true))
.colors_256(Some(true))
.unicode_box_drawing(Some(true));
let result = over.apply_to(base);
assert!(result.true_color);
assert!(result.colors_256);
assert!(result.unicode_box_drawing);
assert!(!result.mouse_sgr);
}
#[test]
fn apply_to_none_keeps_original() {
let base = TerminalCapabilities::modern();
let over = CapabilityOverride::new();
let result = over.apply_to(base);
assert_eq!(result.true_color, base.true_color);
assert_eq!(result.mouse_sgr, base.mouse_sgr);
}
#[test]
fn push_pop_override() {
clear_all_overrides();
assert!(!has_active_overrides());
assert_eq!(override_depth(), 0);
{
let _guard = push_override(CapabilityOverride::dumb());
assert!(has_active_overrides());
assert_eq!(override_depth(), 1);
}
assert!(!has_active_overrides());
assert_eq!(override_depth(), 0);
}
#[test]
fn nested_overrides() {
clear_all_overrides();
{
let _outer = push_override(
CapabilityOverride::new()
.true_color(Some(true))
.mouse_sgr(Some(true)),
);
assert_eq!(override_depth(), 1);
{
let _inner = push_override(CapabilityOverride::new().true_color(Some(false)));
assert_eq!(override_depth(), 2);
let caps = current_capabilities_with_base(TerminalCapabilities::dumb());
assert!(!caps.true_color); assert!(caps.mouse_sgr); }
assert_eq!(override_depth(), 1);
let caps = current_capabilities_with_base(TerminalCapabilities::dumb());
assert!(caps.true_color); }
assert_eq!(override_depth(), 0);
}
#[test]
fn with_capability_override_scope() {
clear_all_overrides();
let result = with_capability_override(CapabilityOverride::modern(), || {
assert!(has_active_overrides());
let caps = current_capabilities_with_base(TerminalCapabilities::dumb());
caps.true_color
});
assert!(result);
assert!(!has_active_overrides());
}
#[test]
fn with_capability_override_nested() {
clear_all_overrides();
with_capability_override(CapabilityOverride::new().true_color(Some(true)), || {
with_capability_override(CapabilityOverride::new().mouse_sgr(Some(false)), || {
let caps = current_capabilities_with_base(TerminalCapabilities::dumb());
assert!(caps.true_color);
assert!(!caps.mouse_sgr);
});
});
}
#[test]
fn with_overrides_method() {
clear_all_overrides();
with_capability_override(CapabilityOverride::dumb(), || {
let caps = TerminalCapabilities::with_overrides();
assert!(!caps.true_color);
assert!(!caps.colors_256);
assert!(!caps.unicode_box_drawing);
assert!(!caps.unicode_emoji);
assert!(!caps.double_width);
});
}
#[test]
fn clear_all_overrides_works() {
let _g1 = push_override(CapabilityOverride::dumb());
let _g2 = push_override(CapabilityOverride::modern());
assert_eq!(override_depth(), 2);
clear_all_overrides();
assert_eq!(override_depth(), 0);
}
#[test]
fn default_override_is_empty() {
let over = CapabilityOverride::default();
assert!(over.is_empty());
}
#[test]
fn is_empty_false_for_single_override() {
let over = CapabilityOverride::new().true_color(Some(true));
assert!(!over.is_empty());
}
#[test]
fn dumb_disables_all_fields() {
let over = CapabilityOverride::dumb();
assert_eq!(over.unicode_box_drawing, Some(false));
assert_eq!(over.unicode_emoji, Some(false));
assert_eq!(over.double_width, Some(false));
assert_eq!(over.osc8_hyperlinks, Some(false));
assert_eq!(over.scroll_region, Some(false));
assert_eq!(over.kitty_keyboard, Some(false));
assert_eq!(over.focus_events, Some(false));
assert_eq!(over.bracketed_paste, Some(false));
assert_eq!(over.osc52_clipboard, Some(false));
assert_eq!(over.in_tmux, Some(false));
assert_eq!(over.in_screen, Some(false));
assert_eq!(over.in_zellij, Some(false));
}
#[test]
fn modern_enables_features_disables_mux() {
let over = CapabilityOverride::modern();
assert_eq!(over.unicode_box_drawing, Some(true));
assert_eq!(over.unicode_emoji, Some(true));
assert_eq!(over.double_width, Some(true));
assert_eq!(over.osc8_hyperlinks, Some(true));
assert_eq!(over.scroll_region, Some(true));
assert_eq!(over.focus_events, Some(true));
assert_eq!(over.bracketed_paste, Some(true));
assert_eq!(over.osc52_clipboard, Some(true));
assert_eq!(over.in_screen, Some(false));
assert_eq!(over.in_zellij, Some(false));
}
#[test]
fn tmux_sets_bracketed_paste_and_colors() {
let over = CapabilityOverride::tmux();
assert_eq!(over.colors_256, Some(true));
assert_eq!(over.bracketed_paste, Some(true));
assert_eq!(over.mouse_sgr, Some(true));
assert_eq!(over.scroll_region, Some(true));
assert_eq!(over.kitty_keyboard, Some(false));
}
#[test]
fn builder_all_optional_features() {
let over = CapabilityOverride::new()
.unicode_emoji(Some(true))
.double_width(Some(false))
.in_screen(Some(true))
.in_zellij(Some(true))
.osc8_hyperlinks(Some(true))
.osc52_clipboard(Some(false))
.scroll_region(Some(true))
.focus_events(Some(true))
.bracketed_paste(Some(false))
.kitty_keyboard(Some(true));
assert_eq!(over.unicode_emoji, Some(true));
assert_eq!(over.double_width, Some(false));
assert_eq!(over.in_screen, Some(true));
assert_eq!(over.in_zellij, Some(true));
assert_eq!(over.osc8_hyperlinks, Some(true));
assert_eq!(over.osc52_clipboard, Some(false));
assert_eq!(over.scroll_region, Some(true));
assert_eq!(over.focus_events, Some(true));
assert_eq!(over.bracketed_paste, Some(false));
assert_eq!(over.kitty_keyboard, Some(true));
}
#[test]
fn apply_to_covers_all_mux_flags() {
let base = TerminalCapabilities::dumb();
let over = CapabilityOverride::new()
.in_tmux(Some(true))
.in_screen(Some(true))
.in_zellij(Some(true));
let result = over.apply_to(base);
assert!(result.in_tmux);
assert!(result.in_screen);
assert!(result.in_zellij);
}
#[test]
fn apply_to_covers_input_features() {
let base = TerminalCapabilities::dumb();
let over = CapabilityOverride::new()
.kitty_keyboard(Some(true))
.focus_events(Some(true))
.bracketed_paste(Some(true))
.osc52_clipboard(Some(true));
let result = over.apply_to(base);
assert!(result.kitty_keyboard);
assert!(result.focus_events);
assert!(result.bracketed_paste);
assert!(result.osc52_clipboard);
}
#[test]
fn current_capabilities_with_base_composes_stack() {
clear_all_overrides();
let base = TerminalCapabilities::dumb();
let _g1 = push_override(CapabilityOverride::new().true_color(Some(true)));
let _g2 = push_override(CapabilityOverride::new().mouse_sgr(Some(true)));
let caps = current_capabilities_with_base(base);
assert!(caps.true_color);
assert!(caps.mouse_sgr);
assert!(!caps.colors_256);
clear_all_overrides();
}
#[test]
fn override_clone() {
let over = CapabilityOverride::new()
.true_color(Some(true))
.in_tmux(Some(false));
let cloned = over.clone();
assert_eq!(over.true_color, cloned.true_color);
assert_eq!(over.in_tmux, cloned.in_tmux);
}
#[test]
fn is_empty_false_for_colors_256() {
assert!(!CapabilityOverride::new().colors_256(Some(true)).is_empty());
}
#[test]
fn is_empty_false_for_unicode_box_drawing() {
assert!(
!CapabilityOverride::new()
.unicode_box_drawing(Some(false))
.is_empty()
);
}
#[test]
fn is_empty_false_for_unicode_emoji() {
assert!(
!CapabilityOverride::new()
.unicode_emoji(Some(true))
.is_empty()
);
}
#[test]
fn is_empty_false_for_double_width() {
assert!(
!CapabilityOverride::new()
.double_width(Some(true))
.is_empty()
);
}
#[test]
fn is_empty_false_for_sync_output() {
assert!(
!CapabilityOverride::new()
.sync_output(Some(false))
.is_empty()
);
}
#[test]
fn is_empty_false_for_osc8_hyperlinks() {
assert!(
!CapabilityOverride::new()
.osc8_hyperlinks(Some(true))
.is_empty()
);
}
#[test]
fn is_empty_false_for_scroll_region() {
assert!(
!CapabilityOverride::new()
.scroll_region(Some(true))
.is_empty()
);
}
#[test]
fn is_empty_false_for_in_tmux() {
assert!(!CapabilityOverride::new().in_tmux(Some(true)).is_empty());
}
#[test]
fn is_empty_false_for_in_screen() {
assert!(!CapabilityOverride::new().in_screen(Some(true)).is_empty());
}
#[test]
fn is_empty_false_for_in_zellij() {
assert!(!CapabilityOverride::new().in_zellij(Some(true)).is_empty());
}
#[test]
fn is_empty_false_for_kitty_keyboard() {
assert!(
!CapabilityOverride::new()
.kitty_keyboard(Some(true))
.is_empty()
);
}
#[test]
fn is_empty_false_for_focus_events() {
assert!(
!CapabilityOverride::new()
.focus_events(Some(false))
.is_empty()
);
}
#[test]
fn is_empty_false_for_bracketed_paste() {
assert!(
!CapabilityOverride::new()
.bracketed_paste(Some(true))
.is_empty()
);
}
#[test]
fn is_empty_false_for_mouse_sgr() {
assert!(!CapabilityOverride::new().mouse_sgr(Some(true)).is_empty());
}
#[test]
fn is_empty_false_for_osc52_clipboard() {
assert!(
!CapabilityOverride::new()
.osc52_clipboard(Some(false))
.is_empty()
);
}
#[test]
fn apply_to_covers_unicode_emoji() {
let base = TerminalCapabilities::dumb();
let result = CapabilityOverride::new()
.unicode_emoji(Some(true))
.apply_to(base);
assert!(result.unicode_emoji);
}
#[test]
fn apply_to_covers_double_width() {
let base = TerminalCapabilities::dumb();
let result = CapabilityOverride::new()
.double_width(Some(true))
.apply_to(base);
assert!(result.double_width);
}
#[test]
fn apply_to_covers_sync_output() {
let base = TerminalCapabilities::dumb();
let result = CapabilityOverride::new()
.sync_output(Some(true))
.apply_to(base);
assert!(result.sync_output);
}
#[test]
fn apply_to_covers_osc8_hyperlinks() {
let base = TerminalCapabilities::dumb();
let result = CapabilityOverride::new()
.osc8_hyperlinks(Some(true))
.apply_to(base);
assert!(result.osc8_hyperlinks);
}
#[test]
fn apply_to_covers_scroll_region() {
let base = TerminalCapabilities::dumb();
let result = CapabilityOverride::new()
.scroll_region(Some(true))
.apply_to(base);
assert!(result.scroll_region);
}
#[test]
fn apply_to_covers_mouse_sgr() {
let base = TerminalCapabilities::dumb();
let result = CapabilityOverride::new()
.mouse_sgr(Some(true))
.apply_to(base);
assert!(result.mouse_sgr);
}
#[test]
fn dumb_override_disables_all_on_modern_base() {
let base = TerminalCapabilities::modern();
let result = CapabilityOverride::dumb().apply_to(base);
assert!(!result.true_color);
assert!(!result.colors_256);
assert!(!result.unicode_box_drawing);
assert!(!result.unicode_emoji);
assert!(!result.double_width);
assert!(!result.sync_output);
assert!(!result.osc8_hyperlinks);
assert!(!result.scroll_region);
assert!(!result.in_tmux);
assert!(!result.in_screen);
assert!(!result.in_zellij);
assert!(!result.kitty_keyboard);
assert!(!result.focus_events);
assert!(!result.bracketed_paste);
assert!(!result.mouse_sgr);
assert!(!result.osc52_clipboard);
}
#[test]
fn modern_override_enables_features_on_dumb_base() {
let base = TerminalCapabilities::dumb();
let result = CapabilityOverride::modern().apply_to(base);
assert!(result.true_color);
assert!(result.colors_256);
assert!(result.unicode_box_drawing);
assert!(result.unicode_emoji);
assert!(result.double_width);
assert!(result.sync_output);
assert!(result.osc8_hyperlinks);
assert!(result.scroll_region);
assert!(!result.in_tmux);
assert!(!result.in_screen);
assert!(!result.in_zellij);
assert!(result.kitty_keyboard);
assert!(result.focus_events);
assert!(result.bracketed_paste);
assert!(result.mouse_sgr);
assert!(result.osc52_clipboard);
}
#[test]
fn tmux_none_fields_passthrough() {
let over = CapabilityOverride::tmux();
assert!(over.true_color.is_none());
assert!(over.unicode_box_drawing.is_none());
assert!(over.unicode_emoji.is_none());
assert!(over.double_width.is_none());
}
#[test]
fn builder_in_tmux_individually() {
let over = CapabilityOverride::new().in_tmux(Some(true));
assert_eq!(over.in_tmux, Some(true));
assert!(over.true_color.is_none()); }
#[test]
fn builder_sync_output_individually() {
let over = CapabilityOverride::new().sync_output(Some(false));
assert_eq!(over.sync_output, Some(false));
assert!(over.colors_256.is_none());
}
#[test]
fn builder_overwrite_field_to_none() {
let over = CapabilityOverride::new()
.true_color(Some(true))
.true_color(None);
assert!(over.true_color.is_none());
assert!(over.is_empty());
}
#[test]
fn builder_overwrite_dumb_field_to_none() {
let over = CapabilityOverride::dumb().true_color(None);
assert!(over.true_color.is_none());
assert!(!over.is_empty()); }
#[test]
fn guard_drop_after_clear_all_is_noop() {
clear_all_overrides();
let guard = push_override(CapabilityOverride::dumb());
assert_eq!(override_depth(), 1);
clear_all_overrides();
assert_eq!(override_depth(), 0);
drop(guard);
assert_eq!(override_depth(), 0);
}
#[test]
fn multiple_guards_drop_after_clear_all() {
clear_all_overrides();
let g1 = push_override(CapabilityOverride::dumb());
let g2 = push_override(CapabilityOverride::modern());
assert_eq!(override_depth(), 2);
clear_all_overrides();
assert_eq!(override_depth(), 0);
drop(g2);
drop(g1);
assert_eq!(override_depth(), 0);
}
#[test]
fn three_level_nesting_innermost_wins() {
clear_all_overrides();
let _l1 = push_override(CapabilityOverride::new().true_color(Some(true)));
let _l2 = push_override(CapabilityOverride::new().true_color(Some(false)));
let _l3 = push_override(CapabilityOverride::new().true_color(Some(true)));
assert_eq!(override_depth(), 3);
let caps = current_capabilities_with_base(TerminalCapabilities::dumb());
assert!(caps.true_color);
clear_all_overrides();
}
#[test]
fn three_level_nesting_partial_overrides() {
clear_all_overrides();
let _l1 = push_override(CapabilityOverride::new().true_color(Some(true)));
let _l2 = push_override(CapabilityOverride::new().mouse_sgr(Some(true)));
let _l3 = push_override(CapabilityOverride::new().colors_256(Some(true)));
let caps = current_capabilities_with_base(TerminalCapabilities::dumb());
assert!(caps.true_color); assert!(caps.mouse_sgr); assert!(caps.colors_256); assert!(!caps.sync_output);
clear_all_overrides();
}
#[test]
fn with_overrides_from_applies_stack() {
clear_all_overrides();
let base = TerminalCapabilities::dumb();
let _g = push_override(CapabilityOverride::new().true_color(Some(true)));
let caps = base.with_overrides_from(base);
assert!(caps.true_color);
assert!(!caps.colors_256);
clear_all_overrides();
}
#[test]
fn with_overrides_from_without_active_overrides() {
clear_all_overrides();
let base = TerminalCapabilities::modern();
let caps = base.with_overrides_from(base);
assert_eq!(caps.true_color, base.true_color);
assert_eq!(caps.mouse_sgr, base.mouse_sgr);
}
#[test]
fn with_capability_override_cleans_up_on_panic() {
clear_all_overrides();
let result = std::panic::catch_unwind(|| {
with_capability_override(CapabilityOverride::dumb(), || {
assert!(has_active_overrides());
panic!("deliberate panic");
});
});
assert!(result.is_err());
assert!(!has_active_overrides());
assert_eq!(override_depth(), 0);
}
#[test]
fn debug_format_contains_field_names() {
let over = CapabilityOverride::new().true_color(Some(true));
let dbg = format!("{over:?}");
assert!(dbg.contains("true_color"));
assert!(dbg.contains("Some(true)"));
}
#[test]
fn debug_format_empty_override() {
let over = CapabilityOverride::new();
let dbg = format!("{over:?}");
assert!(dbg.contains("CapabilityOverride"));
assert!(dbg.contains("None"));
}
#[test]
fn clear_all_then_push_resumes_normally() {
clear_all_overrides();
let _g1 = push_override(CapabilityOverride::dumb());
clear_all_overrides();
assert_eq!(override_depth(), 0);
let _g2 = push_override(CapabilityOverride::modern());
assert_eq!(override_depth(), 1);
assert!(has_active_overrides());
let caps = current_capabilities_with_base(TerminalCapabilities::dumb());
assert!(caps.true_color);
clear_all_overrides();
}
#[test]
fn current_capabilities_uses_detect_as_base() {
clear_all_overrides();
let _g = push_override(CapabilityOverride::dumb());
let caps = current_capabilities();
assert!(!caps.true_color);
assert!(!caps.mouse_sgr);
clear_all_overrides();
}
#[test]
fn with_overrides_integrates_full_stack() {
clear_all_overrides();
let _g = push_override(CapabilityOverride::modern());
let caps = TerminalCapabilities::with_overrides();
assert!(caps.true_color);
assert!(caps.kitty_keyboard);
assert!(!caps.in_tmux);
clear_all_overrides();
}
#[test]
fn second_guard_dropped_first_still_active() {
clear_all_overrides();
let g1 = push_override(CapabilityOverride::new().true_color(Some(true)));
let g2 = push_override(CapabilityOverride::new().true_color(Some(false)));
drop(g2);
assert_eq!(override_depth(), 1);
let caps = current_capabilities_with_base(TerminalCapabilities::dumb());
assert!(caps.true_color);
drop(g1);
assert_eq!(override_depth(), 0);
}
#[test]
fn with_capability_override_returns_string() {
clear_all_overrides();
let val = with_capability_override(CapabilityOverride::dumb(), || {
String::from("computed value")
});
assert_eq!(val, "computed value");
}
#[test]
fn with_capability_override_returns_tuple() {
clear_all_overrides();
let (a, b) = with_capability_override(CapabilityOverride::modern(), || {
let caps = current_capabilities_with_base(TerminalCapabilities::dumb());
(caps.true_color, caps.mouse_sgr)
});
assert!(a);
assert!(b);
}
#[test]
fn apply_to_disables_on_modern_base() {
let base = TerminalCapabilities::modern();
let result = CapabilityOverride::new()
.true_color(Some(false))
.kitty_keyboard(Some(false))
.apply_to(base);
assert!(!result.true_color);
assert!(!result.kitty_keyboard);
assert!(result.colors_256);
assert!(result.unicode_box_drawing);
}
#[test]
fn current_capabilities_with_base_no_overrides_returns_base() {
clear_all_overrides();
let base = TerminalCapabilities::modern();
let caps = current_capabilities_with_base(base);
assert_eq!(caps.true_color, base.true_color);
assert_eq!(caps.colors_256, base.colors_256);
assert_eq!(caps.unicode_box_drawing, base.unicode_box_drawing);
assert_eq!(caps.unicode_emoji, base.unicode_emoji);
assert_eq!(caps.double_width, base.double_width);
assert_eq!(caps.sync_output, base.sync_output);
assert_eq!(caps.osc8_hyperlinks, base.osc8_hyperlinks);
assert_eq!(caps.scroll_region, base.scroll_region);
assert_eq!(caps.in_tmux, base.in_tmux);
assert_eq!(caps.in_screen, base.in_screen);
assert_eq!(caps.in_zellij, base.in_zellij);
assert_eq!(caps.kitty_keyboard, base.kitty_keyboard);
assert_eq!(caps.focus_events, base.focus_events);
assert_eq!(caps.bracketed_paste, base.bracketed_paste);
assert_eq!(caps.mouse_sgr, base.mouse_sgr);
assert_eq!(caps.osc52_clipboard, base.osc52_clipboard);
}
}