From ace16b63a9035cb42e10e1fee06de1e1f2c61a84 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 26 Jul 2022 16:17:26 -0700 Subject: [PATCH] Checkpoint, still converting terminal to events. Not compiling --- crates/terminal/src/connected_el.rs | 6 +- crates/terminal/src/connected_view.rs | 22 +-- crates/terminal/src/mappings/keys.rs | 12 ++ crates/terminal/src/terminal.rs | 266 ++++++++++++++++---------- crates/terminal/src/terminal_view.rs | 4 +- 5 files changed, 189 insertions(+), 121 deletions(-) diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index 856f5ba210..26e3fc9665 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -514,8 +514,12 @@ impl Element for TerminalEl { let settings = cx.global::(); let font_cache = cx.font_cache(); + //First step, make all methods take mut and update internal event queue to take new actions + //Update process terminal event to handle all actions correctly + //And it's done. + //Setup layout information - let terminal_theme = settings.theme.terminal.clone(); //-_- + let terminal_theme = settings.theme.terminal.clone(); //TODO: Try to minimize this clone. let text_style = TerminalEl::make_text_style(font_cache, &settings); let selection_color = settings.theme.editor.selection.selection; let dimensions = { diff --git a/crates/terminal/src/connected_view.rs b/crates/terminal/src/connected_view.rs index 7bbc272a96..c235f68e58 100644 --- a/crates/terminal/src/connected_view.rs +++ b/crates/terminal/src/connected_view.rs @@ -1,6 +1,6 @@ use gpui::{ - actions, keymap::Keystroke, AppContext, ClipboardItem, Element, ElementBox, ModelHandle, - MutableAppContext, View, ViewContext, + actions, keymap::Keystroke, AppContext, Element, ElementBox, ModelHandle, MutableAppContext, + View, ViewContext, }; use crate::{connected_el::TerminalEl, Event, Terminal}; @@ -43,20 +43,19 @@ impl ConnectedView { modal: bool, cx: &mut ViewContext, ) -> Self { - cx.observe(&terminal, |_, _, cx| cx.notify()).detach(); + // cx.observe(&terminal, |_, _, cx| cx.notify()).detach(); //Terminal notifies for us cx.subscribe(&terminal, |this, _, event, cx| match event { Event::Wakeup => { - if cx.is_self_focused() { - cx.notify() - } else { + if !cx.is_self_focused() { this.has_new_content = true; - cx.emit(Event::TitleChanged); + cx.emit(Event::Wakeup); } } Event::Bell => { this.has_bell = true; - cx.emit(Event::TitleChanged); + cx.emit(Event::Wakeup); } + _ => cx.emit(*event), }) .detach(); @@ -83,7 +82,7 @@ impl ConnectedView { pub fn clear_bel(&mut self, cx: &mut ViewContext) { self.has_bell = false; - cx.emit(Event::TitleChanged); + cx.emit(Event::Wakeup); } fn clear(&mut self, _: &Clear, cx: &mut ViewContext) { @@ -92,10 +91,7 @@ impl ConnectedView { ///Attempt to paste the clipboard into the terminal fn copy(&mut self, _: &Copy, cx: &mut ViewContext) { - self.terminal - .read(cx) - .copy() - .map(|text| cx.write_to_clipboard(ClipboardItem::new(text))); + self.terminal.read(cx).copy() } ///Attempt to paste the clipboard into the terminal diff --git a/crates/terminal/src/mappings/keys.rs b/crates/terminal/src/mappings/keys.rs index f88bfa927a..c353db57a0 100644 --- a/crates/terminal/src/mappings/keys.rs +++ b/crates/terminal/src/mappings/keys.rs @@ -35,6 +35,18 @@ impl Modifiers { } } +///This function checks if to_esc_str would work, assuming all terminal settings are off. +///Note that this function is conservative. It can fail in cases where the actual to_esc_str succeeds. +///This is unavoidable for our use case. GPUI cannot wait until we acquire the terminal +///lock to determine whether we could actually send the keystroke with the current settings. Therefore, +///This conservative guess is used instead. Note that in practice the case where this method +///Returns false when the actual terminal would consume the keystroke never actually happens. All +///keystrokes that depend on terminal modes also have a mapping that doesn't depend on the terminal mode. +///This is fragile, but as these mappings are locked up in legacy compatibility, it's probably good enough +pub fn might_convert(keystroke: &Keystroke) -> bool { + to_esc_str(keystroke, &TermMode::NONE).is_some() +} + pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode) -> Option { let modifiers = Modifiers::new(&keystroke); diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 621b90b417..a91e22c892 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -18,15 +18,19 @@ use alacritty_terminal::{ index::{Direction, Point}, selection::{Selection, SelectionType}, sync::FairMutex, - term::{test::TermSize, RenderableContent, TermMode}, + term::{RenderableContent, TermMode}, tty::{self, setup_env}, Term, }; use anyhow::{bail, Result}; use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; +use itertools::Itertools; +use mappings::keys::might_convert; use modal::deploy_modal; use settings::{Settings, Shell}; -use std::{collections::HashMap, fmt::Display, path::PathBuf, sync::Arc, time::Duration}; +use std::{ + cmp::Ordering, collections::HashMap, fmt::Display, path::PathBuf, sync::Arc, time::Duration, +}; use terminal_view::TerminalView; use thiserror::Error; @@ -49,22 +53,62 @@ pub fn init(cx: &mut MutableAppContext) { connected_view::init(cx); } -const DEFAULT_TITLE: &str = "Terminal"; - const DEBUG_TERMINAL_WIDTH: f32 = 100.; const DEBUG_TERMINAL_HEIGHT: f32 = 30.; //This needs to be wide enough that the CI & a local dev's prompt can fill the whole space. const DEBUG_CELL_WIDTH: f32 = 5.; const DEBUG_LINE_HEIGHT: f32 = 5.; ///Upward flowing events, for changing the title and such -#[derive(Copy, Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub enum Event { TitleChanged, CloseTerminal, Activate, - Wakeup, Bell, - KeyInput, + Wakeup, +} + +#[derive(Clone, Debug)] +enum InternalEvent { + TermEvent(AlacTermEvent), + Resize(TermDimensions), + Clear, + Keystroke(Keystroke), + Paste(String), + SetSelection(Option), + Copy, +} + +impl PartialEq for InternalEvent { + fn eq(&self, other: &Self) -> bool { + if matches!(self, other) { + true + } else { + false + } + } +} + +impl Eq for InternalEvent {} + +impl PartialOrd for InternalEvent { + fn partial_cmp(&self, other: &Self) -> Option { + if self.eq(other) { + Some(Ordering::Equal) + } else if matches!(other, InternalEvent::Copy) { + Some(Ordering::Less) + } else if matches!(self, InternalEvent::Copy) { + Some(Ordering::Greater) + } else { + Some(Ordering::Equal) + } + } +} + +impl Ord for InternalEvent { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap() + } } ///A translation struct for Alacritty to communicate with us from their event loop @@ -322,8 +366,10 @@ impl TerminalBuilder { let terminal = Terminal { pty_tx: Notifier(pty_tx), term, - title: shell_txt.to_string(), + event_stack: vec![], + title: shell_txt.clone(), + default_title: shell_txt, }; Ok(TerminalBuilder { @@ -373,98 +419,136 @@ impl TerminalBuilder { pub struct Terminal { pty_tx: Notifier, term: Arc>>, - pub title: String, - event_stack: Vec, + event_stack: Vec, + default_title: String, + title: String, } impl Terminal { fn push_events(&mut self, events: Vec) { - self.event_stack.extend(events) + self.event_stack + .extend(events.into_iter().map(|e| InternalEvent::TermEvent(e))) } ///Takes events from Alacritty and translates them to behavior on this view fn process_terminal_event( &mut self, - event: alacritty_terminal::event::Event, + event: &InternalEvent, term: &mut Term, cx: &mut ModelContext, ) { + // TODO: Handle is_self_focused in subscription on terminal view match 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), - - AlacTermEvent::MouseCursorDirty => { - //Calculate new cursor style. - //TODO: alacritty/src/input.rs:L922-L939 - //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.pty_tx.notify( - format( + InternalEvent::TermEvent(term_event) => match term_event { + AlacTermEvent::Wakeup => { + cx.emit(Event::Wakeup); + } + AlacTermEvent::PtyWrite(out) => self.notify_pty(out.clone()), + AlacTermEvent::MouseCursorDirty => { + //Calculate new cursor style. + //TODO: alacritty/src/input.rs:L922-L939 + //Check on correctly handling mouse events for terminals + cx.platform().set_cursor_style(CursorStyle::Arrow); //??? + } + AlacTermEvent::Title(title) => { + self.title = title.to_string(); + cx.emit(Event::TitleChanged); + } + AlacTermEvent::ResetTitle => { + self.title = self.default_title.clone(); + cx.emit(Event::TitleChanged); + } + AlacTermEvent::ClipboardStore(_, data) => { + cx.write_to_clipboard(ClipboardItem::new(data.to_string())) + } + AlacTermEvent::ClipboardLoad(_, format) => self.notify_pty(format( &cx.read_from_clipboard() .map(|ci| ci.text().to_string()) .unwrap_or("".to_string()), - ) - .into_bytes(), - ), - AlacTermEvent::ColorRequest(index, format) => { - let color = term.colors()[index].unwrap_or_else(|| { - let term_style = &cx.global::().theme.terminal; - to_alac_rgb(get_color_at_index(&index, &term_style.colors)) - }); - self.pty_tx.notify(format(color).into_bytes()) + )), + AlacTermEvent::ColorRequest(index, format) => { + let color = term.colors()[*index].unwrap_or_else(|| { + let term_style = &cx.global::().theme.terminal; + to_alac_rgb(get_color_at_index(index, &term_style.colors)) + }); + self.notify_pty(format(color)) + } + 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), + AlacTermEvent::TextAreaSizeRequest(_) => { + println!("Received text area resize request") + } + }, + InternalEvent::Resize(new_size) => { + self.pty_tx + .0 + .send(Msg::Resize(new_size.clone().into())) + .ok(); + + term.resize(*new_size); } - AlacTermEvent::CursorBlinkingChange => { - //TODO: Set a timer to blink the cursor on and off + InternalEvent::Clear => { + self.notify_pty("\x0c".to_string()); + term.clear_screen(ClearMode::Saved); } - AlacTermEvent::Bell => { - cx.emit(Event::Bell); + InternalEvent::Keystroke(keystroke) => { + let esc = to_esc_str(keystroke, term.mode()); + if let Some(esc) = esc { + self.notify_pty(esc); + } } - AlacTermEvent::Exit => cx.emit(Event::CloseTerminal), - AlacTermEvent::TextAreaSizeRequest(_) => println!("Received text area resize request"), + InternalEvent::Paste(text) => { + if term.mode().contains(TermMode::BRACKETED_PASTE) { + self.notify_pty("\x1b[200~".to_string()); + self.notify_pty(text.replace('\x1b', "").to_string()); + self.notify_pty("\x1b[201~".to_string()); + } else { + self.notify_pty(text.replace("\r\n", "\r").replace('\n', "\r")); + } + } + InternalEvent::Copy => { + if let Some(txt) = term.selection_to_string() { + cx.write_to_clipboard(ClipboardItem::new(txt)) + } + } + InternalEvent::SetSelection(sel) => {} } } + fn notify_pty(&self, txt: String) { + self.pty_tx.notify(txt.into_bytes()); + } + + //TODO: + // - Continue refactor into event system + // - Fix PTYWrite call to not be circular and messy + // - Change title to be emitted and maintained externally + ///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) { - self.event_stack.push(AlacTermEvent::PtyWrite(input)) + self.event_stack + .push(InternalEvent::TermEvent(AlacTermEvent::PtyWrite(input))) } ///Resize the terminal and the PTY. This locks the terminal. - pub fn set_size(&self, new_size: WindowSize) { - self.pty_tx.0.send(Msg::Resize(new_size)).ok(); - - let term_size = TermSize::new(new_size.num_cols as usize, new_size.num_lines as usize); - self.term.lock().resize(term_size); + pub fn set_size(&mut self, new_size: TermDimensions) { + self.event_stack + .push(InternalEvent::Resize(new_size.into())) } pub fn clear(&mut self) { - self.write_to_pty("\x0c".into()); - self.term.lock().clear_screen(ClearMode::Saved); + self.event_stack.push(InternalEvent::Clear) } pub fn try_keystroke(&mut self, keystroke: &Keystroke) -> bool { - let guard = self.term.lock(); - let mode = guard.mode(); - let esc = to_esc_str(keystroke, mode); - drop(guard); - if esc.is_some() { - self.write_to_pty(esc.unwrap()); + if might_convert(keystroke) { + self.event_stack + .push(InternalEvent::Keystroke(keystroke.clone())); true } else { false @@ -473,55 +557,27 @@ impl Terminal { ///Paste text into the terminal pub fn paste(&mut self, text: &str) { - if self.term.lock().mode().contains(TermMode::BRACKETED_PASTE) { - self.write_to_pty("\x1b[200~".to_string()); - self.write_to_pty(text.replace('\x1b', "").to_string()); - self.write_to_pty("\x1b[201~".to_string()); - } else { - self.write_to_pty(text.replace("\r\n", "\r").replace('\n', "\r")); - } + self.event_stack + .push(InternalEvent::Paste(text.to_string())); } - pub fn copy(&self) -> Option { - let term = self.term.lock(); - term.selection_to_string() - } - - ///Takes the selection out of the terminal - pub fn take_selection(&self) -> Option { - self.term.lock().selection.take() - } - ///Sets the selection object on the terminal - pub fn set_selection(&self, sel: Option) { - self.term.lock().selection = sel; + pub fn copy(&self) { + self.event_stack.push(InternalEvent::Copy); } pub fn render_lock(&mut self, cx: &mut ModelContext, f: F) -> T where F: FnOnce(RenderableContent, char) -> T, { - let m = self.term.clone(); //TODO avoid clone? - let mut term = m.lock(); //Lock + let m = self.term.clone(); //Arc clone + let mut term = m.lock(); - //TODO, handle resizes - // if let Some(new_size) = new_size { - // self.pty_tx.0.send(Msg::Resize(new_size.into())).ok(); - // } - - // if let Some(new_size) = new_size { - // term.resize(new_size); //Reflow - // } - - for event in self - .event_stack - .iter() - .map(|event| event.clone()) - .collect::>() //TODO avoid copy - .drain(..) - { - self.process_terminal_event(event, &mut term, cx) + for event in self.event_stack.clone().into_iter().sorted() { + self.process_terminal_event(&event, &mut term, cx) } + self.event_stack.clear(); + let content = term.renderable_content(); let cursor_text = term.grid()[content.cursor.point].c; diff --git a/crates/terminal/src/terminal_view.rs b/crates/terminal/src/terminal_view.rs index 69bac7df1d..655be38ad0 100644 --- a/crates/terminal/src/terminal_view.rs +++ b/crates/terminal/src/terminal_view.rs @@ -208,7 +208,7 @@ impl Item for TerminalView { ) -> ElementBox { let title = match &self.content { TerminalContent::Connected(connected) => { - connected.read(cx).handle().read(cx).title.clone() + connected.read(cx).handle().read(cx).title.to_string() } TerminalContent::Error(_) => "Terminal".to_string(), }; @@ -294,7 +294,7 @@ impl Item for TerminalView { } fn should_update_tab_on_event(event: &Self::Event) -> bool { - matches!(event, &Event::TitleChanged) + matches!(event, &Event::TitleChanged | &Event::Wakeup) } fn should_close_item_on_event(event: &Self::Event) -> bool {