fix(tui): preserve drafts around mouse reports
This commit is contained in:
+126
-1
@@ -419,8 +419,17 @@ fn strip_raw_mouse_report_runs(input: &str, cursor: usize) -> Option<(String, us
|
||||
index += 1;
|
||||
}
|
||||
let run = &chars[start..index];
|
||||
if looks_like_raw_mouse_report_run(run) {
|
||||
if let Some(keep) = raw_mouse_report_keep_mask(run) {
|
||||
changed = true;
|
||||
for (offset, ch) in run.iter().copied().enumerate() {
|
||||
if !keep[offset] {
|
||||
continue;
|
||||
}
|
||||
if start + offset < cursor {
|
||||
new_cursor += 1;
|
||||
}
|
||||
output.push(ch);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
for (offset, ch) in run.iter().copied().enumerate() {
|
||||
@@ -465,6 +474,98 @@ fn has_sgr_mouse_marker(run: &[char]) -> bool {
|
||||
run.windows(2).any(|window| window == ['[', '<'])
|
||||
}
|
||||
|
||||
fn raw_mouse_report_keep_mask(run: &[char]) -> Option<Vec<bool>> {
|
||||
let mut ranges: Vec<(usize, usize)> = Vec::new();
|
||||
let mut index = 0usize;
|
||||
|
||||
while index < run.len() {
|
||||
let (start, body_start) = if run[index] == '\x1b'
|
||||
&& run.get(index + 1) == Some(&'[')
|
||||
&& run.get(index + 2) == Some(&'<')
|
||||
{
|
||||
(index, index + 3)
|
||||
} else if run[index] == '[' && run.get(index + 1) == Some(&'<') {
|
||||
(index, index + 2)
|
||||
} else {
|
||||
index += 1;
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut end = body_start;
|
||||
let mut has_digit = false;
|
||||
let mut has_separator = false;
|
||||
let mut matched = false;
|
||||
while end < run.len() {
|
||||
match run[end] {
|
||||
'0'..='9' => {
|
||||
has_digit = true;
|
||||
end += 1;
|
||||
}
|
||||
';' | ':' => {
|
||||
has_separator = true;
|
||||
end += 1;
|
||||
}
|
||||
'M' | 'm' if has_digit && has_separator => {
|
||||
ranges.push((start, end + 1));
|
||||
index = end + 1;
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
index = index.saturating_add(1);
|
||||
}
|
||||
}
|
||||
|
||||
if ranges.is_empty() {
|
||||
if looks_like_raw_mouse_report_run(run) {
|
||||
return Some(vec![false; run.len()]);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
ranges.sort_unstable_by_key(|(start, _)| *start);
|
||||
let first_start = ranges[0].0;
|
||||
let mut prefix_start = first_start;
|
||||
while prefix_start > 0 && is_raw_mouse_report_fragment_char(run[prefix_start - 1]) {
|
||||
prefix_start -= 1;
|
||||
}
|
||||
if prefix_start < first_start
|
||||
&& looks_like_raw_mouse_report_fragment(&run[prefix_start..first_start])
|
||||
{
|
||||
ranges.push((prefix_start, first_start));
|
||||
}
|
||||
|
||||
let last_end = ranges.iter().map(|(_, end)| *end).max().unwrap_or_default();
|
||||
if last_end < run.len() && looks_like_raw_mouse_report_fragment(&run[last_end..]) {
|
||||
ranges.push((last_end, run.len()));
|
||||
}
|
||||
|
||||
ranges.sort_unstable_by_key(|(start, _)| *start);
|
||||
let mut keep = vec![true; run.len()];
|
||||
for (start, end) in ranges {
|
||||
for slot in keep.iter_mut().take(end.min(run.len())).skip(start) {
|
||||
*slot = false;
|
||||
}
|
||||
}
|
||||
Some(keep)
|
||||
}
|
||||
|
||||
fn is_raw_mouse_report_fragment_char(ch: char) -> bool {
|
||||
matches!(ch, ';' | ':' | 'M' | 'm') || ch.is_ascii_digit()
|
||||
}
|
||||
|
||||
fn looks_like_raw_mouse_report_fragment(run: &[char]) -> bool {
|
||||
if run.len() < 4 {
|
||||
return false;
|
||||
}
|
||||
run.iter().any(|ch| ch.is_ascii_digit())
|
||||
&& run.iter().any(|ch| matches!(ch, ';' | ':'))
|
||||
&& run.iter().any(|ch| matches!(ch, 'M' | 'm'))
|
||||
}
|
||||
|
||||
const MAX_SUBMITTED_INPUT_CHARS: usize = 16_000;
|
||||
const MAX_DRAFT_HISTORY: usize = 50;
|
||||
|
||||
@@ -4504,6 +4605,30 @@ mod tests {
|
||||
assert_eq!(app.cursor_position, "draft ".chars().count());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composer_preserves_draft_suffix_when_stripping_mouse_report() {
|
||||
let mut app = App::new(test_options(false), &Config::default());
|
||||
app.use_mouse_capture = true;
|
||||
app.insert_str("commit -m");
|
||||
|
||||
app.insert_str("[<65;44;18M");
|
||||
|
||||
assert_eq!(app.input, "commit -m");
|
||||
assert_eq!(app.cursor_position, "commit -m".chars().count());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composer_preserves_numeric_draft_when_stripping_mouse_report() {
|
||||
let mut app = App::new(test_options(false), &Config::default());
|
||||
app.use_mouse_capture = true;
|
||||
app.insert_str("123");
|
||||
|
||||
app.insert_str("[<65;44;18M");
|
||||
|
||||
assert_eq!(app.input, "123");
|
||||
assert_eq!(app.cursor_position, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composer_keeps_mouse_like_text_when_mouse_capture_is_disabled() {
|
||||
let mut app = App::new(test_options(false), &Config::default());
|
||||
|
||||
Reference in New Issue
Block a user