use alacritty_terminal::term::TermMode; use gpui::{ actions, keymap::Keystroke, AppContext, Element, ElementBox, ModelHandle, MutableAppContext, View, ViewContext, }; use crate::{connected_el::TerminalEl, Event, Terminal}; ///Event to transmit the scroll from the element to the view #[derive(Clone, Debug, PartialEq)] pub struct ScrollTerminal(pub i32); actions!( terminal, [Up, Down, CtrlC, Escape, Enter, Clear, Copy, Paste,] ); pub fn init(cx: &mut MutableAppContext) { //Global binding overrrides cx.add_action(ConnectedView::ctrl_c); cx.add_action(ConnectedView::up); cx.add_action(ConnectedView::down); cx.add_action(ConnectedView::escape); cx.add_action(ConnectedView::enter); //Useful terminal views cx.add_action(ConnectedView::copy); cx.add_action(ConnectedView::paste); cx.add_action(ConnectedView::clear); } ///A terminal view, maintains the PTY's file handles and communicates with the terminal pub struct ConnectedView { terminal: ModelHandle, has_new_content: bool, //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, } impl ConnectedView { pub fn from_terminal( terminal: ModelHandle, modal: bool, cx: &mut ViewContext, ) -> Self { cx.observe(&terminal, |_, _, cx| cx.notify()).detach(); cx.subscribe(&terminal, |this, _, event, cx| match event { Event::Wakeup => { if !cx.is_self_focused() { this.has_new_content = true; cx.notify(); cx.emit(Event::Wakeup); } } Event::Bell => { this.has_bell = true; cx.emit(Event::Wakeup); } _ => cx.emit(*event), }) .detach(); Self { terminal, has_new_content: true, has_bell: false, modal, } } pub fn handle(&self) -> ModelHandle { self.terminal.clone() } pub fn has_new_content(&self) -> bool { self.has_new_content } pub fn has_bell(&self) -> bool { self.has_bell } pub fn clear_bel(&mut self, cx: &mut ViewContext) { self.has_bell = false; cx.emit(Event::Wakeup); } fn clear(&mut self, _: &Clear, cx: &mut ViewContext) { self.terminal.update(cx, |term, _| term.clear()); } ///Attempt to paste the clipboard into the terminal fn copy(&mut self, _: &Copy, cx: &mut ViewContext) { self.terminal.update(cx, |term, _| term.copy()) } ///Attempt to paste the clipboard into the terminal fn paste(&mut self, _: &Paste, cx: &mut ViewContext) { cx.read_from_clipboard().map(|item| { self.terminal.read(cx).paste(item.text()); }); } ///Synthesize the keyboard event corresponding to 'up' fn up(&mut self, _: &Up, cx: &mut ViewContext) { self.terminal .read(cx) .try_keystroke(&Keystroke::parse("up").unwrap()); } ///Synthesize the keyboard event corresponding to 'down' fn down(&mut self, _: &Down, cx: &mut ViewContext) { self.terminal .read(cx) .try_keystroke(&Keystroke::parse("down").unwrap()); } ///Synthesize the keyboard event corresponding to 'ctrl-c' fn ctrl_c(&mut self, _: &CtrlC, cx: &mut ViewContext) { self.terminal .read(cx) .try_keystroke(&Keystroke::parse("ctrl-c").unwrap()); } ///Synthesize the keyboard event corresponding to 'escape' fn escape(&mut self, _: &Escape, cx: &mut ViewContext) { self.terminal .read(cx) .try_keystroke(&Keystroke::parse("escape").unwrap()); } ///Synthesize the keyboard event corresponding to 'enter' fn enter(&mut self, _: &Enter, cx: &mut ViewContext) { self.terminal .read(cx) .try_keystroke(&Keystroke::parse("enter").unwrap()); } } impl View for ConnectedView { fn ui_name() -> &'static str { "Terminal" } fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox { let terminal_handle = self.terminal.clone().downgrade(); TerminalEl::new(cx.handle(), terminal_handle, self.modal) .contained() .boxed() } fn on_focus(&mut self, _cx: &mut ViewContext) { self.has_new_content = false; } fn selected_text_range(&self, cx: &AppContext) -> Option> { if self .terminal .read(cx) .last_mode .contains(TermMode::ALT_SCREEN) { None } else { Some(0..0) } } fn replace_text_in_range( &mut self, _: Option>, text: &str, cx: &mut ViewContext, ) { self.terminal .update(cx, |terminal, _| terminal.write_to_pty(text.into())); } }