diff --git a/crates/buffer/src/language.rs b/crates/buffer/src/language.rs index 1a9a29aac5..e7755070cf 100644 --- a/crates/buffer/src/language.rs +++ b/crates/buffer/src/language.rs @@ -23,8 +23,9 @@ pub struct AutoclosePair { pub struct Language { pub(crate) config: LanguageConfig, pub(crate) grammar: Grammar, - pub(crate) highlight_query: Query, + pub(crate) highlights_query: Query, pub(crate) brackets_query: Query, + pub(crate) indents_query: Query, pub(crate) highlight_map: Mutex, } @@ -68,19 +69,25 @@ impl Language { Self { config, brackets_query: Query::new(grammar, "").unwrap(), - highlight_query: Query::new(grammar, "").unwrap(), + highlights_query: Query::new(grammar, "").unwrap(), + indents_query: Query::new(grammar, "").unwrap(), grammar, highlight_map: Default::default(), } } - pub fn with_highlights_query(mut self, highlights_query_source: &str) -> Result { - self.highlight_query = Query::new(self.grammar, highlights_query_source)?; + pub fn with_highlights_query(mut self, source: &str) -> Result { + self.highlights_query = Query::new(self.grammar, source)?; Ok(self) } - pub fn with_brackets_query(mut self, brackets_query_source: &str) -> Result { - self.brackets_query = Query::new(self.grammar, brackets_query_source)?; + pub fn with_brackets_query(mut self, source: &str) -> Result { + self.brackets_query = Query::new(self.grammar, source)?; + Ok(self) + } + + pub fn with_indents_query(mut self, source: &str) -> Result { + self.indents_query = Query::new(self.grammar, source)?; Ok(self) } @@ -97,7 +104,8 @@ impl Language { } pub fn set_theme(&self, theme: &SyntaxTheme) { - *self.highlight_map.lock() = HighlightMap::new(self.highlight_query.capture_names(), theme); + *self.highlight_map.lock() = + HighlightMap::new(self.highlights_query.capture_names(), theme); } } @@ -110,28 +118,22 @@ mod tests { let grammar = tree_sitter_rust::language(); let registry = LanguageRegistry { languages: vec![ - Arc::new(Language { - config: LanguageConfig { + Arc::new(Language::new( + LanguageConfig { name: "Rust".to_string(), path_suffixes: vec!["rs".to_string()], ..Default::default() }, grammar, - highlight_query: Query::new(grammar, "").unwrap(), - brackets_query: Query::new(grammar, "").unwrap(), - highlight_map: Default::default(), - }), - Arc::new(Language { - config: LanguageConfig { + )), + Arc::new(Language::new( + LanguageConfig { name: "Make".to_string(), path_suffixes: vec!["Makefile".to_string(), "mk".to_string()], ..Default::default() }, grammar, - highlight_query: Query::new(grammar, "").unwrap(), - brackets_query: Query::new(grammar, "").unwrap(), - highlight_map: Default::default(), - }), + )), ], }; diff --git a/crates/buffer/src/lib.rs b/crates/buffer/src/lib.rs index a2f34a5d78..edeac8cec6 100644 --- a/crates/buffer/src/lib.rs +++ b/crates/buffer/src/lib.rs @@ -30,10 +30,12 @@ use std::{ any::Any, cell::RefCell, cmp, + collections::BTreeMap, convert::{TryFrom, TryInto}, ffi::OsString, hash::BuildHasher, iter::Iterator, + mem, ops::{Deref, DerefMut, Range}, path::{Path, PathBuf}, str, @@ -149,6 +151,7 @@ impl Drop for QueryCursorHandle { QUERY_CURSORS.lock().push(cursor) } } +const SPACES: &'static str = " "; pub struct Buffer { fragments: SumTree, @@ -162,6 +165,7 @@ pub struct Buffer { history: History, file: Option>, language: Option>, + autoindent_requests: Vec, sync_parse_timeout: Duration, syntax_tree: Mutex>, parsing_in_background: bool, @@ -190,6 +194,13 @@ struct SyntaxTree { version: clock::Global, } +#[derive(Clone, Debug)] +struct AutoindentRequest { + position: Anchor, + indent_size: u8, + force: bool, +} + #[derive(Clone, Debug)] struct Transaction { start: clock::Global, @@ -628,6 +639,7 @@ impl Buffer { parsing_in_background: false, parse_count: 0, sync_parse_timeout: Duration::from_millis(1), + autoindent_requests: Default::default(), language, saved_mtime, selections: HashMap::default(), @@ -878,14 +890,13 @@ impl Buffer { } if let Some(language) = self.language.clone() { - // The parse tree is out of date, so grab the syntax tree to synchronously - // splice all the edits that have happened since the last parse. - let old_tree = self.syntax_tree(); - let parsed_text = self.visible_text.clone(); + let old_snapshot = self.snapshot(); let parsed_version = self.version(); let parse_task = cx.background().spawn({ let language = language.clone(); - async move { Self::parse_text(&parsed_text, old_tree, &language) } + let text = old_snapshot.visible_text.clone(); + let tree = old_snapshot.tree.clone(); + async move { Self::parse_text(&text, tree, &language) } }); match cx @@ -894,13 +905,12 @@ impl Buffer { { Ok(new_tree) => { *self.syntax_tree.lock() = Some(SyntaxTree { - tree: new_tree, + tree: new_tree.clone(), dirty: false, version: parsed_version, }); self.parse_count += 1; - cx.emit(Event::Reparsed); - cx.notify(); + self.did_finish_parsing(new_tree, old_snapshot, language, cx); return true; } Err(parse_task) => { @@ -914,7 +924,7 @@ impl Buffer { }); let parse_again = this.version > parsed_version || language_changed; *this.syntax_tree.lock() = Some(SyntaxTree { - tree: new_tree, + tree: new_tree.clone(), dirty: false, version: parsed_version, }); @@ -925,8 +935,7 @@ impl Buffer { return; } - cx.emit(Event::Reparsed); - cx.notify(); + this.did_finish_parsing(new_tree, old_snapshot, language, cx); }); }) .detach(); @@ -956,6 +965,243 @@ impl Buffer { }) } + fn did_finish_parsing( + &mut self, + new_tree: Tree, + old_snapshot: Snapshot, + language: Arc, + cx: &mut ModelContext, + ) { + let mut autoindent_requests_by_row = BTreeMap::::default(); + let mut autoindent_requests = mem::take(&mut self.autoindent_requests); + for request in autoindent_requests.drain(..) { + let row = request.position.to_point(&*self).row; + autoindent_requests_by_row + .entry(row) + .and_modify(|req| { + req.indent_size = req.indent_size.max(request.indent_size); + req.force |= request.force; + }) + .or_insert(request); + } + self.autoindent_requests = autoindent_requests; + + let mut cursor = QueryCursorHandle::new(); + + self.start_transaction(None).unwrap(); + let mut row_range = None; + for row in autoindent_requests_by_row.keys().copied() { + match &mut row_range { + None => row_range = Some(row..(row + 1)), + Some(range) => { + if range.end == row { + range.end += 1; + } else { + self.perform_autoindent( + range.clone(), + &new_tree, + &old_snapshot, + &autoindent_requests_by_row, + language.as_ref(), + &mut cursor, + cx, + ); + row_range.take(); + } + } + } + } + if let Some(range) = row_range { + self.perform_autoindent( + range, + &new_tree, + &old_snapshot, + &autoindent_requests_by_row, + language.as_ref(), + &mut cursor, + cx, + ); + } + self.end_transaction(None, cx).unwrap(); + + cx.emit(Event::Reparsed); + cx.notify(); + } + + fn perform_autoindent( + &mut self, + row_range: Range, + new_tree: &Tree, + old_snapshot: &Snapshot, + autoindent_requests: &BTreeMap, + language: &Language, + cursor: &mut QueryCursor, + cx: &mut ModelContext, + ) { + let max_row = self.row_count() - 1; + let prev_non_blank_row = self.prev_non_blank_row(row_range.start); + + // Get the "indentation ranges" that intersect this row range. + let indent_capture_ix = language.indents_query.capture_index_for_name("indent"); + let end_capture_ix = language.indents_query.capture_index_for_name("end"); + cursor.set_point_range( + Point::new(prev_non_blank_row.unwrap_or(row_range.start), 0).into() + ..Point::new(row_range.end, 0).into(), + ); + let mut indentation_ranges = Vec::>::new(); + for mat in cursor.matches( + &language.indents_query, + new_tree.root_node(), + TextProvider(&self.visible_text), + ) { + let mut start_row = None; + let mut end_row = None; + for capture in mat.captures { + if Some(capture.index) == indent_capture_ix { + start_row.get_or_insert(capture.node.start_position().row as u32); + end_row.get_or_insert(max_row.min(capture.node.end_position().row as u32 + 1)); + } else if Some(capture.index) == end_capture_ix { + end_row = Some(capture.node.start_position().row as u32); + } + } + if let Some((start_row, end_row)) = start_row.zip(end_row) { + let range = start_row..end_row; + match indentation_ranges.binary_search_by_key(&range.start, |r| r.start) { + Err(ix) => indentation_ranges.insert(ix, range), + Ok(ix) => { + let prev_range = &mut indentation_ranges[ix]; + prev_range.end = prev_range.end.max(range.end); + } + } + } + } + + #[derive(Debug)] + struct Adjustment { + row: u32, + indent: bool, + outdent: bool, + } + + let mut adjustments = Vec::::new(); + 'outer: for range in &indentation_ranges { + let mut indent_row = range.start; + loop { + indent_row += 1; + if indent_row > max_row { + continue 'outer; + } + if row_range.contains(&indent_row) || !self.is_line_blank(indent_row) { + break; + } + } + + let mut outdent_row = range.end; + loop { + outdent_row += 1; + if outdent_row > max_row { + break; + } + if row_range.contains(&outdent_row) || !self.is_line_blank(outdent_row) { + break; + } + } + + match adjustments.binary_search_by_key(&indent_row, |a| a.row) { + Ok(ix) => adjustments[ix].indent = true, + Err(ix) => adjustments.insert( + ix, + Adjustment { + row: indent_row, + indent: true, + outdent: false, + }, + ), + } + match adjustments.binary_search_by_key(&outdent_row, |a| a.row) { + Ok(ix) => adjustments[ix].outdent = true, + Err(ix) => adjustments.insert( + ix, + Adjustment { + row: outdent_row, + indent: false, + outdent: true, + }, + ), + } + } + + let mut adjustments = adjustments.iter().peekable(); + for row in row_range { + if let Some(request) = autoindent_requests.get(&row) { + while let Some(adjustment) = adjustments.peek() { + if adjustment.row < row { + adjustments.next(); + } else { + if adjustment.row == row { + match (adjustment.indent, adjustment.outdent) { + (true, false) => self.indent_line(row, request.indent_size, cx), + (false, true) => self.outdent_line(row, request.indent_size, cx), + _ => {} + } + } + break; + } + } + } + } + } + + fn prev_non_blank_row(&self, mut row: u32) -> Option { + while row > 0 { + row -= 1; + if !self.is_line_blank(row) { + return Some(row); + } + } + None + } + + fn next_non_blank_row(&self, mut row: u32) -> Option { + let row_count = self.row_count(); + row += 1; + while row < row_count { + if !self.is_line_blank(row) { + return Some(row); + } + row += 1; + } + None + } + + fn indent_line(&mut self, row: u32, size: u8, cx: &mut ModelContext) { + self.edit( + [Point::new(row, 0)..Point::new(row, 0)], + &SPACES[..(size as usize)], + cx, + ) + } + + fn outdent_line(&mut self, row: u32, size: u8, cx: &mut ModelContext) { + let mut end_column = 0; + for c in self.chars_at(Point::new(row, 0)) { + if c == ' ' { + end_column += 1; + if end_column == size as u32 { + break; + } + } else { + break; + } + } + self.edit([Point::new(row, 0)..Point::new(row, end_column)], "", cx); + } + + fn is_line_blank(&self, row: u32) -> bool { + self.text_for_range(Point::new(row, 0)..Point::new(row, self.line_len(row))) + .all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none()) + } + pub fn range_for_syntax_ancestor(&self, range: Range) -> Option> { if let Some(tree) = self.syntax_tree() { let root = tree.root_node(); @@ -997,6 +1243,18 @@ impl Buffer { .min_by_key(|(open_range, close_range)| close_range.end - open_range.start) } + pub fn request_autoindent_for_line(&mut self, row: u32, indent_size: u8, force: bool) { + assert!( + self.history.transaction_depth > 0, + "autoindent can only be requested during a transaction" + ); + self.autoindent_requests.push(AutoindentRequest { + position: self.anchor_before(Point::new(row, 0)), + indent_size, + force, + }); + } + fn diff(&self, new_text: Arc, cx: &AppContext) -> Task { // TODO: it would be nice to not allocate here. let old_text = self.text(); @@ -2162,6 +2420,7 @@ impl Clone for Buffer { parsing_in_background: false, sync_parse_timeout: self.sync_parse_timeout, parse_count: self.parse_count, + autoindent_requests: self.autoindent_requests.clone(), deferred_replicas: self.deferred_replicas.clone(), replica_id: self.replica_id, remote_id: self.remote_id.clone(), @@ -2227,7 +2486,7 @@ impl Snapshot { let chunks = self.visible_text.chunks_in_range(range.clone()); if let Some((language, tree)) = self.language.as_ref().zip(self.tree.as_ref()) { let captures = self.query_cursor.set_byte_range(range.clone()).captures( - &language.highlight_query, + &language.highlights_query, tree.root_node(), TextProvider(&self.visible_text), ); @@ -4016,6 +4275,29 @@ mod tests { }); } + #[gpui::test] + async fn test_request_autoindent(mut cx: gpui::TestAppContext) { + let rust_lang = rust_lang(); + let buffer = cx.add_model(|cx| { + let text = "fn a() {}".into(); + Buffer::from_history(0, History::new(text), None, Some(rust_lang.clone()), cx) + }); + + buffer + .condition(&cx, |buffer, _| !buffer.is_parsing()) + .await; + + buffer.update(&mut cx, |buffer, cx| { + buffer.start_transaction(None).unwrap(); + buffer.edit([8..8], "\n\n", cx); + buffer.request_autoindent_for_line(1, 4, true); + assert_eq!(buffer.text(), "fn a() {\n\n}"); + + buffer.end_transaction(None, cx).unwrap(); + assert_eq!(buffer.text(), "fn a() {\n \n}"); + }); + } + #[derive(Clone)] struct Envelope { message: T, @@ -4104,6 +4386,8 @@ mod tests { }, tree_sitter_rust::language(), ) + .with_indents_query(r#" (_ "{" "}" @end) @indent "#) + .unwrap() .with_brackets_query(r#" ("{" @open "}" @close) "#) .unwrap(), ) diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 856aa37799..3fa4bdfa27 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -745,6 +745,7 @@ impl Editor { if !self.skip_autoclose_end(text, cx) { self.start_transaction(cx); self.insert(text, cx); + self.request_autoindent(cx); self.autoclose_pairs(cx); self.end_transaction(cx); } @@ -792,6 +793,17 @@ impl Editor { self.end_transaction(cx); } + fn request_autoindent(&mut self, cx: &mut ViewContext) { + let selections = self.selections(cx); + let tab_size = self.build_settings.borrow()(cx).tab_size as u8; + self.buffer.update(cx, |buffer, _| { + for selection in selections.iter() { + let row = selection.head().to_point(&*buffer).row; + buffer.request_autoindent_for_line(row, tab_size, true); + } + }); + } + fn autoclose_pairs(&mut self, cx: &mut ViewContext) { let selections = self.selections(cx); let new_autoclose_pair_state = self.buffer.update(cx, |buffer, cx| { diff --git a/crates/zed/languages/rust/indents.scm b/crates/zed/languages/rust/indents.scm new file mode 100644 index 0000000000..8893ba41b7 --- /dev/null +++ b/crates/zed/languages/rust/indents.scm @@ -0,0 +1,10 @@ +[ + (where_clause) + (field_expression) + (call_expression) +] @indent + +(_ "[" "]" @end) @indent +(_ "<" ">" @end) @indent +(_ "{" "}" @end) @indent +(_ "(" ")" @end) @indent diff --git a/crates/zed/src/language.rs b/crates/zed/src/language.rs index 774d8c6502..5a542ffc48 100644 --- a/crates/zed/src/language.rs +++ b/crates/zed/src/language.rs @@ -22,6 +22,8 @@ fn rust() -> Language { .unwrap() .with_brackets_query(load_query("rust/brackets.scm").as_ref()) .unwrap() + .with_indents_query(load_query("rust/indents.scm").as_ref()) + .unwrap() } fn load_query(path: &str) -> Cow<'static, str> {