Support multi-cursor autocompletion

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
Co-Authored-By: Max Brunsfeld <max@zed.dev>
This commit is contained in:
Antonio Scandurra 2022-02-03 18:58:36 +01:00
parent 60595a64bd
commit 3e8707ebf6
3 changed files with 93 additions and 41 deletions

View File

@ -1687,20 +1687,56 @@ impl Editor {
.get(completion_ix.unwrap_or(completion_state.selected_item))?; .get(completion_ix.unwrap_or(completion_state.selected_item))?;
let completion = completion_state.completions.get(mat.candidate_id)?; let completion = completion_state.completions.get(mat.candidate_id)?;
self.start_transaction(cx); let snippet;
let text;
if completion.is_snippet() { if completion.is_snippet() {
self.insert_snippet(completion.old_range.clone(), &completion.new_text, cx) snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
.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::<usize>(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 { } else {
self.buffer.update(cx, |buffer, cx| { self.buffer.update(cx, |buffer, cx| {
let snapshot = buffer.read(cx); buffer.edit_with_autoindent(ranges, text, 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);
}
}); });
} }
self.end_transaction(cx); self.end_transaction(cx);
@ -1796,29 +1832,37 @@ impl Editor {
}) })
} }
pub fn insert_snippet<S>( pub fn insert_snippet(
&mut self, &mut self,
range: Range<S>, insertion_ranges: &[Range<usize>],
text: &str, snippet: Snippet,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Result<()> ) -> Result<()> {
where
S: Clone + ToOffset,
{
let snippet = Snippet::parse(text)?;
let tabstops = self.buffer.update(cx, |buffer, cx| { let tabstops = self.buffer.update(cx, |buffer, cx| {
buffer.edit_with_autoindent([range.clone()], snippet.text, cx); buffer.edit_with_autoindent(insertion_ranges.iter().cloned(), &snippet.text, cx);
let snapshot = buffer.read(cx);
let start = range.start.to_offset(&snapshot); let snapshot = &*buffer.read(cx);
let snippet = &snippet;
snippet snippet
.tabstops .tabstops
.iter() .iter()
.map(|ranges| { .map(|tabstop| {
ranges tabstop
.into_iter() .iter()
.map(|range| { .flat_map(|tabstop_range| {
snapshot.anchor_before(start + range.start) let mut delta = 0 as isize;
..snapshot.anchor_after(start + range.end) 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::<Vec<_>>() .collect::<Vec<_>>()
}) })
@ -1840,8 +1884,8 @@ impl Editor {
self.move_to_snippet_tabstop(Bias::Right, cx) self.move_to_snippet_tabstop(Bias::Right, cx)
} }
pub fn move_to_prev_snippet_tabstop(&mut self, cx: &mut ViewContext<Self>) -> bool { pub fn move_to_prev_snippet_tabstop(&mut self, cx: &mut ViewContext<Self>) {
self.move_to_snippet_tabstop(Bias::Left, cx) self.move_to_snippet_tabstop(Bias::Left, cx);
} }
pub fn move_to_snippet_tabstop(&mut self, bias: Bias, cx: &mut ViewContext<Self>) -> bool { pub fn move_to_snippet_tabstop(&mut self, bias: Bias, cx: &mut ViewContext<Self>) -> bool {
@ -2017,7 +2061,8 @@ impl Editor {
} }
pub fn outdent(&mut self, _: &Outdent, cx: &mut ViewContext<Self>) { pub fn outdent(&mut self, _: &Outdent, cx: &mut ViewContext<Self>) {
if self.move_to_prev_snippet_tabstop(cx) { if !self.snippet_stack.is_empty() {
self.move_to_prev_snippet_tabstop(cx);
return; return;
} }
@ -6939,19 +6984,19 @@ mod tests {
let (_, editor) = cx.add_window(|cx| build_editor(buffer, settings, cx)); let (_, editor) = cx.add_window(|cx| build_editor(buffer, settings, cx));
editor.update(&mut cx, |editor, cx| { editor.update(&mut cx, |editor, cx| {
editor let snippet = Snippet::parse("f(${1:one}, ${2:two})$0").unwrap();
.insert_snippet(2..2, "f(${1:one}, ${2:two})$0", cx) editor.insert_snippet(&[2..2], snippet, cx).unwrap();
.unwrap();
assert_eq!(editor.text(cx), "a.f(one, two) b"); assert_eq!(editor.text(cx), "a.f(one, two) b");
assert_eq!(editor.selected_ranges::<usize>(cx), &[4..7]); assert_eq!(editor.selected_ranges::<usize>(cx), &[4..7]);
// Can't move earlier than the first tab stop // 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::<usize>(cx), &[4..7]);
assert!(editor.move_to_next_snippet_tabstop(cx)); assert!(editor.move_to_next_snippet_tabstop(cx));
assert_eq!(editor.selected_ranges::<usize>(cx), &[9..12]); assert_eq!(editor.selected_ranges::<usize>(cx), &[9..12]);
assert!(editor.move_to_prev_snippet_tabstop(cx)); editor.move_to_prev_snippet_tabstop(cx);
assert_eq!(editor.selected_ranges::<usize>(cx), &[4..7]); assert_eq!(editor.selected_ranges::<usize>(cx), &[4..7]);
assert!(editor.move_to_next_snippet_tabstop(cx)); assert!(editor.move_to_next_snippet_tabstop(cx));
@ -6959,7 +7004,8 @@ mod tests {
assert_eq!(editor.selected_ranges::<usize>(cx), &[13..13]); assert_eq!(editor.selected_ranges::<usize>(cx), &[13..13]);
// As soon as the last tab stop is reached, snippet state is gone // 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::<usize>(cx), &[13..13]);
}); });
} }

View File

@ -1314,6 +1314,12 @@ impl MultiBufferSnapshot {
} }
} }
pub fn bytes_at<'a, T: ToOffset>(&'a self, position: T) -> impl 'a + Iterator<Item = u8> {
self.bytes_in_range(position.to_offset(self)..self.len())
.flatten()
.copied()
}
pub fn buffer_rows<'a>(&'a self, start_row: u32) -> MultiBufferRows<'a> { pub fn buffer_rows<'a>(&'a self, start_row: u32) -> MultiBufferRows<'a> {
let mut result = MultiBufferRows { let mut result = MultiBufferRows {
buffer_row_range: 0..0, buffer_row_range: 0..0,

View File

@ -8,7 +8,7 @@ pub struct Snippet {
pub tabstops: Vec<TabStop>, pub tabstops: Vec<TabStop>,
} }
type TabStop = SmallVec<[Range<usize>; 2]>; type TabStop = SmallVec<[Range<isize>; 2]>;
impl Snippet { impl Snippet {
pub fn parse(source: &str) -> Result<Self> { pub fn parse(source: &str) -> Result<Self> {
@ -19,7 +19,7 @@ impl Snippet {
let last_tabstop = tabstops let last_tabstop = tabstops
.remove(&0) .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 { Ok(Snippet {
text, text,
tabstops: tabstops.into_values().chain(Some(last_tabstop)).collect(), tabstops: tabstops.into_values().chain(Some(last_tabstop)).collect(),
@ -87,7 +87,7 @@ fn parse_tabstop<'a>(
tabstops tabstops
.entry(tabstop_index) .entry(tabstop_index)
.or_default() .or_default()
.push(tabstop_start..text.len()); .push(tabstop_start as isize..text.len() as isize);
Ok(source) Ok(source)
} }
@ -161,7 +161,7 @@ mod tests {
); );
} }
fn tabstops(snippet: &Snippet) -> Vec<Vec<Range<usize>>> { fn tabstops(snippet: &Snippet) -> Vec<Vec<Range<isize>>> {
snippet.tabstops.iter().map(|t| t.to_vec()).collect() snippet.tabstops.iter().map(|t| t.to_vec()).collect()
} }
} }