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 all 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: 3 additions & 1 deletion mux/src/renderable.rs
Expand Up @@ -74,6 +74,7 @@ pub fn terminal_get_lines(
term: &mut Terminal,
lines: Range<StableRowIndex>,
) -> (StableRowIndex, Vec<Line>) {
let reverse = term.get_reverse_video();
let screen = term.screen_mut();
let phys_range = screen.stable_range(&lines);
(
Expand All @@ -84,8 +85,9 @@ pub fn terminal_get_lines(
.skip(phys_range.start)
.take(phys_range.end - phys_range.start)
.map(|line| {
let cloned = line.clone();
let mut cloned = line.clone();
line.clear_dirty();
cloned.set_reverse(reverse);
cloned
})
.collect(),
Expand Down
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()
}
}
80 changes: 74 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
for t in &mut self.tabs {
*t = false;
}
Expand Down Expand Up @@ -240,6 +242,9 @@ pub struct TerminalState {
/// Reverse Wraparound Mode
reverse_wraparound_mode: bool,

/// Reverse video mode
reverse_video_mode: bool,

/// https://vt100.net/docs/vt510-rm/DECOM.html
/// When OriginMode is enabled, cursor is constrained to the
/// scroll region and its position is relative to the scroll
Expand Down Expand Up @@ -290,6 +295,7 @@ pub struct TerminalState {
g0_charset: CharSet,
g1_charset: CharSet,
shift_out: bool,
newline_mode: bool,

tabs: TabStop,

Expand Down Expand Up @@ -475,6 +481,7 @@ impl TerminalState {
// a dec terminal is false, because it is more useful this way.
dec_auto_wrap: true,
reverse_wraparound_mode: false,
reverse_video_mode: false,
dec_origin_mode: false,
insert: false,
application_cursor_keys: false,
Expand All @@ -495,6 +502,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 +1036,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 @@ -1795,6 +1806,7 @@ impl TerminalState {
self.screen.saved_cursor().take();

self.reverse_wraparound_mode = false;
self.reverse_video_mode = false;
}
Device::RequestPrimaryDeviceAttributes => {
let mut ident = "\x1b[?65".to_string(); // Vt500
Expand All @@ -1819,6 +1831,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 +1967,16 @@ 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.reverse_video_mode = true;
}

Mode::ResetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::ReverseVideo)) => {
// Turn off reverse video for all of the lines on the
// display.
self.reverse_video_mode = false;
}

Mode::SetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::Select132Columns))
Expand All @@ -1977,6 +2001,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 +2855,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 @@ -2941,6 +2973,11 @@ impl TerminalState {

Ok(zones)
}

#[inline]
pub fn get_reverse_video(&self) -> bool {
self.reverse_video_mode
}
}

/// A helper struct for implementing `vtparse::VTActor` while compartmentalizing
Expand Down Expand Up @@ -3189,6 +3226,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 +3298,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 +3367,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 +3417,7 @@ impl<'a> Performer<'a> {
self.insert = false;
self.dec_auto_wrap = true;
self.reverse_wraparound_mode = false;
self.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 +3436,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
20 changes: 20 additions & 0 deletions term/src/test/mod.rs
Expand Up @@ -703,6 +703,26 @@ fn test_dec_special_graphics() {
);
}

/// Test double-width / double-height sequences.
#[test]
fn test_dec_double_width() {
let mut term = TestTerm::new(4, 50, 0);

term.print("\u{1b}#3line1\r\nline2\u{1b}#4\r\nli\u{1b}#6ne3\r\n\u{1b}#5line4");
assert_visible_contents(
&term,
file!(),
line!(),
&["line1", "line2", "line3", "line4"],
);

let lines = term.screen().visible_lines();
assert!(lines[0].is_double_height_top());
assert!(lines[1].is_double_height_bottom());
assert!(lines[2].is_double_width());
assert!(lines[3].is_single_width());
}

/// Test the behavior of wrapped lines when we resize the terminal
/// wider and then narrower.
#[test]
Expand Down