diff options
author | Dustin <dustin1114@gmail.com> | 2019-10-14 13:50:58 -0400 |
---|---|---|
committer | Christian Duerr <contact@christianduerr.com> | 2019-10-14 19:50:58 +0200 |
commit | 401c2aab964b60e8c3b072e5098ba8e366d04944 (patch) | |
tree | 8b8fb2f11a93ebb3e59dedb41e01a89f30791455 /alacritty_terminal | |
parent | 3475e449870b382cda4ea6d48f980577cd8c929e (diff) | |
download | alacritty-401c2aab964b60e8c3b072e5098ba8e366d04944.tar.gz alacritty-401c2aab964b60e8c3b072e5098ba8e366d04944.zip |
Add support for title stack escape sequences
This commit adds the concept of a "title stack" to the terminal. Some programs
(e.g. vim) send control sequences `CSI 22 ; 0` (push title) and `CSI 23 ; 0`
(pop title).
The title stack is just a history of previous titles. Applications can push
the current title onto the stack, and pop it back off (setting the window title
in the process).
Fixes #2840.
Diffstat (limited to 'alacritty_terminal')
-rw-r--r-- | alacritty_terminal/src/ansi.rs | 41 | ||||
-rw-r--r-- | alacritty_terminal/src/config/window.rs | 8 | ||||
-rw-r--r-- | alacritty_terminal/src/term/mod.rs | 100 | ||||
-rw-r--r-- | alacritty_terminal/src/tty/windows/conpty.rs | 2 |
4 files changed, 125 insertions, 26 deletions
diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs index b34c1cd9..493b02aa 100644 --- a/alacritty_terminal/src/ansi.rs +++ b/alacritty_terminal/src/ansi.rs @@ -328,6 +328,12 @@ pub trait Handler { /// Run the dectest routine fn dectest(&mut self) {} + + /// Push a title onto the stack + fn push_title(&mut self) {} + + /// Pop the last title from the stack + fn pop_title(&mut self) {} } /// Describes shape of cursor @@ -412,7 +418,13 @@ impl Mode { /// Create mode from a primitive /// /// TODO lots of unhandled values.. - pub fn from_primitive(private: bool, num: i64) -> Option<Mode> { + pub fn from_primitive(intermediate: Option<&u8>, num: i64) -> Option<Mode> { + let private = match intermediate { + Some(b'?') => true, + None => false, + _ => return None, + }; + if private { Some(match num { 1 => Mode::CursorKeys, @@ -991,22 +1003,18 @@ where handler.clear_line(mode); }, ('S', None) => handler.scroll_up(Line(arg_or_default!(idx: 0, default: 1) as usize)), + ('t', None) => match arg_or_default!(idx: 0, default: 1) as usize { + 22 => handler.push_title(), + 23 => handler.pop_title(), + _ => unhandled!(), + }, ('T', None) => handler.scroll_down(Line(arg_or_default!(idx: 0, default: 1) as usize)), ('L', None) => { handler.insert_blank_lines(Line(arg_or_default!(idx: 0, default: 1) as usize)) }, ('l', intermediate) => { - let is_private_mode = match intermediate { - Some(b'?') => true, - None => false, - _ => { - unhandled!(); - return; - }, - }; for arg in args { - let mode = Mode::from_primitive(is_private_mode, *arg); - match mode { + match Mode::from_primitive(intermediate, *arg) { Some(mode) => handler.unset_mode(mode), None => { unhandled!(); @@ -1027,17 +1035,8 @@ where handler.goto_line(Line(arg_or_default!(idx: 0, default: 1) as usize - 1)) }, ('h', intermediate) => { - let is_private_mode = match intermediate { - Some(b'?') => true, - None => false, - _ => { - unhandled!(); - return; - }, - }; for arg in args { - let mode = Mode::from_primitive(is_private_mode, *arg); - match mode { + match Mode::from_primitive(intermediate, *arg) { Some(mode) => handler.set_mode(mode), None => { unhandled!(); diff --git a/alacritty_terminal/src/config/window.rs b/alacritty_terminal/src/config/window.rs index f470f936..49bd70bb 100644 --- a/alacritty_terminal/src/config/window.rs +++ b/alacritty_terminal/src/config/window.rs @@ -36,8 +36,8 @@ pub struct WindowConfig { startup_mode: StartupMode, /// Window title - #[serde(deserialize_with = "failure_default")] - pub title: Option<String>, + #[serde(default = "default_title")] + pub title: String, /// Window class #[serde(deserialize_with = "from_string_or_deserialize")] @@ -56,6 +56,10 @@ pub struct WindowConfig { pub start_maximized: Option<bool>, } +pub fn default_title() -> String { + DEFAULT_NAME.to_string() +} + impl WindowConfig { pub fn startup_mode(&self) -> StartupMode { match self.start_maximized { diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index 4e51d734..382b10a9 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -27,7 +27,7 @@ use crate::ansi::{ self, Attr, CharsetIndex, Color, CursorStyle, Handler, NamedColor, StandardCharset, TermInfo, }; use crate::clipboard::{Clipboard, ClipboardType}; -use crate::config::{Config, VisualBellAnimation}; +use crate::config::{Config, VisualBellAnimation, DEFAULT_NAME}; use crate::cursor::CursorKey; use crate::event::{Event, EventListener}; use crate::grid::{ @@ -47,6 +47,9 @@ pub mod color; /// Used to match equal brackets, when performing a bracket-pair selection. const BRACKET_PAIRS: [(char, char); 4] = [('(', ')'), ('[', ']'), ('{', '}'), ('<', '>')]; +/// Max size of the window title stack +const TITLE_STACK_MAX_DEPTH: usize = 4096; + /// A type that can expand a given point to a region /// /// Usually this is implemented for some 2-D array type since @@ -760,6 +763,13 @@ pub struct Term<T> { /// Terminal focus pub is_focused: bool, + + /// Current title of the window + title: String, + + /// Stack of saved window titles. When a title is popped from this stack, the `title` for the + /// term is set, and the Glutin window's title attribute is changed through the event listener. + title_stack: Vec<String>, } /// Terminal size info @@ -887,6 +897,8 @@ impl<T> Term<T> { clipboard, event_proxy, is_focused: true, + title: config.window.title.clone(), + title_stack: Vec::new(), } } @@ -1322,6 +1334,9 @@ impl<T: EventListener> ansi::Handler for Term<T> { #[cfg(not(windows))] fn set_title(&mut self, title: &str) { if self.dynamic_title { + trace!("Setting window title to '{}'", title); + + self.title = title.into(); self.event_proxy.send_event(Event::Title(title.to_owned())); } } @@ -1336,13 +1351,16 @@ impl<T: EventListener> ansi::Handler for Term<T> { // // The starts_with check is necessary because other shells e.g. bash set a // different title and don't need Alacritty prepended. + trace!("Setting window title to '{}'", title); + let title = if !tty::is_conpty() && title.starts_with(' ') { format!("Alacritty {}", title.trim()) } else { title.to_owned() }; - self.event_proxy.send_event(Event::Title(title)); + self.title = title.clone(); + self.event_proxy.send_event(Event::Title(title.to_owned())); } } @@ -1912,6 +1930,8 @@ impl<T: EventListener> ansi::Handler for Term<T> { self.grid.reset(&Cell::default()); self.alt_grid.reset(&Cell::default()); self.scroll_region = Line(0)..self.grid.num_lines(); + self.title = DEFAULT_NAME.to_string(); + self.title_stack.clear(); } #[inline] @@ -2089,6 +2109,31 @@ impl<T: EventListener> ansi::Handler for Term<T> { trace!("Setting cursor style {:?}", style); self.cursor_style = style; } + + #[inline] + fn push_title(&mut self) { + trace!("Pushing '{}' onto title stack", self.title); + + if self.title_stack.len() >= TITLE_STACK_MAX_DEPTH { + let removed = self.title_stack.remove(0); + trace!( + "Removing '{}' from bottom of title stack that exceeds its maximum depth", + removed + ); + } + + self.title_stack.push(self.title.clone()); + } + + #[inline] + fn pop_title(&mut self) { + trace!("Attempting to pop title from stack..."); + + if let Some(popped) = self.title_stack.pop() { + trace!("Title '{}' popped from stack", popped); + self.set_title(&popped); + } + } } struct TabStops { @@ -2301,6 +2346,57 @@ mod tests { scrolled_grid.scroll_display(Scroll::Top); assert_eq!(term.grid, scrolled_grid); } + + #[test] + fn window_title() { + let size = SizeInfo { + width: 21.0, + height: 51.0, + cell_width: 3.0, + cell_height: 3.0, + padding_x: 0.0, + padding_y: 0.0, + dpr: 1.0, + }; + let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock); + + // Title can be set + { + term.title = "Test".to_string(); + assert_eq!(term.title, "Test"); + } + + // Title can be pushed onto stack + { + term.push_title(); + term.title = "Next".to_string(); + assert_eq!(term.title, "Next"); + assert_eq!(term.title_stack.get(0).unwrap(), "Test"); + } + + // Title can be popped from stack and set as the window title + { + term.pop_title(); + assert_eq!(term.title, "Test"); + assert!(term.title_stack.is_empty()); + } + + // Title stack doesn't grow infinitely + { + for _ in 0..4097 { + term.push_title(); + } + assert_eq!(term.title_stack.len(), 4096); + } + + // Title and title stack reset when terminal state is reset + { + term.push_title(); + term.reset_state(); + assert_eq!(term.title, "Alacritty"); + assert!(term.title_stack.is_empty()); + } + } } #[cfg(all(test, feature = "bench"))] diff --git a/alacritty_terminal/src/tty/windows/conpty.rs b/alacritty_terminal/src/tty/windows/conpty.rs index fe49b4dc..185acfc2 100644 --- a/alacritty_terminal/src/tty/windows/conpty.rs +++ b/alacritty_terminal/src/tty/windows/conpty.rs @@ -143,7 +143,7 @@ pub fn new<'a, C>( let mut startup_info_ex: STARTUPINFOEXW = Default::default(); - let title = config.window.title.as_ref().map(String::as_str).unwrap_or("Alacritty"); + let title = config.window.title.clone(); let title = U16CString::from_str(title).unwrap(); startup_info_ex.StartupInfo.lpTitle = title.as_ptr() as LPWSTR; |