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))?;
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::<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 {
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<S>(
pub fn insert_snippet(
&mut self,
range: Range<S>,
text: &str,
insertion_ranges: &[Range<usize>],
snippet: Snippet,
cx: &mut ViewContext<Self>,
) -> 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::<Vec<_>>()
})
@ -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<Self>) -> bool {
self.move_to_snippet_tabstop(Bias::Left, cx)
pub fn move_to_prev_snippet_tabstop(&mut self, cx: &mut ViewContext<Self>) {
self.move_to_snippet_tabstop(Bias::Left, cx);
}
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>) {
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::<usize>(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::<usize>(cx), &[4..7]);
assert!(editor.move_to_next_snippet_tabstop(cx));
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!(editor.move_to_next_snippet_tabstop(cx));
@ -6959,7 +7004,8 @@ mod tests {
assert_eq!(editor.selected_ranges::<usize>(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::<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> {
let mut result = MultiBufferRows {
buffer_row_range: 0..0,

View File

@ -8,7 +8,7 @@ pub struct Snippet {
pub tabstops: Vec<TabStop>,
}
type TabStop = SmallVec<[Range<usize>; 2]>;
type TabStop = SmallVec<[Range<isize>; 2]>;
impl Snippet {
pub fn parse(source: &str) -> Result<Self> {
@ -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<Vec<Range<usize>>> {
fn tabstops(snippet: &Snippet) -> Vec<Vec<Range<isize>>> {
snippet.tabstops.iter().map(|t| t.to_vec()).collect()
}
}