Handle hiding cursor on keyboard input at GPUI level (#55664)

Instead of manually handing hiding the cursor on keyboard input at the
editor level, GPUI will now take care of it.

This makes it significantly easier to handle the edge cases, and allows
delegating the cursor restoration to the platform itself in the macOS
case. On Linux and Windows, we still have to restore the cursor on
movement ourselves, but this now happens at the platform-specific level.

Bugs fixed by this change:
- No cursor when "Unsaved edits" prompt appears
- Cursor disappears when clicking a panel button if it contains a search
bar (e.g. collab panel)

### Setting rename

The `hide_mouse` setting value `"on_typing_and_movement"` has been
renamed to `"on_typing_and_action"` to better reflect what it actually
does — it hides the cursor when a keystroke resolves to an action (e.g.
cursor movement, deletion). Existing settings are migrated
automatically.

### Tested platforms
- [x] macOS
- [x] Wayland
- [x] X11
- [x] Windows
- [x] Web

Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- Renamed the `hide_mouse` setting value `on_typing_and_movement` to
`on_typing_and_action` to better describe its behavior (existing
settings are auto-migrated)
- Fixed a few situations where the mouse cursor would be incorrectly
hidden
This commit is contained in:
Agus Zubiaga authored and GitHub committed 2026-05-04 22:51:56 +00:00
1 parent d1525d92b1
commit ba6d258b0f
34 files changed
+716 -450

No files matched your search

+5 -4
View File
@@ -230,15 +230,16 @@
// //
// Default: "bar" // Default: "bar"
"cursor_shape": "bar", "cursor_shape": "bar",
// Determines when the mouse cursor should be hidden in an editor or input box. // Determines when the mouse cursor should be hidden in response to keyboard
// input.
// //
// 1. Never hide the mouse cursor: // 1. Never hide the mouse cursor:
// "never" // "never"
// 2. Hide only when typing: // 2. Hide only when typing:
// "on_typing" // "on_typing"
// 3. Hide on both typing and cursor movement: // 3. Hide on typing and on key bindings that resolve to an action:
// "on_typing_and_movement" // "on_typing_and_action"
"hide_mouse": "on_typing_and_movement", "hide_mouse": "on_typing_and_action",
// Determines whether the focused panel follows the mouse location. // Determines whether the focused panel follows the mouse location.
"focus_follows_mouse": { "focus_follows_mouse": {
"enabled": false, "enabled": false,
+4 -187
View File
@@ -65,8 +65,8 @@ pub use display_map::{
pub use edit_prediction_types::Direction; pub use edit_prediction_types::Direction;
pub use editor_settings::{ pub use editor_settings::{
CompletionDetailAlignment, CurrentLineHighlight, DiffViewStyle, DocumentColorsRenderMode, CompletionDetailAlignment, CurrentLineHighlight, DiffViewStyle, DocumentColorsRenderMode,
EditorSettings, EditorSettingsScrollbarProxy, HideMouseMode, ScrollBeyondLastLine, EditorSettings, EditorSettingsScrollbarProxy, ScrollBeyondLastLine, ScrollbarAxes,
ScrollbarAxes, SearchSettings, ShowMinimap, ui_scrollbar_settings_from_raw, SearchSettings, ShowMinimap, ui_scrollbar_settings_from_raw,
}; };
pub use element::{ pub use element::{
CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition, CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
@@ -334,11 +334,6 @@ enum DisplayDiffHunk {
}, },
} }
pub enum HideMouseCursorOrigin {
TypingAction,
MovementAction,
}
pub fn init(cx: &mut App) { pub fn init(cx: &mut App) {
cx.set_global(GlobalBlameRenderer(Arc::new(()))); cx.set_global(GlobalBlameRenderer(Arc::new(())));
cx.set_global(breadcrumbs::RenderBreadcrumbText(render_breadcrumb_text)); cx.set_global(breadcrumbs::RenderBreadcrumbText(render_breadcrumb_text));
@@ -1354,9 +1349,7 @@ pub struct Editor {
_scroll_cursor_center_top_bottom_task: Task<()>, _scroll_cursor_center_top_bottom_task: Task<()>,
serialize_selections: Task<()>, serialize_selections: Task<()>,
serialize_folds: Task<()>, serialize_folds: Task<()>,
mouse_cursor_hidden: bool,
minimap: Option<Entity<Self>>, minimap: Option<Entity<Self>>,
hide_mouse_mode: HideMouseMode,
pub change_list: ChangeList, pub change_list: ChangeList,
inline_value_cache: InlineValueCache, inline_value_cache: InlineValueCache,
number_deleted_lines: bool, number_deleted_lines: bool,
@@ -2624,9 +2617,6 @@ impl Editor {
blink_manager.disable(cx); blink_manager.disable(cx);
} }
}); });
if active {
editor.show_mouse_cursor(cx);
}
}), }),
] ]
}) })
@@ -2658,11 +2648,7 @@ impl Editor {
text_style_refinement: None, text_style_refinement: None,
load_diff_task: load_uncommitted_diff, load_diff_task: load_uncommitted_diff,
temporary_diff_override: false, temporary_diff_override: false,
mouse_cursor_hidden: false,
minimap: None, minimap: None,
hide_mouse_mode: EditorSettings::get_global(cx)
.hide_mouse
.unwrap_or_default(),
change_list: ChangeList::new(), change_list: ChangeList::new(),
mode, mode,
selection_drag_state: SelectionDragState::None, selection_drag_state: SelectionDragState::None,
@@ -3004,31 +2990,6 @@ impl Editor {
self.last_bounds.as_ref() self.last_bounds.as_ref()
} }
fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
if self.mouse_cursor_hidden {
self.mouse_cursor_hidden = false;
cx.notify();
}
}
pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
let hide_mouse_cursor = match origin {
HideMouseCursorOrigin::TypingAction => {
matches!(
self.hide_mouse_mode,
HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
)
}
HideMouseCursorOrigin::MovementAction => {
matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
}
};
if self.mouse_cursor_hidden != hide_mouse_cursor {
self.mouse_cursor_hidden = hide_mouse_cursor;
cx.notify();
}
}
fn accept_edit_prediction_keystroke( fn accept_edit_prediction_keystroke(
&self, &self,
granularity: EditPredictionGranularity, granularity: EditPredictionGranularity,
@@ -4830,8 +4791,6 @@ impl Editor {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.unfold_buffers_with_selections(cx); self.unfold_buffers_with_selections(cx);
let selections = self.selections.all_adjusted(&self.display_snapshot(cx)); let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
@@ -5333,7 +5292,6 @@ impl Editor {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.transact(window, cx, |this, window, cx| { self.transact(window, cx, |this, window, cx| {
let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = { let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
let selections = this let selections = this
@@ -5572,8 +5530,6 @@ impl Editor {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let buffer = self.buffer.read(cx); let buffer = self.buffer.read(cx);
let snapshot = buffer.snapshot(cx); let snapshot = buffer.snapshot(cx);
@@ -5643,8 +5599,6 @@ impl Editor {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default(); let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
let mut rows = Vec::new(); let mut rows = Vec::new();
let mut rows_inserted = 0; let mut rows_inserted = 0;
@@ -6699,7 +6653,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return None; return None;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx) self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
} }
@@ -6712,7 +6665,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return None; return None;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx) self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
} }
@@ -6725,7 +6677,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return None; return None;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx) self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
} }
@@ -6735,7 +6686,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Option<Task<Result<()>>> { ) -> Option<Task<Result<()>>> {
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx) self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
} }
@@ -7229,7 +7179,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return None; return None;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let actions_menu = let actions_menu =
if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? { if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
@@ -11293,7 +11242,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.transact(window, cx, |this, window, cx| { self.transact(window, cx, |this, window, cx| {
this.select_autoclose_pair(window, cx); this.select_autoclose_pair(window, cx);
@@ -11346,7 +11294,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.transact(window, cx, |this, window, cx| { self.transact(window, cx, |this, window, cx| {
this.change_selections(Default::default(), window, cx, |s| { this.change_selections(Default::default(), window, cx, |s| {
s.move_with(&mut |map, selection| { s.move_with(&mut |map, selection| {
@@ -11372,7 +11319,6 @@ impl Editor {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
if self.move_to_prev_snippet_tabstop(window, cx) { if self.move_to_prev_snippet_tabstop(window, cx) {
return; return;
} }
@@ -11391,7 +11337,6 @@ impl Editor {
} }
if self.move_to_next_snippet_tabstop(window, cx) { if self.move_to_next_snippet_tabstop(window, cx) {
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
return; return;
} }
cx.propagate(); cx.propagate();
@@ -11409,7 +11354,6 @@ impl Editor {
} }
if self.move_to_prev_snippet_tabstop(window, cx) { if self.move_to_prev_snippet_tabstop(window, cx) {
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
return; return;
} }
cx.propagate(); cx.propagate();
@@ -11422,13 +11366,11 @@ impl Editor {
} }
if self.move_to_next_snippet_tabstop(window, cx) { if self.move_to_next_snippet_tabstop(window, cx) {
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
return; return;
} }
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx)); let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
let buffer = self.buffer.read(cx); let buffer = self.buffer.read(cx);
let snapshot = buffer.snapshot(cx); let snapshot = buffer.snapshot(cx);
@@ -11560,7 +11502,6 @@ impl Editor {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx)); let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
let mut prev_edited_row = 0; let mut prev_edited_row = 0;
let mut row_delta = 0; let mut row_delta = 0;
@@ -11668,7 +11609,6 @@ impl Editor {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let selections = self.selections.all::<Point>(&display_map); let selections = self.selections.all::<Point>(&display_map);
let mut deletion_ranges = Vec::new(); let mut deletion_ranges = Vec::new();
@@ -11747,7 +11687,6 @@ impl Editor {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let selections = self let selections = self
.selections .selections
.all::<MultiBufferOffset>(&self.display_snapshot(cx)) .all::<MultiBufferOffset>(&self.display_snapshot(cx))
@@ -11769,7 +11708,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let selections = self.selections.all::<Point>(&display_map); let selections = self.selections.all::<Point>(&display_map);
@@ -11983,7 +11921,6 @@ impl Editor {
} }
pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) { pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.join_lines_impl(true, window, cx); self.join_lines_impl(true, window, cx);
} }
@@ -12065,7 +12002,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let snapshot = self.buffer.read(cx).snapshot(cx); let snapshot = self.buffer.read(cx).snapshot(cx);
@@ -12168,7 +12104,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let mut buffer_ids = HashSet::default(); let mut buffer_ids = HashSet::default();
let snapshot = self.buffer().read(cx).snapshot(cx); let snapshot = self.buffer().read(cx).snapshot(cx);
for selection in self for selection in self
@@ -12190,7 +12125,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let selections = self let selections = self
.selections .selections
.all(&self.display_snapshot(cx)) .all(&self.display_snapshot(cx))
@@ -12219,7 +12153,6 @@ impl Editor {
.map(|selection| selection.range()) .map(|selection| selection.range())
.collect(); .collect();
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.restore_hunks_in_ranges(selections, window, cx); self.restore_hunks_in_ranges(selections, window, cx);
let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded(); let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
@@ -12622,7 +12555,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let display_snapshot = self.display_snapshot(cx); let display_snapshot = self.display_snapshot(cx);
@@ -12863,7 +12795,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let display_snapshot = self.display_snapshot(cx); let display_snapshot = self.display_snapshot(cx);
let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot); let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
@@ -13017,7 +12948,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = self.buffer.read(cx).snapshot(cx); let buffer = self.buffer.read(cx).snapshot(cx);
@@ -13439,7 +13369,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let buffer = self.buffer.read(cx).snapshot(cx); let buffer = self.buffer.read(cx).snapshot(cx);
let mut new_selections = Vec::new(); let mut new_selections = Vec::new();
@@ -13539,7 +13468,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = display_map.buffer_snapshot(); let buffer = display_map.buffer_snapshot();
@@ -13687,7 +13615,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
if self.mode.is_single_line() { if self.mode.is_single_line() {
cx.propagate(); cx.propagate();
return; return;
@@ -13801,7 +13728,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
if self.mode.is_single_line() { if self.mode.is_single_line() {
cx.propagate(); cx.propagate();
return; return;
@@ -13898,7 +13824,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let text_layout_details = &self.text_layout_details(window, cx); let text_layout_details = &self.text_layout_details(window, cx);
self.transact(window, cx, |this, window, cx| { self.transact(window, cx, |this, window, cx| {
let edits = this.change_selections(Default::default(), window, cx, |s| { let edits = this.change_selections(Default::default(), window, cx, |s| {
@@ -13963,7 +13888,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
if self.mode.is_single_line() { if self.mode.is_single_line() {
cx.propagate(); cx.propagate();
return; return;
@@ -14426,7 +14350,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let item = self.cut_common(true, window, cx); let item = self.cut_common(true, window, cx);
cx.write_to_clipboard(item); cx.write_to_clipboard(item);
} }
@@ -14435,7 +14358,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
s.move_with(&mut |snapshot, sel| { s.move_with(&mut |snapshot, sel| {
if sel.is_empty() { if sel.is_empty() {
@@ -14456,7 +14378,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() { let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() { if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
(kill_ring.text().to_string(), kill_ring.metadata_json()) (kill_ring.text().to_string(), kill_ring.metadata_json())
@@ -14804,7 +14725,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
if let Some(item) = cx.read_from_clipboard() { if let Some(item) = cx.read_from_clipboard() {
let clipboard_string = item.entries().iter().find_map(|entry| match entry { let clipboard_string = item.entries().iter().find_map(|entry| match entry {
ClipboardEntry::String(s) => Some(s), ClipboardEntry::String(s) => Some(s),
@@ -14828,8 +14748,6 @@ impl Editor {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) { if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
if let Some((selections, _)) = if let Some((selections, _)) =
self.selection_history.transaction(transaction_id).cloned() self.selection_history.transaction(transaction_id).cloned()
@@ -14858,8 +14776,6 @@ impl Editor {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) { if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
if let Some((_, Some(selections))) = if let Some((_, Some(selections))) =
self.selection_history.transaction(transaction_id).cloned() self.selection_history.transaction(transaction_id).cloned()
@@ -14893,7 +14809,6 @@ impl Editor {
} }
pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) { pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_with(&mut |map, selection| { s.move_with(&mut |map, selection| {
let cursor = if selection.is_empty() { let cursor = if selection.is_empty() {
@@ -14907,14 +14822,12 @@ impl Editor {
} }
pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) { pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None)); s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
}) })
} }
pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) { pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_with(&mut |map, selection| { s.move_with(&mut |map, selection| {
let cursor = if selection.is_empty() { let cursor = if selection.is_empty() {
@@ -14928,7 +14841,6 @@ impl Editor {
} }
pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) { pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_heads_with(&mut |map, head, _| { s.move_heads_with(&mut |map, head, _| {
(movement::right(map, head), SelectionGoal::None) (movement::right(map, head), SelectionGoal::None)
@@ -14946,8 +14858,6 @@ impl Editor {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let text_layout_details = &self.text_layout_details(window, cx); let text_layout_details = &self.text_layout_details(window, cx);
let selection_count = self.selections.count(); let selection_count = self.selections.count();
let first_selection = self.selections.first_anchor(); let first_selection = self.selections.first_anchor();
@@ -14989,8 +14899,6 @@ impl Editor {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let text_layout_details = &self.text_layout_details(window, cx); let text_layout_details = &self.text_layout_details(window, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
@@ -15026,8 +14934,6 @@ impl Editor {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let text_layout_details = &self.text_layout_details(window, cx); let text_layout_details = &self.text_layout_details(window, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
@@ -15054,7 +14960,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let text_layout_details = &self.text_layout_details(window, cx); let text_layout_details = &self.text_layout_details(window, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_heads_with(&mut |map, head, goal| { s.move_heads_with(&mut |map, head, goal| {
@@ -15069,7 +14974,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let text_layout_details = &self.text_layout_details(window, cx); let text_layout_details = &self.text_layout_details(window, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_heads_with(&mut |map, head, goal| { s.move_heads_with(&mut |map, head, goal| {
@@ -15088,8 +14992,6 @@ impl Editor {
return; return;
}; };
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let text_layout_details = &self.text_layout_details(window, cx); let text_layout_details = &self.text_layout_details(window, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
@@ -15128,8 +15030,6 @@ impl Editor {
return; return;
}; };
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let effects = if action.center_cursor { let effects = if action.center_cursor {
SelectionEffects::scroll(Autoscroll::center()) SelectionEffects::scroll(Autoscroll::center())
} else { } else {
@@ -15157,7 +15057,6 @@ impl Editor {
} }
pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) { pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let text_layout_details = &self.text_layout_details(window, cx); let text_layout_details = &self.text_layout_details(window, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_heads_with(&mut |map, head, goal| { s.move_heads_with(&mut |map, head, goal| {
@@ -15174,8 +15073,6 @@ impl Editor {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let text_layout_details = &self.text_layout_details(window, cx); let text_layout_details = &self.text_layout_details(window, cx);
let selection_count = self.selections.count(); let selection_count = self.selections.count();
let first_selection = self.selections.first_anchor(); let first_selection = self.selections.first_anchor();
@@ -15212,8 +15109,6 @@ impl Editor {
return; return;
}; };
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let text_layout_details = &self.text_layout_details(window, cx); let text_layout_details = &self.text_layout_details(window, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
@@ -15252,8 +15147,6 @@ impl Editor {
return; return;
}; };
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let effects = if action.center_cursor { let effects = if action.center_cursor {
SelectionEffects::scroll(Autoscroll::center()) SelectionEffects::scroll(Autoscroll::center())
} else { } else {
@@ -15280,7 +15173,6 @@ impl Editor {
} }
pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) { pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let text_layout_details = &self.text_layout_details(window, cx); let text_layout_details = &self.text_layout_details(window, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_heads_with(&mut |map, head, goal| { s.move_heads_with(&mut |map, head, goal| {
@@ -15371,7 +15263,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_cursors_with(&mut |map, head, _| { s.move_cursors_with(&mut |map, head, _| {
( (
@@ -15388,7 +15279,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_cursors_with(&mut |map, head, _| { s.move_cursors_with(&mut |map, head, _| {
( (
@@ -15405,7 +15295,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_heads_with(&mut |map, head, _| { s.move_heads_with(&mut |map, head, _| {
( (
@@ -15422,7 +15311,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_heads_with(&mut |map, head, _| { s.move_heads_with(&mut |map, head, _| {
( (
@@ -15442,7 +15330,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.transact(window, cx, |this, window, cx| { self.transact(window, cx, |this, window, cx| {
this.select_autoclose_pair(window, cx); this.select_autoclose_pair(window, cx);
this.change_selections(Default::default(), window, cx, |s| { this.change_selections(Default::default(), window, cx, |s| {
@@ -15476,7 +15363,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.transact(window, cx, |this, window, cx| { self.transact(window, cx, |this, window, cx| {
this.select_autoclose_pair(window, cx); this.select_autoclose_pair(window, cx);
this.change_selections(Default::default(), window, cx, |s| { this.change_selections(Default::default(), window, cx, |s| {
@@ -15507,7 +15393,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_cursors_with(&mut |map, head, _| { s.move_cursors_with(&mut |map, head, _| {
(movement::next_word_end(map, head), SelectionGoal::None) (movement::next_word_end(map, head), SelectionGoal::None)
@@ -15521,7 +15406,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_cursors_with(&mut |map, head, _| { s.move_cursors_with(&mut |map, head, _| {
(movement::next_subword_end(map, head), SelectionGoal::None) (movement::next_subword_end(map, head), SelectionGoal::None)
@@ -15535,7 +15419,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_heads_with(&mut |map, head, _| { s.move_heads_with(&mut |map, head, _| {
(movement::next_word_end(map, head), SelectionGoal::None) (movement::next_word_end(map, head), SelectionGoal::None)
@@ -15549,7 +15432,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_heads_with(&mut |map, head, _| { s.move_heads_with(&mut |map, head, _| {
(movement::next_subword_end(map, head), SelectionGoal::None) (movement::next_subword_end(map, head), SelectionGoal::None)
@@ -15566,7 +15448,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.transact(window, cx, |this, window, cx| { self.transact(window, cx, |this, window, cx| {
this.change_selections(Default::default(), window, cx, |s| { this.change_selections(Default::default(), window, cx, |s| {
s.move_with(&mut |map, selection| { s.move_with(&mut |map, selection| {
@@ -15599,7 +15480,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.transact(window, cx, |this, window, cx| { self.transact(window, cx, |this, window, cx| {
this.change_selections(Default::default(), window, cx, |s| { this.change_selections(Default::default(), window, cx, |s| {
s.move_with(&mut |map, selection| { s.move_with(&mut |map, selection| {
@@ -15630,7 +15510,6 @@ impl Editor {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line(); let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_cursors_with(&mut |map, head, _| { s.move_cursors_with(&mut |map, head, _| {
( (
@@ -15653,7 +15532,6 @@ impl Editor {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line(); let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_heads_with(&mut |map, head, _| { s.move_heads_with(&mut |map, head, _| {
( (
@@ -15678,7 +15556,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.transact(window, cx, |this, window, cx| { self.transact(window, cx, |this, window, cx| {
this.change_selections(Default::default(), window, cx, |s| { this.change_selections(Default::default(), window, cx, |s| {
s.move_with(&mut |_, selection| { s.move_with(&mut |_, selection| {
@@ -15704,7 +15581,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_cursors_with(&mut |map, head, _| { s.move_cursors_with(&mut |map, head, _| {
( (
@@ -15721,7 +15597,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_heads_with(&mut |map, head, _| { s.move_heads_with(&mut |map, head, _| {
( (
@@ -15741,7 +15616,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.transact(window, cx, |this, window, cx| { self.transact(window, cx, |this, window, cx| {
this.select_to_end_of_line( this.select_to_end_of_line(
&SelectToEndOfLine { &SelectToEndOfLine {
@@ -15763,7 +15637,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.transact(window, cx, |this, window, cx| { self.transact(window, cx, |this, window, cx| {
this.select_to_end_of_line( this.select_to_end_of_line(
&SelectToEndOfLine { &SelectToEndOfLine {
@@ -15781,7 +15654,6 @@ impl Editor {
}); });
}); });
} }
this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let item = this.cut_common(false, window, cx); let item = this.cut_common(false, window, cx);
cx.write_to_clipboard(item); cx.write_to_clipboard(item);
}); });
@@ -15797,7 +15669,6 @@ impl Editor {
cx.propagate(); cx.propagate();
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_with(&mut |map, selection| { s.move_with(&mut |map, selection| {
selection.collapse_to( selection.collapse_to(
@@ -15818,7 +15689,6 @@ impl Editor {
cx.propagate(); cx.propagate();
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_with(&mut |map, selection| { s.move_with(&mut |map, selection| {
selection.collapse_to( selection.collapse_to(
@@ -15839,7 +15709,6 @@ impl Editor {
cx.propagate(); cx.propagate();
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_heads_with(&mut |map, head, _| { s.move_heads_with(&mut |map, head, _| {
( (
@@ -15860,7 +15729,6 @@ impl Editor {
cx.propagate(); cx.propagate();
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_heads_with(&mut |map, head, _| { s.move_heads_with(&mut |map, head, _| {
( (
@@ -15881,7 +15749,6 @@ impl Editor {
cx.propagate(); cx.propagate();
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_with(&mut |map, selection| { s.move_with(&mut |map, selection| {
selection.collapse_to( selection.collapse_to(
@@ -15931,7 +15798,6 @@ impl Editor {
cx.propagate(); cx.propagate();
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_with(&mut |map, selection| { s.move_with(&mut |map, selection| {
selection.collapse_to( selection.collapse_to(
@@ -15956,7 +15822,6 @@ impl Editor {
cx.propagate(); cx.propagate();
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_with(&mut |map, selection| { s.move_with(&mut |map, selection| {
selection.collapse_to( selection.collapse_to(
@@ -15981,7 +15846,6 @@ impl Editor {
cx.propagate(); cx.propagate();
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_heads_with(&mut |map, head, _| { s.move_heads_with(&mut |map, head, _| {
( (
@@ -16002,7 +15866,6 @@ impl Editor {
cx.propagate(); cx.propagate();
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_heads_with(&mut |map, head, _| { s.move_heads_with(&mut |map, head, _| {
( (
@@ -16023,7 +15886,6 @@ impl Editor {
cx.propagate(); cx.propagate();
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_heads_with(&mut |map, head, _| { s.move_heads_with(&mut |map, head, _| {
( (
@@ -16044,7 +15906,6 @@ impl Editor {
cx.propagate(); cx.propagate();
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_heads_with(&mut |map, head, _| { s.move_heads_with(&mut |map, head, _| {
( (
@@ -16065,7 +15926,6 @@ impl Editor {
cx.propagate(); cx.propagate();
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.select_ranges(vec![Anchor::Min..Anchor::Min]); s.select_ranges(vec![Anchor::Min..Anchor::Min]);
}); });
@@ -16079,7 +15939,6 @@ impl Editor {
) { ) {
let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx)); let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
selection.set_head(Point::zero(), SelectionGoal::None); selection.set_head(Point::zero(), SelectionGoal::None);
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.select(vec![selection]); s.select(vec![selection]);
}); });
@@ -16090,7 +15949,6 @@ impl Editor {
cx.propagate(); cx.propagate();
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let cursor = self.buffer.read(cx).read(cx).len(); let cursor = self.buffer.read(cx).read(cx).len();
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.select_ranges(vec![cursor..cursor]) s.select_ranges(vec![cursor..cursor])
@@ -16179,7 +16037,6 @@ impl Editor {
} }
pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) { pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let buffer = self.buffer.read(cx).snapshot(cx); let buffer = self.buffer.read(cx).snapshot(cx);
let mut selection = self let mut selection = self
.selections .selections
@@ -16191,14 +16048,12 @@ impl Editor {
} }
pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) { pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
s.select_ranges(vec![Anchor::Min..Anchor::Max]); s.select_ranges(vec![Anchor::Min..Anchor::Max]);
}); });
} }
pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) { pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections.all::<Point>(&display_map); let mut selections = self.selections.all::<Point>(&display_map);
let max_point = display_map.buffer_snapshot().max_point(); let max_point = display_map.buffer_snapshot().max_point();
@@ -16297,8 +16152,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let all_selections = self.selections.all::<Point>(&display_map); let all_selections = self.selections.all::<Point>(&display_map);
let text_layout_details = self.text_layout_details(window, cx); let text_layout_details = self.text_layout_details(window, cx);
@@ -16720,8 +16573,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Result<()> { ) -> Result<()> {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
self.select_next_match_internal(&display_map, false, None, window, cx)?; self.select_next_match_internal(&display_map, false, None, window, cx)?;
@@ -16787,7 +16638,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Result<()> { ) -> Result<()> {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
self.select_next_match_internal( self.select_next_match_internal(
&display_map, &display_map,
@@ -16804,7 +16654,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Result<()> { ) -> Result<()> {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = display_map.buffer_snapshot(); let buffer = display_map.buffer_snapshot();
let mut selections = self.selections.all::<MultiBufferOffset>(&display_map); let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
@@ -17011,7 +16860,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.transact(window, cx, |this, _window, cx| { self.transact(window, cx, |this, _window, cx| {
let mut selections = this let mut selections = this
.selections .selections
@@ -17202,7 +17050,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let text_layout_details = &self.text_layout_details(window, cx); let text_layout_details = &self.text_layout_details(window, cx);
self.transact(window, cx, |this, window, cx| { self.transact(window, cx, |this, window, cx| {
let mut selections = this let mut selections = this
@@ -17505,8 +17352,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let buffer = self.buffer.read(cx).snapshot(cx); let buffer = self.buffer.read(cx).snapshot(cx);
let old_selections = self let old_selections = self
.selections .selections
@@ -17574,8 +17419,6 @@ impl Editor {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = self.buffer.read(cx).snapshot(cx); let buffer = self.buffer.read(cx).snapshot(cx);
@@ -17691,8 +17534,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
if let Some((mut selections, scroll_behavior, is_selection_reversed)) = if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
self.select_syntax_node_history.pop() self.select_syntax_node_history.pop()
{ {
@@ -17735,7 +17576,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let buffer = self.buffer.read(cx).snapshot(cx); let buffer = self.buffer.read(cx).snapshot(cx);
let selections = self let selections = self
@@ -17817,8 +17657,6 @@ impl Editor {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let buffer = self.buffer.read(cx).snapshot(cx); let buffer = self.buffer.read(cx).snapshot(cx);
let mut selected_sibling = false; let mut selected_sibling = false;
@@ -17877,8 +17715,6 @@ impl Editor {
) { ) {
let old_selections: Arc<[_]> = self.selections.all_anchors(&self.display_snapshot(cx)); let old_selections: Arc<[_]> = self.selections.all_anchors(&self.display_snapshot(cx));
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx); let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
let mut selected_sibling = false; let mut selected_sibling = false;
@@ -18017,8 +17853,6 @@ impl Editor {
return false; return false;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = self.buffer.read(cx).snapshot(cx); let buffer = self.buffer.read(cx).snapshot(cx);
@@ -18082,8 +17916,6 @@ impl Editor {
cx: &mut Context<Self>, cx: &mut Context<Self>,
move_to_end: bool, move_to_end: bool,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = self.buffer.read(cx).snapshot(cx); let buffer = self.buffer.read(cx).snapshot(cx);
let old_selections = self.selections.all::<MultiBufferOffset>(&display_map); let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
@@ -18115,7 +17947,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.change_selections(Default::default(), window, cx, |s| { self.change_selections(Default::default(), window, cx, |s| {
s.move_offsets_with(&mut |snapshot, selection| { s.move_offsets_with(&mut |snapshot, selection| {
let Some(enclosing_bracket_ranges) = let Some(enclosing_bracket_ranges) =
@@ -18172,7 +18003,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
if let Some(entry) = self.selection_history.undo_stack.pop_back() { if let Some(entry) = self.selection_history.undo_stack.pop_back() {
self.selection_history.mode = SelectionHistoryMode::Undoing; self.selection_history.mode = SelectionHistoryMode::Undoing;
self.with_selection_effects_deferred(window, cx, |this, window, cx| { self.with_selection_effects_deferred(window, cx, |this, window, cx| {
@@ -18198,7 +18028,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
if let Some(entry) = self.selection_history.redo_stack.pop_back() { if let Some(entry) = self.selection_history.redo_stack.pop_back() {
self.selection_history.mode = SelectionHistoryMode::Redoing; self.selection_history.mode = SelectionHistoryMode::Redoing;
self.with_selection_effects_deferred(window, cx, |this, window, cx| { self.with_selection_effects_deferred(window, cx, |this, window, cx| {
@@ -18386,7 +18215,6 @@ impl Editor {
if !self.diagnostics_enabled() { if !self.diagnostics_enabled() {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx) self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
} }
@@ -18399,7 +18227,6 @@ impl Editor {
if !self.diagnostics_enabled() { if !self.diagnostics_enabled() {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx) self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
} }
@@ -18497,7 +18324,6 @@ impl Editor {
} }
pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) { pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let snapshot = self.snapshot(window, cx); let snapshot = self.snapshot(window, cx);
let selection = self.selections.newest::<Point>(&self.display_snapshot(cx)); let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
self.go_to_hunk_before_or_after_position( self.go_to_hunk_before_or_after_position(
@@ -18566,7 +18392,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let snapshot = self.snapshot(window, cx); let snapshot = self.snapshot(window, cx);
let selection = self.selections.newest::<Point>(&snapshot.display_snapshot); let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
self.go_to_hunk_before_or_after_position( self.go_to_hunk_before_or_after_position(
@@ -18660,7 +18485,6 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Editor>, cx: &mut Context<Editor>,
) { ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let snapshot = self.snapshot(window, cx); let snapshot = self.snapshot(window, cx);
let buffer = &snapshot.buffer_snapshot(); let buffer = &snapshot.buffer_snapshot();
let position = self let position = self
@@ -20180,7 +20004,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return None; return None;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let project = match &self.project { let project = match &self.project {
Some(project) => project.clone(), Some(project) => project.clone(),
@@ -20205,7 +20028,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return None; return None;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let project = match &self.project { let project = match &self.project {
Some(project) => project.clone(), Some(project) => project.clone(),
@@ -20327,7 +20149,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return None; return None;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let project = match &self.project { let project = match &self.project {
Some(project) => project.clone(), Some(project) => project.clone(),
None => return None, None => return None,
@@ -21911,7 +21732,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let buffers = self.buffer.read(cx).all_buffers(); let buffers = self.buffer.read(cx).all_buffers();
for branch_buffer in buffers { for branch_buffer in buffers {
@@ -21944,7 +21764,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
let snapshot = self.snapshot(window, cx); let snapshot = self.snapshot(window, cx);
let hunks = snapshot.hunks_for_ranges( let hunks = snapshot.hunks_for_ranges(
self.selections self.selections
@@ -24284,7 +24103,6 @@ impl Editor {
if self.read_only(cx) { if self.read_only(cx) {
return; return;
} }
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.transact(window, cx, |this, window, cx| { self.transact(window, cx, |this, window, cx| {
let edits = this let edits = this
.selections .selections
@@ -25363,7 +25181,6 @@ impl Editor {
self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin; self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs; self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default(); self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
} }
if old_cursor_shape != self.cursor_shape { if old_cursor_shape != self.cursor_shape {
@@ -26021,8 +25838,8 @@ impl Editor {
} }
}); });
if let Some(position_map) = self.last_position_map.clone() if cx.is_cursor_visible()
&& !self.mouse_cursor_hidden && let Some(position_map) = self.last_position_map.clone()
{ {
EditorElement::mouse_moved( EditorElement::mouse_moved(
self, self,
+2 -5
View File
@@ -6,9 +6,8 @@ use project::project_settings::DiagnosticSeverity;
pub use settings::{ pub use settings::{
CodeLens, CompletionDetailAlignment, CurrentLineHighlight, DelayMs, DiffViewStyle, DisplayIn, CodeLens, CompletionDetailAlignment, CurrentLineHighlight, DelayMs, DiffViewStyle, DisplayIn,
DocumentColorsRenderMode, DoubleClickInMultibuffer, GoToDefinitionFallback, DocumentColorsRenderMode, DoubleClickInMultibuffer, GoToDefinitionFallback,
GoToDefinitionScrollStrategy, HideMouseMode, MinimapThumb, MinimapThumbBorder, GoToDefinitionScrollStrategy, MinimapThumb, MinimapThumbBorder, MultiCursorModifier,
MultiCursorModifier, ScrollBeyondLastLine, ScrollbarDiagnostics, SeedQuerySetting, ShowMinimap, ScrollBeyondLastLine, ScrollbarDiagnostics, SeedQuerySetting, ShowMinimap, SnippetSortOrder,
SnippetSortOrder,
}; };
use settings::{RegisterSetting, RelativeLineNumbers, Settings}; use settings::{RegisterSetting, RelativeLineNumbers, Settings};
use ui::scrollbars::ShowScrollbar; use ui::scrollbars::ShowScrollbar;
@@ -55,7 +54,6 @@ pub struct EditorSettings {
pub go_to_definition_fallback: GoToDefinitionFallback, pub go_to_definition_fallback: GoToDefinitionFallback,
pub go_to_definition_scroll_strategy: GoToDefinitionScrollStrategy, pub go_to_definition_scroll_strategy: GoToDefinitionScrollStrategy,
pub jupyter: Jupyter, pub jupyter: Jupyter,
pub hide_mouse: Option<HideMouseMode>,
pub snippet_sort_order: SnippetSortOrder, pub snippet_sort_order: SnippetSortOrder,
pub diagnostics_max_severity: Option<DiagnosticSeverity>, pub diagnostics_max_severity: Option<DiagnosticSeverity>,
pub inline_code_actions: bool, pub inline_code_actions: bool,
@@ -291,7 +289,6 @@ impl Settings for EditorSettings {
jupyter: Jupyter { jupyter: Jupyter {
enabled: editor.jupyter.unwrap().enabled.unwrap(), enabled: editor.jupyter.unwrap().enabled.unwrap(),
}, },
hide_mouse: editor.hide_mouse,
snippet_sort_order: editor.snippet_sort_order.unwrap(), snippet_sort_order: editor.snippet_sort_order.unwrap(),
diagnostics_max_severity: editor.diagnostics_max_severity.map(Into::into), diagnostics_max_severity: editor.diagnostics_max_severity.map(Into::into),
inline_code_actions: editor.inline_code_actions.unwrap(), inline_code_actions: editor.inline_code_actions.unwrap(),
+1 -4
View File
@@ -1262,7 +1262,6 @@ impl EditorElement {
let text_hovered = text_hitbox.is_hovered(window); let text_hovered = text_hitbox.is_hovered(window);
let gutter_hovered = gutter_hitbox.is_hovered(window); let gutter_hovered = gutter_hitbox.is_hovered(window);
editor.set_gutter_hovered(gutter_hovered, cx); editor.set_gutter_hovered(gutter_hovered, cx);
editor.show_mouse_cursor(cx);
let point_for_position = position_map.point_for_position(event.position); let point_for_position = position_map.point_for_position(event.position);
let valid_point = point_for_position.nearest_valid; let valid_point = point_for_position.nearest_valid;
@@ -6607,9 +6606,7 @@ impl EditorElement {
}), }),
|window| { |window| {
let editor = self.editor.read(cx); let editor = self.editor.read(cx);
if editor.mouse_cursor_hidden { if let SelectionDragState::ReadyToDrag {
window.set_window_cursor_style(CursorStyle::None);
} else if let SelectionDragState::ReadyToDrag {
mouse_down_time, .. mouse_down_time, ..
} = &editor.selection_drag_state } = &editor.selection_drag_state
{ {
+7 -25
View File
@@ -120,7 +120,12 @@ impl Editor {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let hovered_link_modifier = Editor::is_cmd_or_ctrl_pressed(&modifiers, cx); let hovered_link_modifier = Editor::is_cmd_or_ctrl_pressed(&modifiers, cx);
if !hovered_link_modifier || self.has_pending_selection() || self.mouse_cursor_hidden { if !hovered_link_modifier || self.has_pending_selection() {
self.hide_hovered_link(cx);
return;
}
if !cx.is_cursor_visible() {
self.hide_hovered_link(cx); self.hide_hovered_link(cx);
return; return;
} }
@@ -782,7 +787,7 @@ fn surrounding_filename(
mod tests { mod tests {
use super::*; use super::*;
use crate::{ use crate::{
DisplayPoint, HideMouseCursorOrigin, DisplayPoint,
display_map::ToDisplayPoint, display_map::ToDisplayPoint,
editor_tests::init_test, editor_tests::init_test,
inlays::inlay_hints::tests::{cached_hint_labels, visible_hint_labels}, inlays::inlay_hints::tests::{cached_hint_labels, visible_hint_labels},
@@ -1413,29 +1418,6 @@ mod tests {
Let's test a [complex](«https://zed.dev/channel/ˇ») case. Let's test a [complex](«https://zed.dev/channel/ˇ») case.
"}, "},
); );
// Cursor hidden with secondary key
let screen_coord = cx.pixel_position(indoc! {"
Let's test a [complex](https://zed.dev/ˇchannel/) case.
"});
cx.simulate_mouse_move(screen_coord, None, Modifiers::none());
cx.update_editor(|editor, _, cx| {
editor.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
});
cx.simulate_modifiers_change(Modifiers::secondary_key());
assert_no_highlight!(cx);
// Cursor active again
let screen_coord = cx.pixel_position(indoc! {"
Let's test a [complex](https://ˇzed.dev/channel/) case.
"});
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
cx.assert_editor_text_highlights(
HighlightKey::HoveredLinkState,
indoc! {"
Let's test a [complex](«https://zed.dev/channel/ˇ») case.
"},
);
} }
#[gpui::test] #[gpui::test]
+39
View File
@@ -257,6 +257,22 @@ pub enum QuitMode {
Explicit, Explicit,
} }
/// Controls when GPUI hides the mouse cursor in response to keyboard input.
///
/// Restoration on mouse motion is handled by the platform layer; this enum
/// only describes the policy for *triggering* a hide.
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum CursorHideMode {
/// Never hide the cursor automatically.
Never,
/// Hide on character-producing key presses (typing).
OnTyping,
/// Hide on character-producing key presses, *and* when a key binding
/// resolves to an action that consumes the keystroke.
#[default]
OnTypingAndAction,
}
#[doc(hidden)] #[doc(hidden)]
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
pub struct SystemWindowTab { pub struct SystemWindowTab {
@@ -649,6 +665,7 @@ pub struct App {
pub(crate) window_update_stack: Vec<WindowId>, pub(crate) window_update_stack: Vec<WindowId>,
pub(crate) mode: GpuiMode, pub(crate) mode: GpuiMode,
pub(crate) cursor_hide_mode: CursorHideMode,
flushing_effects: bool, flushing_effects: bool,
pending_updates: usize, pending_updates: usize,
quit_mode: QuitMode, quit_mode: QuitMode,
@@ -737,6 +754,7 @@ impl App {
inspector_element_registry: InspectorElementRegistry::default(), inspector_element_registry: InspectorElementRegistry::default(),
quit_mode: QuitMode::default(), quit_mode: QuitMode::default(),
quitting: false, quitting: false,
cursor_hide_mode: CursorHideMode::default(),
#[cfg(any(test, feature = "test-support", debug_assertions))] #[cfg(any(test, feature = "test-support", debug_assertions))]
name: None, name: None,
@@ -877,6 +895,27 @@ impl App {
self.platform.quit(); self.platform.quit();
} }
/// Returns the current policy for hiding the cursor in response to
/// keyboard input.
pub fn cursor_hide_mode(&self) -> CursorHideMode {
self.cursor_hide_mode
}
/// Sets the policy controlling when GPUI hides the cursor in response
/// to keyboard input.
pub fn set_cursor_hide_mode(&mut self, mode: CursorHideMode) {
self.cursor_hide_mode = mode;
}
/// Returns whether the cursor is currently visible according to the
/// platform. This will report `false` after a keyboard input has hidden
/// the cursor and the user has not yet moved the mouse to restore it.
///
/// See [`App::set_cursor_hide_mode`].
pub fn is_cursor_visible(&self) -> bool {
self.platform.is_cursor_visible()
}
/// Schedules all windows in the application to be redrawn. This can be called /// Schedules all windows in the application to be redrawn. This can be called
/// multiple times in an update cycle and still result in a single redraw. /// multiple times in an update cycle and still result in a single redraw.
pub fn refresh_windows(&mut self) { pub fn refresh_windows(&mut self) {
+8 -3
View File
@@ -212,6 +212,14 @@ pub trait Platform: 'static {
fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>; fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
fn set_cursor_style(&self, style: CursorStyle); fn set_cursor_style(&self, style: CursorStyle);
/// Hides the mouse cursor until the user moves the mouse over one of
/// this application's windows.
fn hide_cursor_until_mouse_moves(&self);
/// Returns whether the mouse cursor is currently visible.
fn is_cursor_visible(&self) -> bool;
fn should_auto_hide_scrollbars(&self) -> bool; fn should_auto_hide_scrollbars(&self) -> bool;
fn read_from_clipboard(&self) -> Option<ClipboardItem>; fn read_from_clipboard(&self) -> Option<ClipboardItem>;
@@ -1818,9 +1826,6 @@ pub enum CursorStyle {
/// A cursor indicating that the operation will result in a context menu /// A cursor indicating that the operation will result in a context menu
/// corresponds to the CSS cursor value `context-menu` /// corresponds to the CSS cursor value `context-menu`
ContextualMenu, ContextualMenu,
/// Hide the cursor
None,
} }
/// A clipboard item that should be copied to the clipboard /// A clipboard item that should be copied to the clipboard
@@ -403,6 +403,12 @@ impl Platform for TestPlatform {
*self.active_cursor.lock() = style; *self.active_cursor.lock() = style;
} }
fn hide_cursor_until_mouse_moves(&self) {}
fn is_cursor_visible(&self) -> bool {
true
}
fn should_auto_hide_scrollbars(&self) -> bool { fn should_auto_hide_scrollbars(&self) -> bool {
false false
} }
+8
View File
@@ -202,6 +202,14 @@ impl Platform for VisualTestPlatform {
self.platform.set_cursor_style(style) self.platform.set_cursor_style(style)
} }
fn hide_cursor_until_mouse_moves(&self) {
self.platform.hide_cursor_until_mouse_moves();
}
fn is_cursor_visible(&self) -> bool {
self.platform.is_cursor_visible()
}
fn should_auto_hide_scrollbars(&self) -> bool { fn should_auto_hide_scrollbars(&self) -> bool {
self.platform.should_auto_hide_scrollbars() self.platform.should_auto_hide_scrollbars()
} }
+40 -19
View File
@@ -3,21 +3,22 @@ use crate::Inspector;
use crate::{ use crate::{
Action, AnyDrag, AnyElement, AnyImageCache, AnyTooltip, AnyView, App, AppContext, Arena, Asset, Action, AnyDrag, AnyElement, AnyImageCache, AnyTooltip, AnyView, App, AppContext, Arena, Asset,
AsyncWindowContext, AvailableSpace, Background, BorderStyle, Bounds, BoxShadow, Capslock, AsyncWindowContext, AvailableSpace, Background, BorderStyle, Bounds, BoxShadow, Capslock,
Context, Corners, CursorStyle, Decorations, DevicePixels, DispatchActionListener, Context, Corners, CursorHideMode, CursorStyle, Decorations, DevicePixels,
DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity,
FileDropEvent, FontId, Global, GlobalElementId, GlyphId, GpuSpecs, Hsla, InputHandler, IsZero, EntityId, EventEmitter, FileDropEvent, FontId, Global, GlobalElementId, GlyphId, GpuSpecs,
KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke, KeystrokeEvent, LayoutId, Hsla, InputHandler, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke,
LineLayoutIndex, Modifiers, ModifiersChangedEvent, MonochromeSprite, MouseButton, MouseEvent, KeystrokeEvent, LayoutId, LineLayoutIndex, Modifiers, ModifiersChangedEvent, MonochromeSprite,
MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, MouseButton, MouseEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas,
PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, Priority, PromptButton, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite,
PromptLevel, Quad, Render, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, Priority, PromptButton, PromptLevel, Quad, Render, RenderGlyphParams, RenderImage,
Replay, ResizeEdge, SMOOTH_SVG_SCALE_FACTOR, SUBPIXEL_VARIANTS_X, SUBPIXEL_VARIANTS_Y, RenderImageParams, RenderSvgParams, Replay, ResizeEdge, SMOOTH_SVG_SCALE_FACTOR,
ScaledPixels, Scene, Shadow, SharedString, Size, StrikethroughStyle, Style, SubpixelSprite, SUBPIXEL_VARIANTS_X, SUBPIXEL_VARIANTS_Y, ScaledPixels, Scene, Shadow, SharedString, Size,
SubscriberSet, Subscription, SystemWindowTab, SystemWindowTabController, TabStopMap, StrikethroughStyle, Style, SubpixelSprite, SubscriberSet, Subscription, SystemWindowTab,
TaffyLayoutEngine, Task, TextRenderingMode, TextStyle, TextStyleRefinement, ThermalState, SystemWindowTabController, TabStopMap, TaffyLayoutEngine, Task, TextRenderingMode, TextStyle,
TransformationMatrix, Underline, UnderlineStyle, WindowAppearance, WindowBackgroundAppearance, TextStyleRefinement, ThermalState, TransformationMatrix, Underline, UnderlineStyle,
WindowBounds, WindowControls, WindowDecorations, WindowOptions, WindowParams, WindowTextSystem, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations,
point, prelude::*, px, rems, size, transparent_black, WindowOptions, WindowParams, WindowTextSystem, point, prelude::*, px, rems, size,
transparent_black,
}; };
use anyhow::{Context as _, Result, anyhow}; use anyhow::{Context as _, Result, anyhow};
use collections::{FxHashMap, FxHashSet}; use collections::{FxHashMap, FxHashSet};
@@ -1457,10 +1458,6 @@ impl Window {
move |active| { move |active| {
handle handle
.update(&mut cx, |_, window, cx| { .update(&mut cx, |_, window, cx| {
if !active {
cx.platform.set_cursor_style(CursorStyle::Arrow);
}
window.active.set(active); window.active.set(active);
window.modifiers = window.platform_window.modifiers(); window.modifiers = window.platform_window.modifiers();
window.capslock = window.platform_window.capslock(); window.capslock = window.platform_window.capslock();
@@ -4473,6 +4470,14 @@ impl Window {
} else if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() { } else if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
self.pending_modifier.saw_keystroke = true; self.pending_modifier.saw_keystroke = true;
keystroke = Some(key_down_event.keystroke.clone()); keystroke = Some(key_down_event.keystroke.clone());
if key_down_event.keystroke.key_char.is_some()
&& matches!(
cx.cursor_hide_mode,
CursorHideMode::OnTyping | CursorHideMode::OnTypingAndAction
)
{
cx.platform.hide_cursor_until_mouse_moves();
}
} }
let Some(keystroke) = keystroke else { let Some(keystroke) = keystroke else {
@@ -4738,6 +4743,22 @@ impl Window {
node_id: DispatchNodeId, node_id: DispatchNodeId,
action: &dyn Action, action: &dyn Action,
cx: &mut App, cx: &mut App,
) {
self.dispatch_action_on_node_inner(node_id, action, cx);
if !cx.propagate_event
&& cx.cursor_hide_mode == CursorHideMode::OnTypingAndAction
&& self.last_input_was_keyboard()
{
cx.platform.hide_cursor_until_mouse_moves();
}
}
fn dispatch_action_on_node_inner(
&mut self,
node_id: DispatchNodeId,
action: &dyn Action,
cx: &mut App,
) { ) {
let dispatch_path = self.rendered_frame.dispatch_tree.dispatch_path(node_id); let dispatch_path = self.rendered_frame.dispatch_tree.dispatch_path(node_id);
+12 -6
View File
@@ -80,6 +80,10 @@ pub(crate) trait LinuxClient {
options: WindowParams, options: WindowParams,
) -> anyhow::Result<Box<dyn PlatformWindow>>; ) -> anyhow::Result<Box<dyn PlatformWindow>>;
fn set_cursor_style(&self, style: CursorStyle); fn set_cursor_style(&self, style: CursorStyle);
fn hide_cursor_until_mouse_moves(&self) {}
fn is_cursor_visible(&self) -> bool {
true
}
fn open_uri(&self, uri: &str); fn open_uri(&self, uri: &str);
fn reveal_path(&self, path: PathBuf); fn reveal_path(&self, path: PathBuf);
fn write_to_primary(&self, item: ClipboardItem); fn write_to_primary(&self, item: ClipboardItem);
@@ -530,6 +534,14 @@ impl<P: LinuxClient + 'static> Platform for LinuxPlatform<P> {
self.inner.set_cursor_style(style) self.inner.set_cursor_style(style)
} }
fn hide_cursor_until_mouse_moves(&self) {
self.inner.hide_cursor_until_mouse_moves()
}
fn is_cursor_visible(&self) -> bool {
self.inner.is_cursor_visible()
}
fn should_auto_hide_scrollbars(&self) -> bool { fn should_auto_hide_scrollbars(&self) -> bool {
self.inner.with_common(|common| common.auto_hide_scrollbars) self.inner.with_common(|common| common.auto_hide_scrollbars)
} }
@@ -776,12 +788,6 @@ pub(super) fn cursor_style_to_icon_names(style: CursorStyle) -> &'static [&'stat
CursorStyle::DragLink => &["alias"], CursorStyle::DragLink => &["alias"],
CursorStyle::DragCopy => &["copy"], CursorStyle::DragCopy => &["copy"],
CursorStyle::ContextualMenu => &["context-menu"], CursorStyle::ContextualMenu => &["context-menu"],
CursorStyle::None => {
#[cfg(debug_assertions)]
panic!("CursorStyle::None should be handled separately in the client");
#[cfg(not(debug_assertions))]
&[DEFAULT_CURSOR_ICON_NAME]
}
} }
} }
-6
View File
@@ -37,11 +37,5 @@ pub(super) fn to_shape(style: CursorStyle) -> Shape {
CursorStyle::DragLink => Shape::Alias, CursorStyle::DragLink => Shape::Alias,
CursorStyle::DragCopy => Shape::Copy, CursorStyle::DragCopy => Shape::Copy,
CursorStyle::ContextualMenu => Shape::ContextMenu, 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
}
} }
} }
+101 -33
View File
@@ -256,6 +256,7 @@ pub(crate) struct WaylandClientState {
keyboard_focused_window: Option<WaylandWindowStatePtr>, keyboard_focused_window: Option<WaylandWindowStatePtr>,
loop_handle: LoopHandle<'static, WaylandClientStatePtr>, loop_handle: LoopHandle<'static, WaylandClientStatePtr>,
cursor_style: Option<CursorStyle>, cursor_style: Option<CursorStyle>,
cursor_hidden_window: Option<WaylandWindowStatePtr>,
clipboard: Clipboard, clipboard: Clipboard,
data_offers: Vec<DataOffer<WlDataOffer>>, data_offers: Vec<DataOffer<WlDataOffer>>,
primary_data_offer: Option<DataOffer<ZwpPrimarySelectionOfferV1>>, primary_data_offer: Option<DataOffer<ZwpPrimarySelectionOfferV1>>,
@@ -410,6 +411,65 @@ impl WaylandClientStatePtr {
{ {
state.keyboard_focused_window = Some(window); state.keyboard_focused_window = Some(window);
} }
if let Some(window) = state.cursor_hidden_window.take()
&& !window.ptr_eq(&closed_window)
{
state.cursor_hidden_window = Some(window);
}
}
}
impl WaylandClientState {
fn hide_cursor_until_mouse_moves(&mut self) {
if self.cursor_hidden_window.is_some() {
return;
}
let Some(focused_window) = self.mouse_focused_window.clone() else {
// No surface to apply the hidden cursor to.
return;
};
let Some(wl_pointer) = self.wl_pointer.clone() else {
// Seat lost its pointer capability; nothing to hide.
return;
};
let serial = self.serial_tracker.get(SerialKind::MouseEnter);
wl_pointer.set_cursor(serial, None, 0, 0);
self.cursor_hidden_window = Some(focused_window);
}
fn restore_cursor_after_hide(&mut self) {
if self.cursor_hidden_window.take().is_none() {
return;
}
let Some(style) = self.cursor_style else {
return;
};
let serial = self.serial_tracker.get(SerialKind::MouseEnter);
if let Some(cursor_shape_device) = &self.cursor_shape_device {
cursor_shape_device.set_shape(serial, to_shape(style));
return;
}
let Some(focused_window) = self.mouse_focused_window.clone() else {
log::warn!(
"wayland: no focused surface to restore cursor style {:?} after hide; cursor may stay invisible",
style
);
return;
};
let Some(wl_pointer) = self.wl_pointer.clone() else {
log::warn!(
"wayland: no wl_pointer to restore cursor style {:?} after hide; cursor may stay invisible",
style
);
return;
};
let scale = focused_window.primary_output_scale();
self.cursor.set_icon(
&wl_pointer,
serial,
cursor_style_to_icon_names(style),
scale,
);
} }
} }
@@ -665,6 +725,7 @@ impl WaylandClient {
loop_handle: handle.clone(), loop_handle: handle.clone(),
enter_token: None, enter_token: None,
cursor_style: None, cursor_style: None,
cursor_hidden_window: None,
clipboard: Clipboard::new(conn.clone(), handle.clone()), clipboard: Clipboard::new(conn.clone(), handle.clone()),
data_offers: Vec::new(), data_offers: Vec::new(),
primary_data_offer: None, primary_data_offer: None,
@@ -785,33 +846,42 @@ impl LinuxClient for WaylandClient {
.as_ref() .as_ref()
.is_some_and(|w| !w.is_blocked())); .is_some_and(|w| !w.is_blocked()));
if need_update { if !need_update {
let serial = state.serial_tracker.get(SerialKind::MouseEnter); return;
state.cursor_style = Some(style);
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, to_shape(style));
} else if let Some(focused_window) = &state.mouse_focused_window {
// cursor-shape-v1 isn't supported, set the cursor using a surface.
let wl_pointer = state
.wl_pointer
.clone()
.expect("window is focused by pointer");
let scale = focused_window.primary_output_scale();
state.cursor.set_icon(
&wl_pointer,
serial,
cursor_style_to_icon_names(style),
scale,
);
}
} }
state.cursor_style = Some(style);
// Don't clobber the invisible cursor; restore reads back from `cursor_style`.
if state.cursor_hidden_window.is_some() {
return;
}
let serial = state.serial_tracker.get(SerialKind::MouseEnter);
if let Some(cursor_shape_device) = &state.cursor_shape_device {
cursor_shape_device.set_shape(serial, to_shape(style));
} else if let Some(focused_window) = &state.mouse_focused_window {
// cursor-shape-v1 isn't supported, set the cursor using a surface.
let wl_pointer = state
.wl_pointer
.clone()
.expect("window is focused by pointer");
let scale = focused_window.primary_output_scale();
state.cursor.set_icon(
&wl_pointer,
serial,
cursor_style_to_icon_names(style),
scale,
);
}
}
fn hide_cursor_until_mouse_moves(&self) {
self.0.borrow_mut().hide_cursor_until_mouse_moves();
}
fn is_cursor_visible(&self) -> bool {
self.0.borrow().cursor_hidden_window.is_none()
} }
fn open_uri(&self, uri: &str) { fn open_uri(&self, uri: &str) {
@@ -1432,6 +1502,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
state.enter_token.take(); state.enter_token.take();
// Prevent keyboard events from repeating after opening e.g. a file chooser and closing it quickly // Prevent keyboard events from repeating after opening e.g. a file chooser and closing it quickly
state.repeat.current_id += 1; state.repeat.current_id += 1;
state.restore_cursor_after_hide();
if let Some(window) = keyboard_focused_window { if let Some(window) = keyboard_focused_window {
if let Some(ref mut compose) = state.compose_state { if let Some(ref mut compose) = state.compose_state {
@@ -1732,14 +1803,9 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
if state.enter_token.is_some() { if state.enter_token.is_some() {
state.enter_token = None; state.enter_token = None;
} }
state.restore_cursor_after_hide();
if let Some(style) = state.cursor_style { if let Some(style) = state.cursor_style {
if let CursorStyle::None = style { if let Some(cursor_shape_device) = &state.cursor_shape_device {
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, to_shape(style)); cursor_shape_device.set_shape(serial, to_shape(style));
} else { } else {
let scale = window.primary_output_scale(); let scale = window.primary_output_scale();
@@ -1765,6 +1831,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
state.mouse_focused_window = None; state.mouse_focused_window = None;
state.mouse_location = None; state.mouse_location = None;
state.button_pressed = None; state.button_pressed = None;
state.cursor_hidden_window = None;
drop(state); drop(state);
focused_window.handle_input(input); focused_window.handle_input(input);
@@ -1780,6 +1847,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
return; return;
} }
state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32))); state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32)));
state.restore_cursor_after_hide();
if let Some(window) = state.mouse_focused_window.clone() { if let Some(window) = state.mouse_focused_window.clone() {
if window.is_blocked() { if window.is_blocked() {
+116 -33
View File
@@ -211,6 +211,8 @@ pub struct X11ClientState {
pub(crate) cursor_handle: cursor::Handle, pub(crate) cursor_handle: cursor::Handle,
pub(crate) cursor_styles: HashMap<xproto::Window, CursorStyle>, pub(crate) cursor_styles: HashMap<xproto::Window, CursorStyle>,
pub(crate) cursor_cache: HashMap<CursorStyle, Option<xproto::Cursor>>, pub(crate) cursor_cache: HashMap<CursorStyle, Option<xproto::Cursor>>,
pub(crate) invisible_cursor_cache: Option<xproto::Cursor>,
pub(crate) cursor_hidden_window: Option<xproto::Window>,
pointer_device_states: BTreeMap<xinput::DeviceId, PointerDeviceState>, pointer_device_states: BTreeMap<xinput::DeviceId, PointerDeviceState>,
@@ -249,6 +251,9 @@ impl X11ClientStatePtr {
if state.keyboard_focused_window == Some(x_window) { if state.keyboard_focused_window == Some(x_window) {
state.keyboard_focused_window = None; state.keyboard_focused_window = None;
} }
if state.cursor_hidden_window == Some(x_window) {
state.cursor_hidden_window = None;
}
state.cursor_styles.remove(&x_window); state.cursor_styles.remove(&x_window);
} }
@@ -543,6 +548,8 @@ impl X11Client {
cursor_handle, cursor_handle,
cursor_styles: HashMap::default(), cursor_styles: HashMap::default(),
cursor_cache: HashMap::default(), cursor_cache: HashMap::default(),
cursor_hidden_window: None,
invisible_cursor_cache: None,
pointer_device_states, pointer_device_states,
@@ -971,6 +978,7 @@ impl X11Client {
compose_state.reset(); compose_state.reset();
} }
state.pre_edit_text.take(); state.pre_edit_text.take();
state.restore_cursor_after_hide();
drop(state); drop(state);
self.reset_ime(); self.reset_ime();
window.handle_ime_delete(); window.handle_ime_delete();
@@ -1224,6 +1232,7 @@ impl X11Client {
Event::XinputMotion(event) => { Event::XinputMotion(event) => {
let window = self.get_window(event.event)?; let window = self.get_window(event.event)?;
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
state.restore_cursor_after_hide();
if window.is_blocked() { if window.is_blocked() {
// We want to set the cursor to the default arrow // We want to set the cursor to the default arrow
// when the window is blocked // when the window is blocked
@@ -1286,6 +1295,7 @@ impl X11Client {
window.set_hovered(true); window.set_hovered(true);
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
state.mouse_focused_window = Some(event.event); state.mouse_focused_window = Some(event.event);
state.restore_cursor_after_hide();
} }
Event::XinputLeave(event) if event.mode == xinput::NotifyMode::NORMAL => { Event::XinputLeave(event) if event.mode == xinput::NotifyMode::NORMAL => {
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
@@ -1667,11 +1677,17 @@ impl LinuxClient for X11Client {
return; return;
} }
state.cursor_styles.insert(focused_window, style);
// Don't clobber the invisible cursor; restore reads back from `cursor_styles`.
if state.cursor_hidden_window == Some(focused_window) {
return;
}
let Some(cursor) = state.get_cursor_icon(style) else { let Some(cursor) = state.get_cursor_icon(style) else {
return; return;
}; };
state.cursor_styles.insert(focused_window, style);
check_reply( check_reply(
|| "Failed to set cursor style", || "Failed to set cursor style",
state.xcb_connection.change_window_attributes( state.xcb_connection.change_window_attributes(
@@ -1686,6 +1702,14 @@ impl LinuxClient for X11Client {
state.xcb_connection.flush().log_err(); state.xcb_connection.flush().log_err();
} }
fn hide_cursor_until_mouse_moves(&self) {
self.0.borrow_mut().hide_cursor_until_mouse_moves();
}
fn is_cursor_visible(&self) -> bool {
self.0.borrow().cursor_hidden_window.is_none()
}
fn open_uri(&self, uri: &str) { fn open_uri(&self, uri: &str) {
#[cfg(any(feature = "wayland", feature = "x11"))] #[cfg(any(feature = "wayland", feature = "x11"))]
open_uri_internal( open_uri_internal(
@@ -1986,41 +2010,33 @@ impl X11ClientState {
return *cursor; return *cursor;
} }
let result; let result = 'outer: {
match style { let mut errors = String::new();
CursorStyle::None => match create_invisible_cursor(&self.xcb_connection) { let cursor_icon_names = cursor_style_to_icon_names(style);
Ok(loaded_cursor) => result = Ok(loaded_cursor), for cursor_icon_name in cursor_icon_names {
Err(err) => result = Err(err.context("X11: error while creating invisible cursor")), match self
}, .cursor_handle
_ => 'outer: { .load_cursor(&self.xcb_connection, cursor_icon_name)
let mut errors = String::new(); {
let cursor_icon_names = cursor_style_to_icon_names(style); Ok(loaded_cursor) => {
for cursor_icon_name in cursor_icon_names { if loaded_cursor != x11rb::NONE {
match self break 'outer Ok(loaded_cursor);
.cursor_handle
.load_cursor(&self.xcb_connection, cursor_icon_name)
{
Ok(loaded_cursor) => {
if loaded_cursor != x11rb::NONE {
result = Ok(loaded_cursor);
break 'outer;
}
}
Err(err) => {
errors.push_str(&err.to_string());
errors.push('\n');
} }
} }
Err(err) => {
errors.push_str(&err.to_string());
errors.push('\n');
}
} }
if errors.is_empty() { }
result = Err(anyhow!( if errors.is_empty() {
"errors while loading cursor icons {:?}:\n{}", Err(anyhow!(
cursor_icon_names, "errors while loading cursor icons {:?}:\n{}",
errors cursor_icon_names,
)); errors
} else { ))
result = Err(anyhow!("did not find cursor icons {:?}", cursor_icon_names)); } else {
} Err(anyhow!("did not find cursor icons {:?}", cursor_icon_names))
} }
}; };
@@ -2052,6 +2068,73 @@ impl X11ClientState {
self.cursor_cache.insert(style, cursor); self.cursor_cache.insert(style, cursor);
cursor cursor
} }
fn get_or_create_invisible_cursor(&mut self) -> Option<xproto::Cursor> {
if let Some(cursor) = self.invisible_cursor_cache {
return Some(cursor);
}
let cursor = create_invisible_cursor(&self.xcb_connection)
.context("X11: error while creating invisible cursor")
.log_err()?;
self.invisible_cursor_cache = Some(cursor);
Some(cursor)
}
fn hide_cursor_until_mouse_moves(&mut self) {
if self.cursor_hidden_window.is_some() {
return;
}
let Some(focused_window) = self.mouse_focused_window else {
// No window to apply the per-window invisible cursor to.
return;
};
let Some(invisible_cursor) = self.get_or_create_invisible_cursor() else {
return;
};
check_reply(
|| "Failed to hide cursor",
self.xcb_connection.change_window_attributes(
focused_window,
&ChangeWindowAttributesAux {
cursor: Some(invisible_cursor),
..Default::default()
},
),
)
.log_err();
self.xcb_connection.flush().log_err();
self.cursor_hidden_window = Some(focused_window);
}
fn restore_cursor_after_hide(&mut self) {
let Some(hidden_window) = self.cursor_hidden_window.take() else {
return;
};
let style = self
.cursor_styles
.get(&hidden_window)
.copied()
.unwrap_or(CursorStyle::Arrow);
let Some(cursor) = self.get_cursor_icon(style) else {
log::warn!(
"X11: no cursor icon available to restore {:?} after hide; cursor may stay invisible",
style
);
return;
};
check_reply(
|| "Failed to restore cursor style after hide",
self.xcb_connection.change_window_attributes(
hidden_window,
&ChangeWindowAttributesAux {
cursor: Some(cursor),
..Default::default()
},
),
)
.log_err();
self.xcb_connection.flush().log_err();
}
} }
// Adapted from: // Adapted from:
+25 -8
View File
@@ -52,7 +52,10 @@ use std::{
ptr, ptr,
rc::Rc, rc::Rc,
slice, str, slice, str,
sync::{Arc, OnceLock, atomic::AtomicBool}, sync::{
Arc, OnceLock,
atomic::{AtomicBool, Ordering},
},
}; };
use util::{ use util::{
ResultExt, ResultExt,
@@ -179,7 +182,8 @@ pub(crate) struct MacPlatformState {
dock_menu: Option<id>, dock_menu: Option<id>,
menus: Option<Vec<OwnedMenu>>, menus: Option<Vec<OwnedMenu>>,
keyboard_mapper: Rc<MacKeyboardMapper>, keyboard_mapper: Rc<MacKeyboardMapper>,
cursor_hidden: Arc<AtomicBool>, /// Mirrors `[NSCursor setHiddenUntilMouseMoves:]` state, which AppKit doesn't expose.
cursor_visible: Arc<AtomicBool>,
} }
impl MacPlatform { impl MacPlatform {
@@ -216,7 +220,7 @@ impl MacPlatform {
on_thermal_state_change: None, on_thermal_state_change: None,
menus: None, menus: None,
keyboard_mapper, keyboard_mapper,
cursor_hidden: Arc::new(AtomicBool::new(false)), cursor_visible: Arc::new(AtomicBool::new(true)),
})) }))
} }
@@ -621,10 +625,10 @@ impl Platform for MacPlatform {
handle: AnyWindowHandle, handle: AnyWindowHandle,
options: WindowParams, options: WindowParams,
) -> Result<Box<dyn PlatformWindow>> { ) -> Result<Box<dyn PlatformWindow>> {
let (cursor_hidden, foreground_executor, background_executor, renderer_context) = { let (cursor_visible, foreground_executor, background_executor, renderer_context) = {
let guard = self.0.lock(); let guard = self.0.lock();
( (
guard.cursor_hidden.clone(), guard.cursor_visible.clone(),
guard.foreground_executor.clone(), guard.foreground_executor.clone(),
guard.background_executor.clone(), guard.background_executor.clone(),
guard.renderer_context.clone(), guard.renderer_context.clone(),
@@ -634,7 +638,7 @@ impl Platform for MacPlatform {
Ok(Box::new(MacWindow::open( Ok(Box::new(MacWindow::open(
handle, handle,
options, options,
cursor_hidden, cursor_visible,
foreground_executor, foreground_executor,
background_executor, background_executor,
renderer_context, renderer_context,
@@ -991,12 +995,25 @@ impl Platform for MacPlatform {
/// Match cursor style to one of the styles available /// Match cursor style to one of the styles available
/// in macOS's [NSCursor](https://developer.apple.com/documentation/appkit/nscursor). /// in macOS's [NSCursor](https://developer.apple.com/documentation/appkit/nscursor).
fn set_cursor_style(&self, style: CursorStyle) { fn set_cursor_style(&self, style: CursorStyle) {
let cursor_hidden = self.0.lock().cursor_hidden.clone();
unsafe { unsafe {
set_active_window_cursor_style(style, &cursor_hidden); set_active_window_cursor_style(style);
} }
} }
fn hide_cursor_until_mouse_moves(&self) {
let cursor_visible = self.0.lock().cursor_visible.clone();
if !cursor_visible.swap(false, Ordering::Relaxed) {
return;
}
unsafe {
let _: () = msg_send![class!(NSCursor), setHiddenUntilMouseMoves: YES];
}
}
fn is_cursor_visible(&self) -> bool {
self.0.lock().cursor_visible.load(Ordering::Relaxed)
}
fn should_auto_hide_scrollbars(&self) -> bool { fn should_auto_hide_scrollbars(&self) -> bool {
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
const NSScrollerStyleOverlay: NSInteger = 1; const NSScrollerStyleOverlay: NSInteger = 1;
+22 -49
View File
@@ -324,10 +324,7 @@ pub(crate) fn convert_mouse_position(position: NSPoint, window_height: Pixels) -
/// This function is not thread safe. Callers must ensure this is called on the AppKit main /// This function is not thread safe. Callers must ensure this is called on the AppKit main
/// thread because it reads the active AppKit window and updates GPUI window state associated /// thread because it reads the active AppKit window and updates GPUI window state associated
/// with Objective-C objects. /// with Objective-C objects.
pub(crate) unsafe fn set_active_window_cursor_style( pub(crate) unsafe fn set_active_window_cursor_style(style: CursorStyle) {
style: CursorStyle,
cursor_hidden: &AtomicBool,
) {
// SAFETY: The caller guarantees AppKit main-thread access. The class check ensures the // SAFETY: The caller guarantees AppKit main-thread access. The class check ensures the
// window has our WINDOW_STATE_IVAR before reading it. // window has our WINDOW_STATE_IVAR before reading it.
unsafe { unsafe {
@@ -345,9 +342,6 @@ pub(crate) unsafe fn set_active_window_cursor_style(
}; };
let Some(active_window) = active_window else { let Some(active_window) = active_window else {
if !matches!(style, CursorStyle::None) {
unhide_cursor(cursor_hidden);
}
return; return;
}; };
@@ -363,22 +357,6 @@ pub(crate) unsafe fn set_active_window_cursor_style(
} }
} }
/// Unhides the cursor if this GPUI platform instance has hidden it.
///
/// # Safety
///
/// Must be called on the AppKit main thread.
unsafe fn unhide_cursor(cursor_hidden: &AtomicBool) {
unsafe {
if cursor_hidden
.compare_exchange(true, false, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
let _: () = msg_send![class!(NSCursor), unhide];
}
}
}
unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const Class { unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const Class {
unsafe { unsafe {
let mut decl = ClassDecl::new(name, superclass).unwrap(); let mut decl = ClassDecl::new(name, superclass).unwrap();
@@ -496,7 +474,7 @@ struct MacWindowState {
blurred_view: Option<id>, blurred_view: Option<id>,
background_appearance: WindowBackgroundAppearance, background_appearance: WindowBackgroundAppearance,
cursor_style: CursorStyle, cursor_style: CursorStyle,
cursor_hidden: Arc<AtomicBool>, cursor_visible: Arc<AtomicBool>,
display_link: Option<DisplayLink>, display_link: Option<DisplayLink>,
renderer: renderer::Renderer, renderer: renderer::Renderer,
request_frame_callback: Option<Box<dyn FnMut(RequestFrameOptions)>>, request_frame_callback: Option<Box<dyn FnMut(RequestFrameOptions)>>,
@@ -698,7 +676,7 @@ impl MacWindow {
tabbing_identifier, tabbing_identifier,
.. ..
}: WindowParams, }: WindowParams,
cursor_hidden: Arc<AtomicBool>, cursor_visible: Arc<AtomicBool>,
foreground_executor: ForegroundExecutor, foreground_executor: ForegroundExecutor,
background_executor: BackgroundExecutor, background_executor: BackgroundExecutor,
renderer_context: renderer::Context, renderer_context: renderer::Context,
@@ -816,7 +794,7 @@ impl MacWindow {
blurred_view: None, blurred_view: None,
background_appearance: WindowBackgroundAppearance::Opaque, background_appearance: WindowBackgroundAppearance::Opaque,
cursor_style: CursorStyle::Arrow, cursor_style: CursorStyle::Arrow,
cursor_hidden, cursor_visible,
display_link: None, display_link: None,
renderer: renderer::new_renderer( renderer: renderer::new_renderer(
renderer_context, renderer_context,
@@ -1850,26 +1828,7 @@ extern "C" fn reset_cursor_rects(this: &Object, _: Sel) {
let _: () = msg_send![super(this, class!(NSView)), resetCursorRects]; let _: () = msg_send![super(this, class!(NSView)), resetCursorRects];
let window_state = get_window_state(this); let window_state = get_window_state(this);
let cursor_style; let cursor_style = window_state.lock().cursor_style;
let cursor_hidden;
{
let window_state = window_state.lock();
if matches!(window_state.cursor_style, CursorStyle::None) {
if window_state
.cursor_hidden
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
let _: () = msg_send![class!(NSCursor), hide];
}
return;
}
cursor_style = window_state.cursor_style;
cursor_hidden = window_state.cursor_hidden.clone();
};
let cursor: id = match cursor_style { let cursor: id = match cursor_style {
CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor], CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
@@ -1905,11 +1864,8 @@ extern "C" fn reset_cursor_rects(this: &Object, _: Sel) {
CursorStyle::DragLink => msg_send![class!(NSCursor), dragLinkCursor], CursorStyle::DragLink => msg_send![class!(NSCursor), dragLinkCursor],
CursorStyle::DragCopy => msg_send![class!(NSCursor), dragCopyCursor], CursorStyle::DragCopy => msg_send![class!(NSCursor), dragCopyCursor],
CursorStyle::ContextualMenu => msg_send![class!(NSCursor), contextualMenuCursor], CursorStyle::ContextualMenu => msg_send![class!(NSCursor), contextualMenuCursor],
CursorStyle::None => unreachable!(),
}; };
unhide_cursor(&cursor_hidden);
let bounds = NSView::bounds(this as *const Object as id); let bounds = NSView::bounds(this as *const Object as id);
let _: () = msg_send![this, addCursorRect: bounds cursor: cursor]; let _: () = msg_send![this, addCursorRect: bounds cursor: cursor];
} }
@@ -2140,6 +2096,20 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
let event = unsafe { platform_input_from_native(native_event, Some(window_height)) }; let event = unsafe { platform_input_from_native(native_event, Some(window_height)) };
if let Some(mut event) = event { if let Some(mut event) = event {
// AppKit unhides the cursor on the next mouse movement; mirror that here.
if matches!(
event,
PlatformInput::MouseMove(_)
| PlatformInput::MouseDown(_)
| PlatformInput::MouseUp(_)
| PlatformInput::MousePressure(_)
| PlatformInput::MouseExited(_)
| PlatformInput::ScrollWheel(_)
| PlatformInput::Pinch(_)
) {
lock.cursor_visible.store(true, Ordering::Relaxed);
}
match &mut event { match &mut event {
PlatformInput::MouseDown( PlatformInput::MouseDown(
event @ MouseDownEvent { event @ MouseDownEvent {
@@ -2365,6 +2335,9 @@ extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id)
let lock = window_state.lock(); let lock = window_state.lock();
let is_active = unsafe { lock.native_window.isKeyWindow() == YES }; let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
// AppKit also unhides the cursor on activation changes, so mirror that here.
lock.cursor_visible.store(true, Ordering::Relaxed);
// When opening a pop-up while the application isn't active, Cocoa sends a spurious // When opening a pop-up while the application isn't active, Cocoa sends a spurious
// `windowDidBecomeKey` message to the previous key window even though that window // `windowDidBecomeKey` message to the previous key window even though that window
// isn't actually key. This causes a bug if the application is later activated while // isn't actually key. This causes a bug if the application is later activated while
-6
View File
@@ -327,12 +327,6 @@ pub fn cursor_style_methods(input: TokenStream) -> TokenStream {
self 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() output.into()
+99 -8
View File
@@ -13,11 +13,12 @@ use gpui::{
use gpui_wgpu::WgpuContext; use gpui_wgpu::WgpuContext;
use std::{ use std::{
borrow::Cow, borrow::Cow,
cell::RefCell, cell::{Cell, RefCell},
path::{Path, PathBuf}, path::{Path, PathBuf},
rc::Rc, rc::Rc,
sync::Arc, sync::Arc,
}; };
use wasm_bindgen::prelude::*;
static BUNDLED_FONTS: &[&[u8]] = &[ static BUNDLED_FONTS: &[&[u8]] = &[
include_bytes!("../../../assets/fonts/ibm-plex-sans/IBMPlexSans-Regular.ttf"), include_bytes!("../../../assets/fonts/ibm-plex-sans/IBMPlexSans-Regular.ttf"),
@@ -39,6 +40,9 @@ pub struct WebPlatform {
active_display: Rc<dyn PlatformDisplay>, active_display: Rc<dyn PlatformDisplay>,
callbacks: RefCell<WebPlatformCallbacks>, callbacks: RefCell<WebPlatformCallbacks>,
wgpu_context: Rc<RefCell<Option<WgpuContext>>>, wgpu_context: Rc<RefCell<Option<WgpuContext>>>,
cursor_visible: Rc<Cell<bool>>,
last_cursor_css: Rc<Cell<&'static str>>,
_cursor_restore_listeners: Vec<EventListenerHandle>,
} }
#[derive(Default)] #[derive(Default)]
@@ -77,6 +81,14 @@ impl WebPlatform {
let active_display: Rc<dyn PlatformDisplay> = let active_display: Rc<dyn PlatformDisplay> =
Rc::new(WebDisplay::new(browser_window.clone())); Rc::new(WebDisplay::new(browser_window.clone()));
let cursor_visible = Rc::new(Cell::new(true));
let last_cursor_css = Rc::new(Cell::new("default"));
let cursor_restore_listeners = cursor_restore_listeners(
&browser_window,
cursor_visible.clone(),
last_cursor_css.clone(),
);
Self { Self {
browser_window, browser_window,
background_executor, background_executor,
@@ -86,6 +98,9 @@ impl WebPlatform {
active_display, active_display,
callbacks: RefCell::new(WebPlatformCallbacks::default()), callbacks: RefCell::new(WebPlatformCallbacks::default()),
wgpu_context: Rc::new(RefCell::new(None)), wgpu_context: Rc::new(RefCell::new(None)),
cursor_visible,
last_cursor_css,
_cursor_restore_listeners: cursor_restore_listeners,
} }
} }
} }
@@ -292,18 +307,25 @@ impl Platform for WebPlatform {
CursorStyle::DragLink => "alias", CursorStyle::DragLink => "alias",
CursorStyle::DragCopy => "copy", CursorStyle::DragCopy => "copy",
CursorStyle::ContextualMenu => "context-menu", CursorStyle::ContextualMenu => "context-menu",
CursorStyle::None => "none",
}; };
if let Some(document) = self.browser_window.document() { self.last_cursor_css.set(css_cursor);
if let Some(body) = document.body() { if self.cursor_visible.get() {
if let Err(error) = body.style().set_property("cursor", css_cursor) { set_body_cursor(&self.browser_window, css_cursor);
log::warn!("Failed to set cursor style: {error:?}");
}
}
} }
} }
fn hide_cursor_until_mouse_moves(&self) {
if !self.cursor_visible.replace(false) {
return;
}
set_body_cursor(&self.browser_window, "none");
}
fn is_cursor_visible(&self) -> bool {
self.cursor_visible.get()
}
fn should_auto_hide_scrollbars(&self) -> bool { fn should_auto_hide_scrollbars(&self) -> bool {
true true
} }
@@ -342,3 +364,72 @@ impl Platform for WebPlatform {
self.callbacks.borrow_mut().keyboard_layout_change = Some(callback); self.callbacks.borrow_mut().keyboard_layout_change = Some(callback);
} }
} }
struct EventListenerHandle {
target: web_sys::EventTarget,
event_name: &'static str,
closure: Closure<dyn FnMut(JsValue)>,
}
impl Drop for EventListenerHandle {
fn drop(&mut self) {
self.target
.remove_event_listener_with_callback(
self.event_name,
self.closure.as_ref().unchecked_ref(),
)
.ok();
}
}
fn cursor_restore_listeners(
browser_window: &web_sys::Window,
cursor_visible: Rc<Cell<bool>>,
last_cursor_css: Rc<Cell<&'static str>>,
) -> Vec<EventListenerHandle> {
let mut handles = Vec::new();
let Some(document) = browser_window.document() else {
return handles;
};
let make_restore_handler = |browser_window: web_sys::Window| {
let cursor_visible = cursor_visible.clone();
let last_cursor_css = last_cursor_css.clone();
Closure::<dyn FnMut(JsValue)>::new(move |_event: JsValue| {
if !cursor_visible.replace(true) {
set_body_cursor(&browser_window, last_cursor_css.get());
}
})
};
let mut add_listener = |target: &web_sys::EventTarget, event_name: &'static str| {
let closure = make_restore_handler(browser_window.clone());
target
.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())
.ok();
handles.push(EventListenerHandle {
target: target.clone(),
event_name,
closure,
});
};
let document_target: &web_sys::EventTarget = document.as_ref();
let window_target: &web_sys::EventTarget = browser_window.as_ref();
add_listener(document_target, "mousemove");
add_listener(document_target, "mouseenter");
add_listener(window_target, "blur");
add_listener(document_target, "visibilitychange");
handles
}
fn set_body_cursor(browser_window: &web_sys::Window, css_cursor: &str) {
if let Some(document) = browser_window.document()
&& let Some(body) = document.body()
&& let Err(error) = body.style().set_property("cursor", css_cursor)
{
log::warn!("Failed to set cursor style: {error:?}");
}
}
+25 -2
View File
@@ -1,4 +1,4 @@
use std::rc::Rc; use std::{rc::Rc, sync::atomic::Ordering};
use ::util::ResultExt; use ::util::ResultExt;
use anyhow::Context as _; use anyhow::Context as _;
@@ -297,6 +297,7 @@ impl WindowsWindowInner {
fn handle_mouse_move_msg(&self, handle: HWND, lparam: LPARAM, wparam: WPARAM) -> Option<isize> { fn handle_mouse_move_msg(&self, handle: HWND, lparam: LPARAM, wparam: WPARAM) -> Option<isize> {
self.start_tracking_mouse(handle, TME_LEAVE); self.start_tracking_mouse(handle, TME_LEAVE);
self.restore_cursor_after_hide();
let Some(mut func) = self.state.callbacks.input.take() else { let Some(mut func) = self.state.callbacks.input.take() else {
return Some(1); return Some(1);
@@ -330,6 +331,9 @@ impl WindowsWindowInner {
fn handle_mouse_leave_msg(&self) -> Option<isize> { fn handle_mouse_leave_msg(&self) -> Option<isize> {
self.state.hovered.set(false); self.state.hovered.set(false);
// The next window's `WM_SETCURSOR` picks its own cursor, so we just clear
// the flag for tight `is_cursor_visible()` semantics.
self.state.cursor_visible.store(true, Ordering::Relaxed);
if let Some(mut callback) = self.state.callbacks.hovered_status_change.take() { if let Some(mut callback) = self.state.callbacks.hovered_status_change.take() {
callback(false); callback(false);
self.state self.state
@@ -726,6 +730,10 @@ impl WindowsWindowInner {
let activated = wparam.loword() > 0; let activated = wparam.loword() > 0;
let this = self.clone(); let this = self.clone();
if !activated {
this.state.cursor_visible.store(true, Ordering::Relaxed);
}
// When the window is activated (gains focus), reset the modifier tracking state. // When the window is activated (gains focus), reset the modifier tracking state.
// This fixes the issue where Alt-Tab away and back leaves stale modifier state // This fixes the issue where Alt-Tab away and back leaves stale modifier state
// (especially the Alt key) because Windows doesn't always send key-up events to // (especially the Alt key) because Windows doesn't always send key-up events to
@@ -915,6 +923,7 @@ impl WindowsWindowInner {
fn handle_nc_mouse_move_msg(&self, handle: HWND, lparam: LPARAM) -> Option<isize> { fn handle_nc_mouse_move_msg(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {
self.start_tracking_mouse(handle, TME_LEAVE | TME_NONCLIENT); self.start_tracking_mouse(handle, TME_LEAVE | TME_NONCLIENT);
self.restore_cursor_after_hide();
let mut func = self.state.callbacks.input.take()?; let mut func = self.state.callbacks.input.take()?;
let scale_factor = self.state.scale_factor.get(); let scale_factor = self.state.scale_factor.get();
@@ -1078,8 +1087,13 @@ impl WindowsWindowInner {
{ {
return None; return None;
} }
let cursor = if self.state.cursor_visible.load(Ordering::Relaxed) {
self.state.current_cursor.get()
} else {
None
};
unsafe { unsafe {
SetCursor(self.state.current_cursor.get()); SetCursor(cursor);
}; };
Some(0) Some(0)
} }
@@ -1233,6 +1247,15 @@ impl WindowsWindowInner {
} }
} }
/// Clear the hidden flag and restore the cursor immediately
fn restore_cursor_after_hide(&self) {
if !self.state.cursor_visible.swap(true, Ordering::Relaxed) {
unsafe {
SetCursor(self.state.current_cursor.get());
}
}
}
fn start_tracking_mouse(&self, handle: HWND, flags: TRACKMOUSEEVENT_FLAGS) { fn start_tracking_mouse(&self, handle: HWND, flags: TRACKMOUSEEVENT_FLAGS) {
if !self.state.hovered.get() { if !self.state.hovered.get() {
self.state.hovered.set(true); self.state.hovered.set(true);
+30
View File
@@ -63,6 +63,8 @@ pub(crate) struct WindowsPlatformState {
jump_list: RefCell<JumpList>, jump_list: RefCell<JumpList>,
// NOTE: standard cursor handles don't need to close. // NOTE: standard cursor handles don't need to close.
pub(crate) current_cursor: Cell<Option<HCURSOR>>, pub(crate) current_cursor: Cell<Option<HCURSOR>>,
/// Shared with each window so `WM_SETCURSOR` can read it directly.
pub(crate) cursor_visible: Arc<AtomicBool>,
directx_devices: RefCell<Option<DirectXDevices>>, directx_devices: RefCell<Option<DirectXDevices>>,
} }
@@ -87,6 +89,7 @@ impl WindowsPlatformState {
callbacks, callbacks,
jump_list: RefCell::new(jump_list), jump_list: RefCell::new(jump_list),
current_cursor: Cell::new(current_cursor), current_cursor: Cell::new(current_cursor),
cursor_visible: Arc::new(AtomicBool::new(true)),
directx_devices: RefCell::new(directx_devices), directx_devices: RefCell::new(directx_devices),
menus: RefCell::new(Vec::new()), menus: RefCell::new(Vec::new()),
} }
@@ -219,6 +222,7 @@ impl WindowsPlatform {
icon: self.icon, icon: self.icon,
executor: self.foreground_executor.clone(), executor: self.foreground_executor.clone(),
current_cursor: self.inner.state.current_cursor.get(), current_cursor: self.inner.state.current_cursor.get(),
cursor_visible: self.inner.state.cursor_visible.clone(),
drop_target_helper: self.drop_target_helper.clone().unwrap(), drop_target_helper: self.drop_target_helper.clone().unwrap(),
validation_number: self.inner.validation_number, validation_number: self.inner.validation_number,
main_receiver: self.inner.main_receiver.clone(), main_receiver: self.inner.main_receiver.clone(),
@@ -682,6 +686,31 @@ impl Platform for WindowsPlatform {
} }
} }
fn hide_cursor_until_mouse_moves(&self) {
if !self
.inner
.state
.cursor_visible
.swap(false, Ordering::Relaxed)
{
return;
}
for handle in self.raw_window_handles.read().iter() {
let Some(window) = window_from_hwnd(handle.as_raw()) else {
continue;
};
if window.state.hovered.get() {
unsafe { SetCursor(None) };
break;
}
}
}
fn is_cursor_visible(&self) -> bool {
self.inner.state.cursor_visible.load(Ordering::Relaxed)
}
fn should_auto_hide_scrollbars(&self) -> bool { fn should_auto_hide_scrollbars(&self) -> bool {
should_auto_hide_scrollbars().log_err().unwrap_or(false) should_auto_hide_scrollbars().log_err().unwrap_or(false)
} }
@@ -1015,6 +1044,7 @@ pub(crate) struct WindowCreationInfo {
pub(crate) icon: HICON, pub(crate) icon: HICON,
pub(crate) executor: ForegroundExecutor, pub(crate) executor: ForegroundExecutor,
pub(crate) current_cursor: Option<HCURSOR>, pub(crate) current_cursor: Option<HCURSOR>,
pub(crate) cursor_visible: Arc<AtomicBool>,
pub(crate) drop_target_helper: IDropTargetHelper, pub(crate) drop_target_helper: IDropTargetHelper,
pub(crate) validation_number: usize, pub(crate) validation_number: usize,
pub(crate) main_receiver: PriorityQueueReceiver<RunnableVariant>, pub(crate) main_receiver: PriorityQueueReceiver<RunnableVariant>,
-1
View File
@@ -115,7 +115,6 @@ pub(crate) fn load_cursor(style: CursorStyle) -> Option<HCURSOR> {
CursorStyle::ResizeUpLeftDownRight => (&SIZENWSE, IDC_SIZENWSE), CursorStyle::ResizeUpLeftDownRight => (&SIZENWSE, IDC_SIZENWSE),
CursorStyle::ResizeUpRightDownLeft => (&SIZENESW, IDC_SIZENESW), CursorStyle::ResizeUpRightDownLeft => (&SIZENESW, IDC_SIZENESW),
CursorStyle::OperationNotAllowed => (&NO, IDC_NO), CursorStyle::OperationNotAllowed => (&NO, IDC_NO),
CursorStyle::None => return None,
_ => (&ARROW, IDC_ARROW), _ => (&ARROW, IDC_ARROW),
}; };
Some( Some(
+8
View File
@@ -66,6 +66,8 @@ pub struct WindowsWindowState {
pub click_state: ClickState, pub click_state: ClickState,
pub current_cursor: Cell<Option<HCURSOR>>, pub current_cursor: Cell<Option<HCURSOR>>,
/// Shared with [`WindowsPlatformState::cursor_visible`].
pub cursor_visible: Arc<AtomicBool>,
pub nc_button_pressed: Cell<Option<u32>>, pub nc_button_pressed: Cell<Option<u32>>,
pub display: Cell<WindowsDisplay>, pub display: Cell<WindowsDisplay>,
@@ -98,6 +100,7 @@ impl WindowsWindowState {
directx_devices: &DirectXDevices, directx_devices: &DirectXDevices,
window_params: &CREATESTRUCTW, window_params: &CREATESTRUCTW,
current_cursor: Option<HCURSOR>, current_cursor: Option<HCURSOR>,
cursor_visible: Arc<AtomicBool>,
display: WindowsDisplay, display: WindowsDisplay,
min_size: Option<Size<Pixels>>, min_size: Option<Size<Pixels>>,
appearance: WindowAppearance, appearance: WindowAppearance,
@@ -158,6 +161,7 @@ impl WindowsWindowState {
renderer: RefCell::new(renderer), renderer: RefCell::new(renderer),
click_state, click_state,
current_cursor: Cell::new(current_cursor), current_cursor: Cell::new(current_cursor),
cursor_visible,
nc_button_pressed: Cell::new(nc_button_pressed), nc_button_pressed: Cell::new(nc_button_pressed),
display: Cell::new(display), display: Cell::new(display),
fullscreen: Cell::new(fullscreen), fullscreen: Cell::new(fullscreen),
@@ -234,6 +238,7 @@ impl WindowsWindowInner {
&context.directx_devices, &context.directx_devices,
cs, cs,
context.current_cursor, context.current_cursor,
context.cursor_visible.clone(),
context.display, context.display,
context.min_size, context.min_size,
context.appearance, context.appearance,
@@ -373,6 +378,7 @@ struct WindowCreateContext {
min_size: Option<Size<Pixels>>, min_size: Option<Size<Pixels>>,
executor: ForegroundExecutor, executor: ForegroundExecutor,
current_cursor: Option<HCURSOR>, current_cursor: Option<HCURSOR>,
cursor_visible: Arc<AtomicBool>,
drop_target_helper: IDropTargetHelper, drop_target_helper: IDropTargetHelper,
validation_number: usize, validation_number: usize,
main_receiver: PriorityQueueReceiver<RunnableVariant>, main_receiver: PriorityQueueReceiver<RunnableVariant>,
@@ -394,6 +400,7 @@ impl WindowsWindow {
icon, icon,
executor, executor,
current_cursor, current_cursor,
cursor_visible,
drop_target_helper, drop_target_helper,
validation_number, validation_number,
main_receiver, main_receiver,
@@ -473,6 +480,7 @@ impl WindowsWindow {
min_size: params.window_min_size, min_size: params.window_min_size,
executor, executor,
current_cursor, current_cursor,
cursor_visible,
drop_target_helper, drop_target_helper,
validation_number, validation_number,
main_receiver, main_receiver,
+6
View File
@@ -358,3 +358,9 @@ pub(crate) mod m_2026_04_17 {
pub(crate) use settings::promote_show_branch_icon_true_to_show_branch_status_icon; pub(crate) use settings::promote_show_branch_icon_true_to_show_branch_status_icon;
} }
pub(crate) mod m_2026_05_04 {
mod settings;
pub(crate) use settings::SETTINGS_PATTERNS;
}
@@ -0,0 +1,38 @@
use std::ops::Range;
use tree_sitter::{Query, QueryMatch};
use crate::MigrationPatterns;
use crate::patterns::SETTINGS_ROOT_KEY_VALUE_PATTERN;
pub const SETTINGS_PATTERNS: MigrationPatterns =
&[(SETTINGS_ROOT_KEY_VALUE_PATTERN, replace_setting_value)];
fn replace_setting_value(
contents: &str,
mat: &QueryMatch,
query: &Query,
) -> Option<(Range<usize>, String)> {
let setting_capture_ix = query.capture_index_for_name("name")?;
let setting_name_range = mat
.nodes_for_capture_index(setting_capture_ix)
.next()?
.byte_range();
let setting_name = contents.get(setting_name_range)?;
if setting_name != "hide_mouse" {
return None;
}
let value_capture_ix = query.capture_index_for_name("value")?;
let value_range = mat
.nodes_for_capture_index(value_capture_ix)
.next()?
.byte_range();
let value = contents.get(value_range.clone())?;
if value.trim() != "\"on_typing_and_movement\"" {
return None;
}
Some((value_range, "\"on_typing_and_action\"".to_string()))
}
+44
View File
@@ -253,6 +253,10 @@ pub fn migrate_settings(text: &str) -> Result<Option<String>> {
MigrationType::Json( MigrationType::Json(
migrations::m_2026_04_17::promote_show_branch_icon_true_to_show_branch_status_icon, migrations::m_2026_04_17::promote_show_branch_icon_true_to_show_branch_status_icon,
), ),
MigrationType::TreeSitter(
migrations::m_2026_05_04::SETTINGS_PATTERNS,
&SETTINGS_QUERY_2026_05_04,
),
]; ];
run_migrations(text, migrations) run_migrations(text, migrations)
} }
@@ -395,6 +399,10 @@ define_query!(
KEYMAP_QUERY_2026_03_23, KEYMAP_QUERY_2026_03_23,
migrations::m_2026_03_23::KEYMAP_PATTERNS migrations::m_2026_03_23::KEYMAP_PATTERNS
); );
define_query!(
SETTINGS_QUERY_2026_05_04,
migrations::m_2026_05_04::SETTINGS_PATTERNS
);
// custom query // custom query
static EDIT_PREDICTION_SETTINGS_MIGRATION_QUERY: LazyLock<Query> = LazyLock::new(|| { static EDIT_PREDICTION_SETTINGS_MIGRATION_QUERY: LazyLock<Query> = LazyLock::new(|| {
@@ -5346,6 +5354,42 @@ mod tests {
); );
} }
#[test]
fn test_rename_hide_mouse_on_typing_and_movement_to_on_typing_and_action() {
assert_migrate_settings(
r#"
{
"hide_mouse": "on_typing_and_movement"
}
"#,
Some(
r#"
{
"hide_mouse": "on_typing_and_action"
}
"#,
),
);
}
#[test]
fn test_chain_hide_mouse_while_typing_to_on_typing_and_action() {
assert_migrate_settings(
r#"
{
"hide_mouse_while_typing": true
}
"#,
Some(
r#"
{
"hide_mouse": "on_typing_and_action"
}
"#,
),
);
}
#[test] #[test]
fn test_promote_show_branch_icon_true_to_show_branch_status_icon_no_change_when_already_migrated() fn test_promote_show_branch_icon_true_to_show_branch_status_icon_no_change_when_already_migrated()
{ {
+1 -1
View File
@@ -192,6 +192,7 @@ impl VsCodeSettings {
..GlobalLspSettingsContent::default() ..GlobalLspSettingsContent::default()
}), }),
helix_mode: None, helix_mode: None,
hide_mouse: None,
image_viewer: None, image_viewer: None,
journal: None, journal: None,
language_models: None, language_models: None,
@@ -267,7 +268,6 @@ impl VsCodeSettings {
go_to_definition_fallback: None, go_to_definition_fallback: None,
go_to_definition_scroll_strategy: None, go_to_definition_scroll_strategy: None,
gutter: self.gutter_content(), gutter: self.gutter_content(),
hide_mouse: None,
horizontal_scroll_margin: None, horizontal_scroll_margin: None,
hover_popover_delay: self.read_u64("editor.hover.delay").map(Into::into), hover_popover_delay: self.read_u64("editor.hover.delay").map(Into::into),
hover_popover_enabled: self.read_bool("editor.hover.enabled"), hover_popover_enabled: self.read_bool("editor.hover.enabled"),
-32
View File
@@ -22,10 +22,6 @@ pub struct EditorSettingsContent {
/// ///
/// Default: bar /// Default: bar
pub cursor_shape: Option<CursorShape>, pub cursor_shape: Option<CursorShape>,
/// Determines when the mouse cursor should be hidden in an editor or input box.
///
/// Default: on_typing_and_movement
pub hide_mouse: Option<HideMouseMode>,
/// Determines how snippets are sorted relative to other completion items. /// Determines how snippets are sorted relative to other completion items.
/// ///
/// Default: inline /// Default: inline
@@ -836,34 +832,6 @@ pub enum GoToDefinitionScrollStrategy {
Preserve, Preserve,
} }
/// Determines when the mouse cursor should be hidden in an editor or input box.
///
/// Default: on_typing_and_movement
#[derive(
Copy,
Clone,
Debug,
Default,
Serialize,
Deserialize,
PartialEq,
Eq,
JsonSchema,
MergeFrom,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum HideMouseMode {
/// Never hide the mouse cursor
Never,
/// Hide only when typing
OnTyping,
/// Hide on both typing and cursor movement
#[default]
OnTypingAndMovement,
}
/// Determines how snippets are sorted relative to other completion items. /// Determines how snippets are sorted relative to other completion items.
/// ///
/// Default: inline /// Default: inline
@@ -82,6 +82,35 @@ pub enum ParseStatus {
Failed { error: String }, Failed { error: String },
} }
/// Determines when the mouse cursor should be hidden in response to keyboard
/// input.
///
/// Default: on_typing_and_action
#[derive(
Copy,
Clone,
Debug,
Default,
Serialize,
Deserialize,
PartialEq,
Eq,
JsonSchema,
MergeFrom,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum HideMouseMode {
/// Never hide the mouse cursor
Never,
/// Hide only when typing
OnTyping,
/// Hide on typing and on key bindings that resolve to an action
#[default]
OnTypingAndAction,
}
#[with_fallible_options] #[with_fallible_options]
#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)] #[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct SettingsContent { pub struct SettingsContent {
@@ -156,6 +185,13 @@ pub struct SettingsContent {
/// Default: false /// Default: false
pub helix_mode: Option<bool>, pub helix_mode: Option<bool>,
/// Determines when the mouse cursor should be hidden in response to
/// keyboard input. Applies globally across all input surfaces (editors,
/// terminals, palettes, etc.).
///
/// Default: on_typing_and_action
pub hide_mouse: Option<HideMouseMode>,
pub journal: Option<JournalSettingsContent>, pub journal: Option<JournalSettingsContent>,
/// A map of log scopes to the desired log level. /// A map of log scopes to the desired log level.
+2 -2
View File
@@ -1145,9 +1145,9 @@ fn appearance_page() -> SettingsPage {
description: "When to hide the mouse cursor.", description: "When to hide the mouse cursor.",
field: Box::new(SettingField { field: Box::new(SettingField {
json_path: Some("hide_mouse"), json_path: Some("hide_mouse"),
pick: |settings_content| settings_content.editor.hide_mouse.as_ref(), pick: |settings_content| settings_content.hide_mouse.as_ref(),
write: |settings_content, value, _| { write: |settings_content, value, _| {
settings_content.editor.hide_mouse = value; settings_content.hide_mouse = value;
}, },
}), }),
metadata: None, metadata: None,
+2 -3
View File
@@ -7,8 +7,8 @@ mod surround;
use editor::display_map::{DisplayRow, DisplaySnapshot}; use editor::display_map::{DisplayRow, DisplaySnapshot};
use editor::{ use editor::{
DisplayPoint, Editor, EditorSettings, HideMouseCursorOrigin, MultiBufferOffset, DisplayPoint, Editor, EditorSettings, MultiBufferOffset, NavigationOverlayLabel,
NavigationOverlayLabel, NavigationTargetOverlay, SelectionEffects, ToOffset, ToPoint, movement, NavigationTargetOverlay, SelectionEffects, ToOffset, ToPoint, movement,
}; };
use gpui::actions; use gpui::actions;
use gpui::{App, Context, Font, Hsla, Pixels, Window, WindowTextSystem}; use gpui::{App, Context, Font, Hsla, Pixels, Window, WindowTextSystem};
@@ -807,7 +807,6 @@ impl Vim {
) { ) {
let count = Vim::take_count(cx).unwrap_or(1); let count = Vim::take_count(cx).unwrap_or(1);
self.update_editor(cx, |_, editor, cx| { self.update_editor(cx, |_, editor, cx| {
editor.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let display_map = editor.display_map.update(cx, |map, cx| map.snapshot(cx)); let display_map = editor.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = editor.selections.all::<Point>(&display_map); let mut selections = editor.selections.all::<Point>(&display_map);
let max_point = display_map.buffer_snapshot().max_point(); let max_point = display_map.buffer_snapshot().max_point();
+2 -6
View File
@@ -22,8 +22,8 @@ mod visual;
use crate::normal::paste::Paste as VimPaste; use crate::normal::paste::Paste as VimPaste;
use collections::HashMap; use collections::HashMap;
use editor::{ use editor::{
Anchor, Bias, Editor, EditorEvent, EditorSettings, HideMouseCursorOrigin, MultiBufferOffset, Anchor, Bias, Editor, EditorEvent, EditorSettings, MultiBufferOffset, NavigationOverlayKey,
NavigationOverlayKey, NavigationTargetOverlay, SelectionEffects, NavigationTargetOverlay, SelectionEffects,
actions::Paste, actions::Paste,
display_map::ToDisplayPoint, display_map::ToDisplayPoint,
movement::{self, FindRange}, movement::{self, FindRange},
@@ -1089,10 +1089,6 @@ impl Vim {
if let Some(action) = keystroke_event.action.as_ref() { if let Some(action) = keystroke_event.action.as_ref() {
// Keystroke is handled by the vim system, so continue forward // Keystroke is handled by the vim system, so continue forward
if action.name().starts_with("vim::") { if action.name().starts_with("vim::") {
self.update_editor(cx, |_, editor, cx| {
editor.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx)
});
return; return;
} }
} else if window.has_pending_keystrokes() || keystroke_event.keystroke.is_ime_in_progress() } else if window.has_pending_keystrokes() || keystroke_event.keystroke.is_ime_in_progress()
+21
View File
@@ -382,6 +382,8 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut App) {
}) })
.detach(); .detach();
init_cursor_hide_mode(cx);
cx.observe_new(|_multi_workspace: &mut MultiWorkspace, window, cx| { cx.observe_new(|_multi_workspace: &mut MultiWorkspace, window, cx| {
let Some(window) = window else { let Some(window) = window else {
return; return;
@@ -1852,6 +1854,25 @@ fn notify_settings_errors(result: settings::SettingsParseResult, is_user: bool,
}; };
} }
#[derive(Copy, Clone, Debug, settings::RegisterSetting)]
struct CursorHideModeSetting(gpui::CursorHideMode);
impl Settings for CursorHideModeSetting {
fn from_settings(content: &settings::SettingsContent) -> Self {
Self(match content.hide_mouse.unwrap_or_default() {
settings::HideMouseMode::Never => gpui::CursorHideMode::Never,
settings::HideMouseMode::OnTyping => gpui::CursorHideMode::OnTyping,
settings::HideMouseMode::OnTypingAndAction => gpui::CursorHideMode::OnTypingAndAction,
})
}
}
fn init_cursor_hide_mode(cx: &mut App) {
let apply = |cx: &mut App| cx.set_cursor_hide_mode(CursorHideModeSetting::get_global(cx).0);
apply(cx);
cx.observe_global::<SettingsStore>(apply).detach();
}
pub fn watch_settings_files(fs: Arc<dyn fs::Fs>, cx: &mut App) { pub fn watch_settings_files(fs: Arc<dyn fs::Fs>, cx: &mut App) {
MigrationNotification::set_global(cx.new(|_| MigrationNotification), cx); MigrationNotification::set_global(cx.new(|_| MigrationNotification), cx);
+4 -5
View File
@@ -801,9 +801,8 @@ List of `string` values
## Hide Mouse ## Hide Mouse
- Description: Determines when the mouse cursor should be hidden in an editor or input box. - Description: Determines when the mouse cursor should be hidden in response to keyboard input.
- Setting: `hide_mouse` - Default: `on_typing_and_action`
- Default: `on_typing_and_movement`
**Options** **Options**
@@ -823,11 +822,11 @@ List of `string` values
} }
``` ```
3. Hide on both typing and cursor movement: 3. Hide on typing and on key bindings that resolve to an action:
```json [settings] ```json [settings]
{ {
"hide_mouse": "on_typing_and_movement" "hide_mouse": "on_typing_and_action"
} }
``` ```
+2 -2
View File
@@ -183,8 +183,8 @@ TBD: Centered layout related settings
// Highlight the current line in the editor: none, gutter, line, all // Highlight the current line in the editor: none, gutter, line, all
"current_line_highlight": "all", "current_line_highlight": "all",
// When does the mouse cursor hide: never, on_typing, on_typing_and_movement // When does the mouse cursor hide: never, on_typing, on_typing_and_action
"hide_mouse": "on_typing_and_movement", "hide_mouse": "on_typing_and_action",
// Whether to highlight all occurrences of the selected text in an editor. // Whether to highlight all occurrences of the selected text in an editor.
"selection_highlight": true, "selection_highlight": true,