Added cursor blink and settings

Mikayla Maki created

Change summary

assets/settings/default.json          | 13 ++++++
crates/settings/src/settings.rs       | 16 ++++++++
crates/terminal/src/connected_el.rs   | 56 +++++++++++++++++++++-------
crates/terminal/src/connected_view.rs | 14 +++++--
crates/terminal/src/terminal.rs       | 22 ++++++++--
crates/terminal/src/terminal_view.rs  |  9 +++-
6 files changed, 104 insertions(+), 26 deletions(-)

Detailed changes

assets/settings/default.json 🔗

@@ -102,6 +102,19 @@
         //
         //
         "working_directory": "current_project_directory",
+        //Set the cursor blinking behavior in the terminal.
+        //May take 4 values:
+        // 1. Never blink the cursor, ignoring the terminal mode
+        //        "blinking": "never",
+        // 2. Default the cursor blink to off, but allow the terminal to 
+        //    turn blinking on
+        //        "blinking": "off",
+        // 3. Default the cursor blink to on, but allow the terminal to 
+        //    turn blinking off
+        //        "blinking": "on",
+        // 4. Always blink the cursor, ignoring the terminal mode
+        //        "blinking": "always",
+        "blinking": "on",
         //Any key-value pairs added to this list will be added to the terminal's
         //enviroment. Use `:` to seperate multiple values.
         "env": {

crates/settings/src/settings.rs 🔗

@@ -83,6 +83,22 @@ pub struct TerminalSettings {
     pub font_size: Option<f32>,
     pub font_family: Option<String>,
     pub env: Option<HashMap<String, String>>,
+    pub blinking: Option<TerminalBlink>,
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum TerminalBlink {
+    Never,
+    On,
+    Off,
+    Always,
+}
+
+impl Default for TerminalBlink {
+    fn default() -> Self {
+        TerminalBlink::On
+    }
 }
 
 #[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)]

crates/terminal/src/connected_el.rs 🔗

@@ -21,7 +21,7 @@ use gpui::{
 };
 use itertools::Itertools;
 use ordered_float::OrderedFloat;
-use settings::Settings;
+use settings::{Settings, TerminalBlink};
 use theme::TerminalStyle;
 use util::ResultExt;
 
@@ -201,6 +201,7 @@ pub struct TerminalEl {
     view: WeakViewHandle<ConnectedView>,
     modal: bool,
     focused: bool,
+    blink_state: bool,
 }
 
 impl TerminalEl {
@@ -209,12 +210,14 @@ impl TerminalEl {
         terminal: WeakModelHandle<Terminal>,
         modal: bool,
         focused: bool,
+        blink_state: bool,
     ) -> TerminalEl {
         TerminalEl {
             view,
             terminal,
             modal,
             focused,
+            blink_state,
         }
     }
 
@@ -568,6 +571,33 @@ impl TerminalEl {
 
         (point, side)
     }
+
+    pub fn should_show_cursor(
+        settings: Option<TerminalBlink>,
+        blinking_on: bool,
+        focused: bool,
+        blink_show: bool,
+    ) -> bool {
+        if !focused {
+            true
+        } else {
+            match settings {
+                Some(setting) => match setting {
+                TerminalBlink::Never => true,
+                TerminalBlink::On | TerminalBlink::Off if blinking_on => blink_show,
+                TerminalBlink::On | TerminalBlink::Off /*if !blinking_on */ => true,
+                TerminalBlink::Always => focused && blink_show,
+            },
+                None => {
+                    if blinking_on {
+                        blink_show
+                    } else {
+                        false
+                    }
+                }
+            }
+        }
+    }
 }
 
 impl Element for TerminalEl {
@@ -580,6 +610,7 @@ impl Element for TerminalEl {
         cx: &mut gpui::LayoutContext,
     ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
         let settings = cx.global::<Settings>();
+        let blink_settings = settings.terminal_overrides.blinking.clone();
         let font_cache = cx.font_cache();
 
         //Setup layout information
@@ -598,13 +629,13 @@ impl Element for TerminalEl {
             terminal_theme.colors.background
         };
 
-        let (cells, selection, cursor, display_offset, cursor_text) = self
+        let (cells, selection, cursor, display_offset, cursor_text, blink_mode) = self
             .terminal
             .upgrade(cx)
             .unwrap()
             .update(cx.app, |terminal, mcx| {
                 terminal.set_size(dimensions);
-                terminal.render_lock(mcx, |content, cursor_text| {
+                terminal.render_lock(mcx, |content, cursor_text, style| {
                     let mut cells = vec![];
                     cells.extend(
                         content
@@ -628,6 +659,7 @@ impl Element for TerminalEl {
                         content.cursor,
                         content.display_offset,
                         cursor_text,
+                        style,
                     )
                 })
             });
@@ -643,19 +675,13 @@ impl Element for TerminalEl {
         );
 
         //Layout cursor
-        //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
-        };
-
         let cursor = {
-            if show_cursor {
+            if !TerminalEl::should_show_cursor(
+                blink_settings,
+                blink_mode,
+                self.focused,
+                self.blink_state,
+            ) {
                 None
             } else {
                 let cursor_point = DisplayCursor::from(cursor.point, display_offset);

crates/terminal/src/connected_view.rs 🔗

@@ -132,7 +132,7 @@ impl ConnectedView {
     }
 
     //Following code copied from editor cursor
-    pub fn show_cursor(&self) -> bool {
+    pub fn blink_show(&self) -> bool {
         self.blinking_paused || self.show_cursor
     }
 
@@ -253,9 +253,15 @@ impl View for ConnectedView {
 
         Stack::new()
             .with_child(
-                TerminalEl::new(cx.handle(), terminal_handle, self.modal, focused)
-                    .contained()
-                    .boxed(),
+                TerminalEl::new(
+                    cx.handle(),
+                    terminal_handle,
+                    self.modal,
+                    focused,
+                    self.blink_show(),
+                )
+                .contained()
+                .boxed(),
             )
             .with_child(ChildView::new(&self.context_menu).boxed())
             .boxed()

crates/terminal/src/terminal.rs 🔗

@@ -25,7 +25,7 @@ use futures::{
 };
 
 use modal::deploy_modal;
-use settings::{Settings, Shell};
+use settings::{Settings, Shell, TerminalBlink};
 use std::{collections::HashMap, fmt::Display, path::PathBuf, sync::Arc, time::Duration};
 use thiserror::Error;
 
@@ -254,6 +254,7 @@ impl TerminalBuilder {
         shell: Option<Shell>,
         env: Option<HashMap<String, String>>,
         initial_size: TerminalSize,
+        blink_settings: Option<TerminalBlink>,
     ) -> Result<TerminalBuilder> {
         let pty_config = {
             let alac_shell = shell.clone().and_then(|shell| match shell {
@@ -290,7 +291,18 @@ impl TerminalBuilder {
         //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()));
+        let mut term = Term::new(&config, &initial_size, ZedListener(events_tx.clone()));
+
+        //Start off blinking if we need to
+        match blink_settings {
+            Some(setting) => match setting {
+                TerminalBlink::On | TerminalBlink::Always => {
+                    term.set_mode(alacritty_terminal::ansi::Mode::BlinkingCursor)
+                }
+                _ => {}
+            },
+            None => term.set_mode(alacritty_terminal::ansi::Mode::BlinkingCursor),
+        }
         let term = Arc::new(FairMutex::new(term));
 
         //Setup the pty...
@@ -322,7 +334,7 @@ impl TerminalBuilder {
         //And connect them together
         let event_loop = EventLoop::new(
             term.clone(),
-            ZedListener(events_tx),
+            ZedListener(events_tx.clone()),
             pty,
             pty_config.hold,
             false,
@@ -583,7 +595,7 @@ impl Terminal {
 
     pub fn render_lock<F, T>(&mut self, cx: &mut ModelContext<Self>, f: F) -> T
     where
-        F: FnOnce(RenderableContent, char) -> T,
+        F: FnOnce(RenderableContent, char, bool) -> T,
     {
         let m = self.term.clone(); //Arc clone
         let mut term = m.lock();
@@ -599,7 +611,7 @@ impl Terminal {
 
         let cursor_text = term.grid()[content.cursor.point].c;
 
-        f(content, cursor_text)
+        f(content, cursor_text, term.cursor_style().blinking)
     }
 
     ///Scroll the terminal

crates/terminal/src/terminal_view.rs 🔗

@@ -94,8 +94,13 @@ impl TerminalView {
         let shell = settings.terminal_overrides.shell.clone();
         let envs = settings.terminal_overrides.env.clone(); //Should be short and cheap.
 
-        let content = match TerminalBuilder::new(working_directory.clone(), shell, envs, size_info)
-        {
+        let content = match TerminalBuilder::new(
+            working_directory.clone(),
+            shell,
+            envs,
+            size_info,
+            settings.terminal_overrides.blinking.clone(),
+        ) {
             Ok(terminal) => {
                 let terminal = cx.add_model(|cx| terminal.subscribe(cx));
                 let view = cx.add_view(|cx| ConnectedView::from_terminal(terminal, modal, cx));