From 39044fe39c0557747ee12c5dc6d163dfdf65d88c Mon Sep 17 00:00:00 2001
From: rainy-me <github@rainy.me>
Date: Sun, 3 Oct 2021 11:58:10 +0900
Subject: [PATCH 1/3] Allow locate parent module action in cargo toml

---
 crates/rust-analyzer/src/handlers.rs | 80 ++++++++++++++++++++++++++--
 editors/code/src/commands.ts         |  6 +--
 editors/code/src/util.ts             |  5 ++
 3 files changed, 83 insertions(+), 8 deletions(-)

diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index e62bb9499fa..a7ca5a7a346 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -20,15 +20,17 @@ use lsp_types::{
     CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem,
     CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
     CodeLens, CompletionItem, Diagnostic, DiagnosticTag, DocumentFormattingParams, FoldingRange,
-    FoldingRangeParams, HoverContents, Location, NumberOrString, Position, PrepareRenameResponse,
-    Range, RenameParams, SemanticTokensDeltaParams, SemanticTokensFullDeltaResult,
-    SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult,
-    SemanticTokensResult, SymbolInformation, SymbolTag, TextDocumentIdentifier, Url, WorkspaceEdit,
+    FoldingRangeParams, HoverContents, Location, LocationLink, NumberOrString, Position,
+    PrepareRenameResponse, Range, RenameParams, SemanticTokensDeltaParams,
+    SemanticTokensFullDeltaResult, SemanticTokensParams, SemanticTokensRangeParams,
+    SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, SymbolTag,
+    TextDocumentIdentifier, Url, WorkspaceEdit,
 };
-use project_model::TargetKind;
+use project_model::{ProjectWorkspace, TargetKind};
 use serde_json::json;
 use stdx::{format_to, never};
 use syntax::{algo, ast, AstNode, TextRange, TextSize, T};
+use vfs::AbsPath;
 
 use crate::{
     cargo_target_spec::CargoTargetSpec,
@@ -603,6 +605,74 @@ pub(crate) fn handle_parent_module(
     params: lsp_types::TextDocumentPositionParams,
 ) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
     let _p = profile::span("handle_parent_module");
+    if let Ok(file_path) = &params.text_document.uri.to_file_path() {
+        if file_path.file_name().unwrap_or_default() == "Cargo.toml" {
+            // search parent workspace and collect a list of `LocationLink`path,
+            // since cargo.toml doesn't have file_id
+            let links: Vec<LocationLink> = snap
+                .workspaces
+                .iter()
+                .filter_map(|ws| match ws {
+                    ProjectWorkspace::Cargo { cargo, .. } => cargo
+                        .packages()
+                        .find(|&pkg| {
+                            cargo[pkg]
+                                .targets
+                                .iter()
+                                .find_map(|&it| {
+                                    let pkg_parent_path = cargo[it].root.parent()?;
+                                    let file_parent_path = AbsPath::assert(file_path.parent()?);
+                                    if pkg_parent_path == file_parent_path {
+                                        Some(())
+                                    } else {
+                                        None
+                                    }
+                                })
+                                .is_some()
+                        })
+                        .and_then(|_| Some(cargo)),
+                    _ => None,
+                })
+                .map(|ws| {
+                    let target_cargo_toml_path = ws.workspace_root().join("Cargo.toml");
+                    let target_cargo_toml_url =
+                        to_proto::url_from_abs_path(&target_cargo_toml_path);
+                    LocationLink {
+                        origin_selection_range: None,
+                        target_uri: target_cargo_toml_url,
+                        target_range: Range::default(),
+                        target_selection_range: Range::default(),
+                    }
+                })
+                .collect::<_>();
+            return Ok(Some(links.into()));
+        }
+
+        // check if invoked at the crate root
+        let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
+        let crate_id = match snap.analysis.crate_for(file_id)?.first() {
+            Some(&crate_id) => crate_id,
+            None => return Ok(None),
+        };
+        let cargo_spec = match CargoTargetSpec::for_file(&snap, file_id)? {
+            Some(it) => it,
+            None => return Ok(None),
+        };
+
+        if snap.analysis.crate_root(crate_id)? == file_id {
+            let cargo_toml_url = to_proto::url_from_abs_path(&cargo_spec.cargo_toml);
+            let res = vec![LocationLink {
+                origin_selection_range: None,
+                target_uri: cargo_toml_url,
+                target_range: Range::default(),
+                target_selection_range: Range::default(),
+            }]
+            .into();
+            return Ok(Some(res));
+        }
+    }
+
+    // locate parent module by semantics
     let position = from_proto::file_position(&snap, params)?;
     let navs = snap.analysis.parent_module(position)?;
     let res = to_proto::goto_definition_response(&snap, None, navs)?;
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 0e08b60a93c..623e33c7efb 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -8,7 +8,7 @@ import { applySnippetWorkspaceEdit, applySnippetTextEdits } from './snippets';
 import { spawnSync } from 'child_process';
 import { RunnableQuickPick, selectRunnable, createTask, createArgs } from './run';
 import { AstInspector } from './ast_inspector';
-import { isRustDocument, sleep, isRustEditor } from './util';
+import { isRustDocument, isCargoTomlDocument, sleep, isRustEditor } from './util';
 import { startDebugSession, makeDebugConfig } from './debug';
 import { LanguageClient } from 'vscode-languageclient/node';
 
@@ -185,10 +185,10 @@ export function onEnter(ctx: Ctx): Cmd {
 
 export function parentModule(ctx: Ctx): Cmd {
     return async () => {
-        const editor = ctx.activeRustEditor;
+        const editor = vscode.window.activeTextEditor;
         const client = ctx.client;
         if (!editor || !client) return;
-
+        if (!(isRustDocument(editor.document) || isCargoTomlDocument(editor.document))) return;
         const locations = await client.sendRequest(ra.parentModule, {
             textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
             position: client.code2ProtocolConverter.asPosition(
diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts
index aa57081a5f3..057a3d2e194 100644
--- a/editors/code/src/util.ts
+++ b/editors/code/src/util.ts
@@ -104,6 +104,11 @@ export function isRustDocument(document: vscode.TextDocument): document is RustD
     return document.languageId === 'rust' && document.uri.scheme === 'file';
 }
 
+export function isCargoTomlDocument(document: vscode.TextDocument): document is RustDocument {
+    // ideally `document.languageId` should be 'toml' but user maybe not have toml extension installed
+    return document.uri.scheme === 'file' && document.fileName.endsWith('Cargo.toml');
+}
+
 export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor {
     return isRustDocument(editor.document);
 }

From aaa0771719c0ec8a9104896ce9fdc99e95c1805e Mon Sep 17 00:00:00 2001
From: rainy-me <github@yue.coffee>
Date: Thu, 7 Oct 2021 23:44:25 +0900
Subject: [PATCH 2/3] Fix: compare pkg via manifest

---
 crates/rust-analyzer/src/handlers.rs | 17 +----------------
 1 file changed, 1 insertion(+), 16 deletions(-)

diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index a7ca5a7a346..306de042517 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -30,7 +30,6 @@ use project_model::{ProjectWorkspace, TargetKind};
 use serde_json::json;
 use stdx::{format_to, never};
 use syntax::{algo, ast, AstNode, TextRange, TextSize, T};
-use vfs::AbsPath;
 
 use crate::{
     cargo_target_spec::CargoTargetSpec,
@@ -615,21 +614,7 @@ pub(crate) fn handle_parent_module(
                 .filter_map(|ws| match ws {
                     ProjectWorkspace::Cargo { cargo, .. } => cargo
                         .packages()
-                        .find(|&pkg| {
-                            cargo[pkg]
-                                .targets
-                                .iter()
-                                .find_map(|&it| {
-                                    let pkg_parent_path = cargo[it].root.parent()?;
-                                    let file_parent_path = AbsPath::assert(file_path.parent()?);
-                                    if pkg_parent_path == file_parent_path {
-                                        Some(())
-                                    } else {
-                                        None
-                                    }
-                                })
-                                .is_some()
-                        })
+                        .find(|&pkg| cargo[pkg].manifest.as_ref() == file_path)
                         .and_then(|_| Some(cargo)),
                     _ => None,
                 })

From 59c755227d3f6903095cf03bce5e76b7747138ec Mon Sep 17 00:00:00 2001
From: rainy-me <github@yue.coffee>
Date: Thu, 14 Oct 2021 07:16:42 +0900
Subject: [PATCH 3/3] Provide navigations to parent modules

---
 crates/project_model/src/cargo_workspace.rs | 35 ++++++++++++++++++-
 crates/rust-analyzer/src/handlers.rs        | 38 ++++++++++++---------
 editors/code/src/commands.ts                |  2 ++
 editors/code/src/lsp_ext.ts                 |  2 +-
 4 files changed, 58 insertions(+), 19 deletions(-)

diff --git a/crates/project_model/src/cargo_workspace.rs b/crates/project_model/src/cargo_workspace.rs
index 1417ac2fa1d..a55901a8f75 100644
--- a/crates/project_model/src/cargo_workspace.rs
+++ b/crates/project_model/src/cargo_workspace.rs
@@ -1,6 +1,6 @@
 //! See [`CargoWorkspace`].
 
-use std::convert::TryInto;
+use std::convert::{TryFrom, TryInto};
 use std::iter;
 use std::path::PathBuf;
 use std::{ops, process::Command};
@@ -400,6 +400,39 @@ impl CargoWorkspace {
         }
     }
 
+    pub fn parent_manifests(&self, manifest_path: &ManifestPath) -> Option<Vec<ManifestPath>> {
+        let mut found = false;
+        let parent_manifests = self
+            .packages()
+            .filter_map(|pkg| {
+                if !found && &self[pkg].manifest == manifest_path {
+                    found = true
+                }
+                self[pkg].dependencies.iter().find_map(|dep| {
+                    if &self[dep.pkg].manifest == manifest_path {
+                        return Some(self[pkg].manifest.clone());
+                    }
+                    None
+                })
+            })
+            .collect::<Vec<ManifestPath>>();
+
+        // some packages has this pkg as dep. return their manifests
+        if parent_manifests.len() > 0 {
+            return Some(parent_manifests);
+        }
+
+        // this pkg is inside this cargo workspace, fallback to workspace root
+        if found {
+            return Some(vec![
+                ManifestPath::try_from(self.workspace_root().join("Cargo.toml")).ok()?
+            ]);
+        }
+
+        // not in this workspace
+        None
+    }
+
     fn is_unique(&self, name: &str) -> bool {
         self.packages.iter().filter(|(_, v)| v.name == name).count() == 1
     }
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 306de042517..0105496ba2a 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -3,6 +3,7 @@
 //! `ide` crate.
 
 use std::{
+    convert::TryFrom,
     io::Write as _,
     process::{self, Stdio},
 };
@@ -26,10 +27,11 @@ use lsp_types::{
     SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, SymbolTag,
     TextDocumentIdentifier, Url, WorkspaceEdit,
 };
-use project_model::{ProjectWorkspace, TargetKind};
+use project_model::{ManifestPath, ProjectWorkspace, TargetKind};
 use serde_json::json;
 use stdx::{format_to, never};
 use syntax::{algo, ast, AstNode, TextRange, TextSize, T};
+use vfs::AbsPathBuf;
 
 use crate::{
     cargo_target_spec::CargoTargetSpec,
@@ -606,28 +608,30 @@ pub(crate) fn handle_parent_module(
     let _p = profile::span("handle_parent_module");
     if let Ok(file_path) = &params.text_document.uri.to_file_path() {
         if file_path.file_name().unwrap_or_default() == "Cargo.toml" {
-            // search parent workspace and collect a list of `LocationLink`path,
-            // since cargo.toml doesn't have file_id
+            // search workspaces for parent packages or fallback to workspace root
+            let abs_path_buf = match AbsPathBuf::try_from(file_path.to_path_buf()).ok() {
+                Some(abs_path_buf) => abs_path_buf,
+                None => return Ok(None),
+            };
+
+            let manifest_path = match ManifestPath::try_from(abs_path_buf).ok() {
+                Some(manifest_path) => manifest_path,
+                None => return Ok(None),
+            };
+
             let links: Vec<LocationLink> = snap
                 .workspaces
                 .iter()
                 .filter_map(|ws| match ws {
-                    ProjectWorkspace::Cargo { cargo, .. } => cargo
-                        .packages()
-                        .find(|&pkg| cargo[pkg].manifest.as_ref() == file_path)
-                        .and_then(|_| Some(cargo)),
+                    ProjectWorkspace::Cargo { cargo, .. } => cargo.parent_manifests(&manifest_path),
                     _ => None,
                 })
-                .map(|ws| {
-                    let target_cargo_toml_path = ws.workspace_root().join("Cargo.toml");
-                    let target_cargo_toml_url =
-                        to_proto::url_from_abs_path(&target_cargo_toml_path);
-                    LocationLink {
-                        origin_selection_range: None,
-                        target_uri: target_cargo_toml_url,
-                        target_range: Range::default(),
-                        target_selection_range: Range::default(),
-                    }
+                .flatten()
+                .map(|parent_manifest_path| LocationLink {
+                    origin_selection_range: None,
+                    target_uri: to_proto::url_from_abs_path(&parent_manifest_path),
+                    target_range: Range::default(),
+                    target_selection_range: Range::default(),
                 })
                 .collect::<_>();
             return Ok(Some(links.into()));
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 623e33c7efb..c9385361f88 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -189,12 +189,14 @@ export function parentModule(ctx: Ctx): Cmd {
         const client = ctx.client;
         if (!editor || !client) return;
         if (!(isRustDocument(editor.document) || isCargoTomlDocument(editor.document))) return;
+
         const locations = await client.sendRequest(ra.parentModule, {
             textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
             position: client.code2ProtocolConverter.asPosition(
                 editor.selection.active,
             ),
         });
+        if (!locations) return;
 
         if (locations.length === 1) {
             const loc = locations[0];
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index ac632a01567..90796e611e6 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -62,7 +62,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.LocationLink[], void>("experimental/parentModule");
+export const parentModule = new lc.RequestType<lc.TextDocumentPositionParams, lc.LocationLink[] | null, void>("experimental/parentModule");
 
 export interface JoinLinesParams {
     textDocument: lc.TextDocumentIdentifier;