Start on completion rendering

Nathan Sobo , Antonio Scandurra , and Max Brunsfeld created

Co-Authored-By: Antonio Scandurra <me@as-cii.com>
Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>

Change summary

crates/editor/src/editor.rs   | 54 +++++++++++++++++++++++++++++++++---
crates/editor/src/element.rs  |  5 +++
crates/language/src/buffer.rs |  6 ++++
3 files changed, 60 insertions(+), 5 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -26,8 +26,8 @@ use gpui::{
 use items::BufferItemHandle;
 use itertools::Itertools as _;
 use language::{
-    AnchorRangeExt as _, BracketPair, Buffer, Diagnostic, DiagnosticSeverity, Language, Point,
-    Selection, SelectionGoal, TransactionId,
+    AnchorRangeExt as _, BracketPair, Buffer, Completion, Diagnostic, DiagnosticSeverity, Language,
+    Point, Selection, SelectionGoal, TransactionId,
 };
 use multi_buffer::MultiBufferChunks;
 pub use multi_buffer::{
@@ -390,6 +390,7 @@ pub struct Editor {
     highlighted_rows: Option<Range<u32>>,
     highlighted_ranges: BTreeMap<TypeId, (Color, Vec<Range<Anchor>>)>,
     nav_history: Option<ItemNavHistory>,
+    completion_state: Option<CompletionState>,
 }
 
 pub struct EditorSnapshot {
@@ -423,6 +424,11 @@ struct BracketPairState {
     pair: BracketPair,
 }
 
+struct CompletionState {
+    completions: Arc<[Completion]>,
+    list: UniformListState,
+}
+
 #[derive(Debug)]
 struct ActiveDiagnosticGroup {
     primary_range: Range<Anchor>,
@@ -539,6 +545,7 @@ impl Editor {
             highlighted_rows: None,
             highlighted_ranges: Default::default(),
             nav_history: None,
+            completion_state: None,
         };
         let selection = Selection {
             id: post_inc(&mut this.next_selection_id),
@@ -1502,9 +1509,46 @@ impl Editor {
         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);
+
+        let completions = self
+            .buffer
+            .update(cx, |buffer, cx| buffer.completions(position, cx));
+
+        cx.spawn_weak(|this, mut cx| async move {
+            let completions = completions.await?;
+            if let Some(this) = cx.read(|cx| this.upgrade(cx)) {
+                this.update(&mut cx, |this, cx| {
+                    this.completion_state = Some(CompletionState {
+                        completions: completions.into(),
+                        list: Default::default(),
+                    });
+                    cx.notify();
+                });
+            }
+
+            Ok::<_, anyhow::Error>(())
+        })
+        .detach_and_log_err(cx);
+    }
+
+    pub fn render_completions(&self) -> Option<ElementBox> {
+        self.completion_state.as_ref().map(|state| {
+            let build_settings = self.build_settings.clone();
+            let completions = state.completions.clone();
+            UniformList::new(
+                state.list.clone(),
+                state.completions.len(),
+                move |range, items, cx| {
+                    let settings = build_settings(cx);
+                    for completion in &completions[range] {
+                        items.push(
+                            Label::new(completion.label().to_string(), settings.style.text.clone()).boxed(),
+                        );
+                    }
+                },
+            )
+            .boxed()
+        })
     }
 
     pub fn clear(&mut self, cx: &mut ViewContext<Self>) {

crates/editor/src/element.rs 🔗

@@ -836,6 +836,7 @@ impl Element for EditorElement {
             max_row.saturating_sub(1) as f32,
         );
 
+        let mut completions = None;
         self.update_view(cx.app, |view, cx| {
             let clamped = view.clamp_scroll_left(scroll_max.x());
             let autoscrolled;
@@ -855,6 +856,8 @@ impl Element for EditorElement {
             if clamped || autoscrolled {
                 snapshot = view.snapshot(cx);
             }
+
+            completions = view.render_completions();
         });
 
         let blocks = self.layout_blocks(
@@ -891,6 +894,7 @@ impl Element for EditorElement {
                 em_width,
                 em_advance,
                 selections,
+                completions,
             }),
         )
     }
@@ -1000,6 +1004,7 @@ pub struct LayoutState {
     highlighted_ranges: Vec<(Range<DisplayPoint>, Color)>,
     selections: HashMap<ReplicaId, Vec<text::Selection<DisplayPoint>>>,
     text_offset: Vector2F,
+    completions: Option<ElementBox>,
 }
 
 fn layout_line(

crates/language/src/buffer.rs 🔗

@@ -2424,6 +2424,12 @@ impl Default for Diagnostic {
     }
 }
 
+impl Completion {
+    pub fn label(&self) -> &str {
+        &self.lsp_completion.label
+    }
+}
+
 pub fn contiguous_ranges(
     values: impl Iterator<Item = u32>,
     max_len: usize,