diff --git a/CHANGELOG.md b/CHANGELOG.md index 80824961a6f..9a27412e008 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added - IME composition preview not appearing on Windows +- Synchronized terminal updates using `DCS = 1 s`/`DCS = 2 s` ### Fixed diff --git a/Cargo.lock b/Cargo.lock index 7a3c8b5cea7..4993927dff8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,7 @@ dependencies = [ "mio-anonymous-pipes", "mio-extras", "miow 0.3.6", - "nix", + "nix 0.19.1", "parking_lot", "regex-automata", "serde", @@ -179,9 +179,9 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "byteorder" -version = "1.3.4" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" [[package]] name = "calloop" @@ -190,7 +190,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b036167e76041694579972c28cf4877b4f92da222560ddb49008937b6a6727c" dependencies = [ "log", - "nix", + "nix 0.18.0", ] [[package]] @@ -493,9 +493,9 @@ dependencies = [ [[package]] name = "derivative" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaed5874effa6cde088c644ddcdcb4ffd1511391c5be4fdd7a5ccd02c7e4a183" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", @@ -504,11 +504,10 @@ dependencies = [ [[package]] name = "dirs" -version = "2.0.2" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff" dependencies = [ - "cfg-if 0.1.10", "dirs-sys", ] @@ -566,10 +565,11 @@ dependencies = [ [[package]] name = "embed-resource" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3b2f39c0462f098c1ca4abc408f7482949bbe2ab19bca5a4419f91f69e5bccc" +checksum = "8f1c0b3e7316e38285d86f43cdc8b60083fa38e6e59666fad66e428498f27a44" dependencies = [ + "cc", "vswhom", "winreg", ] @@ -586,13 +586,13 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c122a393ea57648015bf06fbd3d372378992e86b9ff5a7a497b076a28c79efe" +checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall", + "redox_syscall 0.2.4", "winapi 0.3.9", ] @@ -797,9 +797,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" dependencies = [ "libc", ] @@ -823,9 +823,9 @@ dependencies = [ [[package]] name = "inotify-sys" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4563555856585ab3180a5bf0b2f9f8d301a728462afffc8195b3f5394229c55" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" dependencies = [ "libc", ] @@ -896,15 +896,15 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.81" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" +checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff" [[package]] name = "libloading" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9367bdfa836b7e3cf895867f7a570283444da90562980ec2263d6e1569b16bc" +checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" dependencies = [ "cfg-if 1.0.0", "winapi 0.3.9", @@ -912,9 +912,9 @@ dependencies = [ [[package]] name = "linked-hash-map" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "lock_api" @@ -927,11 +927,11 @@ dependencies = [ [[package]] name = "log" -version = "0.4.11" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "serde", ] @@ -1107,11 +1107,23 @@ dependencies = [ "libc", ] +[[package]] +name = "nix" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", +] + [[package]] name = "nom" -version = "6.0.1" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88034cfd6b4a0d54dd14f4a507eceee36c0b70e5a02236c4e4df571102be17f0" +checksum = "ab6f70b46d6325aa300f1c7bb3d470127dfc27806d8ea6bf294ee0ce643ce2b1" dependencies = [ "memchr", "version_check", @@ -1230,7 +1242,7 @@ dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall", + "redox_syscall 0.1.57", "smallvec", "winapi 0.3.9", ] @@ -1300,6 +1312,15 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "redox_syscall" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ec8ca9416c5ea37062b502703cd7fcb207736bc294f6e0cf367ac6fc234570" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.3.5" @@ -1307,7 +1328,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" dependencies = [ "getrandom", - "redox_syscall", + "redox_syscall 0.1.57", "rust-argon2", ] @@ -1323,9 +1344,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.21" +version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" +checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" [[package]] name = "rust-argon2" @@ -1378,18 +1399,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.118" +version = "1.0.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" +checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.118" +version = "1.0.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" +checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" dependencies = [ "proc-macro2", "quote", @@ -1478,9 +1499,9 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "smallvec" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a55ca5f3b68e41c979bf8c46a6f1da892ca4db8f94023ce0bd32407573b1ac0" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" [[package]] name = "smithay-client-toolkit" @@ -1495,7 +1516,7 @@ dependencies = [ "lazy_static", "log", "memmap2", - "nix", + "nix 0.18.0", "wayland-client", "wayland-cursor", "wayland-protocols", @@ -1542,9 +1563,9 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" [[package]] name = "syn" -version = "1.0.58" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5" +checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" dependencies = [ "proc-macro2", "quote", @@ -1714,7 +1735,7 @@ dependencies = [ "bitflags", "downcast-rs", "libc", - "nix", + "nix 0.18.0", "scoped-tls", "wayland-commons", "wayland-scanner", @@ -1727,7 +1748,7 @@ version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "480450f76717edd64ad04a4426280d737fc3d10a236b982df7b1aee19f0e2d56" dependencies = [ - "nix", + "nix 0.18.0", "once_cell", "smallvec", "wayland-sys", @@ -1739,7 +1760,7 @@ version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6eb122c160223a7660feeaf949d0100281d1279acaaed3720eb3c9894496e5f" dependencies = [ - "nix", + "nix 0.18.0", "wayland-client", "xcursor", ] diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml index 923659d2e9c..febfbdc78bb 100644 --- a/alacritty/Cargo.toml +++ b/alacritty/Cargo.toml @@ -34,7 +34,7 @@ copypasta = { version = "0.7.0", default-features = false } libc = "0.2" unicode-width = "0.1" bitflags = "1" -dirs = "2.0.2" +dirs = "3.0.1" [build-dependencies] gl_generator = "0.14.0" diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml index 41b8b1f8c09..9fae15570e4 100644 --- a/alacritty_terminal/Cargo.toml +++ b/alacritty_terminal/Cargo.toml @@ -25,10 +25,10 @@ log = "0.4" unicode-width = "0.1" base64 = "0.12.0" regex-automata = "0.1.9" -dirs = "2.0.2" +dirs = "3.0.1" [target.'cfg(unix)'.dependencies] -nix = "0.18.0" +nix = "0.19.0" signal-hook = { version = "0.1", features = ["mio-support"] } [target.'cfg(windows)'.dependencies] diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs index 9c8df704b2f..47eb22aeb0a 100644 --- a/alacritty_terminal/src/ansi.rs +++ b/alacritty_terminal/src/ansi.rs @@ -1,6 +1,7 @@ //! ANSI Terminal Stream Parsing. use std::convert::TryFrom; +use std::time::{Duration, Instant}; use std::{io, iter, str}; use log::{debug, trace}; @@ -12,6 +13,18 @@ use alacritty_config_derive::ConfigDeserialize; use crate::index::{Column, Line}; use crate::term::color::Rgb; +/// Maximum time before a synchronized update is aborted. +const SYNC_UPDATE_TIMEOUT: Duration = Duration::from_millis(150); + +/// Maximum number of bytes read in one synchronized update (2MiB). +const SYNC_BUFFER_SIZE: usize = 0x20_0000; + +/// Beginning sequence for synchronized updates. +const SYNC_START_ESCAPE: [u8; 7] = [b'\x1b', b'P', b'=', b'1', b's', b'\x1b', b'\\']; + +/// Termination sequence for synchronized updates. +const SYNC_END_ESCAPE: [u8; 7] = [b'\x1b', b'P', b'=', b'2', b's', b'\x1b', b'\\']; + /// Parse colors in XParseColor format. fn xparse_color(color: &[u8]) -> Option { if !color.is_empty() && color[0] == b'#' { @@ -81,15 +94,109 @@ fn parse_number(input: &[u8]) -> Option { Some(num) } +/// Internal state for VTE processor. +struct ProcessorState { + /// Last processed character for repetition. + preceding_char: Option, + + /// Beginning of a synchronized update. + sync_start: Option, + + /// Bytes read during a synchronized update. + sync_bytes: Vec, +} + +impl Default for ProcessorState { + fn default() -> Self { + Self { + sync_bytes: Vec::with_capacity(SYNC_BUFFER_SIZE), + sync_start: None, + preceding_char: None, + } + } +} + /// The processor wraps a `vte::Parser` to ultimately call methods on a Handler. +#[derive(Default)] pub struct Processor { state: ProcessorState, parser: vte::Parser, } -/// Internal state for VTE processor. -struct ProcessorState { - preceding_char: Option, +impl Processor { + #[inline] + pub fn new() -> Self { + Self::default() + } + + /// Process a new byte from the PTY. + #[inline] + pub fn advance(&mut self, handler: &mut H, byte: u8, writer: &mut W) + where + H: Handler, + W: io::Write, + { + if self.state.sync_start.is_none() { + let mut performer = Performer::new(&mut self.state, handler, writer); + self.parser.advance(&mut performer, byte); + } else { + self.advance_sync(handler, byte, writer); + } + } + + /// End a synchronized update. + pub fn stop_sync(&mut self, handler: &mut H, writer: &mut W) + where + H: Handler, + W: io::Write, + { + // Process all synchronized bytes. + for i in 0..self.state.sync_bytes.len() { + let byte = self.state.sync_bytes[i]; + let mut performer = Performer::new(&mut self.state, handler, writer); + self.parser.advance(&mut performer, byte); + } + + self.state.sync_bytes.clear(); + self.state.sync_start = None; + } + + /// Synchronized update expiration time. + #[inline] + pub fn sync_timeout(&self) -> Option<&Instant> { + self.state.sync_start.as_ref() + } + + /// Number of bytes in the synchronization buffer. + #[inline] + pub fn sync_bytes_count(&self) -> usize { + self.state.sync_bytes.len() + } + + /// Process a new byte during a synchronized update. + #[cold] + fn advance_sync(&mut self, handler: &mut H, byte: u8, writer: &mut W) + where + H: Handler, + W: io::Write, + { + self.state.sync_bytes.push(byte); + + // Get the last few bytes for comparison. + let len = self.state.sync_bytes.len(); + let end = &self.state.sync_bytes[len.saturating_sub(SYNC_END_ESCAPE.len())..]; + debug_assert_eq!(SYNC_END_ESCAPE.len(), SYNC_START_ESCAPE.len()); + + // Extend synchronization period when start is received during synchronization. + if end == SYNC_START_ESCAPE { + self.state.sync_start = Some(Instant::now() + SYNC_UPDATE_TIMEOUT); + } + + // Stop when we receive the termination sequence or the buffer length is at the limit. + if end == SYNC_END_ESCAPE || len >= SYNC_BUFFER_SIZE - 1 { + self.stop_sync(handler, writer); + } + } } /// Helper type that implements `vte::Perform`. @@ -114,28 +221,6 @@ impl<'a, H: Handler + 'a, W: io::Write> Performer<'a, H, W> { } } -impl Default for Processor { - fn default() -> Processor { - Processor { state: ProcessorState { preceding_char: None }, parser: vte::Parser::new() } - } -} - -impl Processor { - pub fn new() -> Processor { - Default::default() - } - - #[inline] - pub fn advance(&mut self, handler: &mut H, byte: u8, writer: &mut W) - where - H: Handler, - W: io::Write, - { - let mut performer = Performer::new(&mut self.state, handler, writer); - self.parser.advance(&mut performer, byte); - } -} - /// Type that handles actions from the parser. /// /// XXX Should probably not provide default impls for everything, but it makes @@ -172,8 +257,6 @@ pub trait Handler { fn move_down(&mut self, _: Line) {} /// Identify the terminal (should write back to the pty stream). - /// - /// TODO this should probably return an io::Result fn identify_terminal(&mut self, _: &mut W, _intermediate: Option) {} /// Report device status. @@ -428,8 +511,6 @@ pub enum Mode { impl Mode { /// Create mode from a primitive. - /// - /// TODO lots of unhandled values. pub fn from_primitive(intermediate: Option<&u8>, num: u16) -> Option { let private = match intermediate { Some(b'?') => true, @@ -779,10 +860,18 @@ where #[inline] fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, action: char) { - debug!( - "[unhandled hook] params={:?}, ints: {:?}, ignore: {:?}, action: {:?}", - params, intermediates, ignore, action - ); + match (action, intermediates) { + ('s', [b'=']) => { + // Start a synchronized update. The end is handled with a separate parser. + if params.iter().next().map_or(false, |param| param[0] == 1) { + self.state.sync_start = Some(Instant::now() + SYNC_UPDATE_TIMEOUT); + } + }, + _ => debug!( + "[unhandled hook] params={:?}, ints: {:?}, ignore: {:?}, action: {:?}", + params, intermediates, ignore, action + ), + } } #[inline] @@ -795,7 +884,6 @@ where debug!("[unhandled unhook]"); } - // TODO replace OSC parsing with parser combinators. #[inline] fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { let terminator = if bell_terminated { "\x07" } else { "\x1b\\" }; @@ -1172,6 +1260,7 @@ where } } +#[inline] fn attrs_from_sgr_parameters(params: &mut ParamsIter<'_>) -> Vec> { let mut attrs = Vec::with_capacity(params.size_hint().0); diff --git a/alacritty_terminal/src/event_loop.rs b/alacritty_terminal/src/event_loop.rs index 2fe3f64e260..4fd0aded0ad 100644 --- a/alacritty_terminal/src/event_loop.rs +++ b/alacritty_terminal/src/event_loop.rs @@ -7,6 +7,7 @@ use std::io::{self, ErrorKind, Read, Write}; use std::marker::Send; use std::sync::Arc; use std::thread::JoinHandle; +use std::time::Instant; use log::error; #[cfg(not(windows))] @@ -58,16 +59,6 @@ struct Writing { written: usize, } -/// All of the mutable state needed to run the event loop. -/// -/// Contains list of items to write, current write state, etc. Anything that -/// would otherwise be mutated on the `EventLoop` goes here. -pub struct State { - write_list: VecDeque>, - writing: Option, - parser: ansi::Processor, -} - pub struct Notifier(pub Sender); impl event::Notify for Notifier { @@ -91,10 +82,15 @@ impl event::OnResize for Notifier { } } -impl Default for State { - fn default() -> State { - State { write_list: VecDeque::new(), parser: ansi::Processor::new(), writing: None } - } +/// All of the mutable state needed to run the event loop. +/// +/// Contains list of items to write, current write state, etc. Anything that +/// would otherwise be mutated on the `EventLoop` goes here. +#[derive(Default)] +pub struct State { + write_list: VecDeque>, + writing: Option, + parser: ansi::Processor, } impl State { @@ -261,8 +257,8 @@ where } } - if processed > 0 { - // Queue terminal redraw. + // Queue terminal redraw unless all processed bytes were synchronized. + if state.parser.sync_bytes_count() < processed && processed > 0 { self.event_proxy.send_event(Event::Wakeup); } @@ -325,13 +321,24 @@ where }; 'event_loop: loop { - if let Err(err) = self.poll.poll(&mut events, None) { + // Wakeup event loop when the maximum synchronization time was exceeded. + let sync_timeout = state.parser.sync_timeout(); + let timeout = sync_timeout.map(|st| st.saturating_duration_since(Instant::now())); + + if let Err(err) = self.poll.poll(&mut events, timeout) { match err.kind() { ErrorKind::Interrupted => continue, _ => panic!("EventLoop polling error: {:?}", err), } } + // Handle synchronized update timeout. + if events.is_empty() { + state.parser.stop_sync(&mut *self.terminal.lock(), &mut self.pty.writer()); + self.event_proxy.send_event(Event::Wakeup); + continue; + } + for event in events.iter() { match event.token() { token if token == channel_token => {