Forgot to commit last night

Mikayla Maki created

Change summary

assets/keymaps/default.json     |  18 +
crates/terminal/src/terminal.rs | 324 ++++++++++++++++++++++------------
crates/theme/src/theme.rs       |   1 
3 files changed, 225 insertions(+), 118 deletions(-)

Detailed changes

assets/keymaps/default.json 🔗

@@ -226,8 +226,7 @@
             "cmd-p": "file_finder::Toggle",
             "cmd-shift-P": "command_palette::Toggle",
             "cmd-shift-M": "diagnostics::Deploy",
-            "cmd-alt-s": "workspace::SaveAll",
-            "shift-cmd-T": "terminal::Deploy"
+            "cmd-alt-s": "workspace::SaveAll"
         }
     },
     // Bindings from Sublime Text
@@ -353,5 +352,20 @@
             "f2": "project_panel::Rename",
             "backspace": "project_panel::Delete"
         }
+    },
+    {
+        "context": "Terminal",
+        "bindings": {
+            "ctrl-c": "terminal::SIGINT",
+            "escape": "terminal::ESCAPE",
+            "ctrl-d": "terminal::Quit",
+            "backspace": "terminal::DEL",
+            "enter": "terminal::RETURN",
+            "left": "terminal::LEFT",
+            "right": "terminal::RIGHT",
+            "up": "terminal::HistoryBack",
+            "down": "terminal::HistoryForward",
+            "tab": "terminal::AutoComplete"
+        }
     }
 ]

crates/terminal/src/terminal.rs 🔗

@@ -1,16 +1,14 @@
 use std::sync::Arc;
 
 use alacritty_terminal::{
-    // ansi::Handler,
     config::{Config, Program, PtyConfig},
     event::{Event, EventListener, Notify},
-    event_loop::{EventLoop, Notifier},
-    grid::{Indexed, Scroll},
+    event_loop::{EventLoop, Msg, Notifier},
+    grid::Indexed,
     index::Point,
     sync::FairMutex,
     term::{cell::Cell, SizeInfo},
-    tty,
-    Term,
+    tty, Term,
 };
 use futures::{
     channel::mpsc::{unbounded, UnboundedSender},
@@ -23,7 +21,7 @@ use gpui::{
     fonts::{with_font_cache, TextStyle},
     geometry::{rect::RectF, vector::vec2f},
     impl_internal_actions,
-    keymap::Keystroke,
+    json::json,
     text_layout::Line,
     Entity,
     Event::KeyDown,
@@ -35,36 +33,52 @@ use smallvec::SmallVec;
 use workspace::{Item, Workspace};
 
 //ASCII Control characters on a keyboard
-const BACKSPACE: char = 8_u8 as char;
-const TAB: char = 9_u8 as char;
-const CARRIAGE_RETURN: char = 13_u8 as char;
-const ESC: char = 27_u8 as char;
-const DEL: char = 127_u8 as char;
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-enum Direction {
-    LEFT,
-    RIGHT,
-}
+//Consts -> Structs -> Impls -> Functions, Vaguely in order of importance
+const ETX_CHAR: char = 3_u8 as char; //'End of text', the control code for 'ctrl-c'
+const TAB_CHAR: char = 9_u8 as char;
+const CARRIAGE_RETURN_CHAR: char = 13_u8 as char;
+const ESC_CHAR: char = 27_u8 as char;
+const DEL_CHAR: char = 127_u8 as char;
+const LEFT_SEQ: &str = "\x1b[D";
+const RIGHT_SEQ: &str = "\x1b[C";
+const UP_SEQ: &str = "\x1b[A";
+const DOWN_SEQ: &str = "\x1b[B";
+const DEFAULT_TITLE: &str = "Terminal";
 
-impl Default for Direction {
-    fn default() -> Self {
-        Direction::LEFT
-    }
-}
-
-#[derive(Clone, Default, Debug, PartialEq, Eq)]
-struct KeyInput(char);
 #[derive(Clone, Default, Debug, PartialEq, Eq)]
-struct DirectionInput(Direction);
-
-actions!(terminal, [Deploy]);
-impl_internal_actions!(terminal, [KeyInput, DirectionInput]);
+struct Input(String);
+
+actions!(
+    terminal,
+    [
+        Deploy,
+        SIGINT,
+        ESCAPE,
+        Quit,
+        DEL,
+        RETURN,
+        LEFT,
+        RIGHT,
+        HistoryBack,
+        HistoryForward,
+        AutoComplete
+    ]
+);
+impl_internal_actions!(terminal, [Input]);
 
 pub fn init(cx: &mut MutableAppContext) {
-    cx.add_action(TerminalView::deploy);
-    cx.add_action(TerminalView::write_key_to_pty);
-    cx.add_action(TerminalView::move_cursor);
+    cx.add_action(Terminal::deploy);
+    cx.add_action(Terminal::write_to_pty);
+    cx.add_action(Terminal::send_sigint); //TODO figure out how to do this properly
+    cx.add_action(Terminal::escape);
+    cx.add_action(Terminal::quit);
+    cx.add_action(Terminal::del);
+    cx.add_action(Terminal::carriage_return);
+    cx.add_action(Terminal::left);
+    cx.add_action(Terminal::right);
+    cx.add_action(Terminal::history_back);
+    cx.add_action(Terminal::history_forward);
+    cx.add_action(Terminal::autocomplete);
 }
 
 #[derive(Clone)]
@@ -76,46 +90,59 @@ impl EventListener for ZedListener {
     }
 }
 
-struct TerminalView {
+struct Terminal {
     pty_tx: Notifier,
     term: Arc<FairMutex<Term<ZedListener>>>,
     title: String,
 }
 
-impl Entity for TerminalView {
+impl Entity for Terminal {
     type Event = ();
 }
 
-impl TerminalView {
+impl Terminal {
     fn new(cx: &mut ViewContext<Self>) -> Self {
+        //Spawn a task so the Alacritty EventLoop to communicate with us
         let (events_tx, mut events_rx) = unbounded();
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn_weak(|this, mut cx| async move {
             while let Some(event) = events_rx.next().await {
-                this.update(&mut cx, |this, cx| {
-                    this.process_terminal_event(event, cx);
-                    cx.notify();
-                });
+                match this.upgrade(&cx) {
+                    Some(handle) => {
+                        handle.update(&mut cx, |this, cx| {
+                            this.process_terminal_event(event, cx);
+                            cx.notify();
+                        });
+                    }
+                    None => break,
+                }
             }
         })
         .detach();
 
+        //TODO: Load from settings
         let pty_config = PtyConfig {
             shell: Some(Program::Just("zsh".to_string())),
             working_directory: None,
             hold: false,
         };
 
+        //TODO: Properly configure this
         let config = Config {
             pty_config: pty_config.clone(),
             ..Default::default()
         };
+
+        //TODO: derive this
         let size_info = SizeInfo::new(400., 100.0, 5., 5., 0., 0., false);
 
+        //Set up the terminal...
         let term = Term::new(&config, size_info, ZedListener(events_tx.clone()));
         let term = Arc::new(FairMutex::new(term));
 
+        //Setup the pty...
         let pty = tty::new(&pty_config, &size_info, None).expect("Could not create tty");
 
+        //And connect them together
         let event_loop = EventLoop::new(
             term.clone(),
             ZedListener(events_tx.clone()),
@@ -124,18 +151,18 @@ impl TerminalView {
             false,
         );
 
+        //Kick things off
         let pty_tx = Notifier(event_loop.channel());
-        let _io_thread = event_loop.spawn(); //todo cleanup
-
-        TerminalView {
-            title: "Terminal".to_string(),
+        let _io_thread = event_loop.spawn();
+        Terminal {
+            title: DEFAULT_TITLE.to_string(),
             term,
             pty_tx,
         }
     }
 
     fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
-        workspace.add_item(Box::new(cx.add_view(|cx| TerminalView::new(cx))), cx);
+        workspace.add_item(Box::new(cx.add_view(|cx| Terminal::new(cx))), cx);
     }
 
     fn process_terminal_event(
@@ -144,35 +171,94 @@ impl TerminalView {
         cx: &mut ViewContext<Self>,
     ) {
         match event {
-            alacritty_terminal::event::Event::Wakeup => cx.notify(),
-            alacritty_terminal::event::Event::PtyWrite(out) => self.pty_tx.notify(out.into_bytes()),
-            _ => {}
+            Event::Wakeup => cx.notify(),
+            Event::PtyWrite(out) => self.write_to_pty(&Input(out), cx),
+            Event::MouseCursorDirty => todo!(), //I think this is outside of Zed's loop
+            Event::Title(title) => self.title = title,
+            Event::ResetTitle => self.title = DEFAULT_TITLE.to_string(),
+            Event::ClipboardStore(_, _) => todo!(),
+            Event::ClipboardLoad(_, _) => todo!(),
+            Event::ColorRequest(_, _) => todo!(),
+            Event::CursorBlinkingChange => todo!(),
+            Event::Bell => todo!(),
+            Event::Exit => todo!(),
+            Event::MouseCursorDirty => todo!(),
         }
         //
     }
 
-    fn write_key_to_pty(&mut self, action: &KeyInput, cx: &mut ViewContext<Self>) {
-        let mut bytes = vec![0; action.0.len_utf8()];
-        action.0.encode_utf8(&mut bytes[..]);
-        self.pty_tx.notify(bytes);
+    fn shutdown_pty(&mut self) {
+        self.pty_tx.0.send(Msg::Shutdown).ok();
     }
 
-    fn move_cursor(&mut self, action: &DirectionInput, cx: &mut ViewContext<Self>) {
-        let term = self.term.lock();
-        match action.0 {
-            Direction::LEFT => {
-                self.pty_tx.notify("\x1b[C".to_string().into_bytes());
-            }
-            Direction::RIGHT => {
-                self.pty_tx.notify("\x1b[D".to_string().into_bytes());
-            }
-        }
+    fn history_back(&mut self, _: &HistoryBack, cx: &mut ViewContext<Self>) {
+        self.write_to_pty(&Input(UP_SEQ.to_string()), cx);
+
+        //Noop.. for now...
+        //This might just need to be forwarded to the terminal?
+        //Behavior changes based on mode...
+    }
+
+    fn history_forward(&mut self, _: &HistoryForward, cx: &mut ViewContext<Self>) {
+        self.write_to_pty(&Input(DOWN_SEQ.to_string()), cx);
+        //Noop.. for now...
+        //This might just need to be forwarded to the terminal by the pty?
+        //Behvaior changes based on mode
+    }
+
+    fn autocomplete(&mut self, _: &AutoComplete, cx: &mut ViewContext<Self>) {
+        self.write_to_pty(&Input(TAB_CHAR.to_string()), cx);
+        //Noop.. for now...
+        //This might just need to be forwarded to the terminal by the pty?
+        //Behvaior changes based on mode
+    }
+
+    fn write_to_pty(&mut self, input: &Input, _cx: &mut ViewContext<Self>) {
+        self.pty_tx.notify(input.0.clone().into_bytes());
+    }
+
+    fn send_sigint(&mut self, _: &SIGINT, cx: &mut ViewContext<Self>) {
+        self.write_to_pty(&Input(ETX_CHAR.to_string()), cx);
+    }
+
+    fn escape(&mut self, _: &ESCAPE, cx: &mut ViewContext<Self>) {
+        self.write_to_pty(&Input(ESC_CHAR.to_string()), cx);
+    }
+
+    fn del(&mut self, _: &DEL, cx: &mut ViewContext<Self>) {
+        self.write_to_pty(&Input(DEL_CHAR.to_string()), cx);
+    }
+
+    fn carriage_return(&mut self, _: &RETURN, cx: &mut ViewContext<Self>) {
+        self.write_to_pty(&Input(CARRIAGE_RETURN_CHAR.to_string()), cx);
+    }
+
+    fn left(&mut self, _: &LEFT, cx: &mut ViewContext<Self>) {
+        self.write_to_pty(&Input(LEFT_SEQ.to_string()), cx);
+    }
+
+    fn right(&mut self, _: &RIGHT, cx: &mut ViewContext<Self>) {
+        self.write_to_pty(&Input(RIGHT_SEQ.to_string()), cx);
+    }
+
+    fn quit(&mut self, _: &Quit, _cx: &mut ViewContext<Self>) {
+        //TODO
+        // cx.dispatch_action(cx.window_id(), workspace::CloseItem());
+    }
+
+    // ShowHistory,
+    // AutoComplete
+}
+
+impl Drop for Terminal {
+    fn drop(&mut self) {
+        self.shutdown_pty();
     }
 }
 
-impl View for TerminalView {
+impl View for Terminal {
     fn ui_name() -> &'static str {
-        "TerminalView"
+        "Terminal"
     }
 
     fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
@@ -198,6 +284,7 @@ impl TerminalEl {
 struct LayoutState {
     lines: Vec<Line>,
     line_height: f32,
+    cursor: RectF,
 }
 
 impl Element for TerminalEl {
@@ -209,9 +296,55 @@ impl Element for TerminalEl {
         constraint: gpui::SizeConstraint,
         cx: &mut gpui::LayoutContext,
     ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
-        let term = self.term.lock();
+        let size = constraint.max;
+        //Get terminal content
+        let mut term = self.term.lock();
+
+        //Set up text rendering
+
+        let text_style = with_font_cache(cx.font_cache.clone(), || TextStyle {
+            color: Color::white(),
+            ..Default::default()
+        });
+        let line_height = cx.font_cache.line_height(text_style.font_size);
+        let em_width = cx
+            .font_cache()
+            .em_width(text_style.font_id, text_style.font_size);
+
+        term.resize(SizeInfo::new(
+            size.x(),
+            size.y(),
+            em_width,
+            line_height,
+            0.,
+            0.,
+            false,
+        ));
+
         let content = term.renderable_content();
 
+        // //Dual owned system from Neovide
+        // let mut block_width = cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x;
+        // if block_width == 0.0 {
+        //     block_width = layout.em_width;
+        // }
+        let cursor = RectF::new(
+            vec2f(
+                content.cursor.point.column.0 as f32 * em_width,
+                content.cursor.point.line.0 as f32 * line_height,
+            ),
+            vec2f(em_width, line_height),
+        );
+
+        // let cursor = Cursor {
+        //     color: selection_style.cursor,
+        //     block_width,
+        //     origin: content_origin + vec2f(x, y),
+        //     line_height: layout.line_height,
+        //     shape: self.cursor_shape,
+        //     block_text,
+        // }
+
         let mut lines = vec![];
         let mut cur_line = vec![];
         let mut last_line = 0;
@@ -235,11 +368,6 @@ impl Element for TerminalEl {
 
         let chunks = vec![(&line[..], None)].into_iter();
 
-        let text_style = with_font_cache(cx.font_cache.clone(), || TextStyle {
-            color: Color::white(),
-            ..Default::default()
-        });
-
         let shaped_lines = layout_highlighted_chunks(
             chunks,
             &text_style,
@@ -248,13 +376,13 @@ impl Element for TerminalEl {
             usize::MAX,
             line.matches('\n').count() + 1,
         );
-        let line_height = cx.font_cache.line_height(text_style.font_size);
 
         (
             constraint.max,
             LayoutState {
                 lines: shaped_lines,
                 line_height,
+                cursor,
             },
         )
     }
@@ -278,19 +406,11 @@ impl Element for TerminalEl {
             origin.set_y(boundaries.max_y());
         }
 
-        let term = self.term.lock();
-        let cursor = term.renderable_content().cursor;
-
-        let bounds = RectF::new(
-            vec2f(
-                cursor.point.column.0 as f32 * 10.0 + 150.0,
-                cursor.point.line.0 as f32 * 10.0 + 150.0,
-            ),
-            vec2f(10.0, 10.0),
-        );
+        let new_origin = bounds.origin() + layout.cursor.origin();
+        let new_cursor = RectF::new(new_origin, layout.cursor.size());
 
         cx.scene.push_quad(Quad {
-            bounds,
+            bounds: new_cursor,
             background: Some(Color::red()),
             border: Default::default(),
             corner_radius: 0.,
@@ -310,38 +430,8 @@ impl Element for TerminalEl {
             KeyDown {
                 input: Some(input), ..
             } => {
-                dbg!(event);
-                cx.dispatch_action(KeyInput(input.chars().next().unwrap()));
+                cx.dispatch_action(Input(input.to_string()));
                 true
-            } //TODO: Write control characters (ctrl-c) to pty
-            KeyDown {
-                keystroke: Keystroke { key, .. },
-                input: None,
-                ..
-            } => {
-                dbg!(event);
-                if key == "backspace" {
-                    cx.dispatch_action(KeyInput(DEL));
-                    true
-                } else if key == "enter" {
-                    //There may be some subtlety here in how our terminal works
-                    cx.dispatch_action(KeyInput(CARRIAGE_RETURN));
-                    true
-                } else if key == "tab" {
-                    cx.dispatch_action(KeyInput(TAB));
-                    true
-                } else if key == "left" {
-                    cx.dispatch_action(DirectionInput(Direction::LEFT));
-                    true
-                } else if key == "right" {
-                    cx.dispatch_action(DirectionInput(Direction::RIGHT));
-                    true
-                // } else if key == "escape" { //TODO
-                //     cx.dispatch_action(KeyInput(ESC));
-                //     true
-                } else {
-                    false
-                }
             }
             _ => false,
         }
@@ -354,11 +444,13 @@ impl Element for TerminalEl {
         _paint: &Self::PaintState,
         _cx: &gpui::DebugContext,
     ) -> gpui::serde_json::Value {
-        unreachable!("Should never be called hopefully")
+        json!({
+            "type": "TerminalElement",
+        })
     }
 }
 
-impl Item for TerminalView {
+impl Item for Terminal {
     fn tab_content(&self, style: &theme::Tab, cx: &gpui::AppContext) -> ElementBox {
         let settings = cx.global::<Settings>();
         let search_theme = &settings.theme.search;
@@ -378,7 +470,7 @@ impl Item for TerminalView {
     }
 
     fn project_entry_ids(&self, _cx: &gpui::AppContext) -> SmallVec<[project::ProjectEntryId; 3]> {
-        todo!()
+        SmallVec::new()
     }
 
     fn is_singleton(&self, _cx: &gpui::AppContext) -> bool {

crates/theme/src/theme.rs 🔗

@@ -33,6 +33,7 @@ pub struct Theme {
     pub contact_notification: ContactNotification,
     pub update_notification: UpdateNotification,
     pub tooltip: TooltipStyle,
+    // pub terminal: Terminal,
 }
 
 #[derive(Deserialize, Default)]