tenseleyflow/fackr / 8ca272f

Browse files

fix: auto-close terminal when shell exits

Detect when the shell process exits (via EOF or error on PTY read)
and automatically hide the terminal panel. Typing 'exit' now cleanly
closes the integrated terminal instead of leaving it in a broken state.
Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
8ca272f95a793a02b9849f9c388876b8a04c9476
Parents
99a0358
Tree
7b075b6

2 changed files

StatusFile+-
M src/terminal/panel.rs 9 1
M src/terminal/pty.rs 24 2
src/terminal/panel.rsmodified
@@ -137,11 +137,19 @@ impl TerminalPanel {
137137
         Ok(())
138138
     }
139139
 
140
-    /// Poll for and process PTY output. Returns true if data was received.
140
+    /// Poll for and process PTY output. Returns true if data was received or terminal closed.
141141
     pub fn poll(&mut self) -> bool {
142142
         let mut had_data = false;
143143
 
144144
         if let Some(ref mut pty) = self.pty {
145
+            // Check if shell has exited
146
+            if !pty.is_alive() {
147
+                // Shell exited - close terminal and clean up
148
+                self.visible = false;
149
+                self.pty = None;
150
+                return true; // Trigger render to hide terminal
151
+            }
152
+
145153
             if let Some(data) = pty.read() {
146154
                 self.screen.process(&data);
147155
                 had_data = true;
src/terminal/pty.rsmodified
@@ -5,7 +5,9 @@
55
 use anyhow::Result;
66
 use portable_pty::{native_pty_system, CommandBuilder, PtyPair, PtySize};
77
 use std::io::{Read, Write};
8
+use std::sync::atomic::{AtomicBool, Ordering};
89
 use std::sync::mpsc::{self, Receiver, Sender};
10
+use std::sync::Arc;
911
 use std::thread;
1012
 
1113
 /// Manages a PTY connection to a shell process
@@ -14,6 +16,8 @@ pub struct Pty {
1416
     writer: Box<dyn Write + Send>,
1517
     output_rx: Receiver<Vec<u8>>,
1618
     _output_thread: thread::JoinHandle<()>,
19
+    /// Flag indicating the shell has exited
20
+    shell_exited: Arc<AtomicBool>,
1721
 }
1822
 
1923
 impl Pty {
@@ -50,17 +54,29 @@ impl Pty {
5054
         let mut reader = pair.master.try_clone_reader()?;
5155
         let (output_tx, output_rx): (Sender<Vec<u8>>, Receiver<Vec<u8>>) = mpsc::channel();
5256
 
57
+        // Flag to signal when shell exits
58
+        let shell_exited = Arc::new(AtomicBool::new(false));
59
+        let shell_exited_clone = Arc::clone(&shell_exited);
60
+
5361
         let output_thread = thread::spawn(move || {
5462
             let mut buf = [0u8; 4096];
5563
             loop {
5664
                 match reader.read(&mut buf) {
57
-                    Ok(0) => break, // EOF
65
+                    Ok(0) => {
66
+                        // EOF - shell exited
67
+                        shell_exited_clone.store(true, Ordering::SeqCst);
68
+                        break;
69
+                    }
5870
                     Ok(n) => {
5971
                         if output_tx.send(buf[..n].to_vec()).is_err() {
6072
                             break; // Receiver dropped
6173
                         }
6274
                     }
63
-                    Err(_) => break,
75
+                    Err(_) => {
76
+                        // Error - likely shell exited
77
+                        shell_exited_clone.store(true, Ordering::SeqCst);
78
+                        break;
79
+                    }
6480
                 }
6581
             }
6682
         });
@@ -70,6 +86,7 @@ impl Pty {
7086
             writer,
7187
             output_rx,
7288
             _output_thread: output_thread,
89
+            shell_exited,
7390
         })
7491
     }
7592
 
@@ -104,4 +121,9 @@ impl Pty {
104121
         })?;
105122
         Ok(())
106123
     }
124
+
125
+    /// Check if the shell is still alive
126
+    pub fn is_alive(&self) -> bool {
127
+        !self.shell_exited.load(Ordering::SeqCst)
128
+    }
107129
 }