diff --git a/Cargo.lock b/Cargo.lock index d5bf270e5d194e9af3c27453d9961653ce8db3c3..b35a7ca94c4c4af9a08ba9aab7931a5234479693 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,6 +22,7 @@ dependencies = [ "markdown", "parking_lot", "project", + "proto", "serde_json", "settings", "smol", diff --git a/crates/acp/Cargo.toml b/crates/acp/Cargo.toml index 3992a06cdb757378a49eb90d310f6e9b5ca06f59..9482bcbcffd40826bb8b490a568da77ee3d26336 100644 --- a/crates/acp/Cargo.toml +++ b/crates/acp/Cargo.toml @@ -31,6 +31,7 @@ log.workspace = true markdown.workspace = true parking_lot.workspace = true project.workspace = true +proto.workspace = true settings.workspace = true smol.workspace = true theme.workspace = true diff --git a/crates/acp/src/server.rs b/crates/acp/src/server.rs index 5d7115219a0219bdecc35a026162b00d4e0e199b..983b329041b45586a7a83524de054d4217b21dbb 100644 --- a/crates/acp/src/server.rs +++ b/crates/acp/src/server.rs @@ -270,13 +270,18 @@ impl AcpServer { } pub async fn create_thread(self: Arc, cx: &mut AsyncApp) -> Result> { - let response = self.connection.request(acp::CreateThreadParams).await?; + let response = self + .connection + .request(acp::CreateThreadParams) + .await + .map_err(to_anyhow)?; + let thread_id: ThreadId = response.thread_id.into(); let server = self.clone(); let thread = cx.new(|_| AcpThread { // todo! title: "ACP Thread".into(), - id: thread_id.clone(), + id: thread_id.clone(), // Either next_entry_id: ThreadEntryId(0), entries: Vec::default(), project: self.project.clone(), @@ -297,7 +302,8 @@ impl AcpServer { thread_id: thread_id.clone().into(), message, }) - .await?; + .await + .map_err(to_anyhow)?; Ok(()) } @@ -306,6 +312,16 @@ impl AcpServer { } } +#[track_caller] +fn to_anyhow(e: acp::Error) -> anyhow::Error { + log::error!( + "failed to send message: {code}: {message}", + code = e.code, + message = e.message + ); + anyhow::anyhow!(e.message) +} + impl From for ThreadId { fn from(thread_id: acp::ThreadId) -> Self { Self(thread_id.0.into()) diff --git a/crates/acp/src/thread_view.rs b/crates/acp/src/thread_view.rs index 209e15aed290d995df95339d8967b0244aec58cd..ed4bafb46c39b4ccb1f8d61e628993f82e28f948 100644 --- a/crates/acp/src/thread_view.rs +++ b/crates/acp/src/thread_view.rs @@ -12,8 +12,14 @@ use gpui::{ }; use gpui::{FocusHandle, Task}; use language::Buffer; +<<<<<<< HEAD use language::language_settings::SoftWrap; use markdown::{HeadingLevelStyles, MarkdownElement, MarkdownStyle}; +||||||| parent of 47b80cc740 (Show errors from ACP when requests error) +use markdown::{HeadingLevelStyles, MarkdownElement, MarkdownStyle}; +======= +use markdown::{HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle}; +>>>>>>> 47b80cc740 (Show errors from ACP when requests error) use project::Project; use settings::Settings as _; use theme::ThemeSettings; @@ -32,6 +38,7 @@ pub struct AcpThreadView { // todo! reconsider structure. currently pretty sparse, but easy to clean up if we need to delete entries. thread_entry_views: Vec>, message_editor: Entity, + last_error: Option>, list_state: ListState, send_task: Option>>, } @@ -96,6 +103,7 @@ impl AcpThreadView { message_editor, send_task: None, list_state: list_state, + last_error: None, } } @@ -137,8 +145,37 @@ impl AcpThreadView { this.update_in(cx, |this, window, cx| { match result { Ok(thread) => { +<<<<<<< HEAD let subscription = cx.subscribe_in(&thread, window, Self::handle_thread_event); +||||||| parent of 47b80cc740 (Show errors from ACP when requests error) + let subscription = cx.subscribe(&thread, |this, _, event, cx| { + let count = this.list_state.item_count(); + match event { + AcpThreadEvent::NewEntry => { + this.list_state.splice(count..count, 1); + } + AcpThreadEvent::EntryUpdated(index) => { + this.list_state.splice(*index..*index + 1, 1); + } + } + cx.notify(); + }); +======= + dbg!(&thread); + let subscription = cx.subscribe(&thread, |this, _, event, cx| { + let count = this.list_state.item_count(); + match event { + AcpThreadEvent::NewEntry => { + this.list_state.splice(count..count, 1); + } + AcpThreadEvent::EntryUpdated(index) => { + this.list_state.splice(*index..*index + 1, 1); + } + } + cx.notify(); + }); +>>>>>>> 47b80cc740 (Show errors from ACP when requests error) this.list_state .splice(0..0, thread.read(cx).entries().len()); @@ -148,6 +185,7 @@ impl AcpThreadView { }; } Err(e) => { + dbg!(&e); if let Some(exit_status) = agent.exit_status() { this.thread_state = ThreadState::LoadError( format!( @@ -189,6 +227,7 @@ impl AcpThreadView { } fn chat(&mut self, _: &Chat, window: &mut Window, cx: &mut Context) { + self.last_error.take(); let text = self.message_editor.read(cx).text(cx); if text.is_empty() { return; @@ -198,9 +237,15 @@ impl AcpThreadView { let task = thread.update(cx, |thread, cx| thread.send(&text, cx)); self.send_task = Some(cx.spawn(async move |this, cx| { - task.await?; + let result = task.await; - this.update(cx, |this, _cx| { + this.update(cx, |this, cx| { + if let Err(err) = result { + this.last_error = + Some(cx.new(|cx| { + Markdown::new(format!("Error: {err}").into(), None, None, cx) + })) + } this.send_task.take(); }) })); @@ -865,7 +910,7 @@ impl Focusable for AcpThreadView { } impl Render for AcpThreadView { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let text = self.message_editor.read(cx).text(cx); let is_editor_empty = text.is_empty(); let focus_handle = self.message_editor.focus_handle(cx); @@ -907,6 +952,21 @@ impl Render for AcpThreadView { .into() })), }) + .when_some(self.last_error.clone(), |el, error| { + el.child( + div() + .text_xs() + .p_2() + .gap_2() + .border_t_1() + .border_color(cx.theme().status().error_border) + .bg(cx.theme().status().error_background) + .child(MarkdownElement::new( + error, + default_markdown_style(window, cx), + )), + ) + }) .child( v_flex() .bg(cx.theme().colors().editor_background)