terminal_view: Reuse editor's blink manager (#43351)

Lukas Wirth created

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

Cargo.lock                                |   1 
crates/editor/src/blink_manager.rs        |  19 ++
crates/editor/src/editor.rs               |   8 +
crates/terminal_view/Cargo.toml           |   1 
crates/terminal_view/src/terminal_view.rs | 137 ++++++++++++------------
5 files changed, 89 insertions(+), 77 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -17135,7 +17135,6 @@ dependencies = [
  "serde_json",
  "settings",
  "shellexpand 2.1.2",
- "smol",
  "task",
  "terminal",
  "theme",
@@ -1,20 +1,28 @@
-use crate::EditorSettings;
 use gpui::Context;
-use settings::Settings;
 use settings::SettingsStore;
 use smol::Timer;
 use std::time::Duration;
+use ui::App;
 
 pub struct BlinkManager {
     blink_interval: Duration,
     blink_epoch: usize,
+    /// Whether the blinking is paused.
     blinking_paused: bool,
+    /// Whether the cursor should be visibly rendered or not.
     visible: bool,
+    /// Whether the blinking currently enabled.
     enabled: bool,
+    /// Whether the blinking is enabled in the settings.
+    blink_enabled_in_settings: fn(&App) -> bool,
 }
 
 impl BlinkManager {
-    pub fn new(blink_interval: Duration, cx: &mut Context<Self>) -> Self {
+    pub fn new(
+        blink_interval: Duration,
+        blink_enabled_in_settings: fn(&App) -> bool,
+        cx: &mut Context<Self>,
+    ) -> Self {
         // Make sure we blink the cursors if the setting is re-enabled
         cx.observe_global::<SettingsStore>(move |this, cx| {
             this.blink_cursors(this.blink_epoch, cx)
@@ -27,6 +35,7 @@ impl BlinkManager {
             blinking_paused: false,
             visible: true,
             enabled: false,
+            blink_enabled_in_settings,
         }
     }
 
@@ -55,7 +64,7 @@ impl BlinkManager {
     }
 
     fn blink_cursors(&mut self, epoch: usize, cx: &mut Context<Self>) {
-        if EditorSettings::get_global(cx).cursor_blink {
+        if (self.blink_enabled_in_settings)(cx) {
             if epoch == self.blink_epoch && self.enabled && !self.blinking_paused {
                 self.visible = !self.visible;
                 cx.notify();
@@ -83,6 +92,7 @@ impl BlinkManager {
         }
     }
 
+    /// Enable the blinking of the cursor.
     pub fn enable(&mut self, cx: &mut Context<Self>) {
         if self.enabled {
             return;
@@ -95,6 +105,7 @@ impl BlinkManager {
         self.blink_cursors(self.blink_epoch, cx);
     }
 
+    /// Disable the blinking of the cursor.
     pub fn disable(&mut self, _cx: &mut Context<Self>) {
         self.visible = false;
         self.enabled = false;

crates/editor/src/editor.rs 🔗

@@ -12,7 +12,7 @@
 //!
 //! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
 pub mod actions;
-mod blink_manager;
+pub mod blink_manager;
 mod bracket_colorization;
 mod clangd_ext;
 pub mod code_context_menus;
@@ -1888,7 +1888,11 @@ impl Editor {
         let selections = SelectionsCollection::new();
 
         let blink_manager = cx.new(|cx| {
-            let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
+            let mut blink_manager = BlinkManager::new(
+                CURSOR_BLINK_INTERVAL,
+                |cx| EditorSettings::get_global(cx).cursor_blink,
+                cx,
+            );
             if is_minimap {
                 blink_manager.disable(cx);
             }

crates/terminal_view/Cargo.toml 🔗

@@ -39,7 +39,6 @@ serde.workspace = true
 serde_json.workspace = true
 settings.workspace = true
 shellexpand.workspace = true
-smol.workspace = true
 terminal.workspace = true
 theme.workspace = true
 ui.workspace = true

crates/terminal_view/src/terminal_view.rs 🔗

@@ -7,7 +7,7 @@ mod terminal_slash_command;
 pub mod terminal_tab_tooltip;
 
 use assistant_slash_command::SlashCommandRegistry;
-use editor::{EditorSettings, actions::SelectAll};
+use editor::{EditorSettings, actions::SelectAll, blink_manager::BlinkManager};
 use gpui::{
     Action, AnyElement, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
     KeyContext, KeyDownEvent, Keystroke, MouseButton, MouseDownEvent, Pixels, Render,
@@ -51,7 +51,6 @@ use workspace::{
 
 use serde::Deserialize;
 use settings::{Settings, SettingsStore, TerminalBlink, WorkingDirectory};
-use smol::Timer;
 use zed_actions::assistant::InlineAssist;
 
 use std::{
@@ -126,12 +125,10 @@ pub struct TerminalView {
     has_bell: bool,
     context_menu: Option<(Entity<ContextMenu>, gpui::Point<Pixels>, Subscription)>,
     cursor_shape: CursorShape,
-    blink_state: bool,
+    blink_manager: Entity<BlinkManager>,
     mode: TerminalMode,
     blinking_terminal_enabled: bool,
     cwd_serialized: bool,
-    blinking_paused: bool,
-    blink_epoch: usize,
     hover: Option<HoverTarget>,
     hover_tooltip_update: Task<()>,
     workspace_id: Option<WorkspaceId>,
@@ -237,6 +234,25 @@ impl TerminalView {
 
         let scroll_handle = TerminalScrollHandle::new(terminal.read(cx));
 
+        let blink_manager = cx.new(|cx| {
+            BlinkManager::new(
+                CURSOR_BLINK_INTERVAL,
+                |cx| {
+                    !matches!(
+                        TerminalSettings::get_global(cx).blinking,
+                        TerminalBlink::Off
+                    )
+                },
+                cx,
+            )
+        });
+
+        let _subscriptions = vec![
+            focus_in,
+            focus_out,
+            cx.observe(&blink_manager, |_, _, cx| cx.notify()),
+            cx.observe_global::<SettingsStore>(Self::settings_changed),
+        ];
         Self {
             terminal,
             workspace: workspace_handle,
@@ -245,10 +261,8 @@ impl TerminalView {
             focus_handle,
             context_menu: None,
             cursor_shape,
-            blink_state: true,
+            blink_manager,
             blinking_terminal_enabled: false,
-            blinking_paused: false,
-            blink_epoch: 0,
             hover: None,
             hover_tooltip_update: Task::ready(()),
             mode: TerminalMode::Standalone,
@@ -259,11 +273,7 @@ impl TerminalView {
             scroll_handle,
             cwd_serialized: false,
             ime_state: None,
-            _subscriptions: vec![
-                focus_in,
-                focus_out,
-                cx.observe_global::<SettingsStore>(Self::settings_changed),
-            ],
+            _subscriptions,
             _terminal_subscriptions: terminal_subscriptions,
         }
     }
@@ -424,6 +434,11 @@ impl TerminalView {
         let breadcrumb_visibility_changed = self.show_breadcrumbs != settings.toolbar.breadcrumbs;
         self.show_breadcrumbs = settings.toolbar.breadcrumbs;
 
+        let should_blink = match settings.blinking {
+            TerminalBlink::Off => false,
+            TerminalBlink::On => true,
+            TerminalBlink::TerminalControlled => self.blinking_terminal_enabled,
+        };
         let new_cursor_shape = settings.cursor_shape;
         let old_cursor_shape = self.cursor_shape;
         if old_cursor_shape != new_cursor_shape {
@@ -433,6 +448,15 @@ impl TerminalView {
             });
         }
 
+        self.blink_manager.update(
+            cx,
+            if should_blink {
+                BlinkManager::enable
+            } else {
+                BlinkManager::disable
+            },
+        );
+
         if breadcrumb_visibility_changed {
             cx.emit(ItemEvent::UpdateBreadcrumbs);
         }
@@ -610,9 +634,8 @@ impl TerminalView {
     }
 
     pub fn should_show_cursor(&self, focused: bool, cx: &mut Context<Self>) -> bool {
-        //Don't blink the cursor when not focused, blinking is disabled, or paused
+        // Always show cursor when not focused or in special modes
         if !focused
-            || self.blinking_paused
             || self
                 .terminal
                 .read(cx)
@@ -623,45 +646,17 @@ impl TerminalView {
             return true;
         }
 
+        // When focused, check blinking settings and blink manager state
         match TerminalSettings::get_global(cx).blinking {
-            //If the user requested to never blink, don't blink it.
             TerminalBlink::Off => true,
-            //If the terminal is controlling it, check terminal mode
-            TerminalBlink::TerminalControlled => {
-                !self.blinking_terminal_enabled || self.blink_state
+            TerminalBlink::On | TerminalBlink::TerminalControlled => {
+                self.blink_manager.read(cx).visible()
             }
-            TerminalBlink::On => self.blink_state,
-        }
-    }
-
-    fn blink_cursors(&mut self, epoch: usize, window: &mut Window, cx: &mut Context<Self>) {
-        if epoch == self.blink_epoch && !self.blinking_paused {
-            self.blink_state = !self.blink_state;
-            cx.notify();
-
-            let epoch = self.next_blink_epoch();
-            cx.spawn_in(window, async move |this, cx| {
-                Timer::after(CURSOR_BLINK_INTERVAL).await;
-                this.update_in(cx, |this, window, cx| this.blink_cursors(epoch, window, cx))
-                    .ok();
-            })
-            .detach();
         }
     }
 
-    pub fn pause_cursor_blinking(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        self.blink_state = true;
-        cx.notify();
-
-        let epoch = self.next_blink_epoch();
-        cx.spawn_in(window, async move |this, cx| {
-            Timer::after(CURSOR_BLINK_INTERVAL).await;
-            this.update_in(cx, |this, window, cx| {
-                this.resume_cursor_blinking(epoch, window, cx)
-            })
-            .ok();
-        })
-        .detach();
+    pub fn pause_cursor_blinking(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
+        self.blink_manager.update(cx, BlinkManager::pause_blinking);
     }
 
     pub fn terminal(&self) -> &Entity<Terminal> {
@@ -685,23 +680,6 @@ impl TerminalView {
         cx.notify();
     }
 
-    fn next_blink_epoch(&mut self) -> usize {
-        self.blink_epoch += 1;
-        self.blink_epoch
-    }
-
-    fn resume_cursor_blinking(
-        &mut self,
-        epoch: usize,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        if epoch == self.blink_epoch {
-            self.blinking_paused = false;
-            self.blink_cursors(epoch, window, cx);
-        }
-    }
-
     ///Attempt to paste the clipboard into the terminal
     fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
         self.terminal.update(cx, |term, _| term.copy(None));
@@ -893,11 +871,21 @@ fn subscribe_for_terminal_events(
                 }
 
                 Event::BlinkChanged(blinking) => {
+                    terminal_view.blinking_terminal_enabled = *blinking;
+
+                    // If in terminal-controlled mode and focused, update blink manager
                     if matches!(
                         TerminalSettings::get_global(cx).blinking,
                         TerminalBlink::TerminalControlled
-                    ) {
-                        terminal_view.blinking_terminal_enabled = *blinking;
+                    ) && terminal_view.focus_handle.is_focused(window)
+                    {
+                        terminal_view.blink_manager.update(cx, |manager, cx| {
+                            if *blinking {
+                                manager.enable(cx);
+                            } else {
+                                manager.disable(cx);
+                            }
+                        });
                     }
                 }
 
@@ -1023,12 +1011,23 @@ impl TerminalView {
             terminal.set_cursor_shape(self.cursor_shape);
             terminal.focus_in();
         });
-        self.blink_cursors(self.blink_epoch, window, cx);
+
+        let should_blink = match TerminalSettings::get_global(cx).blinking {
+            TerminalBlink::Off => false,
+            TerminalBlink::On => true,
+            TerminalBlink::TerminalControlled => self.blinking_terminal_enabled,
+        };
+
+        if should_blink {
+            self.blink_manager.update(cx, BlinkManager::enable);
+        }
+
         window.invalidate_character_coordinates();
         cx.notify();
     }
 
-    fn focus_out(&mut self, _: &mut Window, cx: &mut Context<Self>) {
+    fn focus_out(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
+        self.blink_manager.update(cx, BlinkManager::disable);
         self.terminal.update(cx, |terminal, _| {
             terminal.focus_out();
             terminal.set_cursor_shape(CursorShape::Hollow);