fetching dependencies from the server

This commit is contained in:
Bruno Ortiz 2023-04-02 21:58:20 -03:00
parent 1201b156d8
commit 09e0a00d36
9 changed files with 155 additions and 156 deletions

View 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(),
}
}

View file

@ -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))
}

View file

@ -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(),
})
}

View file

@ -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;

View file

@ -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)

View file

@ -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 });
}
}
};

View file

@ -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) {

View file

@ -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 {

View file

@ -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");