diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index e122fb557b..1813d8f52d 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -1710,8 +1710,8 @@ mod tests { .update(cx_c, |p, cx| p.open_buffer((worktree_id, "file1"), cx)) .await .unwrap(); - buffer_b.update(cx_b, |buf, cx| buf.edit([0..0], "i-am-b, ", cx)); - buffer_c.update(cx_c, |buf, cx| buf.edit([0..0], "i-am-c, ", cx)); + buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "i-am-b, ")], cx)); + buffer_c.update(cx_c, |buf, cx| buf.edit([(0..0, "i-am-c, ")], cx)); // Open and edit that buffer as the host. let buffer_a = project_a @@ -1723,7 +1723,7 @@ mod tests { .condition(cx_a, |buf, _| buf.text() == "i-am-c, i-am-b, ") .await; buffer_a.update(cx_a, |buf, cx| { - buf.edit([buf.len()..buf.len()], "i-am-a", cx) + buf.edit([(buf.len()..buf.len(), "i-am-a")], cx) }); // Wait for edits to propagate @@ -1739,7 +1739,7 @@ mod tests { // Edit the buffer as the host and concurrently save as guest B. let save_b = buffer_b.update(cx_b, |buf, cx| buf.save(cx)); - buffer_a.update(cx_a, |buf, cx| buf.edit([0..0], "hi-a, ", cx)); + buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "hi-a, ")], cx)); save_b.await.unwrap(); assert_eq!( fs.load("/a/file1".as_ref()).await.unwrap(), @@ -1869,7 +1869,7 @@ mod tests { .await .unwrap(); - buffer_b.update(cx_b, |buf, cx| buf.edit([0..0], "world ", cx)); + buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "world ")], cx)); buffer_b.read_with(cx_b, |buf, _| { assert!(buf.is_dirty()); assert!(!buf.has_conflict()); @@ -1883,7 +1883,7 @@ mod tests { assert!(!buf.has_conflict()); }); - buffer_b.update(cx_b, |buf, cx| buf.edit([0..0], "hello ", cx)); + buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "hello ")], cx)); buffer_b.read_with(cx_b, |buf, _| { assert!(buf.is_dirty()); assert!(!buf.has_conflict()); @@ -2039,9 +2039,9 @@ mod tests { // Edit the buffer as client A while client B is still opening it. cx_b.background().simulate_random_delay().await; - buffer_a.update(cx_a, |buf, cx| buf.edit([0..0], "X", cx)); + buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "X")], cx)); cx_b.background().simulate_random_delay().await; - buffer_a.update(cx_a, |buf, cx| buf.edit([1..1], "Y", cx)); + buffer_a.update(cx_a, |buf, cx| buf.edit([(1..1, "Y")], cx)); let text = buffer_a.read_with(cx_a, |buf, _| buf.text()); let buffer_b = buffer_b.await.unwrap(); @@ -2727,8 +2727,8 @@ mod tests { .await .unwrap(); buffer_b.update(cx_b, |buffer, cx| { - buffer.edit([4..7], "six", cx); - buffer.edit([10..11], "6", cx); + buffer.edit([(4..7, "six")], cx); + buffer.edit([(10..11, "6")], cx); assert_eq!(buffer.text(), "let six = 6;"); assert!(buffer.is_dirty()); assert!(!buffer.has_conflict()); @@ -4009,7 +4009,7 @@ mod tests { ); rename.editor.update(cx, |rename_editor, cx| { rename_editor.buffer().update(cx, |rename_buffer, cx| { - rename_buffer.edit([0..3], "THREE", cx); + rename_buffer.edit([(0..3, "THREE")], cx); }); }); }); diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index d6e061ffc6..f004533118 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -843,7 +843,7 @@ pub mod tests { let ix = snapshot.buffer_snapshot.text().find("seven").unwrap(); buffer.update(cx, |buffer, cx| { - buffer.edit(vec![ix..ix], "and ", cx); + buffer.edit([(ix..ix, "and ")], cx); }); let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); @@ -878,11 +878,10 @@ pub mod tests { buffer.update(cx, |buffer, cx| { buffer.edit( vec![ - Point::new(1, 0)..Point::new(1, 0), - Point::new(1, 1)..Point::new(1, 1), - Point::new(2, 1)..Point::new(2, 1), + (Point::new(1, 0)..Point::new(1, 0), "\t"), + (Point::new(1, 1)..Point::new(1, 1), "\t"), + (Point::new(2, 1)..Point::new(2, 1), "\t"), ], - "\t", cx, ) }); diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index b7f1836cdc..c5c06048e5 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1154,7 +1154,7 @@ mod tests { // Insert a line break, separating two block decorations into separate lines. let buffer_snapshot = buffer.update(cx, |buffer, cx| { - buffer.edit([Point::new(1, 1)..Point::new(1, 1)], "!!!\n", cx); + buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], cx); buffer.snapshot(cx) }); diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 3c020dceb7..8fa107a8b5 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1248,10 +1248,9 @@ mod tests { let buffer_snapshot = buffer.update(cx, |buffer, cx| { buffer.edit( vec![ - Point::new(0, 0)..Point::new(0, 1), - Point::new(2, 3)..Point::new(2, 3), + (Point::new(0, 0)..Point::new(0, 1), "123"), + (Point::new(2, 3)..Point::new(2, 3), "123"), ], - "123", cx, ); buffer.snapshot(cx) @@ -1274,7 +1273,7 @@ mod tests { ); let buffer_snapshot = buffer.update(cx, |buffer, cx| { - buffer.edit(vec![Point::new(2, 6)..Point::new(4, 3)], "456", cx); + buffer.edit([(Point::new(2, 6)..Point::new(4, 3), "456")], cx); buffer.snapshot(cx) }); let (snapshot4, _) = map.read(buffer_snapshot.clone(), subscription.consume().into_inner()); @@ -1330,7 +1329,7 @@ mod tests { // Edit within one of the folds. let buffer_snapshot = buffer.update(cx, |buffer, cx| { - buffer.edit(vec![0..1], "12345", cx); + buffer.edit([(0..1, "12345")], cx); buffer.snapshot(cx) }); let (snapshot, _) = @@ -1372,7 +1371,7 @@ mod tests { assert_eq!(snapshot.text(), "aa…cccc\nd…eeeee"); let buffer_snapshot = buffer.update(cx, |buffer, cx| { - buffer.edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", cx); + buffer.edit([(Point::new(2, 2)..Point::new(3, 1), "")], cx); buffer.snapshot(cx) }); let (snapshot, _) = map.read(buffer_snapshot.clone(), subscription.consume().into_inner()); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 54fac76ab8..5d1bbacf3e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -549,7 +549,7 @@ struct SnippetState { pub struct RenameState { pub range: Range, - pub old_name: String, + pub old_name: Arc, pub editor: ViewHandle, block_id: BlockId, } @@ -1318,7 +1318,7 @@ impl Editor { pub fn replace_selections_with( &mut self, cx: &mut ViewContext, - find_replacement: impl Fn(&DisplaySnapshot) -> DisplayPoint, + mut find_replacement: impl FnMut(&DisplaySnapshot) -> DisplayPoint, ) { let display_map = self.snapshot(cx); let cursor = find_replacement(&display_map); @@ -1336,7 +1336,7 @@ impl Editor { pub fn move_selections( &mut self, cx: &mut ViewContext, - move_selection: impl Fn(&DisplaySnapshot, &mut Selection), + mut move_selection: impl FnMut(&DisplaySnapshot, &mut Selection), ) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let selections = self @@ -1354,7 +1354,7 @@ impl Editor { pub fn move_selection_heads( &mut self, cx: &mut ViewContext, - update_head: impl Fn( + mut update_head: impl FnMut( &DisplaySnapshot, DisplayPoint, SelectionGoal, @@ -1369,7 +1369,7 @@ impl Editor { pub fn move_cursors( &mut self, cx: &mut ViewContext, - update_cursor_position: impl Fn( + mut update_cursor_position: impl FnMut( &DisplaySnapshot, DisplayPoint, SelectionGoal, @@ -1863,133 +1863,91 @@ impl Editor { pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext) { self.transact(cx, |this, cx| { - let mut old_selections = SmallVec::<[_; 32]>::new(); - { + let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = { let selections = this.local_selections::(cx); let buffer = this.buffer.read(cx).snapshot(cx); - for selection in selections.iter() { - let start_point = selection.start.to_point(&buffer); - let indent = buffer - .indent_column_for_line(start_point.row) - .min(start_point.column); - let start = selection.start; - let end = selection.end; + selections + .iter() + .map(|selection| { + let start_point = selection.start.to_point(&buffer); + let indent = buffer + .indent_column_for_line(start_point.row) + .min(start_point.column); + let start = selection.start; + let end = selection.end; - 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 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::(); + 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(); + 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, - ) - }); - } + 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, - buffer.anchor_after(end), - start..end, - indent, - insert_extra_newline, - )); - } - } - - this.buffer.update(cx, |buffer, cx| { - let mut delta = 0_isize; - let mut pending_edit: Option = None; - 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); + let mut new_text = String::with_capacity(1 + indent as usize); new_text.push('\n'); - new_text.extend(iter::repeat(' ').take(pending.indent as usize)); - if pending.insert_extra_newline { + new_text.extend(iter::repeat(' ').take(indent as usize)); + if insert_extra_newline { new_text = new_text.repeat(2); } - buffer.edit_with_autoindent(pending.ranges, new_text, cx); - delta += pending.delta; - } + ( + (start..end, new_text), + (insert_extra_newline, buffer.anchor_after(end)), + ) + }) + .unzip() + }; - let start = (range.start as isize + delta) as usize; - let end = (range.end as isize + delta) as usize; - 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); - } - - let pending = pending_edit.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); + this.buffer.update(cx, |buffer, cx| { + buffer.edit_with_autoindent(edits, cx); let buffer = buffer.read(cx); this.selections = this .selections .iter() .cloned() - .zip(old_selections) - .map( - |(mut new_selection, (_, end_anchor, _, _, insert_extra_newline))| { - let mut cursor = end_anchor.to_point(&buffer); - if insert_extra_newline { - cursor.row -= 1; - cursor.column = buffer.line_len(cursor.row); - } - let anchor = buffer.anchor_after(cursor); - new_selection.start = anchor.clone(); - new_selection.end = anchor; - new_selection - }, - ) + .zip(selection_fixup_info) + .map(|(mut new_selection, (extra_newline_inserted, end))| { + let mut cursor = end.to_point(&buffer); + if extra_newline_inserted { + cursor.row -= 1; + cursor.column = buffer.line_len(cursor.row); + } + let anchor = buffer.anchor_after(cursor); + new_selection.start = anchor.clone(); + new_selection.end = anchor; + new_selection + }) .collect(); }); this.request_autoscroll(Autoscroll::Fit, cx); }); - - #[derive(Default)] - struct PendingEdit { - indent: u32, - insert_extra_newline: bool, - delta: isize, - ranges: SmallVec<[Range; 32]>, - } } pub fn insert(&mut self, text: &str, cx: &mut ViewContext) { + let text: Arc = text.into(); self.transact(cx, |this, cx| { let old_selections = this.local_selections::(cx); let selection_anchors = this.buffer.update(cx, |buffer, cx| { @@ -2000,8 +1958,12 @@ impl Editor { .map(|s| (s.id, s.goal, snapshot.anchor_after(s.end))) .collect::>() }; - let edit_ranges = old_selections.iter().map(|s| s.start..s.end); - buffer.edit_with_autoindent(edit_ranges, text, cx); + buffer.edit_with_autoindent( + old_selections + .iter() + .map(|s| (s.start..s.end, text.clone())), + cx, + ); anchors }); @@ -2059,14 +2021,18 @@ impl Editor { drop(snapshot); self.buffer.update(cx, |buffer, cx| { + let pair_start: Arc = pair.start.clone().into(); buffer.edit( - selections.iter().map(|s| s.start.clone()..s.start.clone()), - &pair.start, + selections + .iter() + .map(|s| (s.start.clone()..s.start.clone(), pair_start.clone())), cx, ); + let pair_end: Arc = pair.end.clone().into(); buffer.edit( - selections.iter().map(|s| s.end.clone()..s.end.clone()), - &pair.end, + selections + .iter() + .map(|s| (s.end.clone()..s.end.clone(), pair_end.clone())), cx, ); }); @@ -2143,7 +2109,13 @@ impl Editor { }) .collect::>(); - buffer.edit(selection_ranges, &pair.end, cx); + let pair_end: Arc = pair.end.clone().into(); + buffer.edit( + selection_ranges + .iter() + .map(|range| (range.clone(), pair_end.clone())), + cx, + ); snapshot = buffer.snapshot(cx); new_selections = Some( @@ -2402,7 +2374,8 @@ impl Editor { this.insert_snippet(&ranges, snippet, cx).log_err(); } else { this.buffer.update(cx, |buffer, cx| { - buffer.edit_with_autoindent(ranges, text, cx); + buffer + .edit_with_autoindent(ranges.iter().map(|range| (range.clone(), text)), cx); }); } }); @@ -2759,7 +2732,14 @@ impl Editor { cx: &mut ViewContext, ) -> Result<()> { let tabstops = self.buffer.update(cx, |buffer, cx| { - buffer.edit_with_autoindent(insertion_ranges.iter().cloned(), &snippet.text, cx); + let snippet_text: Arc = snippet.text.clone().into(); + buffer.edit_with_autoindent( + insertion_ranges + .iter() + .cloned() + .map(|range| (range, snippet_text.clone())), + cx, + ); let snapshot = &*buffer.read(cx); let snippet = &snippet; @@ -2940,8 +2920,10 @@ impl Editor { .count(); let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size); buffer.edit( - [selection.start..selection.start], - " ".repeat(chars_to_next_tab_stop as usize), + [( + selection.start..selection.start, + " ".repeat(chars_to_next_tab_stop as usize), + )], cx, ); selection.start.column += chars_to_next_tab_stop; @@ -2991,8 +2973,10 @@ impl Editor { let columns_to_next_tab_stop = tab_size - (indent_column % tab_size); let row_start = Point::new(row, 0); buffer.edit( - [row_start..row_start], - " ".repeat(columns_to_next_tab_stop as usize), + [( + row_start..row_start, + " ".repeat(columns_to_next_tab_stop as usize), + )], cx, ); @@ -3050,7 +3034,13 @@ impl Editor { self.transact(cx, |this, cx| { this.buffer.update(cx, |buffer, cx| { - buffer.edit(deletion_ranges, "", cx); + let empty_str: Arc = "".into(); + buffer.edit( + deletion_ranges + .into_iter() + .map(|range| (range, empty_str.clone())), + cx, + ); }); this.update_selections( this.local_selections::(cx), @@ -3112,7 +3102,13 @@ impl Editor { self.transact(cx, |this, cx| { let buffer = this.buffer.update(cx, |buffer, cx| { - buffer.edit(edit_ranges, "", cx); + let empty_str: Arc = "".into(); + buffer.edit( + edit_ranges + .into_iter() + .map(|range| (range, empty_str.clone())), + cx, + ); buffer.snapshot(cx) }); let new_selections = new_cursors @@ -3160,14 +3156,12 @@ impl Editor { .text_for_range(start..end) .chain(Some("\n")) .collect::(); - edits.push((start, text, rows.len() as u32)); + edits.push((start..start, text)); } self.transact(cx, |this, cx| { this.buffer.update(cx, |buffer, cx| { - for (point, text, _) in edits.into_iter().rev() { - buffer.edit(Some(point..point), text, cx); - } + buffer.edit(edits, cx); }); this.request_autoscroll(Autoscroll::Fit, cx); @@ -3276,7 +3270,7 @@ impl Editor { this.unfold_ranges(unfold_ranges, true, cx); this.buffer.update(cx, |buffer, cx| { for (range, text) in edits { - buffer.edit([range], text, cx); + buffer.edit([(range, text)], cx); } }); this.fold_ranges(refold_ranges, cx); @@ -3379,7 +3373,7 @@ impl Editor { this.unfold_ranges(unfold_ranges, true, cx); this.buffer.update(cx, |buffer, cx| { for (range, text) in edits { - buffer.edit([range], text, cx); + buffer.edit([(range, text)], cx); } }); this.fold_ranges(refold_ranges, cx); @@ -3495,7 +3489,7 @@ impl Editor { }; delta += to_insert.len() as isize - range.len() as isize; - buffer.edit([range], to_insert, cx); + buffer.edit([(range, to_insert)], cx); selection.start += to_insert.len(); selection.end = selection.start; }); @@ -4210,11 +4204,11 @@ impl Editor { for selection in &mut selections { // Get the line comment prefix. Split its trailing whitespace into a separate string, // as that portion won't be used for detecting if a line is a comment. - let full_comment_prefix = if let Some(prefix) = buffer + let full_comment_prefix: Arc = if let Some(prefix) = buffer .language_at(selection.start, cx) .and_then(|l| l.line_comment_prefix()) { - prefix.to_string() + prefix.into() } else { return; }; @@ -4281,15 +4275,22 @@ impl Editor { if !edit_ranges.is_empty() { if all_selection_lines_are_comments { - buffer.edit(edit_ranges.iter().cloned(), "", cx); + let empty_str: Arc = "".into(); + buffer.edit( + edit_ranges + .iter() + .cloned() + .map(|range| (range, empty_str.clone())), + cx, + ); } else { let min_column = edit_ranges.iter().map(|r| r.start.column).min().unwrap(); - let edit_ranges = edit_ranges.iter().map(|range| { + let edits = edit_ranges.iter().map(|range| { let position = Point::new(range.start.row, min_column); - position..position + (position..position, full_comment_prefix.clone()) }); - buffer.edit(edit_ranges, &full_comment_prefix, cx); + buffer.edit(edits, cx); } } } @@ -4670,7 +4671,7 @@ impl Editor { let rename_end = rename_start + rename_buffer_range.len(); let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end); let mut old_highlight_id = None; - let old_name = buffer + let old_name: Arc = buffer .chunks(rename_start..rename_end, true) .map(|chunk| { if old_highlight_id.is_none() { @@ -4678,7 +4679,8 @@ impl Editor { } chunk.text }) - .collect(); + .collect::() + .into(); drop(buffer); @@ -4692,7 +4694,7 @@ impl Editor { } editor .buffer - .update(cx, |buffer, cx| buffer.edit([0..0], &old_name, cx)); + .update(cx, |buffer, cx| buffer.edit([(0..0, old_name.clone())], cx)); editor.select_all(&SelectAll, cx); editor }); @@ -5614,7 +5616,7 @@ impl Editor { self.buffer.read(cx).read(cx).text() } - pub fn set_text(&mut self, text: impl Into, cx: &mut ViewContext) { + pub fn set_text(&mut self, text: impl Into>, cx: &mut ViewContext) { self.transact(cx, |this, cx| { this.buffer .read(cx) @@ -6619,8 +6621,8 @@ mod tests { // Simulate an edit in another editor buffer.update(cx, |buffer, cx| { buffer.start_transaction_at(now, cx); - buffer.edit([0..1], "a", cx); - buffer.edit([1..1], "b", cx); + buffer.edit([(0..1, "a")], cx); + buffer.edit([(1..1, "b")], cx); buffer.end_transaction_at(now, cx); }); @@ -6988,10 +6990,9 @@ mod tests { buffer.update(cx, |buffer, cx| { buffer.edit( vec![ - Point::new(1, 0)..Point::new(1, 0), - Point::new(1, 1)..Point::new(1, 1), + (Point::new(1, 0)..Point::new(1, 0), "\t"), + (Point::new(1, 1)..Point::new(1, 1), "\t"), ], - "\t", cx, ); }); @@ -7626,10 +7627,9 @@ mod tests { buffer.update(cx, |buffer, cx| { buffer.edit( [ - Point::new(1, 2)..Point::new(3, 0), - Point::new(4, 2)..Point::new(6, 0), + (Point::new(1, 2)..Point::new(3, 0), ""), + (Point::new(4, 2)..Point::new(6, 0), ""), ], - "", cx, ); assert_eq!( @@ -7688,7 +7688,7 @@ mod tests { // Edit the buffer directly, deleting ranges surrounding the editor's selections buffer.update(cx, |buffer, cx| { - buffer.edit([2..5, 10..13, 18..21], "", cx); + buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], cx); assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent()); }); diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index ae061196bd..7ba9f24806 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -255,38 +255,33 @@ impl MultiBuffer { self.subscriptions.subscribe() } - pub fn edit(&mut self, ranges: I, new_text: T, cx: &mut ModelContext) + pub fn edit(&mut self, edits: I, cx: &mut ModelContext) where - I: IntoIterator>, + I: IntoIterator, T)>, S: ToOffset, - T: Into, + T: Into>, { - self.edit_internal(ranges, new_text, false, cx) + self.edit_internal(edits, false, cx) } - pub fn edit_with_autoindent( - &mut self, - ranges: I, - new_text: T, - cx: &mut ModelContext, - ) where - I: IntoIterator>, + pub fn edit_with_autoindent(&mut self, edits: I, cx: &mut ModelContext) + where + I: IntoIterator, T)>, S: ToOffset, - T: Into, + T: Into>, { - self.edit_internal(ranges, new_text, true, cx) + self.edit_internal(edits, true, cx) } pub fn edit_internal( &mut self, - ranges_iter: I, - new_text: T, + edits_iter: I, autoindent: bool, cx: &mut ModelContext, ) where - I: IntoIterator>, + I: IntoIterator, T)>, S: ToOffset, - T: Into, + T: Into>, { if self.buffers.borrow().is_empty() { return; @@ -294,24 +289,29 @@ impl MultiBuffer { if let Some(buffer) = self.as_singleton() { let snapshot = self.read(cx); - let ranges = ranges_iter - .into_iter() - .map(|range| range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot)); + let edits = edits_iter.into_iter().map(|(range, new_text)| { + ( + range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot), + new_text, + ) + }); return buffer.update(cx, |buffer, cx| { let language_name = buffer.language().map(|language| language.name()); let indent_size = cx.global::().tab_size(language_name.as_deref()); if autoindent { - buffer.edit_with_autoindent(ranges, new_text, indent_size, cx); + buffer.edit_with_autoindent(edits, indent_size, cx); } else { - buffer.edit(ranges, new_text, cx); + buffer.edit(edits, cx); } }); } let snapshot = self.read(cx); - let mut buffer_edits: HashMap, bool)>> = Default::default(); + let mut buffer_edits: HashMap, Arc, bool)>> = + Default::default(); let mut cursor = snapshot.excerpts.cursor::(); - for range in ranges_iter { + for (range, new_text) in edits_iter { + let new_text: Arc = new_text.into(); let start = range.start.to_offset(&snapshot); let end = range.end.to_offset(&snapshot); cursor.seek(&start, Bias::Right, &()); @@ -335,7 +335,7 @@ impl MultiBuffer { buffer_edits .entry(start_excerpt.buffer_id) .or_insert(Vec::new()) - .push((buffer_start..buffer_end, true)); + .push((buffer_start..buffer_end, new_text, true)); } else { let start_excerpt_range = buffer_start..start_excerpt.range.end.to_offset(&start_excerpt.buffer); @@ -344,11 +344,11 @@ impl MultiBuffer { buffer_edits .entry(start_excerpt.buffer_id) .or_insert(Vec::new()) - .push((start_excerpt_range, true)); + .push((start_excerpt_range, new_text.clone(), true)); buffer_edits .entry(end_excerpt.buffer_id) .or_insert(Vec::new()) - .push((end_excerpt_range, false)); + .push((end_excerpt_range, new_text.clone(), false)); cursor.seek(&start, Bias::Right, &()); cursor.next(&()); @@ -359,25 +359,32 @@ impl MultiBuffer { buffer_edits .entry(excerpt.buffer_id) .or_insert(Vec::new()) - .push((excerpt.range.to_offset(&excerpt.buffer), false)); + .push(( + excerpt.range.to_offset(&excerpt.buffer), + new_text.clone(), + false, + )); cursor.next(&()); } } } - let new_text = new_text.into(); for (buffer_id, mut edits) in buffer_edits { - edits.sort_unstable_by_key(|(range, _)| range.start); + edits.sort_unstable_by_key(|(range, _, _)| range.start); self.buffers.borrow()[&buffer_id] .buffer .update(cx, |buffer, cx| { let mut edits = edits.into_iter().peekable(); let mut insertions = Vec::new(); let mut deletions = Vec::new(); - while let Some((mut range, mut is_insertion)) = edits.next() { - while let Some((next_range, next_is_insertion)) = edits.peek() { + let empty_str: Arc = "".into(); + while let Some((mut range, mut new_text, mut is_insertion)) = edits.next() { + while let Some((next_range, next_new_text, next_is_insertion)) = + edits.peek() + { if range.end >= next_range.start { range.end = cmp::max(next_range.end, range.end); + new_text = format!("{new_text}{next_new_text}").into(); is_insertion |= *next_is_insertion; edits.next(); } else { @@ -386,24 +393,26 @@ impl MultiBuffer { } if is_insertion { - insertions.push( + insertions.push(( buffer.anchor_before(range.start)..buffer.anchor_before(range.end), - ); + new_text, + )); } else if !range.is_empty() { - deletions.push( + deletions.push(( buffer.anchor_before(range.start)..buffer.anchor_before(range.end), - ); + empty_str.clone(), + )); } } let language_name = buffer.language().map(|l| l.name()); let indent_size = cx.global::().tab_size(language_name.as_deref()); if autoindent { - buffer.edit_with_autoindent(deletions, "", indent_size, cx); - buffer.edit_with_autoindent(insertions, new_text.clone(), indent_size, cx); + buffer.edit_with_autoindent(deletions, indent_size, cx); + buffer.edit_with_autoindent(insertions, indent_size, cx); } else { - buffer.edit(deletions, "", cx); - buffer.edit(insertions, new_text.clone(), cx); + buffer.edit(deletions, cx); + buffer.edit(insertions, cx); } }) } @@ -1249,28 +1258,34 @@ impl MultiBuffer { pub fn randomly_edit( &mut self, rng: &mut impl rand::Rng, - count: usize, + edit_count: usize, cx: &mut ModelContext, ) { use text::RandomCharIter; let snapshot = self.read(cx); - let mut old_ranges: Vec> = Vec::new(); - for _ in 0..count { - let last_end = old_ranges.last().map_or(0, |last_range| last_range.end + 1); - if last_end > snapshot.len() { + let mut edits: Vec<(Range, Arc)> = Vec::new(); + let mut last_end = None; + for _ in 0..edit_count { + if last_end.map_or(false, |last_end| last_end >= snapshot.len()) { break; } - let end_ix = snapshot.clip_offset(rng.gen_range(0..=last_end), Bias::Right); - let start_ix = snapshot.clip_offset(rng.gen_range(0..=end_ix), Bias::Left); - old_ranges.push(start_ix..end_ix); + + let new_start = last_end.map_or(0, |last_end| last_end + 1); + let end = snapshot.clip_offset(rng.gen_range(new_start..=snapshot.len()), Bias::Right); + let start = snapshot.clip_offset(rng.gen_range(new_start..=end), Bias::Right); + last_end = Some(end); + let range = start..end; + + let new_text_len = rng.gen_range(0..10); + let new_text: String = RandomCharIter::new(&mut *rng).take(new_text_len).collect(); + + edits.push((range, new_text.into())); } - let new_text_len = rng.gen_range(0..10); - let new_text: String = RandomCharIter::new(&mut *rng).take(new_text_len).collect(); - log::info!("mutating multi-buffer at {:?}: {:?}", old_ranges, new_text); + log::info!("mutating multi-buffer with {:?}", edits); drop(snapshot); - self.edit(old_ranges.iter().cloned(), new_text.as_str(), cx); + self.edit(edits, cx); } pub fn randomly_edit_excerpts( @@ -2950,7 +2965,7 @@ mod tests { .collect::>() ); - buffer.update(cx, |buffer, cx| buffer.edit([1..3], "XXX\n", cx)); + buffer.update(cx, |buffer, cx| buffer.edit([(1..3, "XXX\n")], cx)); let snapshot = multibuffer.read(cx).snapshot(cx); assert_eq!(snapshot.text(), buffer.read(cx).text()); @@ -2973,11 +2988,11 @@ mod tests { let snapshot = multibuffer.read(cx).snapshot(cx); assert_eq!(snapshot.text(), "a"); - guest_buffer.update(cx, |buffer, cx| buffer.edit([1..1], "b", cx)); + guest_buffer.update(cx, |buffer, cx| buffer.edit([(1..1, "b")], cx)); let snapshot = multibuffer.read(cx).snapshot(cx); assert_eq!(snapshot.text(), "ab"); - guest_buffer.update(cx, |buffer, cx| buffer.edit([2..2], "c", cx)); + guest_buffer.update(cx, |buffer, cx| buffer.edit([(2..2, "c")], cx)); let snapshot = multibuffer.read(cx).snapshot(cx); assert_eq!(snapshot.text(), "abc"); } @@ -3091,12 +3106,12 @@ mod tests { ); buffer_1.update(cx, |buffer, cx| { + let text = "\n"; buffer.edit( [ - Point::new(0, 0)..Point::new(0, 0), - Point::new(2, 1)..Point::new(2, 3), + (Point::new(0, 0)..Point::new(0, 0), text), + (Point::new(2, 1)..Point::new(2, 3), text), ], - "\n", cx, ); }); @@ -3234,8 +3249,8 @@ mod tests { let multibuffer = cx.add_model(|cx| MultiBuffer::singleton(buffer.clone(), cx)); let old_snapshot = multibuffer.read(cx).snapshot(cx); buffer.update(cx, |buffer, cx| { - buffer.edit([0..0], "X", cx); - buffer.edit([5..5], "Y", cx); + buffer.edit([(0..0, "X")], cx); + buffer.edit([(5..5, "Y")], cx); }); let new_snapshot = multibuffer.read(cx).snapshot(cx); @@ -3268,12 +3283,12 @@ mod tests { assert_eq!(Anchor::max().to_offset(&old_snapshot), 10); buffer_1.update(cx, |buffer, cx| { - buffer.edit([0..0], "W", cx); - buffer.edit([5..5], "X", cx); + buffer.edit([(0..0, "W")], cx); + buffer.edit([(5..5, "X")], cx); }); buffer_2.update(cx, |buffer, cx| { - buffer.edit([0..0], "Y", cx); - buffer.edit([6..0], "Z", cx); + buffer.edit([(0..0, "Y")], cx); + buffer.edit([(6..0, "Z")], cx); }); let new_snapshot = multibuffer.read(cx).snapshot(cx); @@ -3302,7 +3317,7 @@ mod tests { // Create an insertion id in buffer 1 that doesn't exist in buffer 2. // Add an excerpt from buffer 1 that spans this new insertion. - buffer_1.update(cx, |buffer, cx| buffer.edit([4..4], "123", cx)); + buffer_1.update(cx, |buffer, cx| buffer.edit([(4..4, "123")], cx)); let excerpt_id_1 = multibuffer.update(cx, |multibuffer, cx| { multibuffer .push_excerpts(buffer_1.clone(), [0..7], cx) @@ -3823,18 +3838,16 @@ mod tests { multibuffer.start_transaction_at(now, cx); multibuffer.edit( [ - Point::new(0, 0)..Point::new(0, 0), - Point::new(1, 0)..Point::new(1, 0), + (Point::new(0, 0)..Point::new(0, 0), "A"), + (Point::new(1, 0)..Point::new(1, 0), "A"), ], - "A", cx, ); multibuffer.edit( [ - Point::new(0, 1)..Point::new(0, 1), - Point::new(1, 1)..Point::new(1, 1), + (Point::new(0, 1)..Point::new(0, 1), "B"), + (Point::new(1, 1)..Point::new(1, 1), "B"), ], - "B", cx, ); multibuffer.end_transaction_at(now, cx); @@ -3843,19 +3856,19 @@ mod tests { // Edit buffer 1 through the multibuffer now += 2 * group_interval; multibuffer.start_transaction_at(now, cx); - multibuffer.edit([2..2], "C", cx); + multibuffer.edit([(2..2, "C")], cx); multibuffer.end_transaction_at(now, cx); assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678"); // Edit buffer 1 independently buffer_1.update(cx, |buffer_1, cx| { buffer_1.start_transaction_at(now); - buffer_1.edit([3..3], "D", cx); + buffer_1.edit([(3..3, "D")], cx); buffer_1.end_transaction_at(now, cx); now += 2 * group_interval; buffer_1.start_transaction_at(now); - buffer_1.edit([4..4], "E", cx); + buffer_1.edit([(4..4, "E")], cx); buffer_1.end_transaction_at(now, cx); }); assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678"); diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 3a1c0f0667..647481367a 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -880,14 +880,18 @@ impl Buffer { if column > current_column { let offset = Point::new(row, 0).to_offset(&*self); self.edit( - [offset..offset], - " ".repeat((column - current_column) as usize), + [( + 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)], - "", + [( + Point::new(row, 0)..Point::new(row, current_column - column), + "", + )], cx, ); } @@ -925,13 +929,15 @@ impl Buffer { match tag { ChangeTag::Equal => offset += len, ChangeTag::Delete => { - self.edit([range], "", cx); + self.edit([(range, "")], cx); } ChangeTag::Insert => { self.edit( - [offset..offset], - &diff.new_text - [range.start - diff.start_offset..range.end - diff.start_offset], + [( + offset..offset, + &diff.new_text[range.start - diff.start_offset + ..range.end - diff.start_offset], + )], cx, ); offset += len; @@ -1049,71 +1055,68 @@ impl Buffer { pub fn set_text(&mut self, text: T, cx: &mut ModelContext) -> Option where - T: Into, + T: Into>, { - self.edit_internal([0..self.len()], text, None, cx) + self.edit_internal([(0..self.len(), text)], None, cx) } pub fn edit( &mut self, - ranges_iter: I, - new_text: T, + edits_iter: I, cx: &mut ModelContext, ) -> Option where - I: IntoIterator>, + I: IntoIterator, T)>, S: ToOffset, - T: Into, + T: Into>, { - self.edit_internal(ranges_iter, new_text, None, cx) + self.edit_internal(edits_iter, None, cx) } pub fn edit_with_autoindent( &mut self, - ranges_iter: I, - new_text: T, + edits_iter: I, indent_size: u32, cx: &mut ModelContext, ) -> Option where - I: IntoIterator>, + I: IntoIterator, T)>, S: ToOffset, - T: Into, + T: Into>, { - self.edit_internal(ranges_iter, new_text, Some(indent_size), cx) + self.edit_internal(edits_iter, Some(indent_size), cx) } pub fn edit_internal( &mut self, - ranges_iter: I, - new_text: T, + edits_iter: I, autoindent_size: Option, cx: &mut ModelContext, ) -> Option where - I: IntoIterator>, + I: IntoIterator, T)>, S: ToOffset, - T: Into, + T: Into>, { - let new_text = new_text.into(); - - // Skip invalid ranges and coalesce contiguous ones. - let mut ranges: Vec> = Vec::new(); - for range in ranges_iter { + // Skip invalid edits and coalesce contiguous ones. + let mut edits: Vec<(Range, Arc)> = Vec::new(); + for (range, new_text) in edits_iter { let range = range.start.to_offset(self)..range.end.to_offset(self); + let new_text = new_text.into(); if !new_text.is_empty() || !range.is_empty() { - if let Some(prev_range) = ranges.last_mut() { + if let Some((prev_range, prev_text)) = edits.last_mut() { if prev_range.end >= range.start { prev_range.end = cmp::max(prev_range.end, range.end); + *prev_text = format!("{prev_text}{new_text}").into(); } else { - ranges.push(range); + edits.push((range, new_text)); } } else { - ranges.push(range); + edits.push((range, new_text)); } } } - if ranges.is_empty() { + if edits.is_empty() { return None; } @@ -1125,9 +1128,9 @@ impl Buffer { .and_then(|_| autoindent_size) .map(|autoindent_size| { let before_edit = self.snapshot(); - let edited = ranges + let edited = edits .iter() - .filter_map(|range| { + .filter_map(|(range, new_text)| { let start = range.start.to_point(self); if new_text.starts_with('\n') && start.column == self.line_len(start.row) @@ -1141,30 +1144,29 @@ impl Buffer { (before_edit, edited, autoindent_size) }); - let first_newline_ix = new_text.find('\n'); - let new_text_len = new_text.len(); - - let edit = self.text.edit(ranges.iter().cloned(), new_text); - let edit_id = edit.local_timestamp(); + let edit_operation = self.text.edit(edits.iter().cloned()); + let edit_id = edit_operation.local_timestamp(); if let Some((before_edit, edited, size)) = autoindent_request { - let mut inserted = None; - if let Some(first_newline_ix) = first_newline_ix { - let mut delta = 0isize; - inserted = Some( - ranges - .iter() - .map(|range| { - let start = - (delta + range.start as isize) as usize + first_newline_ix + 1; - let end = (delta + range.start as isize) as usize + new_text_len; - delta += - (range.end as isize - range.start as isize) + new_text_len as isize; - self.anchor_before(start)..self.anchor_after(end) - }) - .collect(), - ); - } + let mut delta = 0isize; + + let inserted_ranges = edits + .into_iter() + .filter_map(|(range, new_text)| { + let first_newline_ix = new_text.find('\n')?; + let new_text_len = new_text.len(); + let start = (delta + range.start as isize) as usize + first_newline_ix + 1; + let end = (delta + range.start as isize) as usize + new_text_len; + delta += new_text_len as isize - (range.end as isize - range.start as isize); + Some(self.anchor_before(start)..self.anchor_after(end)) + }) + .collect::>>(); + + let inserted = if inserted_ranges.is_empty() { + None + } else { + Some(inserted_ranges) + }; self.autoindent_requests.push(Arc::new(AutoindentRequest { before_edit, @@ -1175,7 +1177,7 @@ impl Buffer { } self.end_transaction(cx); - self.send_operation(Operation::Buffer(edit), cx); + self.send_operation(Operation::Buffer(edit_operation), cx); Some(edit_id) } @@ -1433,25 +1435,26 @@ impl Buffer { ) where T: rand::Rng, { - let mut old_ranges: Vec> = Vec::new(); + let mut edits: Vec<(Range, String)> = Vec::new(); + let mut last_end = None; for _ in 0..old_range_count { - let last_end = old_ranges.last().map_or(0, |last_range| last_range.end + 1); - if last_end > self.len() { + if last_end.map_or(false, |last_end| last_end >= self.len()) { break; } - old_ranges.push(self.text.random_byte_range(last_end, rng)); + + let new_start = last_end.map_or(0, |last_end| last_end + 1); + let range = self.random_byte_range(new_start, rng); + last_end = Some(range.end); + + let new_text_len = rng.gen_range(0..10); + let new_text: String = crate::random_char_iter::RandomCharIter::new(&mut *rng) + .take(new_text_len) + .collect(); + + edits.push((range, new_text)); } - let new_text_len = rng.gen_range(0..10); - let new_text: String = crate::random_char_iter::RandomCharIter::new(&mut *rng) - .take(new_text_len) - .collect(); - log::info!( - "mutating buffer {} at {:?}: {:?}", - self.replica_id(), - old_ranges, - new_text - ); - self.edit(old_ranges.iter().cloned(), new_text.as_str(), cx); + log::info!("mutating buffer {} with {:?}", self.replica_id(), edits); + self.edit(edits, cx); } pub fn randomly_undo_redo(&mut self, rng: &mut impl rand::Rng, cx: &mut ModelContext) { diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 406d0d751e..312b192cb9 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -78,7 +78,11 @@ pub fn serialize_edit_operation(operation: &EditOperation) -> proto::operation:: lamport_timestamp: operation.timestamp.lamport, version: serialize_version(&operation.version), ranges: operation.ranges.iter().map(serialize_range).collect(), - new_text: operation.new_text.clone(), + new_text: operation + .new_text + .iter() + .map(|text| text.to_string()) + .collect(), } } @@ -244,7 +248,7 @@ pub fn deserialize_edit_operation(edit: proto::operation::Edit) -> EditOperation }, version: deserialize_version(edit.version), ranges: edit.ranges.into_iter().map(deserialize_range).collect(), - new_text: edit.new_text, + new_text: edit.new_text.into_iter().map(Arc::from).collect(), } } diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 57f0e6bbe0..623d7a9bb3 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -93,7 +93,7 @@ fn test_edit_events(cx: &mut gpui::MutableAppContext) { // An edit emits an edited event, followed by a dirtied event, // since the buffer was previously in a clean state. - buffer.edit(Some(2..4), "XYZ", cx); + buffer.edit([(2..4, "XYZ")], cx); // An empty transaction does not emit any events. buffer.start_transaction(); @@ -102,8 +102,8 @@ fn test_edit_events(cx: &mut gpui::MutableAppContext) { // A transaction containing two edits emits one edited event. now += Duration::from_secs(1); buffer.start_transaction_at(now); - buffer.edit(Some(5..5), "u", cx); - buffer.edit(Some(6..6), "w", cx); + buffer.edit([(5..5, "u")], cx); + buffer.edit([(6..6, "w")], cx); buffer.end_transaction_at(now, cx); // Undoing a transaction emits one edited event. @@ -178,11 +178,11 @@ async fn test_reparse(cx: &mut gpui::TestAppContext) { buf.start_transaction(); let offset = buf.text().find(")").unwrap(); - buf.edit(vec![offset..offset], "b: C", cx); + buf.edit([(offset..offset, "b: C")], cx); assert!(!buf.is_parsing()); let offset = buf.text().find("}").unwrap(); - buf.edit(vec![offset..offset], " d; ", cx); + buf.edit([(offset..offset, " d; ")], cx); assert!(!buf.is_parsing()); buf.end_transaction(cx); @@ -207,19 +207,19 @@ async fn test_reparse(cx: &mut gpui::TestAppContext) { // * add a turbofish to the method call buffer.update(cx, |buf, cx| { let offset = buf.text().find(";").unwrap(); - buf.edit(vec![offset..offset], ".e", cx); + buf.edit([(offset..offset, ".e")], cx); assert_eq!(buf.text(), "fn a(b: C) { d.e; }"); assert!(buf.is_parsing()); }); buffer.update(cx, |buf, cx| { let offset = buf.text().find(";").unwrap(); - buf.edit(vec![offset..offset], "(f)", cx); + buf.edit([(offset..offset, "(f)")], cx); assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }"); assert!(buf.is_parsing()); }); buffer.update(cx, |buf, cx| { let offset = buf.text().find("(f)").unwrap(); - buf.edit(vec![offset..offset], "::", cx); + buf.edit([(offset..offset, "::")], cx); assert_eq!(buf.text(), "fn a(b: C) { d.e::(f); }"); assert!(buf.is_parsing()); }); @@ -576,13 +576,13 @@ fn test_edit_with_autoindent(cx: &mut MutableAppContext) { let text = "fn a() {}"; let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); - buffer.edit_with_autoindent([8..8], "\n\n", 4, cx); + buffer.edit_with_autoindent([(8..8, "\n\n")], 4, cx); assert_eq!(buffer.text(), "fn a() {\n \n}"); - buffer.edit_with_autoindent([Point::new(1, 4)..Point::new(1, 4)], "b()\n", 4, cx); + buffer.edit_with_autoindent([(Point::new(1, 4)..Point::new(1, 4), "b()\n")], 4, cx); assert_eq!(buffer.text(), "fn a() {\n b()\n \n}"); - buffer.edit_with_autoindent([Point::new(2, 4)..Point::new(2, 4)], ".c", 4, cx); + buffer.edit_with_autoindent([(Point::new(2, 4)..Point::new(2, 4), ".c")], 4, cx); assert_eq!(buffer.text(), "fn a() {\n b()\n .c\n}"); buffer @@ -605,8 +605,10 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta // Lines 2 and 3 don't match the indentation suggestion. When editing these lines, // their indentation is not adjusted. buffer.edit_with_autoindent( - [empty(Point::new(1, 1)), empty(Point::new(2, 1))], - "()", + [ + (empty(Point::new(1, 1)), "()"), + (empty(Point::new(2, 1)), "()"), + ], 4, cx, ); @@ -624,8 +626,10 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta // When appending new content after these lines, the indentation is based on the // preceding lines' actual indentation. buffer.edit_with_autoindent( - [empty(Point::new(1, 1)), empty(Point::new(2, 1))], - "\n.f\n.g", + [ + (empty(Point::new(1, 1)), "\n.f\n.g"), + (empty(Point::new(2, 1)), "\n.f\n.g"), + ], 4, cx, ); @@ -657,7 +661,7 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppConte let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); - buffer.edit_with_autoindent([5..5], "\nb", 4, cx); + buffer.edit_with_autoindent([(5..5, "\nb")], 4, cx); assert_eq!( buffer.text(), " @@ -669,7 +673,7 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppConte // The indentation suggestion changed because `@end` node (a close paren) // is now at the beginning of the line. - buffer.edit_with_autoindent([Point::new(1, 4)..Point::new(1, 5)], "", 4, cx); + buffer.edit_with_autoindent([(Point::new(1, 4)..Point::new(1, 5), "")], 4, cx); assert_eq!( buffer.text(), " @@ -683,24 +687,35 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppConte }); } +#[gpui::test] +fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut MutableAppContext) { + cx.add_model(|cx| { + let text = "a\nb"; + let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); + buffer.edit_with_autoindent([(0..1, "\n"), (2..3, "\n")], 4, cx); + assert_eq!(buffer.text(), "\n\n\n"); + buffer + }); +} + #[gpui::test] fn test_serialization(cx: &mut gpui::MutableAppContext) { let mut now = Instant::now(); let buffer1 = cx.add_model(|cx| { let mut buffer = Buffer::new(0, "abc", cx); - buffer.edit([3..3], "D", cx); + buffer.edit([(3..3, "D")], cx); now += Duration::from_secs(1); buffer.start_transaction_at(now); - buffer.edit([4..4], "E", cx); + buffer.edit([(4..4, "E")], cx); buffer.end_transaction_at(now, cx); assert_eq!(buffer.text(), "abcDE"); buffer.undo(cx); assert_eq!(buffer.text(), "abcD"); - buffer.edit([4..4], "F", cx); + buffer.edit([(4..4, "F")], cx); assert_eq!(buffer.text(), "abcDF"); buffer }); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5243afe91c..a9f24f2b61 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2234,7 +2234,7 @@ impl Project { buffer.finalize_last_transaction(); buffer.start_transaction(); for (range, text) in edits { - buffer.edit([range], text, cx); + buffer.edit([(range, text)], cx); } if buffer.end_transaction(cx).is_some() { let transaction = buffer.finalize_last_transaction().unwrap().clone(); @@ -2625,7 +2625,7 @@ impl Project { buffer.finalize_last_transaction(); buffer.start_transaction(); for (range, text) in edits { - buffer.edit([range], text, cx); + buffer.edit([(range, text)], cx); } let transaction = if buffer.end_transaction(cx).is_some() { let transaction = buffer.finalize_last_transaction().unwrap().clone(); @@ -2981,7 +2981,7 @@ impl Project { buffer.finalize_last_transaction(); buffer.start_transaction(); for (range, text) in edits { - buffer.edit([range], text, cx); + buffer.edit([(range, text)], cx); } let transaction = if buffer.end_transaction(cx).is_some() { let transaction = buffer.finalize_last_transaction().unwrap().clone(); @@ -5096,7 +5096,7 @@ mod tests { }); // Edit a buffer. The changes are reported to the language server. - rust_buffer.update(cx, |buffer, cx| buffer.edit([16..16], "2", cx)); + rust_buffer.update(cx, |buffer, cx| buffer.edit([(16..16, "2")], cx)); assert_eq!( fake_rust_server .receive_notification::() @@ -5153,8 +5153,8 @@ mod tests { }); // Changes are reported only to servers matching the buffer's language. - toml_buffer.update(cx, |buffer, cx| buffer.edit([5..5], "23", cx)); - rust_buffer2.update(cx, |buffer, cx| buffer.edit([0..0], "let x = 1;", cx)); + toml_buffer.update(cx, |buffer, cx| buffer.edit([(5..5, "23")], cx)); + rust_buffer2.update(cx, |buffer, cx| buffer.edit([(0..0, "let x = 1;")], cx)); assert_eq!( fake_rust_server .receive_notification::() @@ -5282,7 +5282,7 @@ mod tests { }); // The renamed file's version resets after changing language server. - rust_buffer2.update(cx, |buffer, cx| buffer.edit([0..0], "// ", cx)); + rust_buffer2.update(cx, |buffer, cx| buffer.edit([(0..0, "// ")], cx)); assert_eq!( fake_json_server .receive_notification::() @@ -5751,7 +5751,7 @@ mod tests { .await; // Edit the buffer, moving the content down - buffer.update(cx, |buffer, cx| buffer.edit([0..0], "\n\n", cx)); + buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "\n\n")], cx)); let change_notification_1 = fake_server .receive_notification::() .await; @@ -5922,9 +5922,9 @@ mod tests { // Keep editing the buffer and ensure disk-based diagnostics get translated according to the // changes since the last save. buffer.update(cx, |buffer, cx| { - buffer.edit(Some(Point::new(2, 0)..Point::new(2, 0)), " ", cx); - buffer.edit(Some(Point::new(2, 8)..Point::new(2, 10)), "(x: usize)", cx); - buffer.edit(Some(Point::new(3, 10)..Point::new(3, 10)), "xxx", cx); + buffer.edit([(Point::new(2, 0)..Point::new(2, 0), " ")], cx); + buffer.edit([(Point::new(2, 8)..Point::new(2, 10), "(x: usize)")], cx); + buffer.edit([(Point::new(3, 10)..Point::new(3, 10), "xxx")], cx); }); let change_notification_2 = fake_server .receive_notification::() @@ -6137,18 +6137,24 @@ mod tests { // Simulate editing the buffer after the language server computes some edits. buffer.update(cx, |buffer, cx| { buffer.edit( - [Point::new(0, 0)..Point::new(0, 0)], - "// above first function\n", + [( + Point::new(0, 0)..Point::new(0, 0), + "// above first function\n", + )], cx, ); buffer.edit( - [Point::new(2, 0)..Point::new(2, 0)], - " // inside first function\n", + [( + Point::new(2, 0)..Point::new(2, 0), + " // inside first function\n", + )], cx, ); buffer.edit( - [Point::new(6, 4)..Point::new(6, 4)], - "// inside second function ", + [( + Point::new(6, 4)..Point::new(6, 4), + "// inside second function ", + )], cx, ); @@ -6222,7 +6228,7 @@ mod tests { buffer.update(cx, |buffer, cx| { for (range, new_text) in edits { - buffer.edit([range], new_text, cx); + buffer.edit([(range, new_text)], cx); } assert_eq!( buffer.text(), @@ -6357,7 +6363,7 @@ mod tests { ); for (range, new_text) in edits { - buffer.edit([range], new_text, cx); + buffer.edit([(range, new_text)], cx); } assert_eq!( buffer.text(), @@ -6752,7 +6758,7 @@ mod tests { buffer .update(cx, |buffer, cx| { assert_eq!(buffer.text(), "the old contents"); - buffer.edit(Some(0..0), "a line of text.\n".repeat(10 * 1024), cx); + buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], cx); buffer.save(cx) }) .await @@ -6789,7 +6795,7 @@ mod tests { .unwrap(); buffer .update(cx, |buffer, cx| { - buffer.edit(Some(0..0), "a line of text.\n".repeat(10 * 1024), cx); + buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], cx); buffer.save(cx) }) .await @@ -6817,7 +6823,7 @@ mod tests { project.create_buffer("", None, cx).unwrap() }); buffer.update(cx, |buffer, cx| { - buffer.edit([0..0], "abc", cx); + buffer.edit([(0..0, "abc")], cx); assert!(buffer.is_dirty()); assert!(!buffer.has_conflict()); }); @@ -7091,7 +7097,7 @@ mod tests { assert!(!buffer.is_dirty()); assert!(events.borrow().is_empty()); - buffer.edit(vec![1..2], "", cx); + buffer.edit([(1..2, "")], cx); }); // after the first edit, the buffer is dirty, and emits a dirtied event. @@ -7112,8 +7118,8 @@ mod tests { assert_eq!(*events.borrow(), &[language::Event::Saved]); events.borrow_mut().clear(); - buffer.edit(vec![1..1], "B", cx); - buffer.edit(vec![2..2], "D", cx); + buffer.edit([(1..1, "B")], cx); + buffer.edit([(2..2, "D")], cx); }); // after editing again, the buffer is dirty, and emits another dirty event. @@ -7132,7 +7138,7 @@ mod tests { // TODO - currently, after restoring the buffer to its // previously-saved state, the is still considered dirty. - buffer.edit([1..3], "", cx); + buffer.edit([(1..3, "")], cx); assert!(buffer.text() == "ac"); assert!(buffer.is_dirty()); }); @@ -7176,7 +7182,7 @@ mod tests { worktree.flush_fs_events(&cx).await; buffer3.update(cx, |buffer, cx| { - buffer.edit(Some(0..0), "x", cx); + buffer.edit([(0..0, "x")], cx); }); events.borrow_mut().clear(); fs::remove_file(dir.path().join("file3")).unwrap(); @@ -7270,7 +7276,7 @@ mod tests { // Modify the buffer buffer.update(cx, |buffer, cx| { - buffer.edit(vec![0..0], " ", cx); + buffer.edit([(0..0, " ")], cx); assert!(buffer.is_dirty()); assert!(!buffer.has_conflict()); }); @@ -7735,7 +7741,8 @@ mod tests { .await .unwrap(); buffer_4.update(cx, |buffer, cx| { - buffer.edit([20..28, 31..43], "two::TWO", cx); + let text = "two::TWO"; + buffer.edit([(20..28, text), (31..43, text)], cx); }); assert_eq!( diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 16a59a3eff..bf18db9e2b 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -729,7 +729,7 @@ message Operation { uint32 lamport_timestamp = 3; repeated VectorClockEntry version = 4; repeated Range ranges = 5; - optional string new_text = 6; + repeated string new_text = 6; } message Undo { diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index a4b73bffde..d1f17608f2 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -268,7 +268,7 @@ impl BufferSearchBar { self.query_editor.update(cx, |query_editor, cx| { query_editor.buffer().update(cx, |query_buffer, cx| { let len = query_buffer.read(cx).len(); - query_buffer.edit([0..len], query, cx); + query_buffer.edit([(0..len, query)], cx); }); }); } diff --git a/crates/text/src/tests.rs b/crates/text/src/tests.rs index 9348ff0ba6..7994d5b8f1 100644 --- a/crates/text/src/tests.rs +++ b/crates/text/src/tests.rs @@ -20,15 +20,15 @@ fn init_logger() { fn test_edit() { let mut buffer = Buffer::new(0, 0, History::new("abc".into())); assert_eq!(buffer.text(), "abc"); - buffer.edit(vec![3..3], "def"); + buffer.edit([(3..3, "def")]); assert_eq!(buffer.text(), "abcdef"); - buffer.edit(vec![0..0], "ghi"); + buffer.edit([(0..0, "ghi")]); assert_eq!(buffer.text(), "ghiabcdef"); - buffer.edit(vec![5..5], "jkl"); + buffer.edit([(5..5, "jkl")]); assert_eq!(buffer.text(), "ghiabjklcdef"); - buffer.edit(vec![6..7], ""); + buffer.edit([(6..7, "")]); assert_eq!(buffer.text(), "ghiabjlcdef"); - buffer.edit(vec![4..9], "mno"); + buffer.edit([(4..9, "mno")]); assert_eq!(buffer.text(), "ghiamnoef"); } @@ -52,8 +52,8 @@ fn test_random_edits(mut rng: StdRng) { ); for _i in 0..operations { - let (old_ranges, new_text, _) = buffer.randomly_edit(&mut rng, 5); - for old_range in old_ranges.iter().rev() { + let (edits, _) = buffer.randomly_edit(&mut rng, 5); + for (old_range, new_text) in edits.iter().rev() { reference_string.replace_range(old_range.clone(), &new_text); } assert_eq!(buffer.text(), reference_string); @@ -151,10 +151,10 @@ fn test_random_edits(mut rng: StdRng) { #[test] fn test_line_len() { let mut buffer = Buffer::new(0, 0, History::new("".into())); - buffer.edit(vec![0..0], "abcd\nefg\nhij"); - buffer.edit(vec![12..12], "kl\nmno"); - buffer.edit(vec![18..18], "\npqrs\n"); - buffer.edit(vec![18..21], "\nPQ"); + buffer.edit([(0..0, "abcd\nefg\nhij")]); + buffer.edit([(12..12, "kl\nmno")]); + buffer.edit([(18..18, "\npqrs\n")]); + buffer.edit([(18..21, "\nPQ")]); assert_eq!(buffer.line_len(0), 4); assert_eq!(buffer.line_len(1), 3); @@ -281,10 +281,10 @@ fn test_text_summary_for_range() { #[test] fn test_chars_at() { let mut buffer = Buffer::new(0, 0, History::new("".into())); - buffer.edit(vec![0..0], "abcd\nefgh\nij"); - buffer.edit(vec![12..12], "kl\nmno"); - buffer.edit(vec![18..18], "\npqrs"); - buffer.edit(vec![18..21], "\nPQ"); + buffer.edit([(0..0, "abcd\nefgh\nij")]); + buffer.edit([(12..12, "kl\nmno")]); + buffer.edit([(18..18, "\npqrs")]); + buffer.edit([(18..21, "\nPQ")]); let chars = buffer.chars_at(Point::new(0, 0)); assert_eq!(chars.collect::(), "abcd\nefgh\nijkl\nmno\nPQrs"); @@ -303,8 +303,8 @@ fn test_chars_at() { // Regression test: let mut buffer = Buffer::new(0, 0, History::new("".into())); - buffer.edit(vec![0..0], "[workspace]\nmembers = [\n \"xray_core\",\n \"xray_server\",\n \"xray_cli\",\n \"xray_wasm\",\n]\n"); - buffer.edit(vec![60..60], "\n"); + buffer.edit([(0..0, "[workspace]\nmembers = [\n \"xray_core\",\n \"xray_server\",\n \"xray_cli\",\n \"xray_wasm\",\n]\n")]); + buffer.edit([(60..60, "\n")]); let chars = buffer.chars_at(Point::new(6, 0)); assert_eq!(chars.collect::(), " \"xray_wasm\",\n]\n"); @@ -313,32 +313,32 @@ fn test_chars_at() { #[test] fn test_anchors() { let mut buffer = Buffer::new(0, 0, History::new("".into())); - buffer.edit(vec![0..0], "abc"); + buffer.edit([(0..0, "abc")]); let left_anchor = buffer.anchor_before(2); let right_anchor = buffer.anchor_after(2); - buffer.edit(vec![1..1], "def\n"); + buffer.edit([(1..1, "def\n")]); assert_eq!(buffer.text(), "adef\nbc"); assert_eq!(left_anchor.to_offset(&buffer), 6); assert_eq!(right_anchor.to_offset(&buffer), 6); assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 }); - buffer.edit(vec![2..3], ""); + buffer.edit([(2..3, "")]); assert_eq!(buffer.text(), "adf\nbc"); assert_eq!(left_anchor.to_offset(&buffer), 5); assert_eq!(right_anchor.to_offset(&buffer), 5); assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 }); - buffer.edit(vec![5..5], "ghi\n"); + buffer.edit([(5..5, "ghi\n")]); assert_eq!(buffer.text(), "adf\nbghi\nc"); assert_eq!(left_anchor.to_offset(&buffer), 5); assert_eq!(right_anchor.to_offset(&buffer), 9); assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); assert_eq!(right_anchor.to_point(&buffer), Point { row: 2, column: 0 }); - buffer.edit(vec![7..9], ""); + buffer.edit([(7..9, "")]); assert_eq!(buffer.text(), "adf\nbghc"); assert_eq!(left_anchor.to_offset(&buffer), 5); assert_eq!(right_anchor.to_offset(&buffer), 7); @@ -434,7 +434,7 @@ fn test_anchors_at_start_and_end() { let before_start_anchor = buffer.anchor_before(0); let after_end_anchor = buffer.anchor_after(0); - buffer.edit(vec![0..0], "abc"); + buffer.edit([(0..0, "abc")]); assert_eq!(buffer.text(), "abc"); assert_eq!(before_start_anchor.to_offset(&buffer), 0); assert_eq!(after_end_anchor.to_offset(&buffer), 3); @@ -442,8 +442,8 @@ fn test_anchors_at_start_and_end() { let after_start_anchor = buffer.anchor_after(0); let before_end_anchor = buffer.anchor_before(3); - buffer.edit(vec![3..3], "def"); - buffer.edit(vec![0..0], "ghi"); + buffer.edit([(3..3, "def")]); + buffer.edit([(0..0, "ghi")]); assert_eq!(buffer.text(), "ghiabcdef"); assert_eq!(before_start_anchor.to_offset(&buffer), 0); assert_eq!(after_start_anchor.to_offset(&buffer), 3); @@ -457,9 +457,9 @@ fn test_undo_redo() { // Set group interval to zero so as to not group edits in the undo stack. buffer.history.group_interval = Duration::from_secs(0); - buffer.edit(vec![1..1], "abx"); - buffer.edit(vec![3..4], "yzef"); - buffer.edit(vec![3..5], "cd"); + buffer.edit([(1..1, "abx")]); + buffer.edit([(3..4, "yzef")]); + buffer.edit([(3..5, "cd")]); assert_eq!(buffer.text(), "1abcdef234"); let entries = buffer.history.undo_stack.clone(); @@ -493,19 +493,19 @@ fn test_history() { let mut buffer = Buffer::new(0, 0, History::new("123456".into())); buffer.start_transaction_at(now); - buffer.edit(vec![2..4], "cd"); + buffer.edit([(2..4, "cd")]); buffer.end_transaction_at(now); assert_eq!(buffer.text(), "12cd56"); buffer.start_transaction_at(now); - buffer.edit(vec![4..5], "e"); + buffer.edit([(4..5, "e")]); buffer.end_transaction_at(now).unwrap(); assert_eq!(buffer.text(), "12cde6"); now += buffer.history.group_interval + Duration::from_millis(1); buffer.start_transaction_at(now); - buffer.edit(vec![0..1], "a"); - buffer.edit(vec![1..1], "b"); + buffer.edit([(0..1, "a")]); + buffer.edit([(1..1, "b")]); buffer.end_transaction_at(now).unwrap(); assert_eq!(buffer.text(), "ab2cde6"); @@ -537,19 +537,19 @@ fn test_finalize_last_transaction() { let mut buffer = Buffer::new(0, 0, History::new("123456".into())); buffer.start_transaction_at(now); - buffer.edit(vec![2..4], "cd"); + buffer.edit([(2..4, "cd")]); buffer.end_transaction_at(now); assert_eq!(buffer.text(), "12cd56"); buffer.finalize_last_transaction(); buffer.start_transaction_at(now); - buffer.edit(vec![4..5], "e"); + buffer.edit([(4..5, "e")]); buffer.end_transaction_at(now).unwrap(); assert_eq!(buffer.text(), "12cde6"); buffer.start_transaction_at(now); - buffer.edit(vec![0..1], "a"); - buffer.edit(vec![1..1], "b"); + buffer.edit([(0..1, "a")]); + buffer.edit([(1..1, "b")]); buffer.end_transaction_at(now).unwrap(); assert_eq!(buffer.text(), "ab2cde6"); @@ -572,8 +572,8 @@ fn test_edited_ranges_for_transaction() { let mut buffer = Buffer::new(0, 0, History::new("1234567".into())); buffer.start_transaction_at(now); - buffer.edit(vec![2..4], "cd"); - buffer.edit(vec![6..6], "efg"); + buffer.edit([(2..4, "cd")]); + buffer.edit([(6..6, "efg")]); buffer.end_transaction_at(now); assert_eq!(buffer.text(), "12cd56efg7"); @@ -585,7 +585,7 @@ fn test_edited_ranges_for_transaction() { [2..4, 6..9] ); - buffer.edit(vec![5..5], "hijk"); + buffer.edit([(5..5, "hijk")]); assert_eq!(buffer.text(), "12cd5hijk6efg7"); assert_eq!( buffer @@ -594,7 +594,7 @@ fn test_edited_ranges_for_transaction() { [2..4, 10..13] ); - buffer.edit(vec![4..4], "l"); + buffer.edit([(4..4, "l")]); assert_eq!(buffer.text(), "12cdl5hijk6efg7"); assert_eq!( buffer @@ -612,11 +612,11 @@ fn test_concurrent_edits() { let mut buffer2 = Buffer::new(2, 0, History::new(text.into())); let mut buffer3 = Buffer::new(3, 0, History::new(text.into())); - let buf1_op = buffer1.edit(vec![1..2], "12"); + let buf1_op = buffer1.edit([(1..2, "12")]); assert_eq!(buffer1.text(), "a12cdef"); - let buf2_op = buffer2.edit(vec![3..4], "34"); + let buf2_op = buffer2.edit([(3..4, "34")]); assert_eq!(buffer2.text(), "abc34ef"); - let buf3_op = buffer3.edit(vec![5..6], "56"); + let buf3_op = buffer3.edit([(5..6, "56")]); assert_eq!(buffer3.text(), "abcde56"); buffer1.apply_op(buf2_op.clone()).unwrap(); @@ -665,7 +665,7 @@ fn test_random_concurrent_edits(mut rng: StdRng) { let buffer = &mut buffers[replica_index]; match rng.gen_range(0..=100) { 0..=50 if mutation_count != 0 => { - let op = buffer.randomly_edit(&mut rng, 5).2; + let op = buffer.randomly_edit(&mut rng, 5).1; network.broadcast(buffer.replica_id, vec![op]); log::info!("buffer {} text: {:?}", buffer.replica_id, buffer.text()); mutation_count -= 1; diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index c3b7718711..7bf8ee208f 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -91,26 +91,34 @@ impl HistoryEntry { self.transaction.id } - fn push_edit(&mut self, edit: &EditOperation) { - self.transaction.edit_ids.push(edit.timestamp.local()); - self.transaction.end.observe(edit.timestamp.local()); + fn push_edit(&mut self, edit_operation: &EditOperation) { + self.transaction + .edit_ids + .push(edit_operation.timestamp.local()); + self.transaction + .end + .observe(edit_operation.timestamp.local()); - let mut other_ranges = edit.ranges.iter().peekable(); + let mut edits = edit_operation + .ranges + .iter() + .zip(edit_operation.new_text.iter()) + .peekable(); let mut new_ranges = Vec::new(); - let insertion_len = edit.new_text.as_ref().map_or(0, |t| t.len()); let mut delta = 0; for mut self_range in self.transaction.ranges.iter().cloned() { self_range.start += delta; self_range.end += delta; - while let Some(other_range) = other_ranges.peek() { + while let Some((other_range, new_text)) = edits.peek() { + let insertion_len = new_text.len(); let mut other_range = (*other_range).clone(); other_range.start += delta; other_range.end += delta; if other_range.start <= self_range.end { - other_ranges.next().unwrap(); + edits.next().unwrap(); delta += insertion_len; if other_range.end < self_range.start { @@ -129,7 +137,8 @@ impl HistoryEntry { new_ranges.push(self_range); } - for other_range in other_ranges { + for (other_range, new_text) in edits { + let insertion_len = new_text.len(); new_ranges.push(other_range.start + delta..other_range.end + delta + insertion_len); delta += insertion_len; } @@ -515,7 +524,7 @@ pub struct EditOperation { pub timestamp: InsertionTimestamp, pub version: clock::Global, pub ranges: Vec>, - pub new_text: Option, + pub new_text: Vec>, } #[derive(Clone, Debug, Eq, PartialEq)] @@ -606,20 +615,16 @@ impl Buffer { self.history.group_interval } - pub fn edit(&mut self, ranges: R, new_text: T) -> Operation + pub fn edit(&mut self, edits: R) -> Operation where R: IntoIterator, - I: ExactSizeIterator>, + I: ExactSizeIterator, T)>, S: ToOffset, - T: Into, + T: Into>, { - let new_text = new_text.into(); - let new_text_len = new_text.len(); - let new_text = if new_text_len > 0 { - Some(new_text) - } else { - None - }; + let edits = edits + .into_iter() + .map(|(range, new_text)| (range, new_text.into())); self.start_transaction(); let timestamp = InsertionTimestamp { @@ -627,8 +632,7 @@ impl Buffer { local: self.local_clock.tick().value, lamport: self.lamport_clock.tick().value, }; - let operation = - Operation::Edit(self.apply_local_edit(ranges.into_iter(), new_text, timestamp)); + let operation = Operation::Edit(self.apply_local_edit(edits, timestamp)); self.history.push(operation.clone()); self.history.push_undo(operation.local_timestamp()); @@ -637,35 +641,35 @@ impl Buffer { operation } - fn apply_local_edit( + fn apply_local_edit>>( &mut self, - ranges: impl ExactSizeIterator>, - new_text: Option, + edits: impl ExactSizeIterator, T)>, timestamp: InsertionTimestamp, ) -> EditOperation { - let mut edits = Patch::default(); + let mut edits_patch = Patch::default(); let mut edit_op = EditOperation { timestamp, version: self.version(), - ranges: Vec::with_capacity(ranges.len()), - new_text: None, + ranges: Vec::with_capacity(edits.len()), + new_text: Vec::with_capacity(edits.len()), }; let mut new_insertions = Vec::new(); let mut insertion_offset = 0; - let mut ranges = ranges - .map(|range| range.start.to_offset(&*self)..range.end.to_offset(&*self)) + let mut ranges = edits + .map(|(range, new_text)| (range.to_offset(&*self), new_text)) .peekable(); let mut new_ropes = RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0)); let mut old_fragments = self.fragments.cursor::(); let mut new_fragments = - old_fragments.slice(&ranges.peek().unwrap().start, Bias::Right, &None); + old_fragments.slice(&ranges.peek().unwrap().0.start, Bias::Right, &None); new_ropes.push_tree(new_fragments.summary().text); let mut fragment_start = old_fragments.start().visible; - for range in ranges { + for (range, new_text) in ranges { + let new_text = new_text.into(); let fragment_end = old_fragments.end(&None).visible; // If the current fragment ends before this range, then jump ahead to the first fragment @@ -706,9 +710,9 @@ impl Buffer { } // Insert the new text before any existing fragments within the range. - if let Some(new_text) = new_text.as_deref() { + if !new_text.is_empty() { let new_start = new_fragments.summary().text.visible; - edits.push(Edit { + edits_patch.push(Edit { old: fragment_start..fragment_start, new: new_start..new_start + new_text.len(), }); @@ -727,7 +731,7 @@ impl Buffer { visible: true, }; new_insertions.push(InsertionFragment::insert_new(&fragment)); - new_ropes.push_str(new_text); + new_ropes.push_str(new_text.as_ref()); new_fragments.push(fragment, &None); insertion_offset += new_text.len(); } @@ -750,7 +754,7 @@ impl Buffer { if intersection.len > 0 { if fragment.visible && !intersection.visible { let new_start = new_fragments.summary().text.visible; - edits.push(Edit { + edits_patch.push(Edit { old: fragment_start..intersection_end, new: new_start..new_start, }); @@ -767,6 +771,7 @@ impl Buffer { let full_range_end = FullOffset(range.end + old_fragments.start().deleted); edit_op.ranges.push(full_range_start..full_range_end); + edit_op.new_text.push(new_text); } // If the current fragment has been partially consumed, then consume the rest of it @@ -794,8 +799,7 @@ impl Buffer { self.snapshot.insertions.edit(new_insertions, &()); self.snapshot.visible_text = visible_text; self.snapshot.deleted_text = deleted_text; - self.subscriptions.publish_mut(&edits); - edit_op.new_text = new_text; + self.subscriptions.publish_mut(&edits_patch); edit_op } @@ -822,7 +826,7 @@ impl Buffer { self.apply_remote_edit( &edit.version, &edit.ranges, - edit.new_text.as_deref(), + &edit.new_text, edit.timestamp, ); self.snapshot.version.observe(edit.timestamp.local()); @@ -852,14 +856,15 @@ impl Buffer { &mut self, version: &clock::Global, ranges: &[Range], - new_text: Option<&str>, + new_text: &[Arc], timestamp: InsertionTimestamp, ) { if ranges.is_empty() { return; } - let mut edits = Patch::default(); + let edits = ranges.into_iter().zip(new_text.into_iter()); + let mut edits_patch = Patch::default(); let cx = Some(version.clone()); let mut new_insertions = Vec::new(); let mut insertion_offset = 0; @@ -874,7 +879,7 @@ impl Buffer { new_ropes.push_tree(new_fragments.summary().text); let mut fragment_start = old_fragments.start().0.full_offset(); - for range in ranges { + for (range, new_text) in edits { let fragment_end = old_fragments.end(&cx).0.full_offset(); // If the current fragment ends before this range, then jump ahead to the first fragment @@ -944,13 +949,13 @@ impl Buffer { } // Insert the new text before any existing fragments within the range. - if let Some(new_text) = new_text { + if !new_text.is_empty() { let mut old_start = old_fragments.start().1; if old_fragments.item().map_or(false, |f| f.visible) { old_start += fragment_start.0 - old_fragments.start().0.full_offset().0; } let new_start = new_fragments.summary().text.visible; - edits.push(Edit { + edits_patch.push(Edit { old: old_start..old_start, new: new_start..new_start + new_text.len(), }); @@ -995,7 +1000,7 @@ impl Buffer { let old_start = old_fragments.start().1 + (fragment_start.0 - old_fragments.start().0.full_offset().0); let new_start = new_fragments.summary().text.visible; - edits.push(Edit { + edits_patch.push(Edit { old: old_start..old_start + intersection.len, new: new_start..new_start, }); @@ -1036,7 +1041,7 @@ impl Buffer { self.snapshot.visible_text = visible_text; self.snapshot.deleted_text = deleted_text; self.snapshot.insertions.edit(new_insertions, &()); - self.subscriptions.publish_mut(&edits); + self.subscriptions.publish_mut(&edits_patch) } fn apply_undo(&mut self, undo: &UndoOperation) -> Result<()> { @@ -1381,7 +1386,11 @@ impl Buffer { &(), ) .unwrap(); - assert_eq!(insertion_fragment.fragment_id, fragment.id); + assert_eq!( + insertion_fragment.fragment_id, fragment.id, + "fragment: {:?}\ninsertion: {:?}", + fragment, insertion_fragment + ); } let mut cursor = self.snapshot.fragments.cursor::>(); @@ -1416,31 +1425,32 @@ impl Buffer { pub fn randomly_edit( &mut self, rng: &mut T, - old_range_count: usize, - ) -> (Vec>, String, Operation) + edit_count: usize, + ) -> (Vec<(Range, Arc)>, Operation) where T: rand::Rng, { - let mut old_ranges: Vec> = Vec::new(); - for _ in 0..old_range_count { - let last_end = old_ranges.last().map_or(0, |last_range| last_range.end + 1); - if last_end > self.len() { + let mut edits: Vec<(Range, Arc)> = Vec::new(); + let mut last_end = None; + for _ in 0..edit_count { + if last_end.map_or(false, |last_end| last_end >= self.len()) { break; } - old_ranges.push(self.random_byte_range(last_end, rng)); + let new_start = last_end.map_or(0, |last_end| last_end + 1); + let range = self.random_byte_range(new_start, rng); + last_end = Some(range.end); + + let new_text_len = rng.gen_range(0..10); + let new_text: String = crate::random_char_iter::RandomCharIter::new(&mut *rng) + .take(new_text_len) + .collect(); + + edits.push((range, new_text.into())); } - let new_text_len = rng.gen_range(0..10); - let new_text: String = crate::random_char_iter::RandomCharIter::new(&mut *rng) - .take(new_text_len) - .collect(); - log::info!( - "mutating buffer {} at {:?}: {:?}", - self.replica_id, - old_ranges, - new_text - ); - let op = self.edit(old_ranges.iter().cloned(), new_text.as_str()); - (old_ranges, new_text, op) + + log::info!("mutating buffer {} with {:?}", self.replica_id, edits); + let op = self.edit(edits.iter().cloned()); + (edits, op) } pub fn randomly_undo_redo(&mut self, rng: &mut impl rand::Rng) -> Vec { diff --git a/crates/vim/src/normal/delete.rs b/crates/vim/src/normal/delete.rs index 8516b5ee94..c90c1dbafd 100644 --- a/crates/vim/src/normal/delete.rs +++ b/crates/vim/src/normal/delete.rs @@ -1,29 +1,31 @@ use crate::{motion::Motion, Vim}; +use collections::HashMap; use editor::Bias; use gpui::MutableAppContext; -use language::SelectionGoal; pub fn delete_over(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) { vim.update_active_editor(cx, |editor, cx| { editor.transact(cx, |editor, cx| { editor.set_clip_at_line_ends(false, cx); + let mut original_columns: HashMap<_, _> = Default::default(); editor.move_selections(cx, |map, selection| { let original_head = selection.head(); motion.expand_selection(map, selection, true); - selection.goal = SelectionGoal::Column(original_head.column()); + original_columns.insert(selection.id, original_head.column()); }); editor.insert(&"", cx); // Fixup cursor position after the deletion editor.set_clip_at_line_ends(true, cx); - editor.move_cursors(cx, |map, mut cursor, goal| { + editor.move_selections(cx, |map, selection| { + let mut cursor = selection.head(); if motion.linewise() { - if let SelectionGoal::Column(column) = goal { - *cursor.column_mut() = column + if let Some(column) = original_columns.get(&selection.id) { + *cursor.column_mut() = *column } } - - (map.clip_point(cursor, Bias::Left), SelectionGoal::None) + cursor = map.clip_point(cursor, Bias::Left); + selection.collapse_to(cursor, selection.goal) }); }); });