1use alacritty_terminal::term::TermMode;
2use context_menu::{ContextMenu, ContextMenuItem};
3use gpui::{
4 actions,
5 elements::{ChildView, ParentElement, Stack},
6 geometry::vector::Vector2F,
7 impl_internal_actions,
8 keymap::Keystroke,
9 AppContext, Element, ElementBox, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle,
10};
11use workspace::pane;
12
13use crate::{connected_el::TerminalEl, Event, Terminal};
14
15///Event to transmit the scroll from the element to the view
16#[derive(Clone, Debug, PartialEq)]
17pub struct ScrollTerminal(pub i32);
18
19#[derive(Clone, PartialEq)]
20pub struct DeployContextMenu {
21 pub position: Vector2F,
22}
23
24actions!(
25 terminal,
26 [Up, Down, CtrlC, Escape, Enter, Clear, Copy, Paste,]
27);
28impl_internal_actions!(project_panel, [DeployContextMenu]);
29
30pub fn init(cx: &mut MutableAppContext) {
31 //Global binding overrrides
32 cx.add_action(ConnectedView::ctrl_c);
33 cx.add_action(ConnectedView::up);
34 cx.add_action(ConnectedView::down);
35 cx.add_action(ConnectedView::escape);
36 cx.add_action(ConnectedView::enter);
37 //Useful terminal views
38 cx.add_action(ConnectedView::deploy_context_menu);
39 cx.add_action(ConnectedView::copy);
40 cx.add_action(ConnectedView::paste);
41 cx.add_action(ConnectedView::clear);
42}
43
44///A terminal view, maintains the PTY's file handles and communicates with the terminal
45pub struct ConnectedView {
46 terminal: ModelHandle<Terminal>,
47 has_new_content: bool,
48 //Currently using iTerm bell, show bell emoji in tab until input is received
49 has_bell: bool,
50 // Only for styling purposes. Doesn't effect behavior
51 modal: bool,
52 context_menu: ViewHandle<ContextMenu>,
53}
54
55impl ConnectedView {
56 pub fn from_terminal(
57 terminal: ModelHandle<Terminal>,
58 modal: bool,
59 cx: &mut ViewContext<Self>,
60 ) -> Self {
61 cx.observe(&terminal, |_, _, cx| cx.notify()).detach();
62 cx.subscribe(&terminal, |this, _, event, cx| match event {
63 Event::Wakeup => {
64 if !cx.is_self_focused() {
65 this.has_new_content = true;
66 cx.notify();
67 cx.emit(Event::Wakeup);
68 }
69 }
70 Event::Bell => {
71 this.has_bell = true;
72 cx.emit(Event::Wakeup);
73 }
74
75 _ => cx.emit(*event),
76 })
77 .detach();
78
79 Self {
80 terminal,
81 has_new_content: true,
82 has_bell: false,
83 modal,
84 context_menu: cx.add_view(|cx| ContextMenu::new(cx)),
85 }
86 }
87
88 pub fn handle(&self) -> ModelHandle<Terminal> {
89 self.terminal.clone()
90 }
91
92 pub fn has_new_content(&self) -> bool {
93 self.has_new_content
94 }
95
96 pub fn has_bell(&self) -> bool {
97 self.has_bell
98 }
99
100 pub fn clear_bel(&mut self, cx: &mut ViewContext<ConnectedView>) {
101 self.has_bell = false;
102 cx.emit(Event::Wakeup);
103 }
104
105 pub fn deploy_context_menu(&mut self, action: &DeployContextMenu, cx: &mut ViewContext<Self>) {
106 let menu_entries = vec![
107 ContextMenuItem::item("Clear Buffer", Clear),
108 ContextMenuItem::item("Close Terminal", pane::CloseActiveItem),
109 ];
110
111 self.context_menu
112 .update(cx, |menu, cx| menu.show(action.position, menu_entries, cx));
113
114 cx.notify();
115 }
116
117 fn clear(&mut self, _: &Clear, cx: &mut ViewContext<Self>) {
118 self.terminal.update(cx, |term, _| term.clear());
119 }
120
121 ///Attempt to paste the clipboard into the terminal
122 fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
123 self.terminal.update(cx, |term, _| term.copy())
124 }
125
126 ///Attempt to paste the clipboard into the terminal
127 fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
128 cx.read_from_clipboard().map(|item| {
129 self.terminal.read(cx).paste(item.text());
130 });
131 }
132
133 ///Synthesize the keyboard event corresponding to 'up'
134 fn up(&mut self, _: &Up, cx: &mut ViewContext<Self>) {
135 self.clear_bel(cx);
136 self.terminal
137 .read(cx)
138 .try_keystroke(&Keystroke::parse("up").unwrap());
139 }
140
141 ///Synthesize the keyboard event corresponding to 'down'
142 fn down(&mut self, _: &Down, cx: &mut ViewContext<Self>) {
143 self.clear_bel(cx);
144 self.terminal
145 .read(cx)
146 .try_keystroke(&Keystroke::parse("down").unwrap());
147 }
148
149 ///Synthesize the keyboard event corresponding to 'ctrl-c'
150 fn ctrl_c(&mut self, _: &CtrlC, cx: &mut ViewContext<Self>) {
151 self.clear_bel(cx);
152 self.terminal
153 .read(cx)
154 .try_keystroke(&Keystroke::parse("ctrl-c").unwrap());
155 }
156
157 ///Synthesize the keyboard event corresponding to 'escape'
158 fn escape(&mut self, _: &Escape, cx: &mut ViewContext<Self>) {
159 self.clear_bel(cx);
160 self.terminal
161 .read(cx)
162 .try_keystroke(&Keystroke::parse("escape").unwrap());
163 }
164
165 ///Synthesize the keyboard event corresponding to 'enter'
166 fn enter(&mut self, _: &Enter, cx: &mut ViewContext<Self>) {
167 self.clear_bel(cx);
168 self.terminal
169 .read(cx)
170 .try_keystroke(&Keystroke::parse("enter").unwrap());
171 }
172}
173
174impl View for ConnectedView {
175 fn ui_name() -> &'static str {
176 "Terminal"
177 }
178
179 fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
180 let terminal_handle = self.terminal.clone().downgrade();
181
182 Stack::new()
183 .with_child(
184 TerminalEl::new(cx.handle(), terminal_handle, self.modal)
185 .contained()
186 .boxed(),
187 )
188 .with_child(ChildView::new(&self.context_menu).boxed())
189 .boxed()
190 }
191
192 fn on_focus(&mut self, _cx: &mut ViewContext<Self>) {
193 self.has_new_content = false;
194 }
195
196 fn selected_text_range(&self, cx: &AppContext) -> Option<std::ops::Range<usize>> {
197 if self
198 .terminal
199 .read(cx)
200 .last_mode
201 .contains(TermMode::ALT_SCREEN)
202 {
203 None
204 } else {
205 Some(0..0)
206 }
207 }
208
209 fn replace_text_in_range(
210 &mut self,
211 _: Option<std::ops::Range<usize>>,
212 text: &str,
213 cx: &mut ViewContext<Self>,
214 ) {
215 self.terminal
216 .update(cx, |terminal, _| terminal.write_to_pty(text.into()));
217 }
218
219 fn keymap_context(&self, _: &gpui::AppContext) -> gpui::keymap::Context {
220 let mut context = Self::default_keymap_context();
221 if self.modal {
222 context.set.insert("ModalTerminal".into());
223 }
224 context
225 }
226}