diff --git a/crates/buffer/src/lib.rs b/crates/buffer/src/lib.rs index 6598d154b1..2d145f52e4 100644 --- a/crates/buffer/src/lib.rs +++ b/crates/buffer/src/lib.rs @@ -116,6 +116,9 @@ lazy_static! { static ref QUERY_CURSORS: Mutex> = Default::default(); } +// TODO - Make this configurable +const INDENT_SIZE: u32 = 4; + struct QueryCursorHandle(Option); impl QueryCursorHandle { @@ -195,8 +198,8 @@ struct SyntaxTree { #[derive(Clone, Debug)] struct AutoindentRequest { position: Anchor, - indent_size: u8, - force: bool, + end_position: Option, + prev_suggestion: Option, } #[derive(Clone, Debug)] @@ -875,15 +878,11 @@ impl Buffer { } if let Some(language) = self.language.clone() { - let old_tree = self.syntax_tree.lock().as_mut().map(|tree| { - self.interpolate_tree(tree); - tree.clone() - }); + let old_tree = self.syntax_tree(); let text = self.visible_text.clone(); let parsed_version = self.version(); let parse_task = cx.background().spawn({ let language = language.clone(); - let old_tree = old_tree.as_ref().map(|t| t.tree.clone()); async move { Self::parse_text(&text, old_tree, &language) } }); @@ -892,13 +891,7 @@ impl Buffer { .block_with_timeout(self.sync_parse_timeout, parse_task) { Ok(new_tree) => { - self.did_finish_parsing( - old_tree.map(|t| t.tree), - new_tree, - parsed_version, - language, - cx, - ); + self.did_finish_parsing(new_tree, parsed_version, language, cx); return true; } Err(parse_task) => { @@ -911,18 +904,8 @@ impl Buffer { !Arc::ptr_eq(curr_language, &language) }); let parse_again = this.version > parsed_version || language_changed; - let old_tree = old_tree.map(|mut old_tree| { - this.interpolate_tree(&mut old_tree); - old_tree.tree - }); this.parsing_in_background = false; - this.did_finish_parsing( - old_tree, - new_tree, - parsed_version, - language, - cx, - ); + this.did_finish_parsing(new_tree, parsed_version, language, cx); if parse_again && this.reparse(cx) { return; @@ -979,125 +962,62 @@ impl Buffer { fn did_finish_parsing( &mut self, - old_tree: Option, - new_tree: Tree, - new_version: clock::Global, + tree: Tree, + version: clock::Global, language: Arc, cx: &mut ModelContext, ) { - self.perform_autoindent(old_tree, &new_tree, language, cx); + self.perform_autoindent(&tree, language, cx); self.parse_count += 1; - *self.syntax_tree.lock() = Some(SyntaxTree { - tree: new_tree, - version: new_version, - }); + *self.syntax_tree.lock() = Some(SyntaxTree { tree, version }); cx.emit(Event::Reparsed); cx.notify(); } fn perform_autoindent( &mut self, - old_tree: Option, new_tree: &Tree, language: Arc, cx: &mut ModelContext, ) { let mut autoindent_requests = mem::take(&mut self.autoindent_requests); - let mut autoindent_requests_by_row = BTreeMap::::default(); + let mut prev_suggestions_by_row = BTreeMap::>::default(); 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); + let start_row = request.position.to_point(&*self).row; + let end_row = if let Some(end_position) = request.end_position { + end_position.to_point(&*self).row + } else { + start_row + }; + for row in start_row..=end_row { + let prev_suggestion = request.prev_suggestion; + prev_suggestions_by_row + .entry(row) + .and_modify(|suggestion| *suggestion = suggestion.or(prev_suggestion)) + .or_insert(request.prev_suggestion); + } } self.autoindent_requests = autoindent_requests; - let mut row_range: Option> = None; - let mut cursor1 = QueryCursorHandle::new(); - let mut cursor2 = QueryCursorHandle::new(); + let mut cursor = QueryCursorHandle::new(); self.start_transaction(None).unwrap(); - for row in autoindent_requests_by_row.keys().copied() { - if let Some(range) = &mut row_range { - if range.end == row { - range.end += 1; - continue; + let mut prev_suggestions = prev_suggestions_by_row.iter(); + for row_range in contiguous_ranges(prev_suggestions_by_row.keys().copied()) { + let new_suggestions = self + .suggest_autoindents(row_range.clone(), new_tree, &language, &mut cursor) + .collect::>(); + let old_suggestions = prev_suggestions.by_ref().take(row_range.len()); + for ((row, old_suggestion), new_suggestion) in old_suggestions.zip(new_suggestions) { + if *old_suggestion != Some(new_suggestion) { + self.set_indent_column_for_line(*row, new_suggestion, cx); } - self.perform_autoindent_for_rows( - range.clone(), - old_tree.as_ref(), - &new_tree, - &autoindent_requests_by_row, - language.as_ref(), - &mut cursor1, - &mut cursor2, - cx, - ); } - row_range = Some(row..(row + 1)); - } - if let Some(range) = row_range { - self.perform_autoindent_for_rows( - range, - old_tree.as_ref(), - &new_tree, - &autoindent_requests_by_row, - language.as_ref(), - &mut cursor1, - &mut cursor2, - cx, - ); } self.end_transaction(None, cx).unwrap(); } - fn perform_autoindent_for_rows( - &mut self, - row_range: Range, - old_tree: Option<&Tree>, - new_tree: &Tree, - autoindent_requests: &BTreeMap, - language: &Language, - cursor1: &mut QueryCursor, - cursor2: &mut QueryCursor, - cx: &mut ModelContext, - ) { - let new_suggestions = self.suggest_autoindents( - autoindent_requests, - row_range.clone(), - new_tree, - language, - cursor1, - ); - - if let Some(old_tree) = old_tree { - let old_suggestions = self.suggest_autoindents( - autoindent_requests, - row_range.clone(), - old_tree, - language, - cursor2, - ); - let suggestions = old_suggestions.zip(new_suggestions).collect::>(); - let requests = autoindent_requests.range(row_range); - for ((row, request), (old_suggestion, new_suggestion)) in requests.zip(suggestions) { - if request.force || new_suggestion != old_suggestion { - self.set_indent_column_for_line(*row, new_suggestion, cx); - } - } - } else { - for (row, new_suggestion) in row_range.zip(new_suggestions.collect::>()) { - self.set_indent_column_for_line(row, new_suggestion, cx); - } - } - } - fn suggest_autoindents<'a>( &'a self, - autoindent_requests: &'a BTreeMap, row_range: Range, tree: &Tree, language: &Language, @@ -1151,7 +1071,6 @@ impl Buffer { let mut prev_indent_column = prev_non_blank_row.map_or(0, |prev_row| self.indent_column_for_line(prev_row)); row_range.map(move |row| { - let request = autoindent_requests.get(&row).unwrap(); let row_start = Point::new(row, self.indent_column_for_line(row)); eprintln!( @@ -1159,16 +1078,17 @@ impl Buffer { row, prev_indent_column ); - let mut increase_from_prev_row = false; + let mut indent_from_prev_row = false; let mut dedent_to_row = u32::MAX; for (range, node_kind) in &indentation_ranges { - if range.start.row == prev_row && prev_row < row && range.end > row_start { - eprintln!(" indent because of {} {:?}", node_kind, range); - increase_from_prev_row = true; + if range.start.row >= row { + break; } - if range.start.row < row - && (Point::new(prev_row, 0)..=row_start).contains(&range.end) - { + if range.start.row == prev_row && range.end > row_start { + eprintln!(" indent because of {} {:?}", node_kind, range); + indent_from_prev_row = true; + } + if range.end.row >= prev_row && range.end <= row_start { eprintln!(" outdent because of {} {:?}", node_kind, range); dedent_to_row = dedent_to_row.min(range.start.row); } @@ -1176,11 +1096,11 @@ impl Buffer { let mut indent_column = prev_indent_column; if dedent_to_row < row { - if !increase_from_prev_row { + if !indent_from_prev_row { indent_column = self.indent_column_for_line(dedent_to_row); } - } else if increase_from_prev_row { - indent_column += request.indent_size as u32; + } else if indent_from_prev_row { + indent_column += INDENT_SIZE; } prev_indent_column = indent_column; @@ -1287,18 +1207,6 @@ 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(); @@ -1524,20 +1432,41 @@ impl Buffer { I: IntoIterator>, S: ToOffset, T: Into, + { + self.edit_internal(ranges_iter, new_text, false, cx) + } + + pub fn edit_with_autoindent( + &mut self, + ranges_iter: I, + new_text: T, + cx: &mut ModelContext, + ) where + I: IntoIterator>, + S: ToOffset, + T: Into, + { + self.edit_internal(ranges_iter, new_text, true, cx) + } + + pub fn edit_internal( + &mut self, + ranges_iter: I, + new_text: T, + autoindent: bool, + cx: &mut ModelContext, + ) where + I: IntoIterator>, + S: ToOffset, + T: Into, { let new_text = new_text.into(); - let new_text = if new_text.len() > 0 { - Some(new_text) - } else { - None - }; - let has_new_text = new_text.is_some(); // Skip invalid ranges and coalesce contiguous ones. let mut ranges: Vec> = Vec::new(); for range in ranges_iter { let range = range.start.to_offset(&*self)..range.end.to_offset(&*self); - if has_new_text || !range.is_empty() { + if !new_text.is_empty() || !range.is_empty() { if let Some(prev_range) = ranges.last_mut() { if prev_range.end >= range.start { prev_range.end = cmp::max(prev_range.end, range.end); @@ -1549,24 +1478,83 @@ impl Buffer { } } } + if ranges.is_empty() { + return; + } - if !ranges.is_empty() { - self.start_transaction_at(None, Instant::now()).unwrap(); - let timestamp = InsertionTimestamp { - replica_id: self.replica_id, - local: self.local_clock.tick().value, - lamport: self.lamport_clock.tick().value, - }; - let edit = self.apply_local_edit(&ranges, new_text, timestamp); + // When autoindent is enabled, compute the previous indent suggestion for all edited lines. + if autoindent { + if let Some((language, tree)) = self.language.as_ref().zip(self.syntax_tree()) { + let mut cursor = QueryCursorHandle::new(); + let starts_with_newline = new_text.starts_with('\n'); - self.history.push(edit.clone()); - self.history.push_undo(edit.timestamp.local()); - self.last_edit = edit.timestamp.local(); - self.version.observe(edit.timestamp.local()); + let mut autoindent_requests = mem::take(&mut self.autoindent_requests); + let edited_rows = ranges.iter().filter_map(|range| { + let start = range.start.to_point(&*self); + if !starts_with_newline || start.column < self.line_len(start.row) { + Some(start.row) + } else { + None + } + }); + for row_range in contiguous_ranges(edited_rows) { + let suggestions = self + .suggest_autoindents(row_range.clone(), &tree, language, &mut cursor) + .collect::>(); + for (row, suggestion) in row_range.zip(suggestions) { + autoindent_requests.push(AutoindentRequest { + position: self.anchor_before(Point::new(row, 0)), + end_position: None, + prev_suggestion: Some(suggestion), + }) + } + } + self.autoindent_requests = autoindent_requests; + } + } - self.end_transaction_at(None, Instant::now(), cx).unwrap(); - self.send_operation(Operation::Edit(edit), cx); + let new_text = if new_text.len() > 0 { + Some(new_text) + } else { + None }; + + self.start_transaction_at(None, Instant::now()).unwrap(); + let timestamp = InsertionTimestamp { + replica_id: self.replica_id, + local: self.local_clock.tick().value, + lamport: self.lamport_clock.tick().value, + }; + let edit = self.apply_local_edit(&ranges, new_text, timestamp); + + self.history.push(edit.clone()); + self.history.push_undo(edit.timestamp.local()); + self.last_edit = edit.timestamp.local(); + self.version.observe(edit.timestamp.local()); + + if autoindent { + let ranges = edit.ranges.iter().map(|range| { + Anchor { + offset: range.start, + bias: Bias::Left, + version: edit.version.clone(), + }..Anchor { + offset: range.end, + bias: Bias::Right, + version: edit.version.clone(), + } + }); + for range in ranges { + self.autoindent_requests.push(AutoindentRequest { + position: range.start, + end_position: Some(range.end), + prev_suggestion: None, + }) + } + } + + self.end_transaction_at(None, Instant::now(), cx).unwrap(); + self.send_operation(Operation::Edit(edit), cx); } fn did_edit(&self, was_dirty: bool, cx: &mut ModelContext) { @@ -3437,6 +3425,28 @@ impl ToPoint for usize { } } +fn contiguous_ranges(mut values: impl Iterator) -> impl Iterator> { + let mut current_range: Option> = None; + std::iter::from_fn(move || loop { + if let Some(value) = values.next() { + if let Some(range) = &mut current_range { + if value == range.end { + range.end += 1; + continue; + } + } + + let prev_range = current_range.clone(); + current_range = Some(value..(value + 1)); + if prev_range.is_some() { + return prev_range; + } + } else { + return current_range.take(); + } + }) +} + #[cfg(test)] mod tests { use crate::random_char_iter::RandomCharIter; @@ -4329,7 +4339,7 @@ mod tests { } #[gpui::test] - async fn test_request_autoindent(mut cx: gpui::TestAppContext) { + async fn test_edit_with_autoindent(mut cx: gpui::TestAppContext) { let rust_lang = rust_lang(); let buffer = cx.add_model(|cx| { let text = "fn a() {}".into(); @@ -4341,16 +4351,25 @@ mod tests { .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(); + buffer.edit_with_autoindent([8..8], "\n\n", cx); assert_eq!(buffer.text(), "fn a() {\n \n}"); + + buffer.edit_with_autoindent([Point::new(1, 4)..Point::new(1, 4)], "b()\n", cx); + assert_eq!(buffer.text(), "fn a() {\n b()\n \n}"); + + buffer.edit_with_autoindent([Point::new(2, 4)..Point::new(2, 4)], ".c", cx); + assert_eq!(buffer.text(), "fn a() {\n b()\n .c\n}"); }); } + #[test] + fn test_contiguous_ranges() { + assert_eq!( + contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].iter().copied()).collect::>(), + &[1..4, 5..7, 9..13] + ); + } + #[derive(Clone)] struct Envelope { message: T, @@ -4439,7 +4458,13 @@ mod tests { }, tree_sitter_rust::language(), ) - .with_indents_query(r#" (_ "{" "}" @end) @indent "#) + .with_indents_query( + r#" + (call_expression) @indent + (field_expression) @indent + (_ "{" "}" @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 27cb92afa2..9acb8fc2f7 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -747,7 +747,6 @@ 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); } @@ -769,7 +768,7 @@ impl Editor { let mut new_selections = Vec::new(); self.buffer.update(cx, |buffer, cx| { let edit_ranges = old_selections.iter().map(|(_, range)| range.clone()); - buffer.edit(edit_ranges, text, cx); + buffer.edit_with_autoindent(edit_ranges, text, cx); let text_len = text.len() as isize; let mut delta = 0_isize; new_selections = old_selections @@ -795,17 +794,6 @@ 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| {