Support @-mentions in inline assists and when editing old agent panel messages (#29734)

Closes #ISSUE

Co-authored-by: Bennet <bennet@zed.dev>

Release Notes:

- Added support for context `@mentions` in the inline prompt editor and
when editing past messages in the agent panel.

---------

Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
This commit is contained in:
Cole Miller 2025-05-02 16:08:53 -04:00 committed by GitHub
parent c918f6cde1
commit 9547d42b15
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 499 additions and 156 deletions

View file

@ -9,6 +9,7 @@ use assistant_settings::AssistantSettings;
use assistant_tool::{ActionLog, AnyToolCard, Tool, ToolWorkingSet};
use chrono::{DateTime, Utc};
use collections::HashMap;
use editor::display_map::CreaseMetadata;
use feature_flags::{self, FeatureFlagAppExt};
use futures::future::Shared;
use futures::{FutureExt, StreamExt as _};
@ -39,10 +40,10 @@ use uuid::Uuid;
use zed_llm_client::CompletionMode;
use crate::ThreadStore;
use crate::context::{AgentContext, ContextLoadResult, LoadedContext};
use crate::context::{AgentContext, AgentContextHandle, ContextLoadResult, LoadedContext};
use crate::thread_store::{
SerializedLanguageModel, SerializedMessage, SerializedMessageSegment, SerializedThread,
SerializedToolResult, SerializedToolUse, SharedProjectContext,
SerializedCrease, SerializedLanguageModel, SerializedMessage, SerializedMessageSegment,
SerializedThread, SerializedToolResult, SerializedToolUse, SharedProjectContext,
};
use crate::tool_use::{PendingToolUse, ToolUse, ToolUseMetadata, ToolUseState};
@ -96,6 +97,15 @@ impl MessageId {
}
}
/// Stored information that can be used to resurrect a context crease when creating an editor for a past message.
#[derive(Clone, Debug)]
pub struct MessageCrease {
pub range: Range<usize>,
pub metadata: CreaseMetadata,
/// None for a deserialized message, Some otherwise.
pub context: Option<AgentContextHandle>,
}
/// A message in a [`Thread`].
#[derive(Debug, Clone)]
pub struct Message {
@ -103,6 +113,7 @@ pub struct Message {
pub role: Role,
pub segments: Vec<MessageSegment>,
pub loaded_context: LoadedContext,
pub creases: Vec<MessageCrease>,
}
impl Message {
@ -473,6 +484,18 @@ impl Thread {
text: message.context,
images: Vec::new(),
},
creases: message
.creases
.into_iter()
.map(|crease| MessageCrease {
range: crease.start..crease.end,
metadata: CreaseMetadata {
icon_path: crease.icon_path,
label: crease.label,
},
context: None,
})
.collect(),
})
.collect(),
next_message_id,
@ -826,6 +849,7 @@ impl Thread {
text: impl Into<String>,
loaded_context: ContextLoadResult,
git_checkpoint: Option<GitStoreCheckpoint>,
creases: Vec<MessageCrease>,
cx: &mut Context<Self>,
) -> MessageId {
if !loaded_context.referenced_buffers.is_empty() {
@ -840,6 +864,7 @@ impl Thread {
Role::User,
vec![MessageSegment::Text(text.into())],
loaded_context.loaded_context,
creases,
cx,
);
@ -860,7 +885,13 @@ impl Thread {
segments: Vec<MessageSegment>,
cx: &mut Context<Self>,
) -> MessageId {
self.insert_message(Role::Assistant, segments, LoadedContext::default(), cx)
self.insert_message(
Role::Assistant,
segments,
LoadedContext::default(),
Vec::new(),
cx,
)
}
pub fn insert_message(
@ -868,6 +899,7 @@ impl Thread {
role: Role,
segments: Vec<MessageSegment>,
loaded_context: LoadedContext,
creases: Vec<MessageCrease>,
cx: &mut Context<Self>,
) -> MessageId {
let id = self.next_message_id.post_inc();
@ -876,6 +908,7 @@ impl Thread {
role,
segments,
loaded_context,
creases,
});
self.touch_updated_at();
cx.emit(ThreadEvent::MessageAdded(id));
@ -995,6 +1028,16 @@ impl Thread {
})
.collect(),
context: message.loaded_context.text.clone(),
creases: message
.creases
.iter()
.map(|crease| SerializedCrease {
start: crease.range.start,
end: crease.range.end,
icon_path: crease.metadata.icon_path.clone(),
label: crease.metadata.label.clone(),
})
.collect(),
})
.collect(),
initial_project_snapshot,
@ -2502,7 +2545,13 @@ mod tests {
// Insert user message with context
let message_id = thread.update(cx, |thread, cx| {
thread.insert_user_message("Please explain this code", loaded_context, None, cx)
thread.insert_user_message(
"Please explain this code",
loaded_context,
None,
Vec::new(),
cx,
)
});
// Check content and context in message object
@ -2578,7 +2627,7 @@ fn main() {{
.update(|cx| load_context(new_contexts, &project, &None, cx))
.await;
let message1_id = thread.update(cx, |thread, cx| {
thread.insert_user_message("Message 1", loaded_context, None, cx)
thread.insert_user_message("Message 1", loaded_context, None, Vec::new(), cx)
});
// Second message with contexts 1 and 2 (context 1 should be skipped as it's already included)
@ -2593,7 +2642,7 @@ fn main() {{
.update(|cx| load_context(new_contexts, &project, &None, cx))
.await;
let message2_id = thread.update(cx, |thread, cx| {
thread.insert_user_message("Message 2", loaded_context, None, cx)
thread.insert_user_message("Message 2", loaded_context, None, Vec::new(), cx)
});
// Third message with all three contexts (contexts 1 and 2 should be skipped)
@ -2609,7 +2658,7 @@ fn main() {{
.update(|cx| load_context(new_contexts, &project, &None, cx))
.await;
let message3_id = thread.update(cx, |thread, cx| {
thread.insert_user_message("Message 3", loaded_context, None, cx)
thread.insert_user_message("Message 3", loaded_context, None, Vec::new(), cx)
});
// Check what contexts are included in each message
@ -2723,6 +2772,7 @@ fn main() {{
"What is the best way to learn Rust?",
ContextLoadResult::default(),
None,
Vec::new(),
cx,
)
});
@ -2756,6 +2806,7 @@ fn main() {{
"Are there any good books?",
ContextLoadResult::default(),
None,
Vec::new(),
cx,
)
});
@ -2805,7 +2856,7 @@ fn main() {{
// Insert user message with the buffer as context
thread.update(cx, |thread, cx| {
thread.insert_user_message("Explain this code", loaded_context, None, cx)
thread.insert_user_message("Explain this code", loaded_context, None, Vec::new(), cx)
});
// Create a request and check that it doesn't have a stale buffer warning yet
@ -2839,6 +2890,7 @@ fn main() {{
"What does the code do now?",
ContextLoadResult::default(),
None,
Vec::new(),
cx,
)
});