Hide the mouse when the user is typing in the editor - take 2 (#27519)

Smit Barmase , Peter Tripp , Thomas Mickley-Doyle , Agus , Kirill Bulatov , Agus Zubiaga , and Angelk90 created

Closes #4461

Take 2 on https://github.com/zed-industries/zed/pull/25040. 

Fixes panic caused due to using `setHiddenUntilMouseMoves` return type
to `set` cursor on macOS.

Release Notes:

- Now cursor hides when the user is typing in editor. It will stay
hidden until it is moved again. This behavior is `true` by default, and
can be configured with `hide_mouse_while_typing` in settings.

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
Co-authored-by: Thomas Mickley-Doyle <thomas@zed.dev>
Co-authored-by: Agus <agus@zed.dev>
Co-authored-by: Kirill Bulatov <kirill@zed.dev>
Co-authored-by: Agus Zubiaga <hi@aguz.me>
Co-authored-by: Angelk90 <angelo.k90@hotmail.it>

Change summary

assets/settings/default.json                     |  2 +
crates/editor/src/editor.rs                      | 24 +++++++++++++-
crates/editor/src/editor_settings.rs             |  5 +++
crates/editor/src/element.rs                     | 30 +++++++++++------
crates/gpui/examples/window_shadow.rs            |  2 
crates/gpui/src/elements/div.rs                  |  2 
crates/gpui/src/elements/text.rs                 |  2 
crates/gpui/src/platform.rs                      |  3 +
crates/gpui/src/platform/linux/platform.rs       |  6 +++
crates/gpui/src/platform/linux/wayland.rs        |  6 +++
crates/gpui/src/platform/linux/wayland/client.rs |  8 ++++
crates/gpui/src/platform/linux/x11/client.rs     | 29 ++++++++++++++---
crates/gpui/src/platform/mac/platform.rs         |  6 +++
crates/gpui/src/platform/windows/events.rs       | 18 +++++++++-
crates/gpui/src/platform/windows/platform.rs     |  8 ++--
crates/gpui/src/platform/windows/util.rs         | 23 +++++++------
crates/gpui/src/platform/windows/window.rs       |  6 +-
crates/gpui/src/window.rs                        | 13 +++++--
crates/gpui_macros/src/styles.rs                 |  7 ++++
crates/markdown/src/markdown.rs                  |  4 +-
crates/terminal_view/src/terminal_element.rs     |  4 +-
crates/ui/src/components/indent_guides.rs        |  2 
crates/workspace/src/pane_group.rs               |  2 
crates/workspace/src/workspace.rs                |  2 
docs/src/configuring-zed.md                      | 10 ++++++
25 files changed, 172 insertions(+), 52 deletions(-)

Detailed changes

assets/settings/default.json 🔗

@@ -155,6 +155,8 @@
   //
   // Default: not set, defaults to "bar"
   "cursor_shape": null,
+  // Determines whether the mouse cursor is hidden when typing in an editor or input box.
+  "hide_mouse_while_typing": true,
   // How to highlight the current line in the editor.
   //
   // 1. Don't highlight the current line:

crates/editor/src/editor.rs 🔗

@@ -790,6 +790,8 @@ pub struct Editor {
     _scroll_cursor_center_top_bottom_task: Task<()>,
     serialize_selections: Task<()>,
     serialize_folds: Task<()>,
+    mouse_cursor_hidden: bool,
+    hide_mouse_while_typing: bool,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
@@ -1568,6 +1570,10 @@ impl Editor {
             serialize_folds: Task::ready(()),
             text_style_refinement: None,
             load_diff_task: load_uncommitted_diff,
+            mouse_cursor_hidden: false,
+            hide_mouse_while_typing: EditorSettings::get_global(cx)
+                .hide_mouse_while_typing
+                .unwrap_or(true),
         };
         if let Some(breakpoints) = this.breakpoint_store.as_ref() {
             this._subscriptions
@@ -2999,6 +3005,8 @@ impl Editor {
             return;
         }
 
+        self.mouse_cursor_hidden = self.hide_mouse_while_typing;
+
         let selections = self.selections.all_adjusted(cx);
         let mut bracket_inserted = false;
         let mut edits = Vec::new();
@@ -3403,6 +3411,7 @@ impl Editor {
     }
 
     pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
+        self.mouse_cursor_hidden = self.hide_mouse_while_typing;
         self.transact(window, cx, |this, window, cx| {
             let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
                 let selections = this.selections.all::<usize>(cx);
@@ -3518,6 +3527,8 @@ impl Editor {
     }
 
     pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
+        self.mouse_cursor_hidden = self.hide_mouse_while_typing;
+
         let buffer = self.buffer.read(cx);
         let snapshot = buffer.snapshot(cx);
 
@@ -3575,6 +3586,8 @@ impl Editor {
     }
 
     pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
+        self.mouse_cursor_hidden = self.hide_mouse_while_typing;
+
         let buffer = self.buffer.read(cx);
         let snapshot = buffer.snapshot(cx);
 
@@ -7765,6 +7778,7 @@ impl Editor {
     }
 
     pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
+        self.mouse_cursor_hidden = self.hide_mouse_while_typing;
         self.transact(window, cx, |this, window, cx| {
             this.select_autoclose_pair(window, cx);
             let mut linked_ranges = HashMap::<_, Vec<_>>::default();
@@ -7863,6 +7877,7 @@ impl Editor {
     }
 
     pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
+        self.mouse_cursor_hidden = self.hide_mouse_while_typing;
         self.transact(window, cx, |this, window, cx| {
             this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
                 let line_mode = s.line_mode;
@@ -7884,7 +7899,7 @@ impl Editor {
         if self.move_to_prev_snippet_tabstop(window, cx) {
             return;
         }
-
+        self.mouse_cursor_hidden = self.hide_mouse_while_typing;
         self.outdent(&Outdent, window, cx);
     }
 
@@ -7892,7 +7907,7 @@ impl Editor {
         if self.move_to_next_snippet_tabstop(window, cx) || self.read_only(cx) {
             return;
         }
-
+        self.mouse_cursor_hidden = self.hide_mouse_while_typing;
         let mut selections = self.selections.all_adjusted(cx);
         let buffer = self.buffer.read(cx);
         let snapshot = buffer.snapshot(cx);
@@ -16669,6 +16684,11 @@ impl Editor {
             self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
             self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
             self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
+            self.hide_mouse_while_typing = editor_settings.hide_mouse_while_typing.unwrap_or(true);
+
+            if !self.hide_mouse_while_typing {
+                self.mouse_cursor_hidden = false;
+            }
         }
 
         if old_cursor_shape != self.cursor_shape {

crates/editor/src/editor_settings.rs 🔗

@@ -39,6 +39,7 @@ pub struct EditorSettings {
     #[serde(default)]
     pub go_to_definition_fallback: GoToDefinitionFallback,
     pub jupyter: Jupyter,
+    pub hide_mouse_while_typing: Option<bool>,
 }
 
 #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@@ -235,6 +236,10 @@ pub struct EditorSettingsContent {
     ///
     /// Default: None
     pub cursor_shape: Option<CursorShape>,
+    /// Determines whether the mouse cursor should be hidden while typing in an editor or input box.
+    ///
+    /// Default: true
+    pub hide_mouse_while_typing: Option<bool>,
     /// How to highlight the current line in the editor.
     ///
     /// Default: all

crates/editor/src/element.rs 🔗

@@ -894,6 +894,7 @@ impl EditorElement {
         let gutter_hovered = gutter_hitbox.is_hovered(window);
         editor.set_gutter_hovered(gutter_hovered, cx);
         editor.gutter_breakpoint_indicator = None;
+        editor.mouse_cursor_hidden = false;
 
         if gutter_hovered {
             let new_point = position_map
@@ -4307,7 +4308,7 @@ impl EditorElement {
         let is_singleton = self.editor.read(cx).is_singleton(cx);
 
         let line_height = layout.position_map.line_height;
-        window.set_cursor_style(CursorStyle::Arrow, &layout.gutter_hitbox);
+        window.set_cursor_style(CursorStyle::Arrow, Some(&layout.gutter_hitbox));
 
         for LineNumberLayout {
             shaped_line,
@@ -4340,9 +4341,9 @@ impl EditorElement {
             // In singleton buffers, we select corresponding lines on the line number click, so use | -like cursor.
             // In multi buffers, we open file at the line number clicked, so use a pointing hand cursor.
             if is_singleton {
-                window.set_cursor_style(CursorStyle::IBeam, &hitbox);
+                window.set_cursor_style(CursorStyle::IBeam, Some(&hitbox));
             } else {
-                window.set_cursor_style(CursorStyle::PointingHand, &hitbox);
+                window.set_cursor_style(CursorStyle::PointingHand, Some(&hitbox));
             }
         }
     }
@@ -4564,7 +4565,7 @@ impl EditorElement {
                     .read(cx)
                     .all_diff_hunks_expanded()
                 {
-                    window.set_cursor_style(CursorStyle::PointingHand, hunk_hitbox);
+                    window.set_cursor_style(CursorStyle::PointingHand, Some(hunk_hitbox));
                 }
             }
         }
@@ -4636,18 +4637,24 @@ impl EditorElement {
                 bounds: layout.position_map.text_hitbox.bounds,
             }),
             |window| {
-                let cursor_style = if self
-                    .editor
-                    .read(cx)
+                let editor = self.editor.read(cx);
+                if editor.mouse_cursor_hidden {
+                    window.set_cursor_style(CursorStyle::None, None);
+                } else if editor
                     .hovered_link_state
                     .as_ref()
                     .is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty())
                 {
-                    CursorStyle::PointingHand
+                    window.set_cursor_style(
+                        CursorStyle::PointingHand,
+                        Some(&layout.position_map.text_hitbox),
+                    );
                 } else {
-                    CursorStyle::IBeam
+                    window.set_cursor_style(
+                        CursorStyle::IBeam,
+                        Some(&layout.position_map.text_hitbox),
+                    );
                 };
-                window.set_cursor_style(cursor_style, &layout.position_map.text_hitbox);
 
                 self.paint_lines_background(layout, window, cx);
                 let invisible_display_ranges = self.paint_highlights(layout, window);
@@ -4842,7 +4849,7 @@ impl EditorElement {
                     ));
                 })
             }
-            window.set_cursor_style(CursorStyle::Arrow, &hitbox);
+            window.set_cursor_style(CursorStyle::Arrow, Some(&hitbox));
         }
 
         window.on_mouse_event({
@@ -6598,6 +6605,7 @@ impl Element for EditorElement {
                         },
                         false,
                     );
+
                     // Offset the content_bounds from the text_bounds by the gutter margin (which
                     // is roughly half a character wide) to make hit testing work more like how we want.
                     let content_origin =

crates/gpui/src/elements/div.rs 🔗

@@ -1617,7 +1617,7 @@ impl Interactivity {
 
                                         if !cx.has_active_drag() {
                                             if let Some(mouse_cursor) = style.mouse_cursor {
-                                                window.set_cursor_style(mouse_cursor, hitbox);
+                                                window.set_cursor_style(mouse_cursor, Some(hitbox));
                                             }
                                         }
 

crates/gpui/src/elements/text.rs 🔗

@@ -700,7 +700,7 @@ impl Element for InteractiveText {
                             .iter()
                             .any(|range| range.contains(&ix))
                         {
-                            window.set_cursor_style(crate::CursorStyle::PointingHand, hitbox)
+                            window.set_cursor_style(crate::CursorStyle::PointingHand, Some(hitbox))
                         }
                     }
 

crates/gpui/src/platform.rs 🔗

@@ -1228,6 +1228,9 @@ pub enum CursorStyle {
     /// A cursor indicating that the operation will result in a context menu
     /// corresponds to the CSS cursor value `context-menu`
     ContextualMenu,
+
+    /// Hide the cursor
+    None,
 }
 
 impl Default for CursorStyle {

crates/gpui/src/platform/linux/platform.rs 🔗

@@ -666,6 +666,12 @@ impl CursorStyle {
             CursorStyle::DragLink => "alias",
             CursorStyle::DragCopy => "copy",
             CursorStyle::ContextualMenu => "context-menu",
+            CursorStyle::None => {
+                #[cfg(debug_assertions)]
+                panic!("CursorStyle::None should be handled separately in the client");
+                #[cfg(not(debug_assertions))]
+                "default"
+            }
         }
         .to_string()
     }

crates/gpui/src/platform/linux/wayland.rs 🔗

@@ -35,6 +35,12 @@ impl CursorStyle {
             CursorStyle::DragLink => Shape::Alias,
             CursorStyle::DragCopy => Shape::Copy,
             CursorStyle::ContextualMenu => Shape::ContextMenu,
+            CursorStyle::None => {
+                #[cfg(debug_assertions)]
+                panic!("CursorStyle::None should be handled separately in the client");
+                #[cfg(not(debug_assertions))]
+                Shape::Default
+            }
         }
     }
 }

crates/gpui/src/platform/linux/wayland/client.rs 🔗

@@ -667,7 +667,13 @@ impl LinuxClient for WaylandClient {
             let serial = state.serial_tracker.get(SerialKind::MouseEnter);
             state.cursor_style = Some(style);
 
-            if let Some(cursor_shape_device) = &state.cursor_shape_device {
+            if let CursorStyle::None = style {
+                let wl_pointer = state
+                    .wl_pointer
+                    .clone()
+                    .expect("window is focused by pointer");
+                wl_pointer.set_cursor(serial, None, 0, 0);
+            } else if let Some(cursor_shape_device) = &state.cursor_shape_device {
                 cursor_shape_device.set_shape(serial, style.to_shape());
             } else if let Some(focused_window) = &state.mouse_focused_window {
                 // cursor-shape-v1 isn't supported, set the cursor using a surface.

crates/gpui/src/platform/linux/x11/client.rs 🔗

@@ -1438,13 +1438,16 @@ impl LinuxClient for X11Client {
         let cursor = match state.cursor_cache.get(&style) {
             Some(cursor) => *cursor,
             None => {
-                let Some(cursor) = state
-                    .cursor_handle
-                    .load_cursor(&state.xcb_connection, &style.to_icon_name())
-                    .log_err()
-                else {
+                let Some(cursor) = (match style {
+                    CursorStyle::None => create_invisible_cursor(&state.xcb_connection).log_err(),
+                    _ => state
+                        .cursor_handle
+                        .load_cursor(&state.xcb_connection, &style.to_icon_name())
+                        .log_err(),
+                }) else {
                     return;
                 };
+
                 state.cursor_cache.insert(style, cursor);
                 cursor
             }
@@ -1938,3 +1941,19 @@ fn make_scroll_wheel_event(
         touch_phase: TouchPhase::default(),
     }
 }
+
+fn create_invisible_cursor(
+    connection: &XCBConnection,
+) -> anyhow::Result<crate::platform::linux::x11::client::xproto::Cursor> {
+    let empty_pixmap = connection.generate_id()?;
+    let root = connection.setup().roots[0].root;
+    connection.create_pixmap(1, empty_pixmap, root, 1, 1)?;
+
+    let cursor = connection.generate_id()?;
+    connection.create_cursor(cursor, empty_pixmap, empty_pixmap, 0, 0, 0, 0, 0, 0, 0, 0)?;
+
+    connection.free_pixmap(empty_pixmap)?;
+
+    connection.flush()?;
+    Ok(cursor)
+}

crates/gpui/src/platform/mac/platform.rs 🔗

@@ -891,6 +891,11 @@ impl Platform for MacPlatform {
     /// in macOS's [NSCursor](https://developer.apple.com/documentation/appkit/nscursor).
     fn set_cursor_style(&self, style: CursorStyle) {
         unsafe {
+            if style == CursorStyle::None {
+                let _: () = msg_send![class!(NSCursor), setHiddenUntilMouseMoves:YES];
+                return;
+            }
+
             let new_cursor: id = match style {
                 CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
                 CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],
@@ -925,6 +930,7 @@ impl Platform for MacPlatform {
                 CursorStyle::DragLink => msg_send![class!(NSCursor), dragLinkCursor],
                 CursorStyle::DragCopy => msg_send![class!(NSCursor), dragCopyCursor],
                 CursorStyle::ContextualMenu => msg_send![class!(NSCursor), contextualMenuCursor],
+                CursorStyle::None => unreachable!(),
             };
 
             let old_cursor: id = msg_send![class!(NSCursor), currentCursor];

crates/gpui/src/platform/windows/events.rs 🔗

@@ -1127,7 +1127,19 @@ fn handle_nc_mouse_up_msg(
 }
 
 fn handle_cursor_changed(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
-    state_ptr.state.borrow_mut().current_cursor = HCURSOR(lparam.0 as _);
+    let mut state = state_ptr.state.borrow_mut();
+    let had_cursor = state.current_cursor.is_some();
+
+    state.current_cursor = if lparam.0 == 0 {
+        None
+    } else {
+        Some(HCURSOR(lparam.0 as _))
+    };
+
+    if had_cursor != state.current_cursor.is_some() {
+        unsafe { SetCursor(state.current_cursor) };
+    }
+
     Some(0)
 }
 
@@ -1138,7 +1150,9 @@ fn handle_set_cursor(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Op
     ) {
         return None;
     }
-    unsafe { SetCursor(Some(state_ptr.state.borrow().current_cursor)) };
+    unsafe {
+        SetCursor(state_ptr.state.borrow().current_cursor);
+    };
     Some(1)
 }
 

crates/gpui/src/platform/windows/platform.rs 🔗

@@ -54,7 +54,7 @@ pub(crate) struct WindowsPlatformState {
     menus: Vec<OwnedMenu>,
     dock_menu_actions: Vec<Box<dyn Action>>,
     // NOTE: standard cursor handles don't need to close.
-    pub(crate) current_cursor: HCURSOR,
+    pub(crate) current_cursor: Option<HCURSOR>,
 }
 
 #[derive(Default)]
@@ -558,11 +558,11 @@ impl Platform for WindowsPlatform {
     fn set_cursor_style(&self, style: CursorStyle) {
         let hcursor = load_cursor(style);
         let mut lock = self.state.borrow_mut();
-        if lock.current_cursor.0 != hcursor.0 {
+        if lock.current_cursor.map(|c| c.0) != hcursor.map(|c| c.0) {
             self.post_message(
                 WM_GPUI_CURSOR_STYLE_CHANGED,
                 WPARAM(0),
-                LPARAM(hcursor.0 as isize),
+                LPARAM(hcursor.map_or(0, |c| c.0 as isize)),
             );
             lock.current_cursor = hcursor;
         }
@@ -683,7 +683,7 @@ impl Drop for WindowsPlatform {
 pub(crate) struct WindowCreationInfo {
     pub(crate) icon: HICON,
     pub(crate) executor: ForegroundExecutor,
-    pub(crate) current_cursor: HCURSOR,
+    pub(crate) current_cursor: Option<HCURSOR>,
     pub(crate) windows_version: WindowsVersion,
     pub(crate) validation_number: usize,
     pub(crate) main_receiver: flume::Receiver<Runnable>,

crates/gpui/src/platform/windows/util.rs 🔗

@@ -106,7 +106,7 @@ pub(crate) fn windows_credentials_target_name(url: &str) -> String {
     format!("zed:url={}", url)
 }
 
-pub(crate) fn load_cursor(style: CursorStyle) -> HCURSOR {
+pub(crate) fn load_cursor(style: CursorStyle) -> Option<HCURSOR> {
     static ARROW: OnceLock<SafeCursor> = OnceLock::new();
     static IBEAM: OnceLock<SafeCursor> = OnceLock::new();
     static CROSS: OnceLock<SafeCursor> = OnceLock::new();
@@ -127,17 +127,20 @@ pub(crate) fn load_cursor(style: CursorStyle) -> HCURSOR {
         | CursorStyle::ResizeUpDown
         | CursorStyle::ResizeRow => (&SIZENS, IDC_SIZENS),
         CursorStyle::OperationNotAllowed => (&NO, IDC_NO),
+        CursorStyle::None => return None,
         _ => (&ARROW, IDC_ARROW),
     };
-    *(*lock.get_or_init(|| {
-        HCURSOR(
-            unsafe { LoadImageW(None, name, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED) }
-                .log_err()
-                .unwrap_or_default()
-                .0,
-        )
-        .into()
-    }))
+    Some(
+        *(*lock.get_or_init(|| {
+            HCURSOR(
+                unsafe { LoadImageW(None, name, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED) }
+                    .log_err()
+                    .unwrap_or_default()
+                    .0,
+            )
+            .into()
+        })),
+    )
 }
 
 /// This function is used to configure the dark mode for the window built-in title bar.

crates/gpui/src/platform/windows/window.rs 🔗

@@ -48,7 +48,7 @@ pub struct WindowsWindowState {
 
     pub click_state: ClickState,
     pub system_settings: WindowsSystemSettings,
-    pub current_cursor: HCURSOR,
+    pub current_cursor: Option<HCURSOR>,
     pub nc_button_pressed: Option<u32>,
 
     pub display: WindowsDisplay,
@@ -76,7 +76,7 @@ impl WindowsWindowState {
         hwnd: HWND,
         transparent: bool,
         cs: &CREATESTRUCTW,
-        current_cursor: HCURSOR,
+        current_cursor: Option<HCURSOR>,
         display: WindowsDisplay,
         gpu_context: &BladeContext,
     ) -> Result<Self> {
@@ -351,7 +351,7 @@ struct WindowCreateContext<'a> {
     transparent: bool,
     is_movable: bool,
     executor: ForegroundExecutor,
-    current_cursor: HCURSOR,
+    current_cursor: Option<HCURSOR>,
     windows_version: WindowsVersion,
     validation_number: usize,
     main_receiver: flume::Receiver<Runnable>,

crates/gpui/src/window.rs 🔗

@@ -407,7 +407,7 @@ pub(crate) type AnyMouseListener =
 
 #[derive(Clone)]
 pub(crate) struct CursorStyleRequest {
-    pub(crate) hitbox_id: HitboxId,
+    pub(crate) hitbox_id: Option<HitboxId>, // None represents whole window
     pub(crate) style: CursorStyle,
 }
 
@@ -1928,10 +1928,10 @@ impl Window {
 
     /// Updates the cursor style at the platform level. This method should only be called
     /// during the prepaint phase of element drawing.
-    pub fn set_cursor_style(&mut self, style: CursorStyle, hitbox: &Hitbox) {
+    pub fn set_cursor_style(&mut self, style: CursorStyle, hitbox: Option<&Hitbox>) {
         self.invalidator.debug_assert_paint();
         self.next_frame.cursor_styles.push(CursorStyleRequest {
-            hitbox_id: hitbox.id,
+            hitbox_id: hitbox.map(|hitbox| hitbox.id),
             style,
         });
     }
@@ -2984,7 +2984,11 @@ impl Window {
                 .cursor_styles
                 .iter()
                 .rev()
-                .find(|request| request.hitbox_id.is_hovered(self))
+                .find(|request| {
+                    request
+                        .hitbox_id
+                        .map_or(true, |hitbox_id| hitbox_id.is_hovered(self))
+                })
                 .map(|request| request.style)
                 .unwrap_or(CursorStyle::Arrow);
             cx.platform.set_cursor_style(style);
@@ -3241,6 +3245,7 @@ impl Window {
             keystroke,
             &dispatch_path,
         );
+
         if !match_result.to_replay.is_empty() {
             self.replay_pending_input(match_result.to_replay, cx)
         }

crates/gpui_macros/src/styles.rs 🔗

@@ -326,6 +326,13 @@ pub fn cursor_style_methods(input: TokenStream) -> TokenStream {
             self.style().mouse_cursor = Some(gpui::CursorStyle::ResizeLeft);
             self
         }
+
+        /// Sets cursor style when hovering over an element to `none`.
+        /// [Docs](https://tailwindcss.com/docs/cursor)
+        #visibility fn cursor_none(mut self, cursor: CursorStyle) -> Self {
+            self.style().mouse_cursor = Some(gpui::CursorStyle::None);
+            self
+        }
     };
 
     output.into()

crates/markdown/src/markdown.rs 🔗

@@ -411,9 +411,9 @@ impl MarkdownElement {
                 .is_some();
 
         if is_hovering_link {
-            window.set_cursor_style(CursorStyle::PointingHand, hitbox);
+            window.set_cursor_style(CursorStyle::PointingHand, Some(hitbox));
         } else {
-            window.set_cursor_style(CursorStyle::IBeam, hitbox);
+            window.set_cursor_style(CursorStyle::IBeam, Some(hitbox));
         }
 
         self.on_mouse_event(window, cx, {

crates/terminal_view/src/terminal_element.rs 🔗

@@ -886,9 +886,9 @@ impl Element for TerminalElement {
                 && bounds.contains(&window.mouse_position())
                 && self.terminal_view.read(cx).hover_target_tooltip.is_some()
             {
-                window.set_cursor_style(gpui::CursorStyle::PointingHand, &layout.hitbox);
+                window.set_cursor_style(gpui::CursorStyle::PointingHand, Some(&layout.hitbox));
             } else {
-                window.set_cursor_style(gpui::CursorStyle::IBeam, &layout.hitbox);
+                window.set_cursor_style(gpui::CursorStyle::IBeam, Some(&layout.hitbox));
             }
 
             let cursor = layout.cursor.take();

crates/ui/src/components/indent_guides.rs 🔗

@@ -311,7 +311,7 @@ mod uniform_list {
                     });
                     let mut hovered_hitbox_id = None;
                     for (i, hitbox) in hitboxes.iter().enumerate() {
-                        window.set_cursor_style(gpui::CursorStyle::PointingHand, hitbox);
+                        window.set_cursor_style(gpui::CursorStyle::PointingHand, Some(hitbox));
                         let indent_guide = &self.indent_guides[i];
                         let fill_color = if hitbox.is_hovered(window) {
                             hovered_hitbox_id = Some(hitbox.id);

crates/workspace/src/pane_group.rs 🔗

@@ -1176,7 +1176,7 @@ mod element {
                         Axis::Vertical => CursorStyle::ResizeRow,
                         Axis::Horizontal => CursorStyle::ResizeColumn,
                     };
-                    window.set_cursor_style(cursor_style, &handle.hitbox);
+                    window.set_cursor_style(cursor_style, Some(&handle.hitbox));
                     window.paint_quad(gpui::fill(
                         handle.divider_bounds,
                         cx.theme().colors().pane_group_border,

docs/src/configuring-zed.md 🔗

@@ -553,6 +553,16 @@ List of `string` values
 "cursor_shape": "hollow"
 ```
 
+## Hide Mouse While Typing
+
+- Description: Determines whether the mouse cursor should be hidden while typing in an editor or input box.
+- Setting: `hide_mouse_while_typing`
+- Default: `true`
+
+**Options**
+
+`boolean` values
+
 ## Editor Scrollbar
 
 - Description: Whether or not to show the editor scrollbar and various elements in it.