From 10b3fae2c3a780e94d7308009985e9dc8f77e14e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 25 Nov 2021 15:44:06 +0100 Subject: [PATCH 1/8] Implement `SelectNext` Co-Authored-By: Nathan Sobo --- Cargo.lock | 1 + crates/buffer/src/lib.rs | 19 ++- crates/buffer/src/rope.rs | 75 +++++++++-- crates/editor/Cargo.toml | 1 + crates/editor/src/display_map.rs | 6 + crates/editor/src/lib.rs | 208 +++++++++++++++++++++++-------- 6 files changed, 247 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 79c577aad84edf52c24b22900b8d16710e12dd1e..5b25f51ff283abd7be231be1b6d539c8a01e95b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1521,6 +1521,7 @@ dependencies = [ name = "editor" version = "0.1.0" dependencies = [ + "aho-corasick", "anyhow", "buffer", "clock", diff --git a/crates/buffer/src/lib.rs b/crates/buffer/src/lib.rs index 6d72571c1c8320e199de2ebe49dfcac02f3c3311..a75d487c1a28e03df713dcc06d773ec3abd00049 100644 --- a/crates/buffer/src/lib.rs +++ b/crates/buffer/src/lib.rs @@ -606,9 +606,8 @@ impl Buffer { self.text_for_range(range).flat_map(str::chars) } - pub fn bytes_at(&self, position: T) -> impl Iterator + '_ { - let offset = position.to_offset(self); - self.visible_text.bytes_at(offset) + pub fn bytes_in_range(&self, range: Range) -> rope::Bytes { + self.content().bytes_in_range(range) } pub fn contains_str_at(&self, position: T, needle: &str) -> bool @@ -618,7 +617,9 @@ impl Buffer { let position = position.to_offset(self); position == self.clip_offset(position, Bias::Left) && self - .bytes_at(position) + .bytes_in_range(position..self.len()) + .flatten() + .copied() .take(needle.len()) .eq(needle.bytes()) } @@ -1581,6 +1582,10 @@ impl Snapshot { self.visible_text.max_point() } + pub fn bytes_in_range(&self, range: Range) -> rope::Bytes { + self.content().bytes_in_range(range) + } + pub fn text_for_range(&self, range: Range) -> Chunks { self.content().text_for_range(range) } @@ -1716,6 +1721,12 @@ impl<'a> Content<'a> { self.visible_text.reversed_chars_at(offset) } + pub fn bytes_in_range(&self, range: Range) -> rope::Bytes<'a> { + let start = range.start.to_offset(self); + let end = range.end.to_offset(self); + self.visible_text.bytes_in_range(start..end) + } + pub fn text_for_range(&self, range: Range) -> Chunks<'a> { let start = range.start.to_offset(self); let end = range.end.to_offset(self); diff --git a/crates/buffer/src/rope.rs b/crates/buffer/src/rope.rs index 71a906fe5dcee851c0781299a32b5593fce1839e..f8170a4ac086f69adebf1749c107df66f0ee76b9 100644 --- a/crates/buffer/src/rope.rs +++ b/crates/buffer/src/rope.rs @@ -3,7 +3,7 @@ use crate::PointUtf16; use super::Point; use arrayvec::ArrayString; use smallvec::SmallVec; -use std::{cmp, fmt, mem, ops::Range, str}; +use std::{cmp, fmt, io, mem, ops::Range, str}; use sum_tree::{Bias, Dimension, SumTree}; #[cfg(test)] @@ -137,8 +137,8 @@ impl Rope { .flat_map(|chunk| chunk.chars().rev()) } - pub fn bytes_at(&self, start: usize) -> impl Iterator + '_ { - self.chunks_in_range(start..self.len()).flat_map(str::bytes) + pub fn bytes_in_range(&self, range: Range) -> Bytes { + Bytes::new(self, range) } pub fn chunks<'a>(&'a self) -> Chunks<'a> { @@ -444,6 +444,59 @@ impl<'a> Iterator for Chunks<'a> { } } +pub struct Bytes<'a> { + chunks: sum_tree::Cursor<'a, Chunk, usize>, + range: Range, +} + +impl<'a> Bytes<'a> { + pub fn new(rope: &'a Rope, range: Range) -> Self { + let mut chunks = rope.chunks.cursor(); + chunks.seek(&range.start, Bias::Right, &()); + Self { chunks, range } + } + + pub fn peek(&self) -> Option<&'a [u8]> { + let chunk = self.chunks.item()?; + let chunk_start = *self.chunks.start(); + if self.range.end <= chunk_start { + return None; + } + + let start = self.range.start.saturating_sub(chunk_start); + let end = self.range.end - chunk_start; + Some(&chunk.0.as_bytes()[start..chunk.0.len().min(end)]) + } +} + +impl<'a> Iterator for Bytes<'a> { + type Item = &'a [u8]; + + fn next(&mut self) -> Option { + let result = self.peek(); + if result.is_some() { + self.chunks.next(&()); + } + result + } +} + +impl<'a> io::Read for Bytes<'a> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + if let Some(chunk) = self.peek() { + let len = cmp::min(buf.len(), chunk.len()); + buf[..len].copy_from_slice(&chunk[..len]); + self.range.start += len; + if len == chunk.len() { + self.chunks.next(&()); + } + Ok(len) + } else { + Ok(0) + } + } +} + #[derive(Clone, Debug, Default)] struct Chunk(ArrayString<{ 2 * CHUNK_BASE }>); @@ -767,7 +820,7 @@ mod tests { use super::*; use crate::random_char_iter::RandomCharIter; use rand::prelude::*; - use std::{cmp::Ordering, env}; + use std::{cmp::Ordering, env, io::Read}; use Bias::{Left, Right}; #[test] @@ -843,10 +896,16 @@ mod tests { for _ in 0..5 { let end_ix = clip_offset(&expected, rng.gen_range(0..=expected.len()), Right); let start_ix = clip_offset(&expected, rng.gen_range(0..=end_ix), Left); - assert_eq!( - actual.chunks_in_range(start_ix..end_ix).collect::(), - &expected[start_ix..end_ix] - ); + + let actual_text = actual.chunks_in_range(start_ix..end_ix).collect::(); + assert_eq!(actual_text, &expected[start_ix..end_ix]); + + let mut actual_text = String::new(); + actual + .bytes_in_range(start_ix..end_ix) + .read_to_string(&mut actual_text) + .unwrap(); + assert_eq!(actual_text, &expected[start_ix..end_ix]); assert_eq!( actual diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 691eaa27d33011e64964d4ec47431c1ad0d4bd36..e2d4b312083020842c077398fb79da8599c128ad 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -20,6 +20,7 @@ sum_tree = { path = "../sum_tree" } theme = { path = "../theme" } util = { path = "../util" } workspace = { path = "../workspace" } +aho-corasick = "0.7" anyhow = "1.0" lazy_static = "1.4" log = "0.4" diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index d87c0e13a942d82b374c9f3ef31728a2e9ad28de..506faa27165eba1b2f70d41c296283d1c543c3e9 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -418,6 +418,12 @@ impl DisplayPoint { } } +impl ToDisplayPoint for usize { + fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint { + map.point_to_display_point(self.to_point(&map.buffer_snapshot), Bias::Left) + } +} + impl ToDisplayPoint for Point { fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint { map.point_to_display_point(*self, Bias::Left) diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 46a69c7ee61a70bd2f7fbe1d37bafbd3f9d6c5b2..ee417b3f6fb6dd08c731dfaa2edac472c5748c6c 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -6,6 +6,7 @@ pub mod movement; #[cfg(test)] mod test; +use aho_corasick::AhoCorasick; use buffer::rope::TextDimension; use clock::ReplicaId; use display_map::*; @@ -87,6 +88,7 @@ action!(SelectLine); action!(SplitSelectionIntoLines); action!(AddSelectionAbove); action!(AddSelectionBelow); +action!(SelectNext); action!(ToggleComments); action!(SelectLargerSyntaxNode); action!(SelectSmallerSyntaxNode); @@ -191,6 +193,7 @@ pub fn init(cx: &mut MutableAppContext, entry_openers: &mut Vec, next_selection_id: usize, add_selections_state: Option, + select_next_state: Option, autoclose_stack: Vec, select_larger_syntax_node_stack: Vec]>>, active_diagnostics: Option, @@ -373,6 +378,12 @@ struct AddSelectionsState { stack: Vec, } +struct SelectNextState { + query: AhoCorasick, + wordwise: bool, + done: bool, +} + #[derive(Debug)] struct BracketPairState { ranges: AnchorRangeSet, @@ -473,6 +484,7 @@ impl Editor { columnar_selection_tail: None, next_selection_id, add_selections_state: None, + select_next_state: None, autoclose_stack: Default::default(), select_larger_syntax_node_stack: Vec::new(), active_diagnostics: None, @@ -619,7 +631,7 @@ impl Editor { } match autoscroll { - Autoscroll::Closest => { + Autoscroll::Fit => { let margin = margin.min(3.0); let target_top = (first_cursor_top - margin).max(0.0); let target_bottom = last_cursor_bottom + margin; @@ -1000,7 +1012,7 @@ impl Editor { goal: selection.goal, }; if self.selections::(cx).next().is_none() { - self.update_selections(vec![selection], Some(Autoscroll::Closest), cx); + self.update_selections(vec![selection], Some(Autoscroll::Fit), cx); } } else { let mut oldest_selection = self.oldest_selection::(cx); @@ -1008,7 +1020,7 @@ impl Editor { oldest_selection.start = oldest_selection.head().clone(); oldest_selection.end = oldest_selection.head().clone(); } - self.update_selections(vec![oldest_selection], Some(Autoscroll::Closest), cx); + self.update_selections(vec![oldest_selection], Some(Autoscroll::Fit), cx); } } @@ -1201,7 +1213,7 @@ impl Editor { )) }); - self.update_selections(new_selections, Some(Autoscroll::Closest), cx); + self.update_selections(new_selections, Some(Autoscroll::Fit), cx); self.end_transaction(cx); #[derive(Default)] @@ -1241,7 +1253,7 @@ impl Editor { .collect(); }); - self.update_selections(new_selections, Some(Autoscroll::Closest), cx); + self.update_selections(new_selections, Some(Autoscroll::Fit), cx); self.end_transaction(cx); } @@ -1341,7 +1353,7 @@ impl Editor { }) .collect(); self.autoclose_stack.pop(); - self.update_selections(new_selections, Some(Autoscroll::Closest), cx); + self.update_selections(new_selections, Some(Autoscroll::Fit), cx); true } else { false @@ -1369,7 +1381,7 @@ impl Editor { selection.goal = SelectionGoal::None; } } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); self.insert("", cx); self.end_transaction(cx); } @@ -1388,7 +1400,7 @@ impl Editor { selection.goal = SelectionGoal::None; } } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); self.insert(&"", cx); self.end_transaction(cx); } @@ -1459,7 +1471,7 @@ impl Editor { } }); - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); self.end_transaction(cx); } @@ -1505,7 +1517,7 @@ impl Editor { self.update_selections( self.selections::(cx).collect(), - Some(Autoscroll::Closest), + Some(Autoscroll::Fit), cx, ); self.end_transaction(cx); @@ -1576,7 +1588,7 @@ impl Editor { .collect(); self.buffer .update(cx, |buffer, cx| buffer.edit(edit_ranges, "", cx)); - self.update_selections(new_selections, Some(Autoscroll::Closest), cx); + self.update_selections(new_selections, Some(Autoscroll::Fit), cx); self.end_transaction(cx); } @@ -1634,7 +1646,7 @@ impl Editor { } }); - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); self.end_transaction(cx); } @@ -1723,7 +1735,7 @@ impl Editor { } }); self.fold_ranges(new_folds, cx); - self.select_ranges(new_selection_ranges, Some(Autoscroll::Closest), cx); + self.select_ranges(new_selection_ranges, Some(Autoscroll::Fit), cx); self.end_transaction(cx); } @@ -1810,7 +1822,7 @@ impl Editor { } }); self.fold_ranges(new_folds, cx); - self.select_ranges(new_selection_ranges, Some(Autoscroll::Closest), cx); + self.select_ranges(new_selection_ranges, Some(Autoscroll::Fit), cx); self.end_transaction(cx); } @@ -1840,7 +1852,7 @@ impl Editor { }); } } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); self.insert("", cx); self.end_transaction(cx); @@ -1925,7 +1937,7 @@ impl Editor { selection.end = selection.start; }); } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); } else { self.insert(clipboard_text, cx); } @@ -1934,12 +1946,12 @@ impl Editor { pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext) { self.buffer.update(cx, |buffer, cx| buffer.undo(cx)); - self.request_autoscroll(Autoscroll::Closest, cx); + self.request_autoscroll(Autoscroll::Fit, cx); } pub fn redo(&mut self, _: &Redo, cx: &mut ViewContext) { self.buffer.update(cx, |buffer, cx| buffer.redo(cx)); - self.request_autoscroll(Autoscroll::Closest, cx); + self.request_autoscroll(Autoscroll::Fit, cx); } pub fn move_left(&mut self, _: &MoveLeft, cx: &mut ViewContext) { @@ -1961,7 +1973,7 @@ impl Editor { selection.reversed = false; selection.goal = SelectionGoal::None; } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); } pub fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext) { @@ -1975,7 +1987,7 @@ impl Editor { selection.set_head(cursor); selection.goal = SelectionGoal::None; } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); } pub fn move_right(&mut self, _: &MoveRight, cx: &mut ViewContext) { @@ -1997,7 +2009,7 @@ impl Editor { selection.reversed = false; selection.goal = SelectionGoal::None; } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); } pub fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext) { @@ -2011,7 +2023,7 @@ impl Editor { selection.set_head(cursor); selection.goal = SelectionGoal::None; } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); } pub fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext) { @@ -2036,7 +2048,7 @@ impl Editor { selection.goal = goal; selection.reversed = false; } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); } pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext) { @@ -2049,7 +2061,7 @@ impl Editor { selection.set_head(cursor); selection.goal = goal; } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); } pub fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext) { @@ -2074,7 +2086,7 @@ impl Editor { selection.goal = goal; selection.reversed = false; } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); } pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext) { @@ -2087,7 +2099,7 @@ impl Editor { selection.set_head(cursor); selection.goal = goal; } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); } pub fn move_to_previous_word_boundary( @@ -2105,7 +2117,7 @@ impl Editor { selection.reversed = false; selection.goal = SelectionGoal::None; } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); } pub fn select_to_previous_word_boundary( @@ -2121,7 +2133,7 @@ impl Editor { selection.set_head(cursor); selection.goal = SelectionGoal::None; } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); } pub fn delete_to_previous_word_boundary( @@ -2141,7 +2153,7 @@ impl Editor { selection.goal = SelectionGoal::None; } } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); self.insert("", cx); self.end_transaction(cx); } @@ -2161,7 +2173,7 @@ impl Editor { selection.reversed = false; selection.goal = SelectionGoal::None; } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); } pub fn select_to_next_word_boundary( @@ -2177,7 +2189,7 @@ impl Editor { selection.set_head(cursor); selection.goal = SelectionGoal::None; } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); } pub fn delete_to_next_word_boundary( @@ -2197,7 +2209,7 @@ impl Editor { selection.goal = SelectionGoal::None; } } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); self.insert("", cx); self.end_transaction(cx); } @@ -2218,7 +2230,7 @@ impl Editor { selection.reversed = false; selection.goal = SelectionGoal::None; } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); } pub fn select_to_beginning_of_line( @@ -2234,7 +2246,7 @@ impl Editor { selection.set_head(new_head.to_point(&display_map)); selection.goal = SelectionGoal::None; } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); } pub fn delete_to_beginning_of_line( @@ -2262,7 +2274,7 @@ impl Editor { selection.goal = SelectionGoal::None; } } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); } pub fn select_to_end_of_line(&mut self, _: &SelectToEndOfLine, cx: &mut ViewContext) { @@ -2274,7 +2286,7 @@ impl Editor { selection.set_head(new_head.to_point(&display_map)); selection.goal = SelectionGoal::None; } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); } pub fn delete_to_end_of_line(&mut self, _: &DeleteToEndOfLine, cx: &mut ViewContext) { @@ -2299,13 +2311,13 @@ impl Editor { reversed: false, goal: SelectionGoal::None, }; - self.update_selections(vec![selection], Some(Autoscroll::Closest), cx); + self.update_selections(vec![selection], Some(Autoscroll::Fit), cx); } pub fn select_to_beginning(&mut self, _: &SelectToBeginning, cx: &mut ViewContext) { let mut selection = self.selections::(cx).last().unwrap().clone(); selection.set_head(Point::zero()); - self.update_selections(vec![selection], Some(Autoscroll::Closest), cx); + self.update_selections(vec![selection], Some(Autoscroll::Fit), cx); } pub fn move_to_end(&mut self, _: &MoveToEnd, cx: &mut ViewContext) { @@ -2318,13 +2330,13 @@ impl Editor { reversed: false, goal: SelectionGoal::None, }; - self.update_selections(vec![selection], Some(Autoscroll::Closest), cx); + self.update_selections(vec![selection], Some(Autoscroll::Fit), cx); } pub fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext) { let mut selection = self.selections::(cx).last().unwrap().clone(); selection.set_head(self.buffer.read(cx).len()); - self.update_selections(vec![selection], Some(Autoscroll::Closest), cx); + self.update_selections(vec![selection], Some(Autoscroll::Fit), cx); } pub fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext) { @@ -2349,7 +2361,7 @@ impl Editor { selection.end = cmp::min(max_point, Point::new(rows.end, 0)); selection.reversed = false; } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); } pub fn split_selection_into_lines( @@ -2383,7 +2395,7 @@ impl Editor { to_unfold.push(selection.start..selection.end); } self.unfold_ranges(to_unfold, cx); - self.update_selections(new_selections, Some(Autoscroll::Closest), cx); + self.update_selections(new_selections, Some(Autoscroll::Fit), cx); } pub fn add_selection_above(&mut self, _: &AddSelectionAbove, cx: &mut ViewContext) { @@ -2481,12 +2493,102 @@ impl Editor { state.stack.pop(); } - self.update_selections(new_selections, Some(Autoscroll::Closest), cx); + self.update_selections(new_selections, Some(Autoscroll::Fit), cx); if state.stack.len() > 1 { self.add_selections_state = Some(state); } } + pub fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext) { + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let buffer = &display_map.buffer_snapshot; + let mut selections = self.selections::(cx).collect::>(); + if let Some(mut select_next_state) = self.select_next_state.take() { + let query = &select_next_state.query; + if !select_next_state.done { + let first_selection = selections.iter().min_by_key(|s| s.id).unwrap(); + let last_selection = selections.iter().max_by_key(|s| s.id).unwrap(); + let mut next_selected_range = None; + + let bytes_after_last_selection = + buffer.bytes_in_range(last_selection.end..buffer.len()); + let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start); + let query_matches = query + .stream_find_iter(bytes_after_last_selection) + .map(|result| (last_selection.end, result)) + .chain( + query + .stream_find_iter(bytes_before_first_selection) + .map(|result| (0, result)), + ); + for (start_offset, query_match) in query_matches { + let query_match = query_match.unwrap(); // can only fail due to I/O + let offset_range = + start_offset + query_match.start()..start_offset + query_match.end(); + let display_range = offset_range.start.to_display_point(&display_map) + ..offset_range.end.to_display_point(&display_map); + + if !select_next_state.wordwise + || (!movement::is_inside_word(&display_map, display_range.start) + && !movement::is_inside_word(&display_map, display_range.end)) + { + next_selected_range = Some(offset_range); + break; + } + } + + if let Some(next_selected_range) = next_selected_range { + selections.push(Selection { + id: post_inc(&mut self.next_selection_id), + start: next_selected_range.start, + end: next_selected_range.end, + reversed: false, + goal: SelectionGoal::None, + }); + selections.sort_unstable_by_key(|s| s.start); + self.update_selections(selections, Some(Autoscroll::Fit), cx); + } else { + select_next_state.done = true; + } + } + + self.select_next_state = Some(select_next_state); + } else if selections.len() == 1 { + let selection = selections.last_mut().unwrap(); + if selection.start == selection.end { + let word_range = movement::surrounding_word( + &display_map, + selection.start.to_display_point(&display_map), + ); + selection.start = word_range.start.to_offset(&display_map, Bias::Left); + selection.end = word_range.end.to_offset(&display_map, Bias::Left); + selection.goal = SelectionGoal::None; + selection.reversed = false; + + let query = buffer + .text_for_range(selection.start..selection.end) + .collect::(); + let select_state = SelectNextState { + query: AhoCorasick::new_auto_configured(&[query]), + wordwise: true, + done: false, + }; + self.update_selections(selections, Some(Autoscroll::Fit), cx); + self.select_next_state = Some(select_state); + } else { + let query = buffer + .text_for_range(selection.start..selection.end) + .collect::(); + self.select_next_state = Some(SelectNextState { + query: AhoCorasick::new_auto_configured(&[query]), + wordwise: false, + done: false, + }); + self.select_next(&SelectNext, cx); + } + } + } + pub fn toggle_comments(&mut self, _: &ToggleComments, cx: &mut ViewContext) { // 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. @@ -2529,7 +2631,10 @@ impl Editor { } let start = Point::new(row, buffer.indent_column_for_line(row)); - let mut line_bytes = buffer.bytes_at(start); + let mut line_bytes = buffer + .bytes_in_range(start..buffer.max_point()) + .flatten() + .copied(); // If this line currently begins with the line comment prefix, then record // the range containing the prefix. @@ -2575,7 +2680,7 @@ impl Editor { self.update_selections( self.selections::(cx).collect(), - Some(Autoscroll::Closest), + Some(Autoscroll::Fit), cx, ); self.end_transaction(cx); @@ -2622,7 +2727,7 @@ impl Editor { if selected_larger_node { stack.push(old_selections); new_selections.sort_unstable_by_key(|selection| selection.start); - self.update_selections(new_selections, Some(Autoscroll::Closest), cx); + self.update_selections(new_selections, Some(Autoscroll::Fit), cx); } self.select_larger_syntax_node_stack = stack; } @@ -2634,7 +2739,7 @@ impl Editor { ) { let mut stack = mem::take(&mut self.select_larger_syntax_node_stack); if let Some(selections) = stack.pop() { - self.update_selections(selections.to_vec(), Some(Autoscroll::Closest), cx); + self.update_selections(selections.to_vec(), Some(Autoscroll::Fit), cx); } self.select_larger_syntax_node_stack = stack; } @@ -2663,7 +2768,7 @@ impl Editor { } } - self.update_selections(selections, Some(Autoscroll::Closest), cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); } pub fn show_next_diagnostic(&mut self, _: &ShowNextDiagnostic, cx: &mut ViewContext) { @@ -3061,6 +3166,7 @@ impl Editor { self.pending_selection = None; self.add_selections_state = None; + self.select_next_state = None; self.select_larger_syntax_node_stack.clear(); while let Some(autoclose_pair_state) = self.autoclose_stack.last() { let all_selections_inside_autoclose_ranges = @@ -3219,7 +3325,7 @@ impl Editor { fn fold_ranges(&mut self, ranges: Vec>, cx: &mut ViewContext) { if !ranges.is_empty() { self.display_map.update(cx, |map, cx| map.fold(ranges, cx)); - self.request_autoscroll(Autoscroll::Closest, cx); + self.request_autoscroll(Autoscroll::Fit, cx); cx.notify(); } } @@ -3228,7 +3334,7 @@ impl Editor { if !ranges.is_empty() { self.display_map .update(cx, |map, cx| map.unfold(ranges, cx)); - self.request_autoscroll(Autoscroll::Closest, cx); + self.request_autoscroll(Autoscroll::Fit, cx); cx.notify(); } } From 861893b7b69718bec8de8dd2a97b25e1b1aeb48e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 25 Nov 2021 16:02:39 +0100 Subject: [PATCH 2/8] Autoscroll vertically to the newest selection on `SelectNext` Co-Authored-By: Nathan Sobo --- crates/editor/src/lib.rs | 64 ++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index ee417b3f6fb6dd08c731dfaa2edac472c5748c6c..0d674c884341e9b5a9dbdbb0d4a3fa025a4a6a27 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -315,9 +315,11 @@ enum SelectMode { All, } +#[derive(PartialEq, Eq)] pub enum Autoscroll { Fit, Center, + Newest, } #[derive(Copy, Clone, PartialEq, Eq)] @@ -607,20 +609,29 @@ impl Editor { return false; }; - let mut selections = self.selections::(cx).peekable(); - let first_cursor_top = selections - .peek() - .unwrap() - .head() - .to_display_point(&display_map) - .row() as f32; - let last_cursor_bottom = selections - .last() - .unwrap() - .head() - .to_display_point(&display_map) - .row() as f32 - + 1.0; + let first_cursor_top; + let last_cursor_bottom; + if autoscroll == Autoscroll::Newest { + let newest_selection = self.newest_selection::(cx); + first_cursor_top = newest_selection.head().to_display_point(&display_map).row() as f32; + last_cursor_bottom = first_cursor_top + 1.; + } else { + let mut selections = self.selections::(cx).peekable(); + first_cursor_top = selections + .peek() + .unwrap() + .head() + .to_display_point(&display_map) + .row() as f32; + last_cursor_bottom = selections + .last() + .unwrap() + .head() + .to_display_point(&display_map) + .row() as f32 + + 1.0; + } + let margin = if matches!(self.mode, EditorMode::AutoHeight { .. }) { 0. } else { @@ -631,7 +642,7 @@ impl Editor { } match autoscroll { - Autoscroll::Fit => { + Autoscroll::Fit | Autoscroll::Newest => { let margin = margin.min(3.0); let target_top = (first_cursor_top - margin).max(0.0); let target_bottom = last_cursor_bottom + margin; @@ -670,14 +681,17 @@ impl Editor { let mut target_right = 0.0_f32; for selection in selections { let head = selection.head().to_display_point(&display_map); - let start_column = head.column().saturating_sub(3); - let end_column = cmp::min(display_map.line_len(head.row()), head.column() + 3); - target_left = target_left - .min(layouts[(head.row() - start_row) as usize].x_for_index(start_column as usize)); - target_right = target_right.max( - layouts[(head.row() - start_row) as usize].x_for_index(end_column as usize) - + max_glyph_width, - ); + if head.row() >= start_row && head.row() < start_row + layouts.len() as u32 { + let start_column = head.column().saturating_sub(3); + let end_column = cmp::min(display_map.line_len(head.row()), head.column() + 3); + target_left = target_left.min( + layouts[(head.row() - start_row) as usize].x_for_index(start_column as usize), + ); + target_right = target_right.max( + layouts[(head.row() - start_row) as usize].x_for_index(end_column as usize) + + max_glyph_width, + ); + } } target_right = target_right.min(scroll_width); @@ -2546,7 +2560,7 @@ impl Editor { goal: SelectionGoal::None, }); selections.sort_unstable_by_key(|s| s.start); - self.update_selections(selections, Some(Autoscroll::Fit), cx); + self.update_selections(selections, Some(Autoscroll::Newest), cx); } else { select_next_state.done = true; } @@ -2573,7 +2587,7 @@ impl Editor { wordwise: true, done: false, }; - self.update_selections(selections, Some(Autoscroll::Fit), cx); + self.update_selections(selections, Some(Autoscroll::Newest), cx); self.select_next_state = Some(select_state); } else { let query = buffer From f42fd8e1bb594755e179d032b2be4bb34833b25f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 25 Nov 2021 16:45:06 +0100 Subject: [PATCH 3/8] Return `Selection`s from `Editor::selections_in_range` Co-Authored-By: Nathan Sobo --- crates/editor/src/element.rs | 53 +++++++++++++++--------------------- crates/editor/src/lib.rs | 36 ++++++++++++++++++------ 2 files changed, 49 insertions(+), 40 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index feab5da26b4c27fb3973fade0201c06e18a02fa5..570a64f5e245db049577090e6589f092645009f2 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -380,13 +380,12 @@ impl EditorElement { for selection in selections { if selection.start != selection.end { - let range_start = cmp::min(selection.start, selection.end); - let range_end = cmp::max(selection.start, selection.end); - let row_range = if range_end.column() == 0 { - cmp::max(range_start.row(), start_row)..cmp::min(range_end.row(), end_row) + let row_range = if selection.end.column() == 0 { + cmp::max(selection.start.row(), start_row) + ..cmp::min(selection.end.row(), end_row) } else { - cmp::max(range_start.row(), start_row) - ..cmp::min(range_end.row() + 1, end_row) + cmp::max(selection.start.row(), start_row) + ..cmp::min(selection.end.row() + 1, end_row) }; let selection = Selection { @@ -399,16 +398,18 @@ impl EditorElement { .map(|row| { let line_layout = &layout.line_layouts[(row - start_row) as usize]; SelectionLine { - start_x: if row == range_start.row() { + start_x: if row == selection.start.row() { content_origin.x() - + line_layout.x_for_index(range_start.column() as usize) + + line_layout + .x_for_index(selection.start.column() as usize) - scroll_left } else { content_origin.x() - scroll_left }, - end_x: if row == range_end.row() { + end_x: if row == selection.end.row() { content_origin.x() - + line_layout.x_for_index(range_end.column() as usize) + + line_layout + .x_for_index(selection.end.column() as usize) - scroll_left } else { content_origin.x() @@ -425,13 +426,13 @@ impl EditorElement { } if view.show_local_cursors() || *replica_id != local_replica_id { - let cursor_position = selection.end; + let cursor_position = selection.head(); if (start_row..end_row).contains(&cursor_position.row()) { let cursor_row_layout = - &layout.line_layouts[(selection.end.row() - start_row) as usize]; - let x = cursor_row_layout.x_for_index(selection.end.column() as usize) + &layout.line_layouts[(cursor_position.row() - start_row) as usize]; + let x = cursor_row_layout.x_for_index(cursor_position.column() as usize) - scroll_left; - let y = selection.end.row() as f32 * layout.line_height - scroll_top; + let y = cursor_position.row() as f32 * layout.line_height - scroll_top; cursors.push(Cursor { color: style.cursor, origin: content_origin + vec2f(x, y), @@ -747,26 +748,16 @@ impl Element for EditorElement { self.update_view(cx.app, |view, cx| { highlighted_row = view.highlighted_row(); for selection_set_id in view.active_selection_sets(cx).collect::>() { - let mut set = Vec::new(); - for selection in view.selections_in_range( + let replica_selections = view.selections_in_range( selection_set_id, DisplayPoint::new(start_row, 0)..DisplayPoint::new(end_row, 0), cx, - ) { - set.push(selection.clone()); + ); + for selection in &replica_selections { if selection_set_id == view.selection_set_id { let is_empty = selection.start == selection.end; - let mut selection_start; - let mut selection_end; - if selection.start < selection.end { - selection_start = selection.start; - selection_end = selection.end; - } else { - selection_start = selection.end; - selection_end = selection.start; - }; - selection_start = snapshot.prev_row_boundary(selection_start).0; - selection_end = snapshot.next_row_boundary(selection_end).0; + 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) { @@ -777,7 +768,7 @@ impl Element for EditorElement { } } - selections.insert(selection_set_id.replica_id, set); + selections.insert(selection_set_id.replica_id, replica_selections); } }); @@ -939,7 +930,7 @@ pub struct LayoutState { line_height: f32, em_width: f32, em_advance: f32, - selections: HashMap>>, + selections: HashMap>>, overscroll: Vector2F, text_offset: Vector2F, max_visible_line_width: f32, diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 0d674c884341e9b5a9dbdbb0d4a3fa025a4a6a27..80199a8e8ed762f02db6a90e3ccb2a082ddef551 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -3022,7 +3022,7 @@ impl Editor { set_id: SelectionSetId, range: Range, cx: &'a mut MutableAppContext, - ) -> impl 'a + Iterator> { + ) -> Vec> { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = self.buffer.read(cx); let selections = self @@ -3036,13 +3036,16 @@ impl Editor { let start_index = self.selection_insertion_index(&selections, start); let pending_selection = if set_id == self.selection_set_id { self.pending_selection.as_ref().and_then(|pending| { - let mut selection_start = pending.selection.start.to_display_point(&display_map); - let mut selection_end = pending.selection.end.to_display_point(&display_map); - if pending.selection.reversed { - mem::swap(&mut selection_start, &mut selection_end); - } + let selection_start = pending.selection.start.to_display_point(&display_map); + let selection_end = pending.selection.end.to_display_point(&display_map); if selection_start <= range.end || selection_end <= range.end { - Some(selection_start..selection_end) + Some(Selection { + id: pending.selection.id, + start: selection_start, + end: selection_end, + reversed: pending.selection.reversed, + goal: pending.selection.goal, + }) } else { None } @@ -3053,9 +3056,16 @@ impl Editor { selections .into_iter() .skip(start_index) - .map(move |s| s.display_range(&display_map)) + .map(move |s| Selection { + id: s.id, + start: s.start.to_display_point(&display_map), + end: s.end.to_display_point(&display_map), + reversed: s.reversed, + goal: s.goal, + }) .take_while(move |r| r.start <= range.end || r.end <= range.end) .chain(pending_selection) + .collect() } fn selection_insertion_index(&self, selections: &[Selection], start: Point) -> usize { @@ -5689,7 +5699,15 @@ mod tests { DisplayPoint::zero()..self.max_point(cx), cx, ) - .collect::>() + .into_iter() + .map(|s| { + if s.reversed { + s.end..s.start + } else { + s.start..s.end + } + }) + .collect() } } From 2f43ef67fdd4c0354f8f16fae34d1c65615dfd38 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 25 Nov 2021 16:53:10 +0100 Subject: [PATCH 4/8] Allow a single start/end bias per `AnchorRangeMap` Co-Authored-By: Nathan Sobo --- crates/buffer/src/anchor.rs | 37 ++++++++++++++++++------------------ crates/buffer/src/lib.rs | 27 ++++++++++++-------------- crates/language/src/proto.rs | 17 ++++++++++++----- 3 files changed, 43 insertions(+), 38 deletions(-) diff --git a/crates/buffer/src/anchor.rs b/crates/buffer/src/anchor.rs index 90a01dd102fb4eb45ea3371f31c2a3ded7de5f75..50f1d897c8d370427fc64986dde81d53e5f6e47d 100644 --- a/crates/buffer/src/anchor.rs +++ b/crates/buffer/src/anchor.rs @@ -29,7 +29,9 @@ pub struct AnchorSet(pub(crate) AnchorMap<()>); #[derive(Clone)] pub struct AnchorRangeMap { pub(crate) version: clock::Global, - pub(crate) entries: Vec<(Range<(FullOffset, Bias)>, T)>, + pub(crate) entries: Vec<(Range, T)>, + pub(crate) start_bias: Bias, + pub(crate) end_bias: Bias, } #[derive(Clone)] @@ -174,9 +176,16 @@ impl AnchorRangeMap { pub fn from_full_offset_ranges( version: clock::Global, - entries: Vec<(Range<(FullOffset, Bias)>, T)>, + start_bias: Bias, + end_bias: Bias, + entries: Vec<(Range, T)>, ) -> Self { - Self { version, entries } + Self { + version, + start_bias, + end_bias, + entries, + } } pub fn ranges<'a, D>( @@ -190,10 +199,8 @@ impl AnchorRangeMap { content.summaries_for_anchor_ranges(self) } - pub fn full_offset_ranges(&self) -> impl Iterator, &T)> { - self.entries - .iter() - .map(|(range, value)| (range.start.0..range.end.0, value)) + pub fn full_offset_ranges(&self) -> impl Iterator, T)> { + self.entries.iter() } pub fn min_by_key<'a, C, D, F, K>( @@ -232,25 +239,19 @@ impl AnchorRangeMap { .map(|(range, value)| (self.resolve_range(range, &content), value)) } - fn resolve_range<'a, D>( - &self, - range: &Range<(FullOffset, Bias)>, - content: &Content<'a>, - ) -> Range + fn resolve_range<'a, D>(&self, range: &Range, content: &Content<'a>) -> Range where D: 'a + TextDimension<'a>, { - let (start, start_bias) = range.start; let mut anchor = Anchor { - full_offset: start, - bias: start_bias, + full_offset: range.start, + bias: self.start_bias, version: self.version.clone(), }; let start = content.summary_for_anchor(&anchor); - let (end, end_bias) = range.end; - anchor.full_offset = end; - anchor.bias = end_bias; + anchor.full_offset = range.end; + anchor.bias = self.end_bias; let end = content.summary_for_anchor(&anchor); start..end diff --git a/crates/buffer/src/lib.rs b/crates/buffer/src/lib.rs index a75d487c1a28e03df713dcc06d773ec3abd00049..e6fb6fb0cad1240d482fe3da1389a4f4c4f2870e 100644 --- a/crates/buffer/src/lib.rs +++ b/crates/buffer/src/lib.rs @@ -1818,27 +1818,22 @@ impl<'a> Content<'a> { let mut rope_cursor = self.visible_text.cursor(0); let mut cursor = self.fragments.cursor::<(VersionedFullOffset, usize)>(); map.entries.iter().map(move |(range, value)| { - let Range { - start: (start_offset, start_bias), - end: (end_offset, end_bias), - } = range; - cursor.seek_forward( - &VersionedFullOffset::Offset(*start_offset), - *start_bias, + &VersionedFullOffset::Offset(range.start), + map.start_bias, &cx, ); let overshoot = if cursor.item().map_or(false, |fragment| fragment.visible) { - *start_offset - cursor.start().0.full_offset() + range.start - cursor.start().0.full_offset() } else { 0 }; summary.add_assign(&rope_cursor.summary::(cursor.start().1 + overshoot)); let start_summary = summary.clone(); - cursor.seek_forward(&VersionedFullOffset::Offset(*end_offset), *end_bias, &cx); + cursor.seek_forward(&VersionedFullOffset::Offset(range.end), map.end_bias, &cx); let overshoot = if cursor.item().map_or(false, |fragment| fragment.visible) { - *end_offset - cursor.start().0.full_offset() + range.end - cursor.start().0.full_offset() } else { 0 }; @@ -1901,14 +1896,16 @@ impl<'a> Content<'a> { let full_start_offset = FullOffset(cursor.start().deleted + start_offset); cursor.seek_forward(&end_offset, end_bias, &None); let full_end_offset = FullOffset(cursor.start().deleted + end_offset); - ( - (full_start_offset, start_bias)..(full_end_offset, end_bias), - value, - ) + (full_start_offset..full_end_offset, value) }) .collect(); - AnchorRangeMap { version, entries } + AnchorRangeMap { + version, + start_bias, + end_bias, + entries, + } } pub fn anchor_set(&self, bias: Bias, entries: E) -> AnchorSet diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index def2172319accb6107ba09a371e50d06ba250383..23a6e2b656471403ac8f1b6ff3626358b0041a00 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -194,8 +194,8 @@ pub fn deserialize_operation(message: proto::Operation) -> Result { .selections .iter() .map(|selection| { - let range = (FullOffset(selection.start as usize), Bias::Left) - ..(FullOffset(selection.end as usize), Bias::Right); + let range = FullOffset(selection.start as usize) + ..FullOffset(selection.end as usize); let state = SelectionState { id: selection.id as usize, reversed: selection.reversed, @@ -204,7 +204,12 @@ pub fn deserialize_operation(message: proto::Operation) -> Result { (range, state) }) .collect(); - let selections = AnchorRangeMap::from_full_offset_ranges(version, entries); + let selections = AnchorRangeMap::from_full_offset_ranges( + version, + Bias::Left, + Bias::Left, + entries, + ); Operation::Buffer(buffer::Operation::UpdateSelections { set_id: clock::Lamport { @@ -276,11 +281,13 @@ pub fn deserialize_selection_set(set: proto::SelectionSet) -> SelectionSet { active: set.is_active, selections: Arc::new(AnchorRangeMap::from_full_offset_ranges( set.version.into(), + Bias::Left, + Bias::Left, set.selections .into_iter() .map(|selection| { - let range = (FullOffset(selection.start as usize), Bias::Left) - ..(FullOffset(selection.end as usize), Bias::Left); + let range = + FullOffset(selection.start as usize)..FullOffset(selection.end as usize); let state = SelectionState { id: selection.id as usize, reversed: selection.reversed, From 2f78d93383bdc329b172bc5219a9641c66444b56 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 25 Nov 2021 17:03:06 +0100 Subject: [PATCH 5/8] Make `summaries_for_anchors`/`summaries_for_anchor_ranges` more generic Co-Authored-By: Nathan Sobo --- crates/buffer/src/anchor.rs | 17 +++++++++++--- crates/buffer/src/lib.rs | 44 +++++++++++++++++++++---------------- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/crates/buffer/src/anchor.rs b/crates/buffer/src/anchor.rs index 50f1d897c8d370427fc64986dde81d53e5f6e47d..50d0c2f92765e4a55c67736c8551eb9bc58398e4 100644 --- a/crates/buffer/src/anchor.rs +++ b/crates/buffer/src/anchor.rs @@ -142,8 +142,12 @@ impl AnchorMap { { let content = content.into(); content - .summaries_for_anchors(self) - .map(move |(sum, value)| (sum, value)) + .summaries_for_anchors( + self.version.clone(), + self.bias, + self.entries.iter().map(|e| &e.0), + ) + .zip(self.entries.iter().map(|e| &e.1)) } } @@ -196,7 +200,14 @@ impl AnchorRangeMap { D: 'a + TextDimension<'a>, { let content = content.into(); - content.summaries_for_anchor_ranges(self) + content + .summaries_for_anchor_ranges( + self.version.clone(), + self.start_bias, + self.end_bias, + self.entries.iter().map(|e| &e.0), + ) + .zip(self.entries.iter().map(|e| &e.1)) } pub fn full_offset_ranges(&self) -> impl Iterator, T)> { diff --git a/crates/buffer/src/lib.rs b/crates/buffer/src/lib.rs index e6fb6fb0cad1240d482fe3da1389a4f4c4f2870e..81288e7c41e0ca761664dda6a2ac261c34a11c4b 100644 --- a/crates/buffer/src/lib.rs +++ b/crates/buffer/src/lib.rs @@ -1786,43 +1786,49 @@ impl<'a> Content<'a> { self.visible_text.cursor(range.start).summary(range.end) } - fn summaries_for_anchors(&self, map: &'a AnchorMap) -> impl Iterator + fn summaries_for_anchors( + &self, + version: clock::Global, + bias: Bias, + ranges: I, + ) -> impl 'a + Iterator where - D: TextDimension<'a>, + D: 'a + TextDimension<'a>, + I: 'a + IntoIterator, { - let cx = Some(map.version.clone()); + let cx = Some(version.clone()); let mut summary = D::default(); let mut rope_cursor = self.visible_text.cursor(0); let mut cursor = self.fragments.cursor::<(VersionedFullOffset, usize)>(); - map.entries.iter().map(move |(offset, value)| { - cursor.seek_forward(&VersionedFullOffset::Offset(*offset), map.bias, &cx); + ranges.into_iter().map(move |offset| { + cursor.seek_forward(&VersionedFullOffset::Offset(*offset), bias, &cx); let overshoot = if cursor.item().map_or(false, |fragment| fragment.visible) { *offset - cursor.start().0.full_offset() } else { 0 }; summary.add_assign(&rope_cursor.summary(cursor.start().1 + overshoot)); - (summary.clone(), value) + summary.clone() }) } - fn summaries_for_anchor_ranges( + fn summaries_for_anchor_ranges( &self, - map: &'a AnchorRangeMap, - ) -> impl Iterator, &'a T)> + version: clock::Global, + start_bias: Bias, + end_bias: Bias, + ranges: I, + ) -> impl 'a + Iterator> where - D: TextDimension<'a>, + D: 'a + TextDimension<'a>, + I: 'a + IntoIterator>, { - let cx = Some(map.version.clone()); + let cx = Some(version); let mut summary = D::default(); let mut rope_cursor = self.visible_text.cursor(0); let mut cursor = self.fragments.cursor::<(VersionedFullOffset, usize)>(); - map.entries.iter().map(move |(range, value)| { - cursor.seek_forward( - &VersionedFullOffset::Offset(range.start), - map.start_bias, - &cx, - ); + ranges.into_iter().map(move |range| { + cursor.seek_forward(&VersionedFullOffset::Offset(range.start), start_bias, &cx); let overshoot = if cursor.item().map_or(false, |fragment| fragment.visible) { range.start - cursor.start().0.full_offset() } else { @@ -1831,7 +1837,7 @@ impl<'a> Content<'a> { summary.add_assign(&rope_cursor.summary::(cursor.start().1 + overshoot)); let start_summary = summary.clone(); - cursor.seek_forward(&VersionedFullOffset::Offset(range.end), map.end_bias, &cx); + cursor.seek_forward(&VersionedFullOffset::Offset(range.end), end_bias, &cx); let overshoot = if cursor.item().map_or(false, |fragment| fragment.visible) { range.end - cursor.start().0.full_offset() } else { @@ -1840,7 +1846,7 @@ impl<'a> Content<'a> { summary.add_assign(&rope_cursor.summary::(cursor.start().1 + overshoot)); let end_summary = summary.clone(); - (start_summary..end_summary, value) + start_summary..end_summary }) } From 09a53a0c6441c0a7f392115f1f8bbd2477df385e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 25 Nov 2021 17:11:30 +0100 Subject: [PATCH 6/8] WIP --- crates/buffer/src/anchor.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/crates/buffer/src/anchor.rs b/crates/buffer/src/anchor.rs index 50d0c2f92765e4a55c67736c8551eb9bc58398e4..f2575ffa0497482e8505c526fb4b22e93adaccfd 100644 --- a/crates/buffer/src/anchor.rs +++ b/crates/buffer/src/anchor.rs @@ -210,6 +210,31 @@ impl AnchorRangeMap { .zip(self.entries.iter().map(|e| &e.1)) } + pub fn intersecting_ranges<'a, D, T>( + &'a self, + range: Range<(T, Bias)>, + content: impl Into> + 'a, + ) -> impl Iterator, &'a T)> + 'a + where + D: 'a + TextDimension<'a>, + T: ToOffset, + { + let content = content.into(); + let range = content.anchor_at(range.start.0, range.start.1) + ..content.anchor_at(range.end.0, range.end.1); + + let mut probe_anchor = Anchor { + full_offset: Default::default(), + bias: self.start_bias, + version: self.version.clone(), + }; + let start_ix = self.entries.binary_search_by(|probe| { + probe_anchor.full_offset = probe.0.start; + probe_anchor.cmp(&range.start, &content).unwrap() + }); + std::iter::empty() + } + pub fn full_offset_ranges(&self) -> impl Iterator, T)> { self.entries.iter() } From d249618ee6f5e412558d0056bc2bf5bb5a9357de Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 25 Nov 2021 13:19:49 -0700 Subject: [PATCH 7/8] Improve range-based selection queries to only resolve the requested selections --- crates/buffer/src/anchor.rs | 20 ++++++++++++---- crates/buffer/src/selection.rs | 23 ++++++++++++++++++ crates/editor/src/element.rs | 12 ++++++---- crates/editor/src/lib.rs | 43 +++++++++++----------------------- 4 files changed, 59 insertions(+), 39 deletions(-) diff --git a/crates/buffer/src/anchor.rs b/crates/buffer/src/anchor.rs index f2575ffa0497482e8505c526fb4b22e93adaccfd..c158cc36e11296214e4bee3a1d7caf47c53b7348 100644 --- a/crates/buffer/src/anchor.rs +++ b/crates/buffer/src/anchor.rs @@ -210,14 +210,14 @@ impl AnchorRangeMap { .zip(self.entries.iter().map(|e| &e.1)) } - pub fn intersecting_ranges<'a, D, T>( + pub fn intersecting_ranges<'a, D, I>( &'a self, - range: Range<(T, Bias)>, + range: Range<(I, Bias)>, content: impl Into> + 'a, ) -> impl Iterator, &'a T)> + 'a where D: 'a + TextDimension<'a>, - T: ToOffset, + I: ToOffset, { let content = content.into(); let range = content.anchor_at(range.start.0, range.start.1) @@ -229,10 +229,20 @@ impl AnchorRangeMap { version: self.version.clone(), }; let start_ix = self.entries.binary_search_by(|probe| { - probe_anchor.full_offset = probe.0.start; + probe_anchor.full_offset = probe.0.end; probe_anchor.cmp(&range.start, &content).unwrap() }); - std::iter::empty() + + match start_ix { + Ok(start_ix) | Err(start_ix) => content + .summaries_for_anchor_ranges( + self.version.clone(), + self.start_bias, + self.end_bias, + self.entries[start_ix..].iter().map(|e| &e.0), + ) + .zip(self.entries.iter().map(|e| &e.1)), + } } pub fn full_offset_ranges(&self) -> impl Iterator, T)> { diff --git a/crates/buffer/src/selection.rs b/crates/buffer/src/selection.rs index b90a6fa10579a63aaace2877cf722d40de53e9eb..bca0b7663feaf69d68482014513bf114f9bfbe3e 100644 --- a/crates/buffer/src/selection.rs +++ b/crates/buffer/src/selection.rs @@ -1,3 +1,5 @@ +use sum_tree::Bias; + use crate::rope::TextDimension; use super::{AnchorRangeMap, Buffer, Content, Point, ToOffset, ToPoint}; @@ -117,6 +119,27 @@ impl SelectionSet { }) } + pub fn intersecting_selections<'a, D, I, C>( + &'a self, + range: Range<(I, Bias)>, + content: C, + ) -> impl 'a + Iterator> + where + D: 'a + TextDimension<'a>, + I: 'a + ToOffset, + C: 'a + Into>, + { + self.selections + .intersecting_ranges(range, content) + .map(|(range, state)| Selection { + id: state.id, + start: range.start, + end: range.end, + reversed: state.reversed, + goal: state.goal, + }) + } + pub fn oldest_selection<'a, D, C>(&'a self, content: C) -> Option> where D: 'a + TextDimension<'a>, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 570a64f5e245db049577090e6589f092645009f2..03b04c8c6bf391bbed0378b3223609589f18a754 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -748,11 +748,13 @@ impl Element for EditorElement { self.update_view(cx.app, |view, cx| { highlighted_row = view.highlighted_row(); for selection_set_id in view.active_selection_sets(cx).collect::>() { - let replica_selections = view.selections_in_range( - selection_set_id, - DisplayPoint::new(start_row, 0)..DisplayPoint::new(end_row, 0), - cx, - ); + let replica_selections = view + .intersecting_selections( + selection_set_id, + DisplayPoint::new(start_row, 0)..DisplayPoint::new(end_row, 0), + cx, + ) + .collect::>(); for selection in &replica_selections { if selection_set_id == view.selection_set_id { let is_empty = selection.start == selection.end; diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 80199a8e8ed762f02db6a90e3ccb2a082ddef551..0404c725083eb62ae1150ba360ca29ef01253b05 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -3017,23 +3017,15 @@ impl Editor { .map(|(set_id, _)| *set_id) } - pub fn selections_in_range<'a>( + pub fn intersecting_selections<'a>( &'a self, set_id: SelectionSetId, range: Range, cx: &'a mut MutableAppContext, - ) -> Vec> { + ) -> impl 'a + Iterator> { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = self.buffer.read(cx); - let selections = self - .buffer - .read(cx) - .selection_set(set_id) - .unwrap() - .selections::(buffer) - .collect::>(); - let start = range.start.to_point(&display_map); - let start_index = self.selection_insertion_index(&selections, start); + let pending_selection = if set_id == self.selection_set_id { self.pending_selection.as_ref().and_then(|pending| { let selection_start = pending.selection.start.to_display_point(&display_map); @@ -3053,9 +3045,17 @@ impl Editor { } else { None }; + + let range = (range.start.to_offset(&display_map, Bias::Left), Bias::Left) + ..(range.end.to_offset(&display_map, Bias::Left), Bias::Right); + let selections = self + .buffer + .read(cx) + .selection_set(set_id) + .unwrap() + .intersecting_selections::(range, buffer); + selections - .into_iter() - .skip(start_index) .map(move |s| Selection { id: s.id, start: s.start.to_display_point(&display_map), @@ -3063,22 +3063,7 @@ impl Editor { reversed: s.reversed, goal: s.goal, }) - .take_while(move |r| r.start <= range.end || r.end <= range.end) .chain(pending_selection) - .collect() - } - - fn selection_insertion_index(&self, selections: &[Selection], start: Point) -> usize { - match selections.binary_search_by_key(&start, |probe| probe.start) { - Ok(index) => index, - Err(index) => { - if index > 0 && selections[index - 1].end > start { - index - 1 - } else { - index - } - } - } } pub fn selections<'a, D>(&self, cx: &'a AppContext) -> impl 'a + Iterator> @@ -5694,7 +5679,7 @@ mod tests { impl Editor { fn selection_ranges(&self, cx: &mut MutableAppContext) -> Vec> { - self.selections_in_range( + self.intersecting_selections( self.selection_set_id, DisplayPoint::zero()..self.max_point(cx), cx, From fc2ae42f4bdabe988993f53b108922c89002fe77 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 25 Nov 2021 13:39:08 -0700 Subject: [PATCH 8/8] Implement cmd-k cmd-d to replace selection with next --- crates/editor/src/lib.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 0404c725083eb62ae1150ba360ca29ef01253b05..453747e83f33ac2fb3d0f7617c46c82f7f1fa2ad 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -88,7 +88,7 @@ action!(SelectLine); action!(SplitSelectionIntoLines); action!(AddSelectionAbove); action!(AddSelectionBelow); -action!(SelectNext); +action!(SelectNext, bool); action!(ToggleComments); action!(SelectLargerSyntaxNode); action!(SelectSmallerSyntaxNode); @@ -193,7 +193,8 @@ pub fn init(cx: &mut MutableAppContext, entry_openers: &mut Vec) { + pub fn select_next(&mut self, action: &SelectNext, cx: &mut ViewContext) { + let replace_newest = action.0; let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = &display_map.buffer_snapshot; let mut selections = self.selections::(cx).collect::>(); @@ -2552,6 +2554,13 @@ impl Editor { } if let Some(next_selected_range) = next_selected_range { + if replace_newest { + if let Some(newest_id) = + selections.iter().max_by_key(|s| s.id).map(|s| s.id) + { + selections.retain(|s| s.id != newest_id); + } + } selections.push(Selection { id: post_inc(&mut self.next_selection_id), start: next_selected_range.start, @@ -2598,7 +2607,7 @@ impl Editor { wordwise: false, done: false, }); - self.select_next(&SelectNext, cx); + self.select_next(action, cx); } } }