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 f8470a27f7..05ef835c70 100644
--- a/package.json
+++ b/package.json
@@ -211,6 +211,48 @@
{
"command": "terraform.plan",
"title": "Terraform: plan"
+ },
+ {
+ "command": "terraform.modules.refreshList",
+ "title": "Refresh",
+ "icon": "$(refresh)"
+ },
+ {
+ "command": "terraform.modules.documentation",
+ "title": "Open Documentation",
+ "icon": "$(book)"
+ }
+ ],
+ "menus": {
+ "view/title": [
+ {
+ "command": "terraform.modules.refreshList",
+ "when": "view == terraform.modules",
+ "group": "navigation"
+ }
+ ],
+ "view/item/context": [
+ {
+ "command": "terraform.modules.documentation",
+ "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 module information. [Learn more](https://www.terraform.io/docs/language/modules/develop/index.html)"
}
]
},
diff --git a/src/extension.ts b/src/extension.ts
index 39a0ca58df..0efa4e0836 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 { TerraformModuleProvider } from './providers/moduleProvider';
import { ServerPath } from './serverPath';
import { SingleInstanceTimeout } from './utils';
import { config, prunedFolderNames } from './vscodeUtils';
@@ -161,6 +162,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 TerraformModuleProvider(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..1ff0765e71
--- /dev/null
+++ b/src/providers/moduleProvider.ts
@@ -0,0 +1,156 @@
+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';
+
+const LOCALMODULE = new vscode.ThemeIcon('symbol-folder', new vscode.ThemeColor('terminal.ansiBrightBlue'));
+const TFREGISTRY = new vscode.ThemeIcon('extensions-view-icon', new vscode.ThemeColor('terminal.ansiBrightMagenta'));
+const GITHUBMODULE = new vscode.ThemeIcon('github');
+
+class TerraformModule extends vscode.TreeItem {
+ constructor(
+ public label: string,
+ public provider: string,
+ public version: string,
+ public type: string,
+ public doclink: string,
+ public state: vscode.TreeItemCollapsibleState,
+ public readonly children?: [TerraformModule],
+ ) {
+ super(label, state);
+ this.tooltip = `${this.provider}@${this.version}`;
+ this.description = `${this.provider}@${this.version}`;
+ }
+
+ iconPath = this.getIcon(this.type);
+
+ getIcon(type: string) {
+ const icon = this.terraformIcon();
+ switch (type) {
+ case 'tfregistry':
+ return icon;
+ case 'local':
+ return LOCALMODULE;
+ case 'github':
+ return GITHUBMODULE;
+ default:
+ return TFREGISTRY;
+ }
+ }
+
+ private terraformIcon() {
+ // need current extension path to find icon svg
+ // could possibly make this a custom icon
+ const myExtDir = vscode.extensions.getExtension('hashicorp.terraform').extensionPath;
+ const svg = vscode.Uri.file(path.join(myExtDir, 'assets', 'icons', 'terraform.svg'));
+ const icon = {
+ light: svg,
+ dark: svg,
+ };
+ return icon;
+ }
+}
+
+export class TerraformModuleProvider implements vscode.TreeDataProvider {
+ private _onDidChangeTreeData: vscode.EventEmitter =
+ new vscode.EventEmitter();
+ readonly onDidChangeTreeData: vscode.Event =
+ this._onDidChangeTreeData.event;
+
+ constructor(ctx: vscode.ExtensionContext, public handler: ClientHandler) {
+ ctx.subscriptions.push(
+ vscode.commands.registerCommand('terraform.modules.refreshList', () => this.refresh()),
+ vscode.commands.registerCommand('terraform.modules.documentation', (module: TerraformModule) => {
+ vscode.env.openExternal(vscode.Uri.parse(module.doclink));
+ }),
+ vscode.window.onDidChangeActiveTextEditor(async (event: vscode.TextEditor | undefined) => {
+ if (event && vscode.workspace.workspaceFolders[0]) {
+ this.refresh();
+ }
+ }),
+ );
+ }
+
+ refresh(): void {
+ this._onDidChangeTreeData.fire();
+ }
+
+ getTreeItem(element: TerraformModule): TerraformModule | Thenable {
+ return element;
+ }
+
+ getChildren(element?: TerraformModule): vscode.ProviderResult {
+ if (element) {
+ return Promise.resolve(element.children);
+ } else {
+ const m = this.getModules();
+ return Promise.resolve(m);
+ }
+ }
+
+ getCollapseState(type: string): vscode.TreeItemCollapsibleState {
+ switch (type) {
+ case 'tfregistry':
+ return vscode.TreeItemCollapsibleState.Collapsed;
+ case 'local':
+ return vscode.TreeItemCollapsibleState.None;
+ case 'github':
+ return vscode.TreeItemCollapsibleState.None;
+ default:
+ return vscode.TreeItemCollapsibleState.None;
+ }
+ }
+
+ async getModules(): Promise {
+ const activeEditor = vscode.window.activeTextEditor;
+ 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.terraform.modulelist`,
+ arguments: [`uri=${documentURI}`],
+ };
+
+ const response = await handler.client.sendRequest(ExecuteCommandRequest.type, params);
+ if (response == null) {
+ return Promise.resolve([]);
+ }
+
+ const list = response.modules.map((m) => {
+ let deps: [TerraformModule];
+ if (m.depmodules === null) {
+ deps = null;
+ } else {
+ deps = m.depmodules.map((dp) => {
+ return new TerraformModule(
+ dp.name,
+ dp.path,
+ dp.version,
+ dp.type,
+ dp.docklink,
+ vscode.TreeItemCollapsibleState.None,
+ );
+ });
+ }
+
+ const state = this.getCollapseState(m.type);
+
+ return new TerraformModule(m.name, m.path, m.version, m.type, m.docklink, state, deps);
+ });
+
+ return list;
+ });
+ }
+}