use rmux_core::{fnmatch, OptionStore};
use rmux_proto::{OptionName, SessionName};
use super::{
capabilities::{apply_template_override, parse_capability_override, split_override_segments},
defaults::{
DEFAULT_CR, DEFAULT_CS, DEFAULT_DSBP, DEFAULT_DSEKS, DEFAULT_DSFCS, DEFAULT_DSMG,
DEFAULT_ENBP, DEFAULT_ENEKS, DEFAULT_ENFCS, DEFAULT_ENMG, DEFAULT_FOOT_FEATURES,
DEFAULT_FSL, DEFAULT_HLS, DEFAULT_ITERM2_FEATURES, DEFAULT_MINTTY_FEATURES, DEFAULT_MS,
DEFAULT_RXVT_UNICODE_FEATURES, DEFAULT_SE, DEFAULT_SS, DEFAULT_SWD, DEFAULT_SYNC,
DEFAULT_TMUX_FEATURES, DEFAULT_TSL, DEFAULT_XTERM_FEATURES,
},
OuterTerminal, OuterTerminalContext,
};
impl OuterTerminal {
pub(crate) fn resolve(options: &OptionStore, context: OuterTerminalContext) -> Self {
Self::resolve_for_session(options, None, context)
}
pub(crate) fn resolve_for_session(
options: &OptionStore,
session_name: Option<&SessionName>,
context: OuterTerminalContext,
) -> Self {
let mut terminal = Self {
context,
focus_events_enabled: matches!(
options.resolve(None, OptionName::FocusEvents),
Some("on")
),
extended_keys_enabled: matches!(
options.resolve(None, OptionName::ExtendedKeys),
Some("on" | "always")
),
clipboard_writes_enabled: matches!(
options.resolve(None, OptionName::SetClipboard),
Some("on" | "external")
),
mouse_reporting_enabled: matches!(
options.resolve(session_name, OptionName::Mouse),
Some("on")
),
..Self::default()
};
terminal.apply_default_family_features();
terminal.apply_terminal_feature_options(options);
terminal.apply_colorterm_overrides();
terminal.apply_terminal_override_options(options);
if terminal.xt_flag || looks_vt100_like(terminal.context.term_name()) {
terminal.vt100_like = true;
terminal.apply_feature_name("bpaste");
terminal.apply_feature_name("focus");
terminal.apply_feature_name("title");
}
if terminal.tc_flag {
terminal.apply_feature_name("RGB");
}
if terminal.xt_flag || terminal.tc_flag {
terminal.apply_terminal_override_options(options);
}
terminal.apply_context_features();
terminal
}
pub(super) fn from_feature_names(feature_names: &[String]) -> Self {
let mut terminal = Self::default();
for feature in feature_names {
terminal.apply_feature_name(feature);
}
terminal
}
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn features_string(&self) -> String {
let mut features = Vec::new();
if self.supports_256 {
features.push("256");
}
if self.enable_bpaste.is_some() && self.disable_bpaste.is_some() {
features.push("bpaste");
}
if self.cursor_colour_template.is_some() && self.cursor_colour_reset.is_some() {
features.push("ccolour");
}
if self.clipboard_template.is_some() {
features.push("clipboard");
}
if self.hyperlink_template.is_some() {
features.push("hyperlinks");
}
if self.cursor_style_template.is_some() && self.cursor_style_reset.is_some() {
features.push("cstyle");
}
if self.enable_extkeys.is_some() && self.disable_extkeys.is_some() {
features.push("extkeys");
}
if self.enable_focus.is_some() && self.disable_focus.is_some() {
features.push("focus");
}
if self.ignore_function_keys {
features.push("ignorefkeys");
}
if self.supports_margins {
features.push("margins");
}
if self.supports_mouse {
features.push("mouse");
}
if self.path_open.is_some() && self.path_close.is_some() {
features.push("osc7");
}
if self.supports_overline {
features.push("overline");
}
if self.supports_rectfill {
features.push("rectfill");
}
if self.supports_rgb {
features.push("RGB");
}
if self.supports_sixel {
features.push("sixel");
}
if self.supports_strikethrough {
features.push("strikethrough");
}
if self.sync_template.is_some() {
features.push("sync");
}
if self.title_open.is_some() && self.title_close.is_some() {
features.push("title");
}
if self.supports_usstyle {
features.push("usstyle");
}
features.join(",")
}
fn apply_default_family_features(&mut self) {
let term = self.context.term_name().to_owned();
let term_program = self.context.term_program().to_owned();
if term.contains("mintty") || term_program.eq_ignore_ascii_case("mintty") {
self.add_features(DEFAULT_MINTTY_FEATURES);
}
if term.starts_with("tmux") {
self.add_features(DEFAULT_TMUX_FEATURES);
}
if term.starts_with("rxvt-unicode") {
self.add_features(DEFAULT_RXVT_UNICODE_FEATURES);
}
if term_program.eq_ignore_ascii_case("iTerm.app") || term.starts_with("iterm") {
self.add_features(DEFAULT_ITERM2_FEATURES);
}
if term.starts_with("foot") {
self.add_features(DEFAULT_FOOT_FEATURES);
}
if term.starts_with("xterm") {
self.add_features(DEFAULT_XTERM_FEATURES);
}
}
fn apply_terminal_feature_options(&mut self, options: &OptionStore) {
let term = self.context.term_name().to_owned();
if term.is_empty() {
return;
}
for entry in options.resolve_array_values(None, OptionName::TerminalFeatures) {
let mut segments = entry.split(':');
let Some(pattern) = segments
.next()
.map(str::trim)
.filter(|pattern| !pattern.is_empty())
else {
continue;
};
if !fnmatch(pattern, &term) {
continue;
}
for feature in segments {
self.apply_feature_name(feature.trim());
}
}
}
fn apply_colorterm_overrides(&mut self) {
let colorterm = self.context.colorterm();
if colorterm.eq_ignore_ascii_case("truecolor") || colorterm.eq_ignore_ascii_case("24bit") {
self.apply_feature_name("RGB");
} else if colorterm.contains("256") {
self.apply_feature_name("256");
}
}
fn apply_terminal_override_options(&mut self, options: &OptionStore) {
let term = self.context.term_name().to_owned();
if term.is_empty() {
return;
}
for entry in options.resolve_array_values(None, OptionName::TerminalOverrides) {
let segments = split_override_segments(&entry);
let Some((pattern, overrides)) = segments.split_first() else {
continue;
};
if pattern.is_empty() || !fnmatch(pattern, &term) {
continue;
}
for override_spec in overrides {
self.apply_capability_override(override_spec);
}
}
}
fn apply_context_features(&mut self) {
let feature_names = self.context.terminal_features().to_vec();
for feature in &feature_names {
self.apply_feature_name(feature);
}
}
fn apply_feature_name(&mut self, feature: &str) {
match feature.to_ascii_lowercase().as_str() {
"256" => self.supports_256 = true,
"bpaste" => {
self.enable_bpaste
.get_or_insert_with(|| DEFAULT_ENBP.to_owned());
self.disable_bpaste
.get_or_insert_with(|| DEFAULT_DSBP.to_owned());
}
"ccolour" => {
self.cursor_colour_template
.get_or_insert_with(|| DEFAULT_CS.to_owned());
self.cursor_colour_reset
.get_or_insert_with(|| DEFAULT_CR.to_owned());
}
"clipboard" => {
self.clipboard_template
.get_or_insert_with(|| DEFAULT_MS.to_owned());
}
"hyperlinks" => {
self.supports_hyperlinks = true;
self.hyperlink_template
.get_or_insert_with(|| DEFAULT_HLS.to_owned());
}
"cstyle" => {
self.cursor_style_template
.get_or_insert_with(|| DEFAULT_SS.to_owned());
self.cursor_style_reset
.get_or_insert_with(|| DEFAULT_SE.to_owned());
}
"extkeys" => {
self.enable_extkeys
.get_or_insert_with(|| DEFAULT_ENEKS.to_owned());
self.disable_extkeys
.get_or_insert_with(|| DEFAULT_DSEKS.to_owned());
}
"focus" => {
self.enable_focus
.get_or_insert_with(|| DEFAULT_ENFCS.to_owned());
self.disable_focus
.get_or_insert_with(|| DEFAULT_DSFCS.to_owned());
}
"ignorefkeys" => self.ignore_function_keys = true,
"margins" => {
self.supports_margins = true;
self.enable_margins
.get_or_insert_with(|| DEFAULT_ENMG.to_owned());
self.disable_margins
.get_or_insert_with(|| DEFAULT_DSMG.to_owned());
}
"mouse" => self.supports_mouse = true,
"osc7" => {
self.path_open.get_or_insert_with(|| DEFAULT_SWD.to_owned());
self.path_close
.get_or_insert_with(|| DEFAULT_FSL.to_owned());
}
"overline" => self.supports_overline = true,
"rectfill" => self.supports_rectfill = true,
"rgb" => {
self.default_colours = true;
self.supports_256 = true;
self.supports_rgb = true;
}
"sixel" => self.supports_sixel = true,
"strikethrough" => self.supports_strikethrough = true,
"sync" => {
self.sync_template
.get_or_insert_with(|| DEFAULT_SYNC.to_owned());
}
"title" => {
self.title_open
.get_or_insert_with(|| DEFAULT_TSL.to_owned());
self.title_close
.get_or_insert_with(|| DEFAULT_FSL.to_owned());
}
"usstyle" => self.supports_usstyle = true,
_ => {}
}
}
fn apply_capability_override(&mut self, spec: &str) {
let Some((name, value, remove)) = parse_capability_override(spec) else {
return;
};
match name.to_ascii_lowercase().as_str() {
"ax" => self.default_colours = !remove,
"bidi" => self.bidi = !remove,
"cs" => apply_template_override(&mut self.cursor_colour_template, value, remove),
"cr" => apply_template_override(&mut self.cursor_colour_reset, value, remove),
"cmg" | "clmg" => self.supports_margins = !remove,
"dsmg" => apply_template_override(&mut self.disable_margins, value, remove),
"enmg" => {
self.supports_margins = !remove;
apply_template_override(&mut self.enable_margins, value, remove);
}
"dsbp" => apply_template_override(&mut self.disable_bpaste, value, remove),
"enbp" => apply_template_override(&mut self.enable_bpaste, value, remove),
"dseks" => apply_template_override(&mut self.disable_extkeys, value, remove),
"eneks" => apply_template_override(&mut self.enable_extkeys, value, remove),
"dsfcs" => apply_template_override(&mut self.disable_focus, value, remove),
"enfcs" => apply_template_override(&mut self.enable_focus, value, remove),
"hls" => {
self.supports_hyperlinks = !remove;
apply_template_override(&mut self.hyperlink_template, value, remove);
}
"nobr" => self.no_bright = !remove,
"rect" => self.supports_rectfill = !remove,
"smol" => self.supports_overline = !remove,
"smulx" | "setulc" | "setulc1" | "ol" => self.supports_usstyle = !remove,
"ss" => apply_template_override(&mut self.cursor_style_template, value, remove),
"se" => apply_template_override(&mut self.cursor_style_reset, value, remove),
"swd" => apply_template_override(&mut self.path_open, value, remove),
"sxl" => self.supports_sixel = !remove,
"sync" => apply_template_override(&mut self.sync_template, value, remove),
"tc" => self.tc_flag = !remove,
"ms" => apply_template_override(&mut self.clipboard_template, value, remove),
"xt" => self.xt_flag = !remove,
_ => {}
}
}
fn add_features(&mut self, features: &[&str]) {
for feature in features {
self.apply_feature_name(feature);
}
}
}
fn looks_vt100_like(term: &str) -> bool {
[
"screen",
"tmux",
"xterm",
"rxvt",
"foot",
"linux",
"alacritty",
"wezterm",
"kitty",
"st",
"vte",
]
.iter()
.any(|prefix| term.starts_with(prefix))
}