Add text icons for completion items (#56396)

Part of https://github.com/zed-industries/zed/issues/4943
Based on the last discussion, Zed does not want to have icons there, so
technically closes the issue?

Helix:

<img width="735" height="266" alt="Screenshot 2026-05-11 at 12 47 36"
src="https://github.com/user-attachments/assets/760e844b-6aad-4a42-ad39-28ea91fe4a90"
/>


VSCode:

<img width="964" height="414" alt="Screenshot 2026-05-11 at 12 49 00"
src="https://github.com/user-attachments/assets/37e383cd-e5aa-49a7-9a07-8af00ad07f27"
/>

<img width="970" height="453" alt="Screenshot 2026-05-11 at 12 49 04"
src="https://github.com/user-attachments/assets/4787d12e-b897-4dfd-a325-30fb45bc6001"
/>

RustRover:

<img width="1182" height="429" alt="Screenshot 2026-05-11 at 12 49 34"
src="https://github.com/user-attachments/assets/5876ce8b-e33d-4f4b-b7a4-44a25048f9f2"
/>

<img width="1050" height="424" alt="Screenshot 2026-05-11 at 12 49 42"
src="https://github.com/user-attachments/assets/4fbcb44b-00b7-4283-9423-556cc335c9b2"
/>


Zed:

<img width="1191" height="457" alt="Screenshot 2026-05-11 at 12 54 17"
src="https://github.com/user-attachments/assets/ff953146-c621-4c17-97f6-2f8504fef4cc"
/>

<img width="1149" height="446" alt="Screenshot 2026-05-11 at 12 54 29"
src="https://github.com/user-attachments/assets/33112b67-2ed1-4bd2-92ea-762744336074"
/>

(tooltip on hover)

<img width="797" height="223" alt="image"
src="https://github.com/user-attachments/assets/9c06054c-51f0-4f9b-b740-a4076d2591c6"
/>

Disabled by default, use `"completion_menu_item_kind": "symbol"` to
enable.

Release Notes:

- Added text icons for completion items (disabled by default, use
`"completion_menu_item_kind": "symbol"` to enable)
This commit is contained in:
Kirill Bulatov 2026-05-12 09:29:16 +03:00 committed by GitHub
parent 51b43c90f9
commit a5bf443ef6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 222 additions and 15 deletions

View file

@ -315,6 +315,14 @@
"completion_menu_scrollbar": "never",
// Whether to align detail text in code completions context menus left or right.
"completion_detail_alignment": "left",
// How to display the LSP item kind (function, method, variable, etc.)
// of each entry in the completions menu.
//
// 1. Do not display item kinds:
// "off" (default)
// 2. Display a single-letter badge, colorized based on the active syntax theme:
// "symbol"
"completion_menu_item_kind": "off",
// How to display diffs in the editor.
//
// Default: split

View file

@ -8,7 +8,7 @@ use gpui::{
use itertools::Itertools;
use language::CodeLabel;
use language::{Buffer, LanguageName, LanguageRegistry};
use lsp::CompletionItemTag;
use lsp::{CompletionItemKind, CompletionItemTag};
use markdown::{CopyButtonVisibility, Markdown, MarkdownElement};
use multi_buffer::Anchor;
use ordered_float::OrderedFloat;
@ -29,8 +29,8 @@ use std::{
};
use task::ResolvedTask;
use ui::{
Color, IntoElement, ListItem, Pixels, Popover, ScrollAxes, Scrollbars, Styled, WithScrollbar,
prelude::*,
Color, IntoElement, ListItem, Pixels, Popover, ScrollAxes, Scrollbars, Styled, Tooltip,
WithScrollbar, prelude::*,
};
use util::ResultExt;
@ -43,7 +43,7 @@ use crate::{
};
use crate::{CodeActionSource, EditorSettings};
use collections::{HashSet, VecDeque};
use settings::{CompletionDetailAlignment, Settings, SnippetSortOrder};
use settings::{CompletionDetailAlignment, CompletionMenuItemKind, Settings, SnippetSortOrder};
pub const MENU_GAP: Pixels = px(4.);
pub const MENU_ASIDE_X_PADDING: Pixels = px(16.);
@ -782,8 +782,9 @@ impl CompletionsMenu {
cx: &mut Context<Editor>,
) -> AnyElement {
let show_completion_documentation = self.show_completion_documentation;
let completion_detail_alignment =
EditorSettings::get_global(cx).completion_detail_alignment;
let editor_settings = EditorSettings::get_global(cx);
let completion_detail_alignment = editor_settings.completion_detail_alignment;
let completion_menu_item_kind = editor_settings.completion_menu_item_kind;
let widest_completion_ix = if self.display_options.dynamic_width {
let completions = self.completions.borrow();
let widest_completion_ix = self
@ -952,7 +953,7 @@ impl CompletionsMenu {
_ => None,
};
let start_slot = completion
let icon_or_color_slot = completion
.color()
.map(|color| {
div()
@ -971,6 +972,27 @@ impl CompletionsMenu {
})
});
let kind_letter_slot = match completion_menu_item_kind {
CompletionMenuItemKind::Off => None,
CompletionMenuItemKind::Symbol => Some(render_completion_kind_letter(
completion.kind(),
item_ix,
&style,
)),
};
let start_slot = match (kind_letter_slot, icon_or_color_slot) {
(Some(letter), Some(icon_or_color)) => Some(
h_flex()
.gap_0p5()
.child(letter)
.child(icon_or_color)
.into_any_element(),
),
(Some(letter), None) => Some(letter),
(None, slot) => slot,
};
div()
.min_w(COMPLETION_MENU_MIN_WIDTH)
.max_w(COMPLETION_MENU_MAX_WIDTH)
@ -1385,6 +1407,127 @@ impl CompletionsMenu {
}
}
fn render_completion_kind_letter(
kind: Option<CompletionItemKind>,
item_ix: usize,
style: &EditorStyle,
) -> AnyElement {
let badge = div()
.flex_none()
.w(IconSize::XSmall.rems())
.text_center()
.text_size(rems_from_px(11.))
.line_height(rems_from_px(14.));
let Some(kind) = kind else {
return badge.into_any_element();
};
let Some(letter) = completion_kind_letter(kind) else {
return badge.into_any_element();
};
let color = completion_kind_highlight_name(kind)
.and_then(|name| {
style.syntax.style_for_name(name).or_else(|| {
let (parent, _) = name.rsplit_once('.')?;
style.syntax.style_for_name(parent)
})
})
.and_then(|hl| hl.color);
badge
.id(("completion-kind", item_ix))
.tooltip(Tooltip::text(completion_kind_name(kind)))
.child(letter)
.when_some(color, |element, color| element.text_color(color))
.into_any_element()
}
fn completion_kind_name(kind: CompletionItemKind) -> &'static str {
match kind {
CompletionItemKind::TEXT => "Text",
CompletionItemKind::METHOD => "Method",
CompletionItemKind::FUNCTION => "Function",
CompletionItemKind::CONSTRUCTOR => "Constructor",
CompletionItemKind::FIELD => "Field",
CompletionItemKind::VARIABLE => "Variable",
CompletionItemKind::CLASS => "Class",
CompletionItemKind::INTERFACE => "Interface",
CompletionItemKind::MODULE => "Module",
CompletionItemKind::PROPERTY => "Property",
CompletionItemKind::UNIT => "Unit",
CompletionItemKind::VALUE => "Value",
CompletionItemKind::ENUM => "Enum",
CompletionItemKind::KEYWORD => "Keyword",
CompletionItemKind::SNIPPET => "Snippet",
CompletionItemKind::COLOR => "Color",
CompletionItemKind::FILE => "File",
CompletionItemKind::REFERENCE => "Reference",
CompletionItemKind::FOLDER => "Folder",
CompletionItemKind::ENUM_MEMBER => "Enum Member",
CompletionItemKind::CONSTANT => "Constant",
CompletionItemKind::STRUCT => "Struct",
CompletionItemKind::EVENT => "Event",
CompletionItemKind::OPERATOR => "Operator",
CompletionItemKind::TYPE_PARAMETER => "Type Parameter",
_ => "Unknown",
}
}
fn completion_kind_letter(kind: CompletionItemKind) -> Option<&'static str> {
Some(match kind {
CompletionItemKind::TEXT => "t",
CompletionItemKind::METHOD => "m",
CompletionItemKind::FUNCTION => "f",
CompletionItemKind::CONSTRUCTOR => "C",
CompletionItemKind::FIELD => "f",
CompletionItemKind::VARIABLE => "v",
CompletionItemKind::CLASS => "c",
CompletionItemKind::INTERFACE => "i",
CompletionItemKind::MODULE => "M",
CompletionItemKind::PROPERTY => "p",
CompletionItemKind::UNIT => "u",
CompletionItemKind::VALUE => "v",
CompletionItemKind::ENUM => "e",
CompletionItemKind::KEYWORD => "k",
CompletionItemKind::SNIPPET => "s",
CompletionItemKind::COLOR => "c",
CompletionItemKind::FILE => "F",
CompletionItemKind::REFERENCE => "r",
CompletionItemKind::FOLDER => "D",
CompletionItemKind::ENUM_MEMBER => "e",
CompletionItemKind::CONSTANT => "c",
CompletionItemKind::STRUCT => "S",
CompletionItemKind::EVENT => "E",
CompletionItemKind::OPERATOR => "o",
CompletionItemKind::TYPE_PARAMETER => "T",
_ => return None,
})
}
fn completion_kind_highlight_name(kind: CompletionItemKind) -> Option<&'static str> {
Some(match kind {
CompletionItemKind::CLASS => "type",
CompletionItemKind::CONSTANT => "constant",
CompletionItemKind::CONSTRUCTOR => "constructor",
CompletionItemKind::ENUM => "enum",
CompletionItemKind::ENUM_MEMBER => "variant",
CompletionItemKind::FIELD => "property",
CompletionItemKind::FUNCTION => "function",
CompletionItemKind::INTERFACE => "type",
CompletionItemKind::METHOD => "function.method",
CompletionItemKind::MODULE => "namespace",
CompletionItemKind::OPERATOR => "operator",
CompletionItemKind::PROPERTY => "property",
CompletionItemKind::STRUCT => "type",
CompletionItemKind::TYPE_PARAMETER => "type",
CompletionItemKind::VARIABLE => "variable",
CompletionItemKind::KEYWORD => "keyword",
CompletionItemKind::SNIPPET => "string",
_ => return None,
})
}
fn exact_case_match_count(query: &str, string_match: &StringMatch) -> usize {
let mut exact_matches = 0;
let mut query_chars = query.chars();

View file

@ -80,9 +80,9 @@ pub use display_map::{
};
pub use edit_prediction_types::Direction;
pub use editor_settings::{
CompletionDetailAlignment, CurrentLineHighlight, DiffViewStyle, DocumentColorsRenderMode,
EditorSettings, EditorSettingsScrollbarProxy, ScrollBeyondLastLine, ScrollbarAxes,
SearchSettings, ShowMinimap, ui_scrollbar_settings_from_raw,
CompletionDetailAlignment, CompletionMenuItemKind, CurrentLineHighlight, DiffViewStyle,
DocumentColorsRenderMode, EditorSettings, EditorSettingsScrollbarProxy, ScrollBeyondLastLine,
ScrollbarAxes, SearchSettings, ShowMinimap, ui_scrollbar_settings_from_raw,
};
pub use element::{
CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,

View file

@ -4,10 +4,11 @@ use gpui::App;
use language::CursorShape;
use project::project_settings::DiagnosticSeverity;
pub use settings::{
CodeLens, CompletionDetailAlignment, CurrentLineHighlight, DelayMs, DiffViewStyle, DisplayIn,
DocumentColorsRenderMode, DoubleClickInMultibuffer, GoToDefinitionFallback,
GoToDefinitionScrollStrategy, MinimapThumb, MinimapThumbBorder, MultiCursorModifier,
ScrollBeyondLastLine, ScrollbarDiagnostics, SeedQuerySetting, ShowMinimap, SnippetSortOrder,
CodeLens, CompletionDetailAlignment, CompletionMenuItemKind, CurrentLineHighlight, DelayMs,
DiffViewStyle, DisplayIn, DocumentColorsRenderMode, DoubleClickInMultibuffer,
GoToDefinitionFallback, GoToDefinitionScrollStrategy, MinimapThumb, MinimapThumbBorder,
MultiCursorModifier, ScrollBeyondLastLine, ScrollbarDiagnostics, SeedQuerySetting, ShowMinimap,
SnippetSortOrder,
};
use settings::{RegisterSetting, RelativeLineNumbers, Settings};
use ui::scrollbars::ShowScrollbar;
@ -63,6 +64,7 @@ pub struct EditorSettings {
pub minimum_contrast_for_highlights: f32,
pub completion_menu_scrollbar: ShowScrollbar,
pub completion_detail_alignment: CompletionDetailAlignment,
pub completion_menu_item_kind: CompletionMenuItemKind,
pub diff_view_style: DiffViewStyle,
pub minimum_split_diff_width: f32,
}
@ -304,6 +306,7 @@ impl Settings for EditorSettings {
.map(ui_scrollbar_settings_from_raw)
.unwrap(),
completion_detail_alignment: editor.completion_detail_alignment.unwrap(),
completion_menu_item_kind: editor.completion_menu_item_kind.unwrap(),
diff_view_style: editor.diff_view_style.unwrap(),
minimum_split_diff_width: editor.minimum_split_diff_width.unwrap(),
}

View file

@ -315,6 +315,7 @@ impl VsCodeSettings {
vertical_scroll_margin: self.read_f32("editor.cursorSurroundingLines"),
completion_menu_scrollbar: None,
completion_detail_alignment: None,
completion_menu_item_kind: None,
diff_view_style: None,
minimum_split_diff_width: None,
}

View file

@ -245,6 +245,16 @@ pub struct EditorSettingsContent {
/// Default: left
pub completion_detail_alignment: Option<CompletionDetailAlignment>,
/// How to display the LSP item kind (function, method, variable, etc.)
/// of each entry in the completions menu.
///
/// - "off": do not display item kinds (default).
/// - "symbol": display a single-letter badge, colorized based on the
/// active syntax theme.
///
/// Default: off
pub completion_menu_item_kind: Option<CompletionMenuItemKind>,
/// How to display diffs in the editor.
///
/// Default: split
@ -300,6 +310,27 @@ pub enum CompletionDetailAlignment {
Right,
}
#[derive(
Debug,
Default,
Clone,
Copy,
Serialize,
Deserialize,
JsonSchema,
MergeFrom,
PartialEq,
Eq,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum CompletionMenuItemKind {
#[default]
Off,
Symbol,
}
impl RelativeLineNumbers {
pub fn enabled(&self) -> bool {
match self {

View file

@ -8500,7 +8500,7 @@ fn language_settings_data() -> Box<[SettingsPageItem]> {
]
}
fn completions_section() -> [SettingsPageItem; 7] {
fn completions_section() -> [SettingsPageItem; 8] {
[
SettingsPageItem::SectionHeader("Completions"),
SettingsPageItem::SettingItem(SettingItem {
@ -8612,6 +8612,21 @@ fn language_settings_data() -> Box<[SettingsPageItem]> {
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Completion Menu Item Kind",
description: "How to display the LSP item kind (function, method, variable, etc.) of each entry in the completions menu.",
field: Box::new(SettingField {
json_path: Some("editor.completion_menu_item_kind"),
pick: |settings_content| {
settings_content.editor.completion_menu_item_kind.as_ref()
},
write: |settings_content, value, _| {
settings_content.editor.completion_menu_item_kind = value;
},
}),
metadata: None,
files: USER,
}),
]
}

View file

@ -500,6 +500,7 @@ fn init_renderers(cx: &mut App) {
.add_basic_renderer::<settings::WordsCompletionMode>(render_dropdown)
.add_basic_renderer::<settings::LspInsertMode>(render_dropdown)
.add_basic_renderer::<settings::CompletionDetailAlignment>(render_dropdown)
.add_basic_renderer::<settings::CompletionMenuItemKind>(render_dropdown)
.add_basic_renderer::<settings::DiffViewStyle>(render_dropdown)
.add_basic_renderer::<settings::AlternateScroll>(render_dropdown)
.add_basic_renderer::<settings::TerminalBlink>(render_dropdown)

View file

@ -384,6 +384,11 @@ TBD: Centered layout related settings
"lsp_document_colors": "inlay", // none, inlay, border, background
// When to show the scrollbar in the completion menu.
"completion_menu_scrollbar": "never", // auto, system, always, never
// How to display the LSP item kind (function, method, variable, etc.)
// of each entry in the completions menu. "symbol" shows a syntax-colored
// single-letter badge.
"completion_menu_item_kind": "off", // off, symbol
// Turn on colorization of brackets in editors (configurable per language)
"colorize_brackets": true,
```