summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--Cargo.lock96
-rw-r--r--alacritty/Cargo.toml2
-rw-r--r--alacritty_terminal/Cargo.toml4
-rw-r--r--alacritty_terminal/src/ansi.rs216
-rw-r--r--alacritty_terminal/src/event_loop.rs41
6 files changed, 278 insertions, 82 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f0fa7e32..37eb741f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,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 ST`/`DCS = 2 s ST`
### Fixed
diff --git a/Cargo.lock b/Cargo.lock
index 43e30741..e3d25717 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -75,7 +75,7 @@ dependencies = [
"mio-anonymous-pipes",
"mio-extras",
"miow 0.3.6",
- "nix",
+ "nix 0.19.1",
"parking_lot",
"regex-automata",
"serde",
@@ -192,14 +192,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b036167e76041694579972c28cf4877b4f92da222560ddb49008937b6a6727c"
dependencies = [
"log",
- "nix",
+ "nix 0.18.0",
]
[[package]]
name = "cc"
-version = "1.0.66"
+version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
+checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
[[package]]
name = "cfg-if"
@@ -394,9 +394,9 @@ dependencies = [
[[package]]
name = "core-text"
-version = "19.1.0"
+version = "19.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2c7f46e8b820fd5f4b28528104b28b0a91cbe9e9c5bde8017087fb44bc93a60"
+checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25"
dependencies = [
"core-foundation 0.9.1",
"core-graphics 0.22.2",
@@ -428,13 +428,14 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
-version = "0.8.1"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
+checksum = "bae8f328835f8f5a6ceb6a7842a7f2d0c03692adb5c889347235d59194731fe3"
dependencies = [
"autocfg",
"cfg-if 1.0.0",
"lazy_static",
+ "loom",
]
[[package]]
@@ -506,11 +507,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",
]
@@ -568,9 +568,9 @@ dependencies = [
[[package]]
name = "embed-resource"
-version = "1.5.1"
+version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bd6a41d89e233bcd6978fe7333191a2054d518d105a1165ada1d2ebc445ce98"
+checksum = "04e03c3dae04b8f252f2866d25e0ec8f2f488efba43bc6e307274b65c4321f94"
dependencies = [
"cc",
"vswhom",
@@ -595,7 +595,7 @@ checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8"
dependencies = [
"cfg-if 1.0.0",
"libc",
- "redox_syscall 0.2.4",
+ "redox_syscall 0.2.5",
"winapi 0.3.9",
]
@@ -705,6 +705,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
[[package]]
+name = "generator"
+version = "0.6.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9fed24fd1e18827652b4d55652899a1e9da8e54d91624dc3437a5bc3a9f9a9c"
+dependencies = [
+ "cc",
+ "libc",
+ "log",
+ "rustversion",
+ "winapi 0.3.9",
+]
+
+[[package]]
name = "getrandom"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -939,6 +952,17 @@ dependencies = [
]
[[package]]
+name = "loom"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d44c73b4636e497b4917eb21c33539efa3816741a2d3ff26c6316f1b529481a4"
+dependencies = [
+ "cfg-if 1.0.0",
+ "generator",
+ "scoped-tls",
+]
+
+[[package]]
name = "malloc_buf"
version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1111,10 +1135,22 @@ dependencies = [
]
[[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.1.0"
+version = "6.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab6f70b46d6325aa300f1c7bb3d470127dfc27806d8ea6bf294ee0ce643ce2b1"
+checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
dependencies = [
"memchr",
"version_check",
@@ -1233,7 +1269,7 @@ dependencies = [
"cfg-if 1.0.0",
"instant",
"libc",
- "redox_syscall 0.2.4",
+ "redox_syscall 0.2.5",
"smallvec",
"winapi 0.3.9",
]
@@ -1305,9 +1341,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "redox_syscall"
-version = "0.2.4"
+version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05ec8ca9416c5ea37062b502703cd7fcb207736bc294f6e0cf367ac6fc234570"
+checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
dependencies = [
"bitflags",
]
@@ -1362,6 +1398,12 @@ dependencies = [
]
[[package]]
+name = "rustversion"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb5d2a036dc6d2d8fd16fde3498b04306e29bd193bf306a57427019b823d5acd"
+
+[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1507,7 +1549,7 @@ dependencies = [
"lazy_static",
"log",
"memmap2",
- "nix",
+ "nix 0.18.0",
"wayland-client",
"wayland-cursor",
"wayland-protocols",
@@ -1574,18 +1616,18 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "1.0.23"
+version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146"
+checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.23"
+version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1"
+checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
dependencies = [
"proc-macro2",
"quote",
@@ -1726,7 +1768,7 @@ dependencies = [
"bitflags",
"downcast-rs",
"libc",
- "nix",
+ "nix 0.18.0",
"scoped-tls",
"wayland-commons",
"wayland-scanner",
@@ -1739,7 +1781,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",
@@ -1751,7 +1793,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 923659d2..febfbdc7 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 41b8b1f8..9fae1557 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 59877c14..b46aec0b 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,21 @@ 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;
+
+/// Number of bytes in the synchronized update DCS sequence before the passthrough parameters.
+const SYNC_ESCAPE_START_LEN: usize = 5;
+
+/// Start of the DCS sequence for beginning synchronized updates.
+const SYNC_START_ESCAPE_START: [u8; SYNC_ESCAPE_START_LEN] = [b'\x1b', b'P', b'=', b'1', b's'];
+
+/// Start of the DCS sequence for terminating synchronized updates.
+const SYNC_END_ESCAPE_START: [u8; SYNC_ESCAPE_START_LEN] = [b'\x1b', b'P', b'=', b'2', b's'];
+
/// Parse colors in XParseColor format.
fn xparse_color(color: &[u8]) -> Option<Rgb> {
if !color.is_empty() && color[0] == b'#' {
@@ -81,15 +97,157 @@ fn parse_number(input: &[u8]) -> Option<u8> {
Some(num)
}
+/// Internal state for VTE processor.
+#[derive(Debug, Default)]
+struct ProcessorState {
+ /// Last processed character for repetition.
+ preceding_char: Option<char>,
+
+ /// DCS sequence waiting for termination.
+ dcs: Option<Dcs>,
+
+ /// State for synchronized terminal updates.
+ sync_state: SyncState,
+}
+
+#[derive(Debug)]
+struct SyncState {
+ /// Expiration time of the synchronized update.
+ timeout: Option<Instant>,
+
+ /// Sync DCS waiting for termination sequence.
+ pending_dcs: Option<Dcs>,
+
+ /// Bytes read during the synchronized update.
+ buffer: Vec<u8>,
+}
+
+impl Default for SyncState {
+ fn default() -> Self {
+ Self { buffer: Vec::with_capacity(SYNC_BUFFER_SIZE), pending_dcs: None, timeout: None }
+ }
+}
+
+/// Pending DCS sequence.
+#[derive(Debug)]
+enum Dcs {
+ /// Begin of the synchronized update.
+ SyncStart,
+
+ /// End of the synchronized update.
+ SyncEnd,
+}
+
/// 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<char>,
+impl Processor {
+ #[inline]
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Process a new byte from the PTY.
+ #[inline]
+ pub fn advance<H, W>(&mut self, handler: &mut H, byte: u8, writer: &mut W)
+ where
+ H: Handler,
+ W: io::Write,
+ {
+ if self.state.sync_state.timeout.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<H, W>(&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_state.buffer.len() {
+ let byte = self.state.sync_state.buffer[i];
+ let mut performer = Performer::new(&mut self.state, handler, writer);
+ self.parser.advance(&mut performer, byte);
+ }
+
+ // Resetting state after processing makes sure we don't interpret buffered sync escapes.
+ self.state.sync_state.buffer.clear();
+ self.state.sync_state.timeout = None;
+ }
+
+ /// Synchronized update expiration time.
+ #[inline]
+ pub fn sync_timeout(&self) -> Option<&Instant> {
+ self.state.sync_state.timeout.as_ref()
+ }
+
+ /// Number of bytes in the synchronization buffer.
+ #[inline]
+ pub fn sync_bytes_count(&self) -> usize {
+ self.state.sync_state.buffer.len()
+ }
+
+ /// Process a new byte during a synchronized update.
+ #[cold]
+ fn advance_sync<H, W>(&mut self, handler: &mut H, byte: u8, writer: &mut W)
+ where
+ H: Handler,
+ W: io::Write,
+ {
+ self.state.sync_state.buffer.push(byte);
+
+ // Handle sync DCS escape sequences.
+ match self.state.sync_state.pending_dcs {
+ Some(_) => self.advance_sync_dcs_end(handler, byte, writer),
+ None => self.advance_sync_dcs_start(),
+ }
+ }
+
+ /// Find the start of sync DCS sequences.
+ fn advance_sync_dcs_start(&mut self) {
+ // Get the last few bytes for comparison.
+ let len = self.state.sync_state.buffer.len();
+ let offset = len.saturating_sub(SYNC_ESCAPE_START_LEN);
+ let end = &self.state.sync_state.buffer[offset..];
+
+ // Check for extension/termination of the synchronized update.
+ if end == SYNC_START_ESCAPE_START {
+ self.state.sync_state.pending_dcs = Some(Dcs::SyncStart);
+ } else if end == SYNC_END_ESCAPE_START || len >= SYNC_BUFFER_SIZE - 1 {
+ self.state.sync_state.pending_dcs = Some(Dcs::SyncEnd);
+ }
+ }
+
+ /// Parse the DCS termination sequence for synchronized updates.
+ fn advance_sync_dcs_end<H, W>(&mut self, handler: &mut H, byte: u8, writer: &mut W)
+ where
+ H: Handler,
+ W: io::Write,
+ {
+ match byte {
+ // Ignore DCS passthrough characters.
+ 0x00..=0x17 | 0x19 | 0x1c..=0x7f | 0xa0..=0xff => (),
+ // Cancel the DCS sequence.
+ 0x18 | 0x1a | 0x80..=0x9f => self.state.sync_state.pending_dcs = None,
+ // Dispatch on ESC.
+ 0x1b => match self.state.sync_state.pending_dcs.take() {
+ Some(Dcs::SyncStart) => {
+ self.state.sync_state.timeout = Some(Instant::now() + SYNC_UPDATE_TIMEOUT);
+ },
+ Some(Dcs::SyncEnd) => self.stop_sync(handler, writer),
+ None => (),
+ },
+ }
+ }
}
/// Helper type that implements `vte::Perform`.
@@ -114,28 +272,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<H, W>(&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 +308,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<W: io::Write>(&mut self, _: &mut W, _intermediate: Option<char>) {}
/// Report device status.
@@ -428,8 +562,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<Mode> {
let private = match intermediate {
Some(b'?') => true,
@@ -779,10 +911,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.dcs = Some(Dcs::SyncStart);
+ }
+ },
+ _ => debug!(
+ "[unhandled hook] params={:?}, ints: {:?}, ignore: {:?}, action: {:?}",
+ params, intermediates, ignore, action
+ ),
+ }
}
#[inline]
@@ -792,10 +932,15 @@ where
#[inline]
fn unhook(&mut self) {
- debug!("[unhandled unhook]");
+ match self.state.dcs {
+ Some(Dcs::SyncStart) => {
+ self.state.sync_state.timeout = Some(Instant::now() + SYNC_UPDATE_TIMEOUT);
+ },
+ Some(Dcs::SyncEnd) => (),
+ _ => 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 +1317,7 @@ where
}
}
+#[inline]
fn attrs_from_sgr_parameters(params: &mut ParamsIter<'_>) -> Vec<Option<Attr>> {
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 8a6441ce..09c71668 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<Cow<'static, [u8]>>,
- writing: Option<Writing>,
- parser: ansi::Processor,
-}
-
pub struct Notifier(pub Sender<Msg>);
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<Cow<'static, [u8]>>,
+ writing: Option<Writing>,
+ 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 the event loop when a synchronized update timeout was reached.
+ 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 => {