WIP

Antonio Scandurra created

Change summary

crates/editor/src/editor.rs       | 12 ++++
crates/editor/src/multi_buffer.rs | 13 ++++
crates/language/src/buffer.rs     | 97 +++++++++++++++++++++++++++++++++
3 files changed, 122 insertions(+)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -117,6 +117,7 @@ action!(Unfold);
 action!(FoldSelectedRanges);
 action!(Scroll, Vector2F);
 action!(Select, SelectPhase);
+action!(ShowAutocomplete);
 
 pub fn init(cx: &mut MutableAppContext, path_openers: &mut Vec<Box<dyn PathOpener>>) {
     path_openers.push(Box::new(items::BufferOpener));
@@ -224,6 +225,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-shift-A", ShowAutocomplete, Some("Editor")),
     ]);
 
     cx.add_action(Editor::open_new);
@@ -287,6 +289,7 @@ pub fn init(cx: &mut MutableAppContext, path_openers: &mut Vec<Box<dyn PathOpene
     cx.add_action(Editor::fold);
     cx.add_action(Editor::unfold);
     cx.add_action(Editor::fold_selected_ranges);
+    cx.add_action(Editor::show_autocomplete);
 }
 
 trait SelectionExt {
@@ -1495,6 +1498,15 @@ impl Editor {
         }
     }
 
+    fn show_autocomplete(&mut self, _: &ShowAutocomplete, cx: &mut ViewContext<Self>) {
+        let position = self
+            .newest_selection::<usize>(&self.buffer.read(cx).read(cx))
+            .head();
+        self.buffer
+            .update(cx, |buffer, cx| buffer.completions(position, cx))
+            .detach_and_log_err(cx);
+    }
+
     pub fn clear(&mut self, cx: &mut ViewContext<Self>) {
         self.start_transaction(cx);
         self.select_all(&SelectAll, cx);

crates/editor/src/multi_buffer.rs 🔗

@@ -5,6 +5,7 @@ use anyhow::Result;
 use clock::ReplicaId;
 use collections::{HashMap, HashSet};
 use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
+pub use language::Completion;
 use language::{
     Buffer, BufferChunks, BufferSnapshot, Chunk, DiagnosticEntry, Event, File, Language, Outline,
     OutlineItem, Selection, ToOffset as _, ToPoint as _, TransactionId,
@@ -847,6 +848,18 @@ impl MultiBuffer {
         })
     }
 
+    pub fn completions<T>(
+        &self,
+        position: T,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<Vec<Completion>>>
+    where
+        T: ToOffset,
+    {
+        let (buffer, text_anchor) = self.text_anchor_for_position(position, cx);
+        buffer.update(cx, |buffer, cx| buffer.completions(text_anchor, cx))
+    }
+
     pub fn language<'a>(&self, cx: &'a AppContext) -> Option<&'a Arc<Language>> {
         self.buffers
             .borrow()

crates/language/src/buffer.rs 🔗

@@ -12,6 +12,7 @@ use crate::{
 use anyhow::{anyhow, Result};
 use clock::ReplicaId;
 use futures::FutureExt as _;
+use fuzzy::StringMatchCandidate;
 use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, MutableAppContext, Task};
 use lazy_static::lazy_static;
 use lsp::LanguageServer;
@@ -114,6 +115,12 @@ pub struct Diagnostic {
     pub is_disk_based: bool,
 }
 
+pub struct Completion {
+    old_range: Range<Anchor>,
+    new_text: String,
+    lsp_completion: lsp::CompletionItem,
+}
+
 struct LanguageServerState {
     server: Arc<LanguageServer>,
     latest_snapshot: watch::Sender<Option<LanguageServerSnapshot>>,
@@ -1611,6 +1618,96 @@ impl Buffer {
             false
         }
     }
+
+    pub fn completions<T>(
+        &self,
+        position: T,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<Vec<Completion>>>
+    where
+        T: ToOffset,
+    {
+        let file = if let Some(file) = self.file.as_ref() {
+            file
+        } else {
+            return Task::ready(Ok(Default::default()));
+        };
+
+        if let Some(file) = file.as_local() {
+            let server = if let Some(lang) = self.language_server.as_ref() {
+                lang.server.clone()
+            } else {
+                return Task::ready(Ok(Default::default()));
+            };
+            let abs_path = file.abs_path(cx);
+            let position = self.offset_to_point_utf16(position.to_offset(self));
+
+            cx.spawn(|this, mut cx| async move {
+                let t0 = Instant::now();
+                let completions = server
+                    .request::<lsp::request::Completion>(lsp::CompletionParams {
+                        text_document_position: lsp::TextDocumentPositionParams::new(
+                            lsp::TextDocumentIdentifier::new(
+                                lsp::Url::from_file_path(abs_path).unwrap(),
+                            ),
+                            position.to_lsp_position(),
+                        ),
+                        context: Default::default(),
+                        work_done_progress_params: Default::default(),
+                        partial_result_params: Default::default(),
+                    })
+                    .await?;
+                dbg!("completions", t0.elapsed());
+                // fuzzy::match_strings(candidates, query, smart_case, max_results, cancel_flag, background)
+
+                let mut completions = if let Some(completions) = completions {
+                    match completions {
+                        lsp::CompletionResponse::Array(completions) => completions,
+                        lsp::CompletionResponse::List(list) => list.items,
+                    }
+                } else {
+                    Default::default()
+                };
+
+                this.update(&mut cx, |this, cx| {
+                    this.edit([0..0], "use std::sync::Arc;\n", cx)
+                });
+
+                let mut futures = Vec::new();
+                for completion in completions {
+                    futures.push(server.request::<lsp::request::ResolveCompletionItem>(completion));
+                }
+
+                let completions = futures::future::try_join_all(futures).await?;
+                dbg!("resolution", t0.elapsed(), completions);
+                // let candidates = completions
+                //     .iter()
+                //     .enumerate()
+                //     .map(|(id, completion)| {
+                //         let text = completion
+                //             .filter_text
+                //             .clone()
+                //             .unwrap_or_else(|| completion.label.clone());
+                //         StringMatchCandidate::new(id, text)
+                //     })
+                //     .collect::<Vec<_>>();
+                // let matches = fuzzy::match_strings(
+                //     &candidates,
+                //     "Arc",
+                //     false,
+                //     100,
+                //     &Default::default(),
+                //     cx.background(),
+                // )
+                // .await;
+                // dbg!(matches);
+
+                Ok(Default::default())
+            })
+        } else {
+            Task::ready(Ok(Default::default()))
+        }
+    }
 }
 
 #[cfg(any(test, feature = "test-support"))]