Improve logic for obtaining surrounds range in Vim mode (#10938)

now correctly retrieves range in cases where escape characters are
present. Fixed #10827


Release Notes:

- vim: Fix logic for finding surrounding quotes to ignore escaped
characters (#10827)
This commit is contained in:
Hans 2024-04-25 11:19:15 +08:00 committed by GitHub
parent d1425603f6
commit 1a27016123
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 77 additions and 18 deletions

View File

@ -23,6 +23,7 @@ collections.workspace = true
command_palette_hooks.workspace = true command_palette_hooks.workspace = true
editor.workspace = true editor.workspace = true
gpui.workspace = true gpui.workspace = true
itertools.workspace = true
language.workspace = true language.workspace = true
log.workspace = true log.workspace = true
nvim-rs = { git = "https://github.com/KillTheMule/nvim-rs", branch = "master", features = [ nvim-rs = { git = "https://github.com/KillTheMule/nvim-rs", branch = "master", features = [

View File

@ -9,6 +9,9 @@ use editor::{
movement::{self, FindRange}, movement::{self, FindRange},
Bias, DisplayPoint, Bias, DisplayPoint,
}; };
use itertools::Itertools;
use gpui::{actions, impl_actions, ViewContext, WindowContext}; use gpui::{actions, impl_actions, ViewContext, WindowContext};
use language::{char_kind, BufferSnapshot, CharKind, Point, Selection}; use language::{char_kind, BufferSnapshot, CharKind, Point, Selection};
use serde::Deserialize; use serde::Deserialize;
@ -801,15 +804,20 @@ fn surrounding_markers(
let mut matched_closes = 0; let mut matched_closes = 0;
let mut opening = None; let mut opening = None;
let mut before_ch = match movement::chars_before(map, point).next() {
Some((ch, _)) => ch,
_ => '\0',
};
if let Some((ch, range)) = movement::chars_after(map, point).next() { if let Some((ch, range)) = movement::chars_after(map, point).next() {
if ch == open_marker { if ch == open_marker && before_ch != '\\' {
if open_marker == close_marker { if open_marker == close_marker {
let mut total = 0; let mut total = 0;
for (ch, _) in movement::chars_before(map, point) { for ((ch, _), (before_ch, _)) in movement::chars_before(map, point).tuple_windows()
{
if ch == '\n' { if ch == '\n' {
break; break;
} }
if ch == open_marker { if ch == open_marker && before_ch != '\\' {
total += 1; total += 1;
} }
} }
@ -823,11 +831,15 @@ fn surrounding_markers(
} }
if opening.is_none() { if opening.is_none() {
for (ch, range) in movement::chars_before(map, point) { for ((ch, range), (before_ch, _)) in movement::chars_before(map, point).tuple_windows() {
if ch == '\n' && !search_across_lines { if ch == '\n' && !search_across_lines {
break; break;
} }
if before_ch == '\\' {
continue;
}
if ch == open_marker { if ch == open_marker {
if matched_closes == 0 { if matched_closes == 0 {
opening = Some(range); opening = Some(range);
@ -839,15 +851,18 @@ fn surrounding_markers(
} }
} }
} }
if opening.is_none() { if opening.is_none() {
for (ch, range) in movement::chars_after(map, point) { for (ch, range) in movement::chars_after(map, point) {
if ch == open_marker { if before_ch != '\\' {
opening = Some(range); if ch == open_marker {
break; opening = Some(range);
} else if ch == close_marker { break;
break; } else if ch == close_marker {
break;
}
} }
before_ch = ch;
} }
} }
@ -857,21 +872,28 @@ fn surrounding_markers(
let mut matched_opens = 0; let mut matched_opens = 0;
let mut closing = None; let mut closing = None;
before_ch = match movement::chars_before(map, opening.end).next() {
Some((ch, _)) => ch,
_ => '\0',
};
for (ch, range) in movement::chars_after(map, opening.end) { for (ch, range) in movement::chars_after(map, opening.end) {
if ch == '\n' && !search_across_lines { if ch == '\n' && !search_across_lines {
break; break;
} }
if ch == close_marker { if before_ch != '\\' {
if matched_opens == 0 { if ch == close_marker {
closing = Some(range); if matched_opens == 0 {
break; closing = Some(range);
break;
}
matched_opens -= 1;
} else if ch == open_marker {
matched_opens += 1;
} }
matched_opens -= 1;
} else if ch == open_marker {
matched_opens += 1;
} }
before_ch = ch;
} }
let Some(mut closing) = closing else { let Some(mut closing) = closing else {
@ -1467,6 +1489,32 @@ mod test {
.await; .await;
} }
#[gpui::test]
async fn test_singleline_surrounding_character_objects_with_escape(
cx: &mut gpui::TestAppContext,
) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc! {
"h\"e\\\"lˇlo \\\"world\"!"
})
.await;
cx.simulate_shared_keystrokes(["v", "i", "\""]).await;
cx.assert_shared_state(indoc! {
"h\"«e\\\"llo \\\"worldˇ»\"!"
})
.await;
cx.set_shared_state(indoc! {
"hello \"teˇst \\\"inside\\\" world\""
})
.await;
cx.simulate_shared_keystrokes(["v", "i", "\""]).await;
cx.assert_shared_state(indoc! {
"hello \"«test \\\"inside\\\" worldˇ»\""
})
.await;
}
#[gpui::test] #[gpui::test]
async fn test_vertical_bars(cx: &mut gpui::TestAppContext) { async fn test_vertical_bars(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await; let mut cx = VimTestContext::new(cx, true).await;

View File

@ -0,0 +1,10 @@
{"Put":{"state":"h\"e\\\"lˇlo \\\"world\"!"}}
{"Key":"v"}
{"Key":"i"}
{"Key":"\""}
{"Get":{"state":"h\"«e\\\"llo \\\"worldˇ»\"!","mode":"Visual"}}
{"Put":{"state":"hello \"teˇst \\\"inside\\\" world\""}}
{"Key":"v"}
{"Key":"i"}
{"Key":"\""}
{"Get":{"state":"hello \"«test \\\"inside\\\" worldˇ»\"","mode":"Visual"}}