From 3e8707ebf66e5dab911f0e9d60ac3066872723ba Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Feb 2022 18:58:36 +0100 Subject: [PATCH] Support multi-cursor autocompletion Co-Authored-By: Nathan Sobo Co-Authored-By: Max Brunsfeld --- crates/editor/src/editor.rs | 120 +++++++++++++++++++++--------- crates/editor/src/multi_buffer.rs | 6 ++ crates/snippet/src/snippet.rs | 8 +- 3 files changed, 93 insertions(+), 41 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index efd8452d9d..37f00fcd6e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1687,20 +1687,56 @@ impl Editor { .get(completion_ix.unwrap_or(completion_state.selected_item))?; let completion = completion_state.completions.get(mat.candidate_id)?; - self.start_transaction(cx); + let snippet; + let text; if completion.is_snippet() { - self.insert_snippet(completion.old_range.clone(), &completion.new_text, cx) - .log_err(); + snippet = Some(Snippet::parse(&completion.new_text).log_err()?); + text = snippet.as_ref().unwrap().text.clone(); + } else { + snippet = None; + text = completion.new_text.clone(); + }; + let snapshot = self.buffer.read(cx).snapshot(cx); + let old_range = completion.old_range.to_offset(&snapshot); + + let selections = self.local_selections::(cx); + let mut common_prefix_len = None; + let mut ranges = Vec::new(); + for selection in &selections { + let start = selection.start.saturating_sub(old_range.len()); + let prefix_len = snapshot + .bytes_at(start) + .zip(completion.new_text.bytes()) + .take_while(|(a, b)| a == b) + .count(); + if common_prefix_len.is_none() { + common_prefix_len = Some(prefix_len); + } + + if common_prefix_len == Some(prefix_len) { + ranges.push(start + prefix_len..selection.end); + } else { + common_prefix_len.take(); + ranges.clear(); + ranges.extend(selections.iter().map(|s| s.start..s.end)); + break; + } + } + let common_prefix_len = common_prefix_len.unwrap_or(0); + let text = &text[common_prefix_len..]; + + self.start_transaction(cx); + if let Some(mut snippet) = snippet { + snippet.text = text.to_string(); + for tabstop in snippet.tabstops.iter_mut().flatten() { + tabstop.start -= common_prefix_len as isize; + tabstop.end -= common_prefix_len as isize; + } + + self.insert_snippet(&ranges, snippet, cx).log_err(); } else { self.buffer.update(cx, |buffer, cx| { - let snapshot = buffer.read(cx); - let old_range = completion.old_range.to_offset(&snapshot); - if old_range.len() != completion.new_text.len() - || !snapshot.contains_str_at(old_range.start, &completion.new_text) - { - drop(snapshot); - buffer.edit_with_autoindent([old_range], &completion.new_text, cx); - } + buffer.edit_with_autoindent(ranges, text, cx); }); } self.end_transaction(cx); @@ -1796,29 +1832,37 @@ impl Editor { }) } - pub fn insert_snippet( + pub fn insert_snippet( &mut self, - range: Range, - text: &str, + insertion_ranges: &[Range], + snippet: Snippet, cx: &mut ViewContext, - ) -> Result<()> - where - S: Clone + ToOffset, - { - let snippet = Snippet::parse(text)?; + ) -> Result<()> { let tabstops = self.buffer.update(cx, |buffer, cx| { - buffer.edit_with_autoindent([range.clone()], snippet.text, cx); - let snapshot = buffer.read(cx); - let start = range.start.to_offset(&snapshot); + buffer.edit_with_autoindent(insertion_ranges.iter().cloned(), &snippet.text, cx); + + let snapshot = &*buffer.read(cx); + let snippet = &snippet; snippet .tabstops .iter() - .map(|ranges| { - ranges - .into_iter() - .map(|range| { - snapshot.anchor_before(start + range.start) - ..snapshot.anchor_after(start + range.end) + .map(|tabstop| { + tabstop + .iter() + .flat_map(|tabstop_range| { + let mut delta = 0 as isize; + insertion_ranges.iter().map(move |insertion_range| { + let insertion_start = insertion_range.start as isize + delta; + delta += + snippet.text.len() as isize - insertion_range.len() as isize; + + let start = snapshot.anchor_before( + (insertion_start + tabstop_range.start) as usize, + ); + let end = snapshot + .anchor_after((insertion_start + tabstop_range.end) as usize); + start..end + }) }) .collect::>() }) @@ -1840,8 +1884,8 @@ impl Editor { self.move_to_snippet_tabstop(Bias::Right, cx) } - pub fn move_to_prev_snippet_tabstop(&mut self, cx: &mut ViewContext) -> bool { - self.move_to_snippet_tabstop(Bias::Left, cx) + pub fn move_to_prev_snippet_tabstop(&mut self, cx: &mut ViewContext) { + self.move_to_snippet_tabstop(Bias::Left, cx); } pub fn move_to_snippet_tabstop(&mut self, bias: Bias, cx: &mut ViewContext) -> bool { @@ -2017,7 +2061,8 @@ impl Editor { } pub fn outdent(&mut self, _: &Outdent, cx: &mut ViewContext) { - if self.move_to_prev_snippet_tabstop(cx) { + if !self.snippet_stack.is_empty() { + self.move_to_prev_snippet_tabstop(cx); return; } @@ -6939,19 +6984,19 @@ mod tests { let (_, editor) = cx.add_window(|cx| build_editor(buffer, settings, cx)); editor.update(&mut cx, |editor, cx| { - editor - .insert_snippet(2..2, "f(${1:one}, ${2:two})$0", cx) - .unwrap(); + let snippet = Snippet::parse("f(${1:one}, ${2:two})$0").unwrap(); + editor.insert_snippet(&[2..2], snippet, cx).unwrap(); assert_eq!(editor.text(cx), "a.f(one, two) b"); assert_eq!(editor.selected_ranges::(cx), &[4..7]); // Can't move earlier than the first tab stop - assert!(!editor.move_to_prev_snippet_tabstop(cx)); + editor.move_to_prev_snippet_tabstop(cx); + assert_eq!(editor.selected_ranges::(cx), &[4..7]); assert!(editor.move_to_next_snippet_tabstop(cx)); assert_eq!(editor.selected_ranges::(cx), &[9..12]); - assert!(editor.move_to_prev_snippet_tabstop(cx)); + editor.move_to_prev_snippet_tabstop(cx); assert_eq!(editor.selected_ranges::(cx), &[4..7]); assert!(editor.move_to_next_snippet_tabstop(cx)); @@ -6959,7 +7004,8 @@ mod tests { assert_eq!(editor.selected_ranges::(cx), &[13..13]); // As soon as the last tab stop is reached, snippet state is gone - assert!(!editor.move_to_prev_snippet_tabstop(cx)); + editor.move_to_prev_snippet_tabstop(cx); + assert_eq!(editor.selected_ranges::(cx), &[13..13]); }); } diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 1904e00edc..417e0bc69b 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -1314,6 +1314,12 @@ impl MultiBufferSnapshot { } } + pub fn bytes_at<'a, T: ToOffset>(&'a self, position: T) -> impl 'a + Iterator { + self.bytes_in_range(position.to_offset(self)..self.len()) + .flatten() + .copied() + } + pub fn buffer_rows<'a>(&'a self, start_row: u32) -> MultiBufferRows<'a> { let mut result = MultiBufferRows { buffer_row_range: 0..0, diff --git a/crates/snippet/src/snippet.rs b/crates/snippet/src/snippet.rs index 88e8aa2f5f..82ec12f5ff 100644 --- a/crates/snippet/src/snippet.rs +++ b/crates/snippet/src/snippet.rs @@ -8,7 +8,7 @@ pub struct Snippet { pub tabstops: Vec, } -type TabStop = SmallVec<[Range; 2]>; +type TabStop = SmallVec<[Range; 2]>; impl Snippet { pub fn parse(source: &str) -> Result { @@ -19,7 +19,7 @@ impl Snippet { let last_tabstop = tabstops .remove(&0) - .unwrap_or_else(|| SmallVec::from_iter([text.len()..text.len()])); + .unwrap_or_else(|| SmallVec::from_iter([text.len() as isize..text.len() as isize])); Ok(Snippet { text, tabstops: tabstops.into_values().chain(Some(last_tabstop)).collect(), @@ -87,7 +87,7 @@ fn parse_tabstop<'a>( tabstops .entry(tabstop_index) .or_default() - .push(tabstop_start..text.len()); + .push(tabstop_start as isize..text.len() as isize); Ok(source) } @@ -161,7 +161,7 @@ mod tests { ); } - fn tabstops(snippet: &Snippet) -> Vec>> { + fn tabstops(snippet: &Snippet) -> Vec>> { snippet.tabstops.iter().map(|t| t.to_vec()).collect() } }