diff --git a/crates/agent_ui/src/acp/completion_provider.rs b/crates/agent_ui/src/acp/completion_provider.rs index dc38c65868385e1e5ee913dd76160c7bdebbd0ad..6d2253b40686baf6719dfc00df72a505842c4bc9 100644 --- a/crates/agent_ui/src/acp/completion_provider.rs +++ b/crates/agent_ui/src/acp/completion_provider.rs @@ -15,7 +15,8 @@ use language::{Buffer, CodeLabel, HighlightId}; use lsp::CompletionContext; use project::lsp_store::CompletionDocumentation; use project::{ - Completion, CompletionIntent, CompletionResponse, Project, ProjectPath, Symbol, WorktreeId, + Completion, CompletionDisplayOptions, CompletionIntent, CompletionResponse, Project, + ProjectPath, Symbol, WorktreeId, }; use prompt_store::PromptStore; use rope::Point; @@ -771,6 +772,7 @@ impl CompletionProvider for ContextPickerCompletionProvider { Ok(vec![CompletionResponse { completions, + display_options: CompletionDisplayOptions::default(), // Since this does its own filtering (see `filter_completions()` returns false), // there is no benefit to computing whether this set of completions is incomplete. is_incomplete: true, @@ -862,6 +864,7 @@ impl CompletionProvider for ContextPickerCompletionProvider { Ok(vec![CompletionResponse { completions, + display_options: CompletionDisplayOptions::default(), // Since this does its own filtering (see `filter_completions()` returns false), // there is no benefit to computing whether this set of completions is incomplete. is_incomplete: true, diff --git a/crates/agent_ui/src/context_picker/completion_provider.rs b/crates/agent_ui/src/context_picker/completion_provider.rs index 020d799c799f4184494fc3d9ec6a0ef8119a9897..b67b463e3bfa654baefece2c97fc505460830f2d 100644 --- a/crates/agent_ui/src/context_picker/completion_provider.rs +++ b/crates/agent_ui/src/context_picker/completion_provider.rs @@ -13,7 +13,10 @@ use http_client::HttpClientWithUrl; use itertools::Itertools; use language::{Buffer, CodeLabel, HighlightId}; use lsp::CompletionContext; -use project::{Completion, CompletionIntent, CompletionResponse, ProjectPath, Symbol, WorktreeId}; +use project::{ + Completion, CompletionDisplayOptions, CompletionIntent, CompletionResponse, ProjectPath, + Symbol, WorktreeId, +}; use prompt_store::PromptStore; use rope::Point; use text::{Anchor, OffsetRangeExt, ToPoint}; @@ -897,6 +900,7 @@ impl CompletionProvider for ContextPickerCompletionProvider { Ok(vec![CompletionResponse { completions, + display_options: CompletionDisplayOptions::default(), // Since this does its own filtering (see `filter_completions()` returns false), // there is no benefit to computing whether this set of completions is incomplete. is_incomplete: true, diff --git a/crates/agent_ui/src/slash_command.rs b/crates/agent_ui/src/slash_command.rs index 87e5d45fe85218c35316cb0a043caadf8837a2ea..c2f26c4f2ed33860196790746dd296e8c617b810 100644 --- a/crates/agent_ui/src/slash_command.rs +++ b/crates/agent_ui/src/slash_command.rs @@ -7,7 +7,10 @@ use fuzzy::{StringMatchCandidate, match_strings}; use gpui::{App, AppContext as _, Context, Entity, Task, WeakEntity, Window}; use language::{Anchor, Buffer, ToPoint}; use parking_lot::Mutex; -use project::{CompletionIntent, CompletionSource, lsp_store::CompletionDocumentation}; +use project::{ + CompletionDisplayOptions, CompletionIntent, CompletionSource, + lsp_store::CompletionDocumentation, +}; use rope::Point; use std::{ ops::Range, @@ -133,6 +136,7 @@ impl SlashCommandCompletionProvider { vec![project::CompletionResponse { completions, + display_options: CompletionDisplayOptions::default(), is_incomplete: false, }] }) @@ -237,6 +241,7 @@ impl SlashCommandCompletionProvider { Ok(vec![project::CompletionResponse { completions, + display_options: CompletionDisplayOptions::default(), // TODO: Could have slash commands indicate whether their completions are incomplete. is_incomplete: true, }]) @@ -244,6 +249,7 @@ impl SlashCommandCompletionProvider { } else { Task::ready(Ok(vec![project::CompletionResponse { completions: Vec::new(), + display_options: CompletionDisplayOptions::default(), is_incomplete: true, }])) } @@ -305,6 +311,7 @@ impl CompletionProvider for SlashCommandCompletionProvider { else { return Task::ready(Ok(vec![project::CompletionResponse { completions: Vec::new(), + display_options: CompletionDisplayOptions::default(), is_incomplete: false, }])); }; diff --git a/crates/collab_ui/src/chat_panel/message_editor.rs b/crates/collab_ui/src/chat_panel/message_editor.rs index 5fead5bcf10cc62dc6f60414978366cb2eac313b..3864ca69d88dd8231aa4b2f5b656c11f41b07282 100644 --- a/crates/collab_ui/src/chat_panel/message_editor.rs +++ b/crates/collab_ui/src/chat_panel/message_editor.rs @@ -12,7 +12,9 @@ use language::{ Anchor, Buffer, BufferSnapshot, CodeLabel, LanguageRegistry, ToOffset, language_settings::SoftWrap, }; -use project::{Completion, CompletionResponse, CompletionSource, search::SearchQuery}; +use project::{ + Completion, CompletionDisplayOptions, CompletionResponse, CompletionSource, search::SearchQuery, +}; use settings::Settings; use std::{ ops::Range, @@ -275,6 +277,7 @@ impl MessageEditor { Task::ready(Ok(vec![CompletionResponse { completions: Vec::new(), + display_options: CompletionDisplayOptions::default(), is_incomplete: false, }])) } @@ -317,6 +320,7 @@ impl MessageEditor { CompletionResponse { is_incomplete: completions.len() >= LIMIT, + display_options: CompletionDisplayOptions::default(), completions, } } diff --git a/crates/debugger_ui/src/session/running/console.rs b/crates/debugger_ui/src/session/running/console.rs index a801cedd26924198d6a0639a3832ab0253b53adb..43d86d95c4a5ad6cc0b7729a3f3579b27e1dfee7 100644 --- a/crates/debugger_ui/src/session/running/console.rs +++ b/crates/debugger_ui/src/session/running/console.rs @@ -15,7 +15,7 @@ use gpui::{ use language::{Anchor, Buffer, CodeLabel, TextBufferSnapshot, ToOffset}; use menu::{Confirm, SelectNext, SelectPrevious}; use project::{ - Completion, CompletionResponse, + Completion, CompletionDisplayOptions, CompletionResponse, debugger::session::{CompletionsQuery, OutputToken, Session}, lsp_store::CompletionDocumentation, search_history::{SearchHistory, SearchHistoryCursor}, @@ -685,6 +685,7 @@ impl ConsoleQueryBarCompletionProvider { Ok(vec![project::CompletionResponse { is_incomplete: completions.len() >= LIMIT, + display_options: CompletionDisplayOptions::default(), completions, }]) }) @@ -797,6 +798,7 @@ impl ConsoleQueryBarCompletionProvider { Ok(vec![project::CompletionResponse { completions, + display_options: CompletionDisplayOptions::default(), is_incomplete: false, }]) }) diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index 01e74284eff4cb140efe43202ef5dda9a002f94d..6d57048985955730bef2c7840d645c87b56915fc 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -11,9 +11,9 @@ use language::{Buffer, LanguageName, LanguageRegistry}; use markdown::{Markdown, MarkdownElement}; use multi_buffer::{Anchor, ExcerptId}; use ordered_float::OrderedFloat; -use project::CompletionSource; use project::lsp_store::CompletionDocumentation; use project::{CodeAction, Completion, TaskSourceKind}; +use project::{CompletionDisplayOptions, CompletionSource}; use task::DebugScenario; use task::TaskContext; @@ -232,6 +232,7 @@ pub struct CompletionsMenu { markdown_cache: Rc)>>>, language_registry: Option>, language: Option, + display_options: CompletionDisplayOptions, snippet_sort_order: SnippetSortOrder, } @@ -271,6 +272,7 @@ impl CompletionsMenu { is_incomplete: bool, buffer: Entity, completions: Box<[Completion]>, + display_options: CompletionDisplayOptions, snippet_sort_order: SnippetSortOrder, language_registry: Option>, language: Option, @@ -304,6 +306,7 @@ impl CompletionsMenu { markdown_cache: RefCell::new(VecDeque::new()).into(), language_registry, language, + display_options, snippet_sort_order, }; @@ -375,6 +378,7 @@ impl CompletionsMenu { markdown_cache: RefCell::new(VecDeque::new()).into(), language_registry: None, language: None, + display_options: CompletionDisplayOptions::default(), snippet_sort_order, } } @@ -737,6 +741,33 @@ impl CompletionsMenu { cx: &mut Context, ) -> AnyElement { let show_completion_documentation = self.show_completion_documentation; + let widest_completion_ix = if self.display_options.dynamic_width { + let completions = self.completions.borrow(); + let widest_completion_ix = self + .entries + .borrow() + .iter() + .enumerate() + .max_by_key(|(_, mat)| { + let completion = &completions[mat.candidate_id]; + let documentation = &completion.documentation; + + let mut len = completion.label.text.chars().count(); + if let Some(CompletionDocumentation::SingleLine(text)) = documentation { + if show_completion_documentation { + len += text.chars().count(); + } + } + + len + }) + .map(|(ix, _)| ix); + drop(completions); + widest_completion_ix + } else { + None + }; + let selected_item = self.selected_item; let completions = self.completions.clone(); let entries = self.entries.clone(); @@ -863,7 +894,13 @@ impl CompletionsMenu { .max_h(max_height_in_lines as f32 * window.line_height()) .track_scroll(self.scroll_handle.clone()) .with_sizing_behavior(ListSizingBehavior::Infer) - .w(rems(34.)); + .map(|this| { + if self.display_options.dynamic_width { + this.with_width_from_item(widest_completion_ix) + } else { + this.w(rems(34.)) + } + }); Popover::new().child(list).into_any_element() } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0494eb6c1f2e446202727ad987304250a3fd0291..00fe6637bf6c5ff8d8caa3c9c223fc55ffaee3cc 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -147,21 +147,22 @@ use multi_buffer::{ use parking_lot::Mutex; use persistence::DB; use project::{ - BreakpointWithPosition, CodeAction, Completion, CompletionIntent, CompletionResponse, - CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, Location, LocationLink, - PrepareRenameResponse, Project, ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind, - debugger::breakpoint_store::Breakpoint, + BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent, + CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, + Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath, + ProjectTransaction, TaskSourceKind, debugger::{ breakpoint_store::{ - BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore, - BreakpointStoreEvent, + Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState, + BreakpointStore, BreakpointStoreEvent, }, session::{Session, SessionEvent}, }, git_store::{GitStoreEvent, RepositoryEvent}, lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle}, - project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter}, - project_settings::{GitGutterSetting, ProjectSettings}, + project_settings::{ + DiagnosticSeverity, GitGutterSetting, GoToDiagnosticSeverityFilter, ProjectSettings, + }, }; use rand::{seq::SliceRandom, thread_rng}; use rpc::{ErrorCode, ErrorExt, proto::PeerId}; @@ -5635,17 +5636,25 @@ impl Editor { // that having one source with `is_incomplete: true` doesn't cause all to be re-queried. let mut completions = Vec::new(); let mut is_incomplete = false; + let mut display_options: Option = None; if let Some(provider_responses) = provider_responses.await.log_err() && !provider_responses.is_empty() { for response in provider_responses { completions.extend(response.completions); is_incomplete = is_incomplete || response.is_incomplete; + match display_options.as_mut() { + None => { + display_options = Some(response.display_options); + } + Some(options) => options.merge(&response.display_options), + } } if completion_settings.words == WordsCompletionMode::Fallback { words = Task::ready(BTreeMap::default()); } } + let display_options = display_options.unwrap_or_default(); let mut words = words.await; if let Some(word_to_exclude) = &word_to_exclude { @@ -5687,6 +5696,7 @@ impl Editor { is_incomplete, buffer.clone(), completions.into(), + display_options, snippet_sort_order, languages, language, @@ -22260,6 +22270,7 @@ fn snippet_completions( if scopes.is_empty() { return Task::ready(Ok(CompletionResponse { completions: vec![], + display_options: CompletionDisplayOptions::default(), is_incomplete: false, })); } @@ -22284,6 +22295,7 @@ fn snippet_completions( if last_word.is_empty() { return Ok(CompletionResponse { completions: vec![], + display_options: CompletionDisplayOptions::default(), is_incomplete: true, }); } @@ -22405,6 +22417,7 @@ fn snippet_completions( Ok(CompletionResponse { completions, + display_options: CompletionDisplayOptions::default(), is_incomplete, }) }) diff --git a/crates/inspector_ui/src/div_inspector.rs b/crates/inspector_ui/src/div_inspector.rs index c3d687e57adbb0b1883d81f6e7ba726d0d60974d..fa8b76517f0125e7319f035b41996e445451510a 100644 --- a/crates/inspector_ui/src/div_inspector.rs +++ b/crates/inspector_ui/src/div_inspector.rs @@ -14,7 +14,10 @@ use language::{ DiagnosticSeverity, LanguageServerId, Point, ToOffset as _, ToPoint as _, }; use project::lsp_store::CompletionDocumentation; -use project::{Completion, CompletionResponse, CompletionSource, Project, ProjectPath}; +use project::{ + Completion, CompletionDisplayOptions, CompletionResponse, CompletionSource, Project, + ProjectPath, +}; use std::fmt::Write as _; use std::ops::Range; use std::path::Path; @@ -664,6 +667,7 @@ impl CompletionProvider for RustStyleCompletionProvider { confirm: None, }) .collect(), + display_options: CompletionDisplayOptions::default(), is_incomplete: false, }])) } diff --git a/crates/keymap_editor/src/keymap_editor.rs b/crates/keymap_editor/src/keymap_editor.rs index 12149061124d2b3144a32b7f54a65ce5af70d492..a8e356276b31e1d6daa79fdf85f6ff3566f9749d 100644 --- a/crates/keymap_editor/src/keymap_editor.rs +++ b/crates/keymap_editor/src/keymap_editor.rs @@ -22,7 +22,7 @@ use gpui::{ }; use language::{Language, LanguageConfig, ToOffset as _}; use notifications::status_toast::{StatusToast, ToastIcon}; -use project::Project; +use project::{CompletionDisplayOptions, Project}; use settings::{BaseKeymap, KeybindSource, KeymapFile, Settings as _, SettingsAssets}; use ui::{ ActiveTheme as _, App, Banner, BorrowAppContext, ContextMenu, IconButtonShape, Indicator, @@ -2911,6 +2911,7 @@ impl CompletionProvider for KeyContextCompletionProvider { confirm: None, }) .collect(), + display_options: CompletionDisplayOptions::default(), is_incomplete: false, }])) } diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 1315915203e917a7be8dd6e41031495971ddec24..73f5da086c78b3a3e597be3d32bdcb7a15649117 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -16,10 +16,10 @@ pub mod lsp_ext_command; pub mod rust_analyzer_ext; use crate::{ - CodeAction, ColorPresentation, Completion, CompletionResponse, CompletionSource, - CoreCompletion, DocumentColor, Hover, InlayHint, LocationLink, LspAction, LspPullDiagnostics, - ManifestProvidersStore, Project, ProjectItem, ProjectPath, ProjectTransaction, - PulledDiagnostics, ResolveState, Symbol, + CodeAction, ColorPresentation, Completion, CompletionDisplayOptions, CompletionResponse, + CompletionSource, CoreCompletion, DocumentColor, Hover, InlayHint, LocationLink, LspAction, + LspPullDiagnostics, ManifestProvidersStore, Project, ProjectItem, ProjectPath, + ProjectTransaction, PulledDiagnostics, ResolveState, Symbol, buffer_store::{BufferStore, BufferStoreEvent}, environment::ProjectEnvironment, lsp_command::{self, *}, @@ -5828,6 +5828,7 @@ impl LspStore { .await; Ok(vec![CompletionResponse { completions, + display_options: CompletionDisplayOptions::default(), is_incomplete: completion_response.is_incomplete, }]) }) @@ -5920,6 +5921,7 @@ impl LspStore { .await; Some(CompletionResponse { completions, + display_options: CompletionDisplayOptions::default(), is_incomplete: completion_response.is_incomplete, }) }); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 229249d48c5ab370c4b354cda7cbf9312790759d..46dd3b7d9e51aa06aa45b9cccb87533f2b90f58c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -574,11 +574,23 @@ impl std::fmt::Debug for Completion { /// Response from a source of completions. pub struct CompletionResponse { pub completions: Vec, + pub display_options: CompletionDisplayOptions, /// When false, indicates that the list is complete and so does not need to be re-queried if it /// can be filtered instead. pub is_incomplete: bool, } +#[derive(Default)] +pub struct CompletionDisplayOptions { + pub dynamic_width: bool, +} + +impl CompletionDisplayOptions { + pub fn merge(&mut self, other: &CompletionDisplayOptions) { + self.dynamic_width = self.dynamic_width && other.dynamic_width; + } +} + /// Response from language server completion request. #[derive(Clone, Debug, Default)] pub(crate) struct CoreCompletionResponse {