tui_test/terminal/
backend.rs1use serde::{Deserialize, Serialize};
4
5use crate::event::BellTracker;
6use crate::profile::Profile;
7use crate::terminal::alacritty::AlacrittyEmu;
8use crate::terminal::emu::Emulator;
9
10#[cfg(feature = "rio")]
11use crate::terminal::rio::RioEmu;
12
13#[cfg(feature = "ghostty")]
14use crate::terminal::ghostty::GhosttyEmu;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
18#[serde(rename_all = "lowercase")]
19pub enum Backend {
20 #[default]
21 Alacritty,
22 #[cfg(feature = "ghostty")]
23 Ghostty,
24 #[cfg(feature = "rio")]
25 Rio,
26}
27
28impl Backend {
29 #[cfg(all(not(feature = "ghostty"), not(feature = "rio")))]
30 pub const ALL: [Self; 1] = [Self::Alacritty];
31 #[cfg(all(feature = "ghostty", not(feature = "rio")))]
32 pub const ALL: [Self; 2] = [Self::Alacritty, Self::Ghostty];
33 #[cfg(all(not(feature = "ghostty"), feature = "rio"))]
34 pub const ALL: [Self; 2] = [Self::Alacritty, Self::Rio];
35 #[cfg(all(feature = "ghostty", feature = "rio"))]
36 pub const ALL: [Self; 3] = [Self::Alacritty, Self::Ghostty, Self::Rio];
37
38 pub const fn as_str(self) -> &'static str {
39 match self {
40 Self::Alacritty => "alacritty",
41 #[cfg(feature = "ghostty")]
42 Self::Ghostty => "ghostty",
43 #[cfg(feature = "rio")]
44 Self::Rio => "rio",
45 }
46 }
47
48 pub fn build(
49 self,
50 cols: u16,
51 rows: u16,
52 profile: &Profile,
53 ) -> anyhow::Result<Box<dyn Emulator>> {
54 match self {
55 Self::Alacritty => Ok(Box::new(AlacrittyEmu::new(cols, rows, profile))),
56 #[cfg(feature = "ghostty")]
57 Self::Ghostty => Ok(Box::new(GhosttyEmu::new(cols, rows, profile)?)),
58 #[cfg(feature = "rio")]
59 Self::Rio => Ok(Box::new(RioEmu::new(cols, rows, profile))),
60 }
61 }
62
63 pub(crate) fn build_with_bells(
67 self,
68 cols: u16,
69 rows: u16,
70 profile: &Profile,
71 bells: BellTracker,
72 ) -> anyhow::Result<Box<dyn Emulator>> {
73 match self {
74 Self::Alacritty => Ok(Box::new(AlacrittyEmu::with_bell_tracker(
75 cols, rows, profile, bells,
76 ))),
77 #[cfg(feature = "ghostty")]
78 Self::Ghostty => self.build(cols, rows, profile),
79 #[cfg(feature = "rio")]
80 Self::Rio => Ok(Box::new(RioEmu::with_bell_tracker(
81 cols, rows, profile, bells,
82 ))),
83 }
84 }
85
86 const fn expected() -> &'static str {
87 match (cfg!(feature = "ghostty"), cfg!(feature = "rio")) {
88 (false, false) => "alacritty",
89 (true, false) => "alacritty, ghostty",
90 (false, true) => "alacritty, rio",
91 (true, true) => "alacritty, ghostty, rio",
92 }
93 }
94}
95
96impl std::str::FromStr for Backend {
97 type Err = String;
98
99 fn from_str(value: &str) -> Result<Self, Self::Err> {
100 match value.trim().to_ascii_lowercase().as_str() {
101 "alacritty" => Ok(Self::Alacritty),
102 #[cfg(feature = "ghostty")]
103 "ghostty" => Ok(Self::Ghostty),
104 #[cfg(feature = "rio")]
105 "rio" => Ok(Self::Rio),
106 other => Err(format!(
107 "unknown terminal backend {other:?}; expected one of: {}",
108 Self::expected()
109 )),
110 }
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn names_round_trip() {
120 for backend in Backend::ALL {
121 assert_eq!(backend.as_str().parse(), Ok(backend));
122 }
123 }
124
125 #[test]
126 fn alacritty_remains_the_default() {
127 assert_eq!(Backend::default(), Backend::Alacritty);
128 }
129
130 #[cfg(not(feature = "rio"))]
131 #[test]
132 fn rio_is_rejected_when_its_feature_is_disabled() {
133 assert!("rio".parse::<Backend>().is_err());
134 assert!(serde_json::from_str::<Backend>("\"rio\"").is_err());
135 }
136
137 #[cfg(feature = "ghostty")]
138 #[test]
139 fn legacy_backend_spelling_is_rejected() {
140 assert!("libghostty".parse::<Backend>().is_err());
141 assert!(serde_json::from_str::<Backend>("\"libghostty\"").is_err());
142 }
143
144 #[test]
145 fn every_enabled_backend_constructs() {
146 for backend in Backend::ALL {
147 let mut emulator = backend
148 .build(10, 2, &Profile::default())
149 .unwrap_or_else(|error| panic!("{}: {error:#}", backend.as_str()));
150 emulator.process(b"ok");
151 assert_eq!(emulator.viewable_rows()[0][0].ch, "o");
152 }
153 }
154}