mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-18 13:55:23 -05:00
Rewrite ansi parser using vte crate
Using the vte crate allows removal of the ansi parser state machine and enables us to just be concerned with actions described in the protocol. In addition to making alacritty simpler, this also improves correctness and performance.
This commit is contained in:
parent
0d6d0dc0af
commit
13d2d66b6b
6 changed files with 434 additions and 815 deletions
16
Cargo.lock
generated
16
Cargo.lock
generated
|
@ -14,6 +14,7 @@ dependencies = [
|
|||
"serde 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_macros 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_yaml 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vte 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -780,6 +781,19 @@ dependencies = [
|
|||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "vte"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"utf8parse 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "0.1.6"
|
||||
|
@ -973,6 +987,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum tempfile 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9270837a93bad1b1dac18fe67e786b3c960513af86231f6f4f57fddd594ff0c8"
|
||||
"checksum time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "3c7ec6d62a20df54e07ab3b78b9a3932972f4b7981de295563686849eb3989af"
|
||||
"checksum user32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6717129de5ac253f5642fc78a51d0c7de6f9f53d617fc94e9bae7f6e71cf5504"
|
||||
"checksum utf8parse 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a15ea87f3194a3a454c78d79082b4f5e85f6956ddb6cb86bbfbe4892aa3c0323"
|
||||
"checksum vte 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "889d519744c8d773708d246d046ad1c1f1c3e319d44aaeb941c56217afc94f0d"
|
||||
"checksum walkdir 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d42144c31c9909882ce76e696b306b88a5b091721251137d5d522d1ef3da7cf9"
|
||||
"checksum wayland-client 0.5.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ced3094c157b5cc0a08d40530e1a627d9f88b9a436971338d2646439128a559e"
|
||||
"checksum wayland-kbd 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "73bc10e84c1da90777beffecd24742baea17564ffc2a9918af41871c748eb050"
|
||||
|
|
|
@ -17,6 +17,7 @@ parking_lot = { version = "0.3.1", features = ["nightly"] }
|
|||
serde = "0.8"
|
||||
serde_yaml = "0.4"
|
||||
serde_macros = "0.8"
|
||||
vte = "0.1.1"
|
||||
|
||||
[build-dependencies]
|
||||
gl_generator = "0.5"
|
||||
|
|
949
src/ansi.rs
949
src/ansi.rs
File diff suppressed because it is too large
Load diff
194
src/io.rs
194
src/io.rs
|
@ -1,194 +0,0 @@
|
|||
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! Unmerged utf8 chars iterator vendored from std::io
|
||||
//!
|
||||
use std::io::{BufRead, ErrorKind, Error};
|
||||
use std::fmt;
|
||||
use std::error as std_error;
|
||||
use std::result;
|
||||
use std::char;
|
||||
|
||||
static UTF8_CHAR_WIDTH: [u8; 256] = [
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x1F
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x3F
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x5F
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x7F
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0x9F
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0xBF
|
||||
0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
|
||||
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // 0xDF
|
||||
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, // 0xEF
|
||||
4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0, // 0xFF
|
||||
];
|
||||
|
||||
/// Given a first byte, determine how many bytes are in this UTF-8 character
|
||||
#[inline]
|
||||
pub fn utf8_char_width(b: u8) -> usize {
|
||||
return UTF8_CHAR_WIDTH[b as usize] as usize;
|
||||
}
|
||||
|
||||
/// An iterator over the `char`s of a reader.
|
||||
///
|
||||
/// This struct is generally created by calling [`utf8_chars()`][utf8_chars] on a reader.
|
||||
/// Please see the documentation of `utf8_chars()` for more details.
|
||||
///
|
||||
/// [utf8_chars]: trait.BufRead.html#method.utf8_chars
|
||||
pub struct Utf8Chars<R> {
|
||||
inner: R,
|
||||
}
|
||||
|
||||
impl<R> Utf8Chars<R> {
|
||||
pub fn new(inner: R) -> Utf8Chars<R> {
|
||||
Utf8Chars { inner: inner }
|
||||
}
|
||||
}
|
||||
|
||||
/// An enumeration of possible errors that can be generated from the `Utf8Chars`
|
||||
/// adapter.
|
||||
#[derive(Debug)]
|
||||
pub enum Utf8CharsError {
|
||||
/// Variant representing that the underlying stream was read successfully
|
||||
/// but contains a byte sequence ill-formed in UTF-8.
|
||||
InvalidUtf8,
|
||||
|
||||
/// Variant representing that the underlying stream contains the start
|
||||
/// of a byte sequence well-formed in UTF-8, but ends prematurely.
|
||||
///
|
||||
/// Contains number of unused bytes
|
||||
IncompleteUtf8(usize),
|
||||
|
||||
/// Variant representing that an I/O error occurred.
|
||||
Io(Error),
|
||||
}
|
||||
|
||||
impl<R: BufRead> Iterator for Utf8Chars<R> {
|
||||
type Item = result::Result<char, Utf8CharsError>;
|
||||
|
||||
// allow(unused_assignments) because consumed += 1 is not recognized as being used
|
||||
#[allow(unused_assignments)]
|
||||
fn next(&mut self) -> Option<result::Result<char, Utf8CharsError>> {
|
||||
macro_rules! read_byte {
|
||||
(EOF => $on_eof: expr) => {
|
||||
{
|
||||
let byte;
|
||||
loop {
|
||||
match self.inner.fill_buf() {
|
||||
Ok(buffer) => {
|
||||
if let Some(&b) = buffer.first() {
|
||||
byte = b;
|
||||
break
|
||||
} else {
|
||||
$on_eof
|
||||
}
|
||||
}
|
||||
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
|
||||
Err(e) => return Some(Err(Utf8CharsError::Io(e))),
|
||||
}
|
||||
}
|
||||
byte
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let first = read_byte!(EOF => return None);
|
||||
self.inner.consume(1);
|
||||
|
||||
let mut consumed = 1;
|
||||
|
||||
macro_rules! continuation_byte {
|
||||
($range: pat) => {
|
||||
{
|
||||
match read_byte!(EOF => return Some(Err(Utf8CharsError::IncompleteUtf8(consumed)))) {
|
||||
byte @ $range => {
|
||||
self.inner.consume(1);
|
||||
consumed += 1;
|
||||
(byte & 0b0011_1111) as u32
|
||||
}
|
||||
_ => return Some(Err(Utf8CharsError::InvalidUtf8))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ranges can be checked against https://tools.ietf.org/html/rfc3629#section-4
|
||||
let code_point = match utf8_char_width(first) {
|
||||
1 => return Some(Ok(first as char)),
|
||||
2 => {
|
||||
let second = continuation_byte!(0x80...0xBF);
|
||||
((first & 0b0001_1111) as u32) << 6 | second
|
||||
}
|
||||
3 => {
|
||||
let second = match first {
|
||||
0xE0 => continuation_byte!(0xA0...0xBF),
|
||||
0xE1...0xEC => continuation_byte!(0x80...0xBF),
|
||||
0xED => continuation_byte!(0x80...0x9F),
|
||||
0xEE...0xEF => continuation_byte!(0x80...0xBF),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let third = continuation_byte!(0x80...0xBF);
|
||||
((first & 0b0000_1111) as u32) << 12 | second << 6 | third
|
||||
}
|
||||
4 => {
|
||||
let second = match first {
|
||||
0xF0 => continuation_byte!(0x90...0xBF),
|
||||
0xF0...0xF3 => continuation_byte!(0x80...0xBF),
|
||||
0xF4 => continuation_byte!(0x80...0x8F),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let third = continuation_byte!(0x80...0xBF);
|
||||
let fourth = continuation_byte!(0x80...0xBF);
|
||||
((first & 0b0000_0111) as u32) << 18 | second << 12 | third << 6 | fourth
|
||||
}
|
||||
_ => return Some(Err(Utf8CharsError::InvalidUtf8))
|
||||
};
|
||||
unsafe {
|
||||
Some(Ok(char::from_u32_unchecked(code_point)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std_error::Error for Utf8CharsError {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
Utf8CharsError::InvalidUtf8 => "invalid UTF-8 byte sequence",
|
||||
Utf8CharsError::IncompleteUtf8(_) => {
|
||||
"stream ended in the middle of an UTF-8 byte sequence"
|
||||
}
|
||||
Utf8CharsError::Io(ref e) => std_error::Error::description(e),
|
||||
}
|
||||
}
|
||||
fn cause(&self) -> Option<&std_error::Error> {
|
||||
match *self {
|
||||
Utf8CharsError::InvalidUtf8 | Utf8CharsError::IncompleteUtf8(_) => None,
|
||||
Utf8CharsError::Io(ref e) => e.cause(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Utf8CharsError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Utf8CharsError::InvalidUtf8 => {
|
||||
"invalid UTF-8 byte sequence".fmt(f)
|
||||
}
|
||||
Utf8CharsError::IncompleteUtf8(_) => {
|
||||
"stream ended in the middle of an UTF-8 byte sequence".fmt(f)
|
||||
}
|
||||
Utf8CharsError::Io(ref e) => e.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
87
src/main.rs
87
src/main.rs
|
@ -31,6 +31,7 @@ extern crate notify;
|
|||
extern crate parking_lot;
|
||||
extern crate serde;
|
||||
extern crate serde_yaml;
|
||||
extern crate vte;
|
||||
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
|
@ -49,7 +50,6 @@ mod tty;
|
|||
pub mod ansi;
|
||||
mod term;
|
||||
mod util;
|
||||
mod io;
|
||||
mod sync;
|
||||
|
||||
use std::sync::{mpsc, Arc};
|
||||
|
@ -58,7 +58,6 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
|||
use parking_lot::{Condvar, Mutex, MutexGuard};
|
||||
|
||||
use config::Config;
|
||||
use io::{Utf8Chars, Utf8CharsError};
|
||||
use meter::Meter;
|
||||
use renderer::{QuadRenderer, GlyphCache};
|
||||
use sync::PriorityMutex;
|
||||
|
@ -69,6 +68,24 @@ use util::thread;
|
|||
/// Channel used by resize handling on mac
|
||||
static mut resize_sender: Option<mpsc::Sender<(u32, u32)>> = None;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Flag(Arc<AtomicBool>);
|
||||
impl Flag {
|
||||
pub fn new(initial_value: bool) -> Flag {
|
||||
Flag(Arc::new(AtomicBool::new(initial_value)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self) -> bool {
|
||||
self.0.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set(&self, value: bool) {
|
||||
self.0.store(value, Ordering::Release)
|
||||
}
|
||||
}
|
||||
|
||||
/// Resize handling for Mac
|
||||
fn window_resize_handler(width: u32, height: u32) {
|
||||
unsafe {
|
||||
|
@ -163,18 +180,27 @@ fn main() {
|
|||
resize_sender = Some(tx.clone());
|
||||
}
|
||||
|
||||
let signal_flag = Flag::new(false);
|
||||
|
||||
let terminal = Arc::new(PriorityMutex::new(terminal));
|
||||
let window = Arc::new(window);
|
||||
|
||||
let pty_reader = PtyReader::spawn(terminal.clone(), reader, window.create_window_proxy());
|
||||
let pty_reader = PtyReader::spawn(
|
||||
terminal.clone(),
|
||||
reader,
|
||||
window.create_window_proxy(),
|
||||
signal_flag.clone()
|
||||
);
|
||||
|
||||
// Wraps a renderer and gives simple draw() api.
|
||||
let mut display = Display::new(window.clone(),
|
||||
terminal.clone(),
|
||||
renderer,
|
||||
glyph_cache,
|
||||
render_timer,
|
||||
rx);
|
||||
let mut display = Display::new(
|
||||
window.clone(),
|
||||
terminal.clone(),
|
||||
renderer,
|
||||
glyph_cache,
|
||||
render_timer,
|
||||
rx
|
||||
);
|
||||
|
||||
// Event processor
|
||||
let mut processor = event::Processor::new(&mut writer, terminal.clone(), tx);
|
||||
|
@ -184,6 +210,8 @@ fn main() {
|
|||
// Wait for something to happen
|
||||
processor.process_events(&window);
|
||||
|
||||
signal_flag.set(false);
|
||||
|
||||
// Maybe draw the terminal
|
||||
let terminal = terminal.lock_high();
|
||||
if terminal.dirty {
|
||||
|
@ -205,45 +233,32 @@ struct PtyReader;
|
|||
impl PtyReader {
|
||||
pub fn spawn<R>(terminal: Arc<PriorityMutex<Term>>,
|
||||
mut pty: R,
|
||||
proxy: ::glutin::WindowProxy)
|
||||
proxy: ::glutin::WindowProxy,
|
||||
signal_flag: Flag)
|
||||
-> std::thread::JoinHandle<()>
|
||||
where R: std::io::Read + Send + 'static
|
||||
{
|
||||
thread::spawn_named("pty reader", move || {
|
||||
let mut buf = [0u8; 4096];
|
||||
let mut start = 0;
|
||||
let mut pty_parser = ansi::Parser::new();
|
||||
let mut pty_parser = ansi::Processor::new();
|
||||
|
||||
loop {
|
||||
if let Ok(got) = pty.read(&mut buf[start..]) {
|
||||
let mut remain = 0;
|
||||
if let Ok(got) = pty.read(&mut buf[..]) {
|
||||
let mut terminal = terminal.lock_high();
|
||||
|
||||
// if `start` is nonzero, then actual bytes in buffer is > `got` by `start` bytes.
|
||||
let end = start + got;
|
||||
{
|
||||
let mut terminal = terminal.lock_low();
|
||||
terminal.dirty = true;
|
||||
for c in Utf8Chars::new(&buf[..end]) {
|
||||
match c {
|
||||
Ok(c) => pty_parser.advance(&mut *terminal, c),
|
||||
Err(err) => match err {
|
||||
Utf8CharsError::IncompleteUtf8(unused) => {
|
||||
remain = unused;
|
||||
break;
|
||||
},
|
||||
_ => panic!("{}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
for byte in &buf[..got] {
|
||||
pty_parser.advance(&mut *terminal, *byte);
|
||||
}
|
||||
|
||||
proxy.wakeup_event_loop();
|
||||
terminal.dirty = true;
|
||||
|
||||
// Move any leftover bytes to front of buffer
|
||||
for i in 0..remain {
|
||||
buf[i] = buf[end - (remain - i)];
|
||||
// Only wake up the event loop if it hasn't already been signaled. This is a
|
||||
// really important optimization because waking up the event loop redundantly
|
||||
// burns *a lot* of cycles.
|
||||
if !signal_flag.get() {
|
||||
proxy.wakeup_event_loop();
|
||||
signal_flag.set(true);
|
||||
}
|
||||
start = remain;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -457,7 +457,7 @@ impl ansi::Handler for Term {
|
|||
/// A character to be displayed
|
||||
#[inline]
|
||||
fn input(&mut self, c: char) {
|
||||
debug_print!("{}", c);
|
||||
debug_print!("{}; attrs = {:?}", c, self.attr);
|
||||
if self.cursor.col == self.grid.num_cols() {
|
||||
debug_println!("wrapping");
|
||||
if (self.cursor.line + 1) >= self.scroll_region.end {
|
||||
|
|
Loading…
Reference in a new issue