diff --git a/crates/ide/src/fetch_crates.rs b/crates/ide/src/fetch_crates.rs index 416082ae73b..5750d6b4261 100644 --- a/crates/ide/src/fetch_crates.rs +++ b/crates/ide/src/fetch_crates.rs @@ -1,9 +1,9 @@ use ide_db::{ base_db::{CrateOrigin, SourceDatabase, SourceDatabaseExt}, - RootDatabase, + FxIndexSet, RootDatabase, }; -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct CrateInfo { pub name: String, pub version: String, @@ -16,7 +16,7 @@ pub struct CrateInfo { // // |=== // image::https://user-images.githubusercontent.com/5748995/229394139-2625beab-f4c9-484b-84ed-ad5dee0b1e1a.png[] -pub(crate) fn fetch_crates(db: &RootDatabase) -> Vec { +pub(crate) fn fetch_crates(db: &RootDatabase) -> FxIndexSet { let crate_graph = db.crate_graph(); crate_graph .iter() diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 96adb11dcde..24e2aed65a5 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -70,7 +70,7 @@ use ide_db::{ salsa::{self, ParallelDatabase}, CrateOrigin, Env, FileLoader, FileSet, SourceDatabase, VfsPath, }, - symbol_index, FxHashMap, LineIndexDatabase, + symbol_index, FxHashMap, FxIndexSet, LineIndexDatabase, }; use syntax::SourceFile; @@ -333,7 +333,7 @@ impl Analysis { self.with_db(|db| view_crate_graph::view_crate_graph(db, full)) } - pub fn fetch_crates(&self) -> Cancellable> { + pub fn fetch_crates(&self) -> Cancellable> { self.with_db(|db| fetch_crates::fetch_crates(db)) } diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 7fe32754c90..4393d5fccbc 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -8,7 +8,15 @@ import { applySnippetWorkspaceEdit, applySnippetTextEdits } from "./snippets"; import { spawnSync } from "child_process"; import { RunnableQuickPick, selectRunnable, createTask, createArgs } from "./run"; import { AstInspector } from "./ast_inspector"; -import { isRustDocument, isCargoTomlDocument, sleep, isRustEditor, RustEditor } from "./util"; +import { + isRustDocument, + isCargoTomlDocument, + sleep, + isRustEditor, + RustEditor, + RustDocument, + closeDocument, +} from "./util"; import { startDebugSession, makeDebugConfig } from "./debug"; import { LanguageClient } from "vscode-languageclient/node"; import { LINKED_COMMANDS } from "./client"; @@ -269,27 +277,63 @@ export function openCargoToml(ctx: CtxInit): Cmd { export function revealDependency(ctx: CtxInit): Cmd { return async (editor: RustEditor) => { - 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); if (dep) { 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)); - parentChain.reverse(); - for (const idx in parentChain) { - await ctx.treeView?.reveal(parentChain[idx], { select: true, expand: true }); - } + await revealParentChain(editor.document, ctx); } }; } +/** + * This function calculates the parent chain of a given file until it reaches it crate root contained in ctx.dependencies. + * This is need because the TreeView is Lazy, so at first it only has the root dependencies: For example if we have the following crates: + * - core + * - alloc + * - std + * + * if I want to reveal alloc/src/str.rs, I have to: + + * 1. reveal every children of alloc + * - core + * - alloc\ + *  |-beches\ + *  |-src\ + *  |- ... + * - std + * 2. reveal every children of src: + * core + * alloc\ + *  |-beches\ + *  |-src\ + *   |- lib.rs\ + *   |- str.rs <------- FOUND IT!\ + *   |- ...\ + *  |- ...\ + * std + */ +async function revealParentChain(document: RustDocument, ctx: CtxInit) { + let documentPath = document.uri.fsPath; + const maxDepth = documentPath.split(path.sep).length - 1; + const parentChain: DependencyId[] = [{ id: documentPath.toLowerCase() }]; + do { + documentPath = path.dirname(documentPath); + parentChain.push({ id: documentPath.toLowerCase() }); + if (parentChain.length >= maxDepth) { + // this is an odd case that can happen when we change a crate version but we'd still have + // a open file referencing the old version + await closeDocument(document); + return; + } + } while (!ctx.dependencies?.contains(documentPath)); + parentChain.reverse(); + for (const idx in parentChain) { + await ctx.treeView?.reveal(parentChain[idx], { select: true, expand: true }); + } +} + export async function execRevealDependency(e: RustEditor): Promise { await vscode.commands.executeCommand("rust-analyzer.revealDependency", e); } diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 69347522b86..dd2373d5847 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -7,6 +7,7 @@ import { Config, prepareVSCodeConfig } from "./config"; import { createClient } from "./client"; import { executeDiscoverProject, + isDocumentInWorkspace, isRustDocument, isRustEditor, LazyOutputChannel, @@ -277,15 +278,16 @@ export class Ctx { ...this, client: client, }; - const rootPath = vscode.workspace.workspaceFolders![0].uri.fsPath; - this._dependencies = new RustDependenciesProvider(rootPath, ctxInit); + this._dependencies = new RustDependenciesProvider(ctxInit); this._treeView = vscode.window.createTreeView("rustDependencies", { treeDataProvider: this._dependencies, showCollapseAll: true, }); + this.pushExtCleanup(this._treeView); vscode.window.onDidChangeActiveTextEditor((e) => { - if (e && isRustEditor(e)) { + // we should skip documents that belong to the current workspace + if (e && isRustEditor(e) && !isDocumentInWorkspace(e.document)) { execRevealDependency(e).catch((reason) => { void vscode.window.showErrorMessage(`Dependency error: ${reason}`); }); diff --git a/editors/code/src/dependencies_provider.ts b/editors/code/src/dependencies_provider.ts index 2e5ea04922f..3edbb316814 100644 --- a/editors/code/src/dependencies_provider.ts +++ b/editors/code/src/dependencies_provider.ts @@ -15,7 +15,7 @@ export class RustDependenciesProvider dependenciesMap: { [id: string]: Dependency | DependencyFile }; ctx: CtxInit; - constructor(private readonly workspaceRoot: string,ctx: CtxInit) { + constructor(ctx: CtxInit) { this.dependenciesMap = {}; this.ctx = ctx; } @@ -37,6 +37,7 @@ export class RustDependenciesProvider } refresh(): void { + this.dependenciesMap = {}; this._onDidChangeTreeData.fire(); } @@ -56,7 +57,7 @@ export class RustDependenciesProvider element?: Dependency | DependencyFile ): vscode.ProviderResult { return new Promise((resolve, _reject) => { - if (!this.workspaceRoot) { + if (!vscode.workspace.workspaceFolders) { void vscode.window.showInformationMessage("No dependency in empty workspace"); return Promise.resolve([]); } @@ -108,6 +109,7 @@ export class Dependency extends vscode.TreeItem { public readonly collapsibleState: vscode.TreeItemCollapsibleState ) { super(label, collapsibleState); + this.id = this.dependencyPath.toLowerCase(); this.tooltip = `${this.label}-${this.version}`; this.description = this.version; this.resourceUri = vscode.Uri.file(dependencyPath); diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts index 922fbcbcf35..0196b37b8b6 100644 --- a/editors/code/src/util.ts +++ b/editors/code/src/util.ts @@ -112,6 +112,24 @@ export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor { return isRustDocument(editor.document); } +export function isDocumentInWorkspace(document: RustDocument): boolean { + const workspaceFolders = vscode.workspace.workspaceFolders; + if (!workspaceFolders) { + return false; + } + for (const folder of workspaceFolders) { + if (document.uri.fsPath.startsWith(folder.uri.fsPath)) { + return true; + } + } + return false; +} + +export async function closeDocument(document: RustDocument) { + await vscode.window.showTextDocument(document, { preview: true, preserveFocus: false }); + await vscode.commands.executeCommand("workbench.action.closeActiveEditor"); +} + export function isValidExecutable(path: string): boolean { log.debug("Checking availability of a binary at", path);