mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-25 01:34:02 +00:00
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:
parent
60595a64bd
commit
3e8707ebf6
3 changed files with 93 additions and 41 deletions
|
@ -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]);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue