Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve VT102 compliance #904

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 33 additions & 0 deletions config/src/lib.rs
Expand Up @@ -1065,6 +1065,24 @@ pub struct Config {
#[serde(default)]
pub default_cursor_style: DefaultCursorStyle,

/// Specifies how often blinking text (normal speed) transitions
/// between visible and invisible, expressed in milliseconds.
/// Setting this to 0 disables slow text blinking. Note that this
/// value is approximate due to the way that the system event loop
/// schedulers manage timers; non-zero values will be at least the
/// interval specified with some degree of slop.
#[serde(default = "default_text_blink_rate")]
pub text_blink_rate: u64,

/// Specifies how often blinking text (rapid speed) transitions
/// between visible and invisible, expressed in milliseconds.
/// Setting this to 0 disables rapid text blinking. Note that this
/// value is approximate due to the way that the system event loop
/// schedulers manage timers; non-zero values will be at least the
/// interval specified with some degree of slop.
#[serde(default = "default_text_blink_rate_rapid")]
pub text_blink_rate_rapid: u64,

/// If non-zero, specifies the period (in seconds) at which various
/// statistics are logged. Note that there is a minimum period of
/// 10 seconds.
Expand Down Expand Up @@ -1121,6 +1139,9 @@ pub struct Config {
#[serde(default = "default_word_boundary")]
pub selection_word_boundary: String,

#[serde(default = "default_enq_answerback")]
pub enq_answerback: String,

#[serde(default = "default_true")]
pub adjust_window_size_when_changing_font_size: bool,

Expand Down Expand Up @@ -1189,6 +1210,10 @@ fn default_word_boundary() -> String {
" \t\n{[}]()\"'`".to_string()
}

fn default_enq_answerback() -> String {
"".to_string()
}

fn default_one_point_oh_f64() -> f64 {
1.0
}
Expand Down Expand Up @@ -1715,6 +1740,14 @@ fn default_cursor_blink_rate() -> u64 {
800
}

fn default_text_blink_rate() -> u64 {
500
}

fn default_text_blink_rate_rapid() -> u64 {
250
}

fn default_swap_backspace_and_delete() -> bool {
// cfg!(target_os = "macos")
// See: https://github.com/wez/wezterm/issues/88
Expand Down
4 changes: 4 additions & 0 deletions config/src/terminal.rs
Expand Up @@ -34,4 +34,8 @@ impl wezterm_term::TerminalConfiguration for TermConfig {
fn alternate_buffer_wheel_scroll_speed(&self) -> u8 {
configuration().alternate_buffer_wheel_scroll_speed
}

fn enq_answerback(&self) -> String {
configuration().enq_answerback.clone()
}
}
4 changes: 4 additions & 0 deletions term/src/config.rs
Expand Up @@ -72,4 +72,8 @@ pub trait TerminalConfiguration: std::fmt::Debug {
fn alternate_buffer_wheel_scroll_speed(&self) -> u8 {
3
}

fn enq_answerback(&self) -> String {
"".to_string()
}
}
24 changes: 19 additions & 5 deletions term/src/screen.rs
Expand Up @@ -32,6 +32,10 @@ pub struct Screen {
/// that we're the primary rather than the alternate screen.
allow_scrollback: bool,

/// Whether reverse video mode is enabled. If set, all new lines will
/// be in reverse video.
pub reverse_video_mode: bool,

/// Physical, visible height of the screen (not including scrollback)
pub physical_rows: usize,
/// Physical, visible width of the screen
Expand Down Expand Up @@ -72,6 +76,7 @@ impl Screen {
physical_rows,
physical_cols,
stable_row_index_offset: 0,
reverse_video_mode: false,
}
}

Expand Down Expand Up @@ -212,7 +217,8 @@ impl Screen {
// lines than the viewport size, or we resized taller,
// pad us back out to the viewport size
while self.lines.len() < physical_rows {
self.lines.push_back(Line::with_width(0));
self.lines
.push_back(Line::with_width_reverse(0, self.reverse_video_mode));
}

let new_cursor_y;
Expand All @@ -233,7 +239,8 @@ impl Screen {
physical_rows.saturating_sub(new_cursor_y as usize);
let actual_num_rows_after_cursor = self.lines.len().saturating_sub(cursor_y);
for _ in actual_num_rows_after_cursor..required_num_rows_after_cursor {
self.lines.push_back(Line::with_width(0));
self.lines
.push_back(Line::with_width_reverse(0, self.reverse_video_mode));
}
} else {
// Compute the new cursor location; this is logically the inverse
Expand Down Expand Up @@ -573,11 +580,15 @@ impl Screen {
if scroll_region.end as usize == self.physical_rows {
// It's cheaper to push() than it is insert() at the end
for _ in 0..to_add {
self.lines.push_back(Line::with_width(0));
self.lines
.push_back(Line::with_width_reverse(0, self.reverse_video_mode));
}
} else {
for _ in 0..to_add {
self.lines.insert(phys_scroll.end, Line::with_width(0));
self.lines.insert(
phys_scroll.end,
Line::with_width_reverse(0, self.reverse_video_mode),
);
}
}
}
Expand Down Expand Up @@ -620,7 +631,10 @@ impl Screen {
}

for _ in 0..num_rows {
self.lines.insert(phys_scroll.start, Line::with_width(0));
self.lines.insert(
phys_scroll.start,
Line::with_width_reverse(0, self.reverse_video_mode),
);
}
}

Expand Down
81 changes: 75 additions & 6 deletions term/src/terminalstate.rs
Expand Up @@ -93,8 +93,10 @@ impl TabStop {
*t = false;
}
}
TabulationClear::ClearAllCharacterTabStops
| TabulationClear::ClearCharacterTabStopsAtActiveLine => {
// If we want to exactly match VT100/xterm behavior, then
// we cannot honor ClearCharacterTabStopsAtActiveLine.
TabulationClear::ClearAllCharacterTabStops => {
// | TabulationClear::ClearCharacterTabStopsAtActiveLine => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// | TabulationClear::ClearCharacterTabStopsAtActiveLine => {
// | TabulationClear::ClearCharacterTabStopsAtActiveLine

I sometimes use brace matching in vim to navigate, so it would be good to avoid this unbalanced curly!

for t in &mut self.tabs {
*t = false;
}
Expand Down Expand Up @@ -290,6 +292,7 @@ pub struct TerminalState {
g0_charset: CharSet,
g1_charset: CharSet,
shift_out: bool,
newline_mode: bool,

tabs: TabStop,

Expand Down Expand Up @@ -495,6 +498,7 @@ impl TerminalState {
g0_charset: CharSet::Ascii,
g1_charset: CharSet::DecLineDrawing,
shift_out: false,
newline_mode: false,
current_mouse_button: MouseButton::None,
tabs: TabStop::new(size.physical_cols, 8),
title: "wezterm".to_string(),
Expand Down Expand Up @@ -1028,6 +1032,9 @@ impl TerminalState {
buf.push(0x1b as char);
}
buf.push(c);
if self.newline_mode && key == Enter {
buf.push(0x0a as char);
}
}
buf.as_str()
}
Expand Down Expand Up @@ -1789,6 +1796,7 @@ impl TerminalState {
self.application_keypad = false;
self.top_and_bottom_margins = 0..self.screen().physical_rows as i64;
self.left_and_right_margins = 0..self.screen().physical_cols;
self.screen.reverse_video_mode = false;
self.screen.activate_alt_screen();
self.screen.saved_cursor().take();
self.screen.activate_primary_screen();
Expand Down Expand Up @@ -1819,6 +1827,12 @@ impl TerminalState {
self.writer.write(ST.as_bytes()).ok();
self.writer.flush().ok();
}
Device::RequestTerminalParameters(a) => {
self.writer
.write(format!("\x1b[{};1;1;128;128;1;0x", a + 2).as_bytes())
.ok();
self.writer.flush().ok();
}
Device::StatusReport => {
self.writer.write(b"\x1b[0n").ok();
self.writer.flush().ok();
Expand Down Expand Up @@ -1949,10 +1963,26 @@ impl TerminalState {
// We always output at our "best" rate
}

Mode::SetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::ReverseVideo))
| Mode::ResetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::ReverseVideo)) => {
// I'm mostly intentionally ignoring this in favor
// of respecting the configured colors
Mode::SetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::ReverseVideo)) => {
// Turn on reverse video for all of the lines on the
// display.
self.screen.reverse_video_mode = true;
for y in 0..self.screen.physical_rows as i64 {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In rust, array dimension accesses are bounds-checked by default which means that there is some (small) overhead for C style iteration. It's preferable to iterate containers directly so that the bounds check happens just once and the individual steps are essentially equivalent to bumping a pointer with no other overhead:

for line in &mut self.screen.lines {
   line.set_reverse(true);
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is O(scrollback_size) whenever the reverse video mode is changed, and some people love having really huge scrollback. Is reverse video really a property of the line? Does it make sense to expose TerminalState::get_reverse_video() as an accessor to avoid this update pass?

For rendering purposes, we could still keep LineBits::REVERSE but teach terminal_get_lines to set it when retrieving the lines that need to be rendered:

https://github.com/wez/wezterm/blob/main/mux/src/renderable.rs#L72-L93

/// Implements Pane::get_lines for Terminal
pub fn terminal_get_lines(
    term: &mut Terminal,
    lines: Range<StableRowIndex>,
) -> (StableRowIndex, Vec<Line>) {
    let screen = term.screen_mut();
    let reverse = term.get_reverse_video(); // added
    let phys_range = screen.stable_range(&lines);
    (
        screen.phys_to_stable_row_index(phys_range.start),
        screen
            .lines
            .iter_mut()
            .skip(phys_range.start)
            .take(phys_range.end - phys_range.start)
            .map(|line| {
                let cloned = line.clone();
                line.clear_dirty();
                cloned.set_reverse(reverse);  // added
                cloned
            })
            .collect(),
    )
}

If something like that makes sense, then terminalstate.rs would only need to track whether the mode is on or not, and not need to set it on the lines themselves, or deal with that when rewrapping.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few thoughts:

  • Setting/clearing reverse ought to only impact lines on the visible screen, not every line in the scrollback. If the entire scrollback is begin affected (or scanned), then I need to fix that.
  • I have the line-level flag there so that reversed lines that enter the scrollback retain the reverse flag. But that's really just an opinion: the real VT100 had no scrollback, and reverse was only ever a whole-screen setting. I should see what xterm does: whether it keeps the reverse on for lines that go into scrollback or not.
  • The flag on the screen is only so that new lines are also set to reverse. But if we decide that reverse goes away for lines that enter scrollback, then we can eliminate the line-level flag entirely and just set a reverse flag at physical screen render time.

Copy link
Author

@ghost ghost Jul 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A small bit of exploring shows that for xterm, konsole, and VTE, reverse video is a property of the screen only, and furthermore when set all lines rendered, including those in scrollback, are reversed.

I will remove the line-level attribute and leave it on the screen terminalstate only.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, reverse is nicer, thank you! However, there is one gap remaining: on lines that do not stretch to the full physical pixels, we do not see the reversed background. Example:

reverse_incomplete

I need to add a bit after render.rs:933, so that the selection and cursor are rendered, and then if there is more physical width remaining fill it with bg_color. Any suggestions?

let line_idx = self.screen.phys_row(VisibleRowIndex::from(y));
let line = self.screen.line_mut(line_idx);
line.set_reverse(true);
}
}

Mode::ResetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::ReverseVideo)) => {
// Turn off reverse video for all of the lines on the
// display.
self.screen.reverse_video_mode = false;
for y in 0..self.screen.physical_rows as i64 {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for line in &mut self.screen.lines {
   line.set_reverse(false);
}

let line_idx = self.screen.phys_row(VisibleRowIndex::from(y));
let line = self.screen.line_mut(line_idx);
line.set_reverse(false);
}
}

Mode::SetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::Select132Columns))
Expand All @@ -1977,6 +2007,13 @@ impl TerminalState {
self.insert = false;
}

Mode::SetMode(TerminalMode::Code(TerminalModeCode::AutomaticNewline)) => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would you mind adding a unit test for this?
It can be similar to the one from your previous PR!

self.newline_mode = true;
}
Mode::ResetMode(TerminalMode::Code(TerminalModeCode::AutomaticNewline)) => {
self.newline_mode = false;
}

Mode::SetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::BracketedPaste)) => {
self.bracketed_paste = true;
}
Expand Down Expand Up @@ -2824,6 +2861,7 @@ impl TerminalState {
self.g0_charset = saved.g0_charset;
self.g1_charset = saved.g1_charset;
self.shift_out = false;
self.newline_mode = false;
}

fn perform_csi_sgr(&mut self, sgr: Sgr) {
Expand Down Expand Up @@ -3189,6 +3227,9 @@ impl<'a> Performer<'a> {
self.cursor.y = y;
self.wrap_next = false;
}
if self.newline_mode {
self.cursor.x = 0;
}
}
ControlCode::CarriageReturn => {
if self.cursor.x >= self.left_and_right_margins.start {
Expand Down Expand Up @@ -3258,6 +3299,15 @@ impl<'a> Performer<'a> {
ControlCode::ShiftOut => {
self.shift_out = true;
}

ControlCode::Enquiry => {
let response = self.config.enq_answerback();
if response.len() > 0 {
write!(self.writer, "{}", response).ok();
self.writer.flush().ok();
}
}

_ => log::warn!("unhandled ControlCode {:?}", control),
}
}
Expand Down Expand Up @@ -3318,6 +3368,23 @@ impl<'a> Performer<'a> {
Esc::Code(EscCode::DecSaveCursorPosition) => self.dec_save_cursor(),
Esc::Code(EscCode::DecRestoreCursorPosition) => self.dec_restore_cursor(),

Esc::Code(EscCode::DecDoubleHeightTopHalfLine) => {
let idx = self.screen.phys_row(self.cursor.y);
self.screen.line_mut(idx).set_double_height_top();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be great to add a some test coverage for these attributes, especially because we're not rendering them :-)

I don't think we have any existing tests or conveniences for inspecting LineBits, but you should still be able to use the same term.print stuff and then use assert_eq! to verify that the right things are set on the lines in the TestTerm.

}
Esc::Code(EscCode::DecDoubleHeightBottomHalfLine) => {
let idx = self.screen.phys_row(self.cursor.y);
self.screen.line_mut(idx).set_double_height_bottom();
}
Esc::Code(EscCode::DecDoubleWidthLine) => {
let idx = self.screen.phys_row(self.cursor.y);
self.screen.line_mut(idx).set_double_width();
}
Esc::Code(EscCode::DecSingleWidthLine) => {
let idx = self.screen.phys_row(self.cursor.y);
self.screen.line_mut(idx).set_single_width();
}

Esc::Code(EscCode::DecScreenAlignmentDisplay) => {
// This one is just to make vttest happy;
// its original purpose was for aligning the CRT.
Expand Down Expand Up @@ -3351,6 +3418,7 @@ impl<'a> Performer<'a> {
self.insert = false;
self.dec_auto_wrap = true;
self.reverse_wraparound_mode = false;
self.screen.reverse_video_mode = false;
self.dec_origin_mode = false;
self.use_private_color_registers_for_each_graphic = false;
self.color_map = default_color_map();
Expand All @@ -3369,6 +3437,7 @@ impl<'a> Performer<'a> {
self.g0_charset = CharSet::Ascii;
self.g1_charset = CharSet::DecLineDrawing;
self.shift_out = false;
self.newline_mode = false;
self.tabs = TabStop::new(self.screen().physical_cols, 8);
self.palette.take();
self.top_and_bottom_margins = 0..self.screen().physical_rows as VisibleRowIndex;
Expand Down
12 changes: 12 additions & 0 deletions termwiz/src/escape/csi.rs
Expand Up @@ -289,6 +289,7 @@ pub enum Device {
/// https://github.com/mintty/mintty/issues/881
/// https://gitlab.gnome.org/GNOME/vte/-/issues/235
RequestTerminalNameAndVersion,
RequestTerminalParameters(i64),
XtSmGraphics(XtSmGraphics),
}

Expand All @@ -307,6 +308,7 @@ impl Display for Device {
Device::RequestPrimaryDeviceAttributes => write!(f, "c")?,
Device::RequestSecondaryDeviceAttributes => write!(f, ">c")?,
Device::RequestTerminalNameAndVersion => write!(f, ">q")?,
Device::RequestTerminalParameters(n) => write!(f, "{};1;1;128;128;1;0x", n + 2)?,
Device::StatusReport => write!(f, "5n")?,
Device::XtSmGraphics(g) => {
write!(f, "?{};{}", g.item, g.action_or_status)?;
Expand Down Expand Up @@ -1518,6 +1520,9 @@ impl<'a> CSIParser<'a> {
('s', &[]) => self.decslrm(params),
('t', &[]) => self.window(params).map(CSI::Window),
('u', &[]) => noparams!(Cursor, RestoreCursor, params),
('x', &[]) => self
.req_terminal_parameters(params.get(0).and_then(CsiParam::as_integer).ok_or(())?)
.map(|dev| CSI::Device(Box::new(dev))),
('y', &[b'*']) => {
fn p(params: &[CsiParam], idx: usize) -> Result<i64, ()> {
params.get(idx).and_then(CsiParam::as_integer).ok_or(())
Expand Down Expand Up @@ -1770,6 +1775,13 @@ impl<'a> CSIParser<'a> {
}
}

fn req_terminal_parameters(&mut self, n: i64) -> Result<Device, ()> {
match n {
0 | 1 => Ok(Device::RequestTerminalParameters(n)),
_ => Err(()),
}
}

/// Parse extended mouse reports known as SGR 1006 mode
fn mouse_sgr1006(&mut self, params: &'a [CsiParam]) -> Result<MouseReport, ()> {
if params.len() != 3 {
Expand Down
9 changes: 9 additions & 0 deletions termwiz/src/escape/esc.rs
Expand Up @@ -85,6 +85,15 @@ pub enum EscCode {
/// https://vt100.net/docs/vt510-rm/DECALN.html
DecScreenAlignmentDisplay = esc!('#', '8'),

/// DECDHL - DEC double-height line, top half
DecDoubleHeightTopHalfLine = esc!('#', '3'),
/// DECDHL - DEC double-height line, bottom half
DecDoubleHeightBottomHalfLine = esc!('#', '4'),
/// DECSWL - DEC single-width line
DecSingleWidthLine = esc!('#', '5'),
/// DECDWL - DEC double-width line
DecDoubleWidthLine = esc!('#', '6'),

/// These are typically sent by the terminal when keys are pressed
ApplicationModeArrowUpPress = esc!('O', 'A'),
ApplicationModeArrowDownPress = esc!('O', 'B'),
Expand Down