Show a bit of a better error if gemini cli exits

Conrad Irwin created

I considered dumping stderr to the screen, but for now it's useful to
see stderr when developing...

Change summary

crates/acp/src/server.rs      | 21 +++++++++++++++------
crates/acp/src/thread_view.rs | 18 +++++++++++++++---
2 files changed, 30 insertions(+), 9 deletions(-)

Detailed changes

crates/acp/src/server.rs 🔗

@@ -7,13 +7,14 @@ use gpui::{App, AppContext, AsyncApp, Context, Entity, Task, WeakEntity};
 use parking_lot::Mutex;
 use project::Project;
 use smol::process::Child;
-use std::{io::Write as _, path::Path, sync::Arc};
+use std::{io::Write as _, path::Path, process::ExitStatus, sync::Arc};
 use util::ResultExt;
 
 pub struct AcpServer {
     connection: Arc<acp::AgentConnection>,
     threads: Arc<Mutex<HashMap<ThreadId, WeakEntity<AcpThread>>>>,
     project: Entity<Project>,
+    exit_status: Arc<Mutex<Option<ExitStatus>>>,
     _handler_task: Task<()>,
     _io_task: Task<()>,
 }
@@ -248,22 +249,26 @@ impl AcpServer {
             stdout,
         );
 
-        let io_task = cx.background_spawn(async move {
-            io_fut.await.log_err();
-            process.status().await.log_err();
+        let exit_status: Arc<Mutex<Option<ExitStatus>>> = Default::default();
+        let io_task = cx.background_spawn({
+            let exit_status = exit_status.clone();
+            async move {
+                io_fut.await.log_err();
+                let result = process.status().await.log_err();
+                *exit_status.lock() = result;
+            }
         });
 
         Arc::new(Self {
             project,
             connection: Arc::new(connection),
             threads,
+            exit_status,
             _handler_task: cx.foreground_executor().spawn(handler_fut),
             _io_task: io_task,
         })
     }
-}
 
-impl AcpServer {
     pub async fn create_thread(self: Arc<Self>, cx: &mut AsyncApp) -> Result<Entity<AcpThread>> {
         let response = self.connection.request(acp::CreateThreadParams).await?;
         let thread_id: ThreadId = response.thread_id.into();
@@ -295,6 +300,10 @@ impl AcpServer {
             .await?;
         Ok(())
     }
+
+    pub fn exit_status(&self) -> Option<ExitStatus> {
+        self.exit_status.lock().clone()
+    }
 }
 
 impl From<acp::ThreadId> for ThreadId {

crates/acp/src/thread_view.rs 🔗

@@ -92,7 +92,7 @@ impl AcpThreadView {
         let project = project.clone();
         let load_task = cx.spawn_in(window, async move |this, cx| {
             let agent = AcpServer::stdio(child, project, cx);
-            let result = agent.create_thread(cx).await;
+            let result = agent.clone().create_thread(cx).await;
 
             this.update(cx, |this, cx| {
                 match result {
@@ -117,7 +117,19 @@ impl AcpThreadView {
                             _subscription: subscription,
                         };
                     }
-                    Err(e) => this.thread_state = ThreadState::LoadError(e.to_string().into()),
+                    Err(e) => {
+                        if let Some(exit_status) = agent.exit_status() {
+                            this.thread_state = ThreadState::LoadError(
+                                format!(
+                                    "Gemini exited with status {}",
+                                    exit_status.code().unwrap_or(-127)
+                                )
+                                .into(),
+                            )
+                        } else {
+                            this.thread_state = ThreadState::LoadError(e.to_string().into())
+                        }
+                    }
                 };
                 cx.notify();
             })
@@ -743,7 +755,7 @@ impl Render for AcpThreadView {
                     .p_2()
                     .flex_1()
                     .justify_end()
-                    .child(Label::new(format!("Failed to load {e}")).into_any_element()),
+                    .child(Label::new(format!("Failed to load: {e}")).into_any_element()),
                 ThreadState::Ready { thread, .. } => v_flex()
                     .flex_1()
                     .gap_2()