From 605bc7a525a5ea68e7b17f3c8ad76d03a7649cc7 Mon Sep 17 00:00:00 2001 From: Nayeem Rahman Date: Wed, 24 Jan 2024 17:12:15 +0000 Subject: [PATCH] feat: accept notifications for subdirectory deno.json files (#1034) --- client/src/commands.ts | 87 ++++++++++++++++++++++++++++++---------- client/src/enable.ts | 4 +- client/src/extension.ts | 20 +++++---- client/src/status_bar.ts | 2 +- client/src/testing.ts | 19 +++------ client/src/types.d.ts | 15 ++++++- 6 files changed, 100 insertions(+), 47 deletions(-) diff --git a/client/src/commands.ts b/client/src/commands.ts index 029f1dbe..88c978bb 100644 --- a/client/src/commands.ts +++ b/client/src/commands.ts @@ -14,6 +14,7 @@ import * as tasks from "./tasks"; import { DenoTestController, TestingFeature } from "./testing"; import type { DenoExtensionContext, + DidChangeDenoConfigurationParams, DidUpgradeCheckParams, TestCommandOptions, } from "./types"; @@ -32,7 +33,7 @@ import * as semver from "semver"; import * as vscode from "vscode"; import { LanguageClient, ServerOptions } from "vscode-languageclient/node"; import type { Location, Position } from "vscode-languageclient/node"; -import { getWorkspacesEnabledInfo } from "./enable"; +import { getWorkspacesEnabledInfo, setupCheckConfig } from "./enable"; import { denoUpgradePromptAndExecute } from "./upgrade"; // deno-lint-ignore no-explicit-any @@ -82,13 +83,15 @@ export function startLanguageServer( if (extensionContext.client) { const client = extensionContext.client; extensionContext.client = undefined; - extensionContext.testController?.dispose(); - extensionContext.testController = undefined; + for (const disposable of extensionContext.clientSubscriptions ?? []) { + disposable.dispose(); + } extensionContext.statusBar.refresh(extensionContext); vscode.commands.executeCommand("setContext", ENABLEMENT_FLAG, false); const timeoutMs = 10_000; await client.stop(timeoutMs); } + extensionContext.clientSubscriptions = []; // Start a new language server const command = await getDenoCommandPath(); @@ -151,34 +154,76 @@ export function startLanguageServer( ); extensionContext.serverCapabilities = client.initializeResult?.capabilities; extensionContext.statusBar.refresh(extensionContext); - context.subscriptions.push(extensionContext.client.onNotification( - "deno/didChangeDenoConfiguration", - () => { - extensionContext.tasksSidebar.refresh(); - }, - )); - context.subscriptions.push(extensionContext.client.onNotification( - "deno/didUpgradeCheck", - (params: DidUpgradeCheckParams) => { - if (extensionContext.serverInfo) { - extensionContext.serverInfo.upgradeAvailable = - params.upgradeAvailable; - extensionContext.statusBar.refresh(extensionContext); - } - }, - )); + + extensionContext.clientSubscriptions.push( + extensionContext.client.onNotification( + "deno/didUpgradeCheck", + (params: DidUpgradeCheckParams) => { + if (extensionContext.serverInfo) { + extensionContext.serverInfo.upgradeAvailable = + params.upgradeAvailable; + extensionContext.statusBar.refresh(extensionContext); + } + }, + ), + ); if (testingFeature.enabled) { - context.subscriptions.push(new DenoTestController(extensionContext)); + extensionContext.clientSubscriptions.push( + new DenoTestController(extensionContext.client), + ); } - context.subscriptions.push( + extensionContext.clientSubscriptions.push( client.onNotification( registryState, createRegistryStateHandler(), ), ); + // TODO(nayeemrmn): LSP version < 1.40.0 don't support the required API for + // "deno/didChangeDenoConfiguration". Remove this eventually. + if (semver.lt(extensionContext.serverInfo.version, "1.40.0")) { + extensionContext.scopesWithDenoJson = new Set(); + extensionContext.clientSubscriptions.push( + extensionContext.client.onNotification( + "deno/didChangeDenoConfiguration", + () => { + extensionContext.tasksSidebar.refresh(); + }, + ), + ); + extensionContext.clientSubscriptions.push( + await setupCheckConfig(extensionContext), + ); + } else { + const scopesWithDenoJson = new Set(); + extensionContext.scopesWithDenoJson = scopesWithDenoJson; + extensionContext.clientSubscriptions.push( + extensionContext.client.onNotification( + "deno/didChangeDenoConfiguration", + ({ changes }: DidChangeDenoConfigurationParams) => { + let changedScopes = false; + for (const change of changes) { + if (change.type == "added") { + const scopePath = vscode.Uri.parse(change.scopeUri).fsPath; + scopesWithDenoJson.add(scopePath); + changedScopes = true; + } else if (change.type == "removed") { + const scopePath = vscode.Uri.parse(change.scopeUri).fsPath; + scopesWithDenoJson.delete(scopePath); + changedScopes = true; + } + } + if (changedScopes) { + extensionContext.tsApi?.refresh(); + } + extensionContext.tasksSidebar.refresh(); + }, + ), + ); + } + extensionContext.tsApi.refresh(); if ( diff --git a/client/src/enable.ts b/client/src/enable.ts index b15c0807..66f9583c 100644 --- a/client/src/enable.ts +++ b/client/src/enable.ts @@ -94,12 +94,12 @@ export async function setupCheckConfig( if (!uri) { return; } - extensionContext.scopesWithDenoJson = []; + extensionContext.scopesWithDenoJson = new Set(); if ( await exists(vscode.Uri.joinPath(uri, "./deno.json")) || await exists(vscode.Uri.joinPath(uri, "./deno.jsonc")) ) { - extensionContext.scopesWithDenoJson.push(uri.fsPath); + extensionContext.scopesWithDenoJson.add(uri.fsPath); } extensionContext.tsApi?.refresh(); } diff --git a/client/src/extension.ts b/client/src/extension.ts index 8be954bd..2d5565cf 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -8,7 +8,7 @@ import { } from "./constants"; import { DenoTextDocumentContentProvider, SCHEME } from "./content_provider"; import { DenoDebugConfigurationProvider } from "./debug_config_provider"; -import { refreshEnableSettings, setupCheckConfig } from "./enable"; +import { refreshEnableSettings } from "./enable"; import { DenoStatusBar } from "./status_bar"; import { activateTaskProvider } from "./tasks"; import { getTsApi } from "./ts_api"; @@ -175,18 +175,18 @@ export async function activate( // Activate the task provider. context.subscriptions.push(activateTaskProvider(extensionContext)); - extensionContext.tsApi = getTsApi(() => ({ - enableSettingsUnscoped: extensionContext.enableSettingsUnscoped, - enableSettingsByFolder: extensionContext.enableSettingsByFolder, - scopesWithDenoJson: extensionContext.scopesWithDenoJson, - })); + extensionContext.tsApi = getTsApi(() => { + return { + enableSettingsUnscoped: extensionContext.enableSettingsUnscoped, + enableSettingsByFolder: extensionContext.enableSettingsByFolder, + scopesWithDenoJson: Array.from(extensionContext.scopesWithDenoJson ?? []), + }; + }); extensionContext.maxTsServerMemory = vscode.workspace.getConfiguration(EXTENSION_NS).get("maxTsServerMemory") ?? null; refreshEnableSettings(extensionContext); - extensionContext.scopesWithDenoJson = []; - context.subscriptions.push(await setupCheckConfig(extensionContext)); extensionContext.tasksSidebar = registerSidebar( extensionContext, @@ -235,6 +235,10 @@ export function deactivate(): Thenable | undefined { const client = extensionContext.client; extensionContext.client = undefined; + for (const disposable of extensionContext.clientSubscriptions ?? []) { + disposable.dispose(); + } + extensionContext.clientSubscriptions = undefined; extensionContext.statusBar.refresh(extensionContext); vscode.commands.executeCommand("setContext", ENABLEMENT_FLAG, false); return client.stop(); diff --git a/client/src/status_bar.ts b/client/src/status_bar.ts index 9992603d..22e334ba 100644 --- a/client/src/status_bar.ts +++ b/client/src/status_bar.ts @@ -31,7 +31,7 @@ export class DenoStatusBar { // show only when "enable" is true and language server started if ( extensionContext.client && extensionContext.serverInfo && - (extensionContext.scopesWithDenoJson.length != 0 || + (extensionContext.scopesWithDenoJson?.size || extensionContext.enableSettingsUnscoped.enable || extensionContext.enableSettingsUnscoped.enablePaths?.length || extensionContext.enableSettingsByFolder.find(([_, s]) => diff --git a/client/src/testing.ts b/client/src/testing.ts index 1aab850f..d8ce69d0 100644 --- a/client/src/testing.ts +++ b/client/src/testing.ts @@ -11,14 +11,13 @@ import { testRunCancel, testRunProgress, } from "./lsp_extensions"; -import type { DenoExtensionContext } from "./types"; -import { assert } from "./util"; import * as vscode from "vscode"; import { FeatureState, MarkupKind } from "vscode-languageclient/node"; import type { ClientCapabilities, DocumentSelector, + LanguageClient, MarkupContent, ServerCapabilities, StaticFeature, @@ -136,19 +135,13 @@ export class DenoTestController implements vscode.Disposable { #runCount = 0; #runs = new Map(); #subscriptions: vscode.Disposable[] = []; - #testController: vscode.TestController; - constructor(extensionContext: DenoExtensionContext) { - const testController = extensionContext.testController = - this - .#testController = - vscode.tests - .createTestController("denoTestController", "Deno"); + constructor(client: LanguageClient) { + const testController = vscode.tests.createTestController( + "denoTestController", + "Deno", + ); this.#subscriptions.push(testController); - - const { client } = extensionContext; - assert(client); - const runHandler = async ( request: vscode.TestRunRequest, cancellation: vscode.CancellationToken, diff --git a/client/src/types.d.ts b/client/src/types.d.ts index 42a80dc2..26032080 100644 --- a/client/src/types.d.ts +++ b/client/src/types.d.ts @@ -27,21 +27,21 @@ interface DenoExperimental { export interface DenoExtensionContext { client: LanguageClient | undefined; + clientSubscriptions: { dispose(): unknown }[] | undefined; clientOptions: LanguageClientOptions; serverInfo: DenoServerInfo | undefined; /** The capabilities returned from the server. */ serverCapabilities: | ServerCapabilities | undefined; + scopesWithDenoJson: Set | undefined; statusBar: DenoStatusBar; - testController: vscode.TestController | undefined; tsApi: TsApi; outputChannel: vscode.OutputChannel; tasksSidebar: DenoTasksTreeDataProvider; maxTsServerMemory: number | null; enableSettingsUnscoped: EnableSettings; enableSettingsByFolder: [string, EnableSettings][]; - scopesWithDenoJson: string[]; } export interface TestCommandOptions { @@ -56,3 +56,14 @@ export interface UpgradeAvailable { export interface DidUpgradeCheckParams { upgradeAvailable: UpgradeAvailable | null; } + +export interface DenoConfigurationChangeEvent { + scopeUri: string; + fileUri: string; + type: "added" | "changed" | "removed"; + configurationType: "denoJson" | "packageJson"; +} + +export interface DidChangeDenoConfigurationParams { + changes: DenoConfigurationChangeEvent[]; +}