@@ -175,16 +175,14 @@ use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
use smallvec::{SmallVec, smallvec};
use snippet::Snippet;
use std::{
- any::TypeId,
+ any::{Any, TypeId},
borrow::Cow,
- cell::OnceCell,
- cell::RefCell,
+ cell::{OnceCell, RefCell},
cmp::{self, Ordering, Reverse},
iter::Peekable,
mem,
num::NonZeroU32,
- ops::Not,
- ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
+ ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
@@ -850,7 +848,7 @@ pub struct ResolvedTasks {
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
struct BufferOffset(usize);
-// Addons allow storing per-editor state in other crates (e.g. Vim)
+/// Addons allow storing per-editor state in other crates (e.g. Vim)
pub trait Addon: 'static {
fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
@@ -1178,6 +1176,7 @@ pub struct Editor {
next_color_inlay_id: usize,
colors: Option<LspColorData>,
folding_newlines: Task<()>,
+ pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
@@ -2257,6 +2256,7 @@ impl Editor {
mode,
selection_drag_state: SelectionDragState::None,
folding_newlines: Task::ready(()),
+ lookup_key: None,
};
if is_minimap {
@@ -16396,7 +16396,7 @@ impl Editor {
anyhow::Ok(Navigated::from_bool(opened))
} else if locations.is_empty() {
- // If there is one definition, just open it directly
+ // If there is one url or file, open it directly
match first_url_or_file {
Some(Either::Left(url)) => {
acx.update(|_, cx| cx.open_url(&url))?;
@@ -16423,8 +16423,6 @@ impl Editor {
let target = locations.pop().unwrap();
editor.update_in(acx, |editor, window, cx| {
- let pane = workspace.read(cx).active_pane().clone();
-
let range = target.range.to_point(target.buffer.read(cx));
let range = editor.range_for_match(&range);
let range = collapse_multiline_range(range);
@@ -16434,6 +16432,7 @@ impl Editor {
{
editor.go_to_singleton_buffer_range(range, window, cx);
} else {
+ let pane = workspace.read(cx).active_pane().clone();
window.defer(cx, move |window, cx| {
let target_editor: Entity<Self> =
workspace.update(cx, |workspace, cx| {
@@ -16607,13 +16606,17 @@ impl Editor {
return;
}
- // If there are multiple definitions, open them in a multibuffer
locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
+
let mut locations = locations.into_iter().peekable();
let mut ranges: Vec<Range<Anchor>> = Vec::new();
let capability = workspace.project().read(cx).capability();
+ // a key to find existing multibuffer editors with the same set of locations
+ // to prevent us from opening more and more multibuffer tabs for searches and the like
+ let mut key = (title.clone(), vec![]);
let excerpt_buffer = cx.new(|cx| {
+ let key = &mut key.1;
let mut multibuffer = MultiBuffer::new(capability);
while let Some(location) = locations.next() {
let buffer = location.buffer.read(cx);
@@ -16621,16 +16624,17 @@ impl Editor {
let range = location.range.to_point(buffer);
ranges_for_buffer.push(range.clone());
- while let Some(next_location) = locations.peek() {
- if next_location.buffer == location.buffer {
- ranges_for_buffer.push(next_location.range.to_point(buffer));
- locations.next();
- } else {
- break;
- }
+ while let Some(next_location) =
+ locations.next_if(|next_location| next_location.buffer == location.buffer)
+ {
+ ranges_for_buffer.push(next_location.range.to_point(buffer));
}
ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
+ key.push((
+ location.buffer.read(cx).remote_id(),
+ ranges_for_buffer.clone(),
+ ));
let (new_ranges, _) = multibuffer.set_excerpts_for_path(
PathKey::for_buffer(&location.buffer, cx),
location.buffer.clone(),
@@ -16643,14 +16647,31 @@ impl Editor {
multibuffer.with_title(title)
});
-
- let editor = cx.new(|cx| {
- Editor::for_multibuffer(
- excerpt_buffer,
- Some(workspace.project().clone()),
- window,
- cx,
- )
+ let existing = workspace.active_pane().update(cx, |pane, cx| {
+ pane.items()
+ .filter_map(|item| item.downcast::<Editor>())
+ .find(|editor| {
+ editor
+ .read(cx)
+ .lookup_key
+ .as_ref()
+ .and_then(|it| {
+ it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
+ })
+ .is_some_and(|it| *it == key)
+ })
+ });
+ let editor = existing.unwrap_or_else(|| {
+ cx.new(|cx| {
+ let mut editor = Editor::for_multibuffer(
+ excerpt_buffer,
+ Some(workspace.project().clone()),
+ window,
+ cx,
+ );
+ editor.lookup_key = Some(Box::new(key));
+ editor
+ })
});
editor.update(cx, |editor, cx| {
match multibuffer_selection_mode {
@@ -21402,6 +21402,159 @@ async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
});
}
+#[gpui::test]
+async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+ let mut cx = EditorLspTestContext::new_rust(
+ lsp::ServerCapabilities {
+ references_provider: Some(lsp::OneOf::Left(true)),
+ ..lsp::ServerCapabilities::default()
+ },
+ cx,
+ )
+ .await;
+
+ cx.set_state(
+ &r#"
+ fn one() {
+ let mut a = two();
+ }
+
+ fn ˇtwo() {}"#
+ .unindent(),
+ );
+ cx.lsp
+ .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
+ Ok(Some(vec![
+ lsp::Location {
+ uri: params.text_document_position.text_document.uri.clone(),
+ range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
+ },
+ lsp::Location {
+ uri: params.text_document_position.text_document.uri,
+ range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
+ },
+ ]))
+ });
+ let navigated = cx
+ .update_editor(|editor, window, cx| {
+ editor.find_all_references(&FindAllReferences, window, cx)
+ })
+ .unwrap()
+ .await
+ .expect("Failed to navigate to references");
+ assert_eq!(
+ navigated,
+ Navigated::Yes,
+ "Should have navigated to references from the FindAllReferences response"
+ );
+ cx.assert_editor_state(
+ &r#"fn one() {
+ let mut a = two();
+ }
+
+ fn ˇtwo() {}"#
+ .unindent(),
+ );
+
+ let editors = cx.update_workspace(|workspace, _, cx| {
+ workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
+ });
+ cx.update_editor(|_, _, _| {
+ assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
+ });
+
+ cx.set_state(
+ &r#"fn one() {
+ let mut a = ˇtwo();
+ }
+
+ fn two() {}"#
+ .unindent(),
+ );
+ let navigated = cx
+ .update_editor(|editor, window, cx| {
+ editor.find_all_references(&FindAllReferences, window, cx)
+ })
+ .unwrap()
+ .await
+ .expect("Failed to navigate to references");
+ assert_eq!(
+ navigated,
+ Navigated::Yes,
+ "Should have navigated to references from the FindAllReferences response"
+ );
+ cx.assert_editor_state(
+ &r#"fn one() {
+ let mut a = ˇtwo();
+ }
+
+ fn two() {}"#
+ .unindent(),
+ );
+ let editors = cx.update_workspace(|workspace, _, cx| {
+ workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
+ });
+ cx.update_editor(|_, _, _| {
+ assert_eq!(
+ editors.len(),
+ 2,
+ "should have re-used the previous multibuffer"
+ );
+ });
+
+ cx.set_state(
+ &r#"fn one() {
+ let mut a = ˇtwo();
+ }
+ fn three() {}
+ fn two() {}"#
+ .unindent(),
+ );
+ cx.lsp
+ .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
+ Ok(Some(vec![
+ lsp::Location {
+ uri: params.text_document_position.text_document.uri.clone(),
+ range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
+ },
+ lsp::Location {
+ uri: params.text_document_position.text_document.uri,
+ range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
+ },
+ ]))
+ });
+ let navigated = cx
+ .update_editor(|editor, window, cx| {
+ editor.find_all_references(&FindAllReferences, window, cx)
+ })
+ .unwrap()
+ .await
+ .expect("Failed to navigate to references");
+ assert_eq!(
+ navigated,
+ Navigated::Yes,
+ "Should have navigated to references from the FindAllReferences response"
+ );
+ cx.assert_editor_state(
+ &r#"fn one() {
+ let mut a = ˇtwo();
+ }
+ fn three() {}
+ fn two() {}"#
+ .unindent(),
+ );
+ let editors = cx.update_workspace(|workspace, _, cx| {
+ workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
+ });
+ cx.update_editor(|_, _, _| {
+ assert_eq!(
+ editors.len(),
+ 3,
+ "should have used a new multibuffer as offsets changed"
+ );
+ });
+}
#[gpui::test]
async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
init_test(cx, |_| {});