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}