• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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