From 8d34fe7e940cfdda5b44fef84ee72dfb357dbce8 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Fri, 8 Jul 2022 16:10:09 -0700 Subject: [PATCH 1/4] Refactor terminal connection into a model which can be copied between terminal views Refactor terminal modal code to use TerminalConnection model handle so we aren't storing TerminalViews in the globals Adjust INSTANCE_BUFFER_SIZE in renderer to handle pathological terminal renders Co-authored-by: mikayla.c.maki@gmail.com --- Cargo.lock | 1 + crates/gpui/src/platform/mac/renderer.rs | 2 +- crates/terminal/Cargo.toml | 2 +- crates/terminal/src/connection.rs | 186 ++++++++++++ crates/terminal/src/gpui_func_tools.rs | 10 - crates/terminal/src/modal.rs | 51 ++-- crates/terminal/src/terminal.rs | 355 +++++++++-------------- crates/terminal/src/terminal_element.rs | 28 +- styles/package-lock.json | 1 + 9 files changed, 377 insertions(+), 259 deletions(-) create mode 100644 crates/terminal/src/connection.rs delete mode 100644 crates/terminal/src/gpui_func_tools.rs diff --git a/Cargo.lock b/Cargo.lock index 0fd0c8a212..22758abe87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4886,6 +4886,7 @@ dependencies = [ "itertools", "mio-extras", "ordered-float", + "parking_lot 0.11.2", "project", "settings", "smallvec", diff --git a/crates/gpui/src/platform/mac/renderer.rs b/crates/gpui/src/platform/mac/renderer.rs index 99dbd2030e..6b95fd6271 100644 --- a/crates/gpui/src/platform/mac/renderer.rs +++ b/crates/gpui/src/platform/mac/renderer.rs @@ -16,7 +16,7 @@ use std::{collections::HashMap, ffi::c_void, iter::Peekable, mem, sync::Arc, vec const SHADERS_METALLIB: &'static [u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib")); -const INSTANCE_BUFFER_SIZE: usize = 1024 * 1024; // This is an arbitrary decision. There's probably a more optimal value. +const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value. pub struct Renderer { sprite_cache: SpriteCache, diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index b44b93e745..bade53d5f0 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -22,7 +22,7 @@ futures = "0.3" ordered-float = "2.1.1" itertools = "0.10" dirs = "4.0.0" - +parking_lot = "0.11" [dev-dependencies] gpui = { path = "../gpui", features = ["test-support"] } diff --git a/crates/terminal/src/connection.rs b/crates/terminal/src/connection.rs new file mode 100644 index 0000000000..edc07cd0e3 --- /dev/null +++ b/crates/terminal/src/connection.rs @@ -0,0 +1,186 @@ +use alacritty_terminal::{ + config::{Config, PtyConfig}, + event::{Event as AlacTermEvent, Notify}, + event_loop::{EventLoop, Msg, Notifier}, + grid::Scroll, + sync::FairMutex, + term::SizeInfo, + tty::{self, setup_env}, + Term, +}; +use futures::{channel::mpsc::unbounded, StreamExt}; +use settings::Settings; +use std::{collections::HashMap, path::PathBuf, sync::Arc}; + +use gpui::{ClipboardItem, CursorStyle, Entity, ModelContext}; + +use crate::{ + color_translation::{get_color_at_index, to_alac_rgb}, + ZedListener, +}; + +const DEFAULT_TITLE: &str = "Terminal"; + +///Upward flowing events, for changing the title and such +#[derive(Copy, Clone, Debug)] +pub enum Event { + TitleChanged, + CloseTerminal, + Activate, + Wakeup, + Bell, +} + +pub struct TerminalConnection { + pub pty_tx: Notifier, + pub term: Arc>>, + pub title: String, + pub associated_directory: Option, +} + +impl TerminalConnection { + pub fn new( + working_directory: Option, + initial_size: SizeInfo, + cx: &mut ModelContext, + ) -> TerminalConnection { + let pty_config = PtyConfig { + shell: None, //Use the users default shell + working_directory: working_directory.clone(), + hold: false, + }; + + let mut env: HashMap = HashMap::new(); + //TODO: Properly set the current locale, + env.insert("LC_ALL".to_string(), "en_US.UTF-8".to_string()); + + let config = Config { + pty_config: pty_config.clone(), + env, + ..Default::default() + }; + + setup_env(&config); + + //Spawn a task so the Alacritty EventLoop can communicate with us in a view context + let (events_tx, mut events_rx) = unbounded(); + + //Set up the terminal... + let term = Term::new(&config, initial_size, ZedListener(events_tx.clone())); + let term = Arc::new(FairMutex::new(term)); + + //Setup the pty... + let pty = tty::new(&pty_config, &initial_size, None).expect("Could not create tty"); + + //And connect them together + let event_loop = EventLoop::new( + term.clone(), + ZedListener(events_tx.clone()), + pty, + pty_config.hold, + false, + ); + + //Kick things off + let pty_tx = event_loop.channel(); + let _io_thread = event_loop.spawn(); + + cx.spawn_weak(|this, mut cx| async move { + //Listen for terminal events + while let Some(event) = events_rx.next().await { + match this.upgrade(&cx) { + Some(this) => { + this.update(&mut cx, |this, cx| { + this.process_terminal_event(event, cx); + cx.notify(); + }); + } + None => break, + } + } + }) + .detach(); + + TerminalConnection { + pty_tx: Notifier(pty_tx), + term, + title: DEFAULT_TITLE.to_string(), + associated_directory: working_directory, + } + } + + ///Takes events from Alacritty and translates them to behavior on this view + fn process_terminal_event( + &mut self, + event: alacritty_terminal::event::Event, + cx: &mut ModelContext, + ) { + match dbg!(event) { + // TODO: Handle is_self_focused in subscription on terminal view + AlacTermEvent::Wakeup => { + cx.emit(Event::Wakeup); + } + AlacTermEvent::PtyWrite(out) => self.write_to_pty(out, cx), + AlacTermEvent::MouseCursorDirty => { + //Calculate new cursor style. + //TODO + //Check on correctly handling mouse events for terminals + cx.platform().set_cursor_style(CursorStyle::Arrow); //??? + } + AlacTermEvent::Title(title) => { + self.title = title; + cx.emit(Event::TitleChanged); + } + AlacTermEvent::ResetTitle => { + self.title = DEFAULT_TITLE.to_string(); + cx.emit(Event::TitleChanged); + } + AlacTermEvent::ClipboardStore(_, data) => { + cx.write_to_clipboard(ClipboardItem::new(data)) + } + AlacTermEvent::ClipboardLoad(_, format) => self.write_to_pty( + format( + &cx.read_from_clipboard() + .map(|ci| ci.text().to_string()) + .unwrap_or("".to_string()), + ), + cx, + ), + AlacTermEvent::ColorRequest(index, format) => { + let color = self.term.lock().colors()[index].unwrap_or_else(|| { + let term_style = &cx.global::().theme.terminal; + to_alac_rgb(get_color_at_index(&index, &term_style.colors)) + }); + self.write_to_pty(format(color), cx) + } + AlacTermEvent::CursorBlinkingChange => { + //TODO: Set a timer to blink the cursor on and off + } + AlacTermEvent::Bell => { + cx.emit(Event::Bell); + } + AlacTermEvent::Exit => cx.emit(Event::CloseTerminal), + } + } + + ///Write the Input payload to the tty. This locks the terminal so we can scroll it. + pub fn write_to_pty(&mut self, input: String, cx: &mut ModelContext) { + self.write_bytes_to_pty(input.into_bytes(), cx); + } + + ///Write the Input payload to the tty. This locks the terminal so we can scroll it. + fn write_bytes_to_pty(&mut self, input: Vec, _: &mut ModelContext) { + self.term.lock().scroll_display(Scroll::Bottom); + self.pty_tx.notify(input); + } +} + +impl Drop for TerminalConnection { + fn drop(&mut self) { + self.pty_tx.0.send(Msg::Shutdown).ok(); + } +} + +impl Entity for TerminalConnection { + type Event = Event; +} diff --git a/crates/terminal/src/gpui_func_tools.rs b/crates/terminal/src/gpui_func_tools.rs deleted file mode 100644 index 4882023162..0000000000 --- a/crates/terminal/src/gpui_func_tools.rs +++ /dev/null @@ -1,10 +0,0 @@ -use gpui::geometry::rect::RectF; - -pub fn paint_layer(cx: &mut gpui::PaintContext, clip_bounds: Option, f: F) -where - F: FnOnce(&mut gpui::PaintContext) -> (), -{ - cx.scene.push_layer(clip_bounds); - f(cx); - cx.scene.pop_layer() -} diff --git a/crates/terminal/src/modal.rs b/crates/terminal/src/modal.rs index 2a1c16ae4b..f122e8b760 100644 --- a/crates/terminal/src/modal.rs +++ b/crates/terminal/src/modal.rs @@ -1,31 +1,48 @@ -use gpui::{ViewContext, ViewHandle}; +use gpui::{ModelHandle, ViewContext, ViewHandle}; use workspace::Workspace; -use crate::{get_working_directory, DeployModal, Event, Terminal}; +use crate::{get_working_directory, DeployModal, Event, Terminal, TerminalConnection}; pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewContext) { - if let Some(stored_terminal) = cx.default_global::>>().clone() { - workspace.toggle_modal(cx, |_, _| stored_terminal); - } else { - let project = workspace.project().read(cx); - let abs_path = project - .active_entry() - .and_then(|entry_id| project.worktree_for_entry(entry_id, cx)) - .and_then(|worktree_handle| worktree_handle.read(cx).as_local()) - .and_then(get_working_directory); + // Pull the terminal connection out of the global if it has been stored + let possible_connection = cx + .update_default_global::>, _, _>( + |possible_connection, _| possible_connection.take(), + ); - let displaced_modal = workspace.toggle_modal(cx, |_, cx| { - let this = cx.add_view(|cx| Terminal::new(cx, abs_path, true)); - cx.subscribe(&this, on_event).detach(); - this + if let Some(stored_connection) = possible_connection { + println!("Found stored connection"); + // Create a view from the stored connection + workspace.toggle_modal(cx, |_, cx| { + cx.add_view(|cx| Terminal::from_connection(stored_connection, true, cx)) }); - cx.set_global(displaced_modal); + } else { + println!("No global connection :("); + // No connection was stored, create a new terminal + if let Some(closed_terminal_handle) = workspace.toggle_modal(cx, |workspace, cx| { + println!("Creating new terminal connection"); + let project = workspace.project().read(cx); + let abs_path = project + .active_entry() + .and_then(|entry_id| project.worktree_for_entry(entry_id, cx)) + .and_then(|worktree_handle| worktree_handle.read(cx).as_local()) + .and_then(get_working_directory); + + let this = cx.add_view(|cx| Terminal::new(abs_path, true, cx)); + let connection_handle = this.read(cx).connection.clone(); + cx.subscribe(&connection_handle, on_event).detach(); + this + }) { + println!("Pulled connection from modal and stored in global"); + let connection = closed_terminal_handle.read(cx).connection.clone(); + cx.set_global(Some(connection)); + } } } pub fn on_event( workspace: &mut Workspace, - _: ViewHandle, + _: ModelHandle, event: &Event, cx: &mut ViewContext, ) { diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 017bb5f00a..af6f59c030 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -1,33 +1,29 @@ pub mod color_translation; -pub mod gpui_func_tools; +pub mod connection; mod modal; pub mod terminal_element; use alacritty_terminal::{ - config::{Config, PtyConfig}, - event::{Event as AlacTermEvent, EventListener, Notify}, - event_loop::{EventLoop, Msg, Notifier}, + event::{Event as AlacTermEvent, EventListener}, + event_loop::Msg, grid::Scroll, - sync::FairMutex, term::SizeInfo, - tty::{self, setup_env}, - Term, }; -use color_translation::{get_color_at_index, to_alac_rgb}; + +use connection::{Event, TerminalConnection}; use dirs::home_dir; -use futures::{ - channel::mpsc::{unbounded, UnboundedSender}, - StreamExt, -}; +use editor::Input; +use futures::channel::mpsc::UnboundedSender; use gpui::{ - actions, elements::*, impl_internal_actions, platform::CursorStyle, ClipboardItem, Entity, + actions, elements::*, impl_internal_actions, ClipboardItem, Entity, ModelHandle, MutableAppContext, View, ViewContext, }; use modal::deploy_modal; + use project::{LocalWorktree, Project, ProjectPath}; use settings::Settings; use smallvec::SmallVec; -use std::{collections::HashMap, path::PathBuf, sync::Arc}; +use std::path::PathBuf; use workspace::{Item, Workspace}; use crate::terminal_element::TerminalEl; @@ -42,16 +38,11 @@ const LEFT_SEQ: &str = "\x1b[D"; const RIGHT_SEQ: &str = "\x1b[C"; const UP_SEQ: &str = "\x1b[A"; const DOWN_SEQ: &str = "\x1b[B"; -const DEFAULT_TITLE: &str = "Terminal"; const DEBUG_TERMINAL_WIDTH: f32 = 1000.; //This needs to be wide enough that the prompt can fill the whole space. const DEBUG_TERMINAL_HEIGHT: f32 = 200.; const DEBUG_CELL_WIDTH: f32 = 5.; const DEBUG_LINE_HEIGHT: f32 = 5.; -///Action for carrying the input to the PTY -#[derive(Clone, Default, Debug, PartialEq, Eq)] -pub struct Input(pub String); - ///Event to transmit the scroll from the element to the view #[derive(Clone, Debug, PartialEq)] pub struct ScrollTerminal(pub i32); @@ -76,12 +67,11 @@ actions!( DeployModal, ] ); -impl_internal_actions!(terminal, [Input, ScrollTerminal]); +impl_internal_actions!(terminal, [ScrollTerminal]); ///Initialize and register all of our action handlers pub fn init(cx: &mut MutableAppContext) { cx.add_action(Terminal::deploy); - cx.add_action(Terminal::write_to_pty); cx.add_action(Terminal::send_sigint); cx.add_action(Terminal::escape); cx.add_action(Terminal::quit); @@ -95,6 +85,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(Terminal::copy); cx.add_action(Terminal::paste); cx.add_action(Terminal::scroll_terminal); + cx.add_action(Terminal::input); cx.add_action(deploy_modal); } @@ -110,21 +101,12 @@ impl EventListener for ZedListener { ///A terminal view, maintains the PTY's file handles and communicates with the terminal pub struct Terminal { - pty_tx: Notifier, - term: Arc>>, - title: String, + connection: ModelHandle, has_new_content: bool, - has_bell: bool, //Currently using iTerm bell, show bell emoji in tab until input is received - cur_size: SizeInfo, + //Currently using iTerm bell, show bell emoji in tab until input is received + has_bell: bool, + // Only for styling purposes. Doesn't effect behavior modal: bool, - associated_directory: Option, -} - -///Upward flowing events, for changing the title and such -pub enum Event { - TitleChanged, - CloseTerminal, - Activate, } impl Entity for Terminal { @@ -133,42 +115,7 @@ impl Entity for Terminal { impl Terminal { ///Create a new Terminal view. This spawns a task, a thread, and opens the TTY devices - fn new(cx: &mut ViewContext, working_directory: Option, modal: bool) -> Self { - //Spawn a task so the Alacritty EventLoop can communicate with us in a view context - let (events_tx, mut events_rx) = unbounded(); - cx.spawn_weak(|this, mut cx| async move { - while let Some(event) = events_rx.next().await { - match this.upgrade(&cx) { - Some(handle) => { - handle.update(&mut cx, |this, cx| { - this.process_terminal_event(event, cx); - cx.notify(); - }); - } - None => break, - } - } - }) - .detach(); - - let pty_config = PtyConfig { - shell: None, //Use the users default shell - working_directory: working_directory.clone(), - hold: false, - }; - - let mut env: HashMap = HashMap::new(); - //TODO: Properly set the current locale, - env.insert("LC_ALL".to_string(), "en_US.UTF-8".to_string()); - - let config = Config { - pty_config: pty_config.clone(), - env, - ..Default::default() - }; - - setup_env(&config); - + fn new(working_directory: Option, modal: bool, cx: &mut ViewContext) -> Self { //The details here don't matter, the terminal will be resized on the first layout let size_info = SizeInfo::new( DEBUG_TERMINAL_WIDTH, @@ -180,108 +127,69 @@ impl Terminal { false, ); - //Set up the terminal... - let term = Term::new(&config, size_info, ZedListener(events_tx.clone())); - let term = Arc::new(FairMutex::new(term)); + let connection = + cx.add_model(|cx| TerminalConnection::new(working_directory, size_info, cx)); - //Setup the pty... - let pty = tty::new(&pty_config, &size_info, None).expect("Could not create tty"); - - //And connect them together - let event_loop = EventLoop::new( - term.clone(), - ZedListener(events_tx.clone()), - pty, - pty_config.hold, - false, - ); - - //Kick things off - let pty_tx = Notifier(event_loop.channel()); - let _io_thread = event_loop.spawn(); - Terminal { - title: DEFAULT_TITLE.to_string(), - term, - pty_tx, - has_new_content: false, - has_bell: false, - cur_size: size_info, - modal, - associated_directory: working_directory, - } + Terminal::from_connection(connection, modal, cx) } - ///Takes events from Alacritty and translates them to behavior on this view - fn process_terminal_event( - &mut self, - event: alacritty_terminal::event::Event, + fn from_connection( + connection: ModelHandle, + modal: bool, cx: &mut ViewContext, - ) { - match event { - AlacTermEvent::Wakeup => { - if !cx.is_self_focused() { - self.has_new_content = true; //Change tab content - cx.emit(Event::TitleChanged); - } else { + ) -> Terminal { + cx.observe(&connection, |_, _, cx| cx.notify()).detach(); + cx.subscribe(&connection, |this, _, event, cx| match dbg!(event) { + Event::Wakeup => { + if cx.is_self_focused() { cx.notify() + } else { + this.has_new_content = true; + cx.emit(Event::TitleChanged); } } - AlacTermEvent::PtyWrite(out) => self.write_to_pty(&Input(out), cx), - AlacTermEvent::MouseCursorDirty => { - //Calculate new cursor style. - //TODO - //Check on correctly handling mouse events for terminals - cx.platform().set_cursor_style(CursorStyle::Arrow); //??? - } - AlacTermEvent::Title(title) => { - self.title = title; + Event::Bell => { + this.has_bell = true; cx.emit(Event::TitleChanged); } - AlacTermEvent::ResetTitle => { - self.title = DEFAULT_TITLE.to_string(); - cx.emit(Event::TitleChanged); - } - AlacTermEvent::ClipboardStore(_, data) => { - cx.write_to_clipboard(ClipboardItem::new(data)) - } - AlacTermEvent::ClipboardLoad(_, format) => self.write_to_pty( - &Input(format( - &cx.read_from_clipboard() - .map(|ci| ci.text().to_string()) - .unwrap_or("".to_string()), - )), - cx, - ), - AlacTermEvent::ColorRequest(index, format) => { - let color = self.term.lock().colors()[index].unwrap_or_else(|| { - let term_style = &cx.global::().theme.terminal; - to_alac_rgb(get_color_at_index(&index, &term_style.colors)) - }); - self.write_to_pty(&Input(format(color)), cx) - } - AlacTermEvent::CursorBlinkingChange => { - //TODO: Set a timer to blink the cursor on and off - } - AlacTermEvent::Bell => { - self.has_bell = true; - cx.emit(Event::TitleChanged); - } - AlacTermEvent::Exit => self.quit(&Quit, cx), + _ => cx.emit(*event), + }) + .detach(); + + Terminal { + connection, + has_new_content: true, + has_bell: false, + modal, } } ///Resize the terminal and the PTY. This locks the terminal. - fn set_size(&mut self, new_size: SizeInfo) { - if new_size != self.cur_size { - self.pty_tx.0.send(Msg::Resize(new_size)).ok(); - self.term.lock().resize(new_size); - self.cur_size = new_size; - } + fn set_size(&self, new_size: SizeInfo, cx: &mut MutableAppContext) { + self.connection.update(cx, |connection, _| { + connection.pty_tx.0.send(Msg::Resize(new_size)).ok(); + connection.term.lock().resize(new_size); + }) } ///Scroll the terminal. This locks the terminal - fn scroll_terminal(&mut self, scroll: &ScrollTerminal, _: &mut ViewContext) { - self.term.lock().scroll_display(Scroll::Delta(scroll.0)); + fn scroll_terminal(&mut self, scroll: &ScrollTerminal, cx: &mut ViewContext) { + self.connection + .read(cx) + .term + .lock() + .scroll_display(Scroll::Delta(scroll.0)); + } + + fn input(&mut self, Input(text): &Input, cx: &mut ViewContext) { + self.connection.update(cx, |connection, cx| { + connection.write_to_pty(text.clone(), cx); + }); + + if self.has_bell { + self.has_bell = false; + cx.emit(Event::TitleChanged); + } } ///Create a new Terminal in the current working directory or the user's home directory @@ -295,16 +203,11 @@ impl Terminal { .and_then(get_working_directory); workspace.add_item( - Box::new(cx.add_view(|cx| Terminal::new(cx, abs_path, false))), + Box::new(cx.add_view(|cx| Terminal::new(abs_path, false, cx))), cx, ); } - ///Send the shutdown message to Alacritty - fn shutdown_pty(&mut self) { - self.pty_tx.0.send(Msg::Shutdown).ok(); - } - ///Tell Zed to close us fn quit(&mut self, _: &Quit, cx: &mut ViewContext) { cx.emit(Event::CloseTerminal); @@ -312,7 +215,7 @@ impl Terminal { ///Attempt to paste the clipboard into the terminal fn copy(&mut self, _: &Copy, cx: &mut ViewContext) { - let term = self.term.lock(); + let term = self.connection.read(cx).term.lock(); let copy_text = term.selection_to_string(); match copy_text { Some(s) => cx.write_to_clipboard(ClipboardItem::new(s)), @@ -323,74 +226,73 @@ impl Terminal { ///Attempt to paste the clipboard into the terminal fn paste(&mut self, _: &Paste, cx: &mut ViewContext) { if let Some(item) = cx.read_from_clipboard() { - self.write_to_pty(&Input(item.text().to_owned()), cx); + self.connection.update(cx, |connection, cx| { + connection.write_to_pty(item.text().to_owned(), cx); + }) } } - ///Write the Input payload to the tty. This locks the terminal so we can scroll it. - fn write_to_pty(&mut self, input: &Input, cx: &mut ViewContext) { - self.write_bytes_to_pty(input.0.clone().into_bytes(), cx); - } - - ///Write the Input payload to the tty. This locks the terminal so we can scroll it. - fn write_bytes_to_pty(&mut self, input: Vec, cx: &mut ViewContext) { - //iTerm bell behavior, bell stays until terminal is interacted with - self.has_bell = false; - cx.emit(Event::TitleChanged); - self.term.lock().scroll_display(Scroll::Bottom); - self.pty_tx.notify(input); - } - ///Send the `up` key fn up(&mut self, _: &Up, cx: &mut ViewContext) { - self.write_to_pty(&Input(UP_SEQ.to_string()), cx); + self.connection.update(cx, |connection, cx| { + connection.write_to_pty(UP_SEQ.to_string(), cx); + }); } ///Send the `down` key fn down(&mut self, _: &Down, cx: &mut ViewContext) { - self.write_to_pty(&Input(DOWN_SEQ.to_string()), cx); + self.connection.update(cx, |connection, cx| { + connection.write_to_pty(DOWN_SEQ.to_string(), cx); + }); } ///Send the `tab` key fn tab(&mut self, _: &Tab, cx: &mut ViewContext) { - self.write_to_pty(&Input(TAB_CHAR.to_string()), cx); + self.connection.update(cx, |connection, cx| { + connection.write_to_pty(TAB_CHAR.to_string(), cx); + }); } ///Send `SIGINT` (`ctrl-c`) fn send_sigint(&mut self, _: &Sigint, cx: &mut ViewContext) { - self.write_to_pty(&Input(ETX_CHAR.to_string()), cx); + self.connection.update(cx, |connection, cx| { + connection.write_to_pty(ETX_CHAR.to_string(), cx); + }); } ///Send the `escape` key fn escape(&mut self, _: &Escape, cx: &mut ViewContext) { - self.write_to_pty(&Input(ESC_CHAR.to_string()), cx); + self.connection.update(cx, |connection, cx| { + connection.write_to_pty(ESC_CHAR.to_string(), cx); + }); } ///Send the `delete` key. TODO: Difference between this and backspace? fn del(&mut self, _: &Del, cx: &mut ViewContext) { - // self.write_to_pty(&Input("\x1b[3~".to_string()), cx) - self.write_to_pty(&Input(DEL_CHAR.to_string()), cx); + self.connection.update(cx, |connection, cx| { + connection.write_to_pty(DEL_CHAR.to_string(), cx); + }); } ///Send a carriage return. TODO: May need to check the terminal mode. fn carriage_return(&mut self, _: &Return, cx: &mut ViewContext) { - self.write_to_pty(&Input(CARRIAGE_RETURN_CHAR.to_string()), cx); + self.connection.update(cx, |connection, cx| { + connection.write_to_pty(CARRIAGE_RETURN_CHAR.to_string(), cx); + }); } //Send the `left` key fn left(&mut self, _: &Left, cx: &mut ViewContext) { - self.write_to_pty(&Input(LEFT_SEQ.to_string()), cx); + self.connection.update(cx, |connection, cx| { + connection.write_to_pty(LEFT_SEQ.to_string(), cx); + }); } //Send the `right` key fn right(&mut self, _: &Right, cx: &mut ViewContext) { - self.write_to_pty(&Input(RIGHT_SEQ.to_string()), cx); - } -} - -impl Drop for Terminal { - fn drop(&mut self) { - self.shutdown_pty(); + self.connection.update(cx, |connection, cx| { + connection.write_to_pty(RIGHT_SEQ.to_string(), cx); + }); } } @@ -441,15 +343,18 @@ impl Item for Terminal { }; flex.with_child( - Label::new(self.title.clone(), tab_theme.label.clone()) - .aligned() - .contained() - .with_margin_left(if self.has_bell { - search_theme.tab_icon_spacing - } else { - 0. - }) - .boxed(), + Label::new( + self.connection.read(cx).title.clone(), + tab_theme.label.clone(), + ) + .aligned() + .contained() + .with_margin_left(if self.has_bell { + search_theme.tab_icon_spacing + } else { + 0. + }) + .boxed(), ) .boxed() } @@ -458,7 +363,11 @@ impl Item for Terminal { //From what I can tell, there's no way to tell the current working //Directory of the terminal from outside the terminal. There might be //solutions to this, but they are non-trivial and require more IPC - Some(Terminal::new(cx, self.associated_directory.clone(), false)) + Some(Terminal::new( + self.connection.read(cx).associated_directory.clone(), + false, + cx, + )) } fn project_path(&self, _cx: &gpui::AppContext) -> Option { @@ -540,22 +449,28 @@ mod tests { use gpui::TestAppContext; use itertools::Itertools; use project::{FakeFs, Fs, RealFs, RemoveOptions, Worktree}; - use std::{path::Path, sync::atomic::AtomicUsize, time::Duration}; + use std::{ + path::Path, + sync::{atomic::AtomicUsize, Arc}, + time::Duration, + }; ///Basic integration test, can we get the terminal to show up, execute a command, //and produce noticable output? #[gpui::test] async fn test_terminal(cx: &mut TestAppContext) { - let terminal = cx.add_view(Default::default(), |cx| Terminal::new(cx, None, false)); + let terminal = cx.add_view(Default::default(), |cx| Terminal::new(None, false, cx)); cx.set_condition_duration(Duration::from_secs(2)); terminal.update(cx, |terminal, cx| { - terminal.write_to_pty(&Input(("expr 3 + 4".to_string()).to_string()), cx); + terminal.connection.update(cx, |connection, cx| { + connection.write_to_pty("expr 3 + 4".to_string(), cx); + }); terminal.carriage_return(&Return, cx); }); terminal - .condition(cx, |terminal, _cx| { - let term = terminal.term.clone(); + .condition(cx, |terminal, cx| { + let term = terminal.connection.read(cx).term.clone(); let content = grid_as_str(term.lock().renderable_content().display_iter); dbg!(&content); content.contains("7") @@ -647,24 +562,34 @@ mod tests { ///If this test is failing for you, check that DEBUG_TERMINAL_WIDTH is wide enough to fit your entire command prompt! #[gpui::test] async fn test_copy(cx: &mut TestAppContext) { - let terminal = cx.add_view(Default::default(), |cx| Terminal::new(cx, None, false)); + let mut result_line: i32 = 0; + let terminal = cx.add_view(Default::default(), |cx| Terminal::new(None, false, cx)); cx.set_condition_duration(Duration::from_secs(2)); terminal.update(cx, |terminal, cx| { - terminal.write_to_pty(&Input(("expr 3 + 4".to_string()).to_string()), cx); + terminal.connection.update(cx, |connection, cx| { + connection.write_to_pty("expr 3 + 4".to_string(), cx); + }); terminal.carriage_return(&Return, cx); }); terminal - .condition(cx, |terminal, _cx| { - let term = terminal.term.clone(); + .condition(cx, |terminal, cx| { + let term = terminal.connection.read(cx).term.clone(); let content = grid_as_str(term.lock().renderable_content().display_iter); - content.contains("7") + + if content.contains("7") { + let idx = content.chars().position(|c| c == '7').unwrap(); + result_line = content.chars().take(idx).filter(|c| *c == '\n').count() as i32; + true + } else { + false + } }) .await; terminal.update(cx, |terminal, cx| { - let mut term = terminal.term.lock(); + let mut term = terminal.connection.read(cx).term.lock(); term.selection = Some(Selection::new( SelectionType::Semantic, Point::new(Line(2), Column(0)), diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index ec48996e78..8576671f6a 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -9,7 +9,7 @@ use alacritty_terminal::{ }, Term, }; -use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine}; +use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine, Input}; use gpui::{ color::Color, elements::*, @@ -31,10 +31,7 @@ use theme::TerminalStyle; use std::{cmp::min, ops::Range, rc::Rc, sync::Arc}; use std::{fmt::Debug, ops::Sub}; -use crate::{ - color_translation::convert_color, gpui_func_tools::paint_layer, Input, ScrollTerminal, - Terminal, ZedListener, -}; +use crate::{color_translation::convert_color, ScrollTerminal, Terminal, ZedListener}; ///Scrolling is unbearably sluggish by default. Alacritty supports a configurable ///Scroll multiplier that is set to 3 by default. This will be removed when I @@ -126,7 +123,7 @@ impl Element for TerminalEl { //Tell the view our new size. Requires a mutable borrow of cx and the view let cur_size = make_new_size(constraint, &cell_width, &line_height); //Note that set_size locks and mutates the terminal. - view_handle.update(cx.app, |view, _cx| view.set_size(cur_size)); + view_handle.update(cx.app, |view, cx| view.set_size(cur_size, cx)); //Now that we're done with the mutable portion, grab the immutable settings and view again let view = view_handle.read(cx); @@ -135,7 +132,8 @@ impl Element for TerminalEl { let theme = &(cx.global::()).theme; (theme.editor.selection.selection, &theme.terminal) }; - let terminal_mutex = view_handle.read(cx).term.clone(); + + let terminal_mutex = view_handle.read(cx).connection.read(cx).term.clone(); let term = terminal_mutex.lock(); let grid = term.grid(); let cursor_point = grid.cursor.point; @@ -220,10 +218,11 @@ impl Element for TerminalEl { layout: &mut Self::LayoutState, cx: &mut gpui::PaintContext, ) -> Self::PaintState { + println!("Painted a terminal element"); //Setup element stuff let clip_bounds = Some(visible_bounds); - paint_layer(cx, clip_bounds, |cx| { + cx.paint_layer(clip_bounds, |cx| { let cur_size = layout.cur_size.clone(); let origin = bounds.origin() + vec2f(layout.em_width.0, 0.); @@ -237,7 +236,7 @@ impl Element for TerminalEl { cx, ); - paint_layer(cx, clip_bounds, |cx| { + cx.paint_layer(clip_bounds, |cx| { //Start with a background color cx.scene.push_quad(Quad { bounds: RectF::new(bounds.origin(), bounds.size()), @@ -266,7 +265,7 @@ impl Element for TerminalEl { }); //Draw Selection - paint_layer(cx, clip_bounds, |cx| { + cx.paint_layer(clip_bounds, |cx| { let mut highlight_y = None; let highlight_lines = layout .layout_lines @@ -305,8 +304,7 @@ impl Element for TerminalEl { } }); - //Draw text - paint_layer(cx, clip_bounds, |cx| { + cx.paint_layer(clip_bounds, |cx| { for layout_line in &layout.layout_lines { for layout_cell in &layout_line.cells { let point = layout_cell.point; @@ -329,16 +327,16 @@ impl Element for TerminalEl { //Draw cursor if let Some(cursor) = &layout.cursor { - paint_layer(cx, clip_bounds, |cx| { + cx.paint_layer(clip_bounds, |cx| { cursor.paint(origin, cx); }) } #[cfg(debug_assertions)] if DEBUG_GRID { - paint_layer(cx, clip_bounds, |cx| { + cx.paint_layer(clip_bounds, |cx| { draw_debug_grid(bounds, layout, cx); - }); + }) } }); } diff --git a/styles/package-lock.json b/styles/package-lock.json index 2eb6d3a1bf..49304dc2fa 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "styles", "version": "1.0.0", "license": "ISC", "dependencies": { From 31361e564d3f8a7dfec7e35b23ace218db94ae82 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Fri, 8 Jul 2022 16:14:41 -0700 Subject: [PATCH 2/4] remove temporary debug print statements Co-authored-by: mikayla.c.maki@gmail.com --- crates/terminal/src/connection.rs | 2 +- crates/terminal/src/modal.rs | 4 ---- crates/terminal/src/terminal.rs | 3 +-- crates/terminal/src/terminal_element.rs | 1 - 4 files changed, 2 insertions(+), 8 deletions(-) diff --git a/crates/terminal/src/connection.rs b/crates/terminal/src/connection.rs index edc07cd0e3..4c615f6c89 100644 --- a/crates/terminal/src/connection.rs +++ b/crates/terminal/src/connection.rs @@ -115,7 +115,7 @@ impl TerminalConnection { event: alacritty_terminal::event::Event, cx: &mut ModelContext, ) { - match dbg!(event) { + match event { // TODO: Handle is_self_focused in subscription on terminal view AlacTermEvent::Wakeup => { cx.emit(Event::Wakeup); diff --git a/crates/terminal/src/modal.rs b/crates/terminal/src/modal.rs index f122e8b760..6273ecd5b4 100644 --- a/crates/terminal/src/modal.rs +++ b/crates/terminal/src/modal.rs @@ -11,16 +11,13 @@ pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewCon ); if let Some(stored_connection) = possible_connection { - println!("Found stored connection"); // Create a view from the stored connection workspace.toggle_modal(cx, |_, cx| { cx.add_view(|cx| Terminal::from_connection(stored_connection, true, cx)) }); } else { - println!("No global connection :("); // No connection was stored, create a new terminal if let Some(closed_terminal_handle) = workspace.toggle_modal(cx, |workspace, cx| { - println!("Creating new terminal connection"); let project = workspace.project().read(cx); let abs_path = project .active_entry() @@ -33,7 +30,6 @@ pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewCon cx.subscribe(&connection_handle, on_event).detach(); this }) { - println!("Pulled connection from modal and stored in global"); let connection = closed_terminal_handle.read(cx).connection.clone(); cx.set_global(Some(connection)); } diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index af6f59c030..4597d3a8a0 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -139,7 +139,7 @@ impl Terminal { cx: &mut ViewContext, ) -> Terminal { cx.observe(&connection, |_, _, cx| cx.notify()).detach(); - cx.subscribe(&connection, |this, _, event, cx| match dbg!(event) { + cx.subscribe(&connection, |this, _, event, cx| match event { Event::Wakeup => { if cx.is_self_focused() { cx.notify() @@ -472,7 +472,6 @@ mod tests { .condition(cx, |terminal, cx| { let term = terminal.connection.read(cx).term.clone(); let content = grid_as_str(term.lock().renderable_content().display_iter); - dbg!(&content); content.contains("7") }) .await; diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 8576671f6a..8decd0c404 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -218,7 +218,6 @@ impl Element for TerminalEl { layout: &mut Self::LayoutState, cx: &mut gpui::PaintContext, ) -> Self::PaintState { - println!("Painted a terminal element"); //Setup element stuff let clip_bounds = Some(visible_bounds); From 20f7fba16f2a201d955fb2859b7013d02730278a Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Fri, 8 Jul 2022 16:16:57 -0700 Subject: [PATCH 3/4] Move terminal scripts to scripts folder, and remove parking_lot from terminal crate Co-authored-by: mikayla.c.maki@gmail.com --- Cargo.lock | 1 - crates/terminal/Cargo.toml | 1 - crates/terminal/{ => scripts}/print256color.sh | 0 crates/terminal/{ => scripts}/truecolor.sh | 0 4 files changed, 2 deletions(-) rename crates/terminal/{ => scripts}/print256color.sh (100%) rename crates/terminal/{ => scripts}/truecolor.sh (100%) diff --git a/Cargo.lock b/Cargo.lock index 22758abe87..0fd0c8a212 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4886,7 +4886,6 @@ dependencies = [ "itertools", "mio-extras", "ordered-float", - "parking_lot 0.11.2", "project", "settings", "smallvec", diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index bade53d5f0..d8f438b6f9 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -22,7 +22,6 @@ futures = "0.3" ordered-float = "2.1.1" itertools = "0.10" dirs = "4.0.0" -parking_lot = "0.11" [dev-dependencies] gpui = { path = "../gpui", features = ["test-support"] } diff --git a/crates/terminal/print256color.sh b/crates/terminal/scripts/print256color.sh similarity index 100% rename from crates/terminal/print256color.sh rename to crates/terminal/scripts/print256color.sh diff --git a/crates/terminal/truecolor.sh b/crates/terminal/scripts/truecolor.sh similarity index 100% rename from crates/terminal/truecolor.sh rename to crates/terminal/scripts/truecolor.sh From ed3666547bab20c393a0a8e16f321139b3366d8a Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Fri, 8 Jul 2022 16:29:29 -0700 Subject: [PATCH 4/4] Make global type more resilient, and fix modal keymap context --- crates/terminal/src/modal.rs | 18 ++++++++++-------- crates/terminal/src/terminal.rs | 4 +++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/crates/terminal/src/modal.rs b/crates/terminal/src/modal.rs index 6273ecd5b4..51fc19efa2 100644 --- a/crates/terminal/src/modal.rs +++ b/crates/terminal/src/modal.rs @@ -1,16 +1,18 @@ -use gpui::{ModelHandle, ViewContext, ViewHandle}; +use gpui::{ModelHandle, ViewContext}; use workspace::Workspace; use crate::{get_working_directory, DeployModal, Event, Terminal, TerminalConnection}; +struct StoredConnection(ModelHandle); + pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewContext) { // Pull the terminal connection out of the global if it has been stored - let possible_connection = cx - .update_default_global::>, _, _>( - |possible_connection, _| possible_connection.take(), - ); + let possible_connection = + cx.update_default_global::, _, _>(|possible_connection, _| { + possible_connection.take() + }); - if let Some(stored_connection) = possible_connection { + if let Some(StoredConnection(stored_connection)) = possible_connection { // Create a view from the stored connection workspace.toggle_modal(cx, |_, cx| { cx.add_view(|cx| Terminal::from_connection(stored_connection, true, cx)) @@ -31,7 +33,7 @@ pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewCon this }) { let connection = closed_terminal_handle.read(cx).connection.clone(); - cx.set_global(Some(connection)); + cx.set_global(Some(StoredConnection(connection))); } } } @@ -44,7 +46,7 @@ pub fn on_event( ) { // Dismiss the modal if the terminal quit if let Event::CloseTerminal = event { - cx.set_global::>>(None); + cx.set_global::>(None); if workspace .modal() .cloned() diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 4597d3a8a0..1dac9aa7a8 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -319,7 +319,9 @@ impl View for Terminal { fn keymap_context(&self, _: &gpui::AppContext) -> gpui::keymap::Context { let mut context = Self::default_keymap_context(); - context.set.insert("ModalTerminal".into()); + if self.modal { + context.set.insert("ModalTerminal".into()); + } context } }