1import * as vscode from "vscode"; 2import * as fspath from "path"; 3import * as fs from "fs"; 4import { CtxInit } from "./ctx"; 5import * as ra from "./lsp_ext"; 6import { FetchDependencyListResult } from "./lsp_ext"; 7 8export class RustDependenciesProvider 9 implements vscode.TreeDataProvider<Dependency | DependencyFile> 10{ 11 dependenciesMap: { [id: string]: Dependency | DependencyFile }; 12 ctx: CtxInit; 13 14 constructor(ctx: CtxInit) { 15 this.dependenciesMap = {}; 16 this.ctx = ctx; 17 } 18 19 private _onDidChangeTreeData: vscode.EventEmitter< 20 Dependency | DependencyFile | undefined | null | void 21 > = new vscode.EventEmitter<Dependency | undefined | null | void>(); 22 23 readonly onDidChangeTreeData: vscode.Event< 24 Dependency | DependencyFile | undefined | null | void 25 > = this._onDidChangeTreeData.event; 26 27 getDependency(filePath: string): Dependency | DependencyFile | undefined { 28 return this.dependenciesMap[filePath.toLowerCase()]; 29 } 30 31 contains(filePath: string): boolean { 32 return filePath.toLowerCase() in this.dependenciesMap; 33 } 34 35 isInitialized(): boolean { 36 return Object.keys(this.dependenciesMap).length !== 0; 37 } 38 39 refresh(): void { 40 this.dependenciesMap = {}; 41 this._onDidChangeTreeData.fire(); 42 } 43 44 getParent?( 45 element: Dependency | DependencyFile 46 ): vscode.ProviderResult<Dependency | DependencyFile> { 47 if (element instanceof Dependency) return undefined; 48 return element.parent; 49 } 50 51 getTreeItem(element: Dependency | DependencyFile): vscode.TreeItem | Thenable<vscode.TreeItem> { 52 if (element.id! in this.dependenciesMap) return this.dependenciesMap[element.id!]; 53 return element; 54 } 55 56 getChildren( 57 element?: Dependency | DependencyFile 58 ): vscode.ProviderResult<Dependency[] | DependencyFile[]> { 59 return new Promise((resolve, _reject) => { 60 if (!vscode.workspace.workspaceFolders) { 61 void vscode.window.showInformationMessage("No dependency in empty workspace"); 62 return Promise.resolve([]); 63 } 64 if (element) { 65 const files = fs.readdirSync(element.dependencyPath).map((fileName) => { 66 const filePath = fspath.join(element.dependencyPath, fileName); 67 const collapsibleState = fs.lstatSync(filePath).isDirectory() 68 ? vscode.TreeItemCollapsibleState.Collapsed 69 : vscode.TreeItemCollapsibleState.None; 70 const dep = new DependencyFile(fileName, filePath, element, collapsibleState); 71 this.dependenciesMap[dep.dependencyPath.toLowerCase()] = dep; 72 return dep; 73 }); 74 return resolve(files); 75 } else { 76 return resolve(this.getRootDependencies()); 77 } 78 }); 79 } 80 81 private async getRootDependencies(): Promise<Dependency[]> { 82 const dependenciesResult: FetchDependencyListResult = await this.ctx.client.sendRequest( 83 ra.fetchDependencyList, 84 {} 85 ); 86 const crates = dependenciesResult.crates; 87 88 return crates 89 .map((crate) => { 90 const dep = this.toDep(crate.name || "unknown", crate.version || "", crate.path); 91 this.dependenciesMap[dep.dependencyPath.toLowerCase()] = dep; 92 return dep; 93 }) 94 .sort((a, b) => { 95 return a.label.localeCompare(b.label); 96 }); 97 } 98 99 private toDep(moduleName: string, version: string, path: string): Dependency { 100 return new Dependency( 101 moduleName, 102 version, 103 vscode.Uri.parse(path).fsPath, 104 vscode.TreeItemCollapsibleState.Collapsed 105 ); 106 } 107} 108 109export class Dependency extends vscode.TreeItem { 110 constructor( 111 public readonly label: string, 112 private version: string, 113 readonly dependencyPath: string, 114 public readonly collapsibleState: vscode.TreeItemCollapsibleState 115 ) { 116 super(label, collapsibleState); 117 this.resourceUri = vscode.Uri.file(dependencyPath); 118 this.id = this.resourceUri.fsPath.toLowerCase(); 119 this.description = this.version; 120 if (this.version) { 121 this.tooltip = `${this.label}-${this.version}`; 122 } else { 123 this.tooltip = this.label; 124 } 125 } 126} 127 128export class DependencyFile extends vscode.TreeItem { 129 constructor( 130 readonly label: string, 131 readonly dependencyPath: string, 132 readonly parent: Dependency | DependencyFile, 133 public readonly collapsibleState: vscode.TreeItemCollapsibleState 134 ) { 135 super(vscode.Uri.file(dependencyPath), collapsibleState); 136 this.id = this.resourceUri!.fsPath.toLowerCase(); 137 const isDir = fs.lstatSync(this.resourceUri!.fsPath).isDirectory(); 138 if (!isDir) { 139 this.command = { 140 command: "vscode.open", 141 title: "Open File", 142 arguments: [this.resourceUri], 143 }; 144 } 145 } 146} 147 148export type DependencyId = { id: string }; 149