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: Image With this change: grafik 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())]); + } +}