Use ESC to cancel dragging in Zed (#30318)

Kirill Bulatov created

Closes https://github.com/zed-industries/zed/issues/11887

ESC is always captured in terminal due to 


https://github.com/zed-industries/zed/blob/980bfae331dd5078bbba9fe0f3993d9ad5ca6e29/crates/terminal/src/terminal.rs#L1339-L1353

so this part is not fixed.

Otherwise, all other drags are cancelled when ESC is pressed:


https://github.com/user-attachments/assets/6e70a1e5-c244-420b-9dec-ae2ac2997a59


Release Notes:

- Allowed to use ESC to cancel dragging in Zed

Change summary

crates/collab_ui/src/collab_panel.rs      |  4 +++-
crates/debugger_ui/src/session/running.rs |  7 +++++++
crates/gpui/src/app.rs                    | 11 +++++++++++
crates/project_panel/src/project_panel.rs |  4 ++++
crates/workspace/src/pane.rs              |  7 +++++++
crates/workspace/src/workspace.rs         | 13 +++++++------
6 files changed, 39 insertions(+), 7 deletions(-)

Detailed changes

crates/collab_ui/src/collab_panel.rs 🔗

@@ -1463,7 +1463,9 @@ impl CollabPanel {
     }
 
     fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
-        if self.take_editing_state(window, cx) {
+        if cx.stop_active_drag(window) {
+            return;
+        } else if self.take_editing_state(window, cx) {
             window.focus(&self.filter_editor.focus_handle(cx));
         } else if !self.reset_filter_editor_text(window, cx) {
             self.focus_handle.focus(window);

crates/debugger_ui/src/session/running.rs 🔗

@@ -352,6 +352,13 @@ pub(crate) fn new_debugger_pane(
                     .px_2()
                     .border_color(cx.theme().colors().border)
                     .track_focus(&focus_handle)
+                    .on_action(|_: &menu::Cancel, window, cx| {
+                        if cx.stop_active_drag(window) {
+                            return;
+                        } else {
+                            cx.propagate();
+                        }
+                    })
                     .child(
                         h_flex()
                             .w_full()

crates/gpui/src/app.rs 🔗

@@ -1537,6 +1537,17 @@ impl App {
         self.active_drag.is_some()
     }
 
+    /// Stops active drag and clears any related effects.
+    pub fn stop_active_drag(&mut self, window: &mut Window) -> bool {
+        if self.active_drag.is_some() {
+            self.active_drag = None;
+            window.refresh();
+            true
+        } else {
+            false
+        }
+    }
+
     /// Set the prompt renderer for GPUI. This will replace the default or platform specific
     /// prompts with this custom implementation.
     pub fn set_prompt_builder(

crates/project_panel/src/project_panel.rs 🔗

@@ -1346,6 +1346,10 @@ impl ProjectPanel {
     }
 
     fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
+        if cx.stop_active_drag(window) {
+            return;
+        }
+
         let previous_edit_state = self.edit_state.take();
         self.update_visible_entries(None, cx);
         self.marked_entries.clear();

crates/workspace/src/pane.rs 🔗

@@ -3326,6 +3326,13 @@ impl Render for Pane {
                     }
                 }),
             )
+            .on_action(cx.listener(|_, _: &menu::Cancel, window, cx| {
+                if cx.stop_active_drag(window) {
+                    return;
+                } else {
+                    cx.propagate();
+                }
+            }))
             .when(self.active_item().is_some() && display_tab_bar, |pane| {
                 pane.child((self.render_tab_bar.clone())(self, window, cx))
             })

crates/workspace/src/workspace.rs 🔗

@@ -5491,14 +5491,15 @@ impl Workspace {
             .ok();
     }
 
-    pub fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
-        if let Some((notification_id, _)) = self.notifications.pop() {
-            dismiss_app_notification(&notification_id, cx);
+    pub fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
+        if cx.stop_active_drag(window) {
             return;
+        } else if let Some((notification_id, _)) = self.notifications.pop() {
+            dismiss_app_notification(&notification_id, cx);
+        } else {
+            cx.emit(Event::ClearActivityIndicator);
+            cx.propagate();
         }
-
-        cx.emit(Event::ClearActivityIndicator);
-        cx.propagate();
     }
 }