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