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:
1 parent
d1525d92b1
commit
ba6d258b0f
34 files changed
+716
-450
No files matched your search
@@ -230,15 +230,16 @@
|
||||
//
|
||||
// Default: "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:
|
||||
// "never"
|
||||
// 2. Hide only when typing:
|
||||
// "on_typing"
|
||||
// 3. Hide on both typing and cursor movement:
|
||||
// "on_typing_and_movement"
|
||||
"hide_mouse": "on_typing_and_movement",
|
||||
// 3. Hide on typing and on key bindings that resolve to an action:
|
||||
// "on_typing_and_action"
|
||||
"hide_mouse": "on_typing_and_action",
|
||||
// Determines whether the focused panel follows the mouse location.
|
||||
"focus_follows_mouse": {
|
||||
"enabled": false,
|
||||
|
||||
+4
-187
@@ -65,8 +65,8 @@ pub use display_map::{
|
||||
pub use edit_prediction_types::Direction;
|
||||
pub use editor_settings::{
|
||||
CompletionDetailAlignment, CurrentLineHighlight, DiffViewStyle, DocumentColorsRenderMode,
|
||||
EditorSettings, EditorSettingsScrollbarProxy, HideMouseMode, ScrollBeyondLastLine,
|
||||
ScrollbarAxes, SearchSettings, ShowMinimap, ui_scrollbar_settings_from_raw,
|
||||
EditorSettings, EditorSettingsScrollbarProxy, ScrollBeyondLastLine, ScrollbarAxes,
|
||||
SearchSettings, ShowMinimap, ui_scrollbar_settings_from_raw,
|
||||
};
|
||||
pub use element::{
|
||||
CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
|
||||
@@ -334,11 +334,6 @@ enum DisplayDiffHunk {
|
||||
},
|
||||
}
|
||||
|
||||
pub enum HideMouseCursorOrigin {
|
||||
TypingAction,
|
||||
MovementAction,
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
cx.set_global(GlobalBlameRenderer(Arc::new(())));
|
||||
cx.set_global(breadcrumbs::RenderBreadcrumbText(render_breadcrumb_text));
|
||||
@@ -1354,9 +1349,7 @@ pub struct Editor {
|
||||
_scroll_cursor_center_top_bottom_task: Task<()>,
|
||||
serialize_selections: Task<()>,
|
||||
serialize_folds: Task<()>,
|
||||
mouse_cursor_hidden: bool,
|
||||
minimap: Option<Entity<Self>>,
|
||||
hide_mouse_mode: HideMouseMode,
|
||||
pub change_list: ChangeList,
|
||||
inline_value_cache: InlineValueCache,
|
||||
number_deleted_lines: bool,
|
||||
@@ -2624,9 +2617,6 @@ impl Editor {
|
||||
blink_manager.disable(cx);
|
||||
}
|
||||
});
|
||||
if active {
|
||||
editor.show_mouse_cursor(cx);
|
||||
}
|
||||
}),
|
||||
]
|
||||
})
|
||||
@@ -2658,11 +2648,7 @@ impl Editor {
|
||||
text_style_refinement: None,
|
||||
load_diff_task: load_uncommitted_diff,
|
||||
temporary_diff_override: false,
|
||||
mouse_cursor_hidden: false,
|
||||
minimap: None,
|
||||
hide_mouse_mode: EditorSettings::get_global(cx)
|
||||
.hide_mouse
|
||||
.unwrap_or_default(),
|
||||
change_list: ChangeList::new(),
|
||||
mode,
|
||||
selection_drag_state: SelectionDragState::None,
|
||||
@@ -3004,31 +2990,6 @@ impl Editor {
|
||||
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(
|
||||
&self,
|
||||
granularity: EditPredictionGranularity,
|
||||
@@ -4830,8 +4791,6 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
|
||||
self.unfold_buffers_with_selections(cx);
|
||||
|
||||
let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
|
||||
@@ -5333,7 +5292,6 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.transact(window, cx, |this, window, cx| {
|
||||
let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
|
||||
let selections = this
|
||||
@@ -5572,8 +5530,6 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
|
||||
let buffer = self.buffer.read(cx);
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
|
||||
@@ -5643,8 +5599,6 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
|
||||
let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
|
||||
let mut rows = Vec::new();
|
||||
let mut rows_inserted = 0;
|
||||
@@ -6699,7 +6653,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return None;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
|
||||
}
|
||||
|
||||
@@ -6712,7 +6665,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return None;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
|
||||
}
|
||||
|
||||
@@ -6725,7 +6677,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return None;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
|
||||
}
|
||||
|
||||
@@ -6735,7 +6686,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
|
||||
}
|
||||
|
||||
@@ -7229,7 +7179,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return None;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
|
||||
let actions_menu =
|
||||
if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
|
||||
@@ -11293,7 +11242,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.transact(window, cx, |this, window, cx| {
|
||||
this.select_autoclose_pair(window, cx);
|
||||
|
||||
@@ -11346,7 +11294,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.transact(window, cx, |this, window, cx| {
|
||||
this.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(&mut |map, selection| {
|
||||
@@ -11372,7 +11319,6 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
if self.move_to_prev_snippet_tabstop(window, cx) {
|
||||
return;
|
||||
}
|
||||
@@ -11391,7 +11337,6 @@ impl Editor {
|
||||
}
|
||||
|
||||
if self.move_to_next_snippet_tabstop(window, cx) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
return;
|
||||
}
|
||||
cx.propagate();
|
||||
@@ -11409,7 +11354,6 @@ impl Editor {
|
||||
}
|
||||
|
||||
if self.move_to_prev_snippet_tabstop(window, cx) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
return;
|
||||
}
|
||||
cx.propagate();
|
||||
@@ -11422,13 +11366,11 @@ impl Editor {
|
||||
}
|
||||
|
||||
if self.move_to_next_snippet_tabstop(window, cx) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
return;
|
||||
}
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
|
||||
let buffer = self.buffer.read(cx);
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
@@ -11560,7 +11502,6 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
|
||||
let mut prev_edited_row = 0;
|
||||
let mut row_delta = 0;
|
||||
@@ -11668,7 +11609,6 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let selections = self.selections.all::<Point>(&display_map);
|
||||
let mut deletion_ranges = Vec::new();
|
||||
@@ -11747,7 +11687,6 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
let selections = self
|
||||
.selections
|
||||
.all::<MultiBufferOffset>(&self.display_snapshot(cx))
|
||||
@@ -11769,7 +11708,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
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>) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.join_lines_impl(true, window, cx);
|
||||
}
|
||||
|
||||
@@ -12065,7 +12002,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
|
||||
@@ -12168,7 +12104,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
let mut buffer_ids = HashSet::default();
|
||||
let snapshot = self.buffer().read(cx).snapshot(cx);
|
||||
for selection in self
|
||||
@@ -12190,7 +12125,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
let selections = self
|
||||
.selections
|
||||
.all(&self.display_snapshot(cx))
|
||||
@@ -12219,7 +12153,6 @@ impl Editor {
|
||||
.map(|selection| selection.range())
|
||||
.collect();
|
||||
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.restore_hunks_in_ranges(selections, window, cx);
|
||||
|
||||
let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
|
||||
@@ -12622,7 +12555,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
|
||||
let display_snapshot = self.display_snapshot(cx);
|
||||
|
||||
@@ -12863,7 +12795,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
let display_snapshot = self.display_snapshot(cx);
|
||||
let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
|
||||
|
||||
@@ -13017,7 +12948,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
@@ -13439,7 +13369,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
|
||||
let mut new_selections = Vec::new();
|
||||
@@ -13539,7 +13468,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let buffer = display_map.buffer_snapshot();
|
||||
@@ -13687,7 +13615,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
if self.mode.is_single_line() {
|
||||
cx.propagate();
|
||||
return;
|
||||
@@ -13801,7 +13728,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
if self.mode.is_single_line() {
|
||||
cx.propagate();
|
||||
return;
|
||||
@@ -13898,7 +13824,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
let text_layout_details = &self.text_layout_details(window, cx);
|
||||
self.transact(window, cx, |this, window, cx| {
|
||||
let edits = this.change_selections(Default::default(), window, cx, |s| {
|
||||
@@ -13963,7 +13888,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
if self.mode.is_single_line() {
|
||||
cx.propagate();
|
||||
return;
|
||||
@@ -14426,7 +14350,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
let item = self.cut_common(true, window, cx);
|
||||
cx.write_to_clipboard(item);
|
||||
}
|
||||
@@ -14435,7 +14358,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.move_with(&mut |snapshot, sel| {
|
||||
if sel.is_empty() {
|
||||
@@ -14456,7 +14378,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
|
||||
if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
|
||||
(kill_ring.text().to_string(), kill_ring.metadata_json())
|
||||
@@ -14804,7 +14725,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
if let Some(item) = cx.read_from_clipboard() {
|
||||
let clipboard_string = item.entries().iter().find_map(|entry| match entry {
|
||||
ClipboardEntry::String(s) => Some(s),
|
||||
@@ -14828,8 +14748,6 @@ impl Editor {
|
||||
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((selections, _)) =
|
||||
self.selection_history.transaction(transaction_id).cloned()
|
||||
@@ -14858,8 +14776,6 @@ impl Editor {
|
||||
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((_, Some(selections))) =
|
||||
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>) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(&mut |map, selection| {
|
||||
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>) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
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>) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(&mut |map, selection| {
|
||||
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>) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_heads_with(&mut |map, head, _| {
|
||||
(movement::right(map, head), SelectionGoal::None)
|
||||
@@ -14946,8 +14858,6 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
|
||||
let text_layout_details = &self.text_layout_details(window, cx);
|
||||
let selection_count = self.selections.count();
|
||||
let first_selection = self.selections.first_anchor();
|
||||
@@ -14989,8 +14899,6 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
|
||||
let text_layout_details = &self.text_layout_details(window, cx);
|
||||
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
@@ -15026,8 +14934,6 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
|
||||
let text_layout_details = &self.text_layout_details(window, cx);
|
||||
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
@@ -15054,7 +14960,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
let text_layout_details = &self.text_layout_details(window, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_heads_with(&mut |map, head, goal| {
|
||||
@@ -15069,7 +14974,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
let text_layout_details = &self.text_layout_details(window, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_heads_with(&mut |map, head, goal| {
|
||||
@@ -15088,8 +14992,6 @@ impl Editor {
|
||||
return;
|
||||
};
|
||||
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
|
||||
let text_layout_details = &self.text_layout_details(window, cx);
|
||||
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
@@ -15128,8 +15030,6 @@ impl Editor {
|
||||
return;
|
||||
};
|
||||
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
|
||||
let effects = if action.center_cursor {
|
||||
SelectionEffects::scroll(Autoscroll::center())
|
||||
} else {
|
||||
@@ -15157,7 +15057,6 @@ impl Editor {
|
||||
}
|
||||
|
||||
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);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_heads_with(&mut |map, head, goal| {
|
||||
@@ -15174,8 +15073,6 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
|
||||
let text_layout_details = &self.text_layout_details(window, cx);
|
||||
let selection_count = self.selections.count();
|
||||
let first_selection = self.selections.first_anchor();
|
||||
@@ -15212,8 +15109,6 @@ impl Editor {
|
||||
return;
|
||||
};
|
||||
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
|
||||
let text_layout_details = &self.text_layout_details(window, cx);
|
||||
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
@@ -15252,8 +15147,6 @@ impl Editor {
|
||||
return;
|
||||
};
|
||||
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
|
||||
let effects = if action.center_cursor {
|
||||
SelectionEffects::scroll(Autoscroll::center())
|
||||
} else {
|
||||
@@ -15280,7 +15173,6 @@ impl Editor {
|
||||
}
|
||||
|
||||
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);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_heads_with(&mut |map, head, goal| {
|
||||
@@ -15371,7 +15263,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_cursors_with(&mut |map, head, _| {
|
||||
(
|
||||
@@ -15388,7 +15279,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_cursors_with(&mut |map, head, _| {
|
||||
(
|
||||
@@ -15405,7 +15295,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_heads_with(&mut |map, head, _| {
|
||||
(
|
||||
@@ -15422,7 +15311,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_heads_with(&mut |map, head, _| {
|
||||
(
|
||||
@@ -15442,7 +15330,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.transact(window, cx, |this, window, cx| {
|
||||
this.select_autoclose_pair(window, cx);
|
||||
this.change_selections(Default::default(), window, cx, |s| {
|
||||
@@ -15476,7 +15363,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.transact(window, cx, |this, window, cx| {
|
||||
this.select_autoclose_pair(window, cx);
|
||||
this.change_selections(Default::default(), window, cx, |s| {
|
||||
@@ -15507,7 +15393,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_cursors_with(&mut |map, head, _| {
|
||||
(movement::next_word_end(map, head), SelectionGoal::None)
|
||||
@@ -15521,7 +15406,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_cursors_with(&mut |map, head, _| {
|
||||
(movement::next_subword_end(map, head), SelectionGoal::None)
|
||||
@@ -15535,7 +15419,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_heads_with(&mut |map, head, _| {
|
||||
(movement::next_word_end(map, head), SelectionGoal::None)
|
||||
@@ -15549,7 +15432,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_heads_with(&mut |map, head, _| {
|
||||
(movement::next_subword_end(map, head), SelectionGoal::None)
|
||||
@@ -15566,7 +15448,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.transact(window, cx, |this, window, cx| {
|
||||
this.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(&mut |map, selection| {
|
||||
@@ -15599,7 +15480,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.transact(window, cx, |this, window, cx| {
|
||||
this.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(&mut |map, selection| {
|
||||
@@ -15630,7 +15510,6 @@ impl Editor {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
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| {
|
||||
s.move_cursors_with(&mut |map, head, _| {
|
||||
(
|
||||
@@ -15653,7 +15532,6 @@ impl Editor {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
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| {
|
||||
s.move_heads_with(&mut |map, head, _| {
|
||||
(
|
||||
@@ -15678,7 +15556,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.transact(window, cx, |this, window, cx| {
|
||||
this.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(&mut |_, selection| {
|
||||
@@ -15704,7 +15581,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_cursors_with(&mut |map, head, _| {
|
||||
(
|
||||
@@ -15721,7 +15597,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_heads_with(&mut |map, head, _| {
|
||||
(
|
||||
@@ -15741,7 +15616,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.transact(window, cx, |this, window, cx| {
|
||||
this.select_to_end_of_line(
|
||||
&SelectToEndOfLine {
|
||||
@@ -15763,7 +15637,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.transact(window, cx, |this, window, cx| {
|
||||
this.select_to_end_of_line(
|
||||
&SelectToEndOfLine {
|
||||
@@ -15781,7 +15654,6 @@ impl Editor {
|
||||
});
|
||||
});
|
||||
}
|
||||
this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
let item = this.cut_common(false, window, cx);
|
||||
cx.write_to_clipboard(item);
|
||||
});
|
||||
@@ -15797,7 +15669,6 @@ impl Editor {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(&mut |map, selection| {
|
||||
selection.collapse_to(
|
||||
@@ -15818,7 +15689,6 @@ impl Editor {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(&mut |map, selection| {
|
||||
selection.collapse_to(
|
||||
@@ -15839,7 +15709,6 @@ impl Editor {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_heads_with(&mut |map, head, _| {
|
||||
(
|
||||
@@ -15860,7 +15729,6 @@ impl Editor {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_heads_with(&mut |map, head, _| {
|
||||
(
|
||||
@@ -15881,7 +15749,6 @@ impl Editor {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(&mut |map, selection| {
|
||||
selection.collapse_to(
|
||||
@@ -15931,7 +15798,6 @@ impl Editor {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(&mut |map, selection| {
|
||||
selection.collapse_to(
|
||||
@@ -15956,7 +15822,6 @@ impl Editor {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(&mut |map, selection| {
|
||||
selection.collapse_to(
|
||||
@@ -15981,7 +15846,6 @@ impl Editor {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_heads_with(&mut |map, head, _| {
|
||||
(
|
||||
@@ -16002,7 +15866,6 @@ impl Editor {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_heads_with(&mut |map, head, _| {
|
||||
(
|
||||
@@ -16023,7 +15886,6 @@ impl Editor {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_heads_with(&mut |map, head, _| {
|
||||
(
|
||||
@@ -16044,7 +15906,6 @@ impl Editor {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_heads_with(&mut |map, head, _| {
|
||||
(
|
||||
@@ -16065,7 +15926,6 @@ impl Editor {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
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));
|
||||
selection.set_head(Point::zero(), SelectionGoal::None);
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.select(vec![selection]);
|
||||
});
|
||||
@@ -16090,7 +15949,6 @@ impl Editor {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
let cursor = self.buffer.read(cx).read(cx).len();
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
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>) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
let mut selection = self
|
||||
.selections
|
||||
@@ -16191,14 +16048,12 @@ impl Editor {
|
||||
}
|
||||
|
||||
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| {
|
||||
s.select_ranges(vec![Anchor::Min..Anchor::Max]);
|
||||
});
|
||||
}
|
||||
|
||||
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 mut selections = self.selections.all::<Point>(&display_map);
|
||||
let max_point = display_map.buffer_snapshot().max_point();
|
||||
@@ -16297,8 +16152,6 @@ impl Editor {
|
||||
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 all_selections = self.selections.all::<Point>(&display_map);
|
||||
let text_layout_details = self.text_layout_details(window, cx);
|
||||
@@ -16720,8 +16573,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Result<()> {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, 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)?;
|
||||
@@ -16787,7 +16638,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Result<()> {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
self.select_next_match_internal(
|
||||
&display_map,
|
||||
@@ -16804,7 +16654,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Result<()> {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let buffer = display_map.buffer_snapshot();
|
||||
let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
|
||||
@@ -17011,7 +16860,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.transact(window, cx, |this, _window, cx| {
|
||||
let mut selections = this
|
||||
.selections
|
||||
@@ -17202,7 +17050,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
let text_layout_details = &self.text_layout_details(window, cx);
|
||||
self.transact(window, cx, |this, window, cx| {
|
||||
let mut selections = this
|
||||
@@ -17505,8 +17352,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
let old_selections = self
|
||||
.selections
|
||||
@@ -17574,8 +17419,6 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
|
||||
@@ -17691,8 +17534,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
|
||||
if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
|
||||
self.select_syntax_node_history.pop()
|
||||
{
|
||||
@@ -17735,7 +17576,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
let selections = self
|
||||
@@ -17817,8 +17657,6 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
let mut selected_sibling = false;
|
||||
|
||||
@@ -17877,8 +17715,6 @@ impl Editor {
|
||||
) {
|
||||
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 mut selected_sibling = false;
|
||||
|
||||
@@ -18017,8 +17853,6 @@ impl Editor {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
|
||||
@@ -18082,8 +17916,6 @@ impl Editor {
|
||||
cx: &mut Context<Self>,
|
||||
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 buffer = self.buffer.read(cx).snapshot(cx);
|
||||
let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
|
||||
@@ -18115,7 +17947,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_offsets_with(&mut |snapshot, selection| {
|
||||
let Some(enclosing_bracket_ranges) =
|
||||
@@ -18172,7 +18003,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
if let Some(entry) = self.selection_history.undo_stack.pop_back() {
|
||||
self.selection_history.mode = SelectionHistoryMode::Undoing;
|
||||
self.with_selection_effects_deferred(window, cx, |this, window, cx| {
|
||||
@@ -18198,7 +18028,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
if let Some(entry) = self.selection_history.redo_stack.pop_back() {
|
||||
self.selection_history.mode = SelectionHistoryMode::Redoing;
|
||||
self.with_selection_effects_deferred(window, cx, |this, window, cx| {
|
||||
@@ -18386,7 +18215,6 @@ impl Editor {
|
||||
if !self.diagnostics_enabled() {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
|
||||
}
|
||||
|
||||
@@ -18399,7 +18227,6 @@ impl Editor {
|
||||
if !self.diagnostics_enabled() {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, 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>) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
let snapshot = self.snapshot(window, cx);
|
||||
let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
|
||||
self.go_to_hunk_before_or_after_position(
|
||||
@@ -18566,7 +18392,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
let snapshot = self.snapshot(window, cx);
|
||||
let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
|
||||
self.go_to_hunk_before_or_after_position(
|
||||
@@ -18660,7 +18485,6 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
let snapshot = self.snapshot(window, cx);
|
||||
let buffer = &snapshot.buffer_snapshot();
|
||||
let position = self
|
||||
@@ -20180,7 +20004,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return None;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
|
||||
let project = match &self.project {
|
||||
Some(project) => project.clone(),
|
||||
@@ -20205,7 +20028,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return None;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
|
||||
let project = match &self.project {
|
||||
Some(project) => project.clone(),
|
||||
@@ -20327,7 +20149,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return None;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
let project = match &self.project {
|
||||
Some(project) => project.clone(),
|
||||
None => return None,
|
||||
@@ -21911,7 +21732,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
|
||||
let buffers = self.buffer.read(cx).all_buffers();
|
||||
for branch_buffer in buffers {
|
||||
@@ -21944,7 +21764,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
let snapshot = self.snapshot(window, cx);
|
||||
let hunks = snapshot.hunks_for_ranges(
|
||||
self.selections
|
||||
@@ -24284,7 +24103,6 @@ impl Editor {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.transact(window, cx, |this, window, cx| {
|
||||
let edits = this
|
||||
.selections
|
||||
@@ -25363,7 +25181,6 @@ impl Editor {
|
||||
self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
|
||||
self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
|
||||
self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
|
||||
self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
|
||||
}
|
||||
|
||||
if old_cursor_shape != self.cursor_shape {
|
||||
@@ -26021,8 +25838,8 @@ impl Editor {
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(position_map) = self.last_position_map.clone()
|
||||
&& !self.mouse_cursor_hidden
|
||||
if cx.is_cursor_visible()
|
||||
&& let Some(position_map) = self.last_position_map.clone()
|
||||
{
|
||||
EditorElement::mouse_moved(
|
||||
self,
|
||||
|
||||
@@ -6,9 +6,8 @@ use project::project_settings::DiagnosticSeverity;
|
||||
pub use settings::{
|
||||
CodeLens, CompletionDetailAlignment, CurrentLineHighlight, DelayMs, DiffViewStyle, DisplayIn,
|
||||
DocumentColorsRenderMode, DoubleClickInMultibuffer, GoToDefinitionFallback,
|
||||
GoToDefinitionScrollStrategy, HideMouseMode, MinimapThumb, MinimapThumbBorder,
|
||||
MultiCursorModifier, ScrollBeyondLastLine, ScrollbarDiagnostics, SeedQuerySetting, ShowMinimap,
|
||||
SnippetSortOrder,
|
||||
GoToDefinitionScrollStrategy, MinimapThumb, MinimapThumbBorder, MultiCursorModifier,
|
||||
ScrollBeyondLastLine, ScrollbarDiagnostics, SeedQuerySetting, ShowMinimap, SnippetSortOrder,
|
||||
};
|
||||
use settings::{RegisterSetting, RelativeLineNumbers, Settings};
|
||||
use ui::scrollbars::ShowScrollbar;
|
||||
@@ -55,7 +54,6 @@ pub struct EditorSettings {
|
||||
pub go_to_definition_fallback: GoToDefinitionFallback,
|
||||
pub go_to_definition_scroll_strategy: GoToDefinitionScrollStrategy,
|
||||
pub jupyter: Jupyter,
|
||||
pub hide_mouse: Option<HideMouseMode>,
|
||||
pub snippet_sort_order: SnippetSortOrder,
|
||||
pub diagnostics_max_severity: Option<DiagnosticSeverity>,
|
||||
pub inline_code_actions: bool,
|
||||
@@ -291,7 +289,6 @@ impl Settings for EditorSettings {
|
||||
jupyter: Jupyter {
|
||||
enabled: editor.jupyter.unwrap().enabled.unwrap(),
|
||||
},
|
||||
hide_mouse: editor.hide_mouse,
|
||||
snippet_sort_order: editor.snippet_sort_order.unwrap(),
|
||||
diagnostics_max_severity: editor.diagnostics_max_severity.map(Into::into),
|
||||
inline_code_actions: editor.inline_code_actions.unwrap(),
|
||||
|
||||
@@ -1262,7 +1262,6 @@ impl EditorElement {
|
||||
let text_hovered = text_hitbox.is_hovered(window);
|
||||
let gutter_hovered = gutter_hitbox.is_hovered(window);
|
||||
editor.set_gutter_hovered(gutter_hovered, cx);
|
||||
editor.show_mouse_cursor(cx);
|
||||
|
||||
let point_for_position = position_map.point_for_position(event.position);
|
||||
let valid_point = point_for_position.nearest_valid;
|
||||
@@ -6607,9 +6606,7 @@ impl EditorElement {
|
||||
}),
|
||||
|window| {
|
||||
let editor = self.editor.read(cx);
|
||||
if editor.mouse_cursor_hidden {
|
||||
window.set_window_cursor_style(CursorStyle::None);
|
||||
} else if let SelectionDragState::ReadyToDrag {
|
||||
if let SelectionDragState::ReadyToDrag {
|
||||
mouse_down_time, ..
|
||||
} = &editor.selection_drag_state
|
||||
{
|
||||
|
||||
@@ -120,7 +120,12 @@ impl Editor {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
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);
|
||||
return;
|
||||
}
|
||||
@@ -782,7 +787,7 @@ fn surrounding_filename(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
DisplayPoint, HideMouseCursorOrigin,
|
||||
DisplayPoint,
|
||||
display_map::ToDisplayPoint,
|
||||
editor_tests::init_test,
|
||||
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.
|
||||
"},
|
||||
);
|
||||
|
||||
// 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]
|
||||
|
||||
@@ -257,6 +257,22 @@ pub enum QuitMode {
|
||||
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)]
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct SystemWindowTab {
|
||||
@@ -649,6 +665,7 @@ pub struct App {
|
||||
|
||||
pub(crate) window_update_stack: Vec<WindowId>,
|
||||
pub(crate) mode: GpuiMode,
|
||||
pub(crate) cursor_hide_mode: CursorHideMode,
|
||||
flushing_effects: bool,
|
||||
pending_updates: usize,
|
||||
quit_mode: QuitMode,
|
||||
@@ -737,6 +754,7 @@ impl App {
|
||||
inspector_element_registry: InspectorElementRegistry::default(),
|
||||
quit_mode: QuitMode::default(),
|
||||
quitting: false,
|
||||
cursor_hide_mode: CursorHideMode::default(),
|
||||
|
||||
#[cfg(any(test, feature = "test-support", debug_assertions))]
|
||||
name: None,
|
||||
@@ -877,6 +895,27 @@ impl App {
|
||||
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
|
||||
/// multiple times in an update cycle and still result in a single redraw.
|
||||
pub fn refresh_windows(&mut self) {
|
||||
|
||||
@@ -212,6 +212,14 @@ pub trait Platform: 'static {
|
||||
fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
|
||||
|
||||
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 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
|
||||
/// corresponds to the CSS cursor value `context-menu`
|
||||
ContextualMenu,
|
||||
|
||||
/// Hide the cursor
|
||||
None,
|
||||
}
|
||||
|
||||
/// A clipboard item that should be copied to the clipboard
|
||||
|
||||
@@ -403,6 +403,12 @@ impl Platform for TestPlatform {
|
||||
*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 {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -202,6 +202,14 @@ impl Platform for VisualTestPlatform {
|
||||
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 {
|
||||
self.platform.should_auto_hide_scrollbars()
|
||||
}
|
||||
|
||||
+40
-19
@@ -3,21 +3,22 @@ use crate::Inspector;
|
||||
use crate::{
|
||||
Action, AnyDrag, AnyElement, AnyImageCache, AnyTooltip, AnyView, App, AppContext, Arena, Asset,
|
||||
AsyncWindowContext, AvailableSpace, Background, BorderStyle, Bounds, BoxShadow, Capslock,
|
||||
Context, Corners, CursorStyle, Decorations, DevicePixels, DispatchActionListener,
|
||||
DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
|
||||
FileDropEvent, FontId, Global, GlobalElementId, GlyphId, GpuSpecs, Hsla, InputHandler, IsZero,
|
||||
KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke, KeystrokeEvent, LayoutId,
|
||||
LineLayoutIndex, Modifiers, ModifiersChangedEvent, MonochromeSprite, MouseButton, MouseEvent,
|
||||
MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
|
||||
PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, Priority, PromptButton,
|
||||
PromptLevel, Quad, Render, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams,
|
||||
Replay, ResizeEdge, SMOOTH_SVG_SCALE_FACTOR, SUBPIXEL_VARIANTS_X, SUBPIXEL_VARIANTS_Y,
|
||||
ScaledPixels, Scene, Shadow, SharedString, Size, StrikethroughStyle, Style, SubpixelSprite,
|
||||
SubscriberSet, Subscription, SystemWindowTab, SystemWindowTabController, TabStopMap,
|
||||
TaffyLayoutEngine, Task, TextRenderingMode, TextStyle, TextStyleRefinement, ThermalState,
|
||||
TransformationMatrix, Underline, UnderlineStyle, WindowAppearance, WindowBackgroundAppearance,
|
||||
WindowBounds, WindowControls, WindowDecorations, WindowOptions, WindowParams, WindowTextSystem,
|
||||
point, prelude::*, px, rems, size, transparent_black,
|
||||
Context, Corners, CursorHideMode, CursorStyle, Decorations, DevicePixels,
|
||||
DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity,
|
||||
EntityId, EventEmitter, FileDropEvent, FontId, Global, GlobalElementId, GlyphId, GpuSpecs,
|
||||
Hsla, InputHandler, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke,
|
||||
KeystrokeEvent, LayoutId, LineLayoutIndex, Modifiers, ModifiersChangedEvent, MonochromeSprite,
|
||||
MouseButton, MouseEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas,
|
||||
PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite,
|
||||
Priority, PromptButton, PromptLevel, Quad, Render, RenderGlyphParams, RenderImage,
|
||||
RenderImageParams, RenderSvgParams, Replay, ResizeEdge, SMOOTH_SVG_SCALE_FACTOR,
|
||||
SUBPIXEL_VARIANTS_X, SUBPIXEL_VARIANTS_Y, ScaledPixels, Scene, Shadow, SharedString, Size,
|
||||
StrikethroughStyle, Style, SubpixelSprite, SubscriberSet, Subscription, SystemWindowTab,
|
||||
SystemWindowTabController, TabStopMap, TaffyLayoutEngine, Task, TextRenderingMode, TextStyle,
|
||||
TextStyleRefinement, ThermalState, TransformationMatrix, Underline, UnderlineStyle,
|
||||
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations,
|
||||
WindowOptions, WindowParams, WindowTextSystem, point, prelude::*, px, rems, size,
|
||||
transparent_black,
|
||||
};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use collections::{FxHashMap, FxHashSet};
|
||||
@@ -1457,10 +1458,6 @@ impl Window {
|
||||
move |active| {
|
||||
handle
|
||||
.update(&mut cx, |_, window, cx| {
|
||||
if !active {
|
||||
cx.platform.set_cursor_style(CursorStyle::Arrow);
|
||||
}
|
||||
|
||||
window.active.set(active);
|
||||
window.modifiers = window.platform_window.modifiers();
|
||||
window.capslock = window.platform_window.capslock();
|
||||
@@ -4473,6 +4470,14 @@ impl Window {
|
||||
} else if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
|
||||
self.pending_modifier.saw_keystroke = true;
|
||||
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 {
|
||||
@@ -4738,6 +4743,22 @@ impl Window {
|
||||
node_id: DispatchNodeId,
|
||||
action: &dyn Action,
|
||||
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);
|
||||
|
||||
|
||||
@@ -80,6 +80,10 @@ pub(crate) trait LinuxClient {
|
||||
options: WindowParams,
|
||||
) -> anyhow::Result<Box<dyn PlatformWindow>>;
|
||||
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 reveal_path(&self, path: PathBuf);
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
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::DragCopy => &["copy"],
|
||||
CursorStyle::ContextualMenu => &["context-menu"],
|
||||
CursorStyle::None => {
|
||||
#[cfg(debug_assertions)]
|
||||
panic!("CursorStyle::None should be handled separately in the client");
|
||||
#[cfg(not(debug_assertions))]
|
||||
&[DEFAULT_CURSOR_ICON_NAME]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,11 +37,5 @@ pub(super) fn to_shape(style: CursorStyle) -> Shape {
|
||||
CursorStyle::DragLink => Shape::Alias,
|
||||
CursorStyle::DragCopy => Shape::Copy,
|
||||
CursorStyle::ContextualMenu => Shape::ContextMenu,
|
||||
CursorStyle::None => {
|
||||
#[cfg(debug_assertions)]
|
||||
panic!("CursorStyle::None should be handled separately in the client");
|
||||
#[cfg(not(debug_assertions))]
|
||||
Shape::Default
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,6 +256,7 @@ pub(crate) struct WaylandClientState {
|
||||
keyboard_focused_window: Option<WaylandWindowStatePtr>,
|
||||
loop_handle: LoopHandle<'static, WaylandClientStatePtr>,
|
||||
cursor_style: Option<CursorStyle>,
|
||||
cursor_hidden_window: Option<WaylandWindowStatePtr>,
|
||||
clipboard: Clipboard,
|
||||
data_offers: Vec<DataOffer<WlDataOffer>>,
|
||||
primary_data_offer: Option<DataOffer<ZwpPrimarySelectionOfferV1>>,
|
||||
@@ -410,6 +411,65 @@ impl WaylandClientStatePtr {
|
||||
{
|
||||
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(),
|
||||
enter_token: None,
|
||||
cursor_style: None,
|
||||
cursor_hidden_window: None,
|
||||
clipboard: Clipboard::new(conn.clone(), handle.clone()),
|
||||
data_offers: Vec::new(),
|
||||
primary_data_offer: None,
|
||||
@@ -785,33 +846,42 @@ impl LinuxClient for WaylandClient {
|
||||
.as_ref()
|
||||
.is_some_and(|w| !w.is_blocked()));
|
||||
|
||||
if need_update {
|
||||
let serial = state.serial_tracker.get(SerialKind::MouseEnter);
|
||||
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,
|
||||
);
|
||||
}
|
||||
if !need_update {
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -1432,6 +1502,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
||||
state.enter_token.take();
|
||||
// Prevent keyboard events from repeating after opening e.g. a file chooser and closing it quickly
|
||||
state.repeat.current_id += 1;
|
||||
state.restore_cursor_after_hide();
|
||||
|
||||
if let Some(window) = keyboard_focused_window {
|
||||
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() {
|
||||
state.enter_token = None;
|
||||
}
|
||||
state.restore_cursor_after_hide();
|
||||
if let Some(style) = state.cursor_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 {
|
||||
if let Some(cursor_shape_device) = &state.cursor_shape_device {
|
||||
cursor_shape_device.set_shape(serial, to_shape(style));
|
||||
} else {
|
||||
let scale = window.primary_output_scale();
|
||||
@@ -1765,6 +1831,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
|
||||
state.mouse_focused_window = None;
|
||||
state.mouse_location = None;
|
||||
state.button_pressed = None;
|
||||
state.cursor_hidden_window = None;
|
||||
|
||||
drop(state);
|
||||
focused_window.handle_input(input);
|
||||
@@ -1780,6 +1847,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
|
||||
return;
|
||||
}
|
||||
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 window.is_blocked() {
|
||||
|
||||
@@ -211,6 +211,8 @@ pub struct X11ClientState {
|
||||
pub(crate) cursor_handle: cursor::Handle,
|
||||
pub(crate) cursor_styles: HashMap<xproto::Window, CursorStyle>,
|
||||
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>,
|
||||
|
||||
@@ -249,6 +251,9 @@ impl X11ClientStatePtr {
|
||||
if state.keyboard_focused_window == Some(x_window) {
|
||||
state.keyboard_focused_window = None;
|
||||
}
|
||||
if state.cursor_hidden_window == Some(x_window) {
|
||||
state.cursor_hidden_window = None;
|
||||
}
|
||||
state.cursor_styles.remove(&x_window);
|
||||
}
|
||||
|
||||
@@ -543,6 +548,8 @@ impl X11Client {
|
||||
cursor_handle,
|
||||
cursor_styles: HashMap::default(),
|
||||
cursor_cache: HashMap::default(),
|
||||
cursor_hidden_window: None,
|
||||
invisible_cursor_cache: None,
|
||||
|
||||
pointer_device_states,
|
||||
|
||||
@@ -971,6 +978,7 @@ impl X11Client {
|
||||
compose_state.reset();
|
||||
}
|
||||
state.pre_edit_text.take();
|
||||
state.restore_cursor_after_hide();
|
||||
drop(state);
|
||||
self.reset_ime();
|
||||
window.handle_ime_delete();
|
||||
@@ -1224,6 +1232,7 @@ impl X11Client {
|
||||
Event::XinputMotion(event) => {
|
||||
let window = self.get_window(event.event)?;
|
||||
let mut state = self.0.borrow_mut();
|
||||
state.restore_cursor_after_hide();
|
||||
if window.is_blocked() {
|
||||
// We want to set the cursor to the default arrow
|
||||
// when the window is blocked
|
||||
@@ -1286,6 +1295,7 @@ impl X11Client {
|
||||
window.set_hovered(true);
|
||||
let mut state = self.0.borrow_mut();
|
||||
state.mouse_focused_window = Some(event.event);
|
||||
state.restore_cursor_after_hide();
|
||||
}
|
||||
Event::XinputLeave(event) if event.mode == xinput::NotifyMode::NORMAL => {
|
||||
let mut state = self.0.borrow_mut();
|
||||
@@ -1667,11 +1677,17 @@ impl LinuxClient for X11Client {
|
||||
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 {
|
||||
return;
|
||||
};
|
||||
|
||||
state.cursor_styles.insert(focused_window, style);
|
||||
check_reply(
|
||||
|| "Failed to set cursor style",
|
||||
state.xcb_connection.change_window_attributes(
|
||||
@@ -1686,6 +1702,14 @@ impl LinuxClient for X11Client {
|
||||
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) {
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
open_uri_internal(
|
||||
@@ -1986,41 +2010,33 @@ impl X11ClientState {
|
||||
return *cursor;
|
||||
}
|
||||
|
||||
let result;
|
||||
match style {
|
||||
CursorStyle::None => match create_invisible_cursor(&self.xcb_connection) {
|
||||
Ok(loaded_cursor) => result = Ok(loaded_cursor),
|
||||
Err(err) => result = Err(err.context("X11: error while creating invisible cursor")),
|
||||
},
|
||||
_ => 'outer: {
|
||||
let mut errors = String::new();
|
||||
let cursor_icon_names = cursor_style_to_icon_names(style);
|
||||
for cursor_icon_name in cursor_icon_names {
|
||||
match self
|
||||
.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');
|
||||
let result = 'outer: {
|
||||
let mut errors = String::new();
|
||||
let cursor_icon_names = cursor_style_to_icon_names(style);
|
||||
for cursor_icon_name in cursor_icon_names {
|
||||
match self
|
||||
.cursor_handle
|
||||
.load_cursor(&self.xcb_connection, cursor_icon_name)
|
||||
{
|
||||
Ok(loaded_cursor) => {
|
||||
if loaded_cursor != x11rb::NONE {
|
||||
break 'outer Ok(loaded_cursor);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
errors.push_str(&err.to_string());
|
||||
errors.push('\n');
|
||||
}
|
||||
}
|
||||
if errors.is_empty() {
|
||||
result = Err(anyhow!(
|
||||
"errors while loading cursor icons {:?}:\n{}",
|
||||
cursor_icon_names,
|
||||
errors
|
||||
));
|
||||
} else {
|
||||
result = Err(anyhow!("did not find cursor icons {:?}", cursor_icon_names));
|
||||
}
|
||||
}
|
||||
if errors.is_empty() {
|
||||
Err(anyhow!(
|
||||
"errors while loading cursor icons {:?}:\n{}",
|
||||
cursor_icon_names,
|
||||
errors
|
||||
))
|
||||
} else {
|
||||
Err(anyhow!("did not find cursor icons {:?}", cursor_icon_names))
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2052,6 +2068,73 @@ impl X11ClientState {
|
||||
self.cursor_cache.insert(style, 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:
|
||||
|
||||
@@ -52,7 +52,10 @@ use std::{
|
||||
ptr,
|
||||
rc::Rc,
|
||||
slice, str,
|
||||
sync::{Arc, OnceLock, atomic::AtomicBool},
|
||||
sync::{
|
||||
Arc, OnceLock,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
},
|
||||
};
|
||||
use util::{
|
||||
ResultExt,
|
||||
@@ -179,7 +182,8 @@ pub(crate) struct MacPlatformState {
|
||||
dock_menu: Option<id>,
|
||||
menus: Option<Vec<OwnedMenu>>,
|
||||
keyboard_mapper: Rc<MacKeyboardMapper>,
|
||||
cursor_hidden: Arc<AtomicBool>,
|
||||
/// Mirrors `[NSCursor setHiddenUntilMouseMoves:]` state, which AppKit doesn't expose.
|
||||
cursor_visible: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl MacPlatform {
|
||||
@@ -216,7 +220,7 @@ impl MacPlatform {
|
||||
on_thermal_state_change: None,
|
||||
menus: None,
|
||||
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,
|
||||
options: WindowParams,
|
||||
) -> 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();
|
||||
(
|
||||
guard.cursor_hidden.clone(),
|
||||
guard.cursor_visible.clone(),
|
||||
guard.foreground_executor.clone(),
|
||||
guard.background_executor.clone(),
|
||||
guard.renderer_context.clone(),
|
||||
@@ -634,7 +638,7 @@ impl Platform for MacPlatform {
|
||||
Ok(Box::new(MacWindow::open(
|
||||
handle,
|
||||
options,
|
||||
cursor_hidden,
|
||||
cursor_visible,
|
||||
foreground_executor,
|
||||
background_executor,
|
||||
renderer_context,
|
||||
@@ -991,12 +995,25 @@ impl Platform for MacPlatform {
|
||||
/// Match cursor style to one of the styles available
|
||||
/// in macOS's [NSCursor](https://developer.apple.com/documentation/appkit/nscursor).
|
||||
fn set_cursor_style(&self, style: CursorStyle) {
|
||||
let cursor_hidden = self.0.lock().cursor_hidden.clone();
|
||||
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 {
|
||||
#[allow(non_upper_case_globals)]
|
||||
const NSScrollerStyleOverlay: NSInteger = 1;
|
||||
|
||||
@@ -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
|
||||
/// thread because it reads the active AppKit window and updates GPUI window state associated
|
||||
/// with Objective-C objects.
|
||||
pub(crate) unsafe fn set_active_window_cursor_style(
|
||||
style: CursorStyle,
|
||||
cursor_hidden: &AtomicBool,
|
||||
) {
|
||||
pub(crate) unsafe fn set_active_window_cursor_style(style: CursorStyle) {
|
||||
// SAFETY: The caller guarantees AppKit main-thread access. The class check ensures the
|
||||
// window has our WINDOW_STATE_IVAR before reading it.
|
||||
unsafe {
|
||||
@@ -345,9 +342,6 @@ pub(crate) unsafe fn set_active_window_cursor_style(
|
||||
};
|
||||
|
||||
let Some(active_window) = active_window else {
|
||||
if !matches!(style, CursorStyle::None) {
|
||||
unhide_cursor(cursor_hidden);
|
||||
}
|
||||
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 {
|
||||
let mut decl = ClassDecl::new(name, superclass).unwrap();
|
||||
@@ -496,7 +474,7 @@ struct MacWindowState {
|
||||
blurred_view: Option<id>,
|
||||
background_appearance: WindowBackgroundAppearance,
|
||||
cursor_style: CursorStyle,
|
||||
cursor_hidden: Arc<AtomicBool>,
|
||||
cursor_visible: Arc<AtomicBool>,
|
||||
display_link: Option<DisplayLink>,
|
||||
renderer: renderer::Renderer,
|
||||
request_frame_callback: Option<Box<dyn FnMut(RequestFrameOptions)>>,
|
||||
@@ -698,7 +676,7 @@ impl MacWindow {
|
||||
tabbing_identifier,
|
||||
..
|
||||
}: WindowParams,
|
||||
cursor_hidden: Arc<AtomicBool>,
|
||||
cursor_visible: Arc<AtomicBool>,
|
||||
foreground_executor: ForegroundExecutor,
|
||||
background_executor: BackgroundExecutor,
|
||||
renderer_context: renderer::Context,
|
||||
@@ -816,7 +794,7 @@ impl MacWindow {
|
||||
blurred_view: None,
|
||||
background_appearance: WindowBackgroundAppearance::Opaque,
|
||||
cursor_style: CursorStyle::Arrow,
|
||||
cursor_hidden,
|
||||
cursor_visible,
|
||||
display_link: None,
|
||||
renderer: renderer::new_renderer(
|
||||
renderer_context,
|
||||
@@ -1850,26 +1828,7 @@ extern "C" fn reset_cursor_rects(this: &Object, _: Sel) {
|
||||
let _: () = msg_send![super(this, class!(NSView)), resetCursorRects];
|
||||
|
||||
let window_state = get_window_state(this);
|
||||
let 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_style = window_state.lock().cursor_style;
|
||||
|
||||
let cursor: id = match cursor_style {
|
||||
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::DragCopy => msg_send![class!(NSCursor), dragCopyCursor],
|
||||
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 _: () = 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)) };
|
||||
|
||||
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 {
|
||||
PlatformInput::MouseDown(
|
||||
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 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
|
||||
// `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
|
||||
|
||||
@@ -327,12 +327,6 @@ pub fn cursor_style_methods(input: TokenStream) -> TokenStream {
|
||||
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()
|
||||
|
||||
@@ -13,11 +13,12 @@ use gpui::{
|
||||
use gpui_wgpu::WgpuContext;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cell::RefCell,
|
||||
cell::{Cell, RefCell},
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
static BUNDLED_FONTS: &[&[u8]] = &[
|
||||
include_bytes!("../../../assets/fonts/ibm-plex-sans/IBMPlexSans-Regular.ttf"),
|
||||
@@ -39,6 +40,9 @@ pub struct WebPlatform {
|
||||
active_display: Rc<dyn PlatformDisplay>,
|
||||
callbacks: RefCell<WebPlatformCallbacks>,
|
||||
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)]
|
||||
@@ -77,6 +81,14 @@ impl WebPlatform {
|
||||
let active_display: Rc<dyn PlatformDisplay> =
|
||||
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 {
|
||||
browser_window,
|
||||
background_executor,
|
||||
@@ -86,6 +98,9 @@ impl WebPlatform {
|
||||
active_display,
|
||||
callbacks: RefCell::new(WebPlatformCallbacks::default()),
|
||||
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::DragCopy => "copy",
|
||||
CursorStyle::ContextualMenu => "context-menu",
|
||||
CursorStyle::None => "none",
|
||||
};
|
||||
|
||||
if let Some(document) = self.browser_window.document() {
|
||||
if let Some(body) = document.body() {
|
||||
if let Err(error) = body.style().set_property("cursor", css_cursor) {
|
||||
log::warn!("Failed to set cursor style: {error:?}");
|
||||
}
|
||||
}
|
||||
self.last_cursor_css.set(css_cursor);
|
||||
if self.cursor_visible.get() {
|
||||
set_body_cursor(&self.browser_window, css_cursor);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
true
|
||||
}
|
||||
@@ -342,3 +364,72 @@ impl Platform for WebPlatform {
|
||||
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:?}");
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::rc::Rc;
|
||||
use std::{rc::Rc, sync::atomic::Ordering};
|
||||
|
||||
use ::util::ResultExt;
|
||||
use anyhow::Context as _;
|
||||
@@ -297,6 +297,7 @@ impl WindowsWindowInner {
|
||||
|
||||
fn handle_mouse_move_msg(&self, handle: HWND, lparam: LPARAM, wparam: WPARAM) -> Option<isize> {
|
||||
self.start_tracking_mouse(handle, TME_LEAVE);
|
||||
self.restore_cursor_after_hide();
|
||||
|
||||
let Some(mut func) = self.state.callbacks.input.take() else {
|
||||
return Some(1);
|
||||
@@ -330,6 +331,9 @@ impl WindowsWindowInner {
|
||||
|
||||
fn handle_mouse_leave_msg(&self) -> Option<isize> {
|
||||
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() {
|
||||
callback(false);
|
||||
self.state
|
||||
@@ -726,6 +730,10 @@ impl WindowsWindowInner {
|
||||
let activated = wparam.loword() > 0;
|
||||
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.
|
||||
// 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
|
||||
@@ -915,6 +923,7 @@ impl WindowsWindowInner {
|
||||
|
||||
fn handle_nc_mouse_move_msg(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {
|
||||
self.start_tracking_mouse(handle, TME_LEAVE | TME_NONCLIENT);
|
||||
self.restore_cursor_after_hide();
|
||||
|
||||
let mut func = self.state.callbacks.input.take()?;
|
||||
let scale_factor = self.state.scale_factor.get();
|
||||
@@ -1078,8 +1087,13 @@ impl WindowsWindowInner {
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let cursor = if self.state.cursor_visible.load(Ordering::Relaxed) {
|
||||
self.state.current_cursor.get()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
unsafe {
|
||||
SetCursor(self.state.current_cursor.get());
|
||||
SetCursor(cursor);
|
||||
};
|
||||
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) {
|
||||
if !self.state.hovered.get() {
|
||||
self.state.hovered.set(true);
|
||||
|
||||
@@ -63,6 +63,8 @@ pub(crate) struct WindowsPlatformState {
|
||||
jump_list: RefCell<JumpList>,
|
||||
// NOTE: standard cursor handles don't need to close.
|
||||
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>>,
|
||||
}
|
||||
|
||||
@@ -87,6 +89,7 @@ impl WindowsPlatformState {
|
||||
callbacks,
|
||||
jump_list: RefCell::new(jump_list),
|
||||
current_cursor: Cell::new(current_cursor),
|
||||
cursor_visible: Arc::new(AtomicBool::new(true)),
|
||||
directx_devices: RefCell::new(directx_devices),
|
||||
menus: RefCell::new(Vec::new()),
|
||||
}
|
||||
@@ -219,6 +222,7 @@ impl WindowsPlatform {
|
||||
icon: self.icon,
|
||||
executor: self.foreground_executor.clone(),
|
||||
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(),
|
||||
validation_number: self.inner.validation_number,
|
||||
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 {
|
||||
should_auto_hide_scrollbars().log_err().unwrap_or(false)
|
||||
}
|
||||
@@ -1015,6 +1044,7 @@ pub(crate) struct WindowCreationInfo {
|
||||
pub(crate) icon: HICON,
|
||||
pub(crate) executor: ForegroundExecutor,
|
||||
pub(crate) current_cursor: Option<HCURSOR>,
|
||||
pub(crate) cursor_visible: Arc<AtomicBool>,
|
||||
pub(crate) drop_target_helper: IDropTargetHelper,
|
||||
pub(crate) validation_number: usize,
|
||||
pub(crate) main_receiver: PriorityQueueReceiver<RunnableVariant>,
|
||||
|
||||
@@ -115,7 +115,6 @@ pub(crate) fn load_cursor(style: CursorStyle) -> Option<HCURSOR> {
|
||||
CursorStyle::ResizeUpLeftDownRight => (&SIZENWSE, IDC_SIZENWSE),
|
||||
CursorStyle::ResizeUpRightDownLeft => (&SIZENESW, IDC_SIZENESW),
|
||||
CursorStyle::OperationNotAllowed => (&NO, IDC_NO),
|
||||
CursorStyle::None => return None,
|
||||
_ => (&ARROW, IDC_ARROW),
|
||||
};
|
||||
Some(
|
||||
|
||||
@@ -66,6 +66,8 @@ pub struct WindowsWindowState {
|
||||
|
||||
pub click_state: ClickState,
|
||||
pub current_cursor: Cell<Option<HCURSOR>>,
|
||||
/// Shared with [`WindowsPlatformState::cursor_visible`].
|
||||
pub cursor_visible: Arc<AtomicBool>,
|
||||
pub nc_button_pressed: Cell<Option<u32>>,
|
||||
|
||||
pub display: Cell<WindowsDisplay>,
|
||||
@@ -98,6 +100,7 @@ impl WindowsWindowState {
|
||||
directx_devices: &DirectXDevices,
|
||||
window_params: &CREATESTRUCTW,
|
||||
current_cursor: Option<HCURSOR>,
|
||||
cursor_visible: Arc<AtomicBool>,
|
||||
display: WindowsDisplay,
|
||||
min_size: Option<Size<Pixels>>,
|
||||
appearance: WindowAppearance,
|
||||
@@ -158,6 +161,7 @@ impl WindowsWindowState {
|
||||
renderer: RefCell::new(renderer),
|
||||
click_state,
|
||||
current_cursor: Cell::new(current_cursor),
|
||||
cursor_visible,
|
||||
nc_button_pressed: Cell::new(nc_button_pressed),
|
||||
display: Cell::new(display),
|
||||
fullscreen: Cell::new(fullscreen),
|
||||
@@ -234,6 +238,7 @@ impl WindowsWindowInner {
|
||||
&context.directx_devices,
|
||||
cs,
|
||||
context.current_cursor,
|
||||
context.cursor_visible.clone(),
|
||||
context.display,
|
||||
context.min_size,
|
||||
context.appearance,
|
||||
@@ -373,6 +378,7 @@ struct WindowCreateContext {
|
||||
min_size: Option<Size<Pixels>>,
|
||||
executor: ForegroundExecutor,
|
||||
current_cursor: Option<HCURSOR>,
|
||||
cursor_visible: Arc<AtomicBool>,
|
||||
drop_target_helper: IDropTargetHelper,
|
||||
validation_number: usize,
|
||||
main_receiver: PriorityQueueReceiver<RunnableVariant>,
|
||||
@@ -394,6 +400,7 @@ impl WindowsWindow {
|
||||
icon,
|
||||
executor,
|
||||
current_cursor,
|
||||
cursor_visible,
|
||||
drop_target_helper,
|
||||
validation_number,
|
||||
main_receiver,
|
||||
@@ -473,6 +480,7 @@ impl WindowsWindow {
|
||||
min_size: params.window_min_size,
|
||||
executor,
|
||||
current_cursor,
|
||||
cursor_visible,
|
||||
drop_target_helper,
|
||||
validation_number,
|
||||
main_receiver,
|
||||
|
||||
@@ -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) 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()))
|
||||
}
|
||||
@@ -253,6 +253,10 @@ pub fn migrate_settings(text: &str) -> Result<Option<String>> {
|
||||
MigrationType::Json(
|
||||
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)
|
||||
}
|
||||
@@ -395,6 +399,10 @@ define_query!(
|
||||
KEYMAP_QUERY_2026_03_23,
|
||||
migrations::m_2026_03_23::KEYMAP_PATTERNS
|
||||
);
|
||||
define_query!(
|
||||
SETTINGS_QUERY_2026_05_04,
|
||||
migrations::m_2026_05_04::SETTINGS_PATTERNS
|
||||
);
|
||||
|
||||
// custom query
|
||||
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]
|
||||
fn test_promote_show_branch_icon_true_to_show_branch_status_icon_no_change_when_already_migrated()
|
||||
{
|
||||
|
||||
@@ -192,6 +192,7 @@ impl VsCodeSettings {
|
||||
..GlobalLspSettingsContent::default()
|
||||
}),
|
||||
helix_mode: None,
|
||||
hide_mouse: None,
|
||||
image_viewer: None,
|
||||
journal: None,
|
||||
language_models: None,
|
||||
@@ -267,7 +268,6 @@ impl VsCodeSettings {
|
||||
go_to_definition_fallback: None,
|
||||
go_to_definition_scroll_strategy: None,
|
||||
gutter: self.gutter_content(),
|
||||
hide_mouse: None,
|
||||
horizontal_scroll_margin: None,
|
||||
hover_popover_delay: self.read_u64("editor.hover.delay").map(Into::into),
|
||||
hover_popover_enabled: self.read_bool("editor.hover.enabled"),
|
||||
|
||||
@@ -22,10 +22,6 @@ pub struct EditorSettingsContent {
|
||||
///
|
||||
/// Default: bar
|
||||
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.
|
||||
///
|
||||
/// Default: inline
|
||||
@@ -836,34 +832,6 @@ pub enum GoToDefinitionScrollStrategy {
|
||||
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.
|
||||
///
|
||||
/// Default: inline
|
||||
|
||||
@@ -82,6 +82,35 @@ pub enum ParseStatus {
|
||||
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]
|
||||
#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)]
|
||||
pub struct SettingsContent {
|
||||
@@ -156,6 +185,13 @@ pub struct SettingsContent {
|
||||
/// Default: false
|
||||
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>,
|
||||
|
||||
/// A map of log scopes to the desired log level.
|
||||
|
||||
@@ -1145,9 +1145,9 @@ fn appearance_page() -> SettingsPage {
|
||||
description: "When to hide the mouse cursor.",
|
||||
field: Box::new(SettingField {
|
||||
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, _| {
|
||||
settings_content.editor.hide_mouse = value;
|
||||
settings_content.hide_mouse = value;
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
|
||||
@@ -7,8 +7,8 @@ mod surround;
|
||||
|
||||
use editor::display_map::{DisplayRow, DisplaySnapshot};
|
||||
use editor::{
|
||||
DisplayPoint, Editor, EditorSettings, HideMouseCursorOrigin, MultiBufferOffset,
|
||||
NavigationOverlayLabel, NavigationTargetOverlay, SelectionEffects, ToOffset, ToPoint, movement,
|
||||
DisplayPoint, Editor, EditorSettings, MultiBufferOffset, NavigationOverlayLabel,
|
||||
NavigationTargetOverlay, SelectionEffects, ToOffset, ToPoint, movement,
|
||||
};
|
||||
use gpui::actions;
|
||||
use gpui::{App, Context, Font, Hsla, Pixels, Window, WindowTextSystem};
|
||||
@@ -807,7 +807,6 @@ impl Vim {
|
||||
) {
|
||||
let count = Vim::take_count(cx).unwrap_or(1);
|
||||
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 mut selections = editor.selections.all::<Point>(&display_map);
|
||||
let max_point = display_map.buffer_snapshot().max_point();
|
||||
|
||||
@@ -22,8 +22,8 @@ mod visual;
|
||||
use crate::normal::paste::Paste as VimPaste;
|
||||
use collections::HashMap;
|
||||
use editor::{
|
||||
Anchor, Bias, Editor, EditorEvent, EditorSettings, HideMouseCursorOrigin, MultiBufferOffset,
|
||||
NavigationOverlayKey, NavigationTargetOverlay, SelectionEffects,
|
||||
Anchor, Bias, Editor, EditorEvent, EditorSettings, MultiBufferOffset, NavigationOverlayKey,
|
||||
NavigationTargetOverlay, SelectionEffects,
|
||||
actions::Paste,
|
||||
display_map::ToDisplayPoint,
|
||||
movement::{self, FindRange},
|
||||
@@ -1089,10 +1089,6 @@ impl Vim {
|
||||
if let Some(action) = keystroke_event.action.as_ref() {
|
||||
// Keystroke is handled by the vim system, so continue forward
|
||||
if action.name().starts_with("vim::") {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx)
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
} else if window.has_pending_keystrokes() || keystroke_event.keystroke.is_ime_in_progress()
|
||||
|
||||
@@ -382,6 +382,8 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut App) {
|
||||
})
|
||||
.detach();
|
||||
|
||||
init_cursor_hide_mode(cx);
|
||||
|
||||
cx.observe_new(|_multi_workspace: &mut MultiWorkspace, window, cx| {
|
||||
let Some(window) = window else {
|
||||
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) {
|
||||
MigrationNotification::set_global(cx.new(|_| MigrationNotification), cx);
|
||||
|
||||
|
||||
@@ -801,9 +801,8 @@ List of `string` values
|
||||
|
||||
## Hide Mouse
|
||||
|
||||
- Description: Determines when the mouse cursor should be hidden in an editor or input box.
|
||||
- Setting: `hide_mouse`
|
||||
- Default: `on_typing_and_movement`
|
||||
- Description: Determines when the mouse cursor should be hidden in response to keyboard input.
|
||||
- Default: `on_typing_and_action`
|
||||
|
||||
**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]
|
||||
{
|
||||
"hide_mouse": "on_typing_and_movement"
|
||||
"hide_mouse": "on_typing_and_action"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -183,8 +183,8 @@ TBD: Centered layout related settings
|
||||
// Highlight the current line in the editor: none, gutter, line, all
|
||||
"current_line_highlight": "all",
|
||||
|
||||
// When does the mouse cursor hide: never, on_typing, on_typing_and_movement
|
||||
"hide_mouse": "on_typing_and_movement",
|
||||
// When does the mouse cursor hide: never, on_typing, on_typing_and_action
|
||||
"hide_mouse": "on_typing_and_action",
|
||||
|
||||
// Whether to highlight all occurrences of the selected text in an editor.
|
||||
"selection_highlight": true,
|
||||
|
||||
Reference in new issue
Block a user