Open excerpts on alt-enter

Nathan Sobo and Max Brunsfeld created

Also: Remove special handling for alt-shift-D binding in diagnostics view that opens excerpts. Rely on alt-enter in all multi-buffers instead.
Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>

Change summary

crates/diagnostics/src/diagnostics.rs | 58 +--------------------
crates/editor/src/editor.rs           | 76 ++++++++++++++++++++++++++--
crates/workspace/src/pane.rs          | 16 ++++++
3 files changed, 88 insertions(+), 62 deletions(-)

Detailed changes

crates/diagnostics/src/diagnostics.rs 🔗

@@ -1,13 +1,11 @@
 pub mod items;
 
 use anyhow::Result;
-use collections::{BTreeSet, HashMap, HashSet};
+use collections::{BTreeSet, HashSet};
 use editor::{
     diagnostic_block_renderer,
     display_map::{BlockDisposition, BlockId, BlockProperties, RenderBlock},
-    highlight_diagnostic_message,
-    items::BufferItemHandle,
-    Autoscroll, Editor, ExcerptId, MultiBuffer, ToOffset,
+    highlight_diagnostic_message, Editor, ExcerptId, MultiBuffer, ToOffset,
 };
 use gpui::{
     action, elements::*, fonts::TextStyle, keymap::Binding, AnyViewHandle, AppContext, Entity,
@@ -31,21 +29,12 @@ use util::TryFutureExt;
 use workspace::{ItemHandle, ItemNavHistory, ItemViewHandle as _, Workspace};
 
 action!(Deploy);
-action!(OpenExcerpts);
 
 const CONTEXT_LINE_COUNT: u32 = 1;
 
 pub fn init(cx: &mut MutableAppContext) {
-    cx.add_bindings([
-        Binding::new("alt-shift-D", Deploy, Some("Workspace")),
-        Binding::new(
-            "alt-shift-D",
-            OpenExcerpts,
-            Some("ProjectDiagnosticsEditor"),
-        ),
-    ]);
+    cx.add_bindings([Binding::new("alt-shift-D", Deploy, Some("Workspace"))]);
     cx.add_action(ProjectDiagnosticsEditor::deploy);
-    cx.add_action(ProjectDiagnosticsEditor::open_excerpts);
 }
 
 type Event = editor::Event;
@@ -180,47 +169,6 @@ impl ProjectDiagnosticsEditor {
         }
     }
 
-    fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext<Self>) {
-        if let Some(workspace) = self.workspace.upgrade(cx) {
-            let editor = self.editor.read(cx);
-            let excerpts = self.excerpts.read(cx);
-            let mut new_selections_by_buffer = HashMap::default();
-
-            for selection in editor.local_selections::<usize>(cx) {
-                for (buffer, mut range) in
-                    excerpts.range_to_buffer_ranges(selection.start..selection.end, cx)
-                {
-                    if selection.reversed {
-                        mem::swap(&mut range.start, &mut range.end);
-                    }
-                    new_selections_by_buffer
-                        .entry(buffer)
-                        .or_insert(Vec::new())
-                        .push(range)
-                }
-            }
-
-            // We defer the pane interaction because we ourselves are a workspace item
-            // and activating a new item causes the pane to call a method on us reentrantly,
-            // which panics if we're on the stack.
-            workspace.defer(cx, |workspace, cx| {
-                for (buffer, ranges) in new_selections_by_buffer {
-                    let buffer = BufferItemHandle(buffer);
-                    if !workspace.activate_pane_for_item(&buffer, cx) {
-                        workspace.activate_next_pane(cx);
-                    }
-                    let editor = workspace
-                        .open_item(buffer, cx)
-                        .downcast::<Editor>()
-                        .unwrap();
-                    editor.update(cx, |editor, cx| {
-                        editor.select_ranges(ranges, Some(Autoscroll::Center), cx)
-                    });
-                }
-            });
-        }
-    }
-
     fn update_excerpts(&mut self, cx: &mut ViewContext<Self>) {
         let paths = mem::take(&mut self.paths_to_update);
         let project = self.model.read(cx).project.clone();

crates/editor/src/editor.rs 🔗

@@ -132,6 +132,7 @@ action!(ShowCompletions);
 action!(ToggleCodeActions, bool);
 action!(ConfirmCompletion, Option<usize>);
 action!(ConfirmCodeAction, Option<usize>);
+action!(OpenExcerpts);

 
 pub fn init(cx: &mut MutableAppContext, path_openers: &mut Vec<Box<dyn PathOpener>>) {
     path_openers.push(Box::new(items::BufferOpener));
@@ -259,6 +260,7 @@ pub fn init(cx: &mut MutableAppContext, path_openers: &mut Vec<Box<dyn PathOpene
         Binding::new("alt-cmd-f", FoldSelectedRanges, Some("Editor")),
         Binding::new("ctrl-space", ShowCompletions, Some("Editor")),
         Binding::new("cmd-.", ToggleCodeActions(false), Some("Editor")),
+        Binding::new("alt-enter", OpenExcerpts, Some("Editor")),

     ]);
 
     cx.add_action(Editor::open_new);
@@ -324,6 +326,7 @@ pub fn init(cx: &mut MutableAppContext, path_openers: &mut Vec<Box<dyn PathOpene
     cx.add_action(Editor::fold_selected_ranges);
     cx.add_action(Editor::show_completions);
     cx.add_action(Editor::toggle_code_actions);
+    cx.add_action(Editor::open_excerpts);

     cx.add_async_action(Editor::confirm_completion);
     cx.add_async_action(Editor::confirm_code_action);
     cx.add_async_action(Editor::rename);
@@ -4179,14 +4182,14 @@ impl Editor {
                     target_editor_handle.update(cx, |target_editor, cx| {
                         // When selecting a definition in a different buffer, disable the nav history
                         // to avoid creating a history entry at the previous cursor location.
-                        let disabled_history = if editor_handle == target_editor_handle {

-                            None

-                        } else {

-                            target_editor.nav_history.take()

-                        };

+                        let prev_nav_history_len = target_editor

+                            .nav_history()

+                            .map_or(0, |history| history.len());

                         target_editor.select_ranges([range], Some(Autoscroll::Center), cx);
-                        if disabled_history.is_some() {

-                            target_editor.nav_history = disabled_history;

+                        if editor_handle != target_editor_handle {

+                            if let Some(history) = target_editor.nav_history() {

+                                history.truncate(prev_nav_history_len);

+                            }

                         }
                     });
                 }
@@ -5351,6 +5354,65 @@ impl Editor {
     pub fn searchable(&self) -> bool {
         self.searchable
     }
+

+    fn open_excerpts(workspace: &mut Workspace, _: &OpenExcerpts, cx: &mut ViewContext<Workspace>) {

+        let active_item = workspace.active_item(cx);

+        let editor_handle = if let Some(editor) = active_item

+            .as_ref()

+            .and_then(|item| item.act_as::<Self>(cx))

+        {

+            editor

+        } else {

+            cx.propagate_action();

+            return;

+        };

+

+        let editor = editor_handle.read(cx);

+        let buffer = editor.buffer.read(cx);

+        if buffer.is_singleton() {

+            cx.propagate_action();

+            return;

+        }

+

+        let mut new_selections_by_buffer = HashMap::default();

+        for selection in editor.local_selections::<usize>(cx) {

+            for (buffer, mut range) in

+                buffer.range_to_buffer_ranges(selection.start..selection.end, cx)

+            {

+                if selection.reversed {

+                    mem::swap(&mut range.start, &mut range.end);

+                }

+                new_selections_by_buffer

+                    .entry(buffer)

+                    .or_insert(Vec::new())

+                    .push(range)

+            }

+        }

+

+        // We defer the pane interaction because we ourselves are a workspace item

+        // and activating a new item causes the pane to call a method on us reentrantly,

+        // which panics if we're on the stack.

+        cx.defer(|workspace, cx| {

+            for (buffer, ranges) in new_selections_by_buffer {

+                let buffer = BufferItemHandle(buffer);

+                if !workspace.activate_pane_for_item(&buffer, cx) {

+                    workspace.activate_next_pane(cx);

+                }

+                let editor = workspace

+                    .open_item(buffer, cx)

+                    .downcast::<Editor>()

+                    .unwrap();

+                editor.update(cx, |editor, cx| {

+                    let prev_nav_history_len =

+                        editor.nav_history().map_or(0, |history| history.len());

+                    editor.select_ranges(ranges, Some(Autoscroll::Newest), cx);

+                    if let Some(history) = editor.nav_history() {

+                        history.truncate(prev_nav_history_len);

+                    }

+                });

+            }

+        });

+    }

 }
 
 impl EditorSnapshot {

crates/workspace/src/pane.rs 🔗

@@ -649,6 +649,14 @@ impl<T: Toolbar> ToolbarHandle for ViewHandle<T> {
 }
 
 impl ItemNavHistory {
+    pub fn len(&self) -> usize {
+        self.history.borrow().len()
+    }
+
+    pub fn truncate(&self, len: usize) {
+        self.history.borrow_mut().truncate(len)
+    }
+
     pub fn new<T: ItemView>(history: Rc<RefCell<NavHistory>>, item_view: &ViewHandle<T>) -> Self {
         Self {
             history,
@@ -666,6 +674,14 @@ impl ItemNavHistory {
 }
 
 impl NavHistory {
+    pub fn len(&self) -> usize {
+        self.backward_stack.len()
+    }
+
+    pub fn truncate(&mut self, len: usize) {
+        self.backward_stack.truncate(len);
+    }
+
     pub fn pop_backward(&mut self) -> Option<NavigationEntry> {
         self.backward_stack.pop_back()
     }