Detailed changes
@@ -101,10 +101,10 @@ use language::{
language_settings::{
self, all_language_settings, language_settings, InlayHintSettings, RewrapBehavior,
},
- point_from_lsp, text_diff_with_options, AutoindentMode, BracketPair, Buffer, Capability,
- CharKind, CodeLabel, CursorShape, Diagnostic, DiffOptions, DiskState, EditPredictionsMode,
- EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
- Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
+ point_from_lsp, text_diff_with_options, AutoindentMode, BracketMatch, BracketPair, Buffer,
+ Capability, CharKind, CodeLabel, CursorShape, Diagnostic, DiffOptions, DiskState,
+ EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize, Language,
+ OffsetRangeExt, Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
};
use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
use linked_editing_ranges::refresh_linked_ranges;
@@ -3153,35 +3153,9 @@ impl Editor {
let (comment_delimiter, insert_extra_newline) = if let Some(language) =
&language_scope
{
- let leading_whitespace_len = buffer
- .reversed_chars_at(start)
- .take_while(|c| c.is_whitespace() && *c != '\n')
- .map(|c| c.len_utf8())
- .sum::<usize>();
-
- let trailing_whitespace_len = buffer
- .chars_at(end)
- .take_while(|c| c.is_whitespace() && *c != '\n')
- .map(|c| c.len_utf8())
- .sum::<usize>();
-
let insert_extra_newline =
- language.brackets().any(|(pair, enabled)| {
- let pair_start = pair.start.trim_end();
- let pair_end = pair.end.trim_start();
-
- enabled
- && pair.newline
- && buffer.contains_str_at(
- end + trailing_whitespace_len,
- pair_end,
- )
- && buffer.contains_str_at(
- (start - leading_whitespace_len)
- .saturating_sub(pair_start.len()),
- pair_start,
- )
- });
+ insert_extra_newline_brackets(&buffer, start..end, language)
+ || insert_extra_newline_tree_sitter(&buffer, start..end);
// Comment extension on newline is allowed only for cursor selections
let comment_delimiter = maybe!({
@@ -15088,6 +15062,72 @@ impl Editor {
}
}
+fn insert_extra_newline_brackets(
+ buffer: &MultiBufferSnapshot,
+ range: Range<usize>,
+ language: &language::LanguageScope,
+) -> bool {
+ let leading_whitespace_len = buffer
+ .reversed_chars_at(range.start)
+ .take_while(|c| c.is_whitespace() && *c != '\n')
+ .map(|c| c.len_utf8())
+ .sum::<usize>();
+ let trailing_whitespace_len = buffer
+ .chars_at(range.end)
+ .take_while(|c| c.is_whitespace() && *c != '\n')
+ .map(|c| c.len_utf8())
+ .sum::<usize>();
+ let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
+
+ language.brackets().any(|(pair, enabled)| {
+ let pair_start = pair.start.trim_end();
+ let pair_end = pair.end.trim_start();
+
+ enabled
+ && pair.newline
+ && buffer.contains_str_at(range.end, pair_end)
+ && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
+ })
+}
+
+fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
+ let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
+ [(buffer, range, _)] => (*buffer, range.clone()),
+ _ => return false,
+ };
+ let pair = {
+ let mut result: Option<BracketMatch> = None;
+
+ for pair in buffer
+ .all_bracket_ranges(range.clone())
+ .filter(move |pair| {
+ pair.open_range.start <= range.start && pair.close_range.end >= range.end
+ })
+ {
+ let len = pair.close_range.end - pair.open_range.start;
+
+ if let Some(existing) = &result {
+ let existing_len = existing.close_range.end - existing.open_range.start;
+ if len > existing_len {
+ continue;
+ }
+ }
+
+ result = Some(pair);
+ }
+
+ result
+ };
+ let Some(pair) = pair else {
+ return false;
+ };
+ pair.newline_only
+ && buffer
+ .chars_for_range(pair.open_range.end..range.start)
+ .chain(buffer.chars_for_range(range.end..pair.close_range.start))
+ .all(|c| c.is_whitespace() && c != '\n')
+}
+
fn get_uncommitted_diff_for_buffer(
project: &Entity<Project>,
buffers: impl IntoIterator<Item = Entity<Buffer>>,
@@ -15934,6 +15934,60 @@ async fn test_rename_without_prepare(cx: &mut gpui::TestAppContext) {
"});
}
+#[gpui::test]
+async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+ let mut cx = EditorTestContext::new(cx).await;
+
+ let language = Arc::new(
+ Language::new(
+ LanguageConfig::default(),
+ Some(tree_sitter_html::LANGUAGE.into()),
+ )
+ .with_brackets_query(
+ r#"
+ ("<" @open "/>" @close)
+ ("</" @open ">" @close)
+ ("<" @open ">" @close)
+ ("\"" @open "\"" @close)
+ ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
+ "#,
+ )
+ .unwrap(),
+ );
+ cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
+
+ cx.set_state(indoc! {"
+ <span>ˇ</span>
+ "});
+ cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
+ cx.assert_editor_state(indoc! {"
+ <span>
+ ˇ
+ </span>
+ "});
+
+ cx.set_state(indoc! {"
+ <span><span></span>ˇ</span>
+ "});
+ cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
+ cx.assert_editor_state(indoc! {"
+ <span><span></span>
+ ˇ</span>
+ "});
+
+ cx.set_state(indoc! {"
+ <span>ˇ
+ </span>
+ "});
+ cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
+ cx.assert_editor_state(indoc! {"
+ <span>
+ ˇ
+ </span>
+ "});
+}
+
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
point..point
@@ -782,6 +782,13 @@ impl EditPreview {
}
}
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct BracketMatch {
+ pub open_range: Range<usize>,
+ pub close_range: Range<usize>,
+ pub newline_only: bool,
+}
+
impl Buffer {
/// Create a new buffer with the given base text.
pub fn local<T: Into<String>>(base_text: T, cx: &Context<Self>) -> Self {
@@ -3556,15 +3563,10 @@ impl BufferSnapshot {
self.syntax.matches(range, self, query)
}
- /// Returns bracket range pairs overlapping or adjacent to `range`
- pub fn bracket_ranges<T: ToOffset>(
+ pub fn all_bracket_ranges(
&self,
- range: Range<T>,
- ) -> impl Iterator<Item = (Range<usize>, Range<usize>)> + '_ {
- // Find bracket pairs that *inclusively* contain the given range.
- let range = range.start.to_offset(self).saturating_sub(1)
- ..self.len().min(range.end.to_offset(self) + 1);
-
+ range: Range<usize>,
+ ) -> impl Iterator<Item = BracketMatch> + '_ {
let mut matches = self.syntax.matches(range.clone(), &self.text, |grammar| {
grammar.brackets_config.as_ref().map(|c| &c.query)
});
@@ -3579,6 +3581,7 @@ impl BufferSnapshot {
let mut open = None;
let mut close = None;
let config = &configs[mat.grammar_index];
+ let pattern = &config.patterns[mat.pattern_index];
for capture in mat.captures {
if capture.index == config.open_capture_ix {
open = Some(capture.node.byte_range());
@@ -3589,21 +3592,37 @@ impl BufferSnapshot {
matches.advance();
- let Some((open, close)) = open.zip(close) else {
+ let Some((open_range, close_range)) = open.zip(close) else {
continue;
};
- let bracket_range = open.start..=close.end;
+ let bracket_range = open_range.start..=close_range.end;
if !bracket_range.overlaps(&range) {
continue;
}
- return Some((open, close));
+ return Some(BracketMatch {
+ open_range,
+ close_range,
+ newline_only: pattern.newline_only,
+ });
}
None
})
}
+ /// Returns bracket range pairs overlapping or adjacent to `range`
+ pub fn bracket_ranges<T: ToOffset>(
+ &self,
+ range: Range<T>,
+ ) -> impl Iterator<Item = BracketMatch> + '_ {
+ // Find bracket pairs that *inclusively* contain the given range.
+ let range = range.start.to_offset(self).saturating_sub(1)
+ ..self.len().min(range.end.to_offset(self) + 1);
+ self.all_bracket_ranges(range)
+ .filter(|pair| !pair.newline_only)
+ }
+
pub fn text_object_ranges<T: ToOffset>(
&self,
range: Range<T>,
@@ -3674,11 +3693,12 @@ impl BufferSnapshot {
pub fn enclosing_bracket_ranges<T: ToOffset>(
&self,
range: Range<T>,
- ) -> impl Iterator<Item = (Range<usize>, Range<usize>)> + '_ {
+ ) -> impl Iterator<Item = BracketMatch> + '_ {
let range = range.start.to_offset(self)..range.end.to_offset(self);
- self.bracket_ranges(range.clone())
- .filter(move |(open, close)| open.start <= range.start && close.end >= range.end)
+ self.bracket_ranges(range.clone()).filter(move |pair| {
+ pair.open_range.start <= range.start && pair.close_range.end >= range.end
+ })
}
/// Returns the smallest enclosing bracket ranges containing the given range or None if no brackets contain range
@@ -3694,14 +3714,14 @@ impl BufferSnapshot {
// Get the ranges of the innermost pair of brackets.
let mut result: Option<(Range<usize>, Range<usize>)> = None;
- for (open, close) in self.enclosing_bracket_ranges(range.clone()) {
+ for pair in self.enclosing_bracket_ranges(range.clone()) {
if let Some(range_filter) = range_filter {
- if !range_filter(open.clone(), close.clone()) {
+ if !range_filter(pair.open_range.clone(), pair.close_range.clone()) {
continue;
}
}
- let len = close.end - open.start;
+ let len = pair.close_range.end - pair.open_range.start;
if let Some((existing_open, existing_close)) = &result {
let existing_len = existing_close.end - existing_open.start;
@@ -3710,7 +3730,7 @@ impl BufferSnapshot {
}
}
- result = Some((open, close));
+ result = Some((pair.open_range, pair.close_range));
}
result
@@ -3401,7 +3401,10 @@ fn assert_bracket_pairs(
.collect::<Vec<_>>();
assert_set_eq!(
- buffer.bracket_ranges(selection_range).collect::<Vec<_>>(),
+ buffer
+ .bracket_ranges(selection_range)
+ .map(|pair| (pair.open_range, pair.close_range))
+ .collect::<Vec<_>>(),
bracket_pairs
);
}
@@ -918,7 +918,7 @@ pub struct Grammar {
pub ts_language: tree_sitter::Language,
pub(crate) error_query: Option<Query>,
pub(crate) highlights_query: Option<Query>,
- pub(crate) brackets_config: Option<BracketConfig>,
+ pub(crate) brackets_config: Option<BracketsConfig>,
pub(crate) redactions_config: Option<RedactionConfig>,
pub(crate) runnable_config: Option<RunnableConfig>,
pub(crate) indents_config: Option<IndentConfig>,
@@ -1039,10 +1039,16 @@ struct InjectionPatternConfig {
combined: bool,
}
-struct BracketConfig {
+struct BracketsConfig {
query: Query,
open_capture_ix: u32,
close_capture_ix: u32,
+ patterns: Vec<BracketsPatternConfig>,
+}
+
+#[derive(Clone, Debug, Default)]
+struct BracketsPatternConfig {
+ newline_only: bool,
}
impl Language {
@@ -1284,11 +1290,24 @@ impl Language {
("close", &mut close_capture_ix),
],
);
+ let patterns = (0..query.pattern_count())
+ .map(|ix| {
+ let mut config = BracketsPatternConfig::default();
+ for setting in query.property_settings(ix) {
+ match setting.key.as_ref() {
+ "newline.only" => config.newline_only = true,
+ _ => {}
+ }
+ }
+ config
+ })
+ .collect();
if let Some((open_capture_ix, close_capture_ix)) = open_capture_ix.zip(close_capture_ix) {
- grammar.brackets_config = Some(BracketConfig {
+ grammar.brackets_config = Some(BracketsConfig {
query,
open_capture_ix,
close_capture_ix,
+ patterns,
});
}
Ok(self)
@@ -5156,11 +5156,11 @@ impl MultiBufferSnapshot {
excerpt
.buffer()
.enclosing_bracket_ranges(excerpt.map_range_to_buffer(range))
- .filter_map(move |(open, close)| {
- if excerpt.contains_buffer_range(open.start..close.end) {
+ .filter_map(move |pair| {
+ if excerpt.contains_buffer_range(pair.open_range.start..pair.close_range.end) {
Some((
- excerpt.map_range_from_buffer(open),
- excerpt.map_range_from_buffer(close),
+ excerpt.map_range_from_buffer(pair.open_range),
+ excerpt.map_range_from_buffer(pair.close_range),
))
} else {
None
@@ -5207,12 +5207,12 @@ impl MultiBufferSnapshot {
excerpt
.buffer()
.bracket_ranges(excerpt.map_range_to_buffer(range))
- .filter_map(move |(start_bracket_range, close_bracket_range)| {
- let buffer_range = start_bracket_range.start..close_bracket_range.end;
+ .filter_map(move |pair| {
+ let buffer_range = pair.open_range.start..pair.close_range.end;
if excerpt.contains_buffer_range(buffer_range) {
Some((
- excerpt.map_range_from_buffer(start_bracket_range),
- excerpt.map_range_from_buffer(close_bracket_range),
+ excerpt.map_range_from_buffer(pair.open_range),
+ excerpt.map_range_from_buffer(pair.close_range),
))
} else {
None
@@ -2,3 +2,4 @@
("</" @open ">" @close)
("<" @open ">" @close)
("\"" @open "\"" @close)
+((element (start_tag) @open (end_tag) @close) (#set! newline.only))