Cargo.lock 🔗
@@ -1562,6 +1562,7 @@ dependencies = [
"serde",
"smallvec",
"smol",
+ "snippet",
"sum_tree",
"text",
"theme",
Max Brunsfeld created
Cargo.lock | 1
crates/editor/Cargo.toml | 2
crates/editor/src/editor.rs | 72 +++++++++++++++++++++++++++++++-
crates/editor/src/multi_buffer.rs | 4
crates/language/src/buffer.rs | 4 -
5 files changed, 75 insertions(+), 8 deletions(-)
@@ -1562,6 +1562,7 @@ dependencies = [
"serde",
"smallvec",
"smol",
+ "snippet",
"sum_tree",
"text",
"theme",
@@ -22,7 +22,9 @@ collections = { path = "../collections" }
fuzzy = { path = "../fuzzy" }
gpui = { path = "../gpui" }
language = { path = "../language" }
+lsp = { path = "../lsp" }
project = { path = "../project" }
+snippet = { path = "../snippet" }
sum_tree = { path = "../sum_tree" }
theme = { path = "../theme" }
util = { path = "../util" }
@@ -42,6 +42,7 @@ use postage::watch;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use smol::Timer;
+use snippet::Snippet;
use std::{
any::TypeId,
cmp::{self, Ordering, Reverse},
@@ -1656,10 +1657,22 @@ impl Editor {
.matches
.get(completion_state.selected_item)?;
let completion = completion_state.completions.get(mat.candidate_id)?;
+
+ if completion.lsp_completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET) {
+ self.insert_snippet(completion.old_range.clone(), &completion.new_text, cx)
+ .log_err();
+ } else {
+ self.buffer.update(cx, |buffer, cx| {
+ buffer.edit_with_autoindent(
+ [completion.old_range.clone()],
+ &completion.new_text,
+ cx,
+ );
+ });
+ }
+
self.buffer.update(cx, |buffer, cx| {
- let mut completion = completion.clone();
- // completion.
- buffer.apply_completion(completion, cx)
+ buffer.apply_additional_edits_for_completion(completion.clone(), cx)
})
}
@@ -1722,6 +1735,42 @@ impl Editor {
})
}
+ pub fn insert_snippet<S>(
+ &mut self,
+ range: Range<S>,
+ text: &str,
+ cx: &mut ViewContext<Self>,
+ ) -> Result<()>
+ where
+ S: Clone + ToOffset,
+ {
+ let snippet = Snippet::parse(text)?;
+ 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);
+ snippet
+ .tabstops
+ .iter()
+ .map(|ranges| {
+ ranges
+ .into_iter()
+ .map(|range| {
+ snapshot.anchor_before(start + range.start)
+ ..snapshot.anchor_after(start + range.end)
+ })
+ .collect::<Vec<_>>()
+ })
+ .collect::<Vec<_>>()
+ });
+
+ if let Some(tabstop) = tabstops.first() {
+ self.select_ranges(tabstop.iter().cloned(), Some(Autoscroll::Fit), cx);
+ }
+
+ Ok(())
+ }
+
pub fn clear(&mut self, cx: &mut ViewContext<Self>) {
self.start_transaction(cx);
self.select_all(&SelectAll, cx);
@@ -6581,6 +6630,23 @@ mod tests {
});
}
+ #[gpui::test]
+ async fn test_snippets(mut cx: gpui::TestAppContext) {
+ let settings = cx.read(EditorSettings::test);
+
+ let text = "a. b";
+ let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
+ 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();
+ assert_eq!(editor.text(cx), "a.f(one, two) b");
+ assert_eq!(editor.selected_ranges::<usize>(cx), &[4..7]);
+ });
+ }
+
#[gpui::test]
async fn test_completion(mut cx: gpui::TestAppContext) {
let settings = cx.read(EditorSettings::test);
@@ -929,7 +929,7 @@ impl MultiBuffer {
}
}
- pub fn apply_completion(
+ pub fn apply_additional_edits_for_completion(
&self,
completion: Completion<Anchor>,
cx: &mut ModelContext<Self>,
@@ -941,7 +941,7 @@ impl MultiBuffer {
.buffer
.clone();
buffer.update(cx, |buffer, cx| {
- buffer.apply_completion(
+ buffer.apply_additional_edits_for_completion(
Completion {
old_range: completion.old_range.start.text_anchor
..completion.old_range.end.text_anchor,
@@ -1777,13 +1777,11 @@ impl Buffer {
}
}
- pub fn apply_completion(
+ pub fn apply_additional_edits_for_completion(
&mut self,
completion: Completion<Anchor>,
cx: &mut ModelContext<Self>,
) -> Option<Task<Result<()>>> {
- self.edit_with_autoindent([completion.old_range], completion.new_text.clone(), cx);
-
self.file.as_ref()?.as_local()?;
let server = self.language_server.as_ref()?.server.clone();
Some(cx.spawn(|this, mut cx| async move {