From 09e0a00d3648eb4080d16f07c6dae73f7c73c431 Mon Sep 17 00:00:00 2001 From: Bruno Ortiz Date: Sun, 2 Apr 2023 21:58:20 -0300 Subject: [PATCH] fetching dependencies from the server --- crates/ide/src/fetch_crates.rs | 59 +++++++++++++++++ crates/ide/src/lib.rs | 6 ++ crates/rust-analyzer/src/handlers.rs | 23 ++++++- crates/rust-analyzer/src/lsp_ext.rs | 13 +++- crates/rust-analyzer/src/main_loop.rs | 2 +- editors/code/src/commands.ts | 8 +-- editors/code/src/ctx.ts | 48 +++++++++----- editors/code/src/dependencies_provider.ts | 77 ++++++----------------- editors/code/src/toolchain.ts | 75 +--------------------- 9 files changed, 155 insertions(+), 156 deletions(-) create mode 100644 crates/ide/src/fetch_crates.rs diff --git a/crates/ide/src/fetch_crates.rs b/crates/ide/src/fetch_crates.rs new file mode 100644 index 0000000000..c0bc4103c2 --- /dev/null +++ b/crates/ide/src/fetch_crates.rs @@ -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 { + 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(), + } +} diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index e3900fa0d6..96adb11dcd 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -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> { + self.with_db(|db| fetch_crates::fetch_crates(db)) + } + pub fn expand_macro(&self, position: FilePosition) -> Cancellable> { self.with_db(|db| expand_macro::expand_macro(db, position)) } diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index a00d0fba7c..2324490e53 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -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 { + 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(), + }) +} diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index 420118ad68..18511da468 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs @@ -27,6 +27,13 @@ pub struct AnalyzerStatusParams { pub text_document: Option, } +#[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, +} pub enum MemoryUsage {} @@ -374,6 +384,7 @@ impl Request for CodeActionRequest { } pub enum CodeActionResolveRequest {} + impl Request for CodeActionResolveRequest { type Params = CodeAction; type Result = CodeAction; diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 72fc1f1e25..7a81a18f4a 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -655,12 +655,12 @@ impl GlobalState { .on_sync_mut::(handlers::handle_workspace_reload) .on_sync_mut::(handlers::handle_proc_macros_rebuild) .on_sync_mut::(handlers::handle_memory_usage) - .on_sync_mut::(handlers::fetch_dependency_graph) .on_sync_mut::(handlers::handle_shuffle_crate_graph) .on_sync::(handlers::handle_join_lines) .on_sync::(handlers::handle_on_enter) .on_sync::(handlers::handle_selection_range) .on_sync::(handlers::handle_matching_brace) + .on::(handlers::fetch_dependency_graph) .on::(handlers::handle_analyzer_status) .on::(handlers::handle_syntax_tree) .on::(handlers::handle_view_hir) diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 70eeab897c..7fe32754c9 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -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 }); } } }; diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index feb39198c2..d62716c26d 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -91,19 +91,25 @@ export class Ctx { private commandFactories: Record; private commandDisposables: Disposable[]; private unlinkedFiles: vscode.Uri[]; - readonly dependencies: RustDependenciesProvider; - readonly treeView: vscode.TreeView; + private _dependencies: RustDependenciesProvider | undefined; + private _treeView: vscode.TreeView | undefined; get client() { return this._client; } + get treeView() { + return this._treeView; + } + + get dependencies() { + return this._dependencies; + } + constructor( readonly extCtx: vscode.ExtensionContext, commandFactories: Record, workspace: Workspace, - dependencies: RustDependenciesProvider, - treeView: vscode.TreeView ) { 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) { diff --git a/editors/code/src/dependencies_provider.ts b/editors/code/src/dependencies_provider.ts index 48d51523e8..195f41417d 100644 --- a/editors/code/src/dependencies_provider.ts +++ b/editors/code/src/dependencies_provider.ts @@ -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 -{ - cargo: Cargo; + implements vscode.TreeDataProvider { 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 { - 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 { - 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 { - 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 { diff --git a/editors/code/src/toolchain.ts b/editors/code/src/toolchain.ts index c068cfc311..771f6bcba4 100644 --- a/editors/code/src/toolchain.ts +++ b/editors/code/src/toolchain.ts @@ -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 - ) {} + ) { } // 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 { - 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 { - 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 { const rustcPath = await getPathForExecutable("rustc"); @@ -210,16 +147,6 @@ export async function getRustcId(dir: string): Promise { return rx.exec(data)![1]; } -export async function getRustcVersion(dir: string): Promise { - 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 { return getPathForExecutable("cargo");