mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 04:53:34 +00:00
Merge #10434
10434: Allow `Locate parent module` command in Cargo.toml r=Veykril a=rainy-me close #10355 Co-authored-by: rainy-me <github@rainy.me> Co-authored-by: rainy-me <github@yue.coffee>
This commit is contained in:
commit
f87debcf87
5 changed files with 108 additions and 9 deletions
|
@ -1,6 +1,6 @@
|
||||||
//! See [`CargoWorkspace`].
|
//! See [`CargoWorkspace`].
|
||||||
|
|
||||||
use std::convert::TryInto;
|
use std::convert::{TryFrom, TryInto};
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{ops, process::Command};
|
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 {
|
fn is_unique(&self, name: &str) -> bool {
|
||||||
self.packages.iter().filter(|(_, v)| v.name == name).count() == 1
|
self.packages.iter().filter(|(_, v)| v.name == name).count() == 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
//! `ide` crate.
|
//! `ide` crate.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
convert::TryFrom,
|
||||||
io::Write as _,
|
io::Write as _,
|
||||||
process::{self, Stdio},
|
process::{self, Stdio},
|
||||||
};
|
};
|
||||||
|
@ -20,15 +21,17 @@ use lsp_types::{
|
||||||
CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem,
|
CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem,
|
||||||
CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
|
CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
|
||||||
CodeLens, CompletionItem, Diagnostic, DiagnosticTag, DocumentFormattingParams, FoldingRange,
|
CodeLens, CompletionItem, Diagnostic, DiagnosticTag, DocumentFormattingParams, FoldingRange,
|
||||||
FoldingRangeParams, HoverContents, Location, NumberOrString, Position, PrepareRenameResponse,
|
FoldingRangeParams, HoverContents, Location, LocationLink, NumberOrString, Position,
|
||||||
Range, RenameParams, SemanticTokensDeltaParams, SemanticTokensFullDeltaResult,
|
PrepareRenameResponse, Range, RenameParams, SemanticTokensDeltaParams,
|
||||||
SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult,
|
SemanticTokensFullDeltaResult, SemanticTokensParams, SemanticTokensRangeParams,
|
||||||
SemanticTokensResult, SymbolInformation, SymbolTag, TextDocumentIdentifier, Url, WorkspaceEdit,
|
SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, SymbolTag,
|
||||||
|
TextDocumentIdentifier, Url, WorkspaceEdit,
|
||||||
};
|
};
|
||||||
use project_model::TargetKind;
|
use project_model::{ManifestPath, ProjectWorkspace, TargetKind};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use stdx::{format_to, never};
|
use stdx::{format_to, never};
|
||||||
use syntax::{algo, ast, AstNode, TextRange, TextSize, T};
|
use syntax::{algo, ast, AstNode, TextRange, TextSize, T};
|
||||||
|
use vfs::AbsPathBuf;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cargo_target_spec::CargoTargetSpec,
|
cargo_target_spec::CargoTargetSpec,
|
||||||
|
@ -601,6 +604,62 @@ pub(crate) fn handle_parent_module(
|
||||||
params: lsp_types::TextDocumentPositionParams,
|
params: lsp_types::TextDocumentPositionParams,
|
||||||
) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
|
) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
|
||||||
let _p = profile::span("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 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.parent_manifests(&manifest_path),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 position = from_proto::file_position(&snap, params)?;
|
||||||
let navs = snap.analysis.parent_module(position)?;
|
let navs = snap.analysis.parent_module(position)?;
|
||||||
let res = to_proto::goto_definition_response(&snap, None, navs)?;
|
let res = to_proto::goto_definition_response(&snap, None, navs)?;
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { applySnippetWorkspaceEdit, applySnippetTextEdits } from './snippets';
|
||||||
import { spawnSync } from 'child_process';
|
import { spawnSync } from 'child_process';
|
||||||
import { RunnableQuickPick, selectRunnable, createTask, createArgs } from './run';
|
import { RunnableQuickPick, selectRunnable, createTask, createArgs } from './run';
|
||||||
import { AstInspector } from './ast_inspector';
|
import { AstInspector } from './ast_inspector';
|
||||||
import { isRustDocument, sleep, isRustEditor } from './util';
|
import { isRustDocument, isCargoTomlDocument, sleep, isRustEditor } from './util';
|
||||||
import { startDebugSession, makeDebugConfig } from './debug';
|
import { startDebugSession, makeDebugConfig } from './debug';
|
||||||
import { LanguageClient } from 'vscode-languageclient/node';
|
import { LanguageClient } from 'vscode-languageclient/node';
|
||||||
|
|
||||||
|
@ -185,9 +185,10 @@ export function onEnter(ctx: Ctx): Cmd {
|
||||||
|
|
||||||
export function parentModule(ctx: Ctx): Cmd {
|
export function parentModule(ctx: Ctx): Cmd {
|
||||||
return async () => {
|
return async () => {
|
||||||
const editor = ctx.activeRustEditor;
|
const editor = vscode.window.activeTextEditor;
|
||||||
const client = ctx.client;
|
const client = ctx.client;
|
||||||
if (!editor || !client) return;
|
if (!editor || !client) return;
|
||||||
|
if (!(isRustDocument(editor.document) || isCargoTomlDocument(editor.document))) return;
|
||||||
|
|
||||||
const locations = await client.sendRequest(ra.parentModule, {
|
const locations = await client.sendRequest(ra.parentModule, {
|
||||||
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
|
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
|
||||||
|
@ -195,6 +196,7 @@ export function parentModule(ctx: Ctx): Cmd {
|
||||||
editor.selection.active,
|
editor.selection.active,
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
if (!locations) return;
|
||||||
|
|
||||||
if (locations.length === 1) {
|
if (locations.length === 1) {
|
||||||
const loc = locations[0];
|
const loc = locations[0];
|
||||||
|
|
|
@ -62,7 +62,7 @@ export interface MatchingBraceParams {
|
||||||
}
|
}
|
||||||
export const matchingBrace = new lc.RequestType<MatchingBraceParams, lc.Position[], void>("experimental/matchingBrace");
|
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 {
|
export interface JoinLinesParams {
|
||||||
textDocument: lc.TextDocumentIdentifier;
|
textDocument: lc.TextDocumentIdentifier;
|
||||||
|
|
|
@ -104,6 +104,11 @@ export function isRustDocument(document: vscode.TextDocument): document is RustD
|
||||||
return document.languageId === 'rust' && document.uri.scheme === 'file';
|
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 {
|
export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor {
|
||||||
return isRustDocument(editor.document);
|
return isRustDocument(editor.document);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue