diff --git a/assets/icons/terraform.svg b/assets/icons/terraform.svg
new file mode 100644
index 0000000000..82636954ab
--- /dev/null
+++ b/assets/icons/terraform.svg
@@ -0,0 +1,4 @@
+
diff --git a/package.json b/package.json
index 67945aab2e..d825a20146 100644
--- a/package.json
+++ b/package.json
@@ -213,6 +213,58 @@
{
"command": "terraform.plan",
"title": "Terraform: plan"
+ },
+ {
+ "command": "terraform.modules.refreshList",
+ "title": "Refresh",
+ "icon": "$(refresh)"
+ },
+ {
+ "command": "terraform.modules.openDocumentation",
+ "title": "Open Documentation",
+ "icon": "$(book)"
+ }
+ ],
+ "menus": {
+ "commandPalette": [
+ {
+ "command": "terraform.modules.refreshList",
+ "when": "false"
+ },
+ {
+ "command": "terraform.modules.openDocumentation",
+ "when": "false"
+ }
+ ],
+ "view/title": [
+ {
+ "command": "terraform.modules.refreshList",
+ "when": "view == terraform.modules",
+ "group": "navigation"
+ }
+ ],
+ "view/item/context": [
+ {
+ "command": "terraform.modules.openDocumentation",
+ "when": "view == terraform.modules"
+ }
+ ]
+ },
+ "views": {
+ "explorer": [
+ {
+ "id": "terraform.modules",
+ "name": "Terraform Module Calls",
+ "icon": "assets/icons/terraform.svg",
+ "visibility": "collapsed",
+ "when": "terraform.showModuleView"
+ }
+ ]
+ },
+ "viewsWelcome": [
+ {
+ "view": "terraform.modules",
+ "contents": "The active editor cannot provide information about installed modules. [Learn more about modules](https://www.terraform.io/docs/language/modules/develop/index.html) You may need to run 'terraform get'"
}
]
},
diff --git a/src/extension.ts b/src/extension.ts
index 782fc27e6c..62d4c0870b 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -5,6 +5,7 @@ import { LanguageClient } from 'vscode-languageclient/node';
import { Utils } from 'vscode-uri';
import { ClientHandler, TerraformLanguageClient } from './clientHandler';
import { defaultVersionString, isValidVersionString, LanguageServerInstaller } from './languageServerInstaller';
+import { ModuleProvider } from './providers/moduleProvider';
import { ServerPath } from './serverPath';
import { SingleInstanceTimeout } from './utils';
import { config, getActiveTextEditor, prunedFolderNames } from './vscodeUtils';
@@ -135,6 +136,11 @@ export async function activate(context: vscode.ExtensionContext): Promise {
}
}
+ vscode.commands.executeCommand('setContext', 'terraform.showModuleView', true);
+ context.subscriptions.push(
+ vscode.window.registerTreeDataProvider('terraform.modules', new ModuleProvider(context, clientHandler)),
+ );
+
// export public API
return { clientHandler, moduleCallers };
}
diff --git a/src/providers/moduleProvider.ts b/src/providers/moduleProvider.ts
new file mode 100644
index 0000000000..6c184a985d
--- /dev/null
+++ b/src/providers/moduleProvider.ts
@@ -0,0 +1,153 @@
+import * as path from 'path';
+import * as vscode from 'vscode';
+import { ExecuteCommandParams, ExecuteCommandRequest } from 'vscode-languageclient';
+import { Utils } from 'vscode-uri';
+import { ClientHandler } from '../clientHandler';
+import { getActiveTextEditor } from '../vscodeUtils';
+
+class ModuleCall extends vscode.TreeItem {
+ constructor(
+ public label: string,
+ public sourceAddr: string,
+ public version: string,
+ public sourceType: string,
+ public docsLink: string,
+ public terraformIcon: string,
+ public readonly children: ModuleCall[],
+ ) {
+ super(
+ label,
+ children.length >= 1 ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None,
+ );
+
+ this.description = this.version ? `${this.version}` : '';
+
+ if (this.version === undefined) {
+ this.tooltip = `${this.sourceAddr}`;
+ } else {
+ this.tooltip = `${this.sourceAddr}@${this.version}`;
+ }
+ }
+
+ iconPath = this.getIcon(this.sourceType);
+
+ getIcon(type: string) {
+ switch (type) {
+ case 'tfregistry':
+ return {
+ light: this.terraformIcon,
+ dark: this.terraformIcon,
+ };
+ case 'local':
+ return new vscode.ThemeIcon('symbol-folder');
+ case 'github':
+ return new vscode.ThemeIcon('github');
+ case 'git':
+ return new vscode.ThemeIcon('git-branch');
+ default:
+ return new vscode.ThemeIcon('extensions-view-icon');
+ }
+ }
+}
+
+export class ModuleProvider implements vscode.TreeDataProvider {
+ private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter<
+ ModuleCall | undefined | null | void
+ >();
+ readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event;
+
+ private svg = '';
+
+ constructor(ctx: vscode.ExtensionContext, public handler: ClientHandler) {
+ this.svg = ctx.asAbsolutePath(path.join('assets', 'icons', 'terraform.svg'));
+ ctx.subscriptions.push(
+ vscode.commands.registerCommand('terraform.modules.refreshList', () => this.refresh()),
+ vscode.commands.registerCommand('terraform.modules.openDocumentation', (module: ModuleCall) => {
+ vscode.env.openExternal(vscode.Uri.parse(module.docsLink));
+ }),
+ vscode.window.onDidChangeActiveTextEditor(async (event: vscode.TextEditor | undefined) => {
+ if (event && getActiveTextEditor()) {
+ this.refresh();
+ }
+ }),
+ );
+ }
+
+ refresh(): void {
+ this._onDidChangeTreeData.fire();
+ }
+
+ getTreeItem(element: ModuleCall): ModuleCall | Thenable {
+ return element;
+ }
+
+ getChildren(element?: ModuleCall): vscode.ProviderResult {
+ if (element) {
+ return Promise.resolve(element.children);
+ } else {
+ const m = this.getModules();
+ return Promise.resolve(m);
+ }
+ }
+
+ async getModules(): Promise {
+ const activeEditor = getActiveTextEditor();
+ if (activeEditor === undefined) {
+ return Promise.resolve([]);
+ }
+
+ const document = activeEditor.document;
+ if (document === undefined) {
+ return Promise.resolve([]);
+ }
+
+ const editor = document.uri;
+ const documentURI = Utils.dirname(editor);
+ const handler = this.handler.getClient(editor);
+
+ return await handler.client.onReady().then(async () => {
+ const params: ExecuteCommandParams = {
+ command: `${handler.commandPrefix}.terraform-ls.module.calls`,
+ arguments: [`uri=${documentURI}`],
+ };
+
+ const response = await handler.client.sendRequest(ExecuteCommandRequest.type, params);
+ if (response == null) {
+ return Promise.resolve([]);
+ }
+
+ const list = response.module_calls.map((m) =>
+ this.toModuleCall(m.name, m.source_addr, m.version, m.source_type, m.docs_link, this.svg, m.dependent_modules),
+ );
+
+ return list;
+ });
+ }
+
+ toModuleCall(
+ name: string,
+ sourceAddr: string,
+ version: string,
+ sourceType: string,
+ docsLink: string,
+ terraformIcon: string,
+ dependents: any,
+ ): ModuleCall {
+ let deps: ModuleCall[] = [];
+ if (dependents.length != 0) {
+ deps = dependents.map((dp) =>
+ this.toModuleCall(
+ dp.name,
+ dp.source_addr,
+ dp.version,
+ dp.source_type,
+ dp.docs_link,
+ terraformIcon,
+ dp.dependent_modules,
+ ),
+ );
+ }
+
+ return new ModuleCall(name, sourceAddr, version, sourceType, docsLink, terraformIcon, deps);
+ }
+}