acp: Fix @mentions when remoting from Windows to Linux (#38882)

Closes #38620

`Url::from_file_path` and `Url::from_directory_path` assume the path
style of the target they were compiled for, so we can't use them in
general. So, switch from `file://` to encoding the absolute path (for
mentions that have one) as a query parameter, which works no matter the
platforms. We'll still parse the old `file://` mention URIs for
compatibility with thread history.

Release Notes:

- windows: Fixed a crash when using `@mentions` in agent threads when
remoting from Windows to Linux or WSL.
This commit is contained in:
Cole Miller
2025-09-25 16:23:45 -04:00
committed by GitHub
parent 236006b6b3
commit ecc35fcd9a
3 changed files with 123 additions and 65 deletions
+38 -27
View File
@@ -48,7 +48,7 @@ use std::{
use text::OffsetRangeExt;
use theme::ThemeSettings;
use ui::{ButtonLike, TintColor, Toggleable, prelude::*};
use util::{ResultExt, debug_panic, paths::PathStyle, rel_path::RelPath};
use util::{ResultExt, debug_panic, rel_path::RelPath};
use workspace::{Workspace, notifications::NotifyResultExt as _};
use zed_actions::agent::Chat;
@@ -108,11 +108,6 @@ impl MessageEditor {
available_commands.clone(),
));
let mention_set = MentionSet::default();
// TODO: fix mentions when remoting with mixed path styles.
let host_and_guest_paths_differ = project
.read(cx)
.remote_client()
.is_some_and(|client| client.read(cx).path_style() != PathStyle::local());
let editor = cx.new(|cx| {
let buffer = cx.new(|cx| Buffer::local("", cx).with_language(Arc::new(language), cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
@@ -122,9 +117,7 @@ impl MessageEditor {
editor.set_show_indent_guides(false, cx);
editor.set_soft_wrap();
editor.set_use_modal_editing(true);
if !host_and_guest_paths_differ {
editor.set_completion_provider(Some(completion_provider.clone()));
}
editor.set_completion_provider(Some(completion_provider.clone()));
editor.set_context_menu_options(ContextMenuOptions {
min_entries_visible: 12,
max_entries_visible: 12,
@@ -1600,7 +1593,7 @@ mod tests {
use serde_json::json;
use text::Point;
use ui::{App, Context, IntoElement, Render, SharedString, Window};
use util::{path, paths::PathStyle, rel_path::rel_path, uri};
use util::{path, paths::PathStyle, rel_path::rel_path};
use workspace::{AppState, Item, Workspace};
use crate::acp::{
@@ -2266,7 +2259,11 @@ mod tests {
editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx);
});
let url_one = uri!("file:///dir/a/one.txt");
let url_one = MentionUri::File {
abs_path: path!("/dir/a/one.txt").into(),
}
.to_uri()
.to_string();
editor.update(&mut cx, |editor, cx| {
let text = editor.text(cx);
assert_eq!(text, format!("Lorem [@one.txt]({url_one}) "));
@@ -2371,7 +2368,11 @@ mod tests {
.into_values()
.collect::<Vec<_>>();
let url_eight = uri!("file:///dir/b/eight.txt");
let url_eight = MentionUri::File {
abs_path: path!("/dir/b/eight.txt").into(),
}
.to_uri()
.to_string();
{
let [_, (uri, Mention::Text { content, .. })] = contents.as_slice() else {
@@ -2470,6 +2471,12 @@ mod tests {
editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx);
});
let symbol = MentionUri::Symbol {
abs_path: path!("/dir/a/one.txt").into(),
name: "MySymbol".into(),
line_range: 0..=0,
};
let contents = message_editor
.update(&mut cx, |message_editor, cx| {
message_editor.mention_set().contents(
@@ -2489,12 +2496,7 @@ mod tests {
panic!("Unexpected mentions");
};
pretty_assertions::assert_eq!(content, "1");
pretty_assertions::assert_eq!(
uri,
&format!("{url_one}?symbol=MySymbol#L1:1")
.parse::<MentionUri>()
.unwrap()
);
pretty_assertions::assert_eq!(uri, &symbol);
}
cx.run_until_parked();
@@ -2502,7 +2504,10 @@ mod tests {
editor.read_with(&cx, |editor, cx| {
assert_eq!(
editor.text(cx),
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({url_one}?symbol=MySymbol#L1:1) ")
format!(
"Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({}) ",
symbol.to_uri(),
)
);
});
@@ -2512,7 +2517,7 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({url_one}?symbol=MySymbol#L1:1) @file x.png")
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({}) @file x.png", symbol.to_uri())
);
assert!(editor.has_visible_completions_menu());
assert_eq!(current_completion_labels(editor), &[format!("x.png dir{slash}")]);
@@ -2541,7 +2546,10 @@ mod tests {
editor.read_with(&cx, |editor, cx| {
assert_eq!(
editor.text(cx),
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({url_one}?symbol=MySymbol#L1:1) ")
format!(
"Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({}) ",
symbol.to_uri()
)
);
});
@@ -2551,7 +2559,7 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({url_one}?symbol=MySymbol#L1:1) @file x.png")
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({}) @file x.png", symbol.to_uri())
);
assert!(editor.has_visible_completions_menu());
assert_eq!(current_completion_labels(editor), &[format!("x.png dir{slash}")]);
@@ -2566,11 +2574,14 @@ mod tests {
// Mention was removed
editor.read_with(&cx, |editor, cx| {
assert_eq!(
editor.text(cx),
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({url_one}?symbol=MySymbol#L1:1) ")
);
});
assert_eq!(
editor.text(cx),
format!(
"Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({}) ",
symbol.to_uri()
)
);
});
// Now getting the contents succeeds, because the invalid mention was removed
let contents = message_editor