Merge pull request #481 from zed-industries/find-usages

Max Brunsfeld created

Find-all-references

Change summary

crates/editor/src/editor.rs       |  86 +++++++++++++
crates/project/src/lsp_command.rs | 205 ++++++++++++++++++++++++++++----
crates/project/src/project.rs     |  23 ++
crates/rpc/proto/zed.proto        | 122 ++++++++++--------
crates/rpc/src/proto.rs           |   4 
crates/server/src/rpc.rs          | 166 +++++++++++++++++++++++++-
6 files changed, 505 insertions(+), 101 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -118,6 +118,7 @@ action!(SelectSmallerSyntaxNode);
 action!(MoveToEnclosingBracket);
 action!(ShowNextDiagnostic);
 action!(GoToDefinition);
+action!(FindAllReferences);
 action!(Rename);
 action!(ConfirmRename);
 action!(PageUp);
@@ -249,6 +250,7 @@ pub fn init(cx: &mut MutableAppContext, path_openers: &mut Vec<Box<dyn PathOpene
         Binding::new("f8", ShowNextDiagnostic, Some("Editor")),
         Binding::new("f2", Rename, Some("Editor")),
         Binding::new("f12", GoToDefinition, Some("Editor")),
+        Binding::new("alt-shift-f12", FindAllReferences, Some("Editor")),
         Binding::new("ctrl-m", MoveToEnclosingBracket, Some("Editor")),
         Binding::new("pageup", PageUp, Some("Editor")),
         Binding::new("pagedown", PageDown, Some("Editor")),
@@ -326,6 +328,7 @@ pub fn init(cx: &mut MutableAppContext, path_openers: &mut Vec<Box<dyn PathOpene
     cx.add_async_action(Editor::confirm_code_action);
     cx.add_async_action(Editor::rename);
     cx.add_async_action(Editor::confirm_rename);
+    cx.add_async_action(Editor::find_all_references);
 }
 
 trait SelectionExt {
@@ -4069,11 +4072,9 @@ impl Editor {
             let definitions = definitions.await?;
             workspace.update(&mut cx, |workspace, cx| {
                 for definition in definitions {
-                    let range = definition
-                        .target_range
-                        .to_offset(definition.target_buffer.read(cx));
+                    let range = definition.range.to_offset(definition.buffer.read(cx));
                     let target_editor_handle = workspace
-                        .open_item(BufferItemHandle(definition.target_buffer), cx)
+                        .open_item(BufferItemHandle(definition.buffer), cx)
                         .downcast::<Self>()
                         .unwrap();
 
@@ -4098,6 +4099,83 @@ impl Editor {
         .detach_and_log_err(cx);
     }
 
+    pub fn find_all_references(
+        workspace: &mut Workspace,
+        _: &FindAllReferences,
+        cx: &mut ViewContext<Workspace>,
+    ) -> Option<Task<Result<()>>> {
+        let active_item = workspace.active_item(cx)?;
+        let editor_handle = active_item.act_as::<Self>(cx)?;
+
+        let editor = editor_handle.read(cx);
+        let buffer = editor.buffer.read(cx);
+        let head = editor.newest_selection::<usize>(&buffer.read(cx)).head();
+        let (buffer, head) = editor.buffer.read(cx).text_anchor_for_position(head, cx)?;
+        let replica_id = editor.replica_id(cx);
+
+        let references = workspace
+            .project()
+            .update(cx, |project, cx| project.references(&buffer, head, cx));
+        Some(cx.spawn(|workspace, mut cx| async move {
+            let mut locations = references.await?;
+            if locations.is_empty() {
+                return Ok(());
+            }
+
+            locations.sort_by_key(|location| location.buffer.id());
+            let mut locations = locations.into_iter().peekable();
+            let mut ranges_to_highlight = Vec::new();
+
+            let excerpt_buffer = cx.add_model(|cx| {
+                let mut symbol_name = None;
+                let mut multibuffer = MultiBuffer::new(replica_id);
+                while let Some(location) = locations.next() {
+                    let buffer = location.buffer.read(cx);
+                    let mut ranges_for_buffer = Vec::new();
+                    let range = location.range.to_offset(buffer);
+                    ranges_for_buffer.push(range.clone());
+                    if symbol_name.is_none() {
+                        symbol_name = Some(buffer.text_for_range(range).collect::<String>());
+                    }
+
+                    while let Some(next_location) = locations.peek() {
+                        if next_location.buffer == location.buffer {
+                            ranges_for_buffer.push(next_location.range.to_offset(buffer));
+                            locations.next();
+                        } else {
+                            break;
+                        }
+                    }
+
+                    ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
+                    ranges_to_highlight.extend(multibuffer.push_excerpts_with_context_lines(
+                        location.buffer.clone(),
+                        ranges_for_buffer,
+                        1,
+                        cx,
+                    ));
+                }
+                multibuffer.with_title(format!("References to `{}`", symbol_name.unwrap()))
+            });
+
+            workspace.update(&mut cx, |workspace, cx| {
+                let editor = workspace.open_item(MultiBufferItemHandle(excerpt_buffer), cx);
+                if let Some(editor) = editor.act_as::<Self>(cx) {
+                    editor.update(cx, |editor, cx| {
+                        let settings = (editor.build_settings)(cx);
+                        editor.highlight_ranges::<Self>(
+                            ranges_to_highlight,
+                            settings.style.highlighted_line_background,
+                            cx,
+                        );
+                    });
+                }
+            });
+
+            Ok(())
+        }))
+    }
+
     pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
         use language::ToOffset as _;
 

crates/project/src/lsp_command.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{Definition, Project, ProjectTransaction};
+use crate::{Location, Project, ProjectTransaction};
 use anyhow::{anyhow, Result};
 use async_trait::async_trait;
 use client::{proto, PeerId};
@@ -66,6 +66,10 @@ pub(crate) struct GetDefinition {
     pub position: PointUtf16,
 }
 
+pub(crate) struct GetReferences {
+    pub position: PointUtf16,
+}
+
 #[async_trait(?Send)]
 impl LspCommand for PrepareRename {
     type Response = Option<Range<Anchor>>;
@@ -287,7 +291,7 @@ impl LspCommand for PerformRename {
 
 #[async_trait(?Send)]
 impl LspCommand for GetDefinition {
-    type Response = Vec<Definition>;
+    type Response = Vec<Location>;
     type LspRequest = lsp::request::GotoDefinition;
     type ProtoRequest = proto::GetDefinition;
 
@@ -310,7 +314,7 @@ impl LspCommand for GetDefinition {
         project: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
         mut cx: AsyncAppContext,
-    ) -> Result<Vec<Definition>> {
+    ) -> Result<Vec<Location>> {
         let mut definitions = Vec::new();
         let (language, language_server) = buffer
             .read_with(&cx, |buffer, _| {
@@ -357,9 +361,9 @@ impl LspCommand for GetDefinition {
                         .clip_point_utf16(point_from_lsp(target_range.start), Bias::Left);
                     let target_end = target_buffer
                         .clip_point_utf16(point_from_lsp(target_range.end), Bias::Left);
-                    definitions.push(Definition {
-                        target_buffer: target_buffer_handle,
-                        target_range: target_buffer.anchor_after(target_start)
+                    definitions.push(Location {
+                        buffer: target_buffer_handle,
+                        range: target_buffer.anchor_after(target_start)
                             ..target_buffer.anchor_before(target_end),
                     });
                 });
@@ -393,25 +397,24 @@ impl LspCommand for GetDefinition {
     }
 
     fn response_to_proto(
-        response: Vec<Definition>,
+        response: Vec<Location>,
         project: &mut Project,
         peer_id: PeerId,
         _: &clock::Global,
         cx: &AppContext,
     ) -> proto::GetDefinitionResponse {
-        let definitions = response
+        let locations = response
             .into_iter()
             .map(|definition| {
-                let buffer =
-                    project.serialize_buffer_for_peer(&definition.target_buffer, peer_id, cx);
-                proto::Definition {
-                    target_start: Some(serialize_anchor(&definition.target_range.start)),
-                    target_end: Some(serialize_anchor(&definition.target_range.end)),
+                let buffer = project.serialize_buffer_for_peer(&definition.buffer, peer_id, cx);
+                proto::Location {
+                    start: Some(serialize_anchor(&definition.range.start)),
+                    end: Some(serialize_anchor(&definition.range.end)),
                     buffer: Some(buffer),
                 }
             })
             .collect();
-        proto::GetDefinitionResponse { definitions }
+        proto::GetDefinitionResponse { locations }
     }
 
     async fn response_from_proto(
@@ -420,30 +423,178 @@ impl LspCommand for GetDefinition {
         project: ModelHandle<Project>,
         _: ModelHandle<Buffer>,
         mut cx: AsyncAppContext,
-    ) -> Result<Vec<Definition>> {
-        let mut definitions = Vec::new();
-        for definition in message.definitions {
-            let buffer = definition.buffer.ok_or_else(|| anyhow!("missing buffer"))?;
-            let target_buffer = project
+    ) -> Result<Vec<Location>> {
+        let mut locations = Vec::new();
+        for location in message.locations {
+            let buffer = location.buffer.ok_or_else(|| anyhow!("missing buffer"))?;
+            let buffer = project
                 .update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
                 .await?;
-            let target_start = definition
-                .target_start
+            let start = location
+                .start
                 .and_then(deserialize_anchor)
                 .ok_or_else(|| anyhow!("missing target start"))?;
-            let target_end = definition
-                .target_end
+            let end = location
+                .end
                 .and_then(deserialize_anchor)
                 .ok_or_else(|| anyhow!("missing target end"))?;
-            definitions.push(Definition {
-                target_buffer,
-                target_range: target_start..target_end,
+            locations.push(Location {
+                buffer,
+                range: start..end,
             })
         }
-        Ok(definitions)
+        Ok(locations)
     }
 
     fn buffer_id_from_proto(message: &proto::GetDefinition) -> u64 {
         message.buffer_id
     }
 }
+
+#[async_trait(?Send)]
+impl LspCommand for GetReferences {
+    type Response = Vec<Location>;
+    type LspRequest = lsp::request::References;
+    type ProtoRequest = proto::GetReferences;
+
+    fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::ReferenceParams {
+        lsp::ReferenceParams {
+            text_document_position: lsp::TextDocumentPositionParams {
+                text_document: lsp::TextDocumentIdentifier {
+                    uri: lsp::Url::from_file_path(path).unwrap(),
+                },
+                position: self.position.to_lsp_position(),
+            },
+            work_done_progress_params: Default::default(),
+            partial_result_params: Default::default(),
+            context: lsp::ReferenceContext {
+                include_declaration: true,
+            },
+        }
+    }
+
+    async fn response_from_lsp(
+        self,
+        locations: Option<Vec<lsp::Location>>,
+        project: ModelHandle<Project>,
+        buffer: ModelHandle<Buffer>,
+        mut cx: AsyncAppContext,
+    ) -> Result<Vec<Location>> {
+        let mut references = Vec::new();
+        let (language, language_server) = buffer
+            .read_with(&cx, |buffer, _| {
+                buffer
+                    .language()
+                    .cloned()
+                    .zip(buffer.language_server().cloned())
+            })
+            .ok_or_else(|| anyhow!("buffer no longer has language server"))?;
+
+        if let Some(locations) = locations {
+            for lsp_location in locations {
+                let target_buffer_handle = project
+                    .update(&mut cx, |this, cx| {
+                        this.open_local_buffer_via_lsp(
+                            lsp_location.uri,
+                            language.name().to_string(),
+                            language_server.clone(),
+                            cx,
+                        )
+                    })
+                    .await?;
+
+                cx.read(|cx| {
+                    let target_buffer = target_buffer_handle.read(cx);
+                    let target_start = target_buffer
+                        .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
+                    let target_end = target_buffer
+                        .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
+                    references.push(Location {
+                        buffer: target_buffer_handle,
+                        range: target_buffer.anchor_after(target_start)
+                            ..target_buffer.anchor_before(target_end),
+                    });
+                });
+            }
+        }
+
+        Ok(references)
+    }
+
+    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetReferences {
+        proto::GetReferences {
+            project_id,
+            buffer_id: buffer.remote_id(),
+            position: Some(language::proto::serialize_anchor(
+                &buffer.anchor_before(self.position),
+            )),
+        }
+    }
+
+    fn from_proto(message: proto::GetReferences, _: &mut Project, buffer: &Buffer) -> Result<Self> {
+        let position = message
+            .position
+            .and_then(deserialize_anchor)
+            .ok_or_else(|| anyhow!("invalid position"))?;
+        if !buffer.can_resolve(&position) {
+            Err(anyhow!("cannot resolve position"))?;
+        }
+        Ok(Self {
+            position: position.to_point_utf16(buffer),
+        })
+    }
+
+    fn response_to_proto(
+        response: Vec<Location>,
+        project: &mut Project,
+        peer_id: PeerId,
+        _: &clock::Global,
+        cx: &AppContext,
+    ) -> proto::GetReferencesResponse {
+        let locations = response
+            .into_iter()
+            .map(|definition| {
+                let buffer = project.serialize_buffer_for_peer(&definition.buffer, peer_id, cx);
+                proto::Location {
+                    start: Some(serialize_anchor(&definition.range.start)),
+                    end: Some(serialize_anchor(&definition.range.end)),
+                    buffer: Some(buffer),
+                }
+            })
+            .collect();
+        proto::GetReferencesResponse { locations }
+    }
+
+    async fn response_from_proto(
+        self,
+        message: proto::GetReferencesResponse,
+        project: ModelHandle<Project>,
+        _: ModelHandle<Buffer>,
+        mut cx: AsyncAppContext,
+    ) -> Result<Vec<Location>> {
+        let mut locations = Vec::new();
+        for location in message.locations {
+            let buffer = location.buffer.ok_or_else(|| anyhow!("missing buffer"))?;
+            let target_buffer = project
+                .update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
+                .await?;
+            let start = location
+                .start
+                .and_then(deserialize_anchor)
+                .ok_or_else(|| anyhow!("missing target start"))?;
+            let end = location
+                .end
+                .and_then(deserialize_anchor)
+                .ok_or_else(|| anyhow!("missing target end"))?;
+            locations.push(Location {
+                buffer: target_buffer,
+                range: start..end,
+            })
+        }
+        Ok(locations)
+    }
+
+    fn buffer_id_from_proto(message: &proto::GetReferences) -> u64 {
+        message.buffer_id
+    }
+}

crates/project/src/project.rs 🔗

@@ -118,9 +118,9 @@ pub struct DiagnosticSummary {
 }
 
 #[derive(Debug)]
-pub struct Definition {
-    pub target_buffer: ModelHandle<Buffer>,
-    pub target_range: Range<language::Anchor>,
+pub struct Location {
+    pub buffer: ModelHandle<Buffer>,
+    pub range: Range<language::Anchor>,
 }
 
 #[derive(Clone, Debug)]
@@ -202,6 +202,7 @@ impl Project {
         client.add_entity_request_handler(Self::handle_get_code_actions);
         client.add_entity_request_handler(Self::handle_get_completions);
         client.add_entity_request_handler(Self::handle_lsp_command::<GetDefinition>);
+        client.add_entity_request_handler(Self::handle_lsp_command::<GetReferences>);
         client.add_entity_request_handler(Self::handle_lsp_command::<PrepareRename>);
         client.add_entity_request_handler(Self::handle_lsp_command::<PerformRename>);
         client.add_entity_request_handler(Self::handle_get_project_symbols);
@@ -1253,11 +1254,21 @@ impl Project {
         buffer: &ModelHandle<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<Definition>>> {
+    ) -> Task<Result<Vec<Location>>> {
         let position = position.to_point_utf16(buffer.read(cx));
         self.request_lsp(buffer.clone(), GetDefinition { position }, cx)
     }
 
+    pub fn references<T: ToPointUtf16>(
+        &self,
+        buffer: &ModelHandle<Buffer>,
+        position: T,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<Vec<Location>>> {
+        let position = position.to_point_utf16(buffer.read(cx));
+        self.request_lsp(buffer.clone(), GetReferences { position }, cx)
+    }
+
     pub fn symbols(&self, query: &str, cx: &mut ModelContext<Self>) -> Task<Result<Vec<Symbol>>> {
         if self.is_local() {
             let mut language_servers = HashMap::default();
@@ -3606,7 +3617,7 @@ mod tests {
         assert_eq!(definitions.len(), 1);
         let definition = definitions.pop().unwrap();
         cx.update(|cx| {
-            let target_buffer = definition.target_buffer.read(cx);
+            let target_buffer = definition.buffer.read(cx);
             assert_eq!(
                 target_buffer
                     .file()
@@ -3616,7 +3627,7 @@ mod tests {
                     .abs_path(cx),
                 Path::new("/dir/a.rs"),
             );
-            assert_eq!(definition.target_range.to_offset(target_buffer), 9..10);
+            assert_eq!(definition.range.to_offset(target_buffer), 9..10);
             assert_eq!(
                 list_worktrees(&project, cx),
                 [("/dir/b.rs".as_ref(), false), ("/dir/a.rs".as_ref(), true)]

crates/rpc/proto/zed.proto 🔗

@@ -23,57 +23,59 @@ message Envelope {
         RemoveProjectCollaborator remove_project_collaborator = 17;
         GetDefinition get_definition = 18;
         GetDefinitionResponse get_definition_response = 19;
-        GetProjectSymbols get_project_symbols = 20;
-        GetProjectSymbolsResponse get_project_symbols_response = 21;
-        OpenBufferForSymbol open_buffer_for_symbol = 22;
-        OpenBufferForSymbolResponse open_buffer_for_symbol_response = 23;
-
-        RegisterWorktree register_worktree = 24;
-        UnregisterWorktree unregister_worktree = 25;
-        ShareWorktree share_worktree = 26;
-        UpdateWorktree update_worktree = 27;
-        UpdateDiagnosticSummary update_diagnostic_summary = 28;
-        DiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 29;
-        DiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 30;
-
-        OpenBuffer open_buffer = 31;
-        OpenBufferResponse open_buffer_response = 32;
-        CloseBuffer close_buffer = 33;
-        UpdateBuffer update_buffer = 34;
-        UpdateBufferFile update_buffer_file = 35;
-        SaveBuffer save_buffer = 36;
-        BufferSaved buffer_saved = 37;
-        BufferReloaded buffer_reloaded = 38;
-        FormatBuffers format_buffers = 39;
-        FormatBuffersResponse format_buffers_response = 40;
-        GetCompletions get_completions = 41;
-        GetCompletionsResponse get_completions_response = 42;
-        ApplyCompletionAdditionalEdits apply_completion_additional_edits = 43;
-        ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 44;
-        GetCodeActions get_code_actions = 45;
-        GetCodeActionsResponse get_code_actions_response = 46;
-        ApplyCodeAction apply_code_action = 47;
-        ApplyCodeActionResponse apply_code_action_response = 48;
-        PrepareRename prepare_rename = 49;
-        PrepareRenameResponse prepare_rename_response = 50;
-        PerformRename perform_rename = 51;
-        PerformRenameResponse perform_rename_response = 52;
-
-        GetChannels get_channels = 53;
-        GetChannelsResponse get_channels_response = 54;
-        JoinChannel join_channel = 55;
-        JoinChannelResponse join_channel_response = 56;
-        LeaveChannel leave_channel = 57;
-        SendChannelMessage send_channel_message = 58;
-        SendChannelMessageResponse send_channel_message_response = 59;
-        ChannelMessageSent channel_message_sent = 60;
-        GetChannelMessages get_channel_messages = 61;
-        GetChannelMessagesResponse get_channel_messages_response = 62;
-
-        UpdateContacts update_contacts = 63;
-
-        GetUsers get_users = 64;
-        GetUsersResponse get_users_response = 65;
+        GetReferences get_references = 20;
+        GetReferencesResponse get_references_response = 21;
+        GetProjectSymbols get_project_symbols = 22;
+        GetProjectSymbolsResponse get_project_symbols_response = 23;
+        OpenBufferForSymbol open_buffer_for_symbol = 24;
+        OpenBufferForSymbolResponse open_buffer_for_symbol_response = 25;
+
+        RegisterWorktree register_worktree = 26;
+        UnregisterWorktree unregister_worktree = 27;
+        ShareWorktree share_worktree = 28;
+        UpdateWorktree update_worktree = 29;
+        UpdateDiagnosticSummary update_diagnostic_summary = 30;
+        DiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 31;
+        DiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 32;
+
+        OpenBuffer open_buffer = 33;
+        OpenBufferResponse open_buffer_response = 34;
+        CloseBuffer close_buffer = 35;
+        UpdateBuffer update_buffer = 36;
+        UpdateBufferFile update_buffer_file = 37;
+        SaveBuffer save_buffer = 38;
+        BufferSaved buffer_saved = 39;
+        BufferReloaded buffer_reloaded = 40;
+        FormatBuffers format_buffers = 41;
+        FormatBuffersResponse format_buffers_response = 42;
+        GetCompletions get_completions = 43;
+        GetCompletionsResponse get_completions_response = 44;
+        ApplyCompletionAdditionalEdits apply_completion_additional_edits = 45;
+        ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 46;
+        GetCodeActions get_code_actions = 47;
+        GetCodeActionsResponse get_code_actions_response = 48;
+        ApplyCodeAction apply_code_action = 49;
+        ApplyCodeActionResponse apply_code_action_response = 50;
+        PrepareRename prepare_rename = 51;
+        PrepareRenameResponse prepare_rename_response = 52;
+        PerformRename perform_rename = 53;
+        PerformRenameResponse perform_rename_response = 54;
+
+        GetChannels get_channels = 55;
+        GetChannelsResponse get_channels_response = 56;
+        JoinChannel join_channel = 57;
+        JoinChannelResponse join_channel_response = 58;
+        LeaveChannel leave_channel = 59;
+        SendChannelMessage send_channel_message = 60;
+        SendChannelMessageResponse send_channel_message_response = 61;
+        ChannelMessageSent channel_message_sent = 62;
+        GetChannelMessages get_channel_messages = 63;
+        GetChannelMessagesResponse get_channel_messages_response = 64;
+
+        UpdateContacts update_contacts = 65;
+
+        GetUsers get_users = 66;
+        GetUsersResponse get_users_response = 67;
     }
 }
 
@@ -166,13 +168,23 @@ message GetDefinition {
  }
 
 message GetDefinitionResponse {
-    repeated Definition definitions = 1;
+    repeated Location locations = 1;
 }
 
-message Definition {
+message GetReferences {
+     uint64 project_id = 1;
+     uint64 buffer_id = 2;
+     Anchor position = 3;
+ }
+
+message GetReferencesResponse {
+    repeated Location locations = 1;
+}
+
+message Location {
     Buffer buffer = 1;
-    Anchor target_start = 2;
-    Anchor target_end = 3;
+    Anchor start = 2;
+    Anchor end = 3;
 }
 
 message GetProjectSymbols {

crates/rpc/src/proto.rs 🔗

@@ -157,6 +157,8 @@ messages!(
     (GetCompletionsResponse, Foreground),
     (GetDefinition, Foreground),
     (GetDefinitionResponse, Foreground),
+    (GetReferences, Foreground),
+    (GetReferencesResponse, Foreground),
     (GetProjectSymbols, Background),
     (GetProjectSymbolsResponse, Background),
     (GetUsers, Foreground),
@@ -208,6 +210,7 @@ request_messages!(
     (GetCodeActions, GetCodeActionsResponse),
     (GetCompletions, GetCompletionsResponse),
     (GetDefinition, GetDefinitionResponse),
+    (GetReferences, GetReferencesResponse),
     (GetProjectSymbols, GetProjectSymbolsResponse),
     (GetUsers, GetUsersResponse),
     (JoinChannel, JoinChannelResponse),
@@ -242,6 +245,7 @@ entity_messages!(
     GetCodeActions,
     GetCompletions,
     GetDefinition,
+    GetReferences,
     GetProjectSymbols,
     JoinProject,
     LeaveProject,

crates/server/src/rpc.rs 🔗

@@ -79,6 +79,7 @@ impl Server {
             .add_message_handler(Server::disk_based_diagnostics_updating)
             .add_message_handler(Server::disk_based_diagnostics_updated)
             .add_request_handler(Server::get_definition)
+            .add_request_handler(Server::get_references)
             .add_request_handler(Server::get_project_symbols)
             .add_request_handler(Server::open_buffer_for_symbol)
             .add_request_handler(Server::open_buffer)
@@ -589,6 +590,20 @@ impl Server {
             .await?)
     }
 
+    async fn get_references(
+        self: Arc<Server>,
+        request: TypedEnvelope<proto::GetReferences>,
+    ) -> tide::Result<proto::GetReferencesResponse> {
+        let host_connection_id = self
+            .state()
+            .read_project(request.payload.project_id, request.sender_id)?
+            .host_connection_id;
+        Ok(self
+            .peer
+            .forward_request(request.sender_id, host_connection_id, request.payload)
+            .await?)
+    }
+
     async fn get_project_symbols(
         self: Arc<Server>,
         request: TypedEnvelope<proto::GetProjectSymbols>,
@@ -2658,13 +2673,13 @@ mod tests {
         cx_b.read(|cx| {
             assert_eq!(definitions_1.len(), 1);
             assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
-            let target_buffer = definitions_1[0].target_buffer.read(cx);
+            let target_buffer = definitions_1[0].buffer.read(cx);
             assert_eq!(
                 target_buffer.text(),
                 "const TWO: usize = 2;\nconst THREE: usize = 3;"
             );
             assert_eq!(
-                definitions_1[0].target_range.to_point(target_buffer),
+                definitions_1[0].range.to_point(target_buffer),
                 Point::new(0, 6)..Point::new(0, 9)
             );
         });
@@ -2683,20 +2698,17 @@ mod tests {
         cx_b.read(|cx| {
             assert_eq!(definitions_2.len(), 1);
             assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
-            let target_buffer = definitions_2[0].target_buffer.read(cx);
+            let target_buffer = definitions_2[0].buffer.read(cx);
             assert_eq!(
                 target_buffer.text(),
                 "const TWO: usize = 2;\nconst THREE: usize = 3;"
             );
             assert_eq!(
-                definitions_2[0].target_range.to_point(target_buffer),
+                definitions_2[0].range.to_point(target_buffer),
                 Point::new(1, 6)..Point::new(1, 11)
             );
         });
-        assert_eq!(
-            definitions_1[0].target_buffer,
-            definitions_2[0].target_buffer
-        );
+        assert_eq!(definitions_1[0].buffer, definitions_2[0].buffer);
 
         cx_b.update(|_| {
             drop(definitions_1);
@@ -2707,6 +2719,142 @@ mod tests {
             .await;
     }
 
+    #[gpui::test(iterations = 10)]
+    async fn test_references(mut cx_a: TestAppContext, mut cx_b: TestAppContext) {
+        cx_a.foreground().forbid_parking();
+        let mut lang_registry = Arc::new(LanguageRegistry::new());
+        let fs = FakeFs::new(cx_a.background());
+        fs.insert_tree(
+            "/root-1",
+            json!({
+                ".zed.toml": r#"collaborators = ["user_b"]"#,
+                "one.rs": "const ONE: usize = 1;",
+                "two.rs": "const TWO: usize = one::ONE + one::ONE;",
+            }),
+        )
+        .await;
+        fs.insert_tree(
+            "/root-2",
+            json!({
+                "three.rs": "const THREE: usize = two::TWO + one::ONE;",
+            }),
+        )
+        .await;
+
+        // Set up a fake language server.
+        let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake();
+        Arc::get_mut(&mut lang_registry)
+            .unwrap()
+            .add(Arc::new(Language::new(
+                LanguageConfig {
+                    name: "Rust".into(),
+                    path_suffixes: vec!["rs".to_string()],
+                    language_server: Some(language_server_config),
+                    ..Default::default()
+                },
+                Some(tree_sitter_rust::language()),
+            )));
+
+        // Connect to a server as 2 clients.
+        let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+        let client_a = server.create_client(&mut cx_a, "user_a").await;
+        let client_b = server.create_client(&mut cx_b, "user_b").await;
+
+        // Share a project as client A
+        let project_a = cx_a.update(|cx| {
+            Project::local(
+                client_a.clone(),
+                client_a.user_store.clone(),
+                lang_registry.clone(),
+                fs.clone(),
+                cx,
+            )
+        });
+        let (worktree_a, _) = project_a
+            .update(&mut cx_a, |p, cx| {
+                p.find_or_create_local_worktree("/root-1", false, cx)
+            })
+            .await
+            .unwrap();
+        worktree_a
+            .read_with(&cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
+            .await;
+        let project_id = project_a.update(&mut cx_a, |p, _| p.next_remote_id()).await;
+        let worktree_id = worktree_a.read_with(&cx_a, |tree, _| tree.id());
+        project_a
+            .update(&mut cx_a, |p, cx| p.share(cx))
+            .await
+            .unwrap();
+
+        // Join the worktree as client B.
+        let project_b = Project::remote(
+            project_id,
+            client_b.clone(),
+            client_b.user_store.clone(),
+            lang_registry.clone(),
+            fs.clone(),
+            &mut cx_b.to_async(),
+        )
+        .await
+        .unwrap();
+
+        // Open the file on client B.
+        let buffer_b = cx_b
+            .background()
+            .spawn(project_b.update(&mut cx_b, |p, cx| {
+                p.open_buffer((worktree_id, "one.rs"), cx)
+            }))
+            .await
+            .unwrap();
+
+        // Request references to a symbol as the guest.
+        let references = project_b.update(&mut cx_b, |p, cx| p.references(&buffer_b, 7, cx));
+
+        let mut fake_language_server = fake_language_servers.next().await.unwrap();
+        fake_language_server.handle_request::<lsp::request::References, _>(|params| {
+            assert_eq!(
+                params.text_document_position.text_document.uri.as_str(),
+                "file:///root-1/one.rs"
+            );
+            Some(vec![
+                lsp::Location {
+                    uri: lsp::Url::from_file_path("/root-1/two.rs").unwrap(),
+                    range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
+                },
+                lsp::Location {
+                    uri: lsp::Url::from_file_path("/root-1/two.rs").unwrap(),
+                    range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
+                },
+                lsp::Location {
+                    uri: lsp::Url::from_file_path("/root-2/three.rs").unwrap(),
+                    range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
+                },
+            ])
+        });
+
+        let references = references.await.unwrap();
+        cx_b.read(|cx| {
+            assert_eq!(references.len(), 3);
+            assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
+
+            let two_buffer = references[0].buffer.read(cx);
+            let three_buffer = references[2].buffer.read(cx);
+            assert_eq!(
+                two_buffer.file().unwrap().path().as_ref(),
+                Path::new("two.rs")
+            );
+            assert_eq!(references[1].buffer, references[0].buffer);
+            assert_eq!(
+                three_buffer.file().unwrap().full_path(cx),
+                Path::new("three.rs")
+            );
+
+            assert_eq!(references[0].range.to_offset(&two_buffer), 24..27);
+            assert_eq!(references[1].range.to_offset(&two_buffer), 35..38);
+            assert_eq!(references[2].range.to_offset(&three_buffer), 37..40);
+        });
+    }
+
     #[gpui::test(iterations = 10)]
     async fn test_project_symbols(mut cx_a: TestAppContext, mut cx_b: TestAppContext) {
         cx_a.foreground().forbid_parking();
@@ -2950,7 +3098,7 @@ mod tests {
         let buffer_b2 = buffer_b2.await.unwrap();
         let definitions = definitions.await.unwrap();
         assert_eq!(definitions.len(), 1);
-        assert_eq!(definitions[0].target_buffer, buffer_b2);
+        assert_eq!(definitions[0].buffer, buffer_b2);
     }
 
     #[gpui::test(iterations = 10)]