From 4a110a578ee2cbf3bf321b28f0bda981dd666f49 Mon Sep 17 00:00:00 2001
From: davidhi7 <77309510+davidhi7@users.noreply.github.com>
Date: Mon, 6 Jul 2026 23:22:14 +0200
Subject: [PATCH] editor: Fix completion labels not being rendered completely
(#56976)
PR https://github.com/zed-industries/zed/pull/45892 changed the logic
for rendering completion labels and divided rendering up into the "main"
part (consisting of the completion's `filter_range`) and "details" part
(everything after `filter_range`), but no longer renders text that comes
before the `filter_range`. This text is probably not relevant in too
many cases but makes some Rust completions unclear and ambiguous, as can
be seen in this example:
Without this change:
With this change:
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Closes #56973
Release Notes:
- Fix completion labels not being rendered completely
---------
Co-authored-by: Smit Barmase
---
crates/editor/src/code_context_menus.rs | 125 +++++++++++++++++-------
1 file changed, 89 insertions(+), 36 deletions(-)
diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs
index 25ac00a495..b192fa3d68 100644
--- a/crates/editor/src/code_context_menus.rs
+++ b/crates/editor/src/code_context_menus.rs
@@ -1,9 +1,9 @@
use crate::scroll::ScrollAmount;
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
- AnyElement, Entity, Focusable, FontWeight, ListSizingBehavior, ScrollHandle, ScrollStrategy,
- SharedString, Size, StrikethroughStyle, StyledText, Task, TaskExt, UniformListScrollHandle,
- div, px, uniform_list,
+ AnyElement, Entity, Focusable, FontWeight, HighlightStyle, ListSizingBehavior, ScrollHandle,
+ ScrollStrategy, SharedString, Size, StrikethroughStyle, StyledText, Task, TaskExt,
+ UniformListScrollHandle, div, px, uniform_list,
};
use itertools::Itertools;
use language::CodeLabel;
@@ -1006,43 +1006,18 @@ impl CompletionsMenu {
let highlights: Vec<_> = highlights.collect();
- let filter_range = &completion.label.filter_range;
- let full_text = &completion.label.text;
-
- let main_text: String = full_text[filter_range.clone()].to_string();
- let main_highlights: Vec<_> = highlights
- .iter()
- .filter_map(|(range, highlight)| {
- if range.end <= filter_range.start
- || range.start >= filter_range.end
- {
- return None;
- }
- let clamped_start =
- range.start.max(filter_range.start) - filter_range.start;
- let clamped_end =
- range.end.min(filter_range.end) - filter_range.start;
- Some((clamped_start..clamped_end, (*highlight)))
- })
- .collect();
- let main_label = StyledText::new(main_text)
+ let ((main_text, main_highlights), (suffix_text, suffix_highlights)) =
+ split_completion_label(
+ &completion.label.text,
+ &completion.label.filter_range,
+ &highlights,
+ );
+ let main_label = StyledText::new(main_text.to_string())
.with_default_highlights(&style.text, main_highlights);
- let suffix_text: String = full_text[filter_range.end..].to_string();
- let suffix_highlights: Vec<_> = highlights
- .iter()
- .filter_map(|(range, highlight)| {
- if range.end <= filter_range.end {
- return None;
- }
- let shifted_start = range.start.saturating_sub(filter_range.end);
- let shifted_end = range.end - filter_range.end;
- Some((shifted_start..shifted_end, (*highlight)))
- })
- .collect();
let suffix_label = if !suffix_text.is_empty() {
Some(
- StyledText::new(suffix_text)
+ StyledText::new(suffix_text.to_string())
.with_default_highlights(&style.text, suffix_highlights),
)
} else {
@@ -1691,6 +1666,42 @@ fn completion_kind_highlight_name(kind: CompletionItemKind) -> Option<&'static s
})
}
+fn split_completion_label<'a>(
+ text: &'a str,
+ filter_range: &Range,
+ highlights: &[(Range, HighlightStyle)],
+) -> (
+ (&'a str, Vec<(Range, HighlightStyle)>),
+ (&'a str, Vec<(Range, HighlightStyle)>),
+) {
+ let (main_text, suffix_text) = text.split_at(filter_range.end);
+ let main_highlights = highlights
+ .iter()
+ .filter_map(|(range, highlight)| {
+ if range.start >= filter_range.end {
+ return None;
+ }
+ let clamped_end = range.end.min(filter_range.end);
+ Some((range.start..clamped_end, *highlight))
+ })
+ .collect();
+ let suffix_highlights = highlights
+ .iter()
+ .filter_map(|(range, highlight)| {
+ if range.end <= filter_range.end {
+ return None;
+ }
+ let shifted_start = range.start.saturating_sub(filter_range.end);
+ let shifted_end = range.end - filter_range.end;
+ Some((shifted_start..shifted_end, *highlight))
+ })
+ .collect();
+ (
+ (main_text, main_highlights),
+ (suffix_text, suffix_highlights),
+ )
+}
+
fn exact_case_match_count(query: &str, string_match: &StringMatch) -> usize {
let mut exact_matches = 0;
let mut query_chars = query.chars();
@@ -2038,3 +2049,45 @@ impl CodeActionsMenu {
)
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn bold() -> HighlightStyle {
+ FontWeight::BOLD.into()
+ }
+
+ fn colored() -> HighlightStyle {
+ HighlightStyle {
+ fade_out: Some(0.5),
+ ..Default::default()
+ }
+ }
+
+ #[test]
+ fn test_split_completion_label_keeps_prefix_before_filter_range() {
+ let ((main_text, main_highlights), (suffix_text, suffix_highlights)) =
+ split_completion_label("&some_str: String", &(1..9), &[(11..17, colored())]);
+
+ assert_eq!(main_text, "&some_str");
+ assert_eq!(suffix_text, ": String");
+ assert_eq!(main_highlights, vec![]);
+ assert_eq!(suffix_highlights, vec![(2..8, colored())]);
+ }
+
+ #[test]
+ fn test_split_completion_label_splits_boundary_spanning_highlight() {
+ let ((main_text, main_highlights), (suffix_text, suffix_highlights)) =
+ split_completion_label(
+ "await.as_deref_mut(&mut self)",
+ &(6..18),
+ &[(0..29, bold())],
+ );
+
+ assert_eq!(main_text, "await.as_deref_mut");
+ assert_eq!(suffix_text, "(&mut self)");
+ assert_eq!(main_highlights, vec![(0..18, bold())]);
+ assert_eq!(suffix_highlights, vec![(0..11, bold())]);
+ }
+}