@@ -23,6 +23,7 @@ collections.workspace = true
command_palette_hooks.workspace = true
editor.workspace = true
gpui.workspace = true
+itertools.workspace = true
language.workspace = true
log.workspace = true
nvim-rs = { git = "https://github.com/KillTheMule/nvim-rs", branch = "master", features = [
@@ -9,6 +9,9 @@ use editor::{
movement::{self, FindRange},
Bias, DisplayPoint,
};
+
+use itertools::Itertools;
+
use gpui::{actions, impl_actions, ViewContext, WindowContext};
use language::{char_kind, BufferSnapshot, CharKind, Point, Selection};
use serde::Deserialize;
@@ -801,15 +804,20 @@ fn surrounding_markers(
let mut matched_closes = 0;
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 ch == open_marker {
+ if ch == open_marker && before_ch != '\\' {
if open_marker == close_marker {
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' {
break;
}
- if ch == open_marker {
+ if ch == open_marker && before_ch != '\\' {
total += 1;
}
}
@@ -823,11 +831,15 @@ fn surrounding_markers(
}
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 {
break;
}
+ if before_ch == '\\' {
+ continue;
+ }
+
if ch == open_marker {
if matched_closes == 0 {
opening = Some(range);
@@ -839,15 +851,18 @@ fn surrounding_markers(
}
}
}
-
if opening.is_none() {
for (ch, range) in movement::chars_after(map, point) {
- if ch == open_marker {
- opening = Some(range);
- break;
- } else if ch == close_marker {
- break;
+ if before_ch != '\\' {
+ if ch == open_marker {
+ opening = Some(range);
+ break;
+ } else if ch == close_marker {
+ break;
+ }
}
+
+ before_ch = ch;
}
}
@@ -857,21 +872,28 @@ fn surrounding_markers(
let mut matched_opens = 0;
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) {
if ch == '\n' && !search_across_lines {
break;
}
- if ch == close_marker {
- if matched_opens == 0 {
- closing = Some(range);
- break;
+ if before_ch != '\\' {
+ if ch == close_marker {
+ if matched_opens == 0 {
+ 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 {
@@ -1467,6 +1489,32 @@ mod test {
.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]
async fn test_vertical_bars(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;