Files
zed/crates/keymap_editor/src/action_completion_provider.rs
T
Danilo LealandMartin Ye 28a179639d Improve warning display UI related to skills (#58838)
This PR tackles how we communicate errors/warnings relative to skills.
It touches three major surfaces: callouts in the thread view, menu items
in the completion menu, and settings UI. These are all the places where
we display something to tell the user a skill might have a problem.

In the settings UI, we now clamp the skill description to five lines.
And if there's a warning, we show the warning icon and the description
in a tooltip when hovering it:

<img width="600" alt="Screenshot 2026-06-08 at 10  51@2x"
src="https://github.com/user-attachments/assets/1a1233b0-c24a-4082-9fe6-2ab1d00ba9fd"
/>

In the thread view, we display callouts with the warnings. I simplified
the long-description one specifically given we can click on each skill
item right there, so no need for a "manage global skills" button or
descriptions in each item:

<img width="600" alt="Screenshot 2026-06-08 at 10  52@2x"
src="https://github.com/user-attachments/assets/4b9aad23-7362-40cd-8ab5-6dfceb46badd"
/>

In the code completion menu, I removed the icon that appeared at the far
right and instead added the ability for it to render a colored warning
icon instead of the icon it'd already display in the left; I think this
simplifies it nicely:

<img width="350" alt="Screenshot 2026-06-08 at 10  53@2x"
src="https://github.com/user-attachments/assets/8d0c731a-31a0-4d30-abab-1eb40f61031e"
/>


Release Notes:

- Agent: Improved how we communicate problems relative to skills, in
both the agent panel and the settings UI.

---------

Co-authored-by: Martin Ye <martin@zed.dev>
2026-06-08 15:46:15 +00:00

138 lines
4.5 KiB
Rust

use collections::HashMap;
use command_palette;
use editor::{CompletionProvider, Editor};
use fuzzy::StringMatchCandidate;
use gpui::{Context, Entity, SharedString, Window};
use language::{self, ToOffset};
use project::{self, CompletionDisplayOptions};
pub struct ActionCompletionProvider {
action_names: Vec<&'static str>,
humanized_names: HashMap<&'static str, SharedString>,
}
impl ActionCompletionProvider {
pub fn new(
action_names: Vec<&'static str>,
humanized_names: HashMap<&'static str, SharedString>,
) -> Self {
Self {
action_names,
humanized_names,
}
}
}
impl CompletionProvider for ActionCompletionProvider {
fn completions(
&self,
buffer: &Entity<language::Buffer>,
buffer_position: language::Anchor,
_trigger: editor::CompletionContext,
_window: &mut Window,
cx: &mut Context<Editor>,
) -> gpui::Task<anyhow::Result<Vec<project::CompletionResponse>>> {
let buffer = buffer.read(cx);
let mut count_back = 0;
for char in buffer.reversed_chars_at(buffer_position) {
if char.is_ascii_alphanumeric() || char == '_' || char == ':' {
count_back += 1;
} else {
break;
}
}
let start_anchor = buffer.anchor_before(
buffer_position
.to_offset(&buffer)
.saturating_sub(count_back),
);
let replace_range = start_anchor..buffer_position;
let snapshot = buffer.text_snapshot();
let query: String = snapshot.text_for_range(replace_range.clone()).collect();
let normalized_query = command_palette::normalize_action_query(&query);
let candidates: Vec<StringMatchCandidate> = self
.action_names
.iter()
.enumerate()
.map(|(ix, &name)| {
let humanized = self
.humanized_names
.get(name)
.cloned()
.unwrap_or_else(|| name.into());
StringMatchCandidate::new(ix, &humanized)
})
.collect();
let executor = cx.background_executor().clone();
let executor_for_fuzzy = executor.clone();
let action_names = self.action_names.clone();
let humanized_names = self.humanized_names.clone();
executor.spawn(async move {
let matches = fuzzy::match_strings(
&candidates,
&normalized_query,
true,
true,
action_names.len(),
&Default::default(),
executor_for_fuzzy,
)
.await;
let completions: Vec<project::Completion> = matches
.iter()
.take(50)
.map(|m| {
let action_name = action_names[m.candidate_id];
let humanized = humanized_names
.get(action_name)
.cloned()
.unwrap_or_else(|| action_name.into());
project::Completion {
replace_range: replace_range.clone(),
label: language::CodeLabel::plain(humanized.to_string(), None),
new_text: action_name.to_string(),
documentation: None,
source: project::CompletionSource::Custom,
icon_path: None,
icon_color: None,
match_start: None,
snippet_deduplication_key: None,
insert_text_mode: None,
confirm: None,
group: None,
}
})
.collect();
Ok(vec![project::CompletionResponse {
completions,
display_options: CompletionDisplayOptions {
dynamic_width: true,
},
is_incomplete: false,
}])
})
}
fn is_completion_trigger(
&self,
_buffer: &Entity<language::Buffer>,
_position: language::Anchor,
text: &str,
_trigger_in_words: bool,
_cx: &mut Context<Editor>,
) -> bool {
text.chars().last().is_some_and(|last_char| {
last_char.is_ascii_alphanumeric() || last_char == '_' || last_char == ':'
})
}
}