mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-08-19 18:10:25 +00:00
fetching dependencies from the server
This commit is contained in:
parent
1201b156d8
commit
09e0a00d36
9 changed files with 155 additions and 156 deletions
59
crates/ide/src/fetch_crates.rs
Normal file
59
crates/ide/src/fetch_crates.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use ide_db::{
|
||||
base_db::{CrateOrigin, SourceDatabase, SourceDatabaseExt},
|
||||
RootDatabase,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CrateInfo {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
pub(crate) fn fetch_crates(db: &RootDatabase) -> Vec<CrateInfo> {
|
||||
let crate_graph = db.crate_graph();
|
||||
crate_graph
|
||||
.iter()
|
||||
.map(|crate_id| &crate_graph[crate_id])
|
||||
.filter(|&data| !matches!(data.origin, CrateOrigin::Local { .. }))
|
||||
.map(|data| {
|
||||
let crate_name = crate_name(data);
|
||||
let version = data.version.clone().unwrap_or_else(|| "".to_owned());
|
||||
let crate_path = crate_path(db, data, &crate_name);
|
||||
|
||||
CrateInfo { name: crate_name, version, path: crate_path }
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn crate_name(data: &ide_db::base_db::CrateData) -> String {
|
||||
data.display_name
|
||||
.clone()
|
||||
.map(|it| it.canonical_name().to_owned())
|
||||
.unwrap_or("unknown".to_string())
|
||||
}
|
||||
|
||||
fn crate_path(db: &RootDatabase, data: &ide_db::base_db::CrateData, crate_name: &str) -> String {
|
||||
let source_root_id = db.file_source_root(data.root_file_id);
|
||||
let source_root = db.source_root(source_root_id);
|
||||
let source_root_path = source_root.path_for_file(&data.root_file_id);
|
||||
match source_root_path.cloned() {
|
||||
Some(mut root_path) => {
|
||||
let mut crate_path = "".to_string();
|
||||
while let Some(vfs_path) = root_path.parent() {
|
||||
match vfs_path.name_and_extension() {
|
||||
Some((name, _)) => {
|
||||
if name.starts_with(crate_name) {
|
||||
crate_path = vfs_path.to_string();
|
||||
break;
|
||||
}
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
root_path = vfs_path;
|
||||
}
|
||||
crate_path
|
||||
}
|
||||
None => "".to_owned(),
|
||||
}
|
||||
}
|
|
@ -59,10 +59,12 @@ mod view_mir;
|
|||
mod interpret_function;
|
||||
mod view_item_tree;
|
||||
mod shuffle_crate_graph;
|
||||
mod fetch_crates;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use cfg::CfgOptions;
|
||||
use fetch_crates::CrateInfo;
|
||||
use ide_db::{
|
||||
base_db::{
|
||||
salsa::{self, ParallelDatabase},
|
||||
|
@ -331,6 +333,10 @@ impl Analysis {
|
|||
self.with_db(|db| view_crate_graph::view_crate_graph(db, full))
|
||||
}
|
||||
|
||||
pub fn fetch_crates(&self) -> Cancellable<Vec<CrateInfo>> {
|
||||
self.with_db(|db| fetch_crates::fetch_crates(db))
|
||||
}
|
||||
|
||||
pub fn expand_macro(&self, position: FilePosition) -> Cancellable<Option<ExpandedMacro>> {
|
||||
self.with_db(|db| expand_macro::expand_macro(db, position))
|
||||
}
|
||||
|
|
|
@ -6,7 +6,13 @@ use ide::AssistResolveStrategy;
|
|||
use lsp_types::{Diagnostic, DiagnosticTag, NumberOrString};
|
||||
use vfs::FileId;
|
||||
|
||||
use crate::{global_state::GlobalStateSnapshot, to_proto, Result};
|
||||
use crate::{
|
||||
global_state::GlobalStateSnapshot, to_proto, Result,
|
||||
lsp_ext::{
|
||||
CrateInfoResult, FetchDependencyGraphResult, FetchDependencyGraphParams,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
pub(crate) mod request;
|
||||
pub(crate) mod notification;
|
||||
|
@ -31,7 +37,7 @@ pub(crate) fn publish_diagnostics(
|
|||
"https://rust-analyzer.github.io/manual.html#{}",
|
||||
d.code.as_str()
|
||||
))
|
||||
.unwrap(),
|
||||
.unwrap(),
|
||||
}),
|
||||
source: Some("rust-analyzer".to_string()),
|
||||
message: d.message,
|
||||
|
@ -42,3 +48,16 @@ pub(crate) fn publish_diagnostics(
|
|||
.collect();
|
||||
Ok(diagnostics)
|
||||
}
|
||||
|
||||
pub(crate) fn fetch_dependency_graph(
|
||||
state: GlobalStateSnapshot,
|
||||
_params: FetchDependencyGraphParams,
|
||||
) -> Result<FetchDependencyGraphResult> {
|
||||
let crates = state.analysis.fetch_crates()?;
|
||||
Ok(FetchDependencyGraphResult {
|
||||
crates: crates
|
||||
.into_iter()
|
||||
.map(|it| CrateInfoResult { name: it.name, version: it.version, path: it.path })
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -27,6 +27,13 @@ pub struct AnalyzerStatusParams {
|
|||
pub text_document: Option<TextDocumentIdentifier>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CrateInfoResult {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub path: String,
|
||||
}
|
||||
pub enum FetchDependencyGraph {}
|
||||
|
||||
impl Request for FetchDependencyGraph {
|
||||
|
@ -38,9 +45,12 @@ impl Request for FetchDependencyGraph {
|
|||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FetchDependencyGraphParams {}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FetchDependencyGraphResult {}
|
||||
pub struct FetchDependencyGraphResult {
|
||||
pub crates: Vec<CrateInfoResult>,
|
||||
}
|
||||
|
||||
pub enum MemoryUsage {}
|
||||
|
||||
|
@ -374,6 +384,7 @@ impl Request for CodeActionRequest {
|
|||
}
|
||||
|
||||
pub enum CodeActionResolveRequest {}
|
||||
|
||||
impl Request for CodeActionResolveRequest {
|
||||
type Params = CodeAction;
|
||||
type Result = CodeAction;
|
||||
|
|
|
@ -655,12 +655,12 @@ impl GlobalState {
|
|||
.on_sync_mut::<lsp_ext::ReloadWorkspace>(handlers::handle_workspace_reload)
|
||||
.on_sync_mut::<lsp_ext::RebuildProcMacros>(handlers::handle_proc_macros_rebuild)
|
||||
.on_sync_mut::<lsp_ext::MemoryUsage>(handlers::handle_memory_usage)
|
||||
.on_sync_mut::<lsp_ext::FetchDependencyGraph>(handlers::fetch_dependency_graph)
|
||||
.on_sync_mut::<lsp_ext::ShuffleCrateGraph>(handlers::handle_shuffle_crate_graph)
|
||||
.on_sync::<lsp_ext::JoinLines>(handlers::handle_join_lines)
|
||||
.on_sync::<lsp_ext::OnEnter>(handlers::handle_on_enter)
|
||||
.on_sync::<lsp_types::request::SelectionRangeRequest>(handlers::handle_selection_range)
|
||||
.on_sync::<lsp_ext::MatchingBrace>(handlers::handle_matching_brace)
|
||||
.on::<lsp_ext::FetchDependencyGraph>(handlers::fetch_dependency_graph)
|
||||
.on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status)
|
||||
.on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree)
|
||||
.on::<lsp_ext::ViewHir>(handlers::handle_view_hir)
|
||||
|
|
|
@ -272,19 +272,19 @@ export function revealDependency(ctx: CtxInit): Cmd {
|
|||
const rootPath = vscode.workspace.workspaceFolders![0].uri.fsPath;
|
||||
const documentPath = editor.document.uri.fsPath;
|
||||
if (documentPath.startsWith(rootPath)) return;
|
||||
const dep = ctx.dependencies.getDependency(documentPath);
|
||||
const dep = ctx.dependencies?.getDependency(documentPath);
|
||||
if (dep) {
|
||||
await ctx.treeView.reveal(dep, { select: true, expand: true });
|
||||
await ctx.treeView?.reveal(dep, { select: true, expand: true });
|
||||
} else {
|
||||
let documentPath = editor.document.uri.fsPath;
|
||||
const parentChain: DependencyId[] = [{ id: documentPath.toLowerCase() }];
|
||||
do {
|
||||
documentPath = path.dirname(documentPath);
|
||||
parentChain.push({ id: documentPath.toLowerCase() });
|
||||
} while (!ctx.dependencies.contains(documentPath));
|
||||
} while (!ctx.dependencies?.contains(documentPath));
|
||||
parentChain.reverse();
|
||||
for (const idx in parentChain) {
|
||||
await ctx.treeView.reveal(parentChain[idx], { select: true, expand: true });
|
||||
await ctx.treeView?.reveal(parentChain[idx], { select: true, expand: true });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -91,19 +91,25 @@ export class Ctx {
|
|||
private commandFactories: Record<string, CommandFactory>;
|
||||
private commandDisposables: Disposable[];
|
||||
private unlinkedFiles: vscode.Uri[];
|
||||
readonly dependencies: RustDependenciesProvider;
|
||||
readonly treeView: vscode.TreeView<Dependency | DependencyFile | DependencyId>;
|
||||
private _dependencies: RustDependenciesProvider | undefined;
|
||||
private _treeView: vscode.TreeView<Dependency | DependencyFile | DependencyId> | undefined;
|
||||
|
||||
get client() {
|
||||
return this._client;
|
||||
}
|
||||
|
||||
get treeView() {
|
||||
return this._treeView;
|
||||
}
|
||||
|
||||
get dependencies() {
|
||||
return this._dependencies;
|
||||
}
|
||||
|
||||
constructor(
|
||||
readonly extCtx: vscode.ExtensionContext,
|
||||
commandFactories: Record<string, CommandFactory>,
|
||||
workspace: Workspace,
|
||||
dependencies: RustDependenciesProvider,
|
||||
treeView: vscode.TreeView<Dependency | DependencyFile | DependencyId>
|
||||
) {
|
||||
extCtx.subscriptions.push(this);
|
||||
this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
|
||||
|
@ -112,9 +118,6 @@ export class Ctx {
|
|||
this.commandDisposables = [];
|
||||
this.commandFactories = commandFactories;
|
||||
this.unlinkedFiles = [];
|
||||
this.dependencies = dependencies;
|
||||
this.treeView = treeView;
|
||||
|
||||
this.state = new PersistentState(extCtx.globalState);
|
||||
this.config = new Config(extCtx);
|
||||
|
||||
|
@ -123,13 +126,6 @@ export class Ctx {
|
|||
this.setServerStatus({
|
||||
health: "stopped",
|
||||
});
|
||||
vscode.window.onDidChangeActiveTextEditor((e) => {
|
||||
if (e && isRustEditor(e)) {
|
||||
execRevealDependency(e).catch((reason) => {
|
||||
void vscode.window.showErrorMessage(`Dependency error: ${reason}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
@ -267,6 +263,28 @@ export class Ctx {
|
|||
}
|
||||
await client.start();
|
||||
this.updateCommands();
|
||||
this.prepareTreeDependenciesView(client);
|
||||
}
|
||||
|
||||
private prepareTreeDependenciesView(client: lc.LanguageClient) {
|
||||
const ctxInit: CtxInit = {
|
||||
...this,
|
||||
client: client
|
||||
};
|
||||
const rootPath = vscode.workspace.workspaceFolders![0].uri.fsPath;
|
||||
this._dependencies = new RustDependenciesProvider(rootPath, ctxInit);
|
||||
this._treeView = vscode.window.createTreeView("rustDependencies", {
|
||||
treeDataProvider: this._dependencies,
|
||||
showCollapseAll: true,
|
||||
});
|
||||
|
||||
vscode.window.onDidChangeActiveTextEditor((e) => {
|
||||
if (e && isRustEditor(e)) {
|
||||
execRevealDependency(e).catch((reason) => {
|
||||
void vscode.window.showErrorMessage(`Dependency error: ${reason}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async restart() {
|
||||
|
@ -369,7 +387,7 @@ export class Ctx {
|
|||
statusBar.color = undefined;
|
||||
statusBar.backgroundColor = undefined;
|
||||
statusBar.command = "rust-analyzer.stopServer";
|
||||
this.dependencies.refresh();
|
||||
this.dependencies?.refresh();
|
||||
break;
|
||||
case "warning":
|
||||
if (status.message) {
|
||||
|
|
|
@ -1,23 +1,16 @@
|
|||
import * as vscode from "vscode";
|
||||
import * as fspath from "path";
|
||||
import * as fs from "fs";
|
||||
import * as os from "os";
|
||||
import { activeToolchain, Cargo, Crate, getRustcVersion } from "./toolchain";
|
||||
import { Ctx } from "./ctx";
|
||||
import { setFlagsFromString } from "v8";
|
||||
import { CtxInit } from "./ctx";
|
||||
import * as ra from "./lsp_ext";
|
||||
|
||||
const debugOutput = vscode.window.createOutputChannel("Debug");
|
||||
import { FetchDependencyGraphResult } from "./lsp_ext";
|
||||
|
||||
export class RustDependenciesProvider
|
||||
implements vscode.TreeDataProvider<Dependency | DependencyFile>
|
||||
{
|
||||
cargo: Cargo;
|
||||
implements vscode.TreeDataProvider<Dependency | DependencyFile> {
|
||||
dependenciesMap: { [id: string]: Dependency | DependencyFile };
|
||||
ctx: Ctx;
|
||||
ctx: CtxInit;
|
||||
|
||||
constructor(private readonly workspaceRoot: string, ctx: Ctx) {
|
||||
this.cargo = new Cargo(this.workspaceRoot || ".", debugOutput);
|
||||
constructor(private readonly workspaceRoot: string, ctx: CtxInit) {
|
||||
this.dependenciesMap = {};
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
@ -62,7 +55,6 @@ export class RustDependenciesProvider
|
|||
void vscode.window.showInformationMessage("No dependency in empty workspace");
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
if (element) {
|
||||
const files = fs.readdirSync(element.dependencyPath).map((fileName) => {
|
||||
const filePath = fspath.join(element.dependencyPath, fileName);
|
||||
|
@ -81,59 +73,26 @@ export class RustDependenciesProvider
|
|||
}
|
||||
|
||||
private async getRootDependencies(): Promise<Dependency[]> {
|
||||
const crates = await this.ctx.client.sendRequest(ra.fetchDependencyGraph, {});
|
||||
|
||||
const registryDir = fspath.join(os.homedir(), ".cargo", "registry", "src");
|
||||
const basePath = fspath.join(registryDir, fs.readdirSync(registryDir)[0]);
|
||||
const deps = await this.getDepsInCartoTree(basePath);
|
||||
const stdlib = await this.getStdLib();
|
||||
this.dependenciesMap[stdlib.dependencyPath.toLowerCase()] = stdlib;
|
||||
return [stdlib].concat(deps);
|
||||
}
|
||||
|
||||
private async getStdLib(): Promise<Dependency> {
|
||||
const toolchain = await activeToolchain();
|
||||
const rustVersion = await getRustcVersion(os.homedir());
|
||||
const stdlibPath = fspath.join(
|
||||
os.homedir(),
|
||||
".rustup",
|
||||
"toolchains",
|
||||
toolchain,
|
||||
"lib",
|
||||
"rustlib",
|
||||
"src",
|
||||
"rust",
|
||||
"library"
|
||||
);
|
||||
const stdlib = new Dependency(
|
||||
"stdlib",
|
||||
rustVersion,
|
||||
stdlibPath,
|
||||
vscode.TreeItemCollapsibleState.Collapsed
|
||||
);
|
||||
|
||||
return stdlib;
|
||||
}
|
||||
|
||||
private async getDepsInCartoTree(basePath: string): Promise<Dependency[]> {
|
||||
const crates: Crate[] = await this.cargo.crates();
|
||||
const toDep = (moduleName: string, version: string): Dependency => {
|
||||
const cratePath = fspath.join(basePath, `${moduleName}-${version}`);
|
||||
return new Dependency(
|
||||
moduleName,
|
||||
version,
|
||||
cratePath,
|
||||
vscode.TreeItemCollapsibleState.Collapsed
|
||||
);
|
||||
};
|
||||
const dependenciesResult: FetchDependencyGraphResult = await this.ctx.client.sendRequest(ra.fetchDependencyGraph, {});
|
||||
const crates = dependenciesResult.crates;
|
||||
|
||||
const deps = crates.map((crate) => {
|
||||
const dep = toDep(crate.name, crate.version);
|
||||
const dep = this.toDep(crate.name, crate.version, crate.path);
|
||||
this.dependenciesMap[dep.dependencyPath.toLowerCase()] = dep;
|
||||
return dep;
|
||||
});
|
||||
return deps;
|
||||
}
|
||||
|
||||
private toDep(moduleName: string, version: string, path: string): Dependency {
|
||||
// const cratePath = fspath.join(basePath, `${moduleName}-${version}`);
|
||||
return new Dependency(
|
||||
moduleName,
|
||||
version,
|
||||
path,
|
||||
vscode.TreeItemCollapsibleState.Collapsed
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class Dependency extends vscode.TreeItem {
|
||||
|
|
|
@ -5,14 +5,8 @@ import * as readline from "readline";
|
|||
import * as vscode from "vscode";
|
||||
import { execute, log, memoizeAsync } from "./util";
|
||||
|
||||
const TREE_LINE_PATTERN = new RegExp(/(.+)\sv(\d+\.\d+\.\d+)(?:\s\((.+)\))?/);
|
||||
const TOOLCHAIN_PATTERN = new RegExp(/(.*)\s\(.*\)/);
|
||||
|
||||
export interface Crate {
|
||||
name: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
interface CompilationArtifact {
|
||||
fileName: string;
|
||||
name: string;
|
||||
|
@ -30,7 +24,7 @@ export class Cargo {
|
|||
readonly rootFolder: string,
|
||||
readonly output: vscode.OutputChannel,
|
||||
readonly env: Record<string, string>
|
||||
) {}
|
||||
) { }
|
||||
|
||||
// Made public for testing purposes
|
||||
static artifactSpec(args: readonly string[]): ArtifactSpec {
|
||||
|
@ -104,40 +98,6 @@ export class Cargo {
|
|||
return artifacts[0].fileName;
|
||||
}
|
||||
|
||||
async crates(): Promise<Crate[]> {
|
||||
const pathToCargo = await cargoPath();
|
||||
return await new Promise((resolve, reject) => {
|
||||
const crates: Crate[] = [];
|
||||
|
||||
const cargo = cp.spawn(pathToCargo, ["tree", "--prefix", "none"], {
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
cwd: this.rootFolder,
|
||||
});
|
||||
const rl = readline.createInterface({ input: cargo.stdout });
|
||||
rl.on("line", (line) => {
|
||||
const match = line.match(TREE_LINE_PATTERN);
|
||||
if (match) {
|
||||
const name = match[1];
|
||||
const version = match[2];
|
||||
const extraInfo = match[3];
|
||||
// ignore duplicates '(*)' and path dependencies
|
||||
if (this.shouldIgnore(extraInfo)) {
|
||||
return;
|
||||
}
|
||||
crates.push({ name, version });
|
||||
}
|
||||
});
|
||||
cargo.on("exit", (exitCode, _) => {
|
||||
if (exitCode === 0) resolve(crates);
|
||||
else reject(new Error(`exit code: ${exitCode}.`));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private shouldIgnore(extraInfo: string): boolean {
|
||||
return extraInfo !== undefined && (extraInfo === "*" || path.isAbsolute(extraInfo));
|
||||
}
|
||||
|
||||
private async runCargo(
|
||||
cargoArgs: string[],
|
||||
onStdoutJson: (obj: any) => void,
|
||||
|
@ -169,29 +129,6 @@ export class Cargo {
|
|||
}
|
||||
}
|
||||
|
||||
export async function activeToolchain(): Promise<string> {
|
||||
const pathToRustup = await rustupPath();
|
||||
return await new Promise((resolve, reject) => {
|
||||
const execution = cp.spawn(pathToRustup, ["show", "active-toolchain"], {
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
cwd: os.homedir(),
|
||||
});
|
||||
const rl = readline.createInterface({ input: execution.stdout });
|
||||
|
||||
let currToolchain: string | undefined = undefined;
|
||||
rl.on("line", (line) => {
|
||||
const match = line.match(TOOLCHAIN_PATTERN);
|
||||
if (match) {
|
||||
currToolchain = match[1];
|
||||
}
|
||||
});
|
||||
execution.on("exit", (exitCode, _) => {
|
||||
if (exitCode === 0 && currToolchain) resolve(currToolchain);
|
||||
else reject(new Error(`exit code: ${exitCode}.`));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** Mirrors `project_model::sysroot::discover_sysroot_dir()` implementation*/
|
||||
export async function getSysroot(dir: string): Promise<string> {
|
||||
const rustcPath = await getPathForExecutable("rustc");
|
||||
|
@ -210,16 +147,6 @@ export async function getRustcId(dir: string): Promise<string> {
|
|||
return rx.exec(data)![1];
|
||||
}
|
||||
|
||||
export async function getRustcVersion(dir: string): Promise<string> {
|
||||
const rustcPath = await getPathForExecutable("rustc");
|
||||
|
||||
// do not memoize the result because the toolchain may change between runs
|
||||
const data = await execute(`${rustcPath} -V`, { cwd: dir });
|
||||
const rx = /(\d\.\d+\.\d+)/;
|
||||
|
||||
return rx.exec(data)![1];
|
||||
}
|
||||
|
||||
/** Mirrors `toolchain::cargo()` implementation */
|
||||
export function cargoPath(): Promise<string> {
|
||||
return getPathForExecutable("cargo");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue