Insert completion text on enter

Max Brunsfeld and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/editor/src/editor.rs       | 36 +++++++++++++++++++++++++++++---
crates/editor/src/multi_buffer.rs | 28 ++++++++++++++++++++++--
crates/language/src/buffer.rs     | 12 +++++-----
crates/server/src/main.rs         |  2 
4 files changed, 64 insertions(+), 14 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -117,7 +117,8 @@ action!(Unfold);
 action!(FoldSelectedRanges);
 action!(Scroll, Vector2F);
 action!(Select, SelectPhase);
-action!(ShowAutocomplete);
+action!(ShowCompletions);
+action!(ConfirmCompletion);
 
 pub fn init(cx: &mut MutableAppContext, path_openers: &mut Vec<Box<dyn PathOpener>>) {
     path_openers.push(Box::new(items::BufferOpener));
@@ -133,6 +134,7 @@ pub fn init(cx: &mut MutableAppContext, path_openers: &mut Vec<Box<dyn PathOpene
             Input("\n".into()),
             Some("Editor && mode == auto_height"),
         ),
+        Binding::new("enter", ConfirmCompletion, Some("Editor && completing")),
         Binding::new("tab", Tab, Some("Editor")),
         Binding::new("shift-tab", Outdent, Some("Editor")),
         Binding::new("ctrl-shift-K", DeleteLine, Some("Editor")),
@@ -225,7 +227,7 @@ pub fn init(cx: &mut MutableAppContext, path_openers: &mut Vec<Box<dyn PathOpene
         Binding::new("alt-cmd-[", Fold, Some("Editor")),
         Binding::new("alt-cmd-]", Unfold, Some("Editor")),
         Binding::new("alt-cmd-f", FoldSelectedRanges, Some("Editor")),
-        Binding::new("ctrl-space", ShowAutocomplete, Some("Editor")),
+        Binding::new("ctrl-space", ShowCompletions, Some("Editor")),
     ]);
 
     cx.add_action(Editor::open_new);
@@ -290,6 +292,7 @@ pub fn init(cx: &mut MutableAppContext, path_openers: &mut Vec<Box<dyn PathOpene
     cx.add_action(Editor::unfold);
     cx.add_action(Editor::fold_selected_ranges);
     cx.add_action(Editor::show_completions);
+    cx.add_action(Editor::confirm_completion);
 }
 
 trait SelectionExt {
@@ -425,7 +428,7 @@ struct BracketPairState {
 }
 
 struct CompletionState {
-    completions: Arc<[Completion]>,
+    completions: Arc<[Completion<Anchor>]>,
     selected_item: usize,
     list: UniformListState,
 }
@@ -1102,6 +1105,11 @@ impl Editor {
     }
 
     pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
+        if self.completion_state.take().is_some() {
+            cx.notify();
+            return;
+        }
+
         if self.mode != EditorMode::Full {
             cx.propagate_action();
             return;
@@ -1506,7 +1514,7 @@ impl Editor {
         }
     }
 
-    fn show_completions(&mut self, _: &ShowAutocomplete, cx: &mut ViewContext<Self>) {
+    fn show_completions(&mut self, _: &ShowCompletions, cx: &mut ViewContext<Self>) {
         let position = self
             .newest_selection::<usize>(&self.buffer.read(cx).read(cx))
             .head();
@@ -1533,6 +1541,23 @@ impl Editor {
         .detach_and_log_err(cx);
     }
 
+    fn confirm_completion(&mut self, _: &ConfirmCompletion, cx: &mut ViewContext<Self>) {
+        if let Some(completion_state) = self.completion_state.take() {
+            if let Some(completion) = completion_state
+                .completions
+                .get(completion_state.selected_item)
+            {
+                self.buffer.update(cx, |buffer, cx| {
+                    buffer.edit_with_autoindent(
+                        [completion.old_range.clone()],
+                        completion.new_text.clone(),
+                        cx,
+                    );
+                })
+            }
+        }
+    }
+
     pub fn has_completions(&self) -> bool {
         self.completion_state.is_some()
     }
@@ -4180,6 +4205,9 @@ impl View for Editor {
             EditorMode::Full => "full",
         };
         cx.map.insert("mode".into(), mode.into());
+        if self.completion_state.is_some() {
+            cx.set.insert("completing".into());
+        }
         cx
     }
 }

crates/editor/src/multi_buffer.rs 🔗

@@ -852,12 +852,34 @@ impl MultiBuffer {
         &self,
         position: T,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<Completion>>>
+    ) -> Task<Result<Vec<Completion<Anchor>>>>
     where
         T: ToOffset,
     {
-        let (buffer, text_anchor) = self.text_anchor_for_position(position, cx);
-        buffer.update(cx, |buffer, cx| buffer.completions(text_anchor, cx))
+        let snapshot = self.snapshot(cx);
+        let anchor = snapshot.anchor_before(position);
+        let buffer = self.buffers.borrow()[&anchor.buffer_id].buffer.clone();
+        let completions =
+            buffer.update(cx, |buffer, cx| buffer.completions(anchor.text_anchor, cx));
+        cx.foreground().spawn(async move {
+            completions.await.map(|completions| {
+                completions
+                    .into_iter()
+                    .map(|completion| Completion {
+                        old_range: snapshot.anchor_in_excerpt(
+                            anchor.excerpt_id.clone(),
+                            completion.old_range.start,
+                        )
+                            ..snapshot.anchor_in_excerpt(
+                                anchor.excerpt_id.clone(),
+                                completion.old_range.end,
+                            ),
+                        new_text: completion.new_text,
+                        lsp_completion: completion.lsp_completion,
+                    })
+                    .collect()
+            })
+        })
     }
 
     pub fn language<'a>(&self, cx: &'a AppContext) -> Option<&'a Arc<Language>> {

crates/language/src/buffer.rs 🔗

@@ -114,10 +114,10 @@ pub struct Diagnostic {
     pub is_disk_based: bool,
 }
 
-pub struct Completion {
-    old_range: Range<Anchor>,
-    new_text: String,
-    lsp_completion: lsp::CompletionItem,
+pub struct Completion<T> {
+    pub old_range: Range<T>,
+    pub new_text: String,
+    pub lsp_completion: lsp::CompletionItem,
 }
 
 struct LanguageServerState {
@@ -1622,7 +1622,7 @@ impl Buffer {
         &self,
         position: T,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<Completion>>>
+    ) -> Task<Result<Vec<Completion<Anchor>>>>
     where
         T: ToOffset,
     {
@@ -2424,7 +2424,7 @@ impl Default for Diagnostic {
     }
 }
 
-impl Completion {
+impl<T> Completion<T> {
     pub fn label(&self) -> &str {
         &self.lsp_completion.label
     }

crates/server/src/main.rs 🔗

@@ -2,6 +2,7 @@ mod admin;
 mod api;
 mod assets;
 mod auth;
+mod careers;
 mod community;
 mod db;
 mod env;
@@ -12,7 +13,6 @@ mod home;
 mod releases;
 mod rpc;
 mod team;
-mod careers;
 
 use self::errors::TideResultExt as _;
 use ::rpc::Peer;