diff --git a/crates/buffer/src/language.rs b/crates/buffer/src/language.rs index e7755070cf696b7b1a6f4dbfd276261b7f934cfe..22609905663428d0473372591113d982068fed57 100644 --- a/crates/buffer/src/language.rs +++ b/crates/buffer/src/language.rs @@ -11,13 +11,15 @@ pub use tree_sitter::{Parser, Tree}; pub struct LanguageConfig { pub name: String, pub path_suffixes: Vec, - pub autoclose_pairs: Vec, + pub brackets: Vec, } -#[derive(Clone, Deserialize)] -pub struct AutoclosePair { +#[derive(Clone, Debug, Deserialize)] +pub struct BracketPair { pub start: String, pub end: String, + pub close: bool, + pub newline: bool, } pub struct Language { @@ -95,8 +97,8 @@ impl Language { self.config.name.as_str() } - pub fn autoclose_pairs(&self) -> &[AutoclosePair] { - &self.config.autoclose_pairs + pub fn brackets(&self) -> &[BracketPair] { + &self.config.brackets } pub fn highlight_map(&self) -> HighlightMap { diff --git a/crates/buffer/src/lib.rs b/crates/buffer/src/lib.rs index 19c74da9f135d6e2757cda5c420790b98941c27c..3817b7131a09c58dec463088ecaafd5465393fa1 100644 --- a/crates/buffer/src/lib.rs +++ b/crates/buffer/src/lib.rs @@ -16,7 +16,7 @@ use clock::ReplicaId; use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task}; pub use highlight_map::{HighlightId, HighlightMap}; use language::Tree; -pub use language::{AutoclosePair, Language, LanguageConfig, LanguageRegistry}; +pub use language::{BracketPair, Language, LanguageConfig, LanguageRegistry}; use lazy_static::lazy_static; use operation_queue::OperationQueue; use parking_lot::Mutex; @@ -1337,6 +1337,13 @@ impl Buffer { self.content().chars_at(position) } + pub fn reversed_chars_at<'a, T: 'a + ToOffset>( + &'a self, + position: T, + ) -> impl Iterator + 'a { + self.content().reversed_chars_at(position) + } + pub fn chars_for_range(&self, range: Range) -> impl Iterator + '_ { self.text_for_range(range).flat_map(str::chars) } @@ -2794,6 +2801,11 @@ impl<'a> Content<'a> { self.visible_text.chars_at(offset) } + pub fn reversed_chars_at(&self, position: T) -> impl Iterator + 'a { + let offset = position.to_offset(self); + self.visible_text.reversed_chars_at(offset) + } + pub fn text_for_range(&self, range: Range) -> Chunks<'a> { let start = range.start.to_offset(self); let end = range.end.to_offset(self); diff --git a/crates/buffer/src/rope.rs b/crates/buffer/src/rope.rs index ebe01258dc7b6d5fb1d6d2083d6ff26e6bbbd9c3..a1c57140025c0d8465a908118f8be0c168d85100 100644 --- a/crates/buffer/src/rope.rs +++ b/crates/buffer/src/rope.rs @@ -115,6 +115,11 @@ impl Rope { self.chunks_in_range(start..self.len()).flat_map(str::chars) } + pub fn reversed_chars_at(&self, start: usize) -> impl Iterator + '_ { + self.reversed_chunks_in_range(0..start) + .flat_map(|chunk| chunk.chars().rev()) + } + pub fn bytes_at(&self, start: usize) -> impl Iterator + '_ { self.chunks_in_range(start..self.len()).flat_map(str::bytes) } @@ -123,8 +128,12 @@ impl Rope { self.chunks_in_range(0..self.len()) } - pub fn chunks_in_range<'a>(&'a self, range: Range) -> Chunks<'a> { - Chunks::new(self, range) + pub fn chunks_in_range(&self, range: Range) -> Chunks { + Chunks::new(self, range, false) + } + + pub fn reversed_chunks_in_range(&self, range: Range) -> Chunks { + Chunks::new(self, range, true) } pub fn to_point(&self, offset: usize) -> Point { @@ -284,38 +293,65 @@ impl<'a> Cursor<'a> { pub struct Chunks<'a> { chunks: sum_tree::Cursor<'a, Chunk, usize>, range: Range, + reversed: bool, } impl<'a> Chunks<'a> { - pub fn new(rope: &'a Rope, range: Range) -> Self { + pub fn new(rope: &'a Rope, range: Range, reversed: bool) -> Self { let mut chunks = rope.chunks.cursor(); - chunks.seek(&range.start, Bias::Right, &()); - Self { chunks, range } + if reversed { + chunks.seek(&range.end, Bias::Left, &()); + } else { + chunks.seek(&range.start, Bias::Right, &()); + } + Self { + chunks, + range, + reversed, + } } pub fn offset(&self) -> usize { - self.range.start.max(*self.chunks.start()) + if self.reversed { + self.range.end.min(self.chunks.end(&())) + } else { + self.range.start.max(*self.chunks.start()) + } } pub fn seek(&mut self, offset: usize) { + let bias = if self.reversed { + Bias::Left + } else { + Bias::Right + }; + if offset >= self.chunks.end(&()) { - self.chunks.seek_forward(&offset, Bias::Right, &()); + self.chunks.seek_forward(&offset, bias, &()); } else { - self.chunks.seek(&offset, Bias::Right, &()); + self.chunks.seek(&offset, bias, &()); + } + + if self.reversed { + self.range.end = offset; + } else { + self.range.start = offset; } - self.range.start = offset; } pub fn peek(&self) -> Option<&'a str> { - if let Some(chunk) = self.chunks.item() { - let offset = *self.chunks.start(); - if self.range.end > offset { - let start = self.range.start.saturating_sub(*self.chunks.start()); - let end = self.range.end - self.chunks.start(); - return Some(&chunk.0[start..chunk.0.len().min(end)]); - } + let chunk = self.chunks.item()?; + if self.reversed && self.range.start >= self.chunks.end(&()) { + return None; } - None + let chunk_start = *self.chunks.start(); + if self.range.end <= chunk_start { + return None; + } + + let start = self.range.start.saturating_sub(chunk_start); + let end = self.range.end - chunk_start; + Some(&chunk.0[start..chunk.0.len().min(end)]) } } @@ -325,7 +361,11 @@ impl<'a> Iterator for Chunks<'a> { fn next(&mut self) -> Option { let result = self.peek(); if result.is_some() { - self.chunks.next(&()); + if self.reversed { + self.chunks.prev(&()); + } else { + self.chunks.next(&()); + } } result } @@ -571,6 +611,16 @@ mod tests { actual.chunks_in_range(start_ix..end_ix).collect::(), &expected[start_ix..end_ix] ); + + assert_eq!( + actual + .reversed_chunks_in_range(start_ix..end_ix) + .collect::>() + .into_iter() + .rev() + .collect::(), + &expected[start_ix..end_ix] + ); } let mut point = Point::new(0, 0); diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 05a7569df7accddf2ec44fb29f31b61269b32c74..a835e5e50cb5c4114b6f556c840cd58be5fafc26 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -296,7 +296,7 @@ pub struct Editor { pending_selection: Option, next_selection_id: usize, add_selections_state: Option, - autoclose_stack: Vec, + autoclose_stack: Vec, select_larger_syntax_node_stack: Vec>, scroll_position: Vector2F, scroll_top_anchor: Anchor, @@ -324,9 +324,9 @@ struct AddSelectionsState { stack: Vec, } -struct AutoclosePairState { +struct BracketPairState { ranges: SmallVec<[Range; 32]>, - pair: AutoclosePair, + pair: BracketPair, } #[derive(Serialize, Deserialize)] @@ -767,7 +767,35 @@ impl Editor { .min(start_point.column); let start = selection.start.to_offset(buffer); let end = selection.end.to_offset(buffer); - old_selections.push((selection.id, start..end, indent)); + + let mut insert_extra_newline = false; + if let Some(language) = buffer.language() { + let leading_whitespace_len = buffer + .reversed_chars_at(start) + .take_while(|c| c.is_whitespace() && *c != '\n') + .map(|c| c.len_utf8()) + .sum::(); + + let trailing_whitespace_len = buffer + .chars_at(end) + .take_while(|c| c.is_whitespace() && *c != '\n') + .map(|c| c.len_utf8()) + .sum::(); + + insert_extra_newline = language.brackets().iter().any(|pair| { + let pair_start = pair.start.trim_end(); + let pair_end = pair.end.trim_start(); + + 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, + ) + }); + } + + old_selections.push((selection.id, start..end, indent, insert_extra_newline)); } } @@ -775,26 +803,33 @@ impl Editor { self.buffer.update(cx, |buffer, cx| { let mut delta = 0_isize; let mut pending_edit: Option = None; - for (_, range, indent) in &old_selections { - if pending_edit - .as_ref() - .map_or(false, |pending| pending.indent != *indent) - { + for (_, range, indent, insert_extra_newline) in &old_selections { + if pending_edit.as_ref().map_or(false, |pending| { + pending.indent != *indent + || pending.insert_extra_newline != *insert_extra_newline + }) { let pending = pending_edit.take().unwrap(); let mut new_text = String::with_capacity(1 + pending.indent as usize); new_text.push('\n'); new_text.extend(iter::repeat(' ').take(pending.indent as usize)); + if pending.insert_extra_newline { + new_text = new_text.repeat(2); + } buffer.edit_with_autoindent(pending.ranges, new_text, cx); delta += pending.delta; } let start = (range.start as isize + delta) as usize; let end = (range.end as isize + delta) as usize; - let text_len = *indent as usize + 1; + let mut text_len = *indent as usize + 1; + if *insert_extra_newline { + text_len *= 2; + } let pending = pending_edit.get_or_insert_with(Default::default); pending.delta += text_len as isize - (end - start) as isize; pending.indent = *indent; + pending.insert_extra_newline = *insert_extra_newline; pending.ranges.push(start..end); } @@ -802,23 +837,33 @@ impl Editor { let mut new_text = String::with_capacity(1 + pending.indent as usize); new_text.push('\n'); new_text.extend(iter::repeat(' ').take(pending.indent as usize)); + if pending.insert_extra_newline { + new_text = new_text.repeat(2); + } buffer.edit_with_autoindent(pending.ranges, new_text, cx); let mut delta = 0_isize; - new_selections.extend(old_selections.into_iter().map(|(id, range, indent)| { - let start = (range.start as isize + delta) as usize; - let end = (range.end as isize + delta) as usize; - let text_len = indent as usize + 1; - let anchor = buffer.anchor_before(start + text_len); - delta += text_len as isize - (end - start) as isize; - Selection { - id, - start: anchor.clone(), - end: anchor, - reversed: false, - goal: SelectionGoal::None, - } - })) + new_selections.extend(old_selections.into_iter().map( + |(id, range, indent, insert_extra_newline)| { + let start = (range.start as isize + delta) as usize; + let end = (range.end as isize + delta) as usize; + let text_before_cursor_len = indent as usize + 1; + let anchor = buffer.anchor_before(start + text_before_cursor_len); + let text_len = if insert_extra_newline { + text_before_cursor_len * 2 + } else { + text_before_cursor_len + }; + delta += text_len as isize - (end - start) as isize; + Selection { + id, + start: anchor.clone(), + end: anchor, + reversed: false, + goal: SelectionGoal::None, + } + }, + )) }); self.update_selections(new_selections, true, cx); @@ -827,6 +872,7 @@ impl Editor { #[derive(Default)] struct PendingEdit { indent: u32, + insert_extra_newline: bool, delta: isize, ranges: SmallVec<[Range; 32]>, } @@ -879,7 +925,7 @@ impl Editor { let new_autoclose_pair_state = self.buffer.update(cx, |buffer, cx| { let autoclose_pair = buffer.language().and_then(|language| { let first_selection_start = selections.first().unwrap().start.to_offset(&*buffer); - let pair = language.autoclose_pairs().iter().find(|pair| { + let pair = language.brackets().iter().find(|pair| { buffer.contains_str_at( first_selection_start.saturating_sub(pair.start.len()), &pair.start, @@ -914,7 +960,7 @@ impl Editor { buffer.edit(selection_ranges, &pair.end, cx); if pair.end.len() == 1 { - Some(AutoclosePairState { + Some(BracketPairState { ranges: selections .iter() .map(|selection| { @@ -4506,14 +4552,18 @@ mod tests { let settings = cx.read(EditorSettings::test); let language = Arc::new(Language::new( LanguageConfig { - autoclose_pairs: vec![ - AutoclosePair { + brackets: vec![ + BracketPair { start: "{".to_string(), end: "}".to_string(), + close: true, + newline: true, }, - AutoclosePair { + BracketPair { start: "/*".to_string(), end: " */".to_string(), + close: true, + newline: true, }, ], ..Default::default() @@ -4612,6 +4662,76 @@ mod tests { }); } + #[gpui::test] + async fn test_extra_newline_insertion(mut cx: gpui::TestAppContext) { + let settings = cx.read(EditorSettings::test); + let language = Arc::new(Language::new( + LanguageConfig { + brackets: vec![ + BracketPair { + start: "{".to_string(), + end: "}".to_string(), + close: true, + newline: true, + }, + BracketPair { + start: "/* ".to_string(), + end: " */".to_string(), + close: true, + newline: true, + }, + ], + ..Default::default() + }, + tree_sitter_rust::language(), + )); + + let text = concat!( + "{ }\n", // Suppress rustfmt + " x\n", // + " /* */\n", // + "x\n", // + "{{} }\n", // + ); + + let buffer = cx.add_model(|cx| { + let history = History::new(text.into()); + Buffer::from_history(0, history, None, Some(language), cx) + }); + let (_, view) = cx.add_window(|cx| build_editor(buffer, settings, cx)); + view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing()) + .await; + + view.update(&mut cx, |view, cx| { + view.select_display_ranges( + &[ + DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3), + DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5), + DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4), + ], + cx, + ) + .unwrap(); + view.newline(&Newline, cx); + + assert_eq!( + view.buffer().read(cx).text(), + concat!( + "{ \n", // Suppress rustfmt + "\n", // + "}\n", // + " x\n", // + " /* \n", // + " \n", // + " */\n", // + "x\n", // + "{{} \n", // + "}\n", // + ) + ); + }); + } + impl Editor { fn selection_ranges(&self, cx: &mut MutableAppContext) -> Vec> { self.selections_in_range( diff --git a/crates/zed/languages/rust/config.toml b/crates/zed/languages/rust/config.toml index ece9b57ca25767d47995a80990db6152c6b5c228..11b273d137df9bbd8134c3a55e49d02459c76537 100644 --- a/crates/zed/languages/rust/config.toml +++ b/crates/zed/languages/rust/config.toml @@ -1,9 +1,10 @@ name = "Rust" path_suffixes = ["rs"] -autoclose_pairs = [ - { start = "{", end = "}" }, - { start = "[", end = "]" }, - { start = "(", end = ")" }, - { start = "\"", end = "\"" }, - { start = "/*", end = " */" }, +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, + { start = "<", end = ">", close = false, newline = true }, + { start = "\"", end = "\"", close = true, newline = false }, + { start = "/*", end = " */", close = true, newline = false }, ]