Checkpoint, this commit does not compile

Mikayla Maki created

Change summary

Cargo.lock                                         |   1 
crates/terminal/Cargo.toml                         |   1 
crates/terminal/src/connection.rs                  | 140 +++++++++++----
crates/terminal/src/modal.rs                       |   6 
crates/terminal/src/terminal.rs                    |  16 +
crates/terminal/src/tests/terminal_test_context.rs |   2 
6 files changed, 116 insertions(+), 50 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -5368,6 +5368,7 @@ dependencies = [
  "shellexpand",
  "smallvec",
  "theme",
+ "thiserror",
  "util",
  "workspace",
 ]

crates/terminal/Cargo.toml 🔗

@@ -25,6 +25,7 @@ dirs = "4.0.0"
 shellexpand = "2.1.0"
 libc = "0.2"
 anyhow = "1"
+thiserror = "1.0"
 
 
 [dev-dependencies]

crates/terminal/src/connection.rs 🔗

@@ -13,12 +13,14 @@ use alacritty_terminal::{
     tty::{self, setup_env},
     Term,
 };
+use anyhow::{bail, Result};
 use futures::{
-    channel::mpsc::{unbounded, UnboundedSender},
+    channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender},
     StreamExt,
 };
 use settings::{Settings, Shell};
-use std::{collections::HashMap, path::PathBuf, sync::Arc};
+use std::{collections::HashMap, fmt::Display, path::PathBuf, sync::Arc};
+use thiserror::Error;
 
 use gpui::{keymap::Keystroke, ClipboardItem, CursorStyle, Entity, ModelContext};
 
@@ -52,23 +54,84 @@ impl EventListener for ZedListener {
     }
 }
 
-pub enum TerminalConnection {
-    Connected(Terminal),
-    Disconnected {
-        directory: Option<PathBuf>,
-        shell: Option<Shell>,
-        error: Option<std::io::Error>,
-    },
+#[derive(Error, Debug)]
+pub struct TerminalError {
+    directory: Option<PathBuf>,
+    shell: Option<Shell>,
+    source: std::io::Error,
+}
+
+impl Display for TerminalError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let dir_string: String = self
+            .directory
+            .map(|path| {
+                match path
+                    .into_os_string()
+                    .into_string()
+                    .map_err(|os_str| format!("<non-utf8 path> {}", os_str.to_string_lossy()))
+                {
+                    Ok(s) => s,
+                    Err(s) => s,
+                }
+            })
+            .unwrap_or_else(|| {
+                let default_dir =
+                    dirs::home_dir().map(|buf| buf.into_os_string().to_string_lossy());
+                match default_dir {
+                    Some(dir) => format!("<none specified, using home> {}", dir),
+                    None => "<none specified, could not find home>".to_string(),
+                }
+            });
+
+        let shell = self
+            .shell
+            .map(|shell| match shell {
+                Shell::System => {
+                    let mut buf = [0; 1024];
+                    let pw = alacritty_unix::get_pw_entry(&mut buf).ok();
+
+                    match pw {
+                        Some(pw) => format!("<system defined shell> {}", pw.shell),
+                        None => "<could not access system defined shell>".to_string(),
+                    }
+                }
+                Shell::Program(s) => s,
+                Shell::WithArguments { program, args } => format!("{} {}", program, args.join(" ")),
+            })
+            .unwrap_or_else(|| {
+                let mut buf = [0; 1024];
+                let pw = alacritty_unix::get_pw_entry(&mut buf).ok();
+                match pw {
+                    Some(pw) => {
+                        format!("<none specified, using system defined shell> {}", pw.shell)
+                    }
+                    None => {
+                        "<none specified, could not access system defined shell> {}".to_string()
+                    }
+                }
+            });
+
+        write!(
+            f,
+            "Working directory: {} Shell command: `{}`, IOError: {}",
+            dir_string, shell, self.source
+        )
+    }
+}
+
+pub struct DisconnectedPTY {
+    terminal: Terminal,
+    events_rx: UnboundedReceiver<AlacTermEvent>,
 }
 
-impl TerminalConnection {
+impl DisconnectedPTY {
     pub fn new(
         working_directory: Option<PathBuf>,
         shell: Option<Shell>,
         env: Option<HashMap<String, String>>,
         initial_size: TerminalDimensions,
-        cx: &mut ModelContext<Self>,
-    ) -> TerminalConnection {
+    ) -> Result<DisconnectedPTY> {
         let pty_config = {
             let alac_shell = shell.clone().and_then(|shell| match shell {
                 Shell::System => None,
@@ -107,11 +170,11 @@ impl TerminalConnection {
         let pty = match tty::new(&pty_config, initial_size.into(), None) {
             Ok(pty) => pty,
             Err(error) => {
-                return TerminalConnection::Disconnected {
+                bail!(TerminalError {
                     directory: working_directory,
                     shell,
-                    error: Some(error),
-                };
+                    source: error,
+                });
             }
         };
 
@@ -149,20 +212,20 @@ impl TerminalConnection {
             associated_directory: working_directory,
         };
 
+        Ok(DisconnectedPTY {
+            terminal,
+            events_rx,
+        })
+    }
+
+    pub fn connect(self, cx: &mut ModelContext<Terminal>) -> Terminal {
         cx.spawn_weak(|this, mut cx| async move {
             //Listen for terminal events
-            while let Some(event) = events_rx.next().await {
+            while let Some(event) = self.events_rx.next().await {
                 match this.upgrade(&cx) {
                     Some(this) => {
                         this.update(&mut cx, |this, cx| {
-                            match this {
-                                TerminalConnection::Connected(conn) => {
-                                    conn.process_terminal_event(event, cx)
-                                }
-                                //There should never be a state where the terminal is disconnected
-                                //And receiving events from the pty
-                                TerminalConnection::Disconnected { .. } => unreachable!(),
-                            }
+                            this.process_terminal_event(event, cx);
 
                             cx.notify();
                         });
@@ -173,14 +236,7 @@ impl TerminalConnection {
         })
         .detach();
 
-        TerminalConnection::Connected(terminal)
-    }
-
-    pub fn get_terminal(&self) -> Option<&Terminal> {
-        match self {
-            TerminalConnection::Connected(conn) => Some(&conn),
-            TerminalConnection::Disconnected { .. } => None,
-        }
+        self.terminal
     }
 }
 
@@ -196,7 +252,7 @@ impl Terminal {
     fn process_terminal_event(
         &mut self,
         event: alacritty_terminal::event::Event,
-        cx: &mut ModelContext<TerminalConnection>,
+        cx: &mut ModelContext<Terminal>,
     ) {
         match event {
             // TODO: Handle is_self_focused in subscription on terminal view
@@ -361,21 +417,23 @@ impl Terminal {
     }
 }
 
-impl Drop for TerminalConnection {
+impl Drop for DisconnectedPTY {
     fn drop(&mut self) {
-        match self {
-            TerminalConnection::Connected(conn) => {
-                conn.pty_tx.0.send(Msg::Shutdown).ok();
-            }
-            _ => {}
-        };
+        self.terminal.pty_tx.0.send(Msg::Shutdown).ok();
+    }
+}
+
+impl Drop for Terminal {
+    fn drop(&mut self) {
+        self.pty_tx.0.send(Msg::Shutdown).ok();
     }
 }
 
-impl Entity for TerminalConnection {
+impl Entity for Terminal {
     type Event = Event;
 }
 
+//TODO Move this around
 mod alacritty_unix {
     use alacritty_terminal::config::Program;
     use gpui::anyhow::{bail, Result};

crates/terminal/src/modal.rs 🔗

@@ -1,10 +1,10 @@
 use gpui::{ModelHandle, ViewContext};
 use workspace::Workspace;
 
-use crate::{get_wd_for_workspace, DeployModal, Event, TerminalConnection, TerminalView};
+use crate::{connection::Terminal, get_wd_for_workspace, DeployModal, Event, TerminalView};
 
 #[derive(Debug)]
-struct StoredConnection(ModelHandle<TerminalConnection>);
+struct StoredConnection(ModelHandle<Terminal>);
 
 pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewContext<Workspace>) {
     // Pull the terminal connection out of the global if it has been stored
@@ -46,7 +46,7 @@ pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewCon
 
 pub fn on_event(
     workspace: &mut Workspace,
-    _: ModelHandle<TerminalConnection>,
+    _: ModelHandle<Terminal>,
     event: &Event,
     cx: &mut ViewContext<Workspace>,
 ) {

crates/terminal/src/terminal.rs 🔗

@@ -3,7 +3,7 @@ pub mod connection;
 mod modal;
 pub mod terminal_element;
 
-use connection::{Event, TerminalConnection};
+use connection::{DisconnectedPTY, Event, Terminal, TerminalError};
 use dirs::home_dir;
 use gpui::{
     actions, elements::*, geometry::vector::vec2f, keymap::Keystroke, AppContext, ClipboardItem,
@@ -64,7 +64,7 @@ pub fn init(cx: &mut MutableAppContext) {
 
 ///A terminal view, maintains the PTY's file handles and communicates with the terminal
 pub struct TerminalView {
-    connection: ModelHandle<TerminalConnection>,
+    connection: Result<ModelHandle<Terminal>, TerminalError>,
     has_new_content: bool,
     //Currently using iTerm bell, show bell emoji in tab until input is received
     has_bell: bool,
@@ -94,14 +94,20 @@ impl TerminalView {
             (shell, envs)
         };
 
-        let connection = cx
-            .add_model(|cx| TerminalConnection::new(working_directory, shell, envs, size_info, cx));
+        let connection = DisconnectedPTY::new(working_directory, shell, envs, size_info)
+            .map(|pty| cx.add_model(|cx| pty.connect(cx)))
+            .map_err(|err| {
+                match err.downcast::<TerminalError>() {
+                    Ok(err) => err,
+                    Err(_) => unreachable!(), //This should never happen
+                }
+            });
 
         TerminalView::from_connection(connection, modal, cx)
     }
 
     fn from_connection(
-        connection: ModelHandle<TerminalConnection>,
+        connection: Result<ModelHandle<Terminal>, TerminalError>,
         modal: bool,
         cx: &mut ViewContext<Self>,
     ) -> TerminalView {

crates/terminal/src/tests/terminal_test_context.rs 🔗

@@ -24,7 +24,7 @@ impl<'a> TerminalTestContext<'a> {
         );
 
         let connection =
-            cx.add_model(|cx| TerminalConnection::new(None, None, None, size_info, cx));
+            cx.add_model(|cx| TerminalConnection::new_tty(None, None, None, size_info, cx));
 
         TerminalTestContext { cx, connection }
     }