Add option for code context menu items to have dynamic width (#37404)
Follow up to https://github.com/zed-industries/zed/pull/30598 This PR introduces the `display_options` field in the `CompletionResponse`, allowing a code context menu width to be dynamically dictated based on its larger item. This will allow us to have the @-mentions and slash commands completion menus in the agent panel not be bigger than it needs to be. It may also be relevant/useful in the future for other use cases. For now, we set all instances of code context menus to use a fixed width, as defined in the PR linked above, which means this PR shouldn't cause any visual change. Release Notes: - N/A Co-authored-by: Michael Sloan <mgsloan+github@gmail.com>
This commit is contained in:
co-authored by
Michael Sloan
parent
4b96ad3fba
commit
946efb03df
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}]));
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}])
|
||||
})
|
||||
|
||||
@@ -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<RefCell<VecDeque<(MarkdownCacheKey, Entity<Markdown>)>>>,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
language: Option<LanguageName>,
|
||||
display_options: CompletionDisplayOptions,
|
||||
snippet_sort_order: SnippetSortOrder,
|
||||
}
|
||||
|
||||
@@ -271,6 +272,7 @@ impl CompletionsMenu {
|
||||
is_incomplete: bool,
|
||||
buffer: Entity<Buffer>,
|
||||
completions: Box<[Completion]>,
|
||||
display_options: CompletionDisplayOptions,
|
||||
snippet_sort_order: SnippetSortOrder,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
language: Option<LanguageName>,
|
||||
@@ -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<Editor>,
|
||||
) -> 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()
|
||||
}
|
||||
|
||||
@@ -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<CompletionDisplayOptions> = 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,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
}]))
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}]))
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -574,11 +574,23 @@ impl std::fmt::Debug for Completion {
|
||||
/// Response from a source of completions.
|
||||
pub struct CompletionResponse {
|
||||
pub completions: Vec<Completion>,
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user