Fix: Filter Kitty keyboard and other terminal queries from history (#23)
This commit is contained in:
2
CLAUDE.md
Normal file
2
CLAUDE.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Version Bumping
|
||||||
|
1. Don't forget to update the version number in Cargo.toml AND in the README.md.
|
||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -133,7 +133,7 @@ checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "claude-chill"
|
name = "claude-chill"
|
||||||
version = "0.1.1"
|
version = "0.1.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = ["David Beesley"]
|
authors = ["David Beesley"]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[](https://github.com/davidbeesley/claude-chill/actions/workflows/ci.yml)
|
[](https://github.com/davidbeesley/claude-chill/actions/workflows/ci.yml)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
@@ -14,7 +14,7 @@ A PTY proxy that tames Claude Code's massive terminal updates using VT-based ren
|
|||||||
|
|
||||||
Claude Code uses synchronized output to update the terminal atomically. It wraps output in sync markers (`\x1b[?2026h` ... `\x1b[?2026l`) so the terminal renders everything at once without flicker.
|
Claude Code uses synchronized output to update the terminal atomically. It wraps output in sync markers (`\x1b[?2026h` ... `\x1b[?2026l`) so the terminal renders everything at once without flicker.
|
||||||
|
|
||||||
The problem: Claude Code sends *entire* screen redraws in these sync blocks - often thousands of lines. Your terminal receives a 5000-line atomic update when only 20 lines are visible. This causes lag, flicker, and makes scrollback useless since each update clears history.
|
The problem: Claude Code sends *entire* screen redraws in these sync blocks - often thousands of lines. Your terminal receives a 5000-line atomic update when only 20 lines are visible. This causes lag, flicker, or jitters in the terminal, making for a poor user experience.
|
||||||
|
|
||||||
## The Solution
|
## The Solution
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,14 @@
|
|||||||
/// to respond via stdin when replayed.
|
/// to respond via stdin when replayed.
|
||||||
///
|
///
|
||||||
/// Query sequences include:
|
/// Query sequences include:
|
||||||
/// - CSI c, CSI 0c, CSI >c, CSI >0c, CSI =c (Device Attributes)
|
/// - CSI c, CSI 0c, CSI >c, CSI >0c, CSI =c (Device Attributes DA1/DA2/DA3)
|
||||||
/// - CSI 5n, CSI 6n, CSI ?6n (Device Status / Cursor Position Reports)
|
/// - CSI 5n, CSI 6n, CSI ?6n (Device Status Reports / Cursor Position)
|
||||||
|
/// - CSI ?u, CSI ?Nu (Kitty keyboard protocol queries)
|
||||||
|
/// - CSI >q (XTVERSION query)
|
||||||
|
/// - CSI Ps $p, CSI ?Ps $p (DECRQM - Request Mode)
|
||||||
|
/// - CSI 14t, CSI 16t, CSI 18t, etc. (XTWINOPS window queries)
|
||||||
|
/// - DCS $ q ... ST (DECRQSS - Request Selection or Setting)
|
||||||
|
/// - DCS + q ... ST (XTGETTCAP - Request Termcap/Terminfo)
|
||||||
/// - OSC N;? ST (color/property queries)
|
/// - OSC N;? ST (color/property queries)
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
@@ -13,16 +19,21 @@ enum FilterState {
|
|||||||
Escape, // Saw ESC
|
Escape, // Saw ESC
|
||||||
Csi, // Saw ESC [
|
Csi, // Saw ESC [
|
||||||
CsiParam, // Saw ESC [ followed by params
|
CsiParam, // Saw ESC [ followed by params
|
||||||
|
CsiParamDollar, // Saw ESC [ N... $ (DECRQM prefix)
|
||||||
CsiGt, // Saw ESC [ >
|
CsiGt, // Saw ESC [ >
|
||||||
CsiGtParam, // Saw ESC [ > followed by params
|
CsiGtParam, // Saw ESC [ > followed by params
|
||||||
CsiEq, // Saw ESC [ =
|
CsiEq, // Saw ESC [ =
|
||||||
CsiQuestion, // Saw ESC [ ?
|
CsiQuestion, // Saw ESC [ ?
|
||||||
CsiQuestionParam, // Saw ESC [ ? followed by params
|
CsiQuestionParam, // Saw ESC [ ? followed by params
|
||||||
|
CsiQuestionParamDollar, // Saw ESC [ ? N... $ (DECRQM private prefix)
|
||||||
Osc, // Saw ESC ]
|
Osc, // Saw ESC ]
|
||||||
OscParam, // Inside OSC, collecting param number
|
OscParam, // Inside OSC, collecting param number
|
||||||
OscSemicolon, // Saw ; in OSC
|
OscSemicolon, // Saw ; in OSC
|
||||||
OscQuery, // Saw ? after ; in OSC (query sequence)
|
OscQuery, // Saw ? after ; in OSC (query sequence)
|
||||||
OscQuerySt, // Saw ESC in OSC query, looking for \
|
OscQuerySt, // Saw ESC in OSC query, looking for \
|
||||||
|
Dcs, // Saw ESC P (Device Control String)
|
||||||
|
DcsCollect, // Collecting DCS content
|
||||||
|
DcsEscape, // Saw ESC in DCS, looking for \
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stateful filter for terminal query sequences.
|
/// Stateful filter for terminal query sequences.
|
||||||
@@ -64,6 +75,7 @@ impl TerminalQueryFilter {
|
|||||||
match byte {
|
match byte {
|
||||||
b'[' => self.state = FilterState::Csi,
|
b'[' => self.state = FilterState::Csi,
|
||||||
b']' => self.state = FilterState::Osc,
|
b']' => self.state = FilterState::Osc,
|
||||||
|
b'P' => self.state = FilterState::Dcs,
|
||||||
_ => {
|
_ => {
|
||||||
// Not a sequence we care about, emit pending
|
// Not a sequence we care about, emit pending
|
||||||
output.extend_from_slice(&self.pending);
|
output.extend_from_slice(&self.pending);
|
||||||
@@ -114,6 +126,20 @@ impl TerminalQueryFilter {
|
|||||||
}
|
}
|
||||||
self.state = FilterState::Normal;
|
self.state = FilterState::Normal;
|
||||||
}
|
}
|
||||||
|
b't' => {
|
||||||
|
// Check if this is an XTWINOPS query (14t, 16t, 18t, etc.)
|
||||||
|
if is_window_query(&self.pending) {
|
||||||
|
self.pending.clear();
|
||||||
|
} else {
|
||||||
|
output.extend_from_slice(&self.pending);
|
||||||
|
self.pending.clear();
|
||||||
|
}
|
||||||
|
self.state = FilterState::Normal;
|
||||||
|
}
|
||||||
|
b'$' => {
|
||||||
|
// Possible DECRQM prefix
|
||||||
|
self.state = FilterState::CsiParamDollar;
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// End of CSI sequence, emit
|
// End of CSI sequence, emit
|
||||||
output.extend_from_slice(&self.pending);
|
output.extend_from_slice(&self.pending);
|
||||||
@@ -131,6 +157,11 @@ impl TerminalQueryFilter {
|
|||||||
self.pending.clear();
|
self.pending.clear();
|
||||||
self.state = FilterState::Normal;
|
self.state = FilterState::Normal;
|
||||||
}
|
}
|
||||||
|
b'q' => {
|
||||||
|
// ESC [ > q - XTVERSION query, discard
|
||||||
|
self.pending.clear();
|
||||||
|
self.state = FilterState::Normal;
|
||||||
|
}
|
||||||
b'0'..=b'9' => self.state = FilterState::CsiGtParam,
|
b'0'..=b'9' => self.state = FilterState::CsiGtParam,
|
||||||
_ => {
|
_ => {
|
||||||
output.extend_from_slice(&self.pending);
|
output.extend_from_slice(&self.pending);
|
||||||
@@ -176,6 +207,11 @@ impl TerminalQueryFilter {
|
|||||||
FilterState::CsiQuestion => {
|
FilterState::CsiQuestion => {
|
||||||
self.pending.push(byte);
|
self.pending.push(byte);
|
||||||
match byte {
|
match byte {
|
||||||
|
b'u' => {
|
||||||
|
// ESC [ ? u - Kitty keyboard protocol query, discard
|
||||||
|
self.pending.clear();
|
||||||
|
self.state = FilterState::Normal;
|
||||||
|
}
|
||||||
b'0'..=b'9' => self.state = FilterState::CsiQuestionParam,
|
b'0'..=b'9' => self.state = FilterState::CsiQuestionParam,
|
||||||
_ => {
|
_ => {
|
||||||
output.extend_from_slice(&self.pending);
|
output.extend_from_slice(&self.pending);
|
||||||
@@ -194,6 +230,15 @@ impl TerminalQueryFilter {
|
|||||||
self.pending.clear();
|
self.pending.clear();
|
||||||
self.state = FilterState::Normal;
|
self.state = FilterState::Normal;
|
||||||
}
|
}
|
||||||
|
b'u' => {
|
||||||
|
// ESC [ ? N u - Kitty keyboard protocol query, discard
|
||||||
|
self.pending.clear();
|
||||||
|
self.state = FilterState::Normal;
|
||||||
|
}
|
||||||
|
b'$' => {
|
||||||
|
// Possible DECRQM private mode prefix
|
||||||
|
self.state = FilterState::CsiQuestionParamDollar;
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
output.extend_from_slice(&self.pending);
|
output.extend_from_slice(&self.pending);
|
||||||
self.pending.clear();
|
self.pending.clear();
|
||||||
@@ -292,6 +337,86 @@ impl TerminalQueryFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FilterState::CsiParamDollar => {
|
||||||
|
self.pending.push(byte);
|
||||||
|
match byte {
|
||||||
|
b'p' => {
|
||||||
|
// ESC [ Ps $ p - DECRQM (Request Mode), discard
|
||||||
|
self.pending.clear();
|
||||||
|
self.state = FilterState::Normal;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
output.extend_from_slice(&self.pending);
|
||||||
|
self.pending.clear();
|
||||||
|
self.state = FilterState::Normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterState::CsiQuestionParamDollar => {
|
||||||
|
self.pending.push(byte);
|
||||||
|
match byte {
|
||||||
|
b'p' => {
|
||||||
|
// ESC [ ? Ps $ p - DECRQM private (Request Mode), discard
|
||||||
|
self.pending.clear();
|
||||||
|
self.state = FilterState::Normal;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
output.extend_from_slice(&self.pending);
|
||||||
|
self.pending.clear();
|
||||||
|
self.state = FilterState::Normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterState::Dcs => {
|
||||||
|
self.pending.push(byte);
|
||||||
|
match byte {
|
||||||
|
b'$' | b'+' => {
|
||||||
|
// ESC P $ or ESC P + - start of query (DECRQSS or XTGETTCAP)
|
||||||
|
self.state = FilterState::DcsCollect;
|
||||||
|
}
|
||||||
|
0x1B => {
|
||||||
|
// ESC in DCS - might be ST
|
||||||
|
self.state = FilterState::DcsEscape;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Other DCS content, collect but we'll discard queries
|
||||||
|
self.state = FilterState::DcsCollect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterState::DcsCollect => {
|
||||||
|
self.pending.push(byte);
|
||||||
|
if byte == 0x1B {
|
||||||
|
// ESC in DCS - looking for ST
|
||||||
|
self.state = FilterState::DcsEscape;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterState::DcsEscape => {
|
||||||
|
self.pending.push(byte);
|
||||||
|
match byte {
|
||||||
|
b'\\' => {
|
||||||
|
// ESC \ - ST, end of DCS
|
||||||
|
// Check if this was a query (starts with $ or +)
|
||||||
|
if is_dcs_query(&self.pending) {
|
||||||
|
// Discard query
|
||||||
|
self.pending.clear();
|
||||||
|
} else {
|
||||||
|
output.extend_from_slice(&self.pending);
|
||||||
|
self.pending.clear();
|
||||||
|
}
|
||||||
|
self.state = FilterState::Normal;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Not ST, continue collecting
|
||||||
|
self.state = FilterState::DcsCollect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,6 +458,52 @@ fn is_device_status_query(pending: &[u8]) -> bool {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_window_query(pending: &[u8]) -> bool {
|
||||||
|
// XTWINOPS queries: ESC [ Ps t where Ps is a query operation
|
||||||
|
// Query operations that cause terminal to respond:
|
||||||
|
// 11 - Report window state (iconified/normal)
|
||||||
|
// 13 - Report window position
|
||||||
|
// 14 - Report window size in pixels
|
||||||
|
// 15 - Report screen size in pixels
|
||||||
|
// 16 - Report character cell size in pixels
|
||||||
|
// 18 - Report window size in characters
|
||||||
|
// 19 - Report screen size in characters
|
||||||
|
// 20 - Report icon label
|
||||||
|
// 21 - Report window title
|
||||||
|
if pending.len() < 4 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let param_start = 2;
|
||||||
|
let param_end = pending.len() - 1; // exclude the 't'
|
||||||
|
let param_slice = &pending[param_start..param_end];
|
||||||
|
|
||||||
|
if let Ok(param_str) = std::str::from_utf8(param_slice) {
|
||||||
|
// Handle single parameter or first parameter before semicolon
|
||||||
|
let first_param = param_str.split(';').next().unwrap_or("");
|
||||||
|
if let Ok(param) = first_param.parse::<u32>() {
|
||||||
|
return matches!(param, 11 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_dcs_query(pending: &[u8]) -> bool {
|
||||||
|
// DCS queries start with ESC P followed by $ or +
|
||||||
|
// ESC P $ q ... ESC \ - DECRQSS (Request Selection or Setting)
|
||||||
|
// ESC P + q ... ESC \ - XTGETTCAP (Request Termcap/Terminfo)
|
||||||
|
if pending.len() < 4 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for ESC P $ or ESC P +
|
||||||
|
pending.len() >= 3
|
||||||
|
&& pending[0] == 0x1B
|
||||||
|
&& pending[1] == b'P'
|
||||||
|
&& (pending[2] == b'$' || pending[2] == b'+')
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -501,4 +672,194 @@ mod tests {
|
|||||||
let flushed = filter.flush();
|
let flushed = filter.flush();
|
||||||
assert_eq!(flushed, b"\x1b[".to_vec());
|
assert_eq!(flushed, b"\x1b[".to_vec());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_filter_kitty_keyboard_query_simple() {
|
||||||
|
let mut filter = TerminalQueryFilter::new();
|
||||||
|
// ESC [ ? u - Kitty keyboard protocol query
|
||||||
|
let input = b"before\x1b[?uafter";
|
||||||
|
let output = filter.filter(input);
|
||||||
|
assert_eq!(output, b"beforeafter".to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_filter_kitty_keyboard_query_with_param() {
|
||||||
|
let mut filter = TerminalQueryFilter::new();
|
||||||
|
// ESC [ ? 1 u - Kitty keyboard query with flags
|
||||||
|
let input = b"before\x1b[?1uafter";
|
||||||
|
let output = filter.filter(input);
|
||||||
|
assert_eq!(output, b"beforeafter".to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_filter_xtversion_query() {
|
||||||
|
let mut filter = TerminalQueryFilter::new();
|
||||||
|
// ESC [ > q - XTVERSION query
|
||||||
|
let input = b"before\x1b[>qafter";
|
||||||
|
let output = filter.filter(input);
|
||||||
|
assert_eq!(output, b"beforeafter".to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_filter_decrqm_query() {
|
||||||
|
let mut filter = TerminalQueryFilter::new();
|
||||||
|
// ESC [ 4 $ p - DECRQM (Request Mode)
|
||||||
|
let input = b"before\x1b[4$pafter";
|
||||||
|
let output = filter.filter(input);
|
||||||
|
assert_eq!(output, b"beforeafter".to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_filter_decrqm_private_query() {
|
||||||
|
let mut filter = TerminalQueryFilter::new();
|
||||||
|
// ESC [ ? 1 $ p - DECRQM private (Request Mode)
|
||||||
|
let input = b"before\x1b[?1$pafter";
|
||||||
|
let output = filter.filter(input);
|
||||||
|
assert_eq!(output, b"beforeafter".to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_filter_xtwinops_window_size_query() {
|
||||||
|
let mut filter = TerminalQueryFilter::new();
|
||||||
|
// ESC [ 14 t - Report window size in pixels
|
||||||
|
let input = b"before\x1b[14tafter";
|
||||||
|
let output = filter.filter(input);
|
||||||
|
assert_eq!(output, b"beforeafter".to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_filter_xtwinops_title_query() {
|
||||||
|
let mut filter = TerminalQueryFilter::new();
|
||||||
|
// ESC [ 21 t - Report window title
|
||||||
|
let input = b"before\x1b[21tafter";
|
||||||
|
let output = filter.filter(input);
|
||||||
|
assert_eq!(output, b"beforeafter".to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_filter_xtwinops_non_query() {
|
||||||
|
let mut filter = TerminalQueryFilter::new();
|
||||||
|
// ESC [ 1 t - De-iconify window (not a query)
|
||||||
|
let input = b"before\x1b[1tafter";
|
||||||
|
let output = filter.filter(input);
|
||||||
|
assert_eq!(output, input.to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_filter_dcs_decrqss() {
|
||||||
|
let mut filter = TerminalQueryFilter::new();
|
||||||
|
// ESC P $ q m ESC \ - DECRQSS query for SGR
|
||||||
|
let input = b"before\x1bP$qm\x1b\\after";
|
||||||
|
let output = filter.filter(input);
|
||||||
|
assert_eq!(output, b"beforeafter".to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_filter_dcs_xtgettcap() {
|
||||||
|
let mut filter = TerminalQueryFilter::new();
|
||||||
|
// ESC P + q 544e ESC \ - XTGETTCAP query
|
||||||
|
let input = b"before\x1bP+q544e\x1b\\after";
|
||||||
|
let output = filter.filter(input);
|
||||||
|
assert_eq!(output, b"beforeafter".to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_split_kitty_keyboard_query() {
|
||||||
|
let mut filter = TerminalQueryFilter::new();
|
||||||
|
// ESC [ ? u split across reads
|
||||||
|
let output1 = filter.filter(b"before\x1b[");
|
||||||
|
let output2 = filter.filter(b"?uafter");
|
||||||
|
assert_eq!(output1, b"before".to_vec());
|
||||||
|
assert_eq!(output2, b"after".to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_split_kitty_keyboard_query_with_param() {
|
||||||
|
let mut filter = TerminalQueryFilter::new();
|
||||||
|
// ESC [ ? 1 u split after the digit
|
||||||
|
let output1 = filter.filter(b"before\x1b[?1");
|
||||||
|
let output2 = filter.filter(b"uafter");
|
||||||
|
assert_eq!(output1, b"before".to_vec());
|
||||||
|
assert_eq!(output2, b"after".to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_split_xtversion_query() {
|
||||||
|
let mut filter = TerminalQueryFilter::new();
|
||||||
|
// ESC [ > q split after >
|
||||||
|
let output1 = filter.filter(b"before\x1b[>");
|
||||||
|
let output2 = filter.filter(b"qafter");
|
||||||
|
assert_eq!(output1, b"before".to_vec());
|
||||||
|
assert_eq!(output2, b"after".to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_split_decrqm_query() {
|
||||||
|
let mut filter = TerminalQueryFilter::new();
|
||||||
|
// ESC [ 4 $ p split after $
|
||||||
|
let output1 = filter.filter(b"before\x1b[4$");
|
||||||
|
let output2 = filter.filter(b"pafter");
|
||||||
|
assert_eq!(output1, b"before".to_vec());
|
||||||
|
assert_eq!(output2, b"after".to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_split_decrqm_private_query() {
|
||||||
|
let mut filter = TerminalQueryFilter::new();
|
||||||
|
// ESC [ ? 1 $ p split after $
|
||||||
|
let output1 = filter.filter(b"before\x1b[?1$");
|
||||||
|
let output2 = filter.filter(b"pafter");
|
||||||
|
assert_eq!(output1, b"before".to_vec());
|
||||||
|
assert_eq!(output2, b"after".to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_split_xtwinops_query() {
|
||||||
|
let mut filter = TerminalQueryFilter::new();
|
||||||
|
// ESC [ 14 t split after 1
|
||||||
|
let output1 = filter.filter(b"before\x1b[1");
|
||||||
|
let output2 = filter.filter(b"4tafter");
|
||||||
|
assert_eq!(output1, b"before".to_vec());
|
||||||
|
assert_eq!(output2, b"after".to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_split_dcs_query() {
|
||||||
|
let mut filter = TerminalQueryFilter::new();
|
||||||
|
// ESC P $ q m ESC \ split after ESC P
|
||||||
|
let output1 = filter.filter(b"before\x1bP");
|
||||||
|
let output2 = filter.filter(b"$qm\x1b\\after");
|
||||||
|
assert_eq!(output1, b"before".to_vec());
|
||||||
|
assert_eq!(output2, b"after".to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_split_dcs_query_at_st() {
|
||||||
|
let mut filter = TerminalQueryFilter::new();
|
||||||
|
// ESC P $ q m ESC \ split before ST
|
||||||
|
let output1 = filter.filter(b"before\x1bP$qm\x1b");
|
||||||
|
let output2 = filter.filter(b"\\after");
|
||||||
|
assert_eq!(output1, b"before".to_vec());
|
||||||
|
assert_eq!(output2, b"after".to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_split_osc_query() {
|
||||||
|
let mut filter = TerminalQueryFilter::new();
|
||||||
|
// ESC ] 11 ; ? BEL split after ;
|
||||||
|
let output1 = filter.filter(b"before\x1b]11;");
|
||||||
|
let output2 = filter.filter(b"?\x07after");
|
||||||
|
assert_eq!(output1, b"before".to_vec());
|
||||||
|
assert_eq!(output2, b"after".to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_split_device_status_query() {
|
||||||
|
let mut filter = TerminalQueryFilter::new();
|
||||||
|
// ESC [ 6 n split after 6
|
||||||
|
let output1 = filter.filter(b"before\x1b[6");
|
||||||
|
let output2 = filter.filter(b"nafter");
|
||||||
|
assert_eq!(output1, b"before".to_vec());
|
||||||
|
assert_eq!(output2, b"after".to_vec());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user