diff --git a/crates/languages/src/javascript/brackets.scm b/crates/languages/src/javascript/brackets.scm
index 48afefeef07e9950cf6c8eba40b79def50c09c71..66bf14f137794b8a620b203c102ca3e3390fea20 100644
--- a/crates/languages/src/javascript/brackets.scm
+++ b/crates/languages/src/javascript/brackets.scm
@@ -2,6 +2,8 @@
("[" @open "]" @close)
("{" @open "}" @close)
("<" @open ">" @close)
+("<" @open "/>" @close)
+("" @open ">" @close)
("\"" @open "\"" @close)
("'" @open "'" @close)
("`" @open "`" @close)
diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs
index 080f051db5e66c48e5335bb9753d4d319ba840b4..29ed528a5e4d0c468c7e892d44b52d0cfc5ba500 100644
--- a/crates/vim/src/motion.rs
+++ b/crates/vim/src/motion.rs
@@ -2279,6 +2279,17 @@ fn matching(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint
line_end = map.max_point().to_point(map);
}
+ if let Some((opening_range, closing_range)) = map
+ .buffer_snapshot
+ .innermost_enclosing_bracket_ranges(offset..offset, None)
+ {
+ if opening_range.contains(&offset) {
+ return closing_range.start.to_display_point(map);
+ } else if closing_range.contains(&offset) {
+ return opening_range.start.to_display_point(map);
+ }
+ }
+
let line_range = map.prev_line_boundary(point).0..line_end;
let visible_line_range =
line_range.start..Point::new(line_range.end.row, line_range.end.column.saturating_sub(1));
@@ -3242,6 +3253,29 @@ mod test {
"#});
}
+ #[gpui::test]
+ async fn test_matching_braces_in_tag(cx: &mut gpui::TestAppContext) {
+ let mut cx = NeovimBackedTestContext::new_typescript(cx).await;
+
+ // test brackets within tags
+ cx.set_shared_state(indoc! {r"function f() {
+ return (
+
+
test
+
+ );
+ }"})
+ .await;
+ cx.simulate_shared_keystrokes("%").await;
+ cx.shared_state().await.assert_eq(indoc! {r"function f() {
+ return (
+
+
test
+
+ );
+ }"});
+ }
+
#[gpui::test]
async fn test_comma_semicolon(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
diff --git a/crates/vim/src/test/neovim_backed_test_context.rs b/crates/vim/src/test/neovim_backed_test_context.rs
index 053e1e587e1b71e8caa7104f14071592c4027a2f..505cdaa9100fcfad0d96bc6a55afe6ff4dc945ea 100644
--- a/crates/vim/src/test/neovim_backed_test_context.rs
+++ b/crates/vim/src/test/neovim_backed_test_context.rs
@@ -183,6 +183,30 @@ impl NeovimBackedTestContext {
}
}
+ pub async fn new_typescript(cx: &mut gpui::TestAppContext) -> NeovimBackedTestContext {
+ #[cfg(feature = "neovim")]
+ cx.executor().allow_parking();
+ // rust stores the name of the test on the current thread.
+ // We use this to automatically name a file that will store
+ // the neovim connection's requests/responses so that we can
+ // run without neovim on CI.
+ let thread = thread::current();
+ let test_name = thread
+ .name()
+ .expect("thread is not named")
+ .split(':')
+ .next_back()
+ .unwrap()
+ .to_string();
+ Self {
+ cx: VimTestContext::new_typescript(cx).await,
+ neovim: NeovimConnection::new(test_name).await,
+
+ last_set_state: None,
+ recent_keystrokes: Default::default(),
+ }
+ }
+
pub async fn set_shared_state(&mut self, marked_text: &str) {
let mode = if marked_text.contains('»') {
Mode::Visual
diff --git a/crates/vim/test_data/test_matching_braces_in_tag.json b/crates/vim/test_data/test_matching_braces_in_tag.json
new file mode 100644
index 0000000000000000000000000000000000000000..44201548a706789bd1a2bde15da8542945ab2e62
--- /dev/null
+++ b/crates/vim/test_data/test_matching_braces_in_tag.json
@@ -0,0 +1,3 @@
+{"Put":{"state":"function f() {\n return (\n \n
test
\n \n );\n}"}}
+{"Key":"%"}
+{"Get":{"state":"function f() {\n return (\n \n
test
\n \n );\n}","mode":"Normal"}}