| import * as vscode from "vscode"; | |
| import * as fspath from "path"; | |
| import * as fs from "fs"; | |
| import type { CtxInit } from "./ctx"; | |
| import * as ra from "./lsp_ext"; | |
| import type { FetchDependencyListResult } from "./lsp_ext"; | |
| import { unwrapUndefinable } from "./util"; | |
| export class RustDependenciesProvider implements vscode.TreeDataProvider< | |
| Dependency | DependencyFile | |
| > { | |
| dependenciesMap: { [id: string]: Dependency | DependencyFile }; | |
| ctx: CtxInit; | |
| constructor(ctx: CtxInit) { | |
| this.dependenciesMap = {}; | |
| this.ctx = ctx; | |
| } | |
| private _onDidChangeTreeData: vscode.EventEmitter< | |
| Dependency | DependencyFile | undefined | null | void | |
| > = new vscode.EventEmitter<Dependency | undefined | null | void>(); | |
| readonly onDidChangeTreeData: vscode.Event< | |
| Dependency | DependencyFile | undefined | null | void | |
| > = this._onDidChangeTreeData.event; | |
| getDependency(filePath: string): Dependency | DependencyFile | undefined { | |
| return this.dependenciesMap[filePath.toLowerCase()]; | |
| } | |
| contains(filePath: string): boolean { | |
| return filePath.toLowerCase() in this.dependenciesMap; | |
| } | |
| isInitialized(): boolean { | |
| return Object.keys(this.dependenciesMap).length !== 0; | |
| } | |
| refresh(): void { | |
| this.dependenciesMap = {}; | |
| this._onDidChangeTreeData.fire(); | |
| } | |
| getParent?( | |
| element: Dependency | DependencyFile, | |
| ): vscode.ProviderResult<Dependency | DependencyFile> { | |
| if (element instanceof Dependency) return undefined; | |
| return element.parent; | |
| } | |
| getTreeItem(element: Dependency | DependencyFile): vscode.TreeItem | Thenable<vscode.TreeItem> { | |
| const dependenciesMap = this.dependenciesMap; | |
| const elementId = element.id!; | |
| if (elementId in dependenciesMap) { | |
| const dependency = unwrapUndefinable(dependenciesMap[elementId]); | |
| return dependency; | |
| } | |
| return element; | |
| } | |
| getChildren( | |
| element?: Dependency | DependencyFile, | |
| ): vscode.ProviderResult<Dependency[] | DependencyFile[]> { | |
| return new Promise((resolve, _reject) => { | |
| if (!vscode.workspace.workspaceFolders) { | |
| 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); | |
| const collapsibleState = fs.lstatSync(filePath).isDirectory() | |
| ? vscode.TreeItemCollapsibleState.Collapsed | |
| : vscode.TreeItemCollapsibleState.None; | |
| const dep = new DependencyFile(fileName, filePath, element, collapsibleState); | |
| this.dependenciesMap[dep.dependencyPath.toLowerCase()] = dep; | |
| return dep; | |
| }); | |
| return resolve(files); | |
| } else { | |
| return resolve(this.getRootDependencies()); | |
| } | |
| }); | |
| } | |
| private async getRootDependencies(): Promise<Dependency[]> { | |
| const dependenciesResult: FetchDependencyListResult = await this.ctx.client.sendRequest( | |
| ra.fetchDependencyList, | |
| {}, | |
| ); | |
| const crates = dependenciesResult.crates; | |
| return crates | |
| .map((crate) => { | |
| const dep = this.toDep(crate.name || "unknown", crate.version || "", crate.path); | |
| this.dependenciesMap[dep.dependencyPath.toLowerCase()] = dep; | |
| return dep; | |
| }) | |
| .sort((a, b) => { | |
| return a.label.localeCompare(b.label); | |
| }); | |
| } | |
| private toDep(moduleName: string, version: string, path: string): Dependency { | |
| return new Dependency( | |
| moduleName, | |
| version, | |
| vscode.Uri.parse(path).fsPath, | |
| vscode.TreeItemCollapsibleState.Collapsed, | |
| ); | |
| } | |
| } | |
| export class Dependency extends vscode.TreeItem { | |
| constructor( | |
| public override readonly label: string, | |
| private version: string, | |
| readonly dependencyPath: string, | |
| public override readonly collapsibleState: vscode.TreeItemCollapsibleState, | |
| ) { | |
| super(label, collapsibleState); | |
| this.resourceUri = vscode.Uri.file(dependencyPath); | |
| this.id = this.resourceUri.fsPath.toLowerCase(); | |
| this.description = this.version; | |
| if (this.version) { | |
| this.tooltip = `${this.label}-${this.version}`; | |
| } else { | |
| this.tooltip = this.label; | |
| } | |
| } | |
| } | |
| export class DependencyFile extends vscode.TreeItem { | |
| constructor( | |
| override readonly label: string, | |
| readonly dependencyPath: string, | |
| readonly parent: Dependency | DependencyFile, | |
| public override readonly collapsibleState: vscode.TreeItemCollapsibleState, | |
| ) { | |
| super(vscode.Uri.file(dependencyPath), collapsibleState); | |
| this.id = this.resourceUri!.fsPath.toLowerCase(); | |
| const isDir = fs.lstatSync(this.resourceUri!.fsPath).isDirectory(); | |
| if (!isDir) { | |
| this.command = { | |
| command: "vscode.open", | |
| title: "Open File", | |
| arguments: [this.resourceUri], | |
| }; | |
| } | |
| } | |
| } | |
| export type DependencyId = { id: string }; |