@@ -7,7 +7,9 @@ use std::sync::atomic::AtomicBool;
use acp_thread::MentionUri;
use agent::{HistoryEntry, HistoryStore};
use anyhow::Result;
-use editor::{CompletionProvider, Editor, ExcerptId};
+use editor::{
+ CompletionProvider, Editor, ExcerptId, code_context_menus::COMPLETION_MENU_MAX_WIDTH,
+};
use fuzzy::{PathMatch, StringMatch, StringMatchCandidate};
use gpui::{App, Entity, Task, WeakEntity};
use language::{Buffer, CodeLabel, CodeLabelBuilder, HighlightId};
@@ -25,6 +27,7 @@ use ui::prelude::*;
use util::ResultExt as _;
use util::paths::PathStyle;
use util::rel_path::RelPath;
+use util::truncate_and_remove_front;
use workspace::Workspace;
use crate::AgentPanel;
@@ -336,14 +339,20 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
mention_set: WeakEntity<MentionSet>,
workspace: Entity<Workspace>,
project: Entity<Project>,
+ label_max_chars: usize,
cx: &mut App,
) -> Option<Completion> {
let path_style = project.read(cx).path_style(cx);
let (file_name, directory) =
extract_file_name_and_directory(&project_path.path, path_prefix, path_style);
- let label =
- build_code_label_for_path(&file_name, directory.as_ref().map(|s| s.as_ref()), None, cx);
+ let label = build_code_label_for_path(
+ &file_name,
+ directory.as_ref().map(|s| s.as_ref()),
+ None,
+ label_max_chars,
+ cx,
+ );
let abs_path = project.read(cx).absolute_path(&project_path, cx)?;
@@ -392,6 +401,7 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
editor: WeakEntity<Editor>,
mention_set: WeakEntity<MentionSet>,
workspace: Entity<Workspace>,
+ label_max_chars: usize,
cx: &mut App,
) -> Option<Completion> {
let project = workspace.read(cx).project().clone();
@@ -414,6 +424,7 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
&symbol.name,
Some(&file_name),
Some(symbol.range.start.0.row + 1),
+ label_max_chars,
cx,
);
@@ -852,7 +863,7 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
buffer: &Entity<Buffer>,
buffer_position: Anchor,
_trigger: CompletionContext,
- _window: &mut Window,
+ window: &mut Window,
cx: &mut Context<Editor>,
) -> Task<Result<Vec<CompletionResponse>>> {
let state = buffer.update(cx, |buffer, cx| {
@@ -948,6 +959,31 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
let search_task =
self.search_mentions(mode, query, Arc::<AtomicBool>::default(), cx);
+ // Calculate maximum characters available for the full label (file_name + space + directory)
+ // based on maximum menu width after accounting for padding, spacing, and icon width
+ let label_max_chars = {
+ // Base06 left padding + Base06 gap + Base06 right padding + icon width
+ let used_pixels = DynamicSpacing::Base06.px(cx) * 3.0
+ + IconSize::XSmall.rems() * window.rem_size();
+
+ let style = window.text_style();
+ let font_id = window.text_system().resolve_font(&style.font());
+ let font_size = TextSize::Small.rems(cx).to_pixels(window.rem_size());
+
+ // Fallback em_width of 10px matches file_finder.rs fallback for TextSize::Small
+ let em_width = cx
+ .text_system()
+ .em_width(font_id, font_size)
+ .unwrap_or(px(10.0));
+
+ // Calculate available pixels for text (file_name + directory)
+ // Using max width since dynamic_width allows the menu to expand up to this
+ let available_pixels = COMPLETION_MENU_MAX_WIDTH - used_pixels;
+
+ // Convert to character count (total available for file_name + directory)
+ (f32::from(available_pixels) / f32::from(em_width)) as usize
+ };
+
cx.spawn(async move |_, cx| {
let matches = search_task.await;
@@ -984,6 +1020,7 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
mention_set.clone(),
workspace.clone(),
project.clone(),
+ label_max_chars,
cx,
)
}
@@ -996,6 +1033,7 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
editor.clone(),
mention_set.clone(),
workspace.clone(),
+ label_max_chars,
cx,
)
}
@@ -1595,6 +1633,7 @@ fn build_code_label_for_path(
file: &str,
directory: Option<&str>,
line_number: Option<u32>,
+ label_max_chars: usize,
cx: &App,
) -> CodeLabel {
let variable_highlight_id = cx
@@ -1608,7 +1647,13 @@ fn build_code_label_for_path(
label.push_str(" ", None);
if let Some(directory) = directory {
- label.push_str(directory, variable_highlight_id);
+ let file_name_chars = file.chars().count();
+ // Account for: file_name + space (ellipsis is handled by truncate_and_remove_front)
+ let directory_max_chars = label_max_chars
+ .saturating_sub(file_name_chars)
+ .saturating_sub(1);
+ let truncated_directory = truncate_and_remove_front(directory, directory_max_chars.max(5));
+ label.push_str(&truncated_directory, variable_highlight_id);
}
if let Some(line_number) = line_number {
label.push_str(&format!(" L{}", line_number), variable_highlight_id);
@@ -49,6 +49,8 @@ pub const MENU_GAP: Pixels = px(4.);
pub const MENU_ASIDE_X_PADDING: Pixels = px(16.);
pub const MENU_ASIDE_MIN_WIDTH: Pixels = px(260.);
pub const MENU_ASIDE_MAX_WIDTH: Pixels = px(500.);
+pub const COMPLETION_MENU_MIN_WIDTH: Pixels = px(280.);
+pub const COMPLETION_MENU_MAX_WIDTH: Pixels = px(540.);
// Constants for the markdown cache. The purpose of this cache is to reduce flickering due to
// documentation not yet being parsed.
@@ -907,26 +909,29 @@ impl CompletionsMenu {
})
});
- div().min_w(px(280.)).max_w(px(540.)).child(
- ListItem::new(mat.candidate_id)
- .inset(true)
- .toggle_state(item_ix == selected_item)
- .on_click(cx.listener(move |editor, _event, window, cx| {
- cx.stop_propagation();
- if let Some(task) = editor.confirm_completion(
- &ConfirmCompletion {
- item_ix: Some(item_ix),
- },
- window,
- cx,
- ) {
- task.detach_and_log_err(cx)
- }
- }))
- .start_slot::<AnyElement>(start_slot)
- .child(h_flex().overflow_hidden().child(completion_label))
- .end_slot::<Label>(documentation_label),
- )
+ div()
+ .min_w(COMPLETION_MENU_MIN_WIDTH)
+ .max_w(COMPLETION_MENU_MAX_WIDTH)
+ .child(
+ ListItem::new(mat.candidate_id)
+ .inset(true)
+ .toggle_state(item_ix == selected_item)
+ .on_click(cx.listener(move |editor, _event, window, cx| {
+ cx.stop_propagation();
+ if let Some(task) = editor.confirm_completion(
+ &ConfirmCompletion {
+ item_ix: Some(item_ix),
+ },
+ window,
+ cx,
+ ) {
+ task.detach_and_log_err(cx)
+ }
+ }))
+ .start_slot::<AnyElement>(start_slot)
+ .child(h_flex().overflow_hidden().child(completion_label))
+ .end_slot::<Label>(documentation_label),
+ )
})
.collect()
}),