Cargo.lock 🔗
@@ -5368,6 +5368,7 @@ dependencies = [
"shellexpand",
"smallvec",
"theme",
+ "thiserror",
"util",
"workspace",
]
Mikayla Maki created
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(-)
@@ -5368,6 +5368,7 @@ dependencies = [
"shellexpand",
"smallvec",
"theme",
+ "thiserror",
"util",
"workspace",
]
@@ -25,6 +25,7 @@ dirs = "4.0.0"
shellexpand = "2.1.0"
libc = "0.2"
anyhow = "1"
+thiserror = "1.0"
[dev-dependencies]
@@ -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};
@@ -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>,
) {
@@ -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 {
@@ -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 }
}