thread_view: Fix issues with images (#36509)

- Clean up failed load tasks for mentions that require async processing
- When dragging and dropping files, hold onto added worktrees until any
async processing has completed; this fixes a bug when dragging items
from outside the project

Release Notes:

- N/A
This commit is contained in:
Cole Miller 2025-08-19 15:14:43 -04:00 committed by GitHub
parent a91acb5f41
commit df9c2aefb1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 85 additions and 61 deletions

View file

@ -763,14 +763,16 @@ fn confirm_completion_callback(
message_editor message_editor
.clone() .clone()
.update(cx, |message_editor, cx| { .update(cx, |message_editor, cx| {
message_editor.confirm_completion( message_editor
crease_text, .confirm_completion(
start, crease_text,
content_len, start,
mention_uri, content_len,
window, mention_uri,
cx, window,
) cx,
)
.detach();
}) })
.ok(); .ok();
}); });

View file

@ -26,7 +26,7 @@ use gpui::{
}; };
use language::{Buffer, Language}; use language::{Buffer, Language};
use language_model::LanguageModelImage; use language_model::LanguageModelImage;
use project::{CompletionIntent, Project, ProjectPath, Worktree}; use project::{Project, ProjectPath, Worktree};
use rope::Point; use rope::Point;
use settings::Settings; use settings::Settings;
use std::{ use std::{
@ -202,18 +202,18 @@ impl MessageEditor {
mention_uri: MentionUri, mention_uri: MentionUri,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) -> Task<()> {
let snapshot = self let snapshot = self
.editor .editor
.update(cx, |editor, cx| editor.snapshot(window, cx)); .update(cx, |editor, cx| editor.snapshot(window, cx));
let Some((excerpt_id, _, _)) = snapshot.buffer_snapshot.as_singleton() else { let Some((excerpt_id, _, _)) = snapshot.buffer_snapshot.as_singleton() else {
return; return Task::ready(());
}; };
let Some(anchor) = snapshot let Some(anchor) = snapshot
.buffer_snapshot .buffer_snapshot
.anchor_in_excerpt(*excerpt_id, start) .anchor_in_excerpt(*excerpt_id, start)
else { else {
return; return Task::ready(());
}; };
if let MentionUri::File { abs_path, .. } = &mention_uri { if let MentionUri::File { abs_path, .. } = &mention_uri {
@ -228,7 +228,7 @@ impl MessageEditor {
.read(cx) .read(cx)
.project_path_for_absolute_path(abs_path, cx) .project_path_for_absolute_path(abs_path, cx)
else { else {
return; return Task::ready(());
}; };
let image = cx let image = cx
.spawn(async move |_, cx| { .spawn(async move |_, cx| {
@ -252,9 +252,9 @@ impl MessageEditor {
window, window,
cx, cx,
) else { ) else {
return; return Task::ready(());
}; };
self.confirm_mention_for_image( return self.confirm_mention_for_image(
crease_id, crease_id,
anchor, anchor,
Some(abs_path.clone()), Some(abs_path.clone()),
@ -262,7 +262,6 @@ impl MessageEditor {
window, window,
cx, cx,
); );
return;
} }
} }
@ -276,27 +275,28 @@ impl MessageEditor {
window, window,
cx, cx,
) else { ) else {
return; return Task::ready(());
}; };
match mention_uri { match mention_uri {
MentionUri::Fetch { url } => { MentionUri::Fetch { url } => {
self.confirm_mention_for_fetch(crease_id, anchor, url, window, cx); self.confirm_mention_for_fetch(crease_id, anchor, url, window, cx)
} }
MentionUri::Directory { abs_path } => { MentionUri::Directory { abs_path } => {
self.confirm_mention_for_directory(crease_id, anchor, abs_path, window, cx); self.confirm_mention_for_directory(crease_id, anchor, abs_path, window, cx)
} }
MentionUri::Thread { id, name } => { MentionUri::Thread { id, name } => {
self.confirm_mention_for_thread(crease_id, anchor, id, name, window, cx); self.confirm_mention_for_thread(crease_id, anchor, id, name, window, cx)
} }
MentionUri::TextThread { path, name } => { MentionUri::TextThread { path, name } => {
self.confirm_mention_for_text_thread(crease_id, anchor, path, name, window, cx); self.confirm_mention_for_text_thread(crease_id, anchor, path, name, window, cx)
} }
MentionUri::File { .. } MentionUri::File { .. }
| MentionUri::Symbol { .. } | MentionUri::Symbol { .. }
| MentionUri::Rule { .. } | MentionUri::Rule { .. }
| MentionUri::Selection { .. } => { | MentionUri::Selection { .. } => {
self.mention_set.insert_uri(crease_id, mention_uri.clone()); self.mention_set.insert_uri(crease_id, mention_uri.clone());
Task::ready(())
} }
} }
} }
@ -308,7 +308,7 @@ impl MessageEditor {
abs_path: PathBuf, abs_path: PathBuf,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) -> Task<()> {
fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<(Arc<Path>, PathBuf)> { fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<(Arc<Path>, PathBuf)> {
let mut files = Vec::new(); let mut files = Vec::new();
@ -331,13 +331,13 @@ impl MessageEditor {
.read(cx) .read(cx)
.project_path_for_absolute_path(&abs_path, cx) .project_path_for_absolute_path(&abs_path, cx)
else { else {
return; return Task::ready(());
}; };
let Some(entry) = self.project.read(cx).entry_for_path(&project_path, cx) else { let Some(entry) = self.project.read(cx).entry_for_path(&project_path, cx) else {
return; return Task::ready(());
}; };
let Some(worktree) = self.project.read(cx).worktree_for_entry(entry.id, cx) else { let Some(worktree) = self.project.read(cx).worktree_for_entry(entry.id, cx) else {
return; return Task::ready(());
}; };
let project = self.project.clone(); let project = self.project.clone();
let task = cx.spawn(async move |_, cx| { let task = cx.spawn(async move |_, cx| {
@ -396,7 +396,9 @@ impl MessageEditor {
}) })
.shared(); .shared();
self.mention_set.directories.insert(abs_path, task.clone()); self.mention_set
.directories
.insert(abs_path.clone(), task.clone());
let editor = self.editor.clone(); let editor = self.editor.clone();
cx.spawn_in(window, async move |this, cx| { cx.spawn_in(window, async move |this, cx| {
@ -414,9 +416,12 @@ impl MessageEditor {
editor.remove_creases([crease_id], cx); editor.remove_creases([crease_id], cx);
}) })
.ok(); .ok();
this.update(cx, |this, _cx| {
this.mention_set.directories.remove(&abs_path);
})
.ok();
} }
}) })
.detach();
} }
fn confirm_mention_for_fetch( fn confirm_mention_for_fetch(
@ -426,13 +431,13 @@ impl MessageEditor {
url: url::Url, url: url::Url,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) -> Task<()> {
let Some(http_client) = self let Some(http_client) = self
.workspace .workspace
.update(cx, |workspace, _cx| workspace.client().http_client()) .update(cx, |workspace, _cx| workspace.client().http_client())
.ok() .ok()
else { else {
return; return Task::ready(());
}; };
let url_string = url.to_string(); let url_string = url.to_string();
@ -450,9 +455,9 @@ impl MessageEditor {
cx.spawn_in(window, async move |this, cx| { cx.spawn_in(window, async move |this, cx| {
let fetch = fetch.await.notify_async_err(cx); let fetch = fetch.await.notify_async_err(cx);
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
let mention_uri = MentionUri::Fetch { url };
if fetch.is_some() { if fetch.is_some() {
this.mention_set.insert_uri(crease_id, mention_uri.clone()); this.mention_set
.insert_uri(crease_id, MentionUri::Fetch { url });
} else { } else {
// Remove crease if we failed to fetch // Remove crease if we failed to fetch
this.editor.update(cx, |editor, cx| { this.editor.update(cx, |editor, cx| {
@ -461,11 +466,11 @@ impl MessageEditor {
}); });
editor.remove_creases([crease_id], cx); editor.remove_creases([crease_id], cx);
}); });
this.mention_set.fetch_results.remove(&url);
} }
}) })
.ok(); .ok();
}) })
.detach();
} }
pub fn confirm_mention_for_selection( pub fn confirm_mention_for_selection(
@ -528,7 +533,7 @@ impl MessageEditor {
name: String, name: String,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) -> Task<()> {
let uri = MentionUri::Thread { let uri = MentionUri::Thread {
id: id.clone(), id: id.clone(),
name, name,
@ -546,7 +551,7 @@ impl MessageEditor {
}) })
.shared(); .shared();
self.mention_set.insert_thread(id, task.clone()); self.mention_set.insert_thread(id.clone(), task.clone());
let editor = self.editor.clone(); let editor = self.editor.clone();
cx.spawn_in(window, async move |this, cx| { cx.spawn_in(window, async move |this, cx| {
@ -564,9 +569,12 @@ impl MessageEditor {
editor.remove_creases([crease_id], cx); editor.remove_creases([crease_id], cx);
}) })
.ok(); .ok();
this.update(cx, |this, _| {
this.mention_set.thread_summaries.remove(&id);
})
.ok();
} }
}) })
.detach();
} }
fn confirm_mention_for_text_thread( fn confirm_mention_for_text_thread(
@ -577,7 +585,7 @@ impl MessageEditor {
name: String, name: String,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) -> Task<()> {
let uri = MentionUri::TextThread { let uri = MentionUri::TextThread {
path: path.clone(), path: path.clone(),
name, name,
@ -595,7 +603,8 @@ impl MessageEditor {
}) })
.shared(); .shared();
self.mention_set.insert_text_thread(path, task.clone()); self.mention_set
.insert_text_thread(path.clone(), task.clone());
let editor = self.editor.clone(); let editor = self.editor.clone();
cx.spawn_in(window, async move |this, cx| { cx.spawn_in(window, async move |this, cx| {
@ -613,9 +622,12 @@ impl MessageEditor {
editor.remove_creases([crease_id], cx); editor.remove_creases([crease_id], cx);
}) })
.ok(); .ok();
this.update(cx, |this, _| {
this.mention_set.text_thread_summaries.remove(&path);
})
.ok();
} }
}) })
.detach();
} }
pub fn contents( pub fn contents(
@ -784,13 +796,15 @@ impl MessageEditor {
) else { ) else {
return; return;
}; };
self.confirm_mention_for_image(crease_id, anchor, None, task, window, cx); self.confirm_mention_for_image(crease_id, anchor, None, task, window, cx)
.detach();
} }
} }
pub fn insert_dragged_files( pub fn insert_dragged_files(
&self, &mut self,
paths: Vec<project::ProjectPath>, paths: Vec<project::ProjectPath>,
added_worktrees: Vec<Entity<Worktree>>,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
@ -798,6 +812,7 @@ impl MessageEditor {
let Some(buffer) = buffer.read(cx).as_singleton() else { let Some(buffer) = buffer.read(cx).as_singleton() else {
return; return;
}; };
let mut tasks = Vec::new();
for path in paths { for path in paths {
let Some(entry) = self.project.read(cx).entry_for_path(&path, cx) else { let Some(entry) = self.project.read(cx).entry_for_path(&path, cx) else {
continue; continue;
@ -805,39 +820,44 @@ impl MessageEditor {
let Some(abs_path) = self.project.read(cx).absolute_path(&path, cx) else { let Some(abs_path) = self.project.read(cx).absolute_path(&path, cx) else {
continue; continue;
}; };
let anchor = buffer.update(cx, |buffer, _cx| buffer.anchor_before(buffer.len()));
let path_prefix = abs_path let path_prefix = abs_path
.file_name() .file_name()
.unwrap_or(path.path.as_os_str()) .unwrap_or(path.path.as_os_str())
.display() .display()
.to_string(); .to_string();
let Some(completion) = ContextPickerCompletionProvider::completion_for_path( let (file_name, _) =
path, crate::context_picker::file_context_picker::extract_file_name_and_directory(
&path_prefix, &path.path,
false, &path_prefix,
entry.is_dir(), );
anchor..anchor,
cx.weak_entity(), let uri = if entry.is_dir() {
self.project.clone(), MentionUri::Directory { abs_path }
cx, } else {
) else { MentionUri::File { abs_path }
continue;
}; };
let new_text = format!("{} ", uri.as_link());
let content_len = new_text.len() - 1;
let anchor = buffer.update(cx, |buffer, _cx| buffer.anchor_before(buffer.len()));
self.editor.update(cx, |message_editor, cx| { self.editor.update(cx, |message_editor, cx| {
message_editor.edit( message_editor.edit(
[( [(
multi_buffer::Anchor::max()..multi_buffer::Anchor::max(), multi_buffer::Anchor::max()..multi_buffer::Anchor::max(),
completion.new_text, new_text,
)], )],
cx, cx,
); );
}); });
if let Some(confirm) = completion.confirm.clone() { tasks.push(self.confirm_completion(file_name, anchor, content_len, uri, window, cx));
confirm(CompletionIntent::Complete, window, cx);
}
} }
cx.spawn(async move |_, _| {
join_all(tasks).await;
drop(added_worktrees);
})
.detach();
} }
pub fn set_read_only(&mut self, read_only: bool, cx: &mut Context<Self>) { pub fn set_read_only(&mut self, read_only: bool, cx: &mut Context<Self>) {
@ -855,7 +875,7 @@ impl MessageEditor {
image: Shared<Task<Result<Arc<Image>, String>>>, image: Shared<Task<Result<Arc<Image>, String>>>,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) -> Task<()> {
let editor = self.editor.clone(); let editor = self.editor.clone();
let task = cx let task = cx
.spawn_in(window, { .spawn_in(window, {
@ -900,9 +920,12 @@ impl MessageEditor {
editor.remove_creases([crease_id], cx); editor.remove_creases([crease_id], cx);
}) })
.ok(); .ok();
this.update(cx, |this, _cx| {
this.mention_set.images.remove(&crease_id);
})
.ok();
} }
}) })
.detach();
} }
pub fn set_mode(&mut self, mode: EditorMode, cx: &mut Context<Self>) { pub fn set_mode(&mut self, mode: EditorMode, cx: &mut Context<Self>) {

View file

@ -3429,8 +3429,7 @@ impl AcpThreadView {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.message_editor.update(cx, |message_editor, cx| { self.message_editor.update(cx, |message_editor, cx| {
message_editor.insert_dragged_files(paths, window, cx); message_editor.insert_dragged_files(paths, added_worktrees, window, cx);
drop(added_worktrees);
}) })
} }