Detailed changes
@@ -262,6 +262,77 @@ impl EditorLspTestContext {
Self::new(language, capabilities, cx).await
}
+ pub async fn new_tsx(
+ capabilities: lsp::ServerCapabilities,
+ cx: &mut gpui::TestAppContext,
+ ) -> EditorLspTestContext {
+ let mut word_characters: HashSet<char> = Default::default();
+ word_characters.insert('$');
+ word_characters.insert('#');
+ let language = Language::new(
+ LanguageConfig {
+ name: "TSX".into(),
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["tsx".to_string()],
+ ..Default::default()
+ },
+ brackets: language::BracketPairConfig {
+ pairs: vec![language::BracketPair {
+ start: "{".to_string(),
+ end: "}".to_string(),
+ close: true,
+ surround: true,
+ newline: true,
+ }],
+ disabled_scopes_by_bracket_ix: Default::default(),
+ },
+ word_characters,
+ ..Default::default()
+ },
+ Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
+ )
+ .with_queries(LanguageQueries {
+ brackets: Some(Cow::from(indoc! {r#"
+ ("(" @open ")" @close)
+ ("[" @open "]" @close)
+ ("{" @open "}" @close)
+ ("<" @open ">" @close)
+ ("<" @open "/>" @close)
+ ("</" @open ">" @close)
+ ("\"" @open "\"" @close)
+ ("'" @open "'" @close)
+ ("`" @open "`" @close)
+ ((jsx_element (jsx_opening_element) @open (jsx_closing_element) @close) (#set! newline.only))"#})),
+ indents: Some(Cow::from(indoc! {r#"
+ [
+ (call_expression)
+ (assignment_expression)
+ (member_expression)
+ (lexical_declaration)
+ (variable_declaration)
+ (assignment_expression)
+ (if_statement)
+ (for_statement)
+ ] @indent
+
+ (_ "[" "]" @end) @indent
+ (_ "<" ">" @end) @indent
+ (_ "{" "}" @end) @indent
+ (_ "(" ")" @end) @indent
+
+ (jsx_opening_element ">" @end) @indent
+
+ (jsx_element
+ (jsx_opening_element) @start
+ (jsx_closing_element)? @end) @indent
+ "#})),
+ ..Default::default()
+ })
+ .expect("Could not parse queries");
+
+ Self::new(language, capabilities, cx).await
+ }
+
pub async fn new_html(cx: &mut gpui::TestAppContext) -> Self {
let language = Language::new(
LanguageConfig {
@@ -2388,6 +2388,7 @@ fn matching(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint
let display_point = map.clip_at_line_end(display_point);
let point = display_point.to_point(map);
let offset = point.to_offset(&map.buffer_snapshot());
+ let snapshot = map.buffer_snapshot();
// Ensure the range is contained by the current line.
let mut line_end = map.next_line_boundary(point).0;
@@ -2395,10 +2396,19 @@ 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)
- {
+ // Attempt to find the smallest enclosing bracket range that also contains
+ // the offset, which only happens if the cursor is currently in a bracket.
+ let range_filter = |_buffer: &language::BufferSnapshot,
+ opening_range: Range<usize>,
+ closing_range: Range<usize>| {
+ opening_range.contains(&offset) || closing_range.contains(&offset)
+ };
+
+ let bracket_ranges = snapshot
+ .innermost_enclosing_bracket_ranges(offset..offset, Some(&range_filter))
+ .or_else(|| snapshot.innermost_enclosing_bracket_ranges(offset..offset, None));
+
+ if let Some((opening_range, closing_range)) = bracket_ranges {
if opening_range.contains(&offset) {
return closing_range.start.to_display_point(map);
} else if closing_range.contains(&offset) {
@@ -2440,7 +2450,6 @@ fn matching(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint
if distance < closest_distance {
closest_pair_destination = Some(close_range.start);
closest_distance = distance;
- continue;
}
}
@@ -2451,7 +2460,6 @@ fn matching(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint
if distance < closest_distance {
closest_pair_destination = Some(open_range.start);
closest_distance = distance;
- continue;
}
}
@@ -3391,6 +3399,22 @@ mod test {
}"});
}
+ #[gpui::test]
+ async fn test_matching_nested_brackets(cx: &mut gpui::TestAppContext) {
+ let mut cx = NeovimBackedTestContext::new_tsx(cx).await;
+
+ cx.set_shared_state(indoc! {r"<Button onClick=ห{() => {}}></Button>"})
+ .await;
+ cx.simulate_shared_keystrokes("%").await;
+ cx.shared_state()
+ .await
+ .assert_eq(indoc! {r"<Button onClick={() => {}ห}></Button>"});
+ cx.simulate_shared_keystrokes("%").await;
+ cx.shared_state()
+ .await
+ .assert_eq(indoc! {r"<Button onClick=ห{() => {}}></Button>"});
+ }
+
#[gpui::test]
async fn test_comma_semicolon(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
@@ -207,6 +207,26 @@ impl NeovimBackedTestContext {
}
}
+ pub async fn new_tsx(cx: &mut gpui::TestAppContext) -> NeovimBackedTestContext {
+ #[cfg(feature = "neovim")]
+ cx.executor().allow_parking();
+ let thread = thread::current();
+ let test_name = thread
+ .name()
+ .expect("thread is not named")
+ .split(':')
+ .next_back()
+ .unwrap()
+ .to_string();
+ Self {
+ cx: VimTestContext::new_tsx(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
@@ -66,6 +66,28 @@ impl VimTestContext {
)
}
+ pub async fn new_tsx(cx: &mut gpui::TestAppContext) -> VimTestContext {
+ Self::init(cx);
+ Self::new_with_lsp(
+ EditorLspTestContext::new_tsx(
+ lsp::ServerCapabilities {
+ completion_provider: Some(lsp::CompletionOptions {
+ trigger_characters: Some(vec![".".to_string()]),
+ ..Default::default()
+ }),
+ rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
+ prepare_provider: Some(true),
+ work_done_progress_options: Default::default(),
+ })),
+ ..Default::default()
+ },
+ cx,
+ )
+ .await,
+ true,
+ )
+ }
+
pub fn init_keybindings(enabled: bool, cx: &mut App) {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |s| s.vim_mode = Some(enabled));
@@ -0,0 +1,5 @@
+{"Put":{"state":"<Button onClick=ห{() => {}}></Button>"}}
+{"Key":"%"}
+{"Get":{"state":"<Button onClick={() => {}ห}></Button>","mode":"Normal"}}
+{"Key":"%"}
+{"Get":{"state":"<Button onClick=ห{() => {}}></Button>","mode":"Normal"}}