From dbb2cb86e01348848f207d151121b50710591e1d Mon Sep 17 00:00:00 2001 From: James Pogran Date: Fri, 14 Jul 2023 15:19:35 -0400 Subject: [PATCH 1/7] Language Status indicator This sets a busy status when finding out the terraform version, which approximates a loading status for the extension because the request for status happens after all the other requests happen "in the background". So, we get a loading bar for the extension in the lower right hand corner. We could make this a statusbarItem instead for better visibility and/or discoverability. --- src/extension.ts | 29 +++++++++------ src/features/languageStatus.ts | 63 ++++++++++++++++++++++++++++++++ src/features/terraformVersion.ts | 40 ++++++++++---------- src/status/installedversion.ts | 20 ++++++++++ src/status/language.ts | 45 +++++++++++++++++++++++ src/status/requiredVersion.ts | 20 ++++++++++ 6 files changed, 184 insertions(+), 33 deletions(-) create mode 100644 src/features/languageStatus.ts create mode 100644 src/status/installedversion.ts create mode 100644 src/status/language.ts create mode 100644 src/status/requiredVersion.ts diff --git a/src/extension.ts b/src/extension.ts index e1302ae39..aabfb4ac8 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -34,6 +34,8 @@ import { TerraformCommands } from './commands/terraform'; import { TerraformVersionFeature } from './features/terraformVersion'; import { TerraformCloudAuthenticationProvider } from './providers/authenticationProvider'; import { TerraformCloudFeature } from './features/terraformCloud'; +import * as lsStatus from './status/language'; +import { LanguageStatusFeature } from './features/languageStatus'; const id = 'terraform'; const brand = `HashiCorp Terraform`; @@ -79,6 +81,7 @@ export async function activate(context: vscode.ExtensionContext): Promise const clientOptions: LanguageClientOptions = { documentSelector: documentSelector, + progressOnInitialization: true, synchronize: { fileEvents: [ vscode.workspace.createFileSystemWatcher('**/*.tf'), @@ -186,12 +189,22 @@ export async function activate(context: vscode.ExtensionContext): Promise client = new LanguageClient(id, serverOptions, clientOptions); client.onDidChangeState((event) => { outputChannel.appendLine(`Client: ${State[event.oldState]} --> ${State[event.newState]}`); - if (event.newState === State.Stopped) { - reporter.sendTelemetryEvent('stopClient'); + switch (event.newState) { + case State.Starting: + lsStatus.setLanguageServerStarting(); + break; + case State.Running: + lsStatus.setLanguageServerRunning(); + break; + case State.Stopped: + lsStatus.setLanguageServerStopped(); + reporter.sendTelemetryEvent('stopClient'); + break; } }); client.registerFeatures([ + new LanguageStatusFeature(client, reporter, outputChannel), new CustomSemanticTokens(client, manifest), new ModuleProvidersFeature(client, new ModuleProvidersDataProvider(context, client, reporter)), new ModuleCallsFeature(client, new ModuleCallsDataProvider(context, client, reporter)), @@ -204,17 +217,7 @@ export async function activate(context: vscode.ExtensionContext): Promise context.subscriptions.push(new GenerateBugReportCommand(context), new TerraformCommands(client, reporter)); try { - outputChannel.appendLine('Starting client'); - await client.start(); - - reporter.sendTelemetryEvent('startClient'); - - const initializeResult = client.initializeResult; - if (initializeResult !== undefined) { - const multiFoldersSupported = initializeResult.capabilities.workspace?.workspaceFolders?.supported; - outputChannel.appendLine(`Multi-folder support: ${multiFoldersSupported}`); - } } catch (error) { await handleLanguageClientStartError(error, context, reporter); } @@ -228,10 +231,12 @@ export async function deactivate(): Promise { outputChannel.appendLine(error.message); reporter.sendTelemetryException(error); vscode.window.showErrorMessage(error.message); + lsStatus.setLanguageServerState(error.message, false, vscode.LanguageStatusSeverity.Error); } else if (typeof error === 'string') { outputChannel.appendLine(error); reporter.sendTelemetryException(new Error(error)); vscode.window.showErrorMessage(error); + lsStatus.setLanguageServerState(error, false, vscode.LanguageStatusSeverity.Error); } } } diff --git a/src/features/languageStatus.ts b/src/features/languageStatus.ts new file mode 100644 index 000000000..644eea60b --- /dev/null +++ b/src/features/languageStatus.ts @@ -0,0 +1,63 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import * as vscode from 'vscode'; +import TelemetryReporter from '@vscode/extension-telemetry'; +import { BaseLanguageClient, ClientCapabilities, FeatureState, StaticFeature } from 'vscode-languageclient'; + +import { ExperimentalClientCapabilities } from './types'; +import * as lsStatus from '../status/language'; + +export class LanguageStatusFeature implements StaticFeature { + private disposables: vscode.Disposable[] = []; + + constructor( + private client: BaseLanguageClient, + private reporter: TelemetryReporter, + private outputChannel: vscode.OutputChannel, + ) {} + + getState(): FeatureState { + return { + kind: 'static', + }; + } + + public fillClientCapabilities(capabilities: ClientCapabilities & ExperimentalClientCapabilities): void { + if (!capabilities['experimental']) { + capabilities['experimental'] = {}; + } + } + + public initialize(): void { + this.reporter.sendTelemetryEvent('startClient'); + + this.outputChannel.appendLine('Started client'); + + const initializeResult = this.client.initializeResult; + if (initializeResult !== undefined) { + const multiFoldersSupported = initializeResult.capabilities.workspace?.workspaceFolders?.supported; + this.outputChannel.appendLine(`Multi-folder support: ${multiFoldersSupported}`); + lsStatus.setVersion(initializeResult.serverInfo?.version ?? ''); + } + + // if (vscode.env.isTelemetryEnabled === false) { + // return; + // } + // this.disposables.push( + // this.client.onTelemetry((event: TelemetryEvent) => { + // if (event.v !== TELEMETRY_VERSION) { + // console.log(`unsupported telemetry event: ${event}`); + // return; + // } + // this.reporter.sendRawTelemetryEvent(event.name, event.properties); + // }), + // ); + } + + public dispose(): void { + this.disposables.forEach((d: vscode.Disposable) => d.dispose()); + } +} diff --git a/src/features/terraformVersion.ts b/src/features/terraformVersion.ts index 0d2da4458..e4d7dbf42 100644 --- a/src/features/terraformVersion.ts +++ b/src/features/terraformVersion.ts @@ -11,35 +11,20 @@ import { ExperimentalClientCapabilities } from './types'; import { Utils } from 'vscode-uri'; import TelemetryReporter from '@vscode/extension-telemetry'; import { LanguageClient } from 'vscode-languageclient/node'; +import * as lsStatus from '../status/language'; +import * as versionStatus from '../status/installedversion'; +import * as requiredVersionStatus from '../status/requiredVersion'; export class TerraformVersionFeature implements StaticFeature { private disposables: vscode.Disposable[] = []; private clientTerraformVersionCommandId = 'client.refreshTerraformVersion'; - private installedVersion = vscode.languages.createLanguageStatusItem('terraform.installedVersion', [ - { language: 'terraform' }, - { language: 'terraform-vars' }, - ]); - private requiredVersion = vscode.languages.createLanguageStatusItem('terraform.requiredVersion', [ - { language: 'terraform' }, - { language: 'terraform-vars' }, - ]); - constructor( private client: LanguageClient, private reporter: TelemetryReporter, private outputChannel: vscode.OutputChannel, - ) { - this.installedVersion.name = 'TerraformInstalledVersion'; - this.installedVersion.detail = 'Installed Version'; - - this.requiredVersion.name = 'TerraformRequiredVersion'; - this.requiredVersion.detail = 'Required Version'; - - this.disposables.push(this.installedVersion); - this.disposables.push(this.requiredVersion); - } + ) {} getState(): FeatureState { return { @@ -67,9 +52,18 @@ export class TerraformVersionFeature implements StaticFeature { const moduleDir = Utils.dirname(editor.document.uri); try { + versionStatus.Waiting(); + requiredVersionStatus.Waiting(); + + lsStatus.setLanguageServerBusy(); + const response = await terraform.terraformVersion(moduleDir.toString(), this.client, this.reporter); - this.installedVersion.text = response.discovered_version || 'unknown'; - this.requiredVersion.text = response.required_version || 'any'; + versionStatus.setVersion(response.discovered_version || 'unknown'); + requiredVersionStatus.setVersion(response.required_version || 'unknown'); + + lsStatus.setLanguageServerRunning(); + versionStatus.Ready(); + requiredVersionStatus.Ready(); } catch (error) { let message = 'Unknown Error'; if (error instanceof Error) { @@ -86,6 +80,10 @@ export class TerraformVersionFeature implements StaticFeature { see this errored here. */ this.outputChannel.appendLine(message); + + lsStatus.setLanguageServerRunning(); + versionStatus.Ready(); + requiredVersionStatus.Ready(); } }); diff --git a/src/status/installedversion.ts b/src/status/installedversion.ts new file mode 100644 index 000000000..1ca8bfeec --- /dev/null +++ b/src/status/installedversion.ts @@ -0,0 +1,20 @@ +import * as vscode from 'vscode'; + +const installedVersion = vscode.languages.createLanguageStatusItem('terraform.installedVersion', [ + { language: 'terraform' }, + { language: 'terraform-vars' }, +]); +installedVersion.name = 'TerraformInstalledVersion'; +installedVersion.detail = 'Terraform Installed'; + +export function setVersion(version: string) { + installedVersion.text = version; +} + +export function Ready() { + installedVersion.busy = false; +} + +export function Waiting() { + installedVersion.busy = true; +} diff --git a/src/status/language.ts b/src/status/language.ts new file mode 100644 index 000000000..c662f7676 --- /dev/null +++ b/src/status/language.ts @@ -0,0 +1,45 @@ +import * as vscode from 'vscode'; + +const lsStatus = vscode.languages.createLanguageStatusItem('terraform-ls.status', [ + { language: 'terraform' }, + { language: 'terraform-vars' }, +]); +lsStatus.name = 'Terraform LS'; +lsStatus.detail = 'Terraform LS'; + +export function setVersion(version: string) { + lsStatus.text = version; +} + +export function setLanguageServerRunning() { + lsStatus.busy = false; +} + +export function setLanguageServerReady() { + lsStatus.busy = false; +} + +export function setLanguageServerStarting() { + lsStatus.busy = true; +} + +export function setLanguageServerBusy() { + lsStatus.busy = true; +} + +export function setLanguageServerStopped() { + // this makes the statusItem a different color in the bar + // and triggers an alert, so the user 'sees' that the ls is stopped + lsStatus.severity = vscode.LanguageStatusSeverity.Warning; + lsStatus.busy = false; +} + +export function setLanguageServerState( + detail: string, + busy: boolean, + severity: vscode.LanguageStatusSeverity = vscode.LanguageStatusSeverity.Information, +) { + lsStatus.busy = busy; + lsStatus.detail = detail; + lsStatus.severity = severity; +} diff --git a/src/status/requiredVersion.ts b/src/status/requiredVersion.ts new file mode 100644 index 000000000..5ae2fb325 --- /dev/null +++ b/src/status/requiredVersion.ts @@ -0,0 +1,20 @@ +import * as vscode from 'vscode'; + +const requiredVersion = vscode.languages.createLanguageStatusItem('terraform.requiredVersion', [ + { language: 'terraform' }, + { language: 'terraform-vars' }, +]); +requiredVersion.name = 'TerraformRequiredVersion'; +requiredVersion.detail = 'Terraform Required'; + +export function setVersion(version: string) { + requiredVersion.text = version; +} + +export function Ready() { + requiredVersion.busy = false; +} + +export function Waiting() { + requiredVersion.busy = true; +} From 326ca5e9c14eecd7a45df23e476dd5acc8d00bbb Mon Sep 17 00:00:00 2001 From: "hashicorp-copywrite[bot]" <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Date: Tue, 18 Jul 2023 16:34:38 +0000 Subject: [PATCH 2/7] [COMPLIANCE] Add required copyright headers Signed-off-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> --- src/status/installedversion.ts | 5 +++++ src/status/language.ts | 5 +++++ src/status/requiredVersion.ts | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/src/status/installedversion.ts b/src/status/installedversion.ts index 1ca8bfeec..c0d8b0e26 100644 --- a/src/status/installedversion.ts +++ b/src/status/installedversion.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + import * as vscode from 'vscode'; const installedVersion = vscode.languages.createLanguageStatusItem('terraform.installedVersion', [ diff --git a/src/status/language.ts b/src/status/language.ts index c662f7676..38bf1d694 100644 --- a/src/status/language.ts +++ b/src/status/language.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + import * as vscode from 'vscode'; const lsStatus = vscode.languages.createLanguageStatusItem('terraform-ls.status', [ diff --git a/src/status/requiredVersion.ts b/src/status/requiredVersion.ts index 5ae2fb325..ae5734481 100644 --- a/src/status/requiredVersion.ts +++ b/src/status/requiredVersion.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + import * as vscode from 'vscode'; const requiredVersion = vscode.languages.createLanguageStatusItem('terraform.requiredVersion', [ From 485e446bb0b7562da182b2bd8966518fa7b7c37e Mon Sep 17 00:00:00 2001 From: James Pogran Date: Thu, 20 Jul 2023 13:30:01 -0400 Subject: [PATCH 3/7] feedback --- src/features/languageStatus.ts | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/features/languageStatus.ts b/src/features/languageStatus.ts index 644eea60b..a1a7e64bd 100644 --- a/src/features/languageStatus.ts +++ b/src/features/languageStatus.ts @@ -33,28 +33,16 @@ export class LanguageStatusFeature implements StaticFeature { public initialize(): void { this.reporter.sendTelemetryEvent('startClient'); - this.outputChannel.appendLine('Started client'); const initializeResult = this.client.initializeResult; - if (initializeResult !== undefined) { - const multiFoldersSupported = initializeResult.capabilities.workspace?.workspaceFolders?.supported; - this.outputChannel.appendLine(`Multi-folder support: ${multiFoldersSupported}`); - lsStatus.setVersion(initializeResult.serverInfo?.version ?? ''); + if (initializeResult === undefined) { + return; } - // if (vscode.env.isTelemetryEnabled === false) { - // return; - // } - // this.disposables.push( - // this.client.onTelemetry((event: TelemetryEvent) => { - // if (event.v !== TELEMETRY_VERSION) { - // console.log(`unsupported telemetry event: ${event}`); - // return; - // } - // this.reporter.sendRawTelemetryEvent(event.name, event.properties); - // }), - // ); + const multiFoldersSupported = initializeResult.capabilities.workspace?.workspaceFolders?.supported; + this.outputChannel.appendLine(`Multi-folder support: ${multiFoldersSupported}`); + lsStatus.setVersion(initializeResult.serverInfo?.version ?? ''); } public dispose(): void { From 5f379fac652f362b0040b02d8e6cdca9e7da015f Mon Sep 17 00:00:00 2001 From: James Pogran Date: Mon, 24 Jul 2023 14:28:27 -0400 Subject: [PATCH 4/7] Update src/status/language.ts Co-authored-by: Radek Simko --- src/status/language.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/status/language.ts b/src/status/language.ts index 38bf1d694..846c294de 100644 --- a/src/status/language.ts +++ b/src/status/language.ts @@ -34,7 +34,7 @@ export function setLanguageServerBusy() { export function setLanguageServerStopped() { // this makes the statusItem a different color in the bar - // and triggers an alert, so the user 'sees' that the ls is stopped + // and triggers an alert, so the user 'sees' that the LS is stopped lsStatus.severity = vscode.LanguageStatusSeverity.Warning; lsStatus.busy = false; } From e3e72844ce3d0d8e558264649c52c466de9f980a Mon Sep 17 00:00:00 2001 From: James Pogran Date: Mon, 24 Jul 2023 14:30:30 -0400 Subject: [PATCH 5/7] remove multi-folder support log line --- src/features/languageStatus.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/features/languageStatus.ts b/src/features/languageStatus.ts index a1a7e64bd..0a9d07725 100644 --- a/src/features/languageStatus.ts +++ b/src/features/languageStatus.ts @@ -40,8 +40,6 @@ export class LanguageStatusFeature implements StaticFeature { return; } - const multiFoldersSupported = initializeResult.capabilities.workspace?.workspaceFolders?.supported; - this.outputChannel.appendLine(`Multi-folder support: ${multiFoldersSupported}`); lsStatus.setVersion(initializeResult.serverInfo?.version ?? ''); } From 2d2421c20db8918a79d2857d5eb4ec3b1e09cc10 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Mon, 24 Jul 2023 14:31:32 -0400 Subject: [PATCH 6/7] fix casing --- src/features/terraformVersion.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/terraformVersion.ts b/src/features/terraformVersion.ts index e4d7dbf42..ab32225bb 100644 --- a/src/features/terraformVersion.ts +++ b/src/features/terraformVersion.ts @@ -12,7 +12,7 @@ import { Utils } from 'vscode-uri'; import TelemetryReporter from '@vscode/extension-telemetry'; import { LanguageClient } from 'vscode-languageclient/node'; import * as lsStatus from '../status/language'; -import * as versionStatus from '../status/installedversion'; +import * as versionStatus from '../status/installedVersion'; import * as requiredVersionStatus from '../status/requiredVersion'; export class TerraformVersionFeature implements StaticFeature { From 8efae5b6020cf64b77b22cf4302bca21f5e54208 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Mon, 24 Jul 2023 14:38:50 -0400 Subject: [PATCH 7/7] fix casing --- src/status/{installedversion.ts => installedVersion.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/status/{installedversion.ts => installedVersion.ts} (100%) diff --git a/src/status/installedversion.ts b/src/status/installedVersion.ts similarity index 100% rename from src/status/installedversion.ts rename to src/status/installedVersion.ts