Skip to content

Commit ee7c657

Browse files
committed
Support title and window manipulation, add an example
1 parent cdc5e33 commit ee7c657

File tree

3 files changed

+203
-3
lines changed

3 files changed

+203
-3
lines changed

examples/window-title.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use std::io::{self, Write as _};
2+
3+
use termina::{
4+
escape::{
5+
csi::{self, Csi},
6+
osc::Osc,
7+
},
8+
PlatformTerminal, Terminal as _,
9+
};
10+
11+
macro_rules! decset {
12+
($mode:ident) => {
13+
Csi::Mode(csi::Mode::SetDecPrivateMode(csi::DecPrivateMode::Code(
14+
csi::DecPrivateModeCode::$mode,
15+
)))
16+
};
17+
}
18+
macro_rules! decreset {
19+
($mode:ident) => {
20+
Csi::Mode(csi::Mode::ResetDecPrivateMode(csi::DecPrivateMode::Code(
21+
csi::DecPrivateModeCode::$mode,
22+
)))
23+
};
24+
}
25+
26+
fn main() -> io::Result<()> {
27+
let mut terminal = PlatformTerminal::new()?;
28+
terminal.enter_raw_mode()?;
29+
30+
write!(
31+
terminal,
32+
"{}{}{}{}Check the window/tab title of your terminal. Press any key to exit. ",
33+
decset!(ClearAndEnableAlternateScreen),
34+
// Save the current title to the terminal's stack.
35+
Csi::Window(Box::new(csi::Window::PushIconAndWindowTitle)),
36+
Osc::SetWindowTitle("Hello, world! - termina"),
37+
Csi::Cursor(csi::Cursor::Position {
38+
line: Default::default(),
39+
col: Default::default(),
40+
}),
41+
)?;
42+
terminal.flush()?;
43+
let _ = terminal.read(|event| matches!(event, termina::Event::Key(_)));
44+
45+
write!(
46+
terminal,
47+
"{}{}",
48+
// Restore the title from the terminal's stack.
49+
Csi::Window(Box::new(csi::Window::PopIconAndWindowTitle)),
50+
decreset!(ClearAndEnableAlternateScreen),
51+
)?;
52+
53+
Ok(())
54+
}

src/escape/csi.rs

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub enum Csi {
2424
Mouse(MouseReport),
2525
Keyboard(Keyboard),
2626
Device(Device),
27-
// TODO: Window(Box<Window>),
27+
Window(Box<Window>),
2828
}
2929

3030
impl Display for Csi {
@@ -39,6 +39,7 @@ impl Display for Csi {
3939
Self::Mouse(report) => report.fmt(f),
4040
Self::Keyboard(keyboard) => keyboard.fmt(f),
4141
Self::Device(device) => device.fmt(f),
42+
Self::Window(window) => window.fmt(f),
4243
}
4344
}
4445
}
@@ -1220,6 +1221,133 @@ impl Display for Device {
12201221
}
12211222
}
12221223

1224+
// Window
1225+
1226+
#[derive(Debug, Clone, PartialEq, Eq)]
1227+
pub enum Window {
1228+
DeIconify,
1229+
Iconify,
1230+
MoveWindow {
1231+
x: i64,
1232+
y: i64,
1233+
},
1234+
ResizeWindowPixels {
1235+
width: Option<i64>,
1236+
height: Option<i64>,
1237+
},
1238+
RaiseWindow,
1239+
LowerWindow,
1240+
RefreshWindow,
1241+
ResizeWindowCells {
1242+
width: Option<i64>,
1243+
height: Option<i64>,
1244+
},
1245+
RestoreMaximizedWindow,
1246+
MaximizeWindow,
1247+
MaximizeWindowVertically,
1248+
MaximizeWindowHorizontally,
1249+
UndoFullScreenMode,
1250+
ChangeToFullScreenMode,
1251+
ToggleFullScreen,
1252+
ReportWindowState,
1253+
ReportWindowPosition,
1254+
ReportTextAreaPosition,
1255+
ReportTextAreaSizePixels,
1256+
ReportWindowSizePixels,
1257+
ReportScreenSizePixels,
1258+
ReportCellSizePixels,
1259+
ReportCellSizePixelsResponse {
1260+
width: Option<i64>,
1261+
height: Option<i64>,
1262+
},
1263+
ReportTextAreaSizeCells,
1264+
ReportScreenSizeCells,
1265+
ReportIconLabel,
1266+
ReportWindowTitle,
1267+
PushIconAndWindowTitle,
1268+
PushIconTitle,
1269+
PushWindowTitle,
1270+
PopIconAndWindowTitle,
1271+
PopIconTitle,
1272+
PopWindowTitle,
1273+
/// DECRQCRA; used by esctest
1274+
ChecksumRectangularArea {
1275+
request_id: i64,
1276+
page_number: i64,
1277+
top: OneBased,
1278+
left: OneBased,
1279+
bottom: OneBased,
1280+
right: OneBased,
1281+
},
1282+
}
1283+
1284+
impl Display for Window {
1285+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1286+
struct NumstrOrEmpty(Option<i64>);
1287+
impl Display for NumstrOrEmpty {
1288+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1289+
if let Some(x) = self.0 {
1290+
write!(f, "{x}")?
1291+
}
1292+
Ok(())
1293+
}
1294+
}
1295+
1296+
match self {
1297+
Window::DeIconify => write!(f, "1t"),
1298+
Window::Iconify => write!(f, "2t"),
1299+
Window::MoveWindow { x, y } => write!(f, "3;{x};{y}t"),
1300+
Window::ResizeWindowPixels { width, height } => {
1301+
write!(f, "4;{};{}t", NumstrOrEmpty(*height), NumstrOrEmpty(*width))
1302+
}
1303+
Window::RaiseWindow => write!(f, "5t"),
1304+
Window::LowerWindow => write!(f, "6t"),
1305+
Window::RefreshWindow => write!(f, "7t"),
1306+
Window::ResizeWindowCells { width, height } => {
1307+
write!(f, "8;{};{}t", NumstrOrEmpty(*height), NumstrOrEmpty(*width))
1308+
}
1309+
Window::RestoreMaximizedWindow => write!(f, "9;0t"),
1310+
Window::MaximizeWindow => write!(f, "9;1t"),
1311+
Window::MaximizeWindowVertically => write!(f, "9;2t"),
1312+
Window::MaximizeWindowHorizontally => write!(f, "9;3t"),
1313+
Window::UndoFullScreenMode => write!(f, "10;0t"),
1314+
Window::ChangeToFullScreenMode => write!(f, "10;1t"),
1315+
Window::ToggleFullScreen => write!(f, "10;2t"),
1316+
Window::ReportWindowState => write!(f, "11t"),
1317+
Window::ReportWindowPosition => write!(f, "13t"),
1318+
Window::ReportTextAreaPosition => write!(f, "13;2t"),
1319+
Window::ReportTextAreaSizePixels => write!(f, "14t"),
1320+
Window::ReportWindowSizePixels => write!(f, "14;2t"),
1321+
Window::ReportScreenSizePixels => write!(f, "15t"),
1322+
Window::ReportCellSizePixels => write!(f, "16t"),
1323+
Window::ReportCellSizePixelsResponse { width, height } => {
1324+
write!(f, "6;{};{}t", NumstrOrEmpty(*height), NumstrOrEmpty(*width))
1325+
}
1326+
Window::ReportTextAreaSizeCells => write!(f, "18t"),
1327+
Window::ReportScreenSizeCells => write!(f, "19t"),
1328+
Window::ReportIconLabel => write!(f, "20t"),
1329+
Window::ReportWindowTitle => write!(f, "21t"),
1330+
Window::PushIconAndWindowTitle => write!(f, "22;0t"),
1331+
Window::PushIconTitle => write!(f, "22;1t"),
1332+
Window::PushWindowTitle => write!(f, "22;2t"),
1333+
Window::PopIconAndWindowTitle => write!(f, "23;0t"),
1334+
Window::PopIconTitle => write!(f, "23;1t"),
1335+
Window::PopWindowTitle => write!(f, "23;2t"),
1336+
Window::ChecksumRectangularArea {
1337+
request_id,
1338+
page_number,
1339+
top,
1340+
left,
1341+
bottom,
1342+
right,
1343+
} => write!(
1344+
f,
1345+
"{request_id};{page_number};{top};{left};{bottom};{right}*y"
1346+
),
1347+
}
1348+
}
1349+
}
1350+
12231351
#[cfg(test)]
12241352
mod test {
12251353
use super::*;
@@ -1259,5 +1387,16 @@ mod test {
12591387
"\x1b[39m",
12601388
Csi::Sgr(Sgr::Foreground(ColorSpec::Reset)).to_string(),
12611389
);
1390+
1391+
// Push current window title to the terminal's stack.
1392+
assert_eq!(
1393+
"\x1b[22;0t",
1394+
Csi::Window(Box::new(Window::PushIconAndWindowTitle)).to_string(),
1395+
);
1396+
// ... and pop it.
1397+
assert_eq!(
1398+
"\x1b[23;0t",
1399+
Csi::Window(Box::new(Window::PopIconAndWindowTitle)).to_string(),
1400+
);
12621401
}
12631402
}

src/escape/osc.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ use std::fmt::{self, Display};
66

77
use crate::base64;
88

9-
#[derive(Debug, Clone, PartialEq, Eq)]
109
pub enum Osc<'a> {
10+
SetIconNameAndWindowTitle(&'a str),
1111
SetWindowTitle(&'a str),
12+
SetWindowTitleSun(&'a str),
13+
SetIconName(&'a str),
14+
SetIconNameSun(&'a str),
1215
ClearSelection(Selection),
1316
QuerySelection(Selection),
1417
SetSelection(Selection, &'a str),
@@ -19,7 +22,11 @@ impl Display for Osc<'_> {
1922
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2023
f.write_str(super::OSC)?;
2124
match self {
22-
Self::SetWindowTitle(title) => write!(f, "2;{title}")?,
25+
Self::SetIconNameAndWindowTitle(s) => write!(f, "0;{s}")?,
26+
Self::SetWindowTitle(s) => write!(f, "2;{s}")?,
27+
Self::SetWindowTitleSun(s) => write!(f, "l{s}")?,
28+
Self::SetIconName(s) => write!(f, "1;{s}")?,
29+
Self::SetIconNameSun(s) => write!(f, "L{s}")?,
2330
Self::ClearSelection(selection) => write!(f, "52;{selection}")?,
2431
Self::QuerySelection(selection) => write!(f, "52;{selection};?")?,
2532
Self::SetSelection(selection, content) => {

0 commit comments

Comments
 (0)