Basic cursor blinking :)

Mikayla Maki created

Change summary

crates/terminal/src/connected_el.rs   | 94 +++++++++++++++++-----------
crates/terminal/src/connected_view.rs | 66 ++++++++++++++++++++
crates/terminal/src/terminal.rs       |  1 
3 files changed, 123 insertions(+), 38 deletions(-)

Detailed changes

crates/terminal/src/connected_el.rs 🔗

@@ -643,49 +643,64 @@ impl Element for TerminalEl {
         );
 
         //Layout cursor
-        let cursor = {
-            let cursor_point = DisplayCursor::from(cursor.point, display_offset);
-            let cursor_text = {
-                let str_trxt = cursor_text.to_string();
-
-                let color = if self.focused {
-                    terminal_theme.colors.background
-                } else {
-                    terminal_theme.colors.foreground
-                };
+        //TODO: This logic can be a lot better
+        let show_cursor = if let Some(view_handle) = self.view.upgrade(cx) {
+            if view_handle.read(cx).show_cursor() {
+                false
+            } else {
+                true
+            }
+        } else {
+            true
+        };
 
-                cx.text_layout_cache.layout_str(
-                    &str_trxt,
-                    text_style.font_size,
-                    &[(
-                        str_trxt.len(),
-                        RunStyle {
-                            font_id: text_style.font_id,
-                            color,
-                            underline: Default::default(),
-                        },
-                    )],
-                )
-            };
+        let cursor = {
+            if show_cursor {
+                None
+            } else {
+                let cursor_point = DisplayCursor::from(cursor.point, display_offset);
+                let cursor_text = {
+                    let str_trxt = cursor_text.to_string();
 
-            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)
+                    let color = if self.focused {
+                        terminal_theme.colors.background
                     } else {
-                        (CursorShape::Underscore, terminal_theme.colors.foreground)
+                        terminal_theme.colors.foreground
                     };
 
-                    Cursor::new(
-                        cursor_position,
-                        block_width,
-                        dimensions.line_height,
-                        color,
-                        shape,
-                        Some(cursor_text),
+                    cx.text_layout_cache.layout_str(
+                        &str_trxt,
+                        text_style.font_size,
+                        &[(
+                            str_trxt.len(),
+                            RunStyle {
+                                font_id: text_style.font_id,
+                                color,
+                                underline: Default::default(),
+                            },
+                        )],
                     )
-                },
-            )
+                };
+
+                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,
+                            color,
+                            shape,
+                            Some(cursor_text),
+                        )
+                    },
+                )
+            }
         };
 
         //Done!
@@ -818,7 +833,10 @@ impl Element for TerminalEl {
 
                 //TODO Talk to keith about how to catch events emitted from an element.
                 if let Some(view) = self.view.upgrade(cx.app) {
-                    view.update(cx.app, |view, cx| view.clear_bel(cx))
+                    view.update(cx.app, |view, cx| {
+                        view.clear_bel(cx);
+                        view.pause_cursor_blinking(cx);
+                    })
                 }
 
                 self.terminal

crates/terminal/src/connected_view.rs 🔗

@@ -1,3 +1,5 @@
+use std::time::Duration;
+
 use alacritty_terminal::term::TermMode;
 use context_menu::{ContextMenu, ContextMenuItem};
 use gpui::{
@@ -9,10 +11,13 @@ use gpui::{
     AnyViewHandle, AppContext, Element, ElementBox, ModelHandle, MutableAppContext, View,
     ViewContext, ViewHandle,
 };
+use smol::Timer;
 use workspace::pane;
 
 use crate::{connected_el::TerminalEl, Event, Terminal};
 
+const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
+
 ///Event to transmit the scroll from the element to the view
 #[derive(Clone, Debug, PartialEq)]
 pub struct ScrollTerminal(pub i32);
@@ -51,6 +56,9 @@ pub struct ConnectedView {
     // Only for styling purposes. Doesn't effect behavior
     modal: bool,
     context_menu: ViewHandle<ContextMenu>,
+    show_cursor: bool,
+    blinking_paused: bool,
+    blink_epoch: usize,
 }
 
 impl ConnectedView {
@@ -83,6 +91,9 @@ impl ConnectedView {
             has_bell: false,
             modal,
             context_menu: cx.add_view(ContextMenu::new),
+            show_cursor: true,
+            blinking_paused: false,
+            blink_epoch: 0,
         }
     }
 
@@ -120,6 +131,59 @@ impl ConnectedView {
         cx.notify();
     }
 
+    //Following code copied from editor cursor
+    pub fn show_cursor(&self) -> bool {
+        self.blinking_paused || self.show_cursor
+    }
+
+    fn blink_cursors(&mut self, epoch: usize, cx: &mut ViewContext<Self>) {
+        if epoch == self.blink_epoch && !self.blinking_paused {
+            self.show_cursor = !self.show_cursor;
+            cx.notify();
+
+            let epoch = self.next_blink_epoch();
+            cx.spawn(|this, mut cx| {
+                let this = this.downgrade();
+                async move {
+                    Timer::after(CURSOR_BLINK_INTERVAL).await;
+                    if let Some(this) = this.upgrade(&cx) {
+                        this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx));
+                    }
+                }
+            })
+            .detach();
+        }
+    }
+
+    pub fn pause_cursor_blinking(&mut self, cx: &mut ViewContext<Self>) {
+        self.show_cursor = true;
+        cx.notify();
+
+        let epoch = self.next_blink_epoch();
+        cx.spawn(|this, mut cx| {
+            let this = this.downgrade();
+            async move {
+                Timer::after(CURSOR_BLINK_INTERVAL).await;
+                if let Some(this) = this.upgrade(&cx) {
+                    this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx))
+                }
+            }
+        })
+        .detach();
+    }
+
+    fn next_blink_epoch(&mut self) -> usize {
+        self.blink_epoch += 1;
+        self.blink_epoch
+    }
+
+    fn resume_cursor_blinking(&mut self, epoch: usize, cx: &mut ViewContext<Self>) {
+        if epoch == self.blink_epoch {
+            self.blinking_paused = false;
+            self.blink_cursors(epoch, cx);
+        }
+    }
+
     ///Attempt to paste the clipboard into the terminal
     fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
         self.terminal.update(cx, |term, _| term.copy())
@@ -200,6 +264,7 @@ impl View for ConnectedView {
     fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
         self.has_new_content = false;
         self.terminal.read(cx).focus_in();
+        self.blink_cursors(self.blink_epoch, cx);
         cx.notify();
     }
 
@@ -208,6 +273,7 @@ impl View for ConnectedView {
         cx.notify();
     }
 
+    //IME stuff
     fn selected_text_range(&self, cx: &AppContext) -> Option<std::ops::Range<usize>> {
         if self
             .terminal

crates/terminal/src/terminal.rs 🔗

@@ -287,6 +287,7 @@ impl TerminalBuilder {
         setup_env(&config);
 
         //Spawn a task so the Alacritty EventLoop can communicate with us in a view context
+        //TODO: Remove with a bounded sender which can be dispatched on &self
         let (events_tx, events_rx) = unbounded();
         //Set up the terminal...
         let term = Term::new(&config, &initial_size, ZedListener(events_tx.clone()));