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:
parent
83c5e796ce
commit
f37de9cbe5
@ -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"
|
||||
|
12
README.md
12
README.md
@ -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?
|
||||
|
@ -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 = []
|
||||
|
@ -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);
|
||||
|
@ -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]);
|
||||
|
||||
|
590
term/src/csi.rs
590
term/src/csi.rs
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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, ¶ms)));
|
||||
} 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(), ¶ms)));
|
||||
}
|
||||
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),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
&[
|
||||
|
Loading…
Reference in New Issue
Block a user