connected_view.rs

  1use gpui::{
  2    actions, keymap::Keystroke, AppContext, Element, ElementBox, ModelHandle, MutableAppContext,
  3    View, ViewContext,
  4};
  5
  6use crate::{connected_el::TerminalEl, Event, Terminal};
  7
  8///Event to transmit the scroll from the element to the view
  9#[derive(Clone, Debug, PartialEq)]
 10pub struct ScrollTerminal(pub i32);
 11
 12actions!(
 13    terminal,
 14    [Up, Down, CtrlC, Escape, Enter, Clear, Copy, Paste,]
 15);
 16
 17pub fn init(cx: &mut MutableAppContext) {
 18    //Global binding overrrides
 19    cx.add_action(ConnectedView::ctrl_c);
 20    cx.add_action(ConnectedView::up);
 21    cx.add_action(ConnectedView::down);
 22    cx.add_action(ConnectedView::escape);
 23    cx.add_action(ConnectedView::enter);
 24    //Useful terminal views
 25    cx.add_action(ConnectedView::copy);
 26    cx.add_action(ConnectedView::paste);
 27    cx.add_action(ConnectedView::clear);
 28}
 29
 30///A terminal view, maintains the PTY's file handles and communicates with the terminal
 31pub struct ConnectedView {
 32    terminal: ModelHandle<Terminal>,
 33    has_new_content: bool,
 34    //Currently using iTerm bell, show bell emoji in tab until input is received
 35    has_bell: bool,
 36    // Only for styling purposes. Doesn't effect behavior
 37    modal: bool,
 38}
 39
 40impl ConnectedView {
 41    pub fn from_terminal(
 42        terminal: ModelHandle<Terminal>,
 43        modal: bool,
 44        cx: &mut ViewContext<Self>,
 45    ) -> Self {
 46        // cx.observe(&terminal, |_, _, cx| cx.notify()).detach(); //Terminal notifies for us
 47        cx.subscribe(&terminal, |this, _, event, cx| match event {
 48            Event::Wakeup => {
 49                if !cx.is_self_focused() {
 50                    this.has_new_content = true;
 51                    cx.emit(Event::Wakeup);
 52                }
 53            }
 54            Event::Bell => {
 55                this.has_bell = true;
 56                cx.emit(Event::Wakeup);
 57            }
 58
 59            _ => cx.emit(*event),
 60        })
 61        .detach();
 62
 63        Self {
 64            terminal,
 65            has_new_content: true,
 66            has_bell: false,
 67            modal,
 68        }
 69    }
 70
 71    pub fn handle(&self) -> ModelHandle<Terminal> {
 72        self.terminal.clone()
 73    }
 74
 75    pub fn has_new_content(&self) -> bool {
 76        self.has_new_content
 77    }
 78
 79    pub fn has_bell(&self) -> bool {
 80        self.has_bell
 81    }
 82
 83    pub fn clear_bel(&mut self, cx: &mut ViewContext<ConnectedView>) {
 84        self.has_bell = false;
 85        cx.emit(Event::Wakeup);
 86    }
 87
 88    fn clear(&mut self, _: &Clear, cx: &mut ViewContext<Self>) {
 89        self.terminal.update(cx, |term, _| term.clear());
 90    }
 91
 92    ///Attempt to paste the clipboard into the terminal
 93    fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
 94        self.terminal.read(cx).copy()
 95    }
 96
 97    ///Attempt to paste the clipboard into the terminal
 98    fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
 99        cx.read_from_clipboard().map(|item| {
100            self.terminal.update(cx, |term, _| term.paste(item.text()));
101        });
102    }
103
104    ///Synthesize the keyboard event corresponding to 'up'
105    fn up(&mut self, _: &Up, cx: &mut ViewContext<Self>) {
106        self.terminal.update(cx, |term, _| {
107            term.try_keystroke(&Keystroke::parse("up").unwrap());
108        });
109    }
110
111    ///Synthesize the keyboard event corresponding to 'down'
112    fn down(&mut self, _: &Down, cx: &mut ViewContext<Self>) {
113        self.terminal.update(cx, |term, _| {
114            term.try_keystroke(&Keystroke::parse("down").unwrap());
115        });
116    }
117
118    ///Synthesize the keyboard event corresponding to 'ctrl-c'
119    fn ctrl_c(&mut self, _: &CtrlC, cx: &mut ViewContext<Self>) {
120        self.terminal.update(cx, |term, _| {
121            term.try_keystroke(&Keystroke::parse("ctrl-c").unwrap());
122        });
123    }
124
125    ///Synthesize the keyboard event corresponding to 'escape'
126    fn escape(&mut self, _: &Escape, cx: &mut ViewContext<Self>) {
127        self.terminal.update(cx, |term, _| {
128            term.try_keystroke(&Keystroke::parse("escape").unwrap());
129        });
130    }
131
132    ///Synthesize the keyboard event corresponding to 'enter'
133    fn enter(&mut self, _: &Enter, cx: &mut ViewContext<Self>) {
134        self.terminal.update(cx, |term, _| {
135            term.try_keystroke(&Keystroke::parse("enter").unwrap());
136        });
137    }
138}
139
140impl View for ConnectedView {
141    fn ui_name() -> &'static str {
142        "Terminal"
143    }
144
145    fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
146        let terminal_handle = self.terminal.clone().downgrade();
147        TerminalEl::new(cx.handle(), terminal_handle, self.modal)
148            .contained()
149            .boxed()
150    }
151
152    fn on_focus(&mut self, _cx: &mut ViewContext<Self>) {
153        self.has_new_content = false;
154    }
155
156    fn selected_text_range(&self, _: &AppContext) -> Option<std::ops::Range<usize>> {
157        Some(0..0)
158    }
159
160    fn replace_text_in_range(
161        &mut self,
162        _: Option<std::ops::Range<usize>>,
163        text: &str,
164        cx: &mut ViewContext<Self>,
165    ) {
166        self.terminal
167            .update(cx, |terminal, _| terminal.write_to_pty(text.into()));
168    }
169}