connection.rs

  1mod events;
  2
  3use alacritty_terminal::{
  4    ansi::{ClearMode, Handler},
  5    config::{Config, PtyConfig},
  6    event::{Event as AlacTermEvent, Notify},
  7    event_loop::{EventLoop, Msg, Notifier},
  8    grid::Scroll,
  9    sync::FairMutex,
 10    term::SizeInfo,
 11    tty::{self, setup_env},
 12    Term,
 13};
 14use futures::{channel::mpsc::unbounded, StreamExt};
 15use settings::Settings;
 16use std::{collections::HashMap, path::PathBuf, sync::Arc};
 17
 18use gpui::{keymap::Keystroke, ClipboardItem, CursorStyle, Entity, ModelContext};
 19
 20use crate::{
 21    color_translation::{get_color_at_index, to_alac_rgb},
 22    ZedListener,
 23};
 24
 25use self::events::to_esc_str;
 26
 27const DEFAULT_TITLE: &str = "Terminal";
 28
 29///Upward flowing events, for changing the title and such
 30#[derive(Copy, Clone, Debug)]
 31pub enum Event {
 32    TitleChanged,
 33    CloseTerminal,
 34    Activate,
 35    Wakeup,
 36    Bell,
 37}
 38
 39pub struct TerminalConnection {
 40    pub pty_tx: Notifier,
 41    pub term: Arc<FairMutex<Term<ZedListener>>>,
 42    pub title: String,
 43    pub associated_directory: Option<PathBuf>,
 44}
 45
 46impl TerminalConnection {
 47    pub fn new(
 48        working_directory: Option<PathBuf>,
 49        initial_size: SizeInfo,
 50        cx: &mut ModelContext<Self>,
 51    ) -> TerminalConnection {
 52        let pty_config = PtyConfig {
 53            shell: None, //Use the users default shell
 54            working_directory: working_directory.clone(),
 55            hold: false,
 56        };
 57
 58        let mut env: HashMap<String, String> = HashMap::new();
 59        //TODO: Properly set the current locale,
 60        env.insert("LC_ALL".to_string(), "en_US.UTF-8".to_string());
 61
 62        let config = Config {
 63            pty_config: pty_config.clone(),
 64            env,
 65            ..Default::default()
 66        };
 67
 68        setup_env(&config);
 69
 70        //Spawn a task so the Alacritty EventLoop can communicate with us in a view context
 71        let (events_tx, mut events_rx) = unbounded();
 72
 73        //Set up the terminal...
 74        let term = Term::new(&config, initial_size, ZedListener(events_tx.clone()));
 75        let term = Arc::new(FairMutex::new(term));
 76
 77        //Setup the pty...
 78        let pty = tty::new(&pty_config, &initial_size, None).expect("Could not create tty");
 79
 80        //And connect them together
 81        let event_loop = EventLoop::new(
 82            term.clone(),
 83            ZedListener(events_tx.clone()),
 84            pty,
 85            pty_config.hold,
 86            false,
 87        );
 88
 89        //Kick things off
 90        let pty_tx = event_loop.channel();
 91        let _io_thread = event_loop.spawn();
 92
 93        cx.spawn_weak(|this, mut cx| async move {
 94            //Listen for terminal events
 95            while let Some(event) = events_rx.next().await {
 96                match this.upgrade(&cx) {
 97                    Some(this) => {
 98                        this.update(&mut cx, |this, cx| {
 99                            this.process_terminal_event(event, cx);
100                            cx.notify();
101                        });
102                    }
103                    None => break,
104                }
105            }
106        })
107        .detach();
108
109        TerminalConnection {
110            pty_tx: Notifier(pty_tx),
111            term,
112            title: DEFAULT_TITLE.to_string(),
113            associated_directory: working_directory,
114        }
115    }
116
117    ///Takes events from Alacritty and translates them to behavior on this view
118    fn process_terminal_event(
119        &mut self,
120        event: alacritty_terminal::event::Event,
121        cx: &mut ModelContext<Self>,
122    ) {
123        match event {
124            // TODO: Handle is_self_focused in subscription on terminal view
125            AlacTermEvent::Wakeup => {
126                cx.emit(Event::Wakeup);
127            }
128            AlacTermEvent::PtyWrite(out) => self.write_to_pty(out),
129            AlacTermEvent::MouseCursorDirty => {
130                //Calculate new cursor style.
131                //TODO: alacritty/src/input.rs:L922-L939
132                //Check on correctly handling mouse events for terminals
133                cx.platform().set_cursor_style(CursorStyle::Arrow); //???
134            }
135            AlacTermEvent::Title(title) => {
136                self.title = title;
137                cx.emit(Event::TitleChanged);
138            }
139            AlacTermEvent::ResetTitle => {
140                self.title = DEFAULT_TITLE.to_string();
141                cx.emit(Event::TitleChanged);
142            }
143            AlacTermEvent::ClipboardStore(_, data) => {
144                cx.write_to_clipboard(ClipboardItem::new(data))
145            }
146            AlacTermEvent::ClipboardLoad(_, format) => self.write_to_pty(format(
147                &cx.read_from_clipboard()
148                    .map(|ci| ci.text().to_string())
149                    .unwrap_or("".to_string()),
150            )),
151            AlacTermEvent::ColorRequest(index, format) => {
152                let color = self.term.lock().colors()[index].unwrap_or_else(|| {
153                    let term_style = &cx.global::<Settings>().theme.terminal;
154                    to_alac_rgb(get_color_at_index(&index, &term_style.colors))
155                });
156                self.write_to_pty(format(color))
157            }
158            AlacTermEvent::CursorBlinkingChange => {
159                //TODO: Set a timer to blink the cursor on and off
160            }
161            AlacTermEvent::Bell => {
162                cx.emit(Event::Bell);
163            }
164            AlacTermEvent::Exit => cx.emit(Event::CloseTerminal),
165        }
166    }
167
168    ///Write the Input payload to the tty. This locks the terminal so we can scroll it.
169    pub fn write_to_pty(&mut self, input: String) {
170        self.write_bytes_to_pty(input.into_bytes());
171    }
172
173    ///Write the Input payload to the tty. This locks the terminal so we can scroll it.
174    fn write_bytes_to_pty(&mut self, input: Vec<u8>) {
175        self.term.lock().scroll_display(Scroll::Bottom);
176        self.pty_tx.notify(input);
177    }
178
179    ///Resize the terminal and the PTY. This locks the terminal.
180    pub fn set_size(&mut self, new_size: SizeInfo) {
181        self.pty_tx.0.send(Msg::Resize(new_size)).ok();
182        self.term.lock().resize(new_size);
183    }
184
185    pub fn clear(&mut self) {
186        self.write_to_pty("\x0c".into());
187        self.term.lock().clear_screen(ClearMode::Saved);
188    }
189
190    pub fn try_keystroke(&mut self, keystroke: &Keystroke) -> bool {
191        let guard = self.term.lock();
192        let mode = guard.mode();
193        let esc = to_esc_str(keystroke, mode);
194        drop(guard);
195        if esc.is_some() {
196            self.write_to_pty(esc.unwrap());
197            true
198        } else {
199            false
200        }
201    }
202}
203
204impl Drop for TerminalConnection {
205    fn drop(&mut self) {
206        self.pty_tx.0.send(Msg::Shutdown).ok();
207    }
208}
209
210impl Entity for TerminalConnection {
211    type Event = Event;
212}