Added focus-in and focus-out behavior to terminal

Mikayla Maki created

Change summary

crates/terminal/src/connected_el.rs   | 13 +++++++++++--
crates/terminal/src/connected_view.rs | 17 +++++++++++++++--
crates/terminal/src/terminal.rs       | 19 ++++++++++++-------
3 files changed, 38 insertions(+), 11 deletions(-)

Detailed changes

crates/terminal/src/connected_el.rs 🔗

@@ -200,6 +200,7 @@ pub struct TerminalEl {
     terminal: WeakModelHandle<Terminal>,
     view: WeakViewHandle<ConnectedView>,
     modal: bool,
+    focused: bool,
 }
 
 impl TerminalEl {
@@ -207,11 +208,13 @@ impl TerminalEl {
         view: WeakViewHandle<ConnectedView>,
         terminal: WeakModelHandle<Terminal>,
         modal: bool,
+        focused: bool,
     ) -> TerminalEl {
         TerminalEl {
             view,
             terminal,
             modal,
+            focused,
         }
     }
 
@@ -660,12 +663,18 @@ impl Element for TerminalEl {
 
             TerminalEl::shape_cursor(cursor_point, dimensions, &cursor_text).map(
                 move |(cursor_position, block_width)| {
+                    let (shape, color) = if self.focused {
+                        (CursorShape::Block, terminal_theme.colors.cursor)
+                    } else {
+                        (CursorShape::Underscore, terminal_theme.colors.foreground)
+                    };
+
                     Cursor::new(
                         cursor_position,
                         block_width,
                         dimensions.line_height,
-                        terminal_theme.colors.cursor,
-                        CursorShape::Block,
+                        color,
+                        shape,
                         Some(cursor_text),
                     )
                 },

crates/terminal/src/connected_view.rs 🔗

@@ -181,9 +181,15 @@ impl View for ConnectedView {
     fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
         let terminal_handle = self.terminal.clone().downgrade();
 
+        let self_id = cx.view_id();
+        let focused = cx
+            .focused_view_id(cx.window_id())
+            .filter(|view_id| *view_id == self_id)
+            .is_some();
+
         Stack::new()
             .with_child(
-                TerminalEl::new(cx.handle(), terminal_handle, self.modal)
+                TerminalEl::new(cx.handle(), terminal_handle, self.modal, focused)
                     .contained()
                     .boxed(),
             )
@@ -191,8 +197,15 @@ impl View for ConnectedView {
             .boxed()
     }
 
-    fn on_focus_in(&mut self, _: AnyViewHandle, _cx: &mut ViewContext<Self>) {
+    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
         self.has_new_content = false;
+        self.terminal.read(cx).focus_in();
+        cx.notify();
+    }
+
+    fn on_focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+        self.terminal.read(cx).focus_out();
+        cx.notify();
     }
 
     fn selected_text_range(&self, cx: &AppContext) -> Option<std::ops::Range<usize>> {

crates/terminal/src/terminal.rs 🔗

@@ -601,18 +601,23 @@ impl Terminal {
         f(content, cursor_text)
     }
 
-    // fn estimate_utilization(last_processed: usize) -> f32 {
-    //     let buffer_utilization = (last_processed as f32 / (READ_BUFFER_SIZE as f32)).clamp(0., 1.);
-
-    //     //Scale result to bias low, then high
-    //     buffer_utilization * buffer_utilization
-    // }
-
     ///Scroll the terminal
     pub fn scroll(&mut self, scroll: Scroll) {
         self.events.push(InternalEvent::Scroll(scroll));
     }
 
+    pub fn focus_in(&self) {
+        if self.last_mode.contains(TermMode::FOCUS_IN_OUT) {
+            self.notify_pty("\x1b[I".to_string());
+        }
+    }
+
+    pub fn focus_out(&self) {
+        if self.last_mode.contains(TermMode::FOCUS_IN_OUT) {
+            self.notify_pty("\x1b[O".to_string());
+        }
+    }
+
     pub fn click(&mut self, point: Point, side: Direction, clicks: usize) {
         let selection_type = match clicks {
             0 => return, //This is a release