From 24572649d6f16c74d79d0ca186d900223f2c5ae5 Mon Sep 17 00:00:00 2001
From: legs <145564979+justsomelegs@users.noreply.github.com>
Date: Sun, 4 Jan 2026 22:15:22 +0000
Subject: [PATCH] editor: Add alignment setting to code completion context menu
detail text (#45892)
Closes #5154
Release Notes:
- Added a setting (`completion_detail_alignment`) to change the detail
text alignment in code completion context menus.
Right Alignment
Left Alignment
---------
Co-authored-by: Danilo Leal
---
assets/settings/default.json | 2 +
crates/editor/src/code_context_menus.rs | 78 ++++++++++++++++++-
crates/editor/src/editor.rs | 4 +-
crates/editor/src/editor_settings.rs | 9 ++-
.../settings/src/settings_content/editor.rs | 26 +++++++
crates/settings/src/vscode_import.rs | 1 +
crates/settings_ui/src/page_data.rs | 15 ++++
crates/settings_ui/src/settings_ui.rs | 1 +
8 files changed, 127 insertions(+), 9 deletions(-)
diff --git a/assets/settings/default.json b/assets/settings/default.json
index f12586a68039783c44015ae5963187ce986b9d8d..e0ce02ae2ebc25aa0cd4165315bd381d725923a3 100644
--- a/assets/settings/default.json
+++ b/assets/settings/default.json
@@ -284,6 +284,8 @@
// 4. Never show the scrollbar:
// "never" (default)
"completion_menu_scrollbar": "never",
+ // Whether to align detail text in code completions context menus left or right.
+ "completion_detail_alignment": "left",
// Show method signatures in the editor, when inside parentheses.
"auto_signature_help": false,
// Whether to show the signature help after completion or a bracket pair inserted.
diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs
index e5520be88e34307220126ebafdba6c6371a5db12..e1ea2822168c836caffd9ef6f59b263c6432d10b 100644
--- a/crates/editor/src/code_context_menus.rs
+++ b/crates/editor/src/code_context_menus.rs
@@ -43,7 +43,7 @@ use crate::{
};
use crate::{CodeActionSource, EditorSettings};
use collections::{HashSet, VecDeque};
-use settings::{Settings, SnippetSortOrder};
+use settings::{CompletionDetailAlignment, Settings, SnippetSortOrder};
pub const MENU_GAP: Pixels = px(4.);
pub const MENU_ASIDE_X_PADDING: Pixels = px(16.);
@@ -786,6 +786,8 @@ impl CompletionsMenu {
cx: &mut Context,
) -> AnyElement {
let show_completion_documentation = self.show_completion_documentation;
+ let completion_detail_alignment =
+ EditorSettings::get_global(cx).completion_detail_alignment;
let widest_completion_ix = if self.display_options.dynamic_width {
let completions = self.completions.borrow();
let widest_completion_ix = self
@@ -839,6 +841,7 @@ impl CompletionsMenu {
};
let filter_start = completion.label.filter_range.start;
+
let highlights = gpui::combine_highlights(
mat.ranges().map(|range| {
(
@@ -880,8 +883,58 @@ impl CompletionsMenu {
}),
);
- let completion_label = StyledText::new(completion.label.text.clone())
- .with_default_highlights(&style.text, highlights);
+ 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)
+ .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)
+ .with_default_highlights(&style.text, suffix_highlights),
+ )
+ } else {
+ None
+ };
+
+ let left_aligned_suffix =
+ matches!(completion_detail_alignment, CompletionDetailAlignment::Left);
+
+ let right_aligned_suffix = matches!(
+ completion_detail_alignment,
+ CompletionDetailAlignment::Right,
+ );
let documentation_label = match documentation {
Some(CompletionDocumentation::SingleLine(text))
@@ -942,7 +995,24 @@ impl CompletionsMenu {
}
}))
.start_slot::(start_slot)
- .child(h_flex().overflow_hidden().child(completion_label))
+ .child(
+ h_flex()
+ .min_w_0()
+ .w_full()
+ .when(left_aligned_suffix, |this| this.justify_start())
+ .when(right_aligned_suffix, |this| {
+ this.justify_between()
+ })
+ .child(
+ div()
+ .flex_none()
+ .whitespace_nowrap()
+ .child(main_label),
+ )
+ .when_some(suffix_label, |this, suffix| {
+ this.child(div().truncate().child(suffix))
+ }),
+ )
.end_slot::