1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-27 07:18:13 +03:00

use termwiz to replace most of the escape parsing

This commit is contained in:
Wez Furlong 2018-08-03 22:37:04 -07:00
parent 83c5e796ce
commit f37de9cbe5
10 changed files with 437 additions and 1002 deletions

View File

@ -25,6 +25,9 @@ directories = "~1.0"
[dependencies.term]
path = "term"
[dependencies.termwiz]
path = "../termwiz"
[target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies]
freetype = "0.3"
servo-fontconfig = "0.4.0"

View File

@ -12,22 +12,22 @@ A terminal emulator implemented in Rust, using OpenGL ES 2 for rendering.
## Quickstart
* Install `rustup` to get the *nightly* `rust` compiler installed on your system.
* Install `rustup` to get the *stable* `rust` compiler installed on your system.
https://www.rust-lang.org/en-US/install.html
* Build in release mode: `rustup run nightly cargo build --release`
* Run it via either `rustup run nightly cargo run --release` or `target/release/wezterm`
* Build in release mode: `rustup run stable cargo build --release`
* Run it via either `rustup run stable cargo run --release` or `target/release/wezterm`
You will need a collection of support libraries; the [`get-deps`](get-deps) script will
attempt to install them for you. If it doesn't know about your system,
[please contribute instructions!](CONTRIBUTING.md)
```
$ curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly
$ curl https://sh.rustup.rs -sSf | sh -s
$ git clone --depth=1 --branch=master https://github.com/wez/wezterm.git
$ cd wezterm
$ sudo ./get-deps
$ rustup run nightly cargo build --release
$ rustup run nightly cargo run --release
$ rustup run stable cargo build --release
$ rustup run stable cargo run --release
```
## What?

View File

@ -4,7 +4,6 @@ name = "term"
version = "0.1.0"
[dependencies]
base64 = "0.9.0"
bitflags = "1.0.1"
failure = "0.1.1"
maplit = "1.0.1"
@ -14,7 +13,9 @@ serde = "1.0.27"
serde_derive = "1.0.27"
unicode-segmentation = "1.2.0"
unicode-width = "0.1.4"
vte = "0.3.2"
[dependencies.termwiz]
path = "../../termwiz"
[features]
debug-escape-sequences = []

View File

@ -70,21 +70,8 @@ macro_rules! bitfield {
};
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
#[repr(u16)]
pub enum Intensity {
Normal = 0,
Bold = 1,
Half = 2,
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
#[repr(u16)]
pub enum Underline {
None = 0,
Single = 1,
Double = 2,
}
pub use termwiz::cell::Intensity;
pub use termwiz::cell::Underline;
impl CellAttributes {
bitfield!(intensity, set_intensity, Intensity, 0b11, 0);

View File

@ -4,30 +4,8 @@ use palette;
use serde::{self, Deserialize, Deserializer};
use std::fmt;
use std::result::Result;
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
/// These correspond to the classic ANSI color indices and are
/// used for convenience/readability here in the code
pub enum AnsiColor {
Black = 0,
Maroon,
Green,
Olive,
Navy,
Purple,
Teal,
Silver,
Grey,
Red,
Lime,
Yellow,
Blue,
Fuschia,
Aqua,
White,
}
pub use termwiz::color::AnsiColor;
use termwiz::color::ColorSpec;
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash)]
pub struct RgbColor {
@ -114,6 +92,16 @@ pub enum ColorAttribute {
Rgb(RgbColor),
}
impl From<ColorSpec> for ColorAttribute {
fn from(spec: ColorSpec) -> Self {
match spec {
ColorSpec::Default => ColorAttribute::Foreground, // FIXME!
ColorSpec::PaletteIndex(i) => ColorAttribute::PaletteIndex(i),
ColorSpec::TrueColor(c) => ColorAttribute::Rgb(RgbColor::new(c.red, c.green, c.blue)),
}
}
}
#[derive(Clone)]
pub struct Palette256(pub [RgbColor; 256]);

View File

@ -1,590 +0,0 @@
//! Parsing CSI escape sequences
use super::*;
#[derive(Debug)]
pub enum LineErase {
ToRight,
ToLeft,
All,
}
#[derive(Debug)]
pub enum DisplayErase {
Below,
Above,
All,
SavedLines,
}
#[derive(Debug)]
pub enum DecPrivateMode {
ApplicationCursorKeys,
BrackedPaste,
SGRMouse,
ButtonEventMouse,
ClearAndEnableAlternateScreen,
StartBlinkingCursor,
ShowCursor,
}
#[derive(Debug)]
pub enum CSIAction {
SetPenNoLink(CellAttributes),
SetForegroundColor(color::ColorAttribute),
SetBackgroundColor(color::ColorAttribute),
SetIntensity(Intensity),
SetUnderline(Underline),
SetItalic(bool),
SetBlink(bool),
SetReverse(bool),
SetStrikethrough(bool),
SetInvisible(bool),
SetCursorXY { x: Position, y: Position },
EraseInLine(LineErase),
EraseInDisplay(DisplayErase),
SetDecPrivateMode(DecPrivateMode, bool),
RestoreDecPrivateMode(DecPrivateMode),
SaveDecPrivateMode(DecPrivateMode),
DeviceStatusReport,
ReportCursorPosition,
SetScrollingRegion { top: i64, bottom: i64 },
RequestDeviceAttributes,
DeleteLines(i64),
InsertLines(i64),
LinePosition(Position),
SaveCursor,
RestoreCursor,
ScrollLines(i64),
SoftReset,
EraseCharacter(i64),
DeleteCharacter(i64),
InsertCharacter(i64),
}
/// Constrol Sequence Initiator (CSI) Parser.
/// Since many sequences allow for composition of actions by separating
/// parameters using the ; character, we need to be able to iterate over
/// the set of parsed actions from a given CSI sequence.
/// `CSIParser` implements an Iterator that yields `CSIAction` instances as
/// it parses them out from the input sequence.
pub struct CSIParser<'a> {
intermediates: &'a [u8],
/// From vte::Perform: this flag is set when more than two intermediates
/// arrived and subsequent characters were ignored.
ignore: bool,
byte: char,
/// While params is_some we have more data to consume. The advance_by
/// method updates the slice as we consume data.
/// In a number of cases an empty params list is used to indicate
/// default values, especially for SGR, so we need to be careful not
/// to update params to an empty slice.
params: Option<&'a [i64]>,
}
impl<'a> CSIParser<'a> {
pub fn new<'b>(
params: &'b [i64],
intermediates: &'b [u8],
ignore: bool,
byte: char,
) -> CSIParser<'b> {
CSIParser {
intermediates,
ignore,
byte,
params: Some(params),
}
}
/// Consume some number of elements from params and update it.
/// Take care to avoid setting params back to an empty slice
/// as this would trigger returning a default value and/or
/// an unterminated parse loop.
fn advance_by(&mut self, n: usize, params: &'a [i64]) {
let (_, next) = params.split_at(n);
if !next.is_empty() {
self.params = Some(next);
}
}
/// Device status report
fn dsr(&mut self, params: &'a [i64]) -> Option<CSIAction> {
match (self.intermediates, params) {
(&[], &[5, _..]) => {
self.advance_by(1, params);
Some(CSIAction::DeviceStatusReport)
}
(&[], &[6, _..]) => {
self.advance_by(1, params);
Some(CSIAction::ReportCursorPosition)
}
_ => {
println!(
"dsr: unhandled sequence {:?} {:?}",
self.intermediates, params
);
None
}
}
}
fn parse_dec_mode(&self, mode: i64) -> Option<DecPrivateMode> {
match mode {
1 => Some(DecPrivateMode::ApplicationCursorKeys),
12 => Some(DecPrivateMode::StartBlinkingCursor),
25 => Some(DecPrivateMode::ShowCursor),
1002 => Some(DecPrivateMode::ButtonEventMouse),
1006 => Some(DecPrivateMode::SGRMouse),
1049 => Some(DecPrivateMode::ClearAndEnableAlternateScreen),
2004 => Some(DecPrivateMode::BrackedPaste),
_ => {
println!("unknown or unhandled DECSET mode: {}", mode);
None
}
}
}
/// DEC Private Mode (DECSET)
fn dec_set_mode(&mut self, params: &'a [i64]) -> Option<CSIAction> {
match *params {
[idx, _..] => {
self.advance_by(1, params);
self.parse_dec_mode(idx)
.map(|m| CSIAction::SetDecPrivateMode(m, true))
}
_ => {
println!(
"dec_set_mode: unhandled sequence {:?} {:?}",
self.intermediates, params
);
None
}
}
}
/// Reset DEC Private Mode (DECRST)
fn dec_reset_mode(&mut self, params: &'a [i64]) -> Option<CSIAction> {
match *params {
[idx, _..] => {
self.advance_by(1, params);
self.parse_dec_mode(idx)
.map(|m| CSIAction::SetDecPrivateMode(m, false))
}
_ => {
println!("dec_reset_mode: unhandled sequence {:?}", params);
None
}
}
}
/// Restore DEC Private Mode
fn dec_restore_mode(&mut self, params: &'a [i64]) -> Option<CSIAction> {
match *params {
[idx, _..] => {
self.advance_by(1, params);
self.parse_dec_mode(idx)
.map(CSIAction::RestoreDecPrivateMode)
}
_ => {
println!("dec_reset_mode: unhandled sequence {:?}", params);
None
}
}
}
/// Save DEC Private Mode
fn dec_save_mode(&mut self, params: &'a [i64]) -> Option<CSIAction> {
match *params {
[idx, _..] => {
self.advance_by(1, params);
self.parse_dec_mode(idx).map(CSIAction::SaveDecPrivateMode)
}
_ => {
println!("dec_save_mode: unhandled sequence {:?}", params);
None
}
}
}
/// Set Graphics Rendition (SGR)
fn sgr(&mut self, params: &'a [i64]) -> Option<CSIAction> {
match *params {
[] => {
// With no parameters, reset to default pen.
// Note that this empty case is only possible for the initial
// iteration.
Some(CSIAction::SetPenNoLink(CellAttributes::default()))
}
[0, _..] => {
// Explicitly set to default pen
self.advance_by(1, params);
Some(CSIAction::SetPenNoLink(CellAttributes::default()))
}
// This variant with a colorspace becomes ambiguous when
// embedded like this: [0, 1, 38, 2, 204, 204, 204, 48, 2, 85, 85, 204]
// so we're turning it off for now.
/*
[38, 2, _colorspace, red, green, blue, _..] => {
// ISO-8613-6 true color foreground
self.advance_by(6, params);
Some(CSIAction::SetForegroundColor(
color::ColorAttribute::Rgb(color::RgbColor {
red: red as u8,
green: green as u8,
blue: blue as u8,
}),
))
}
*/
[38, 2, red, green, blue, _..] => {
// KDE konsole compatibility for truecolor foreground
self.advance_by(5, params);
Some(CSIAction::SetForegroundColor(color::ColorAttribute::Rgb(
color::RgbColor {
red: red as u8,
green: green as u8,
blue: blue as u8,
},
)))
}
// This variant with a colorspace becomes ambiguous when
// embedded like this: [0, 1, 38, 2, 204, 204, 204, 48, 2, 85, 85, 204]
// so we're turning it off for now.
/*
[48, 2, _colorspace, red, green, blue, _..] => {
// ISO-8613-6 true color background
self.advance_by(6, params);
Some(CSIAction::SetBackgroundColor(
color::ColorAttribute::Rgb(color::RgbColor {
red: red as u8,
green: green as u8,
blue: blue as u8,
}),
))
}
*/
[48, 2, red, green, blue, _..] => {
// KDE konsole compatibility for truecolor background
self.advance_by(5, params);
Some(CSIAction::SetBackgroundColor(color::ColorAttribute::Rgb(
color::RgbColor {
red: red as u8,
green: green as u8,
blue: blue as u8,
},
)))
}
[38, 5, idx, _..] => {
// 256 color foreground color index
self.advance_by(3, params);
let color = color::ColorAttribute::PaletteIndex(idx as u8);
Some(CSIAction::SetForegroundColor(color))
}
[48, 5, idx, _..] => {
// 256 color background color index
self.advance_by(3, params);
let color = color::ColorAttribute::PaletteIndex(idx as u8);
Some(CSIAction::SetBackgroundColor(color))
}
[1, _..] => {
self.advance_by(1, params);
Some(CSIAction::SetIntensity(Intensity::Bold))
}
[2, _..] => {
self.advance_by(1, params);
Some(CSIAction::SetIntensity(Intensity::Half))
}
[3, _..] => {
self.advance_by(1, params);
Some(CSIAction::SetItalic(true))
}
[4, _..] => {
self.advance_by(1, params);
Some(CSIAction::SetUnderline(Underline::Single))
}
[5, _..] => {
self.advance_by(1, params);
Some(CSIAction::SetBlink(true))
}
[7, _..] => {
self.advance_by(1, params);
Some(CSIAction::SetReverse(true))
}
[8, _..] => {
self.advance_by(1, params);
Some(CSIAction::SetInvisible(true))
}
[9, _..] => {
self.advance_by(1, params);
Some(CSIAction::SetStrikethrough(true))
}
[21, _..] => {
self.advance_by(1, params);
Some(CSIAction::SetUnderline(Underline::Double))
}
[22, _..] => {
self.advance_by(1, params);
Some(CSIAction::SetIntensity(Intensity::Normal))
}
[23, _..] => {
self.advance_by(1, params);
Some(CSIAction::SetItalic(false))
}
[24, _..] => {
self.advance_by(1, params);
Some(CSIAction::SetUnderline(Underline::None))
}
[25, _..] => {
self.advance_by(1, params);
Some(CSIAction::SetBlink(false))
}
[27, _..] => {
self.advance_by(1, params);
Some(CSIAction::SetReverse(false))
}
[28, _..] => {
self.advance_by(1, params);
Some(CSIAction::SetInvisible(false))
}
[29, _..] => {
self.advance_by(1, params);
Some(CSIAction::SetStrikethrough(false))
}
[idx @ 30...37, _..] => {
self.advance_by(1, params);
Some(CSIAction::SetForegroundColor(
color::ColorAttribute::PaletteIndex(idx as u8 - 30),
))
}
[39, _..] => {
self.advance_by(1, params);
Some(CSIAction::SetForegroundColor(
color::ColorAttribute::Foreground,
))
}
[idx @ 40...47, _..] => {
self.advance_by(1, params);
Some(CSIAction::SetBackgroundColor(
color::ColorAttribute::PaletteIndex(idx as u8 - 40),
))
}
[49, _..] => {
self.advance_by(1, params);
Some(CSIAction::SetBackgroundColor(
color::ColorAttribute::Background,
))
}
[idx @ 90...97, _..] => {
// Bright foreground colors
self.advance_by(1, params);
Some(CSIAction::SetForegroundColor(
color::ColorAttribute::PaletteIndex(idx as u8 - 90 + 8),
))
}
[idx @ 100...107, _..] => {
// Bright background colors
self.advance_by(1, params);
Some(CSIAction::SetBackgroundColor(
color::ColorAttribute::PaletteIndex(idx as u8 - 100 + 8),
))
}
_ => {
println!("parse_sgr: unhandled csi sequence {:?}", params);
None
}
}
}
fn set_scroll_region(&mut self, params: &'a [i64]) -> Option<CSIAction> {
match *params {
[top, bottom] => {
self.advance_by(2, params);
Some(CSIAction::SetScrollingRegion {
top: top.saturating_sub(1).max(0),
bottom: bottom.saturating_sub(1).max(0),
})
}
[] => {
// Default is to restore the region to the full size of
// the screen. We don't have that information here, so
// we're just reporting the maximum possible range and
// relying on the code that acts on this to clamp accordingly
Some(CSIAction::SetScrollingRegion {
top: 0,
bottom: i64::max_value(),
})
}
_ => {
println!("set_scroll_region: invalid sequence: {:?}", params);
None
}
}
}
}
impl<'a> Iterator for CSIParser<'a> {
type Item = CSIAction;
fn next(&mut self) -> Option<CSIAction> {
let params = self.params.take();
#[cfg_attr(feature = "cargo-clippy", allow(match_same_arms))]
match (self.byte, self.intermediates, params) {
(_, _, None) => None,
// ICH: Insert Character
('@', &[], Some(&[])) => Some(CSIAction::InsertCharacter(1)),
('@', &[], Some(&[n])) => Some(CSIAction::InsertCharacter(n)),
// CUU - Cursor Up n times
('A', &[], Some(&[])) => Some(CSIAction::SetCursorXY {
x: Position::Relative(0),
y: Position::Relative(-1),
}),
('A', &[], Some(&[y])) => Some(CSIAction::SetCursorXY {
x: Position::Relative(0),
y: Position::Relative(-y),
}),
// CUD - Cursor Down n times
('B', &[], Some(&[])) => Some(CSIAction::SetCursorXY {
x: Position::Relative(0),
y: Position::Relative(1),
}),
('B', &[], Some(&[y])) => Some(CSIAction::SetCursorXY {
x: Position::Relative(0),
y: Position::Relative(y),
}),
// CUF - Cursor n forward
('C', &[], Some(&[])) => Some(CSIAction::SetCursorXY {
x: Position::Relative(1),
y: Position::Relative(0),
}),
('C', &[], Some(&[x])) => Some(CSIAction::SetCursorXY {
x: Position::Relative(x),
y: Position::Relative(0),
}),
// CUB - Cursor n backward
('D', &[], Some(&[])) => Some(CSIAction::SetCursorXY {
x: Position::Relative(-1),
y: Position::Relative(0),
}),
('D', &[], Some(&[x])) => Some(CSIAction::SetCursorXY {
x: Position::Relative(-x),
y: Position::Relative(0),
}),
// CHA: Cursor Character Absolute
('G', &[], Some(&[])) => Some(CSIAction::SetCursorXY {
x: Position::Absolute(0),
y: Position::Relative(0),
}),
// CHA: Cursor Character Absolute
('G', &[], Some(&[col])) => Some(CSIAction::SetCursorXY {
x: Position::Absolute(col.max(1) - 1),
y: Position::Relative(0),
}),
// H: Cursor Position (CUP)
// f: Horizontal and vertical position (HVP)
('H', &[], Some(&[])) | ('f', &[], Some(&[])) => Some(CSIAction::SetCursorXY {
x: Position::Absolute(0),
y: Position::Absolute(0),
}),
('H', &[], Some(&[y, x])) | ('f', &[], Some(&[y, x])) => {
// Co-ordinates are 1-based, but we want 0-based
Some(CSIAction::SetCursorXY {
x: Position::Absolute(x.max(1) - 1),
y: Position::Absolute(y.max(1) - 1),
})
}
// Erase in Display (ED)
('J', &[], Some(&[])) | ('J', &[], Some(&[0])) => {
Some(CSIAction::EraseInDisplay(DisplayErase::Below))
}
('J', &[], Some(&[1])) => Some(CSIAction::EraseInDisplay(DisplayErase::Above)),
('J', &[], Some(&[2])) => Some(CSIAction::EraseInDisplay(DisplayErase::All)),
('J', &[], Some(&[3])) => Some(CSIAction::EraseInDisplay(DisplayErase::SavedLines)),
// Erase in Line (EL)
('K', &[], Some(&[])) | ('K', &[], Some(&[0])) => {
Some(CSIAction::EraseInLine(LineErase::ToRight))
}
('K', &[], Some(&[1])) => Some(CSIAction::EraseInLine(LineErase::ToLeft)),
('K', &[], Some(&[2])) => Some(CSIAction::EraseInLine(LineErase::All)),
// Insert Liness (IL)
('L', &[], Some(&[])) => Some(CSIAction::InsertLines(1)),
('L', &[], Some(&[n])) => Some(CSIAction::InsertLines(n)),
// Delete Liness (DL)
('M', &[], Some(&[])) => Some(CSIAction::DeleteLines(1)),
('M', &[], Some(&[n])) => Some(CSIAction::DeleteLines(n)),
// DCH: Delete Character
('P', &[], Some(&[])) => Some(CSIAction::DeleteCharacter(1)),
('P', &[], Some(&[n])) => Some(CSIAction::DeleteCharacter(n)),
// SU: Scroll Up Lines
('S', &[], Some(&[])) => Some(CSIAction::ScrollLines(-1)),
('S', &[], Some(&[n])) => Some(CSIAction::ScrollLines(-n)),
// ECH: Erase Character
('X', &[], Some(&[])) => Some(CSIAction::EraseCharacter(1)),
('X', &[], Some(&[n])) => Some(CSIAction::EraseCharacter(n)),
// HPR - Character position Relative
('a', &[], Some(&[])) => Some(CSIAction::SetCursorXY {
x: Position::Relative(1),
y: Position::Relative(0),
}),
('a', &[], Some(&[x])) => Some(CSIAction::SetCursorXY {
x: Position::Relative(x),
y: Position::Relative(0),
}),
('c', &[b'>'], Some(&[]))
| ('c', &[], Some(&[]))
| ('c', &[], Some(&[0]))
| ('c', &[b'>'], Some(&[0])) => Some(CSIAction::RequestDeviceAttributes),
// VPA: Line Position Absolute
('d', &[], Some(&[])) => Some(CSIAction::LinePosition(Position::Absolute(0))),
('d', &[], Some(&[n])) => Some(CSIAction::LinePosition(Position::Absolute(n - 1))),
// VPR: Line Position Relative
('e', &[], Some(&[])) => Some(CSIAction::LinePosition(Position::Relative(0))),
('e', &[], Some(&[n])) => Some(CSIAction::LinePosition(Position::Relative(n))),
('h', &[b'?'], Some(params)) => self.dec_set_mode(params),
('l', &[b'?'], Some(params)) => self.dec_reset_mode(params),
('m', &[], Some(params)) => self.sgr(params),
('n', &[], Some(params)) => self.dsr(params),
('p', &[b'!'], Some(&[])) => Some(CSIAction::SoftReset),
('r', &[], Some(params)) => self.set_scroll_region(params),
('r', &[b'?'], Some(params)) => self.dec_restore_mode(params),
('s', &[b'?'], Some(params)) => self.dec_save_mode(params),
// SCOSC: Save Cursor
('s', &[], Some(&[])) => Some(CSIAction::SaveCursor),
// SCORC: Restore Cursor
('u', &[], Some(&[])) => Some(CSIAction::RestoreCursor),
(b, i, Some(p)) => {
println!(
"cSI unhandled byte={} params={:?} i={} {:?} ignore={}",
b,
p,
std::str::from_utf8(i).unwrap_or("<not utf8>"),
i,
self.ignore
);
None
}
}
}
}

View File

@ -1,7 +1,4 @@
//! Terminal model
#![feature(slice_patterns)]
extern crate base64;
#[macro_use]
extern crate bitflags;
#[macro_use]
@ -14,9 +11,9 @@ extern crate regex;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate termwiz;
extern crate unicode_segmentation;
extern crate unicode_width;
extern crate vte;
use failure::Error;
use std::ops::{Deref, DerefMut, Range};
@ -109,8 +106,6 @@ pub struct CursorPosition {
}
pub mod color;
mod csi;
use self::csi::*;
#[cfg(test)]
mod test;

View File

@ -1,4 +1,5 @@
use super::*;
use termwiz::escape::parser::Parser;
/// Represents the host of the terminal.
/// Provides a means for sending data to the connected pty,
@ -40,7 +41,7 @@ pub struct Terminal {
/// The terminal model/state
state: TerminalState,
/// Baseline terminal escape sequence parser
parser: vte::Parser,
parser: Parser,
}
impl Deref for Terminal {
@ -71,7 +72,7 @@ impl Terminal {
scrollback_size,
hyperlink_rules,
),
parser: vte::Parser::new(),
parser: Parser::new(),
}
}
@ -84,8 +85,6 @@ impl Terminal {
host,
};
for b in bytes.iter() {
self.parser.advance(&mut performer, *b);
}
self.parser.parse(bytes, |action| performer.perform(action));
}
}

View File

@ -1,6 +1,11 @@
use super::*;
use base64;
use std::collections::HashMap;
use std::fmt::Write;
use termwiz::escape::csi::{
Cursor, DecPrivateMode, DecPrivateModeCode, Device, Edit, EraseInDisplay, EraseInLine, Mode,
Sgr,
};
use termwiz::escape::{Action, ControlCode, Esc, EscCode, OperatingSystemCommand, CSI};
use unicode_segmentation::UnicodeSegmentation;
struct TabStop {
@ -1041,10 +1046,159 @@ impl TerminalState {
}
}
fn perform_csi(&mut self, act: CSIAction, host: &mut TerminalHost) {
debug!("{:?}", act);
match act {
CSIAction::DeleteCharacter(n) => {
fn perform_device(&mut self, dev: Device, host: &mut TerminalHost) {
match dev {
Device::DeviceAttributes(a) => eprintln!("unhandled: {:?}", a),
Device::SoftReset => {
self.pen = CellAttributes::default();
// TODO: see https://vt100.net/docs/vt510-rm/DECSTR.html
}
Device::RequestPrimaryDeviceAttributes => {
host.writer().write(DEVICE_IDENT).ok();
}
Device::RequestSecondaryDeviceAttributes => {
host.writer().write(b"\x1b[>0;0;0c").ok();
}
Device::StatusReport => {
host.writer().write(b"\x1b[0n").ok();
}
}
}
fn perform_csi_mode(&mut self, mode: Mode) {
match mode {
Mode::SetDecPrivateMode(DecPrivateMode::Code(
DecPrivateModeCode::StartBlinkingCursor,
))
| Mode::ResetDecPrivateMode(DecPrivateMode::Code(
DecPrivateModeCode::StartBlinkingCursor,
)) => {}
Mode::SetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::BracketedPaste)) => {
self.bracketed_paste = true;
}
Mode::ResetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::BracketedPaste)) => {
self.bracketed_paste = false;
}
Mode::SetDecPrivateMode(DecPrivateMode::Code(
DecPrivateModeCode::ApplicationCursorKeys,
)) => {
self.application_cursor_keys = true;
}
Mode::ResetDecPrivateMode(DecPrivateMode::Code(
DecPrivateModeCode::ApplicationCursorKeys,
)) => {
self.application_cursor_keys = false;
}
Mode::SetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::ShowCursor)) => {
self.cursor_visible = true;
}
Mode::ResetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::ShowCursor)) => {
self.cursor_visible = false;
}
Mode::SetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::MouseTracking))
| Mode::ResetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::MouseTracking)) => {
}
Mode::SetDecPrivateMode(DecPrivateMode::Code(
DecPrivateModeCode::HighlightMouseTracking,
))
| Mode::ResetDecPrivateMode(DecPrivateMode::Code(
DecPrivateModeCode::HighlightMouseTracking,
)) => {}
Mode::SetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::ButtonEventMouse)) => {
self.button_event_mouse = true;
}
Mode::ResetDecPrivateMode(DecPrivateMode::Code(
DecPrivateModeCode::ButtonEventMouse,
)) => {
self.button_event_mouse = false;
}
Mode::SetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::AnyEventMouse))
| Mode::ResetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::AnyEventMouse)) => {
}
Mode::SetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::SGRMouse)) => {
self.sgr_mouse = true;
}
Mode::ResetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::SGRMouse)) => {
self.sgr_mouse = false;
}
Mode::SetDecPrivateMode(DecPrivateMode::Code(
DecPrivateModeCode::ClearAndEnableAlternateScreen,
)) => {
if !self.alt_screen_is_active {
self.save_cursor();
self.alt_screen_is_active = true;
self.set_cursor_pos(&Position::Absolute(0), &Position::Absolute(0));
self.erase_in_display(EraseInDisplay::EraseDisplay);
self.set_scroll_viewport(0);
}
}
Mode::ResetDecPrivateMode(DecPrivateMode::Code(
DecPrivateModeCode::ClearAndEnableAlternateScreen,
)) => {
if self.alt_screen_is_active {
self.alt_screen_is_active = false;
self.restore_cursor();
self.set_scroll_viewport(0);
}
}
Mode::SaveDecPrivateMode(DecPrivateMode::Code(_))
| Mode::RestoreDecPrivateMode(DecPrivateMode::Code(_)) => {
eprintln!("save/restore dec mode unimplemented")
}
Mode::SetDecPrivateMode(DecPrivateMode::Unspecified(n))
| Mode::ResetDecPrivateMode(DecPrivateMode::Unspecified(n))
| Mode::SaveDecPrivateMode(DecPrivateMode::Unspecified(n))
| Mode::RestoreDecPrivateMode(DecPrivateMode::Unspecified(n)) => {
eprintln!("unhandled DecPrivateMode {}", n);
}
}
}
fn erase_in_display(&mut self, erase: EraseInDisplay) {
let cy = self.cursor.y;
let pen = self.pen.clone_sgr_only();
let cols = self.screen().physical_cols;
let rows = self.screen().physical_rows as VisibleRowIndex;
let col_range = 0..cols;
let row_range = match erase {
EraseInDisplay::EraseToEndOfDisplay => cy..rows,
EraseInDisplay::EraseToStartOfDisplay => 0..cy,
EraseInDisplay::EraseDisplay => 0..rows,
EraseInDisplay::EraseScrollback => {
eprintln!("TODO: ed: no support for xterm Erase Saved Lines yet");
return;
}
};
{
let screen = self.screen_mut();
for y in row_range.clone() {
screen.clear_line(y, col_range.clone(), &pen);
}
}
for y in row_range {
if self
.clear_selection_if_intersects(col_range.clone(), y as ScrollbackOrVisibleRowIndex)
{
break;
}
}
}
fn perform_csi_edit(&mut self, edit: Edit) {
match edit {
Edit::DeleteCharacter(n) => {
let y = self.cursor.y;
let x = self.cursor.x;
let limit = (x + n as usize).min(self.screen().physical_cols);
@ -1056,7 +1210,45 @@ impl TerminalState {
}
self.clear_selection_if_intersects(x..limit, y as ScrollbackOrVisibleRowIndex);
}
CSIAction::InsertCharacter(n) => {
Edit::DeleteLine(n) => {
if in_range(self.cursor.y, &self.scroll_region) {
let scroll_region = self.cursor.y..self.scroll_region.end;
self.screen_mut().scroll_up(&scroll_region, n as usize);
let scrollback_region = self.cursor.y as ScrollbackOrVisibleRowIndex
..self.scroll_region.end as ScrollbackOrVisibleRowIndex;
self.clear_selection_if_intersects_rows(scrollback_region);
}
}
Edit::EraseCharacter(n) => {
let y = self.cursor.y;
let x = self.cursor.x;
let limit = (x + n as usize).min(self.screen().physical_cols);
{
let screen = self.screen_mut();
let blank = CellAttributes::default();
for x in x..limit as usize {
screen.set_cell(x, y, ' ', &blank);
}
}
self.clear_selection_if_intersects(x..limit, y as ScrollbackOrVisibleRowIndex);
}
Edit::EraseInLine(erase) => {
let cx = self.cursor.x;
let cy = self.cursor.y;
let pen = self.pen.clone_sgr_only();
let cols = self.screen().physical_cols;
let range = match erase {
EraseInLine::EraseToEndOfLine => cx..cols,
EraseInLine::EraseToStartOfLine => 0..cx,
EraseInLine::EraseLine => 0..cols,
};
self.screen_mut().clear_line(cy, range.clone(), &pen);
self.clear_selection_if_intersects(range, cy as ScrollbackOrVisibleRowIndex);
}
Edit::InsertCharacter(n) => {
let y = self.cursor.y;
let x = self.cursor.x;
// TODO: this limiting behavior may not be correct. There's also a
@ -1070,178 +1262,7 @@ impl TerminalState {
}
self.clear_selection_if_intersects(x..limit, y as ScrollbackOrVisibleRowIndex);
}
CSIAction::EraseCharacter(n) => {
let y = self.cursor.y;
let x = self.cursor.x;
let limit = (x + n as usize).min(self.screen().physical_cols);
{
let screen = self.screen_mut();
let blank = CellAttributes::default();
for x in x..limit as usize {
screen.set_cell(x, y, ' ', &blank);
}
}
self.clear_selection_if_intersects(x..limit, y as ScrollbackOrVisibleRowIndex);
}
CSIAction::SoftReset => {
self.pen = CellAttributes::default();
// TODO: see https://vt100.net/docs/vt510-rm/DECSTR.html
}
CSIAction::SetPenNoLink(pen) => {
let link = self.pen.hyperlink.take();
self.pen = pen;
self.pen.hyperlink = link;
}
CSIAction::SetForegroundColor(color) => {
self.pen.foreground = color;
}
CSIAction::SetBackgroundColor(color) => {
self.pen.background = color;
}
CSIAction::SetIntensity(level) => {
self.pen.set_intensity(level);
}
CSIAction::SetUnderline(level) => {
self.pen.set_underline(level);
}
CSIAction::SetItalic(on) => {
self.pen.set_italic(on);
}
CSIAction::SetBlink(on) => {
self.pen.set_blink(on);
}
CSIAction::SetReverse(on) => {
self.pen.set_reverse(on);
}
CSIAction::SetStrikethrough(on) => {
self.pen.set_strikethrough(on);
}
CSIAction::SetInvisible(on) => {
self.pen.set_invisible(on);
}
CSIAction::SetCursorXY { x, y } => {
self.set_cursor_pos(&x, &y);
}
CSIAction::EraseInLine(erase) => {
let cx = self.cursor.x;
let cy = self.cursor.y;
let pen = self.pen.clone_sgr_only();
let cols = self.screen().physical_cols;
let range = match erase {
LineErase::ToRight => cx..cols,
LineErase::ToLeft => 0..cx,
LineErase::All => 0..cols,
};
self.screen_mut().clear_line(cy, range.clone(), &pen);
self.clear_selection_if_intersects(range, cy as ScrollbackOrVisibleRowIndex);
}
CSIAction::EraseInDisplay(erase) => {
let cy = self.cursor.y;
let pen = self.pen.clone_sgr_only();
let cols = self.screen().physical_cols;
let rows = self.screen().physical_rows as VisibleRowIndex;
let col_range = 0..cols;
let row_range = match erase {
DisplayErase::Below => cy..rows,
DisplayErase::Above => 0..cy,
DisplayErase::All => 0..rows,
DisplayErase::SavedLines => {
eprintln!("TODO: ed: no support for xterm Erase Saved Lines yet");
return;
}
};
{
let mut screen = self.screen_mut();
for y in row_range.clone() {
screen.clear_line(y, col_range.clone(), &pen);
}
}
for y in row_range {
if self.clear_selection_if_intersects(
col_range.clone(),
y as ScrollbackOrVisibleRowIndex,
) {
break;
}
}
}
CSIAction::SetDecPrivateMode(DecPrivateMode::StartBlinkingCursor, _) => {
// ignored
}
CSIAction::SetDecPrivateMode(DecPrivateMode::ShowCursor, on) => {
self.cursor_visible = on;
}
CSIAction::SetDecPrivateMode(DecPrivateMode::ButtonEventMouse, on) => {
self.button_event_mouse = on;
}
CSIAction::SetDecPrivateMode(DecPrivateMode::SGRMouse, on) => {
self.sgr_mouse = on;
}
CSIAction::SetDecPrivateMode(DecPrivateMode::ClearAndEnableAlternateScreen, on) => {
// TODO: some folks like to disable alt screen
match (on, self.alt_screen_is_active) {
(true, false) => {
self.perform_csi(CSIAction::SaveCursor, host);
self.alt_screen_is_active = true;
self.set_cursor_pos(&Position::Absolute(0), &Position::Absolute(0));
self.perform_csi(CSIAction::EraseInDisplay(DisplayErase::All), host);
self.set_scroll_viewport(0);
}
(false, true) => {
self.alt_screen_is_active = false;
self.perform_csi(CSIAction::RestoreCursor, host);
self.set_scroll_viewport(0);
}
_ => {}
}
}
CSIAction::SetDecPrivateMode(DecPrivateMode::ApplicationCursorKeys, on) => {
self.application_cursor_keys = on;
}
CSIAction::SetDecPrivateMode(DecPrivateMode::BrackedPaste, on) => {
self.bracketed_paste = on;
}
CSIAction::SaveDecPrivateMode(mode) => {
eprintln!("SaveDecPrivateMode {:?} not implemented", mode);
}
CSIAction::RestoreDecPrivateMode(mode) => {
eprintln!("RestoreDecPrivateMode {:?} not implemented", mode);
}
CSIAction::DeviceStatusReport => {
// "OK"
host.writer().write(b"\x1b[0n").ok(); // discard write errors
}
CSIAction::ReportCursorPosition => {
let row = self.cursor.y + 1;
let col = self.cursor.x + 1;
write!(host.writer(), "\x1b[{};{}R", row, col).ok();
}
CSIAction::SetScrollingRegion { top, bottom } => {
let rows = self.screen().physical_rows;
let mut top = top.min(rows as i64 - 1);
let mut bottom = bottom.min(rows as i64 - 1);
if top > bottom {
std::mem::swap(&mut top, &mut bottom);
}
self.scroll_region = top..bottom + 1;
}
CSIAction::RequestDeviceAttributes => {
host.writer().write(DEVICE_IDENT).ok();
}
CSIAction::DeleteLines(n) => {
if in_range(self.cursor.y, &self.scroll_region) {
let scroll_region = self.cursor.y..self.scroll_region.end;
self.screen_mut().scroll_up(&scroll_region, n as usize);
let scrollback_region = self.cursor.y as ScrollbackOrVisibleRowIndex
..self.scroll_region.end as ScrollbackOrVisibleRowIndex;
self.clear_selection_if_intersects_rows(scrollback_region);
}
}
CSIAction::InsertLines(n) => {
Edit::InsertLine(n) => {
if in_range(self.cursor.y, &self.scroll_region) {
let scroll_region = self.cursor.y..self.scroll_region.end;
self.screen_mut().scroll_down(&scroll_region, n as usize);
@ -1251,24 +1272,125 @@ impl TerminalState {
self.clear_selection_if_intersects_rows(scrollback_region);
}
}
CSIAction::SaveCursor => {
self.saved_cursor = self.cursor;
Edit::ScrollDown(n) => self.scroll_down(n as usize),
Edit::ScrollUp(n) => self.scroll_up(n as usize),
Edit::EraseInDisplay(erase) => self.erase_in_display(erase),
}
}
fn perform_csi_cursor(&mut self, cursor: Cursor, host: &mut TerminalHost) {
match cursor {
Cursor::SetTopAndBottomMargins { top, bottom } => {
let rows = self.screen().physical_rows;
let mut top = (top as i64).saturating_sub(1).min(rows as i64 - 1);
let mut bottom = (bottom as i64).saturating_sub(1).min(rows as i64 - 1);
if top > bottom {
std::mem::swap(&mut top, &mut bottom);
}
self.scroll_region = top..bottom + 1;
}
CSIAction::RestoreCursor => {
let x = self.saved_cursor.x;
let y = self.saved_cursor.y;
self.set_cursor_pos(&Position::Absolute(x as i64), &Position::Absolute(y));
}
CSIAction::LinePosition(row) => {
self.set_cursor_pos(&Position::Relative(0), &row);
}
CSIAction::ScrollLines(amount) => {
if amount > 0 {
self.scroll_down(amount as usize);
} else {
self.scroll_up((-amount) as usize);
Cursor::ForwardTabulation(n) => {
for _ in 0..n {
self.c0_horizontal_tab();
}
}
Cursor::BackwardTabulation(_) => {}
Cursor::TabulationClear(_) => {}
Cursor::TabulationControl(_) => {}
Cursor::LineTabulation(_) => {}
Cursor::Left(n) => {
self.set_cursor_pos(&Position::Relative(-(n as i64)), &Position::Relative(0))
}
Cursor::Right(n) => {
self.set_cursor_pos(&Position::Relative(n as i64), &Position::Relative(0))
}
Cursor::Up(n) => {
self.set_cursor_pos(&Position::Relative(0), &Position::Relative(-(n as i64)))
}
Cursor::Down(n) => {
self.set_cursor_pos(&Position::Relative(0), &Position::Relative(n as i64))
}
Cursor::CharacterAndLinePosition { line, col } | Cursor::Position { line, col } => self
.set_cursor_pos(
&Position::Absolute((col as i64).saturating_sub(1)),
&Position::Absolute((line as i64).saturating_sub(1)),
),
Cursor::CharacterAbsolute(col) | Cursor::CharacterPositionAbsolute(col) => self
.set_cursor_pos(
&Position::Absolute((col as i64).saturating_sub(1)),
&Position::Relative(0),
),
Cursor::CharacterPositionBackward(col) => {
self.set_cursor_pos(&Position::Relative(-(col as i64)), &Position::Relative(0))
}
Cursor::CharacterPositionForward(col) => {
self.set_cursor_pos(&Position::Relative(col as i64), &Position::Relative(0))
}
Cursor::LinePositionAbsolute(line) => self.set_cursor_pos(
&Position::Relative(0),
&Position::Absolute((line as i64).saturating_sub(1)),
),
Cursor::LinePositionBackward(line) => {
self.set_cursor_pos(&Position::Relative(0), &Position::Relative(-(line as i64)))
}
Cursor::LinePositionForward(line) => {
self.set_cursor_pos(&Position::Relative(0), &Position::Relative(line as i64))
}
Cursor::NextLine(n) => {
for _ in 0..n {
self.new_line(true);
}
}
Cursor::PrecedingLine(n) => {
self.set_cursor_pos(&Position::Absolute(0), &Position::Relative(-(n as i64)))
}
Cursor::ActivePositionReport { .. } => {
// This is really a response from the terminal, and
// we don't need to process it as a terminal command
}
Cursor::RequestActivePositionReport => {
let line = self.cursor.y as u32 + 1;
let col = self.cursor.x as u32 + 1;
let report = CSI::Cursor(Cursor::ActivePositionReport { line, col });
write!(host.writer(), "{}", report).ok();
}
Cursor::SaveCursor => self.save_cursor(),
Cursor::RestoreCursor => self.restore_cursor(),
}
}
fn save_cursor(&mut self) {
self.saved_cursor = self.cursor;
}
fn restore_cursor(&mut self) {
let x = self.saved_cursor.x;
let y = self.saved_cursor.y;
self.set_cursor_pos(&Position::Absolute(x as i64), &Position::Absolute(y));
}
fn perform_csi_sgr(&mut self, sgr: Sgr) {
debug!("{:?}", sgr);
match sgr {
Sgr::Reset => {
let link = self.pen.hyperlink.take();
self.pen = CellAttributes::default();
self.pen.hyperlink = link;
}
Sgr::Intensity(intensity) => self.pen.set_intensity(intensity),
Sgr::Underline(underline) => self.pen.set_underline(underline),
Sgr::Blink(blink) => self.pen.set_blink(if blink == termwiz::cell::Blink::None {
false
} else {
true
}),
Sgr::Italic(italic) => self.pen.set_italic(italic),
Sgr::Inverse(inverse) => self.pen.set_reverse(inverse),
Sgr::Invisible(invis) => self.pen.set_invisible(invis),
Sgr::StrikeThrough(strike) => self.pen.set_strikethrough(strike),
Sgr::Foreground(col) => self.pen.foreground = col.into(),
Sgr::Background(col) => self.pen.background = col.into(),
Sgr::Font(_) => {}
}
}
}
@ -1294,7 +1416,18 @@ impl<'a> DerefMut for Performer<'a> {
}
}
impl<'a> vte::Perform for Performer<'a> {
impl<'a> Performer<'a> {
pub fn perform(&mut self, action: Action) {
match action {
Action::Print(c) => self.print(c),
Action::Control(code) => self.control(code),
Action::DeviceControl(ctrl) => eprintln!("Unhandled {:?}", ctrl),
Action::OperatingSystemCommand(osc) => self.osc_dispatch(*osc),
Action::Esc(esc) => self.esc_dispatch(esc),
Action::CSI(csi) => self.csi_dispatch(csi),
}
}
/// Draw a character to the screen
fn print(&mut self, c: char) {
if self.wrap_next {
@ -1337,166 +1470,94 @@ impl<'a> vte::Perform for Performer<'a> {
}
}
fn execute(&mut self, byte: u8) {
debug!("execute {:02x}", byte);
match byte {
b'\n' | 0x0b /* VT */ | 0x0c /* FF */ => {
fn control(&mut self, control: ControlCode) {
match control {
ControlCode::LineFeed | ControlCode::VerticalTab | ControlCode::FormFeed => {
self.new_line(true /* TODO: depend on terminal mode */)
}
b'\r' => /* CR */ {
ControlCode::CarriageReturn => {
self.set_cursor_pos(&Position::Absolute(0), &Position::Relative(0));
}
0x08 /* BS */ => {
ControlCode::Backspace => {
self.set_cursor_pos(&Position::Relative(-1), &Position::Relative(0));
}
b'\t' => self.c0_horizontal_tab(),
b'\x07' => eprintln!("Ding! (this is the bell"),
_ => println!("unhandled vte execute {}", byte),
}
}
fn hook(&mut self, _: &[i64], _: &[u8], _: bool) {}
fn put(&mut self, _: u8) {}
fn unhook(&mut self) {}
fn osc_dispatch(&mut self, osc: &[&[u8]]) {
match *osc {
[b"0", title] => {
if let Ok(title) = str::from_utf8(title) {
self.title = title.to_string();
self.host.set_title(title);
} else {
eprintln!("OSC: failed to decode utf title for {:?}", title);
}
}
[b"52", _selection_number] => {
// Clear selection
self.host.set_clipboard(None).ok();
}
[b"52", _selection_number, b"?"] => {
// query selection
}
[b"52", _selection_number, selection_data] => {
// Set selection
fn decode_and_clip(data: &[u8], host: &mut TerminalHost) -> Result<(), Error> {
let bytes = base64::decode(data)?;
let s = str::from_utf8(&bytes)?;
host.set_clipboard(Some(s.to_string()))?;
Ok(())
}
match decode_and_clip(selection_data, self.host) {
Ok(_) => (),
Err(err) => {
eprintln!("failed to set clipboard in response to OSC 52: {:?}", err)
}
}
}
[b"8", params, url] => {
// Hyperlinks per:
// https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
match (str::from_utf8(params), str::from_utf8(url)) {
(Ok(params), Ok(url)) => {
let params = hyperlink::parse_link_params(params);
if !url.is_empty() {
self.set_hyperlink(Some(Hyperlink::new(url, &params)));
} else {
self.set_hyperlink(None);
}
}
_ => {
eprintln!("problem decoding URL/params {:?}, {:?}", url, params);
self.set_hyperlink(None)
}
}
}
[b"777", _..] => {
// Appears to be an old RXVT command to address perl extensions
}
[b"7", _cwd_uri] => {
// OSC 7 is used to advise the terminal of the cwd of the running application
}
_ => {
if !osc.is_empty() {
eprint!("OSC unhandled:");
for p in osc.iter() {
eprint!(" {:?}", str::from_utf8(p));
}
eprintln!(" bytes: {:?}", osc);
} else {
eprintln!("OSC unhandled: {:?}", osc);
}
}
}
}
fn csi_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, byte: char) {
/*
println!(
"CSI params={:?}, intermediates={:?} b={:02x} {}",
params,
intermediates,
byte as u8,
byte ,
);
*/
for act in CSIParser::new(params, intermediates, ignore, byte) {
self.state.perform_csi(act, self.host);
ControlCode::HorizontalTab => self.c0_horizontal_tab(),
ControlCode::Bell => eprintln!("Ding! (this is the bell"),
_ => println!("unhandled ControlCode {:?}", control),
}
}
fn esc_dispatch(&mut self, params: &[i64], intermediates: &[u8], _ignore: bool, byte: u8) {
debug!(
"ESC params={:?}, intermediates={:?} b={:02x} {}",
params, intermediates, byte, byte as char
);
// Sequences from both of these sections show up in this handler:
// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-C1-_8-Bit_-Control-Characters
// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Controls-beginning-with-ESC
match (byte, intermediates, params) {
// String Terminator (ST); explicitly has nothing to do here, as its purpose is
// handled by vte::Parser
(b'\\', &[], &[]) => {}
// Application Keypad (DECKPAM)
(b'=', &[], &[]) => {
fn csi_dispatch(&mut self, csi: CSI) {
match csi {
CSI::Sgr(sgr) => self.state.perform_csi_sgr(sgr),
CSI::Cursor(cursor) => self.state.perform_csi_cursor(cursor, self.host),
CSI::Edit(edit) => self.state.perform_csi_edit(edit),
CSI::Mode(mode) => self.state.perform_csi_mode(mode),
CSI::Device(dev) => self.state.perform_device(*dev, self.host),
CSI::Mouse(mouse) => eprintln!("mouse report sent by app? {:?}", mouse),
CSI::Unspecified(unspec) => eprintln!("unknown unspecified CSI: {:?}", unspec),
};
}
fn esc_dispatch(&mut self, esc: Esc) {
match esc {
Esc::Code(EscCode::StringTerminator) => {
// String Terminator (ST); explicitly has nothing to do here, as its purpose is
// handled by vte::Parser
}
Esc::Code(EscCode::DecApplicationKeyPad) => {
debug!("DECKPAM on");
self.application_keypad = true;
}
// Normal Keypad (DECKPAM)
(b'>', &[], &[]) => {
Esc::Code(EscCode::DecNormalKeyPad) => {
debug!("DECKPAM off");
self.application_keypad = false;
}
// Reverse Index (RI)
(b'M', &[], &[]) => self.c1_reverse_index(),
// Index (IND)
(b'D', &[], &[]) => self.c1_index(),
// Next Line (NEL)
(b'E', &[], &[]) => self.c1_nel(),
// Horizontal Tab Set (HTS)
(b'H', &[], &[]) => self.c1_hts(),
Esc::Code(EscCode::ReverseIndex) => self.c1_reverse_index(),
Esc::Code(EscCode::Index) => self.c1_index(),
Esc::Code(EscCode::NextLine) => self.c1_nel(),
Esc::Code(EscCode::HorizontalTabSet) => self.c1_hts(),
Esc::Code(EscCode::DecLineDrawing) => debug!("ESC: smacs/DecLineDrawing"),
Esc::Code(EscCode::AsciiCharacterSet) => debug!("ESC: rmacs/AsciiCharacterSet"),
Esc::Code(EscCode::DecSaveCursorPosition) => self.save_cursor(),
Esc::Code(EscCode::DecRestoreCursorPosition) => self.restore_cursor(),
_ => println!("ESC: unhandled {:?}", esc),
}
}
// Enable alternate character set mode (smacs)
(b'0', &[b'('], &[]) => {
debug!("ESC: smacs");
fn osc_dispatch(&mut self, osc: OperatingSystemCommand) {
match osc {
OperatingSystemCommand::SetIconNameAndWindowTitle(title)
| OperatingSystemCommand::SetWindowTitle(title) => {
self.host.set_title(&title);
}
// Exit alternate character set mode (rmacs)
(b'B', &[b'('], &[]) => {
debug!("ESC: rmacs");
OperatingSystemCommand::SetIconName(_) => {}
OperatingSystemCommand::SetHyperlink(Some(link)) => {
let params: HashMap<_, _> = link
.params()
.iter()
.map(|(a, b)| (a.as_str(), b.as_str()))
.collect();
self.set_hyperlink(Some(Hyperlink::new(link.uri(), &params)));
}
OperatingSystemCommand::SetHyperlink(None) => {
self.set_hyperlink(None);
}
OperatingSystemCommand::Unspecified(unspec) => {
eprintln!("Unhandled {:?}", unspec);
}
// DECSC - Save Cursor
(b'7', &[], &[]) => self.state.perform_csi(CSIAction::SaveCursor, self.host),
// DECRC - Restore Cursor
(b'8', &[], &[]) => self.state.perform_csi(CSIAction::RestoreCursor, self.host),
(..) => {
println!(
"ESC unhandled params={:?}, intermediates={:?} b={:02x} {}",
params, intermediates, byte, byte as char
);
OperatingSystemCommand::ClearSelection(_) => {
self.host.set_clipboard(None).ok();
}
OperatingSystemCommand::QuerySelection(_) => {}
OperatingSystemCommand::SetSelection(_, selection_data) => match self
.host
.set_clipboard(Some(selection_data))
{
Ok(_) => (),
Err(err) => eprintln!("failed to set clipboard in response to OSC 52: {:?}", err),
},
}
}
}

View File

@ -6,6 +6,8 @@ mod c0;
mod c1;
mod csi;
mod selection;
use termwiz::escape::csi::{Edit, EraseInDisplay, EraseInLine};
use termwiz::escape::CSI;
#[derive(Default, Debug)]
struct TestHost {
@ -87,25 +89,14 @@ impl TestTerm {
self.print(format!("{};{}f", row + 1, col + 1));
}
fn erase_in_display(&mut self, erase: DisplayErase) {
self.print(CSI);
let num = match erase {
DisplayErase::Below => 0,
DisplayErase::Above => 1,
DisplayErase::All => 2,
DisplayErase::SavedLines => 3,
};
self.print(format!("{}J", num));
fn erase_in_display(&mut self, erase: EraseInDisplay) {
let csi = CSI::Edit(Edit::EraseInDisplay(erase));
self.print(format!("{}", csi));
}
fn erase_in_line(&mut self, erase: LineErase) {
self.print(CSI);
let num = match erase {
LineErase::ToRight => 0,
LineErase::ToLeft => 1,
LineErase::All => 2,
};
self.print(format!("{}K", num));
fn erase_in_line(&mut self, erase: EraseInLine) {
let csi = CSI::Edit(Edit::EraseInLine(erase));
self.print(format!("{}", csi));
}
fn hyperlink(&mut self, link: &Rc<Hyperlink>) {
@ -347,7 +338,7 @@ fn basic_output() {
],
);
term.erase_in_display(DisplayErase::Above);
term.erase_in_display(EraseInDisplay::EraseToStartOfDisplay);
assert_visible_contents(
&term,
&[
@ -360,7 +351,7 @@ fn basic_output() {
);
term.cup(2, 2);
term.erase_in_line(LineErase::ToRight);
term.erase_in_line(EraseInLine::EraseToEndOfLine);
assert_visible_contents(
&term,
&[
@ -372,7 +363,7 @@ fn basic_output() {
],
);
term.erase_in_line(LineErase::ToLeft);
term.erase_in_line(EraseInLine::EraseToStartOfLine);
assert_visible_contents(
&term,
&[