diff --git a/crates/buffer/src/lib.rs b/crates/buffer/src/lib.rs index 3937f39920..51f8a04603 100644 --- a/crates/buffer/src/lib.rs +++ b/crates/buffer/src/lib.rs @@ -201,6 +201,7 @@ struct SyntaxTree { #[derive(Clone)] struct AutoindentRequest { + selection_set_id: Option, before_edit: Snapshot, edited: AnchorSet, inserted: Option, @@ -985,26 +986,12 @@ impl Buffer { .background() .block_with_timeout(Duration::from_micros(500), indent_columns) { - Ok(indent_columns) => { - log::info!("finished synchronously {:?}", indent_columns); - self.autoindent_requests.clear(); - self.start_transaction(None).unwrap(); - for (row, indent_column) in indent_columns { - self.set_indent_column_for_line(row, indent_column, cx); - } - self.end_transaction(None, cx).unwrap(); - } + Ok(indent_columns) => self.apply_autoindents(indent_columns, cx), Err(indent_columns) => { self.pending_autoindent = Some(cx.spawn(|this, mut cx| async move { let indent_columns = indent_columns.await; - log::info!("finished ASYNC, {:?}", indent_columns); this.update(&mut cx, |this, cx| { - this.autoindent_requests.clear(); - this.start_transaction(None).unwrap(); - for (row, indent_column) in indent_columns { - this.set_indent_column_for_line(row, indent_column, cx); - } - this.end_transaction(None, cx).unwrap(); + this.apply_autoindents(indent_columns, cx); }); })); } @@ -1119,6 +1106,57 @@ impl Buffer { }) } + fn apply_autoindents( + &mut self, + indent_columns: BTreeMap, + cx: &mut ModelContext, + ) { + let selection_set_ids = self + .autoindent_requests + .drain(..) + .filter_map(|req| req.selection_set_id) + .collect::>(); + + self.start_transaction(None).unwrap(); + for (row, indent_column) in &indent_columns { + self.set_indent_column_for_line(*row, *indent_column, cx); + } + + for selection_set_id in selection_set_ids { + if let Some(set) = self.selections.get(&selection_set_id) { + let new_selections = set + .selections + .iter() + .map(|selection| { + let start_point = selection.start.to_point(&*self); + if start_point.column == 0 { + let end_point = selection.end.to_point(&*self); + let delta = Point::new( + 0, + indent_columns.get(&start_point.row).copied().unwrap_or(0), + ); + if delta.column > 0 { + return Selection { + id: selection.id, + goal: selection.goal, + reversed: selection.reversed, + start: self + .anchor_at(start_point + delta, selection.start.bias), + end: self.anchor_at(end_point + delta, selection.end.bias), + }; + } + } + selection.clone() + }) + .collect::>(); + self.update_selection_set(selection_set_id, new_selections, cx) + .unwrap(); + } + } + + self.end_transaction(None, cx).unwrap(); + } + pub fn indent_column_for_line(&self, row: u32) -> u32 { self.content().indent_column_for_line(row) } @@ -1127,23 +1165,11 @@ impl Buffer { let current_column = self.indent_column_for_line(row); if column > current_column { let offset = self.visible_text.to_offset(Point::new(row, 0)); - - // TODO: do this differently. By replacing the preceding newline, - // we force the new indentation to come before any left-biased anchors - // on the line. - let delta = (column - current_column) as usize; - if offset > 0 { - let mut prefix = String::with_capacity(1 + delta); - prefix.push('\n'); - prefix.extend(std::iter::repeat(' ').take(delta)); - self.edit([(offset - 1)..offset], prefix, cx); - } else { - self.edit( - [offset..offset], - std::iter::repeat(' ').take(delta).collect::(), - cx, - ); - } + self.edit( + [offset..offset], + " ".repeat((column - current_column) as usize), + cx, + ); } else if column < current_column { self.edit( [Point::new(row, 0)..Point::new(row, current_column - column)], @@ -1518,7 +1544,16 @@ impl Buffer { }))); } + let selection_set_id = self + .history + .undo_stack + .last() + .unwrap() + .selections_before + .as_ref() + .map(|(set_id, _)| *set_id); self.autoindent_requests.push(Arc::new(AutoindentRequest { + selection_set_id, before_edit, edited, inserted, diff --git a/crates/buffer/src/tests/syntax.rs b/crates/buffer/src/tests/syntax.rs index af7b68c289..4b897dd942 100644 --- a/crates/buffer/src/tests/syntax.rs +++ b/crates/buffer/src/tests/syntax.rs @@ -195,6 +195,58 @@ fn test_edit_with_autoindent(cx: &mut MutableAppContext) { }); } +#[gpui::test] +fn test_autoindent_moves_selections(cx: &mut MutableAppContext) { + cx.add_model(|cx| { + let text = History::new("fn a() {}".into()); + let mut buffer = Buffer::from_history(0, text, None, Some(rust_lang()), cx); + + let selection_set_id = buffer.add_selection_set(Vec::new(), cx); + buffer.start_transaction(Some(selection_set_id)).unwrap(); + buffer.edit_with_autoindent([5..5, 9..9], "\n\n", cx); + buffer + .update_selection_set( + selection_set_id, + vec![ + Selection { + id: 0, + start: buffer.anchor_before(Point::new(1, 0)), + end: buffer.anchor_before(Point::new(1, 0)), + reversed: false, + goal: SelectionGoal::None, + }, + Selection { + id: 1, + start: buffer.anchor_before(Point::new(4, 0)), + end: buffer.anchor_before(Point::new(4, 0)), + reversed: false, + goal: SelectionGoal::None, + }, + ], + cx, + ) + .unwrap(); + assert_eq!(buffer.text(), "fn a(\n\n) {}\n\n"); + + // Ending the transaction runs the auto-indent. The selection + // at the start of the auto-indented row is pushed to the right. + buffer.end_transaction(Some(selection_set_id), cx).unwrap(); + assert_eq!(buffer.text(), "fn a(\n \n) {}\n\n"); + let selection_ranges = buffer + .selection_set(selection_set_id) + .unwrap() + .selections + .iter() + .map(|selection| selection.point_range(&buffer)) + .collect::>(); + + assert_eq!(selection_ranges[0], empty(Point::new(1, 4))); + assert_eq!(selection_ranges[1], empty(Point::new(4, 0))); + + buffer + }); +} + #[gpui::test] fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut MutableAppContext) { cx.add_model(|cx| { @@ -285,10 +337,6 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppConte }); } -fn empty(point: Point) -> Range { - point..point -} - #[test] fn test_contiguous_ranges() { assert_eq!( @@ -326,3 +374,7 @@ fn rust_lang() -> Arc { .unwrap(), ) } + +fn empty(point: Point) -> Range { + point..point +}