From 835af3583982666f49b9248177507128aaa5a393 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 24 Dec 2021 20:50:26 -0700 Subject: [PATCH 1/8] Simplify prev/next_row_boundary methods We added clipping of points against the buffer when excerpt headers were in the buffer, but now that they're just blocks, I think we can avoid the potential to panic in these methods by going back to not clipping. --- crates/editor/src/display_map.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index bcf7d14906d2a9c526ed74c29916eb40419b19c7..cb6b020aff1b9df80adfcc7fb3c6036127c4df12 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -200,38 +200,28 @@ impl DisplaySnapshot { self.buffer_snapshot.max_buffer_row() } - pub fn prev_row_boundary(&self, input_display_point: DisplayPoint) -> (DisplayPoint, Point) { - let mut display_point = input_display_point; + pub fn prev_row_boundary(&self, mut display_point: DisplayPoint) -> (DisplayPoint, Point) { loop { *display_point.column_mut() = 0; let mut point = display_point.to_point(self); - point = self.buffer_snapshot.clip_point(point, Bias::Left); point.column = 0; - let next_display_point = self.point_to_display_point_with_clipping(point, Bias::Left); + let next_display_point = self.point_to_display_point(point, Bias::Left); if next_display_point == display_point { return (display_point, point); } - if next_display_point > display_point { - panic!("invalid display point {:?}", input_display_point); - } display_point = next_display_point; } } - pub fn next_row_boundary(&self, input_display_point: DisplayPoint) -> (DisplayPoint, Point) { - let mut display_point = input_display_point; + pub fn next_row_boundary(&self, mut display_point: DisplayPoint) -> (DisplayPoint, Point) { loop { *display_point.column_mut() = self.line_len(display_point.row()); - let mut point = self.display_point_to_point(display_point, Bias::Right); - point = self.buffer_snapshot.clip_point(point, Bias::Right); + let mut point = display_point.to_point(self); point.column = self.buffer_snapshot.line_len(point.row); let next_display_point = self.point_to_display_point(point, Bias::Right); if next_display_point == display_point { return (display_point, point); } - if next_display_point < display_point { - panic!("invalid display point {:?}", input_display_point); - } display_point = next_display_point; } } From cbc162acf5813b54a70cb57251ea9188c0174595 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 24 Dec 2021 20:51:33 -0700 Subject: [PATCH 2/8] WIP: Allow lines to be moved down across excerpts This is still a bit weird because we can't remove the last line of an excerpt but we still move it into another buffer. There also seem to be issues with undo. --- crates/editor/src/editor.rs | 118 ++++++++++++++++++++++++++++++++++-- 1 file changed, 112 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bf2b25c0a309f6e2cafe84b5aeb2c661b344a6fe..3e54784111d20c45aceca3c830f7dffb9d87deda 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1783,8 +1783,104 @@ impl Editor { } pub fn move_line_down(&mut self, _: &MoveLineDown, cx: &mut ViewContext) { + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let buffer = self.buffer.read(cx).snapshot(cx); + + let mut deletes = Vec::new(); + let mut inserts = Vec::new(); + // let mut new_selection_ranges = Vec::new(); + let mut unfold_ranges = Vec::new(); + let mut refold_ranges = Vec::new(); + + let selections = self.local_selections::(cx); + let mut selections = selections.iter().peekable(); + let mut contiguous_row_selections = Vec::new(); + let mut new_selections = Vec::new(); + + while let Some(selection) = selections.next() { + // Find all the selections that span a contiguous row range + contiguous_row_selections.push(selection.clone()); + let start_row = selection.start.row; + let mut end_row = if selection.end.column > 0 || selection.is_empty() { + display_map + .next_row_boundary(selection.end.to_display_point(&display_map)) + .1 + .row + + 1 + } else { + selection.end.row + }; + + while let Some(next_selection) = selections.peek() { + if next_selection.start.row <= end_row { + end_row = if next_selection.end.column > 0 || next_selection.is_empty() { + display_map + .next_row_boundary(next_selection.end.to_display_point(&display_map)) + .1 + .row + + 1 + } else { + next_selection.end.row + }; + contiguous_row_selections.push(selections.next().unwrap().clone()); + } else { + break; + } + } + + // Move the text spanned by the row range to be after the last line of the row range + if end_row <= buffer.max_point().row { + let range_to_move = Point::new(start_row, 0)..Point::new(end_row, 0); + let insertion_point = Point::new(end_row, buffer.line_len(end_row)); + let insertion_row = insertion_point.row + 1; + let mut text = String::from("\n"); + text.extend(buffer.text_for_range(range_to_move.clone())); + text.pop(); // Drop trailing newline + deletes.push(range_to_move.clone()); + inserts.push((insertion_point, text)); + + // Make the selections relative to the insertion row + new_selections.extend(contiguous_row_selections.drain(..).map(|mut selection| { + selection.start.row = insertion_row + selection.start.row - start_row; + selection.end.row = insertion_row + selection.end.row - start_row; + selection + })); + + // Unfold all the folds spanned by these rows + unfold_ranges.push(range_to_move.clone()); + + // Refold ranges relative to the insertion row + for fold in display_map.folds_in_range( + buffer.anchor_before(range_to_move.start) + ..buffer.anchor_after(range_to_move.end), + ) { + let mut start = fold.start.to_point(&buffer); + let mut end = fold.end.to_point(&buffer); + start.row = insertion_row + start.row - start_row; + end.row = insertion_row + end.row - start_row; + refold_ranges.push(start..end); + } + } else { + new_selections.extend(contiguous_row_selections.drain(..)); + } + } + self.start_transaction(cx); + self.unfold_ranges(unfold_ranges, cx); + self.buffer.update(cx, |buffer, cx| { + for (point, text) in inserts.into_iter().rev() { + buffer.edit([point..point], text, cx); + } + }); + self.fold_ranges(refold_ranges, cx); + self.update_selections(new_selections, Some(Autoscroll::Fit), cx); + self.buffer.update(cx, |buffer, cx| { + buffer.edit(deletes.into_iter().rev(), "", cx); + }); + self.end_transaction(cx); + } + pub fn move_line_down2(&mut self, _: &MoveLineDown, cx: &mut ViewContext) { let selections = self.local_selections::(cx); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = self.buffer.read(cx).snapshot(cx); @@ -1799,10 +1895,12 @@ impl Editor { while let Some(selection) = selections.next() { // Accumulate contiguous regions of rows that we want to move. contiguous_selections.push(selection.point_range(&buffer)); + let SpannedRows { mut buffer_rows, mut display_rows, } = selection.spanned_rows(false, &display_map); + while let Some(next_selection) = selections.peek() { let SpannedRows { buffer_rows: next_buffer_rows, @@ -1818,6 +1916,9 @@ impl Editor { } } + println!("spanned buffer rows {:?}", buffer_rows); + println!("spanned display rows {:?}", display_rows); + // Cut the text from the selected rows and paste it at the end of the next line. if display_rows.end <= display_map.max_point().row() { let start = Point::new(buffer_rows.start, 0).to_offset(&buffer); @@ -1829,27 +1930,30 @@ impl Editor { let next_row_buffer_end = display_map.next_row_boundary(next_row_display_end).1; let next_row_buffer_end_offset = next_row_buffer_end.to_offset(&buffer); + dbg!(next_row_display_end); + dbg!(next_row_buffer_end); + let mut text = String::new(); text.push('\n'); text.extend(buffer.text_for_range(start..end)); edits.push((start..end + 1, String::new())); edits.push((next_row_buffer_end_offset..next_row_buffer_end_offset, text)); - let row_delta = next_row_buffer_end.row - buffer_rows.end + 1; - // Move selections down. + let display_row_delta = next_row_display_end.row() - display_rows.end + 1; for range in &mut contiguous_selections { - range.start.row += row_delta; - range.end.row += row_delta; + range.start.row += display_row_delta; + range.end.row += display_row_delta; } // Move folds down. old_folds.push(start..end); + let buffer_row_delta = next_row_buffer_end.row - buffer_rows.end + 1; for fold in display_map.folds_in_range(start..end) { let mut start = fold.start.to_point(&buffer); let mut end = fold.end.to_point(&buffer); - start.row += row_delta; - end.row += row_delta; + start.row += buffer_row_delta; + end.row += buffer_row_delta; new_folds.push(start..end); } } @@ -1857,6 +1961,8 @@ impl Editor { new_selection_ranges.extend(contiguous_selections.drain(..)); } + self.start_transaction(cx); + self.unfold_ranges(old_folds, cx); self.buffer.update(cx, |buffer, cx| { for (range, text) in edits.into_iter().rev() { From accf90e843437db33f9748d63dafaddeda8ccf14 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 27 Dec 2021 17:08:31 -0800 Subject: [PATCH 3/8] Add MultiBufferSnapshot::range_contains_excerpt_boundary Use this method to disable move_line_down across excerpt boundaries. Co-Authored-By: Nathan Sobo --- crates/editor/src/editor.rs | 109 ++---------------------------- crates/editor/src/multi_buffer.rs | 23 +++++++ 2 files changed, 27 insertions(+), 105 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3e54784111d20c45aceca3c830f7dffb9d87deda..2f61146cc1e00779dae8cc6ac509115134250446 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1783,104 +1783,6 @@ impl Editor { } pub fn move_line_down(&mut self, _: &MoveLineDown, cx: &mut ViewContext) { - let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let buffer = self.buffer.read(cx).snapshot(cx); - - let mut deletes = Vec::new(); - let mut inserts = Vec::new(); - // let mut new_selection_ranges = Vec::new(); - let mut unfold_ranges = Vec::new(); - let mut refold_ranges = Vec::new(); - - let selections = self.local_selections::(cx); - let mut selections = selections.iter().peekable(); - let mut contiguous_row_selections = Vec::new(); - let mut new_selections = Vec::new(); - - while let Some(selection) = selections.next() { - // Find all the selections that span a contiguous row range - contiguous_row_selections.push(selection.clone()); - let start_row = selection.start.row; - let mut end_row = if selection.end.column > 0 || selection.is_empty() { - display_map - .next_row_boundary(selection.end.to_display_point(&display_map)) - .1 - .row - + 1 - } else { - selection.end.row - }; - - while let Some(next_selection) = selections.peek() { - if next_selection.start.row <= end_row { - end_row = if next_selection.end.column > 0 || next_selection.is_empty() { - display_map - .next_row_boundary(next_selection.end.to_display_point(&display_map)) - .1 - .row - + 1 - } else { - next_selection.end.row - }; - contiguous_row_selections.push(selections.next().unwrap().clone()); - } else { - break; - } - } - - // Move the text spanned by the row range to be after the last line of the row range - if end_row <= buffer.max_point().row { - let range_to_move = Point::new(start_row, 0)..Point::new(end_row, 0); - let insertion_point = Point::new(end_row, buffer.line_len(end_row)); - let insertion_row = insertion_point.row + 1; - let mut text = String::from("\n"); - text.extend(buffer.text_for_range(range_to_move.clone())); - text.pop(); // Drop trailing newline - deletes.push(range_to_move.clone()); - inserts.push((insertion_point, text)); - - // Make the selections relative to the insertion row - new_selections.extend(contiguous_row_selections.drain(..).map(|mut selection| { - selection.start.row = insertion_row + selection.start.row - start_row; - selection.end.row = insertion_row + selection.end.row - start_row; - selection - })); - - // Unfold all the folds spanned by these rows - unfold_ranges.push(range_to_move.clone()); - - // Refold ranges relative to the insertion row - for fold in display_map.folds_in_range( - buffer.anchor_before(range_to_move.start) - ..buffer.anchor_after(range_to_move.end), - ) { - let mut start = fold.start.to_point(&buffer); - let mut end = fold.end.to_point(&buffer); - start.row = insertion_row + start.row - start_row; - end.row = insertion_row + end.row - start_row; - refold_ranges.push(start..end); - } - } else { - new_selections.extend(contiguous_row_selections.drain(..)); - } - } - - self.start_transaction(cx); - self.unfold_ranges(unfold_ranges, cx); - self.buffer.update(cx, |buffer, cx| { - for (point, text) in inserts.into_iter().rev() { - buffer.edit([point..point], text, cx); - } - }); - self.fold_ranges(refold_ranges, cx); - self.update_selections(new_selections, Some(Autoscroll::Fit), cx); - self.buffer.update(cx, |buffer, cx| { - buffer.edit(deletes.into_iter().rev(), "", cx); - }); - self.end_transaction(cx); - } - - pub fn move_line_down2(&mut self, _: &MoveLineDown, cx: &mut ViewContext) { let selections = self.local_selections::(cx); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = self.buffer.read(cx).snapshot(cx); @@ -1916,9 +1818,6 @@ impl Editor { } } - println!("spanned buffer rows {:?}", buffer_rows); - println!("spanned display rows {:?}", display_rows); - // Cut the text from the selected rows and paste it at the end of the next line. if display_rows.end <= display_map.max_point().row() { let start = Point::new(buffer_rows.start, 0).to_offset(&buffer); @@ -1930,8 +1829,10 @@ impl Editor { let next_row_buffer_end = display_map.next_row_boundary(next_row_display_end).1; let next_row_buffer_end_offset = next_row_buffer_end.to_offset(&buffer); - dbg!(next_row_display_end); - dbg!(next_row_buffer_end); + if buffer.range_contains_excerpt_boundary(start..next_row_buffer_end_offset) { + new_selection_ranges.extend(contiguous_selections.drain(..)); + continue; + } let mut text = String::new(); text.push('\n'); @@ -1962,7 +1863,6 @@ impl Editor { } self.start_transaction(cx); - self.unfold_ranges(old_folds, cx); self.buffer.update(cx, |buffer, cx| { for (range, text) in edits.into_iter().rev() { @@ -1971,7 +1871,6 @@ impl Editor { }); self.fold_ranges(new_folds, cx); self.select_ranges(new_selection_ranges, Some(Autoscroll::Fit), cx); - self.end_transaction(cx); } diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 36c8577bf0699b6439c9740bf76c97b05b166d7a..c875ce29a6cff7a671551cb0a7858a61f7676ef1 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -1395,6 +1395,23 @@ impl MultiBufferSnapshot { panic!("excerpt not found"); } + pub fn range_contains_excerpt_boundary(&self, range: Range) -> bool { + let start = range.start.to_offset(self); + let end = range.end.to_offset(self); + let mut cursor = self.excerpts.cursor::<(usize, Option<&ExcerptId>)>(); + cursor.seek(&start, Bias::Right, &()); + let start_id = cursor + .item() + .or_else(|| cursor.prev_item()) + .map(|excerpt| &excerpt.id); + cursor.seek_forward(&end, Bias::Right, &()); + let end_id = cursor + .item() + .or_else(|| cursor.prev_item()) + .map(|excerpt| &excerpt.id); + start_id != end_id + } + pub fn parse_count(&self) -> usize { self.parse_count } @@ -2158,6 +2175,12 @@ mod tests { ); assert_eq!(snapshot.buffer_rows(4).collect::>(), [Some(3)]); assert_eq!(snapshot.buffer_rows(5).collect::>(), []); + assert!(!snapshot.range_contains_excerpt_boundary(Point::new(1, 0)..Point::new(1, 5))); + assert!(snapshot.range_contains_excerpt_boundary(Point::new(1, 0)..Point::new(2, 0))); + assert!(snapshot.range_contains_excerpt_boundary(Point::new(1, 0)..Point::new(4, 0))); + assert!(!snapshot.range_contains_excerpt_boundary(Point::new(2, 0)..Point::new(3, 0))); + assert!(!snapshot.range_contains_excerpt_boundary(Point::new(4, 0)..Point::new(4, 2))); + assert!(!snapshot.range_contains_excerpt_boundary(Point::new(4, 2)..Point::new(4, 2))); buffer_1.update(cx, |buffer, cx| { buffer.edit( From 6057d819b0741d7b9ceee2c55b9f1f16979fffb6 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 27 Dec 2021 20:58:01 -0800 Subject: [PATCH 4/8] Add a unit test showing panic in move_line_down --- crates/editor/src/editor.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2f61146cc1e00779dae8cc6ac509115134250446..faee89b545bed6054318ef2291837d3c03bdf314 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1826,6 +1826,7 @@ impl Editor { let next_row_display_end = DisplayPoint::new(display_rows.end, display_map.line_len(display_rows.end)); + let next_row_buffer_end = display_map.next_row_boundary(next_row_display_end).1; let next_row_buffer_end_offset = next_row_buffer_end.to_offset(&buffer); @@ -5168,6 +5169,27 @@ mod tests { }); } + #[gpui::test] + fn test_move_line_up_down_with_blocks(cx: &mut gpui::MutableAppContext) { + let settings = EditorSettings::test(&cx); + let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx); + let (_, editor) = + cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); + editor.update(cx, |editor, cx| { + editor.insert_blocks( + [BlockProperties { + position: Point::new(2, 0), + disposition: BlockDisposition::Below, + height: 1, + render: Arc::new(|_| Empty::new().boxed()), + }], + cx, + ); + editor.select_ranges([Point::new(2, 0)..Point::new(2, 0)], None, cx); + editor.move_line_down(&MoveLineDown, cx); + }); + } + #[gpui::test] fn test_clipboard(cx: &mut gpui::MutableAppContext) { let buffer = MultiBuffer::build_simple("one✅ two three four five six ", cx); From 89bbfb81548f847fc6246b63eba4a17890da9d66 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 27 Dec 2021 20:58:35 -0800 Subject: [PATCH 5/8] wip --- crates/editor/src/display_map.rs | 13 ++++ crates/editor/src/editor.rs | 121 +++++++++++++++---------------- 2 files changed, 70 insertions(+), 64 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index cb6b020aff1b9df80adfcc7fb3c6036127c4df12..06a06ed28fc4f83d6eecad2a49a9c763428d1a7b 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -226,6 +226,19 @@ impl DisplaySnapshot { } } + pub fn next_line_boundary(&self, mut point: Point) -> Point { + loop { + point.column = self.buffer_snapshot.line_len(point.row); + let mut display_point = self.point_to_display_point(point, Bias::Right); + *display_point.column_mut() = self.line_len(display_point.row()); + let next_point = self.display_point_to_point(display_point, Bias::Right); + if next_point == point { + return point; + } + point = next_point; + } + } + fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint { let fold_point = point.to_fold_point(&self.folds_snapshot, bias); let tab_point = self.tabs_snapshot.to_tab_point(fold_point); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index faee89b545bed6054318ef2291837d3c03bdf314..fdf76b24a74d1ddb8d632f45e5856854799207cc 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1783,95 +1783,88 @@ impl Editor { } pub fn move_line_down(&mut self, _: &MoveLineDown, cx: &mut ViewContext) { - let selections = self.local_selections::(cx); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = self.buffer.read(cx).snapshot(cx); let mut edits = Vec::new(); - let mut new_selection_ranges = Vec::new(); - let mut old_folds = Vec::new(); - let mut new_folds = Vec::new(); + let mut unfold_ranges = Vec::new(); + let mut refold_ranges = Vec::new(); + let selections = self.local_selections::(cx); let mut selections = selections.iter().peekable(); - let mut contiguous_selections = Vec::new(); - while let Some(selection) = selections.next() { - // Accumulate contiguous regions of rows that we want to move. - contiguous_selections.push(selection.point_range(&buffer)); + let mut contiguous_row_selections = Vec::new(); + let mut new_selections = Vec::new(); - let SpannedRows { - mut buffer_rows, - mut display_rows, - } = selection.spanned_rows(false, &display_map); + while let Some(selection) = selections.next() { + // Find all the selections that span a contiguous row range + contiguous_row_selections.push(selection.clone()); + let start_row = selection.start.row; + let mut end_row = if selection.end.column > 0 || selection.is_empty() { + display_map.next_line_boundary(selection.end).row + 1 + } else { + selection.end.row + }; while let Some(next_selection) = selections.peek() { - let SpannedRows { - buffer_rows: next_buffer_rows, - display_rows: next_display_rows, - } = next_selection.spanned_rows(false, &display_map); - if next_buffer_rows.start <= buffer_rows.end { - buffer_rows.end = next_buffer_rows.end; - display_rows.end = next_display_rows.end; - contiguous_selections.push(next_selection.point_range(&buffer)); - selections.next().unwrap(); + if next_selection.start.row <= end_row { + end_row = if next_selection.end.column > 0 || next_selection.is_empty() { + display_map.next_line_boundary(next_selection.end).row + 1 + } else { + next_selection.end.row + }; + contiguous_row_selections.push(selections.next().unwrap().clone()); } else { break; } } - // Cut the text from the selected rows and paste it at the end of the next line. - if display_rows.end <= display_map.max_point().row() { - let start = Point::new(buffer_rows.start, 0).to_offset(&buffer); - let end = Point::new(buffer_rows.end - 1, buffer.line_len(buffer_rows.end - 1)) - .to_offset(&buffer); - - let next_row_display_end = - DisplayPoint::new(display_rows.end, display_map.line_len(display_rows.end)); - - let next_row_buffer_end = display_map.next_row_boundary(next_row_display_end).1; - let next_row_buffer_end_offset = next_row_buffer_end.to_offset(&buffer); - - if buffer.range_contains_excerpt_boundary(start..next_row_buffer_end_offset) { - new_selection_ranges.extend(contiguous_selections.drain(..)); - continue; - } - - let mut text = String::new(); - text.push('\n'); - text.extend(buffer.text_for_range(start..end)); - edits.push((start..end + 1, String::new())); - edits.push((next_row_buffer_end_offset..next_row_buffer_end_offset, text)); - - // Move selections down. - let display_row_delta = next_row_display_end.row() - display_rows.end + 1; - for range in &mut contiguous_selections { - range.start.row += display_row_delta; - range.end.row += display_row_delta; - } - - // Move folds down. - old_folds.push(start..end); - let buffer_row_delta = next_row_buffer_end.row - buffer_rows.end + 1; - for fold in display_map.folds_in_range(start..end) { + // Move the text spanned by the row range to be after the last line of the row range + if end_row <= buffer.max_point().row { + let range_to_move = Point::new(start_row, 0)..Point::new(end_row, 0); + let insertion_point = display_map.next_line_boundary(Point::new(end_row, 0)); + let mut text = String::from("\n"); + text.extend(buffer.text_for_range(range_to_move.clone())); + text.pop(); // Drop trailing newline + edits.push((range_to_move.clone(), String::new())); + edits.push((insertion_point..insertion_point, text)); + + let row_delta = insertion_point.row - range_to_move.end.row + 1; + + // Make the selections relative to the insertion row + new_selections.extend(contiguous_row_selections.drain(..).map(|mut selection| { + selection.start.row += row_delta; + selection.end.row += row_delta; + selection + })); + + // Unfold all the folds spanned by these rows + unfold_ranges.push(range_to_move.clone()); + + // Refold ranges relative to the insertion row + for fold in display_map.folds_in_range( + buffer.anchor_before(range_to_move.start) + ..buffer.anchor_after(range_to_move.end), + ) { let mut start = fold.start.to_point(&buffer); let mut end = fold.end.to_point(&buffer); - start.row += buffer_row_delta; - end.row += buffer_row_delta; - new_folds.push(start..end); + start.row += row_delta; + end.row += row_delta; + refold_ranges.push(start..end); } + } else { + new_selections.extend(contiguous_row_selections.drain(..)); } - - new_selection_ranges.extend(contiguous_selections.drain(..)); } self.start_transaction(cx); - self.unfold_ranges(old_folds, cx); + self.unfold_ranges(unfold_ranges, cx); self.buffer.update(cx, |buffer, cx| { for (range, text) in edits.into_iter().rev() { - buffer.edit(Some(range), text, cx); + buffer.edit([range], text, cx); } }); - self.fold_ranges(new_folds, cx); - self.select_ranges(new_selection_ranges, Some(Autoscroll::Fit), cx); + self.fold_ranges(refold_ranges, cx); + self.update_selections(new_selections, Some(Autoscroll::Fit), cx); self.end_transaction(cx); } From 7f786ca8a6b4d76c993a2adab9888ffe12ec895d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 27 Dec 2021 22:11:05 -0800 Subject: [PATCH 6/8] WIP: Start moving toward a simpler interface for detecting prev/next line boundaries --- crates/editor/src/display_map.rs | 23 +- crates/editor/src/display_map/wrap_map.rs | 9 - crates/editor/src/editor.rs | 249 +++++++++++----------- 3 files changed, 134 insertions(+), 147 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 06a06ed28fc4f83d6eecad2a49a9c763428d1a7b..cd1bc3a607c6a4610359d395f24c3da618666ef7 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -226,6 +226,19 @@ impl DisplaySnapshot { } } + pub fn prev_line_boundary(&self, mut point: Point) -> Point { + loop { + point.column = 0; + let mut display_point = self.point_to_display_point(point, Bias::Left); + *display_point.column_mut() = 0; + let next_point = self.display_point_to_point(display_point, Bias::Left); + if next_point == point { + return point; + } + point = next_point; + } + } + pub fn next_line_boundary(&self, mut point: Point) -> Point { loop { point.column = self.buffer_snapshot.line_len(point.row); @@ -247,16 +260,6 @@ impl DisplaySnapshot { DisplayPoint(block_point) } - fn point_to_display_point_with_clipping(&self, point: Point, bias: Bias) -> DisplayPoint { - let fold_point = point.to_fold_point(&self.folds_snapshot, bias); - let tab_point = self.tabs_snapshot.to_tab_point(fold_point); - let wrap_point = self - .wraps_snapshot - .from_tab_point_with_clipping(tab_point, bias); - let block_point = self.blocks_snapshot.to_block_point(wrap_point); - DisplayPoint(block_point) - } - fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point { let block_point = point.0; let wrap_point = self.blocks_snapshot.to_wrap_point(block_point); diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 4a2510fb823dc128c8c547d9c0aae44049a7c2b1..b7e96c490634d92572a8cf9530d6617b1a2d10bf 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -672,15 +672,6 @@ impl WrapSnapshot { WrapPoint(cursor.start().1 .0 + (point.0 - cursor.start().0 .0)) } - pub fn from_tab_point_with_clipping(&self, point: TabPoint, bias: Bias) -> WrapPoint { - let mut cursor = self.transforms.cursor::<(TabPoint, WrapPoint)>(); - cursor.seek(&point, bias, &()); - self.clip_point( - WrapPoint(cursor.start().1 .0 + (point.0 - cursor.start().0 .0)), - bias, - ) - } - pub fn clip_point(&self, mut point: WrapPoint, bias: Bias) -> WrapPoint { if bias == Bias::Left { let mut cursor = self.transforms.cursor::(); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index fdf76b24a74d1ddb8d632f45e5856854799207cc..9fb7995f7baaf9fc003f5e416e486f99ecafd284 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -284,16 +284,8 @@ trait SelectionExt { fn offset_range(&self, buffer: &MultiBufferSnapshot) -> Range; fn point_range(&self, buffer: &MultiBufferSnapshot) -> Range; fn display_range(&self, map: &DisplaySnapshot) -> Range; - fn spanned_rows( - &self, - include_end_if_at_line_start: bool, - map: &DisplaySnapshot, - ) -> SpannedRows; -} - -struct SpannedRows { - buffer_rows: Range, - display_rows: Range, + fn spanned_rows(&self, include_end_if_at_line_start: bool, map: &DisplaySnapshot) + -> Range; } #[derive(Clone, Debug)] @@ -1577,12 +1569,12 @@ impl Editor { let mut edit_ranges = Vec::new(); let mut selections = selections.iter().peekable(); while let Some(selection) = selections.next() { - let mut rows = selection.spanned_rows(false, &display_map).buffer_rows; + let mut rows = selection.spanned_rows(false, &display_map); let goal_display_column = selection.head().to_display_point(&display_map).column(); // Accumulate contiguous regions of rows that we want to delete. while let Some(next_selection) = selections.peek() { - let next_rows = next_selection.spanned_rows(false, &display_map).buffer_rows; + let next_rows = next_selection.spanned_rows(false, &display_map); if next_rows.start <= rows.end { rows.end = next_rows.end; selections.next().unwrap(); @@ -1645,10 +1637,10 @@ impl Editor { let mut selections_iter = selections.iter().peekable(); while let Some(selection) = selections_iter.next() { // Avoid duplicating the same lines twice. - let mut rows = selection.spanned_rows(false, &display_map).buffer_rows; + let mut rows = selection.spanned_rows(false, &display_map); while let Some(next_selection) = selections_iter.peek() { - let next_rows = next_selection.spanned_rows(false, &display_map).buffer_rows; + let next_rows = next_selection.spanned_rows(false, &display_map); if next_rows.start <= rows.end - 1 { rows.end = next_rows.end; selections_iter.next().unwrap(); @@ -1693,92 +1685,98 @@ impl Editor { } pub fn move_line_up(&mut self, _: &MoveLineUp, cx: &mut ViewContext) { - self.start_transaction(cx); - - let selections = self.local_selections::(cx); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = self.buffer.read(cx).snapshot(cx); let mut edits = Vec::new(); - let mut new_selection_ranges = Vec::new(); - let mut old_folds = Vec::new(); - let mut new_folds = Vec::new(); + let mut unfold_ranges = Vec::new(); + let mut refold_ranges = Vec::new(); + let selections = self.local_selections::(cx); let mut selections = selections.iter().peekable(); - let mut contiguous_selections = Vec::new(); + let mut contiguous_row_selections = Vec::new(); + let mut new_selections = Vec::new(); + while let Some(selection) = selections.next() { - // Accumulate contiguous regions of rows that we want to move. - contiguous_selections.push(selection.point_range(&buffer)); - let SpannedRows { - mut buffer_rows, - mut display_rows, - } = selection.spanned_rows(false, &display_map); + // Find all the selections that span a contiguous row range + contiguous_row_selections.push(selection.clone()); + let start_row = selection.start.row; + let mut end_row = if selection.end.column > 0 || selection.is_empty() { + display_map.next_line_boundary(selection.end).row + 1 + } else { + selection.end.row + }; while let Some(next_selection) = selections.peek() { - let SpannedRows { - buffer_rows: next_buffer_rows, - display_rows: next_display_rows, - } = next_selection.spanned_rows(false, &display_map); - if next_buffer_rows.start <= buffer_rows.end { - buffer_rows.end = next_buffer_rows.end; - display_rows.end = next_display_rows.end; - contiguous_selections.push(next_selection.point_range(&buffer)); - selections.next().unwrap(); + if next_selection.start.row <= end_row { + end_row = if next_selection.end.column > 0 || next_selection.is_empty() { + display_map.next_line_boundary(next_selection.end).row + 1 + } else { + next_selection.end.row + }; + contiguous_row_selections.push(selections.next().unwrap().clone()); } else { break; } } - // Cut the text from the selected rows and paste it at the start of the previous line. - if display_rows.start != 0 { - let start = Point::new(buffer_rows.start, 0).to_offset(&buffer); - let end = Point::new(buffer_rows.end - 1, buffer.line_len(buffer_rows.end - 1)) - .to_offset(&buffer); - - let prev_row_display_start = DisplayPoint::new(display_rows.start - 1, 0); - let prev_row_buffer_start = display_map.prev_row_boundary(prev_row_display_start).1; - let prev_row_buffer_start_offset = prev_row_buffer_start.to_offset(&buffer); - - let mut text = String::new(); - text.extend(buffer.text_for_range(start..end)); - text.push('\n'); - edits.push(( - prev_row_buffer_start_offset..prev_row_buffer_start_offset, - text, - )); - edits.push((start - 1..end, String::new())); - - let row_delta = buffer_rows.start - prev_row_buffer_start.row; - - // Move selections up. - for range in &mut contiguous_selections { - range.start.row -= row_delta; - range.end.row -= row_delta; - } - - // Move folds up. - old_folds.push(start..end); - for fold in display_map.folds_in_range(start..end) { - let mut start = fold.start.to_point(&buffer); - let mut end = fold.end.to_point(&buffer); - start.row -= row_delta; - end.row -= row_delta; - new_folds.push(start..end); + // Move the text spanned by the row range to be before the line preceding the row range + if start_row > 0 { + let range_to_move = Point::new(start_row - 1, buffer.line_len(start_row - 1)) + ..Point::new(end_row - 1, buffer.line_len(end_row - 1)); + let insertion_point = display_map.prev_line_boundary(Point::new(start_row - 1, 0)); + + // Don't move lines across excerpts + if !buffer.range_contains_excerpt_boundary(insertion_point..range_to_move.end) { + let text = buffer + .text_for_range(range_to_move.clone()) + .flat_map(|s| s.chars()) + .skip(1) + .chain(['\n']) + .collect::(); + + edits.push((insertion_point..insertion_point, text)); + edits.push((range_to_move.clone(), String::new())); + + let row_delta = range_to_move.start.row - insertion_point.row + 1; + + // Move selections up + new_selections.extend(contiguous_row_selections.drain(..).map( + |mut selection| { + selection.start.row -= row_delta; + selection.end.row -= row_delta; + selection + }, + )); + + // Move folds up + unfold_ranges.push(range_to_move.clone()); + for fold in display_map.folds_in_range( + buffer.anchor_before(range_to_move.start) + ..buffer.anchor_after(range_to_move.end), + ) { + let mut start = fold.start.to_point(&buffer); + let mut end = fold.end.to_point(&buffer); + start.row -= row_delta; + end.row -= row_delta; + refold_ranges.push(start..end); + } } } - new_selection_ranges.extend(contiguous_selections.drain(..)); + // If we didn't move line(s), preserve the existing selections + new_selections.extend(contiguous_row_selections.drain(..)); } - self.unfold_ranges(old_folds, cx); + self.start_transaction(cx); + self.unfold_ranges(unfold_ranges, cx); self.buffer.update(cx, |buffer, cx| { for (range, text) in edits.into_iter().rev() { - buffer.edit(Some(range), text, cx); + buffer.edit([range], text, cx); } }); - self.fold_ranges(new_folds, cx); - self.select_ranges(new_selection_ranges, Some(Autoscroll::Fit), cx); - + self.fold_ranges(refold_ranges, cx); + self.update_selections(new_selections, Some(Autoscroll::Fit), cx); self.end_transaction(cx); } @@ -1822,38 +1820,43 @@ impl Editor { if end_row <= buffer.max_point().row { let range_to_move = Point::new(start_row, 0)..Point::new(end_row, 0); let insertion_point = display_map.next_line_boundary(Point::new(end_row, 0)); - let mut text = String::from("\n"); - text.extend(buffer.text_for_range(range_to_move.clone())); - text.pop(); // Drop trailing newline - edits.push((range_to_move.clone(), String::new())); - edits.push((insertion_point..insertion_point, text)); - - let row_delta = insertion_point.row - range_to_move.end.row + 1; - - // Make the selections relative to the insertion row - new_selections.extend(contiguous_row_selections.drain(..).map(|mut selection| { - selection.start.row += row_delta; - selection.end.row += row_delta; - selection - })); - - // Unfold all the folds spanned by these rows - unfold_ranges.push(range_to_move.clone()); - - // Refold ranges relative to the insertion row - for fold in display_map.folds_in_range( - buffer.anchor_before(range_to_move.start) - ..buffer.anchor_after(range_to_move.end), - ) { - let mut start = fold.start.to_point(&buffer); - let mut end = fold.end.to_point(&buffer); - start.row += row_delta; - end.row += row_delta; - refold_ranges.push(start..end); + + // Don't move lines across excerpt boundaries + if !buffer.range_contains_excerpt_boundary(range_to_move.start..insertion_point) { + let mut text = String::from("\n"); + text.extend(buffer.text_for_range(range_to_move.clone())); + text.pop(); // Drop trailing newline + edits.push((range_to_move.clone(), String::new())); + edits.push((insertion_point..insertion_point, text)); + + let row_delta = insertion_point.row - range_to_move.end.row + 1; + + // Move selections down + new_selections.extend(contiguous_row_selections.drain(..).map( + |mut selection| { + selection.start.row += row_delta; + selection.end.row += row_delta; + selection + }, + )); + + // Move folds down + unfold_ranges.push(range_to_move.clone()); + for fold in display_map.folds_in_range( + buffer.anchor_before(range_to_move.start) + ..buffer.anchor_after(range_to_move.end), + ) { + let mut start = fold.start.to_point(&buffer); + let mut end = fold.end.to_point(&buffer); + start.row += row_delta; + end.row += row_delta; + refold_ranges.push(start..end); + } } - } else { - new_selections.extend(contiguous_row_selections.drain(..)); } + + // If we didn't move line(s), preserve the existing selections + new_selections.extend(contiguous_row_selections.drain(..)); } self.start_transaction(cx); @@ -2405,7 +2408,7 @@ impl Editor { let mut selections = self.local_selections::(cx); let max_point = display_map.buffer_snapshot.max_point(); for selection in &mut selections { - let rows = selection.spanned_rows(true, &display_map).buffer_rows; + let rows = selection.spanned_rows(true, &display_map); selection.start = Point::new(rows.start, 0); selection.end = cmp::min(max_point, Point::new(rows.end, 0)); selection.reversed = false; @@ -3778,30 +3781,20 @@ impl SelectionExt for Selection { &self, include_end_if_at_line_start: bool, map: &DisplaySnapshot, - ) -> SpannedRows { - let display_start = self - .start - .to_point(&map.buffer_snapshot) - .to_display_point(map); - let mut display_end = self - .end - .to_point(&map.buffer_snapshot) - .to_display_point(map); + ) -> Range { + let start = self.start.to_point(&map.buffer_snapshot); + let mut end = self.end.to_point(&map.buffer_snapshot); if !include_end_if_at_line_start - && display_end.row() != map.max_point().row() - && display_start.row() != display_end.row() - && display_end.column() == 0 + && end.row != map.buffer_snapshot.max_point().row + && start.row != end.row + && end.column == 0 { - *display_end.row_mut() -= 1; + end.row -= 1; } - let (display_start, buffer_start) = map.prev_row_boundary(display_start); - let (display_end, buffer_end) = map.next_row_boundary(display_end); - - SpannedRows { - buffer_rows: buffer_start.row..buffer_end.row + 1, - display_rows: display_start.row()..display_end.row() + 1, - } + let buffer_start = map.prev_line_boundary(start); + let buffer_end = map.next_line_boundary(end); + buffer_start.row..buffer_end.row + 1 } } From 137fbd0088b1365566bebc3df02f577552fca780 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 28 Dec 2021 13:47:09 -0800 Subject: [PATCH 7/8] Update editor element to use new `{next,prev}_line_boundary` methods Since these methods take buffer points instead of display points, this adjusts the logic for retrieving the visible selections, so that they are initially returned in terms of buffer points. --- crates/editor/src/display_map.rs | 56 +++++-------------- crates/editor/src/editor.rs | 95 +++++++++++--------------------- crates/editor/src/element.rs | 82 ++++++++++++++++++++------- 3 files changed, 109 insertions(+), 124 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index cd1bc3a607c6a4610359d395f24c3da618666ef7..8a2c95805019e6d51b36c82e6ed4ab24b783f6ab 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -200,53 +200,27 @@ impl DisplaySnapshot { self.buffer_snapshot.max_buffer_row() } - pub fn prev_row_boundary(&self, mut display_point: DisplayPoint) -> (DisplayPoint, Point) { - loop { - *display_point.column_mut() = 0; - let mut point = display_point.to_point(self); - point.column = 0; - let next_display_point = self.point_to_display_point(point, Bias::Left); - if next_display_point == display_point { - return (display_point, point); - } - display_point = next_display_point; - } - } - - pub fn next_row_boundary(&self, mut display_point: DisplayPoint) -> (DisplayPoint, Point) { - loop { - *display_point.column_mut() = self.line_len(display_point.row()); - let mut point = display_point.to_point(self); - point.column = self.buffer_snapshot.line_len(point.row); - let next_display_point = self.point_to_display_point(point, Bias::Right); - if next_display_point == display_point { - return (display_point, point); - } - display_point = next_display_point; - } - } - - pub fn prev_line_boundary(&self, mut point: Point) -> Point { + pub fn prev_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) { loop { point.column = 0; let mut display_point = self.point_to_display_point(point, Bias::Left); *display_point.column_mut() = 0; let next_point = self.display_point_to_point(display_point, Bias::Left); if next_point == point { - return point; + return (point, display_point); } point = next_point; } } - pub fn next_line_boundary(&self, mut point: Point) -> Point { + pub fn next_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) { loop { point.column = self.buffer_snapshot.line_len(point.row); let mut display_point = self.point_to_display_point(point, Bias::Right); *display_point.column_mut() = self.line_len(display_point.row()); let next_point = self.display_point_to_point(display_point, Bias::Right); if next_point == point { - return point; + return (point, display_point); } point = next_point; } @@ -631,23 +605,21 @@ mod tests { log::info!("display text: {:?}", snapshot.text()); // Line boundaries + let buffer = &snapshot.buffer_snapshot; for _ in 0..5 { - let row = rng.gen_range(0..=snapshot.max_point().row()); - let column = rng.gen_range(0..=snapshot.line_len(row)); - let point = snapshot.clip_point(DisplayPoint::new(row, column), Left); + let row = rng.gen_range(0..=buffer.max_point().row); + let column = rng.gen_range(0..=buffer.line_len(row)); + let point = buffer.clip_point(Point::new(row, column), Left); - let (prev_display_bound, prev_buffer_bound) = snapshot.prev_row_boundary(point); - let (next_display_bound, next_buffer_bound) = snapshot.next_row_boundary(point); + let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point); + let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point); - assert!(prev_display_bound <= point); - assert!(next_display_bound >= point); + assert!(prev_buffer_bound <= point); + assert!(next_buffer_bound >= point); assert_eq!(prev_buffer_bound.column, 0); assert_eq!(prev_display_bound.column(), 0); - if next_buffer_bound < snapshot.buffer_snapshot.max_point() { - assert_eq!( - snapshot.buffer_snapshot.chars_at(next_buffer_bound).next(), - Some('\n') - ); + if next_buffer_bound < buffer.max_point() { + assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n')); } assert_eq!( diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9fb7995f7baaf9fc003f5e416e486f99ecafd284..028706b193b95aa917834768c2d7c427bc5ae7fe 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1702,7 +1702,7 @@ impl Editor { contiguous_row_selections.push(selection.clone()); let start_row = selection.start.row; let mut end_row = if selection.end.column > 0 || selection.is_empty() { - display_map.next_line_boundary(selection.end).row + 1 + display_map.next_line_boundary(selection.end).0.row + 1 } else { selection.end.row }; @@ -1710,7 +1710,7 @@ impl Editor { while let Some(next_selection) = selections.peek() { if next_selection.start.row <= end_row { end_row = if next_selection.end.column > 0 || next_selection.is_empty() { - display_map.next_line_boundary(next_selection.end).row + 1 + display_map.next_line_boundary(next_selection.end).0.row + 1 } else { next_selection.end.row }; @@ -1724,7 +1724,9 @@ impl Editor { if start_row > 0 { let range_to_move = Point::new(start_row - 1, buffer.line_len(start_row - 1)) ..Point::new(end_row - 1, buffer.line_len(end_row - 1)); - let insertion_point = display_map.prev_line_boundary(Point::new(start_row - 1, 0)); + let insertion_point = display_map + .prev_line_boundary(Point::new(start_row - 1, 0)) + .0; // Don't move lines across excerpts if !buffer.range_contains_excerpt_boundary(insertion_point..range_to_move.end) { @@ -1798,7 +1800,7 @@ impl Editor { contiguous_row_selections.push(selection.clone()); let start_row = selection.start.row; let mut end_row = if selection.end.column > 0 || selection.is_empty() { - display_map.next_line_boundary(selection.end).row + 1 + display_map.next_line_boundary(selection.end).0.row + 1 } else { selection.end.row }; @@ -1806,7 +1808,7 @@ impl Editor { while let Some(next_selection) = selections.peek() { if next_selection.start.row <= end_row { end_row = if next_selection.end.column > 0 || next_selection.is_empty() { - display_map.next_line_boundary(next_selection.end).row + 1 + display_map.next_line_boundary(next_selection.end).0.row + 1 } else { next_selection.end.row }; @@ -1819,7 +1821,7 @@ impl Editor { // Move the text spanned by the row range to be after the last line of the row range if end_row <= buffer.max_point().row { let range_to_move = Point::new(start_row, 0)..Point::new(end_row, 0); - let insertion_point = display_map.next_line_boundary(Point::new(end_row, 0)); + let insertion_point = display_map.next_line_boundary(Point::new(end_row, 0)).0; // Don't move lines across excerpt boundaries if !buffer.range_contains_excerpt_boundary(range_to_move.start..insertion_point) { @@ -3017,82 +3019,51 @@ impl Editor { } } - pub fn visible_selections<'a>( - &'a self, - display_rows: Range, - cx: &'a mut MutableAppContext, - ) -> HashMap>> { - let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + pub fn local_selections_in_range( + &self, + range: Range, + display_map: &DisplaySnapshot, + ) -> Vec> { let buffer = &display_map.buffer_snapshot; - let start = if display_rows.start == 0 { - Anchor::min() - } else { - buffer.anchor_before( - DisplayPoint::new(display_rows.start, 0).to_offset(&display_map, Bias::Left), - ) - }; - let end = if display_rows.end > display_map.max_point().row() { - Anchor::max() - } else { - buffer.anchor_before( - DisplayPoint::new(display_rows.end, 0).to_offset(&display_map, Bias::Right), - ) - }; - let start_ix = match self .selections - .binary_search_by(|probe| probe.end.cmp(&start, &buffer).unwrap()) + .binary_search_by(|probe| probe.end.cmp(&range.start, &buffer).unwrap()) { Ok(ix) | Err(ix) => ix, }; let end_ix = match self .selections - .binary_search_by(|probe| probe.start.cmp(&end, &buffer).unwrap()) + .binary_search_by(|probe| probe.start.cmp(&range.end, &buffer).unwrap()) { Ok(ix) => ix + 1, Err(ix) => ix, }; - fn display_selection( + fn point_selection( selection: &Selection, - display_map: &DisplaySnapshot, - ) -> Selection { + buffer: &MultiBufferSnapshot, + ) -> Selection { + let start = selection.start.to_point(&buffer); + let end = selection.end.to_point(&buffer); Selection { id: selection.id, - start: selection.start.to_display_point(&display_map), - end: selection.end.to_display_point(&display_map), + start, + end, reversed: selection.reversed, goal: selection.goal, } } - let mut result = HashMap::default(); - - result.insert( - self.replica_id(cx), - self.selections[start_ix..end_ix] - .iter() - .chain( - self.pending_selection - .as_ref() - .map(|pending| &pending.selection), - ) - .map(|s| display_selection(s, &display_map)) - .collect(), - ); - - for (replica_id, selection) in display_map - .buffer_snapshot - .remote_selections_in_range(&(start..end)) - { - result - .entry(replica_id) - .or_insert(Vec::new()) - .push(display_selection(&selection, &display_map)); - } - - result + self.selections[start_ix..end_ix] + .iter() + .chain( + self.pending_selection + .as_ref() + .map(|pending| &pending.selection), + ) + .map(|s| point_selection(s, &buffer)) + .collect() } pub fn local_selections<'a, D>(&self, cx: &'a AppContext) -> Vec> @@ -3792,8 +3763,8 @@ impl SelectionExt for Selection { end.row -= 1; } - let buffer_start = map.prev_line_boundary(start); - let buffer_end = map.next_line_boundary(end); + let buffer_start = map.prev_line_boundary(start).0; + let buffer_end = map.next_line_boundary(end).0; buffer_start.row..buffer_end.row + 1 } } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index cfe4a99dc4449d7b1eee7ce475053ed502cd3b69..9f80eed2fb4e5bf8a1fbacbde9789aca0627ad39 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1,7 +1,7 @@ use super::{ display_map::{BlockContext, ToDisplayPoint}, - DisplayPoint, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle, Input, Scroll, - Select, SelectPhase, SoftWrap, ToPoint, MAX_LINE_LEN, + Anchor, DisplayPoint, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle, Input, + Scroll, Select, SelectPhase, SoftWrap, ToPoint, MAX_LINE_LEN, }; use clock::ReplicaId; use collections::{BTreeMap, HashMap}; @@ -19,7 +19,7 @@ use gpui::{ MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle, }; use json::json; -use language::Chunk; +use language::{Bias, Chunk}; use smallvec::SmallVec; use std::{ cmp::{self, Ordering}, @@ -731,28 +731,70 @@ impl Element for EditorElement { let scroll_top = scroll_position.y() * line_height; let end_row = ((scroll_top + size.y()) / line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen + let start_anchor = if start_row == 0 { + Anchor::min() + } else { + snapshot + .buffer_snapshot + .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left)) + }; + let end_anchor = if end_row > snapshot.max_point().row() { + Anchor::max() + } else { + snapshot + .buffer_snapshot + .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right)) + }; + + let mut selections = HashMap::default(); let mut active_rows = BTreeMap::new(); let mut highlighted_row = None; - let selections = self.update_view(cx.app, |view, cx| { + self.update_view(cx.app, |view, cx| { highlighted_row = view.highlighted_row(); - let selections = view.visible_selections(start_row..end_row, cx); - for (replica_id, selections) in &selections { - if *replica_id == view.replica_id(cx) { - for selection in selections { - let is_empty = selection.start == selection.end; - let selection_start = snapshot.prev_row_boundary(selection.start).0; - let selection_end = snapshot.next_row_boundary(selection.end).0; - for row in cmp::max(selection_start.row(), start_row) - ..=cmp::min(selection_end.row(), end_row) - { - let contains_non_empty_selection = - active_rows.entry(row).or_insert(!is_empty); - *contains_non_empty_selection |= !is_empty; - } - } + let display_map = view.display_map.update(cx, |map, cx| map.snapshot(cx)); + + let local_selections = view + .local_selections_in_range(start_anchor.clone()..end_anchor.clone(), &display_map); + for selection in &local_selections { + let is_empty = selection.start == selection.end; + let selection_start = snapshot.prev_line_boundary(selection.start).1; + let selection_end = snapshot.next_line_boundary(selection.end).1; + for row in cmp::max(selection_start.row(), start_row) + ..=cmp::min(selection_end.row(), end_row) + { + let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty); + *contains_non_empty_selection |= !is_empty; } } - selections + selections.insert( + view.replica_id(cx), + local_selections + .into_iter() + .map(|selection| crate::Selection { + id: selection.id, + goal: selection.goal, + reversed: selection.reversed, + start: selection.start.to_display_point(&display_map), + end: selection.end.to_display_point(&display_map), + }) + .collect(), + ); + + for (replica_id, selection) in display_map + .buffer_snapshot + .remote_selections_in_range(&(start_anchor..end_anchor)) + { + selections + .entry(replica_id) + .or_insert(Vec::new()) + .push(crate::Selection { + id: selection.id, + goal: selection.goal, + reversed: selection.reversed, + start: selection.start.to_display_point(&display_map), + end: selection.end.to_display_point(&display_map), + }); + } }); let line_number_layouts = self.layout_rows(start_row..end_row, &active_rows, &snapshot, cx); From 7c9e4e513c813a09bce5abdea0632da7374cf2bb Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 29 Dec 2021 23:11:54 -0800 Subject: [PATCH 8/8] Provide an accurate panic message when translating points off the end of a line Maybe we should fail more gracefully in this case, but I think we should at least make the message accurate and see how we do. --- crates/text/src/rope.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/text/src/rope.rs b/crates/text/src/rope.rs index 70399e15f544e4ecc77fef0efb19da2d84976071..89ce278de1a65a2f53658b188cf6452c7d973960 100644 --- a/crates/text/src/rope.rs +++ b/crates/text/src/rope.rs @@ -565,6 +565,12 @@ impl Chunk { if ch == '\n' { point.row += 1; + if point.row > target.row { + panic!( + "point {:?} is beyond the end of a line with length {}", + target, point.column + ); + } point.column = 0; } else { point.column += ch.len_utf8() as u32;