From 39044fe39c0557747ee12c5dc6d163dfdf65d88c Mon Sep 17 00:00:00 2001 From: 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 e62bb9499f..a7ca5a7a34 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> { let _p = profile::span("handle_parent_module"); + if let Ok(file_path) = ¶ms.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 = 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, ¶ms.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 0e08b60a93..623e33c7ef 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 aa57081a5f..057a3d2e19 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 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 a7ca5a7a34..306de04251 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 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 1417ac2fa1..a55901a8f7 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> { + 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::>(); + + // 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 306de04251..0105496ba2 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) = ¶ms.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 = 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 623e33c7ef..c9385361f8 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 ac632a0156..90796e611e 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("experimental/matchingBrace"); -export const parentModule = new lc.RequestType("experimental/parentModule"); +export const parentModule = new lc.RequestType("experimental/parentModule"); export interface JoinLinesParams { textDocument: lc.TextDocumentIdentifier;