use crate::input::mode;
pub fn render_dec_modes(mode_bits: u32, cursor_style: u32, out: &mut Vec<u8>) {
let on = |bit: u32| mode_bits & bit != 0;
if !on(mode::MODE_CURSOR) {
out.extend_from_slice(b"\x1b[?25l");
}
if !on(mode::MODE_WRAP) {
out.extend_from_slice(b"\x1b[?7l");
}
if on(mode::MODE_INSERT) {
out.extend_from_slice(b"\x1b[4h");
}
if on(mode::MODE_KCURSOR) {
out.extend_from_slice(b"\x1b[?1h");
}
if on(mode::MODE_KKEYPAD) {
out.extend_from_slice(b"\x1b=");
}
if on(mode::MODE_CRLF) {
out.extend_from_slice(b"\x1b[20h");
}
if on(mode::MODE_FOCUSON) {
out.extend_from_slice(b"\x1b[?1004h");
}
if on(mode::MODE_BRACKETPASTE) {
out.extend_from_slice(b"\x1b[?2004h");
}
if on(mode::MODE_THEME_UPDATES) {
out.extend_from_slice(b"\x1b[?2031h");
}
if on(mode::MODE_MOUSE_ALL) {
out.extend_from_slice(b"\x1b[?1003h");
} else if on(mode::MODE_MOUSE_BUTTON) {
out.extend_from_slice(b"\x1b[?1002h");
} else if on(mode::MODE_MOUSE_STANDARD) {
out.extend_from_slice(b"\x1b[?1000h");
}
if on(mode::MODE_MOUSE_SGR) {
out.extend_from_slice(b"\x1b[?1006h");
} else if on(mode::MODE_MOUSE_UTF8) {
out.extend_from_slice(b"\x1b[?1005h");
}
if on(mode::MODE_KEYS_KITTY) {
out.extend_from_slice(b"\x1b[>1u");
} else if on(mode::MODE_KEYS_EXTENDED_2) {
out.extend_from_slice(b"\x1b[>4;2m");
} else if on(mode::MODE_KEYS_EXTENDED) {
out.extend_from_slice(b"\x1b[>4;1m");
}
if cursor_style != 0 {
out.extend_from_slice(format!("\x1b[{cursor_style} q").as_bytes());
}
}
pub fn render_dec_modes_for_snapshot(mode_bits: u32, cursor_style: u32, out: &mut Vec<u8>) {
let on = |bit: u32| mode_bits & bit != 0;
out.extend_from_slice(if on(mode::MODE_WRAP) {
b"\x1b[?7h".as_slice()
} else {
b"\x1b[?7l".as_slice()
});
out.extend_from_slice(if on(mode::MODE_INSERT) {
b"\x1b[4h".as_slice()
} else {
b"\x1b[4l".as_slice()
});
out.extend_from_slice(if on(mode::MODE_KCURSOR) {
b"\x1b[?1h".as_slice()
} else {
b"\x1b[?1l".as_slice()
});
out.extend_from_slice(if on(mode::MODE_KKEYPAD) {
b"\x1b=".as_slice()
} else {
b"\x1b>".as_slice()
});
out.extend_from_slice(if on(mode::MODE_CRLF) {
b"\x1b[20h".as_slice()
} else {
b"\x1b[20l".as_slice()
});
out.extend_from_slice(if on(mode::MODE_FOCUSON) {
b"\x1b[?1004h".as_slice()
} else {
b"\x1b[?1004l".as_slice()
});
out.extend_from_slice(if on(mode::MODE_BRACKETPASTE) {
b"\x1b[?2004h".as_slice()
} else {
b"\x1b[?2004l".as_slice()
});
out.extend_from_slice(if on(mode::MODE_THEME_UPDATES) {
b"\x1b[?2031h".as_slice()
} else {
b"\x1b[?2031l".as_slice()
});
out.extend_from_slice(b"\x1b[?2026l");
out.extend_from_slice(b"\x1b[?1000l\x1b[?1002l\x1b[?1003l");
if on(mode::MODE_MOUSE_ALL) {
out.extend_from_slice(b"\x1b[?1003h");
} else if on(mode::MODE_MOUSE_BUTTON) {
out.extend_from_slice(b"\x1b[?1002h");
} else if on(mode::MODE_MOUSE_STANDARD) {
out.extend_from_slice(b"\x1b[?1000h");
}
out.extend_from_slice(b"\x1b[?1005l\x1b[?1006l");
if on(mode::MODE_MOUSE_SGR) {
out.extend_from_slice(b"\x1b[?1006h");
} else if on(mode::MODE_MOUSE_UTF8) {
out.extend_from_slice(b"\x1b[?1005h");
}
if on(mode::MODE_KEYS_KITTY) {
out.extend_from_slice(b"\x1b[>1u");
} else if on(mode::MODE_KEYS_EXTENDED_2) {
out.extend_from_slice(b"\x1b[>4;2m");
} else if on(mode::MODE_KEYS_EXTENDED) {
out.extend_from_slice(b"\x1b[>4;1m");
} else {
out.extend_from_slice(b"\x1b[<u");
out.extend_from_slice(b"\x1b[>4;0m");
}
out.extend_from_slice(format!("\x1b[{cursor_style} q").as_bytes());
}
#[cfg(test)]
mod tests {
use super::*;
fn rendered(mode_bits: u32, cursor_style: u32) -> String {
let mut out = Vec::new();
render_dec_modes(mode_bits, cursor_style, &mut out);
String::from_utf8(out).expect("dec-mode sequences are ascii")
}
#[test]
fn post_reset_defaults_emit_nothing() {
assert_eq!(rendered(mode::MODE_CURSOR | mode::MODE_WRAP, 0), "");
}
#[test]
fn mouse_button_sgr_and_bracketed_paste_are_reasserted() {
let bits = mode::MODE_CURSOR
| mode::MODE_WRAP
| mode::MODE_MOUSE_BUTTON
| mode::MODE_MOUSE_SGR
| mode::MODE_BRACKETPASTE;
let out = rendered(bits, 0);
assert!(out.contains("\x1b[?1002h"), "{out:?}");
assert!(out.contains("\x1b[?1006h"), "{out:?}");
assert!(out.contains("\x1b[?2004h"), "{out:?}");
}
#[test]
fn highest_mouse_level_wins() {
let bits =
mode::MODE_CURSOR | mode::MODE_WRAP | mode::MODE_MOUSE_ALL | mode::MODE_MOUSE_BUTTON;
let out = rendered(bits, 0);
assert!(out.contains("\x1b[?1003h"), "{out:?}");
assert!(!out.contains("\x1b[?1002h"), "{out:?}");
}
#[test]
fn application_cursor_keys_and_keypad() {
let bits = mode::MODE_CURSOR | mode::MODE_WRAP | mode::MODE_KCURSOR | mode::MODE_KKEYPAD;
let out = rendered(bits, 0);
assert!(out.contains("\x1b[?1h"), "{out:?}");
assert!(out.contains("\x1b="), "{out:?}");
}
#[test]
fn cursor_hidden_when_mode_off() {
let out = rendered(mode::MODE_WRAP, 0); assert!(out.contains("\x1b[?25l"), "{out:?}");
}
#[test]
fn cursor_style_emitted_when_set() {
let out = rendered(mode::MODE_CURSOR | mode::MODE_WRAP, 4);
assert!(out.contains("\x1b[4 q"), "{out:?}");
}
#[test]
fn kitty_keyboard_mode_is_reasserted_as_csi_u() {
let bits = mode::MODE_CURSOR
| mode::MODE_WRAP
| mode::MODE_KEYS_EXTENDED_2
| mode::MODE_KEYS_KITTY;
let out = rendered(bits, 0);
assert!(out.contains("\x1b[>1u"), "{out:?}");
assert!(!out.contains("\x1b[>4;2m"), "{out:?}");
}
#[test]
fn snapshot_mode_render_clears_stale_transient_modes() {
let mut out = Vec::new();
render_dec_modes_for_snapshot(mode::MODE_CURSOR | mode::MODE_WRAP, 0, &mut out);
let out = String::from_utf8(out).expect("snapshot modes are ascii");
assert!(out.contains("\x1b[?2026l"), "{out:?}");
assert!(out.contains("\x1b[?2004l"), "{out:?}");
assert!(out.contains("\x1b[?1006l"), "{out:?}");
assert!(out.contains("\x1b[<u"), "{out:?}");
assert!(out.contains("\x1b[>4;0m"), "{out:?}");
assert!(out.contains("\x1b[0 q"), "{out:?}");
assert!(!out.contains("\x1b[?2026h"), "{out:?}");
}
#[test]
fn snapshot_mode_render_sets_enabled_modes_after_clearing_family_state() {
let bits = mode::MODE_CURSOR
| mode::MODE_WRAP
| mode::MODE_MOUSE_BUTTON
| mode::MODE_MOUSE_SGR
| mode::MODE_BRACKETPASTE
| mode::MODE_KEYS_EXTENDED_2;
let mut out = Vec::new();
render_dec_modes_for_snapshot(bits, 4, &mut out);
let out = String::from_utf8(out).expect("snapshot modes are ascii");
assert!(out.contains("\x1b[?1002h"), "{out:?}");
assert!(out.contains("\x1b[?1006h"), "{out:?}");
assert!(out.contains("\x1b[?2004h"), "{out:?}");
assert!(out.contains("\x1b[>4;2m"), "{out:?}");
assert!(out.contains("\x1b[4 q"), "{out:?}");
}
#[test]
fn snapshot_mode_render_prefers_kitty_keyboard_over_xterm_modifier_mode() {
let bits = mode::MODE_CURSOR
| mode::MODE_WRAP
| mode::MODE_KEYS_EXTENDED_2
| mode::MODE_KEYS_KITTY;
let mut out = Vec::new();
render_dec_modes_for_snapshot(bits, 0, &mut out);
let out = String::from_utf8(out).expect("snapshot modes are ascii");
assert!(out.contains("\x1b[>1u"), "{out:?}");
assert!(!out.contains("\x1b[>4;2m"), "{out:?}");
}
}