Highlight whitespace-only inline completions with background (#21954)

Thorsten Ball and Antonio created

Noticed that whitespace-only insertions are really hard to make out, so
this changes it to make them visible by giving them a green background.

![screenshot-2024-12-13-10 49
09@2x](https://github.com/user-attachments/assets/10d83067-46f2-4cb5-97fa-0f44d254890d)


Release Notes:

- N/A

---------

Co-authored-by: Antonio <antonio@zed.dev>

Change summary

crates/assistant/src/prompt_library.rs     | 10 ++---
crates/editor/src/display_map.rs           | 10 +++++-
crates/editor/src/display_map/inlay_map.rs | 20 ++++++++---
crates/editor/src/editor.rs                | 38 +++++++++++++++--------
crates/editor/src/movement.rs              |  4 +-
5 files changed, 53 insertions(+), 29 deletions(-)

Detailed changes

crates/assistant/src/prompt_library.rs 🔗

@@ -11,8 +11,8 @@ use futures::{
 use fuzzy::StringMatchCandidate;
 use gpui::{
     actions, point, size, transparent_black, Action, AppContext, BackgroundExecutor, Bounds,
-    EventEmitter, Global, HighlightStyle, PromptLevel, ReadGlobal, Subscription, Task, TextStyle,
-    TitlebarOptions, UpdateGlobal, View, WindowBounds, WindowHandle, WindowOptions,
+    EventEmitter, Global, PromptLevel, ReadGlobal, Subscription, Task, TextStyle, TitlebarOptions,
+    UpdateGlobal, View, WindowBounds, WindowHandle, WindowOptions,
 };
 use heed::{
     types::{SerdeBincode, SerdeJson, Str},
@@ -928,10 +928,8 @@ impl PromptLibrary {
                                                     status: cx.theme().status().clone(),
                                                     inlay_hints_style:
                                                         editor::make_inlay_hints_style(cx),
-                                                    suggestions_style: HighlightStyle {
-                                                        color: Some(cx.theme().status().predictive),
-                                                        ..HighlightStyle::default()
-                                                    },
+                                                    inline_completion_styles:
+                                                        editor::make_suggestion_styles(cx),
                                                     ..EditorStyle::default()
                                                 },
                                             )),

crates/editor/src/display_map.rs 🔗

@@ -535,10 +535,16 @@ pub(crate) struct Highlights<'a> {
     pub styles: HighlightStyles,
 }
 
+#[derive(Clone, Copy, Debug)]
+pub struct InlineCompletionStyles {
+    pub insertion: HighlightStyle,
+    pub whitespace: HighlightStyle,
+}
+
 #[derive(Default, Debug, Clone, Copy)]
 pub struct HighlightStyles {
     pub inlay_hint: Option<HighlightStyle>,
-    pub suggestion: Option<HighlightStyle>,
+    pub inline_completion: Option<InlineCompletionStyles>,
 }
 
 #[derive(Clone)]
@@ -859,7 +865,7 @@ impl DisplaySnapshot {
             language_aware,
             HighlightStyles {
                 inlay_hint: Some(editor_style.inlay_hints_style),
-                suggestion: Some(editor_style.suggestions_style),
+                inline_completion: Some(editor_style.inline_completion_styles),
             },
         )
         .flat_map(|chunk| {

crates/editor/src/display_map/inlay_map.rs 🔗

@@ -62,9 +62,9 @@ impl Inlay {
         }
     }
 
-    pub fn suggestion<T: Into<Rope>>(id: usize, position: Anchor, text: T) -> Self {
+    pub fn inline_completion<T: Into<Rope>>(id: usize, position: Anchor, text: T) -> Self {
         Self {
-            id: InlayId::Suggestion(id),
+            id: InlayId::InlineCompletion(id),
             position,
             text: text.into(),
         }
@@ -346,7 +346,15 @@ impl<'a> Iterator for InlayChunks<'a> {
                 }
 
                 let mut highlight_style = match inlay.id {
-                    InlayId::Suggestion(_) => self.highlight_styles.suggestion,
+                    InlayId::InlineCompletion(_) => {
+                        self.highlight_styles.inline_completion.map(|s| {
+                            if inlay.text.chars().all(|c| c.is_whitespace()) {
+                                s.whitespace
+                            } else {
+                                s.insertion
+                            }
+                        })
+                    }
                     InlayId::Hint(_) => self.highlight_styles.inlay_hint,
                 };
                 let next_inlay_highlight_endpoint;
@@ -693,7 +701,7 @@ impl InlayMap {
                 let inlay_id = if i % 2 == 0 {
                     InlayId::Hint(post_inc(next_inlay_id))
                 } else {
-                    InlayId::Suggestion(post_inc(next_inlay_id))
+                    InlayId::InlineCompletion(post_inc(next_inlay_id))
                 };
                 log::info!(
                     "creating inlay {:?} at buffer offset {} with bias {:?} and text {:?}",
@@ -1389,7 +1397,7 @@ mod tests {
                     text: "|123|".into(),
                 },
                 Inlay {
-                    id: InlayId::Suggestion(post_inc(&mut next_inlay_id)),
+                    id: InlayId::InlineCompletion(post_inc(&mut next_inlay_id)),
                     position: buffer.read(cx).snapshot(cx).anchor_after(3),
                     text: "|456|".into(),
                 },
@@ -1605,7 +1613,7 @@ mod tests {
                     text: "|456|".into(),
                 },
                 Inlay {
-                    id: InlayId::Suggestion(post_inc(&mut next_inlay_id)),
+                    id: InlayId::InlineCompletion(post_inc(&mut next_inlay_id)),
                     position: buffer.read(cx).snapshot(cx).anchor_before(7),
                     text: "\n|567|\n".into(),
                 },

crates/editor/src/editor.rs 🔗

@@ -259,14 +259,14 @@ pub fn render_parsed_markdown(
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub(crate) enum InlayId {
-    Suggestion(usize),
+    InlineCompletion(usize),
     Hint(usize),
 }
 
 impl InlayId {
     fn id(&self) -> usize {
         match self {
-            Self::Suggestion(id) => *id,
+            Self::InlineCompletion(id) => *id,
             Self::Hint(id) => *id,
         }
     }
@@ -405,7 +405,7 @@ pub struct EditorStyle {
     pub syntax: Arc<SyntaxTheme>,
     pub status: StatusColors,
     pub inlay_hints_style: HighlightStyle,
-    pub suggestions_style: HighlightStyle,
+    pub inline_completion_styles: InlineCompletionStyles,
     pub unnecessary_code_fade: f32,
 }
 
@@ -422,7 +422,10 @@ impl Default for EditorStyle {
             // style and retrieve them directly from the theme.
             status: StatusColors::dark(),
             inlay_hints_style: HighlightStyle::default(),
-            suggestions_style: HighlightStyle::default(),
+            inline_completion_styles: InlineCompletionStyles {
+                insertion: HighlightStyle::default(),
+                whitespace: HighlightStyle::default(),
+            },
             unnecessary_code_fade: Default::default(),
         }
     }
@@ -440,6 +443,19 @@ pub fn make_inlay_hints_style(cx: &WindowContext) -> HighlightStyle {
     }
 }
 
+pub fn make_suggestion_styles(cx: &WindowContext) -> InlineCompletionStyles {
+    InlineCompletionStyles {
+        insertion: HighlightStyle {
+            color: Some(cx.theme().status().predictive),
+            ..HighlightStyle::default()
+        },
+        whitespace: HighlightStyle {
+            background_color: Some(cx.theme().status().created_background),
+            ..HighlightStyle::default()
+        },
+    }
+}
+
 type CompletionId = usize;
 
 enum InlineCompletion {
@@ -4735,7 +4751,7 @@ impl Editor {
             {
                 let mut inlays = Vec::new();
                 for (range, new_text) in &edits {
-                    let inlay = Inlay::suggestion(
+                    let inlay = Inlay::inline_completion(
                         post_inc(&mut self.next_inlay_id),
                         range.start,
                         new_text.as_str(),
@@ -9901,10 +9917,9 @@ impl Editor {
                                                     font_weight: Some(FontWeight::BOLD),
                                                     ..make_inlay_hints_style(cx)
                                                 },
-                                                suggestions_style: HighlightStyle {
-                                                    color: Some(cx.theme().status().predictive),
-                                                    ..HighlightStyle::default()
-                                                },
+                                                inline_completion_styles: make_suggestion_styles(
+                                                    cx,
+                                                ),
                                                 ..EditorStyle::default()
                                             },
                                         ))
@@ -13905,10 +13920,7 @@ impl Render for Editor {
                 syntax: cx.theme().syntax().clone(),
                 status: cx.theme().status().clone(),
                 inlay_hints_style: make_inlay_hints_style(cx),
-                suggestions_style: HighlightStyle {
-                    color: Some(cx.theme().status().predictive),
-                    ..HighlightStyle::default()
-                },
+                inline_completion_styles: make_suggestion_styles(cx),
                 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
             },
         )

crates/editor/src/movement.rs 🔗

@@ -841,12 +841,12 @@ mod tests {
             .flat_map(|offset| {
                 [
                     Inlay {
-                        id: InlayId::Suggestion(post_inc(&mut id)),
+                        id: InlayId::InlineCompletion(post_inc(&mut id)),
                         position: buffer_snapshot.anchor_at(offset, Bias::Left),
                         text: "test".into(),
                     },
                     Inlay {
-                        id: InlayId::Suggestion(post_inc(&mut id)),
+                        id: InlayId::InlineCompletion(post_inc(&mut id)),
                         position: buffer_snapshot.anchor_at(offset, Bias::Right),
                         text: "test".into(),
                     },