From 0ebb25b29b0988be89f42091fd373ea58d7ff9fb Mon Sep 17 00:00:00 2001
From: Aleksey Kladov <aleksey.kladov@gmail.com>
Date: Mon, 25 May 2020 15:55:25 +0200
Subject: [PATCH] Document `parentModule` experimental LSP request

---
 crates/rust-analyzer/src/caps.rs              |  1 +
 crates/rust-analyzer/src/lsp_ext.rs           |  6 +--
 .../rust-analyzer/src/main_loop/handlers.rs   | 32 +++++----------
 crates/rust-analyzer/src/to_proto.rs          | 15 +++++--
 docs/dev/lsp-extensions.md                    | 40 ++++++++++++++++---
 editors/code/src/commands.ts                  |  6 +--
 editors/code/src/lsp_ext.ts                   |  2 +-
 7 files changed, 64 insertions(+), 38 deletions(-)

diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs
index d55cbb15fe8..345693524ea 100644
--- a/crates/rust-analyzer/src/caps.rs
+++ b/crates/rust-analyzer/src/caps.rs
@@ -86,6 +86,7 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti
             "joinLines": true,
             "ssr": true,
             "onEnter": true,
+            "parentModule": true,
         })),
     }
 }
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index c571c62aed3..acb1dacb6b6 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -3,7 +3,7 @@
 use std::{collections::HashMap, path::PathBuf};
 
 use lsp_types::request::Request;
-use lsp_types::{Location, Position, Range, TextDocumentIdentifier};
+use lsp_types::{Position, Range, TextDocumentIdentifier};
 use rustc_hash::FxHashMap;
 use serde::{Deserialize, Serialize};
 
@@ -79,8 +79,8 @@ pub enum ParentModule {}
 
 impl Request for ParentModule {
     type Params = lsp_types::TextDocumentPositionParams;
-    type Result = Vec<Location>;
-    const METHOD: &'static str = "rust-analyzer/parentModule";
+    type Result = Option<lsp_types::GotoDefinitionResponse>;
+    const METHOD: &'static str = "experimental/parentModule";
 }
 
 pub enum JoinLines {}
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index 3ccc95c23e0..1f910ff82b9 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -344,11 +344,8 @@ pub fn handle_goto_definition(
         None => return Ok(None),
         Some(it) => it,
     };
-    let res = to_proto::goto_definition_response(
-        &world,
-        FileRange { file_id: position.file_id, range: nav_info.range },
-        nav_info.info,
-    )?;
+    let src = FileRange { file_id: position.file_id, range: nav_info.range };
+    let res = to_proto::goto_definition_response(&world, Some(src), nav_info.info)?;
     Ok(Some(res))
 }
 
@@ -362,11 +359,8 @@ pub fn handle_goto_implementation(
         None => return Ok(None),
         Some(it) => it,
     };
-    let res = to_proto::goto_definition_response(
-        &world,
-        FileRange { file_id: position.file_id, range: nav_info.range },
-        nav_info.info,
-    )?;
+    let src = FileRange { file_id: position.file_id, range: nav_info.range };
+    let res = to_proto::goto_definition_response(&world, Some(src), nav_info.info)?;
     Ok(Some(res))
 }
 
@@ -380,26 +374,20 @@ pub fn handle_goto_type_definition(
         None => return Ok(None),
         Some(it) => it,
     };
-    let res = to_proto::goto_definition_response(
-        &world,
-        FileRange { file_id: position.file_id, range: nav_info.range },
-        nav_info.info,
-    )?;
+    let src = FileRange { file_id: position.file_id, range: nav_info.range };
+    let res = to_proto::goto_definition_response(&world, Some(src), nav_info.info)?;
     Ok(Some(res))
 }
 
 pub fn handle_parent_module(
     world: WorldSnapshot,
     params: lsp_types::TextDocumentPositionParams,
-) -> Result<Vec<Location>> {
+) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
     let _p = profile("handle_parent_module");
     let position = from_proto::file_position(&world, params)?;
-    world
-        .analysis()
-        .parent_module(position)?
-        .into_iter()
-        .map(|it| to_proto::location(&world, it.file_range()))
-        .collect::<Result<Vec<_>>>()
+    let navs = world.analysis().parent_module(position)?;
+    let res = to_proto::goto_definition_response(&world, None, navs)?;
+    Ok(Some(res))
 }
 
 pub fn handle_runnables(
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 39d58f1e01b..bb7594dbf30 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -403,13 +403,20 @@ pub(crate) fn location(world: &WorldSnapshot, frange: FileRange) -> Result<lsp_t
 
 pub(crate) fn location_link(
     world: &WorldSnapshot,
-    src: FileRange,
+    src: Option<FileRange>,
     target: NavigationTarget,
 ) -> Result<lsp_types::LocationLink> {
-    let src_location = location(world, src)?;
+    let origin_selection_range = match src {
+        Some(src) => {
+            let line_index = world.analysis().file_line_index(src.file_id)?;
+            let range = range(&line_index, src.range);
+            Some(range)
+        }
+        None => None,
+    };
     let (target_uri, target_range, target_selection_range) = location_info(world, target)?;
     let res = lsp_types::LocationLink {
-        origin_selection_range: Some(src_location.range),
+        origin_selection_range,
         target_uri,
         target_range,
         target_selection_range,
@@ -432,7 +439,7 @@ fn location_info(
 
 pub(crate) fn goto_definition_response(
     world: &WorldSnapshot,
-    src: FileRange,
+    src: Option<FileRange>,
     targets: Vec<NavigationTarget>,
 ) -> Result<lsp_types::GotoDefinitionResponse> {
     if world.config.client_caps.location_link {
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 48147b17393..209f470eba5 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -87,6 +87,40 @@ Invoking code action at this position will yield two code actions for importing
 * Is a fixed two-level structure enough?
 * Should we devise a general way to encode custom interaction protocols for GUI refactorings?
 
+## Parent Module
+
+**Issue:** https://github.com/microsoft/language-server-protocol/issues/1002
+
+**Server Capability:** `{ "parentModule": boolean }`
+
+This request is send from client to server to handle "Goto Parent Module" editor action.
+
+**Method:** `experimental/parentModule`
+
+**Request:** `TextDocumentPositionParams`
+
+**Response:** `Location | Location[] | LocationLink[] | null`
+
+
+### Example
+
+```rust
+// src/main.rs
+mod foo;
+// src/foo.rs
+
+/* cursor here*/
+```
+
+`experimental/parentModule` returns a single `Link` to the `mod foo;` declaration.
+
+### Unresolved Question
+
+* An alternative would be to use a more general "gotoSuper" request, which would work for super methods, super classes and super modules.
+  This is the approach IntelliJ Rust is takeing.
+  However, experience shows that super module (which generally has a feeling of navigation between files) should be separate.
+  If you want super module, but the cursor happens to be inside an overriden function, the behavior with single "gotoSuper" request is surprising.
+
 ## Join Lines
 
 **Issue:** https://github.com/microsoft/language-server-protocol/issues/992
@@ -108,11 +142,7 @@ interface JoinLinesParams {
 }
 ```
 
-**Response:**
-
-```typescript
-TextEdit[]
-```
+**Response:** `TextEdit[]`
 
 ### Example
 
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 49e3845d5b1..86302db37c1 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -138,10 +138,10 @@ export function parentModule(ctx: Ctx): Cmd {
             ),
         });
         const loc = response[0];
-        if (loc == null) return;
+        if (!loc) return;
 
-        const uri = client.protocol2CodeConverter.asUri(loc.uri);
-        const range = client.protocol2CodeConverter.asRange(loc.range);
+        const uri = client.protocol2CodeConverter.asUri(loc.targetUri);
+        const range = client.protocol2CodeConverter.asRange(loc.targetRange);
 
         const doc = await vscode.workspace.openTextDocument(uri);
         const e = await vscode.window.showTextDocument(doc);
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index 2a06632619b..4da12eb3092 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -31,7 +31,7 @@ export interface MatchingBraceParams {
 }
 export const matchingBrace = new lc.RequestType<MatchingBraceParams, lc.Position[], void>("experimental/matchingBrace");
 
-export const parentModule = new lc.RequestType<lc.TextDocumentPositionParams, lc.Location[], void>("rust-analyzer/parentModule");
+export const parentModule = new lc.RequestType<lc.TextDocumentPositionParams, lc.LocationLink[], void>("experimental/parentModule");
 
 export interface JoinLinesParams {
     textDocument: lc.TextDocumentIdentifier;