connection.rs

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