From dc7278a52fe3b38975beeafd6476435baae7e25e Mon Sep 17 00:00:00 2001 From: gigara Date: Mon, 1 Dec 2025 19:30:20 +0530 Subject: [PATCH 01/13] Update activateProjectExplorer function to accept projectUri and improve state machine integration --- .../mi/mi-extension/src/project-explorer/activate.ts | 8 +++++--- workspaces/mi/mi-extension/src/stateMachine.ts | 3 +-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/workspaces/mi/mi-extension/src/project-explorer/activate.ts b/workspaces/mi/mi-extension/src/project-explorer/activate.ts index b121e0b181..07fdefea1f 100644 --- a/workspaces/mi/mi-extension/src/project-explorer/activate.ts +++ b/workspaces/mi/mi-extension/src/project-explorer/activate.ts @@ -37,11 +37,13 @@ import { webviews } from '../visualizer/webview'; import { MILanguageClient } from '../lang-client/activator'; let isProjectExplorerInitialized = false; -export async function activateProjectExplorer(treeviewId: string, context: ExtensionContext, lsClient: ExtendedLanguageClient, isInWI: boolean) { +export async function activateProjectExplorer(treeviewId: string, context: ExtensionContext, projectUri: string, isInWI: boolean) { if (isProjectExplorerInitialized) { return; } isProjectExplorerInitialized = true; + const ls = await MILanguageClient.getInstance(projectUri); + const lsClient: ExtendedLanguageClient | undefined = ls.languageClient; const projectExplorerDataProvider = new ProjectExplorerEntryProvider(context); await projectExplorerDataProvider.refresh(); @@ -52,12 +54,12 @@ export async function activateProjectExplorer(treeviewId: string, context: Exten const runtimeVersion = projectDetailsRes.primaryDetails.runtimeVersion.value; const isRegistrySupported = compareVersions(runtimeVersion, RUNTIME_VERSION_440) < 0; - commands.registerCommand(COMMANDS.REFRESH_COMMAND, () => { + commands.registerCommand(COMMANDS.REFRESH_COMMAND, () => { if (isInWI) { commands.executeCommand(COMMANDS.WI_PROJECT_EXPLORER_VIEW_REFRESH); return; } - return projectExplorerDataProvider.refresh(); + return projectExplorerDataProvider.refresh(); }); commands.registerCommand(COMMANDS.ADD_ARTIFACT_COMMAND, (entry: ProjectExplorerEntry) => { diff --git a/workspaces/mi/mi-extension/src/stateMachine.ts b/workspaces/mi/mi-extension/src/stateMachine.ts index b4ca482655..8719e03f3c 100644 --- a/workspaces/mi/mi-extension/src/stateMachine.ts +++ b/workspaces/mi/mi-extension/src/stateMachine.ts @@ -658,9 +658,8 @@ const stateMachine = createMachine({ }, activateOtherFeatures: (context, event) => { return new Promise(async (resolve, reject) => { - const ls = await MILanguageClient.getInstance(context.projectUri!); const treeviewId = context.isInWI ? WI_PROJECT_EXPLORER_VIEW_ID : MI_PROJECT_EXPLORER_VIEW_ID; - await activateProjectExplorer(treeviewId, extension.context, ls.languageClient!, context.isInWI); + await activateProjectExplorer(treeviewId, extension.context, context.projectUri!, context.isInWI); await activateTestExplorer(extension.context); resolve(true); }); From 4eb454d8f91d69bdfdcfa6d5b12dbee4cb787ffe Mon Sep 17 00:00:00 2001 From: gigara Date: Mon, 1 Dec 2025 19:30:56 +0530 Subject: [PATCH 02/13] FIx LS not working when webview closed issue --- workspaces/mi/mi-extension/src/extension.ts | 5 ++ .../mi/mi-extension/src/util/workspace.ts | 57 +++++++++++++++---- .../mi/mi-extension/src/visualizer/webview.ts | 10 +++- 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/workspaces/mi/mi-extension/src/extension.ts b/workspaces/mi/mi-extension/src/extension.ts index 1598808384..91d6e8316e 100644 --- a/workspaces/mi/mi-extension/src/extension.ts +++ b/workspaces/mi/mi-extension/src/extension.ts @@ -34,6 +34,7 @@ import { webviews } from './visualizer/webview'; import { v4 as uuidv4 } from 'uuid'; import path from 'path'; import { COMMANDS } from './constants'; +import { enableLSForWorkspace } from './util/workspace'; const os = require('os'); export async function activate(context: vscode.ExtensionContext) { @@ -92,6 +93,10 @@ export async function activate(context: vscode.ExtensionContext) { activateRuntimeService(context, firstProject); activateVisualizer(context, firstProject); activateAiPanel(context); + + workspace.workspaceFolders?.forEach(folder => { + enableLSForWorkspace(folder.uri.fsPath); + }); } export async function deactivate(): Promise { diff --git a/workspaces/mi/mi-extension/src/util/workspace.ts b/workspaces/mi/mi-extension/src/util/workspace.ts index 82026ab46c..1a9c62cb04 100644 --- a/workspaces/mi/mi-extension/src/util/workspace.ts +++ b/workspaces/mi/mi-extension/src/util/workspace.ts @@ -21,7 +21,8 @@ import * as fs from "fs"; import { COMMANDS } from "../constants"; import path from "path"; import { MILanguageClient } from "../lang-client/activator"; -import { extension } from "../MIExtensionContext"; +import * as vscode from 'vscode'; +import { webviews } from "../visualizer/webview"; export async function replaceFullContentToFile(documentUri: string, content: string) { // Create the file if not present @@ -45,7 +46,7 @@ export async function replaceFullContentToFile(documentUri: string, content: str // Wait for the file to be fully created and accessible const maxRetries = 5; const retryDelay = 100; - + let retries = 0; while (retries < maxRetries) { try { @@ -91,12 +92,12 @@ export async function askForProject(): Promise { return projects.get(quickPick)!; } -export async function saveIdpSchemaToFile(folderPath: string, fileName:string, fileContent?: string,imageOrPdf?:string): Promise { - const documentUri = path.join(folderPath, fileName +".json"); +export async function saveIdpSchemaToFile(folderPath: string, fileName: string, fileContent?: string, imageOrPdf?: string): Promise { + const documentUri = path.join(folderPath, fileName + ".json"); if (!fs.existsSync(folderPath)) { fs.mkdirSync(folderPath, { recursive: true }); } - if(fileContent){ + if (fileContent) { fs.writeFileSync(documentUri, fileContent, 'utf-8'); } if (imageOrPdf) { @@ -110,27 +111,63 @@ export async function saveIdpSchemaToFile(folderPath: string, fileName:string, f } const mimeTypeMatch = imageOrPdf.match(/^data:(.*?);base64,/); if (mimeTypeMatch) { - const mimeType = mimeTypeMatch[1]; + const mimeType = mimeTypeMatch[1]; let extension = ""; if (mimeType === "application/pdf") { extension = ".pdf"; } else if (mimeType.startsWith("image/")) { - extension = mimeType.split("/")[1]; + extension = mimeType.split("/")[1]; extension = `.${extension}`; } else { console.error("Unsupported MIME type:", mimeType); - return false; + return false; } - const base64Data = imageOrPdf.replace(/^data:.*;base64,/, ""); + const base64Data = imageOrPdf.replace(/^data:.*;base64,/, ""); const binaryData = Buffer.from(base64Data, "base64"); const filePath = path.join(folderPath, fileName + extension); fs.writeFileSync(filePath, binaryData); } else { console.error("Invalid base64 string format."); - return false; + return false; } } commands.executeCommand(COMMANDS.REFRESH_COMMAND); return true; } +export function enableLSForWorkspace(workspacePath: string): void { + window.onDidChangeActiveTextEditor(async (event) => { + const artifactsPath = path.join(workspacePath, 'src', 'main', 'wso2mi', 'artifacts'); + const hasActiveWebview = webviews.has(workspacePath); + + if (hasActiveWebview) { + return; + } + if (!event) { // No text editors + await MILanguageClient.stopInstance(workspacePath); + return; + } + if (!event.document.uri.fsPath.startsWith(artifactsPath)) { + return; + } + const hasActiveDocument = hasActiveDocumentInWorkspace(workspacePath); + + if (hasActiveDocument) { + await MILanguageClient.getInstance(workspacePath); + } else { + await MILanguageClient.stopInstance(workspacePath); + } + }); +} + +export function hasActiveDocumentInWorkspace(workspacePath: string): boolean { + const artifactsPath = path.join(workspacePath, 'src', 'main', 'wso2mi', 'artifacts'); + for (const tabGroup of vscode.window.tabGroups.all) { + for (const tab of tabGroup.tabs) { + if (tab.input instanceof vscode.TabInputText && tab.input.uri.fsPath.startsWith(artifactsPath)) { + return true; + } + } + } + return false; +} diff --git a/workspaces/mi/mi-extension/src/visualizer/webview.ts b/workspaces/mi/mi-extension/src/visualizer/webview.ts index 41805e0baa..3571b0a0fd 100644 --- a/workspaces/mi/mi-extension/src/visualizer/webview.ts +++ b/workspaces/mi/mi-extension/src/visualizer/webview.ts @@ -29,6 +29,7 @@ import { MACHINE_VIEW } from '@wso2/mi-core'; import { refreshDiagram } from './activate'; import { MILanguageClient } from '../lang-client/activator'; import { deletePopupStateMachine } from '../stateMachinePopup'; +import { hasActiveDocumentInWorkspace } from '../util/workspace'; export const webviews: Map = new Map(); export class VisualizerWebview { @@ -124,7 +125,7 @@ export class VisualizerWebview { // The JS file from the React build output const jsFiles = getComposerJSFiles(extension.context, 'Visualizer', webview); console.debug('JS files to be included:', jsFiles); - + const scriptUri = jsFiles.map(jsFile => { const scriptTag = ''; console.debug('Generated script tag:', scriptTag); @@ -277,7 +278,12 @@ export class VisualizerWebview { deleteStateMachine(this.projectUri); deletePopupStateMachine(this.projectUri); RPCLayer._messengers.delete(this.projectUri); - await MILanguageClient.stopInstance(this.projectUri); + const hasActiveDocument = hasActiveDocumentInWorkspace(this.projectUri); + + if (!hasActiveDocument) { + await MILanguageClient.stopInstance(this.projectUri); + } + this._panel?.dispose(); while (this._disposables.length) { From 9b3467f5a4ca7a13062e06aacf078bea9b295516 Mon Sep 17 00:00:00 2001 From: gigara Date: Mon, 1 Dec 2025 22:02:22 +0530 Subject: [PATCH 03/13] Improve ls shutdown logic --- workspaces/mi/mi-extension/src/util/workspace.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/workspaces/mi/mi-extension/src/util/workspace.ts b/workspaces/mi/mi-extension/src/util/workspace.ts index 1a9c62cb04..1c0f0d5e8f 100644 --- a/workspaces/mi/mi-extension/src/util/workspace.ts +++ b/workspaces/mi/mi-extension/src/util/workspace.ts @@ -137,7 +137,6 @@ export async function saveIdpSchemaToFile(folderPath: string, fileName: string, export function enableLSForWorkspace(workspacePath: string): void { window.onDidChangeActiveTextEditor(async (event) => { - const artifactsPath = path.join(workspacePath, 'src', 'main', 'wso2mi', 'artifacts'); const hasActiveWebview = webviews.has(workspacePath); if (hasActiveWebview) { @@ -147,9 +146,6 @@ export function enableLSForWorkspace(workspacePath: string): void { await MILanguageClient.stopInstance(workspacePath); return; } - if (!event.document.uri.fsPath.startsWith(artifactsPath)) { - return; - } const hasActiveDocument = hasActiveDocumentInWorkspace(workspacePath); if (hasActiveDocument) { From e17712f2f9759d87a44016d019af42a64ed57d31 Mon Sep 17 00:00:00 2001 From: gigara Date: Mon, 1 Dec 2025 22:11:39 +0530 Subject: [PATCH 04/13] Refactor: Update import statements for vscode and improve tab input handling --- workspaces/mi/mi-extension/src/util/workspace.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/workspaces/mi/mi-extension/src/util/workspace.ts b/workspaces/mi/mi-extension/src/util/workspace.ts index 1c0f0d5e8f..1e223cec2d 100644 --- a/workspaces/mi/mi-extension/src/util/workspace.ts +++ b/workspaces/mi/mi-extension/src/util/workspace.ts @@ -16,12 +16,11 @@ * under the License. */ -import { Position, Range, Uri, WorkspaceEdit, commands, workspace, window } from "vscode"; +import { Position, Range, Uri, WorkspaceEdit, commands, workspace, window, TabInputText } from "vscode"; import * as fs from "fs"; import { COMMANDS } from "../constants"; import path from "path"; import { MILanguageClient } from "../lang-client/activator"; -import * as vscode from 'vscode'; import { webviews } from "../visualizer/webview"; export async function replaceFullContentToFile(documentUri: string, content: string) { @@ -158,9 +157,9 @@ export function enableLSForWorkspace(workspacePath: string): void { export function hasActiveDocumentInWorkspace(workspacePath: string): boolean { const artifactsPath = path.join(workspacePath, 'src', 'main', 'wso2mi', 'artifacts'); - for (const tabGroup of vscode.window.tabGroups.all) { + for (const tabGroup of window.tabGroups.all) { for (const tab of tabGroup.tabs) { - if (tab.input instanceof vscode.TabInputText && tab.input.uri.fsPath.startsWith(artifactsPath)) { + if (tab.input instanceof TabInputText && tab.input.uri.fsPath.startsWith(artifactsPath)) { return true; } } From b5ba1bc4460430ead4d389a10cd5e93df2a7522c Mon Sep 17 00:00:00 2001 From: gigara Date: Tue, 2 Dec 2025 09:48:16 +0530 Subject: [PATCH 05/13] Refactor: Rename workspace functions to project for clarity and consistency --- workspaces/mi/mi-extension/src/extension.ts | 4 ++-- workspaces/mi/mi-extension/src/util/workspace.ts | 16 ++++++++-------- .../mi/mi-extension/src/visualizer/webview.ts | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/workspaces/mi/mi-extension/src/extension.ts b/workspaces/mi/mi-extension/src/extension.ts index 91d6e8316e..60f017d575 100644 --- a/workspaces/mi/mi-extension/src/extension.ts +++ b/workspaces/mi/mi-extension/src/extension.ts @@ -34,7 +34,7 @@ import { webviews } from './visualizer/webview'; import { v4 as uuidv4 } from 'uuid'; import path from 'path'; import { COMMANDS } from './constants'; -import { enableLSForWorkspace } from './util/workspace'; +import { enableLSForProject } from './util/workspace'; const os = require('os'); export async function activate(context: vscode.ExtensionContext) { @@ -95,7 +95,7 @@ export async function activate(context: vscode.ExtensionContext) { activateAiPanel(context); workspace.workspaceFolders?.forEach(folder => { - enableLSForWorkspace(folder.uri.fsPath); + enableLSForProject(folder.uri.fsPath); }); } diff --git a/workspaces/mi/mi-extension/src/util/workspace.ts b/workspaces/mi/mi-extension/src/util/workspace.ts index 1e223cec2d..29afd7e1c3 100644 --- a/workspaces/mi/mi-extension/src/util/workspace.ts +++ b/workspaces/mi/mi-extension/src/util/workspace.ts @@ -134,29 +134,29 @@ export async function saveIdpSchemaToFile(folderPath: string, fileName: string, return true; } -export function enableLSForWorkspace(workspacePath: string): void { +export function enableLSForProject(projectUri: string): void { window.onDidChangeActiveTextEditor(async (event) => { - const hasActiveWebview = webviews.has(workspacePath); + const hasActiveWebview = webviews.has(projectUri); if (hasActiveWebview) { return; } if (!event) { // No text editors - await MILanguageClient.stopInstance(workspacePath); + await MILanguageClient.stopInstance(projectUri); return; } - const hasActiveDocument = hasActiveDocumentInWorkspace(workspacePath); + const hasActiveDocument = hasActiveDocumentInProject(projectUri); if (hasActiveDocument) { - await MILanguageClient.getInstance(workspacePath); + await MILanguageClient.getInstance(projectUri); } else { - await MILanguageClient.stopInstance(workspacePath); + await MILanguageClient.stopInstance(projectUri); } }); } -export function hasActiveDocumentInWorkspace(workspacePath: string): boolean { - const artifactsPath = path.join(workspacePath, 'src', 'main', 'wso2mi', 'artifacts'); +export function hasActiveDocumentInProject(projectUri: string): boolean { + const artifactsPath = path.join(projectUri, 'src', 'main', 'wso2mi', 'artifacts'); for (const tabGroup of window.tabGroups.all) { for (const tab of tabGroup.tabs) { if (tab.input instanceof TabInputText && tab.input.uri.fsPath.startsWith(artifactsPath)) { diff --git a/workspaces/mi/mi-extension/src/visualizer/webview.ts b/workspaces/mi/mi-extension/src/visualizer/webview.ts index 3571b0a0fd..a28fd9ffa2 100644 --- a/workspaces/mi/mi-extension/src/visualizer/webview.ts +++ b/workspaces/mi/mi-extension/src/visualizer/webview.ts @@ -29,7 +29,7 @@ import { MACHINE_VIEW } from '@wso2/mi-core'; import { refreshDiagram } from './activate'; import { MILanguageClient } from '../lang-client/activator'; import { deletePopupStateMachine } from '../stateMachinePopup'; -import { hasActiveDocumentInWorkspace } from '../util/workspace'; +import { hasActiveDocumentInProject } from '../util/workspace'; export const webviews: Map = new Map(); export class VisualizerWebview { @@ -278,7 +278,7 @@ export class VisualizerWebview { deleteStateMachine(this.projectUri); deletePopupStateMachine(this.projectUri); RPCLayer._messengers.delete(this.projectUri); - const hasActiveDocument = hasActiveDocumentInWorkspace(this.projectUri); + const hasActiveDocument = hasActiveDocumentInProject(this.projectUri); if (!hasActiveDocument) { await MILanguageClient.stopInstance(this.projectUri); From d7db138ec9f269ec0bd5dceb2c599ae9f534c59f Mon Sep 17 00:00:00 2001 From: gigara Date: Tue, 2 Dec 2025 10:59:45 +0530 Subject: [PATCH 06/13] Refactor: Rename hasActiveDocumentInProject to hasOpenedDocumentInProject for clarity --- workspaces/mi/mi-extension/src/util/workspace.ts | 4 ++-- workspaces/mi/mi-extension/src/visualizer/webview.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/workspaces/mi/mi-extension/src/util/workspace.ts b/workspaces/mi/mi-extension/src/util/workspace.ts index 29afd7e1c3..c3d25907cf 100644 --- a/workspaces/mi/mi-extension/src/util/workspace.ts +++ b/workspaces/mi/mi-extension/src/util/workspace.ts @@ -145,7 +145,7 @@ export function enableLSForProject(projectUri: string): void { await MILanguageClient.stopInstance(projectUri); return; } - const hasActiveDocument = hasActiveDocumentInProject(projectUri); + const hasActiveDocument = hasOpenedDocumentInProject(projectUri); if (hasActiveDocument) { await MILanguageClient.getInstance(projectUri); @@ -155,7 +155,7 @@ export function enableLSForProject(projectUri: string): void { }); } -export function hasActiveDocumentInProject(projectUri: string): boolean { +export function hasOpenedDocumentInProject(projectUri: string): boolean { const artifactsPath = path.join(projectUri, 'src', 'main', 'wso2mi', 'artifacts'); for (const tabGroup of window.tabGroups.all) { for (const tab of tabGroup.tabs) { diff --git a/workspaces/mi/mi-extension/src/visualizer/webview.ts b/workspaces/mi/mi-extension/src/visualizer/webview.ts index a28fd9ffa2..eac6d9bd21 100644 --- a/workspaces/mi/mi-extension/src/visualizer/webview.ts +++ b/workspaces/mi/mi-extension/src/visualizer/webview.ts @@ -29,7 +29,7 @@ import { MACHINE_VIEW } from '@wso2/mi-core'; import { refreshDiagram } from './activate'; import { MILanguageClient } from '../lang-client/activator'; import { deletePopupStateMachine } from '../stateMachinePopup'; -import { hasActiveDocumentInProject } from '../util/workspace'; +import { hasOpenedDocumentInProject } from '../util/workspace'; export const webviews: Map = new Map(); export class VisualizerWebview { @@ -278,7 +278,7 @@ export class VisualizerWebview { deleteStateMachine(this.projectUri); deletePopupStateMachine(this.projectUri); RPCLayer._messengers.delete(this.projectUri); - const hasActiveDocument = hasActiveDocumentInProject(this.projectUri); + const hasActiveDocument = hasOpenedDocumentInProject(this.projectUri); if (!hasActiveDocument) { await MILanguageClient.stopInstance(this.projectUri); From 2d5333da0ea6c723160e757f6ed6ba00b413ab9d Mon Sep 17 00:00:00 2001 From: gigara Date: Tue, 2 Dec 2025 12:03:23 +0530 Subject: [PATCH 07/13] Refactor: Update enableLSForProject to return Disposable and adjust activation logic --- workspaces/mi/mi-extension/src/extension.ts | 2 +- workspaces/mi/mi-extension/src/util/workspace.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/workspaces/mi/mi-extension/src/extension.ts b/workspaces/mi/mi-extension/src/extension.ts index 60f017d575..efaee9c7b7 100644 --- a/workspaces/mi/mi-extension/src/extension.ts +++ b/workspaces/mi/mi-extension/src/extension.ts @@ -95,7 +95,7 @@ export async function activate(context: vscode.ExtensionContext) { activateAiPanel(context); workspace.workspaceFolders?.forEach(folder => { - enableLSForProject(folder.uri.fsPath); + context.subscriptions.push(enableLSForProject(folder.uri.fsPath)); }); } diff --git a/workspaces/mi/mi-extension/src/util/workspace.ts b/workspaces/mi/mi-extension/src/util/workspace.ts index c3d25907cf..41089529e0 100644 --- a/workspaces/mi/mi-extension/src/util/workspace.ts +++ b/workspaces/mi/mi-extension/src/util/workspace.ts @@ -16,7 +16,7 @@ * under the License. */ -import { Position, Range, Uri, WorkspaceEdit, commands, workspace, window, TabInputText } from "vscode"; +import { Position, Range, Uri, WorkspaceEdit, commands, workspace, window, TabInputText, Disposable } from "vscode"; import * as fs from "fs"; import { COMMANDS } from "../constants"; import path from "path"; @@ -134,8 +134,8 @@ export async function saveIdpSchemaToFile(folderPath: string, fileName: string, return true; } -export function enableLSForProject(projectUri: string): void { - window.onDidChangeActiveTextEditor(async (event) => { +export function enableLSForProject(projectUri: string): Disposable { + return window.onDidChangeActiveTextEditor(async (event) => { const hasActiveWebview = webviews.has(projectUri); if (hasActiveWebview) { From 3fc3550ad07d4e38af4bc1c5a9965d3908dcc8a8 Mon Sep 17 00:00:00 2001 From: gigara Date: Tue, 2 Dec 2025 12:26:47 +0530 Subject: [PATCH 08/13] Refactor: Simplify language client access and improve error handling --- workspaces/mi/mi-extension/src/extension.ts | 2 +- .../mi/mi-extension/src/lang-client/activator.ts | 12 +++++++++--- .../mi/mi-extension/src/project-explorer/activate.ts | 7 +++---- .../project-explorer/project-explorer-provider.ts | 4 ++-- workspaces/mi/mi-extension/src/stateMachine.ts | 7 +------ .../mi/mi-extension/src/test-explorer/activator.ts | 12 ++++++------ workspaces/mi/mi-extension/src/util/workspace.ts | 2 +- 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/workspaces/mi/mi-extension/src/extension.ts b/workspaces/mi/mi-extension/src/extension.ts index efaee9c7b7..411469affc 100644 --- a/workspaces/mi/mi-extension/src/extension.ts +++ b/workspaces/mi/mi-extension/src/extension.ts @@ -102,7 +102,7 @@ export async function activate(context: vscode.ExtensionContext) { export async function deactivate(): Promise { const clients = await MILanguageClient.getAllInstances(); clients.forEach(async client => { - await client?.languageClient?.stop(); + await client?.stop(); }); // close all webviews diff --git a/workspaces/mi/mi-extension/src/lang-client/activator.ts b/workspaces/mi/mi-extension/src/lang-client/activator.ts index fca9269af5..191d0e583e 100644 --- a/workspaces/mi/mi-extension/src/lang-client/activator.ts +++ b/workspaces/mi/mi-extension/src/lang-client/activator.ts @@ -102,7 +102,7 @@ const versionRegex = /(\d+\.\d+\.?\d*)/g; export class MILanguageClient { private static _instances: Map = new Map(); private static lsChannelCache: Map = new Map(); - public languageClient: ExtendedLanguageClient | undefined; + private languageClient: ExtendedLanguageClient | undefined; // eslint-disable-next-line @typescript-eslint/naming-convention private COMPATIBLE_JDK_VERSION = "11"; // Minimum JDK version required to run the language server @@ -110,13 +110,19 @@ export class MILanguageClient { constructor(private projectUri: string) { } - public static async getInstance(projectUri: string): Promise { + public static async getInstance(projectUri: string): Promise { if (!this._instances.has(projectUri)) { const instance = new MILanguageClient(projectUri); await instance.launch(projectUri); this._instances.set(projectUri, instance); } - return this._instances.get(projectUri)!; + const languageClient = this._instances.get(projectUri)!.languageClient; + if (!languageClient) { + const errorMessage = "Language client failed to initialize"; + window.showErrorMessage(errorMessage); + throw new Error(errorMessage); + } + return languageClient; } public static async stopInstance(projectUri: string) { diff --git a/workspaces/mi/mi-extension/src/project-explorer/activate.ts b/workspaces/mi/mi-extension/src/project-explorer/activate.ts index 07fdefea1f..0a0e2cc101 100644 --- a/workspaces/mi/mi-extension/src/project-explorer/activate.ts +++ b/workspaces/mi/mi-extension/src/project-explorer/activate.ts @@ -42,15 +42,14 @@ export async function activateProjectExplorer(treeviewId: string, context: Exten return; } isProjectExplorerInitialized = true; - const ls = await MILanguageClient.getInstance(projectUri); - const lsClient: ExtendedLanguageClient | undefined = ls.languageClient; + const lsClient: ExtendedLanguageClient = await MILanguageClient.getInstance(projectUri); const projectExplorerDataProvider = new ProjectExplorerEntryProvider(context); await projectExplorerDataProvider.refresh(); let registryExplorerDataProvider; const projectTree = window.createTreeView(treeviewId, { treeDataProvider: projectExplorerDataProvider }); - const projectDetailsRes = await lsClient?.getProjectDetails(); + const projectDetailsRes = await lsClient.getProjectDetails(); const runtimeVersion = projectDetailsRes.primaryDetails.runtimeVersion.value; const isRegistrySupported = compareVersions(runtimeVersion, RUNTIME_VERSION_440) < 0; @@ -573,7 +572,7 @@ export async function activateProjectExplorer(treeviewId: string, context: Exten if (filePath !== "") { const fileName = path.basename(filePath); const langClient = await MILanguageClient.getInstance(workspace.uri.fsPath); - const fileUsageIdentifiers = await langClient?.languageClient?.getResourceUsages(filePath); + const fileUsageIdentifiers = await langClient.getResourceUsages(filePath); const fileUsageMessage = fileUsageIdentifiers?.length && fileUsageIdentifiers?.length > 0 ? "It is used in:\n" + fileUsageIdentifiers.join(", ") : "No usage found"; window.showInformationMessage("Do you want to delete : " + fileName + "\n\n" + fileUsageMessage, { modal: true }, "Yes") .then(async answer => { diff --git a/workspaces/mi/mi-extension/src/project-explorer/project-explorer-provider.ts b/workspaces/mi/mi-extension/src/project-explorer/project-explorer-provider.ts index 6161b15c4e..c67e621e8a 100644 --- a/workspaces/mi/mi-extension/src/project-explorer/project-explorer-provider.ts +++ b/workspaces/mi/mi-extension/src/project-explorer/project-explorer-provider.ts @@ -145,8 +145,8 @@ async function getProjectStructureData(): Promise { continue; } const langClient = await MILanguageClient.getInstance(rootPath); - const resp = await langClient?.languageClient?.getProjectExplorerModel(rootPath); - const projectDetailsRes = await langClient?.languageClient?.getProjectDetails(); + const resp = await langClient.getProjectExplorerModel(rootPath); + const projectDetailsRes = await langClient.getProjectDetails(); const runtimeVersion = projectDetailsRes.primaryDetails.runtimeVersion.value; const projectTree = await generateTreeData(workspace, resp, runtimeVersion); diff --git a/workspaces/mi/mi-extension/src/stateMachine.ts b/workspaces/mi/mi-extension/src/stateMachine.ts index 8719e03f3c..2d25d3f12e 100644 --- a/workspaces/mi/mi-extension/src/stateMachine.ts +++ b/workspaces/mi/mi-extension/src/stateMachine.ts @@ -397,12 +397,7 @@ const stateMachine = createMachine({ try { const treeViewId = context.isInWI ? WI_PROJECT_EXPLORER_VIEW_ID : MI_PROJECT_EXPLORER_VIEW_ID; vscode.commands.executeCommand(`${treeViewId}.focus`); - const instance = await MILanguageClient.getInstance(context.projectUri!); - const errors = instance.getErrors(); - if (errors.length) { - return reject(errors); - } - const ls = instance.languageClient; + const ls = await MILanguageClient.getInstance(context.projectUri!); vscode.commands.executeCommand('setContext', 'MI.status', 'projectLoaded'); resolve(ls); diff --git a/workspaces/mi/mi-extension/src/test-explorer/activator.ts b/workspaces/mi/mi-extension/src/test-explorer/activator.ts index dfe4952ea6..7b3d8c7cac 100644 --- a/workspaces/mi/mi-extension/src/test-explorer/activator.ts +++ b/workspaces/mi/mi-extension/src/test-explorer/activator.ts @@ -97,7 +97,7 @@ export async function activateTestExplorer(extensionContext: ExtensionContext) { const fileUri = `${id.split('.xml/')[0]}.xml`; const testCaseName = id.split('.xml/')[1]; const langClient = await MILanguageClient.getInstance(getProjectRoot(Uri.parse(fileUri))!); - const st = await langClient?.languageClient?.getSyntaxTree({ + const st = await langClient.getSyntaxTree({ documentIdentifier: { uri: fileUri }, @@ -160,7 +160,7 @@ export async function activateTestExplorer(extensionContext: ExtensionContext) { try { const projectRoot = getProjectRoot(Uri.file(fileUri)); const langClient = await MILanguageClient.getInstance(projectRoot!); - const st = await langClient?.languageClient?.getSyntaxTree({ + const st = await langClient.getSyntaxTree({ documentIdentifier: { uri: fileUri }, @@ -271,7 +271,7 @@ export async function activateTestExplorer(extensionContext: ExtensionContext) { export async function createTests(uri: Uri) { const projectRoot = getProjectRoot(uri); const langClient = await MILanguageClient.getInstance(projectRoot!); - const projectDetails = await langClient?.languageClient?.getProjectDetails(); + const projectDetails = await langClient.getProjectDetails(); const projectName = projectDetails?.primaryDetails?.projectName?.value ?? getProjectName(uri); if (!testController || !projectRoot || !projectName) { @@ -351,7 +351,7 @@ async function getTestCaseNamesAndTestSuiteType(uri: Uri) { } const langClient = await MILanguageClient.getInstance(projectUri); - const st = await langClient?.languageClient?.getSyntaxTree({ + const st = await langClient.getSyntaxTree({ documentIdentifier: { uri: uri.fsPath }, @@ -363,7 +363,7 @@ async function getTestCaseNamesAndTestSuiteType(uri: Uri) { const unitTestST: UnitTest = st?.syntaxTree["unit-test"]; const testArtifact = unitTestST?.unitTestArtifacts?.testArtifact?.artifact?.textNode; - const projectStructure = await langClient.languageClient!.getProjectStructure(projectUri); + const projectStructure = await langClient!.getProjectStructure(projectUri); const artifacts = projectStructure.directoryMap.src?.main?.wso2mi?.artifacts; const apis = artifacts?.apis?.map((api: ProjectStructureArtifactResponse) => { return { name: api.name, path: api.path.split(projectUri)[1], type: "Api" } }); @@ -397,7 +397,7 @@ async function getTestCases(uri: Uri) { const projectRoot = getProjectRoot(uri); const langClient = await MILanguageClient.getInstance(projectRoot!); - const st = await langClient?.languageClient?.getSyntaxTree({ + const st = await langClient.getSyntaxTree({ documentIdentifier: { uri: uri.fsPath }, diff --git a/workspaces/mi/mi-extension/src/util/workspace.ts b/workspaces/mi/mi-extension/src/util/workspace.ts index 41089529e0..4791dfaa24 100644 --- a/workspaces/mi/mi-extension/src/util/workspace.ts +++ b/workspaces/mi/mi-extension/src/util/workspace.ts @@ -69,7 +69,7 @@ export async function askForProject(): Promise { for (const wrkspace of workspace.workspaceFolders!) { const lsClient = await MILanguageClient.getInstance(wrkspace.uri.fsPath); if (lsClient) { - const projectDetails = await lsClient.languageClient?.getProjectDetails(); + const projectDetails = await lsClient.getProjectDetails(); if (projectDetails?.primaryDetails?.projectName?.value) { if (projects.has(projectDetails.primaryDetails.projectName.value)) { projects.set(wrkspace.uri.fsPath, wrkspace.uri.fsPath); From 129129d3c8559c6abfc1ed25ceeafb1b100581aa Mon Sep 17 00:00:00 2001 From: gigara Date: Tue, 2 Dec 2025 12:47:38 +0530 Subject: [PATCH 09/13] Refactor language client usage across the extension - Replaced instances of `getStateMachine(projectUri).context().langClient` with `await MILanguageClient.getInstance(projectUri)` --- .../mi-extension/src/debugger/debugHelper.ts | 4 +- .../mi/mi-extension/src/debugger/debugger.ts | 9 +- .../src/project-explorer/activate.ts | 4 +- .../src/rpc-managers/ai-panel/rpc-manager.ts | 9 +- .../mi-data-mapper/rpc-manager.ts | 3 +- .../rpc-managers/mi-debugger/rpc-manager.ts | 7 +- .../rpc-managers/mi-diagram/rpc-manager.ts | 95 ++++++++++--------- .../rpc-managers/mi-visualizer/rpc-manager.ts | 29 +++--- .../mi/mi-extension/src/stateMachine.ts | 10 +- .../mi/mi-extension/src/util/schemaBuilder.ts | 5 +- .../mi/mi-extension/src/util/swagger.ts | 3 +- .../mi-extension/src/visualizer/activate.ts | 3 +- 12 files changed, 90 insertions(+), 91 deletions(-) diff --git a/workspaces/mi/mi-extension/src/debugger/debugHelper.ts b/workspaces/mi/mi-extension/src/debugger/debugHelper.ts index ffed46660c..effaae44f8 100644 --- a/workspaces/mi/mi-extension/src/debugger/debugHelper.ts +++ b/workspaces/mi/mi-extension/src/debugger/debugHelper.ts @@ -38,6 +38,7 @@ import { serverLog, showServerOutputChannel } from '../util/serverLogger'; import { getJavaHomeFromConfig, getServerPathFromConfig } from '../util/onboardingUtils'; import * as crypto from 'crypto'; import { Uri, workspace } from "vscode"; +import { MILanguageClient } from '../lang-client/activator'; const child_process = require('child_process'); const findProcess = require('find-process'); @@ -422,7 +423,8 @@ export async function stopServer(projectUri: string, serverPath: string, isWindo export async function executeTasks(projectUri: string, serverPath: string, isDebug: boolean): Promise { const maxTimeout = 120000; return new Promise(async (resolve, reject) => { - const isTerminated = await getStateMachine(projectUri).context().langClient?.shutdownTryoutServer(); + const langClient = await MILanguageClient.getInstance(projectUri); + const isTerminated = await langClient.shutdownTryoutServer(); if (!isTerminated) { reject('Failed to terminate the tryout server. Kill the server manually and try again.'); } diff --git a/workspaces/mi/mi-extension/src/debugger/debugger.ts b/workspaces/mi/mi-extension/src/debugger/debugger.ts index b247fce15f..665d0d7e6c 100644 --- a/workspaces/mi/mi-extension/src/debugger/debugger.ts +++ b/workspaces/mi/mi-extension/src/debugger/debugger.ts @@ -27,6 +27,7 @@ import { webviews } from '../visualizer/webview'; import { extension } from '../MIExtensionContext'; import { reject } from 'lodash'; import { LogLevel, logDebug } from '../util/logger'; +import { MILanguageClient } from '../lang-client/activator'; export interface RuntimeBreakpoint { id: number; @@ -87,7 +88,7 @@ export class Debugger extends EventEmitter { return; } const projectUri = workspace.uri.fsPath; - const langClient = getStateMachine(projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const breakpointPerFile: RuntimeBreakpoint[] = []; // To maintain the valid and invalid breakpoints in the vscode const vscodeBreakpointsPerFile: RuntimeBreakpoint[] = []; @@ -191,7 +192,7 @@ export class Debugger extends EventEmitter { return; } const projectUri = workspace.uri.fsPath; - const langClient = getStateMachine(projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const stepOverBreakpoints: RuntimeBreakpoint[] = []; if (path) { // create BreakpointPosition array @@ -278,7 +279,7 @@ export class Debugger extends EventEmitter { throw new Error(`No workspace found for path: ${filePath}`); } const projectUri = workspace.uri.fsPath; - const langClient = getStateMachine(projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); // create BreakpointPosition[] array const breakpointPositions = breakpoints.map((breakpoint) => { return { line: breakpoint.line, column: breakpoint?.column }; @@ -296,7 +297,7 @@ export class Debugger extends EventEmitter { public async getNextMediatorBreakpoint(): Promise { try { - const langClient = getStateMachine(this.projectUri).context().langClient; + const langClient = await MILanguageClient.getInstance(this.projectUri); if (!langClient) { throw new Error('Language client is not initialized'); } diff --git a/workspaces/mi/mi-extension/src/project-explorer/activate.ts b/workspaces/mi/mi-extension/src/project-explorer/activate.ts index 0a0e2cc101..b43b8dc0c8 100644 --- a/workspaces/mi/mi-extension/src/project-explorer/activate.ts +++ b/workspaces/mi/mi-extension/src/project-explorer/activate.ts @@ -513,7 +513,7 @@ export async function activateProjectExplorer(treeviewId: string, context: Exten window.showErrorMessage('Cannot find workspace folder'); return; } - const langClient = getStateMachine(workspace.uri.fsPath).context().langClient; + const langClient = await MILanguageClient.getInstance(workspace.uri.fsPath); if (!langClient) { window.showErrorMessage('Language client not found.'); return; @@ -660,7 +660,7 @@ export async function activateProjectExplorer(treeviewId: string, context: Exten window.showErrorMessage('Cannot find workspace folder'); throw new Error('Cannot find workspace folder'); } - const langClient = getStateMachine(workspace.uri.fsPath).context().langClient; + const langClient = await MILanguageClient.getInstance(workspace.uri.fsPath); // Read the POM file const workspaceFolder = vscode.workspace.getWorkspaceFolder(Uri.file(filePath)); diff --git a/workspaces/mi/mi-extension/src/rpc-managers/ai-panel/rpc-manager.ts b/workspaces/mi/mi-extension/src/rpc-managers/ai-panel/rpc-manager.ts index b27a86cab7..d76570a97d 100644 --- a/workspaces/mi/mi-extension/src/rpc-managers/ai-panel/rpc-manager.ts +++ b/workspaces/mi/mi-extension/src/rpc-managers/ai-panel/rpc-manager.ts @@ -50,6 +50,7 @@ import { codeDiagnostics } from "../../ai-panel/copilot/diagnostics/diagnostics" import { getLoginMethod } from '../../ai-panel/auth'; import { LoginMethod } from '@wso2/mi-core'; import { logInfo, logWarn, logError, logDebug } from '../../ai-panel/copilot/logger'; +import { MILanguageClient } from '../../lang-client/activator'; export class MIAIPanelRpcManager implements MIAIPanelAPI { private eventHandler: CopilotEventHandler; @@ -374,13 +375,7 @@ export class MIAIPanelRpcManager implements MIAIPanelAPI { this.eventHandler.handleCodeDiagnosticStart(xmlCodes); // Get diagnostics using existing RPC infrastructure - const { getStateMachine } = await import('../../stateMachine'); - const stateMachine = getStateMachine(this.projectUri); - if (!stateMachine) { - throw new Error('State machine not found for project'); - } - - const langClient = stateMachine.context().langClient; + const langClient = await MILanguageClient.getInstance(this.projectUri); if (!langClient) { throw new Error('Language client not available'); } diff --git a/workspaces/mi/mi-extension/src/rpc-managers/mi-data-mapper/rpc-manager.ts b/workspaces/mi/mi-extension/src/rpc-managers/mi-data-mapper/rpc-manager.ts index e0986507e3..81a1a8a475 100644 --- a/workspaces/mi/mi-extension/src/rpc-managers/mi-data-mapper/rpc-manager.ts +++ b/workspaces/mi/mi-extension/src/rpc-managers/mi-data-mapper/rpc-manager.ts @@ -57,6 +57,7 @@ import { DM_OPERATORS_FILE_NAME, DM_OPERATORS_IMPORT_NAME, READONLY_MAPPING_FUNC import { readTSFile, removeMapFunctionEntry, showMappingEndNotification } from "../../util/ai-datamapper-utils"; import { compareVersions } from "../../util/onboardingUtils"; import { mapDataMapper } from "../../ai-panel/copilot/data-mapper/mapper"; +import { MILanguageClient } from "../../lang-client/activator"; const undoRedoManager = new UndoRedoManager(); @@ -331,7 +332,7 @@ export class MiDataMapperRpcManager implements MIDataMapperAPI { const workspaceFolder = workspace.getWorkspaceFolder(Uri.file(filePath)); let miDiagramRpcManager: MiDiagramRpcManager = new MiDiagramRpcManager(this.projectUri); - const langClient = getStateMachine(this.projectUri).context().langClient; + const langClient = await MILanguageClient.getInstance(this.projectUri); const projectDetailsRes = await langClient?.getProjectDetails(); const runtimeVersion = projectDetailsRes.primaryDetails.runtimeVersion.value; const isResourceContentUsed = compareVersions(runtimeVersion, RUNTIME_VERSION_440) >= 0; diff --git a/workspaces/mi/mi-extension/src/rpc-managers/mi-debugger/rpc-manager.ts b/workspaces/mi/mi-extension/src/rpc-managers/mi-debugger/rpc-manager.ts index 8594322cf9..1957ff755b 100644 --- a/workspaces/mi/mi-extension/src/rpc-managers/mi-debugger/rpc-manager.ts +++ b/workspaces/mi/mi-extension/src/rpc-managers/mi-debugger/rpc-manager.ts @@ -33,13 +33,14 @@ import { } from "@wso2/mi-core"; import * as vscode from "vscode"; import { getStateMachine, refreshUI } from "../../stateMachine"; +import { MILanguageClient } from "../../lang-client/activator"; export class MiDebuggerRpcManager implements MiDebuggerAPI { constructor(private projectUri: string) { } async validateBreakpoints(params: ValidateBreakpointsRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const definition = await langClient.validateBreakpoints(params); resolve(definition); @@ -48,7 +49,7 @@ export class MiDebuggerRpcManager implements MiDebuggerAPI { async getBreakpointInfo(params: GetBreakpointInfoRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const breakpointInfo = await langClient.getBreakpointInfo(params); resolve(breakpointInfo); @@ -113,7 +114,7 @@ export class MiDebuggerRpcManager implements MiDebuggerAPI { async getStepOverBreakpoint(params: StepOverBreakpointRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const breakpointInfo = await langClient.getStepOverBreakpoint(params); resolve(breakpointInfo); diff --git a/workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-manager.ts b/workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-manager.ts index 1896e0c023..d95b19e7f4 100644 --- a/workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-manager.ts +++ b/workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-manager.ts @@ -339,6 +339,7 @@ import { MiVisualizerRpcManager } from "../mi-visualizer/rpc-manager"; import { DebuggerConfig } from "../../debugger/config"; import { getKubernetesConfiguration, getKubernetesDataConfiguration } from "../../util/template-engine/mustach-templates/KubernetesConfiguration"; import { parseStringPromise, Builder } from "xml2js"; +import { MILanguageClient } from "../../lang-client/activator"; const AdmZip = require('adm-zip'); const { XMLParser, XMLBuilder } = require("fast-xml-parser"); @@ -473,7 +474,7 @@ export class MiDiagramRpcManager implements MiDiagramAPI { async tryOutMediator(params: MediatorTryOutRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.tryOutMediator(params); resolve(res); }); @@ -481,7 +482,7 @@ export class MiDiagramRpcManager implements MiDiagramAPI { async shutDownTryoutServer(): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.shutdownTryoutServer(); resolve(res); }); @@ -499,7 +500,7 @@ export class MiDiagramRpcManager implements MiDiagramAPI { const payloadPath = path.join(this.projectUri, ".tryout", "input.json"); const payload = fs.readFileSync(payloadPath, "utf8"); params.inputPayload = payload - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.getMediatorInputOutputSchema(params); resolve(res); }); @@ -526,7 +527,7 @@ export class MiDiagramRpcManager implements MiDiagramAPI { } return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.getSyntaxTree({ documentIdentifier: { uri: documentUri @@ -636,7 +637,7 @@ export class MiDiagramRpcManager implements MiDiagramAPI { let fileName: string; let response: GenerateAPIResponse = { apiXml: "", endpointXml: "" }; if (!xmlData) { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const projectDetailsRes = await langClient?.getProjectDetails(); const runtimeVersion = projectDetailsRes.primaryDetails.runtimeVersion.value; const isRegistrySupported = compareVersions(runtimeVersion, RUNTIME_VERSION_440) < 0; @@ -1507,7 +1508,7 @@ export class MiDiagramRpcManager implements MiDiagramAPI { async getEndpointsAndSequences(): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const resp = await langClient.getProjectStructure(this.projectUri); const artifacts = (resp.directoryMap as any).src.main.wso2mi.artifacts; @@ -1536,7 +1537,7 @@ export class MiDiagramRpcManager implements MiDiagramAPI { async getAllAPIcontexts(): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const resp = await langClient.getProjectStructure(this.projectUri); const artifacts = (resp.directoryMap as any).src.main.wso2mi.artifacts; @@ -1552,7 +1553,7 @@ export class MiDiagramRpcManager implements MiDiagramAPI { async getTemplates(): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const resp = await langClient.getProjectStructure(this.projectUri); const artifacts = (resp.directoryMap as any).src.main.wso2mi.artifacts; @@ -3123,7 +3124,7 @@ ${endpointAttributes} async getESBConfigs(): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const resp = await langClient.getProjectStructure(this.projectUri); const ESBConfigs: string[] = []; @@ -3436,7 +3437,7 @@ ${endpointAttributes} async convertPdfToBase64Images(params: string): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const images = await langClient.pdfToImagesBase64(params) resolve(images); }); @@ -3874,7 +3875,7 @@ ${endpointAttributes} async getDefinition(params: GetDefinitionRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const definition = await langClient.getDefinition(params); resolve(definition); @@ -3896,11 +3897,11 @@ ${endpointAttributes} } async getDiagnostics(params: GetDiagnosticsReqeust): Promise { - return getStateMachine(this.projectUri).context().langClient!.getDiagnostics(params); + return (await MILanguageClient.getInstance(this.projectUri)).getDiagnostics(params); } async getAvailableResources(params: GetAvailableResourcesRequest): Promise { - return getStateMachine(this.projectUri).context().langClient!.getAvailableResources(params); + return (await MILanguageClient.getInstance(this.projectUri)).getAvailableResources(params); } async browseFile(params: BrowseFileRequest): Promise { @@ -4201,7 +4202,7 @@ ${endpointAttributes} async getAvailableConnectors(params: GetAvailableConnectorRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.getAvailableConnectors({ documentUri: params.documentUri, connectorName: params.connectorName @@ -4213,7 +4214,7 @@ ${endpointAttributes} async updateConnectors(params: UpdateConnectorRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.updateConnectors({ documentUri: params.documentUri }); @@ -4351,7 +4352,7 @@ ${endpointAttributes} async saveInboundEPUischema(params: SaveInboundEPUischemaRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.saveInboundEPUischema({ connectorName: params.connectorName, uiSchema: params.uiSchema @@ -4363,7 +4364,7 @@ ${endpointAttributes} async getInboundEPUischema(params: GetInboundEPUischemaRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.getInboundEPUischema({ connectorName: params.connectorName, documentPath: params.documentPath @@ -4576,7 +4577,7 @@ ${keyValuesXML}`; async getConnectorConnections(params: GetConnectorConnectionsRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.getConnectorConnections({ documentUri: params.documentUri, connectorName: params.connectorName @@ -4616,7 +4617,7 @@ ${keyValuesXML}`; async getAllRegistryPaths(params: GetAllRegistryPathsRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.getRegistryFiles(params.path); resolve({ registryPaths: res.map(element => element.split(path.sep).join("/")) }); }); @@ -4624,7 +4625,7 @@ ${keyValuesXML}`; async getAllResourcePaths(): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.getResourceFiles(); resolve({ resourcePaths: res }); }); @@ -4632,7 +4633,7 @@ ${keyValuesXML}`; async getConfigurableEntries(): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.getConfigurableEntries(); resolve({ configurableEntries: res }); }); @@ -4640,7 +4641,7 @@ ${keyValuesXML}`; async getAllArtifacts(params: GetAllArtifactsRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.getArifactFiles(params.path); resolve({ artifacts: res }); }); @@ -4648,7 +4649,7 @@ ${keyValuesXML}`; async getArtifactType(params: GetArtifactTypeRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.getArtifactType(params.filePath); resolve({ artifactType: res.artifactType, artifactFolder: res.artifactFolder }); }); @@ -4865,7 +4866,7 @@ ${keyValuesXML}`; extName: "Devant", }; - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); let integrationType: string | undefined; if (params.componentDir) { @@ -5041,7 +5042,7 @@ ${keyValuesXML}`; fs.mkdirSync(path.dirname(openAPISpecPath), { recursive: true }); }; - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const { swagger } = await langClient.swaggerFromAPI({ apiPath }); if (!fs.existsSync(openAPISpecPath)) { // Create the file if not exists @@ -5098,7 +5099,7 @@ ${keyValuesXML}`; return resolve({ swaggerExists: false }); } - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const { swagger: generatedSwagger } = await langClient.swaggerFromAPI({ apiPath: apiPath, swaggerPath: swaggerPath }); const swaggerContent = fs.readFileSync(swaggerPath, 'utf-8'); const isEqualSwagger = isEqualSwaggers({ @@ -5126,7 +5127,7 @@ ${keyValuesXML}`; let generatedSwagger = params.generatedSwagger; let existingSwagger = params.existingSwagger; if (!generatedSwagger || !existingSwagger) { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const response = await langClient.swaggerFromAPI({ apiPath: apiPath, ...(fs.existsSync(swaggerPath) && { swaggerPath: swaggerPath }) }); generatedSwagger = response.swagger; existingSwagger = fs.readFileSync(swaggerPath, 'utf-8'); @@ -5153,7 +5154,7 @@ ${keyValuesXML}`; let generatedSwagger = params.generatedSwagger; let existingSwagger = params.existingSwagger; if (!generatedSwagger || !existingSwagger) { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const response = await langClient.swaggerFromAPI({ apiPath }); generatedSwagger = response.swagger; existingSwagger = fs.readFileSync(swaggerPath, 'utf-8'); @@ -5272,7 +5273,7 @@ ${keyValuesXML}`; let range; if (!params.range) { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const st = await langClient.getSyntaxTree({ documentIdentifier: { uri: filePath @@ -5417,7 +5418,7 @@ ${keyValuesXML}`; async getOpenAPISpec(params: SwaggerTypeRequest): Promise { const swaggerPath = path.join(this.projectUri, SWAGGER_REL_DIR, `${path.basename(params.apiPath, ".xml")}.yaml`); - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); let response; if (params.isRuntimeService) { const versionedUrl = await exposeVersionedServices(this.projectUri); @@ -5542,7 +5543,7 @@ ${keyValuesXML}`; async testDbConnection(req: TestDbConnectionRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient; + const langClient = await MILanguageClient.getInstance(this.projectUri); const response = await langClient?.testDbConnection(req); resolve({ success: response ? response.success : false }); }); @@ -5608,7 +5609,7 @@ ${keyValuesXML}`; async checkDBDriver(className: string): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.checkDBDriver(className); resolve(res); }); @@ -5616,7 +5617,7 @@ ${keyValuesXML}`; async addDBDriver(params: AddDriverRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.addDBDriver(params); resolve(res); }); @@ -5624,7 +5625,7 @@ ${keyValuesXML}`; async removeDBDriver(params: AddDriverRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.removeDBDriver(params); resolve(res); }); @@ -5632,7 +5633,7 @@ ${keyValuesXML}`; async modifyDBDriver(params: AddDriverRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.modifyDBDriver(params); resolve(res); }); @@ -5641,7 +5642,7 @@ ${keyValuesXML}`; async generateDSSQueries(params: ExtendedDSSQueryGenRequest): Promise { const { documentUri, position, ...genQueryParams } = params; return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const xml = await langClient.generateQueries(genQueryParams); if (!xml) { @@ -5667,7 +5668,7 @@ ${keyValuesXML}`; async fetchDSSTables(params: DSSFetchTablesRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.fetchTables({ ...params, tableData: "", datasourceName: "" }); @@ -5677,7 +5678,7 @@ ${keyValuesXML}`; async getMediators(param: GetMediatorsRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); let response = await langClient.getMediators(param); resolve(response); }); @@ -5685,7 +5686,7 @@ ${keyValuesXML}`; async getMediator(param: GetMediatorRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); let response = await langClient.getMediator(param); resolve(response); }); @@ -5693,7 +5694,7 @@ ${keyValuesXML}`; async updateMediator(param: UpdateMediatorRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); let response = await langClient.generateSynapseConfig(param); if (response && response.textEdits) { let edits = response.textEdits; @@ -5717,7 +5718,7 @@ ${keyValuesXML}`; async getLocalInboundConnectors(): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); let response = await langClient.getLocalInboundConnectors(); resolve(response); }); @@ -5725,7 +5726,7 @@ ${keyValuesXML}`; async getConnectionSchema(param: GetConnectionSchemaRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); let response = await langClient.getConnectionSchema(param); resolve(response); }); @@ -5734,7 +5735,7 @@ ${keyValuesXML}`; async getExpressionCompletions(params: ExpressionCompletionsRequest): Promise { return new Promise(async (resolve, reject) => { try { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.getExpressionCompletions(params); if (!res.isIncomplete) { resolve(res); @@ -5751,7 +5752,7 @@ ${keyValuesXML}`; async getHelperPaneInfo(params: GetHelperPaneInfoRequest): Promise { return new Promise(async (resolve, reject) => { try { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); let response = await langClient.getHelperPaneInfo(params); resolve(response); } catch (error) { @@ -5763,7 +5764,7 @@ ${keyValuesXML}`; async testConnectorConnection(params: TestConnectorConnectionRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.testConnectorConnection(params); resolve(res); }); @@ -5887,7 +5888,7 @@ ${keyValuesXML}`; async getValueOfEnvVariable(variableName: string): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const response = await langClient.getConfigurableList(); const envVariable = response.find(variable => variable.key === variableName); if (envVariable && envVariable.value != null && envVariable.value !== "") { @@ -6122,7 +6123,7 @@ ${keyValuesXML}`; } async function exposeVersionedServices(projectUri: string): Promise { - const langClient = getStateMachine(projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(projectUri); const projectDetailsRes = await langClient?.getProjectDetails(); const isVersionedDeploymentEnabled = projectDetailsRes?.buildDetails?.versionedDeployment?.value; if (!isVersionedDeploymentEnabled) { diff --git a/workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-manager.ts b/workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-manager.ts index f36f8f72b4..e8e95286fd 100644 --- a/workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-manager.ts +++ b/workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-manager.ts @@ -92,6 +92,7 @@ import { TextEdit } from "vscode-languageclient"; import { downloadJavaFromMI, downloadMI, getProjectSetupDetails, getSupportedMIVersionsHigherThan, setPathsInWorkSpace, updateRuntimeVersionsInPom, getMIVersionFromPom } from '../../util/onboardingUtils'; import { extractCAppDependenciesAsProjects } from "../../visualizer/activate"; import { findMultiModuleProjectsInWorkspaceDir } from "../../util/migrationUtils"; +import { MILanguageClient } from "../../lang-client/activator"; Mustache.escape = escapeXml; @@ -145,7 +146,7 @@ export class MiVisualizerRpcManager implements MIVisualizerAPI { async getProjectStructure(params: ProjectStructureRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.getProjectStructure(this.projectUri); resolve(res); @@ -154,7 +155,7 @@ export class MiVisualizerRpcManager implements MIVisualizerAPI { async getProjectDetails(): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.getProjectDetails(); resolve(res); }); @@ -162,7 +163,7 @@ export class MiVisualizerRpcManager implements MIVisualizerAPI { async setDeployPlugin(params: MavenDeployPluginDetails): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.setDeployPlugin(params); await this.updatePom([res.textEdit]); resolve(res); @@ -171,7 +172,7 @@ export class MiVisualizerRpcManager implements MIVisualizerAPI { async getDeployPluginDetails(): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.getDeployPluginDetails(); resolve(res); }); @@ -179,7 +180,7 @@ export class MiVisualizerRpcManager implements MIVisualizerAPI { async removeDeployPlugin(): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.removeDeployPlugin(); if (res.range.start.line !== 0 && res.range.start.character !== 0) { await this.updatePom([res]); @@ -196,7 +197,7 @@ export class MiVisualizerRpcManager implements MIVisualizerAPI { */ async updateProperties(params: UpdatePropertiesRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.updateProperties(params); await this.updatePom(res.textEdits); resolve(true); @@ -212,7 +213,7 @@ export class MiVisualizerRpcManager implements MIVisualizerAPI { async reloadDependencies(params?: ReloadDependenciesRequest): Promise { return new Promise(async (resolve) => { let reloadDependenciesResult = true; - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const updateDependenciesResult = await langClient?.updateConnectorDependencies(); if (!updateDependenciesResult.toLowerCase().startsWith("success")) { const connectorsNotDownloaded: string[] = []; @@ -324,7 +325,7 @@ export class MiVisualizerRpcManager implements MIVisualizerAPI { async updateDependencies(params: UpdateDependenciesRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const projectDetails = await langClient.getProjectDetails(); const existingDependencies = projectDetails.dependencies || []; @@ -367,7 +368,7 @@ export class MiVisualizerRpcManager implements MIVisualizerAPI { async getDependencyStatusList(): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.getDependencyStatusList(); resolve(res); }); @@ -423,7 +424,7 @@ export class MiVisualizerRpcManager implements MIVisualizerAPI { async updateConnectorDependencies(): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.updateConnectorDependencies(); await extractCAppDependenciesAsProjects(this.projectUri); resolve(res); @@ -432,7 +433,7 @@ export class MiVisualizerRpcManager implements MIVisualizerAPI { async updateDependenciesFromOverview(params: UpdateDependenciesRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.updateDependencies({ dependencies: params.dependencies }); await this.updatePom(res.textEdits); resolve(true); @@ -775,7 +776,7 @@ export class MiVisualizerRpcManager implements MIVisualizerAPI { } async getProjectOverview(params: ProjectStructureRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const res = await langClient.getOverviewModel(); resolve(res); }); @@ -861,7 +862,7 @@ export class MiVisualizerRpcManager implements MIVisualizerAPI { async importOpenAPISpec(params: ImportOpenAPISpecRequest): Promise { const { filePath } = params; - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); if (filePath && filePath.length > 0) { const connectorGenRequest = { openAPIPath: filePath, @@ -903,7 +904,7 @@ export class MiVisualizerRpcManager implements MIVisualizerAPI { async updateAiDependencies(params: UpdateAiDependenciesRequest): Promise { return new Promise(async (resolve) => { - const langClient = getStateMachine(this.projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(this.projectUri); const projectDetails = await langClient.getProjectDetails(); const existingDependencies = projectDetails.dependencies || []; diff --git a/workspaces/mi/mi-extension/src/stateMachine.ts b/workspaces/mi/mi-extension/src/stateMachine.ts index 2d25d3f12e..7ef35f8349 100644 --- a/workspaces/mi/mi-extension/src/stateMachine.ts +++ b/workspaces/mi/mi-extension/src/stateMachine.ts @@ -14,7 +14,6 @@ import { VisualizerLocation, webviewReady } from '@wso2/mi-core'; -import { ExtendedLanguageClient } from './lang-client/ExtendedLanguageClient'; import { VisualizerWebview, webviews } from './visualizer/webview'; import { RPCLayer } from './RPCLayer'; import { history } from './history/activator'; @@ -34,7 +33,6 @@ import { containsMultiModuleNatureInProjectFile, containsMultiModuleNatureInPomF const fs = require('fs'); interface MachineContext extends VisualizerLocation { - langClient: ExtendedLanguageClient | null; dependenciesResolved?: boolean; isInWI: boolean; isLegacyRuntime?: boolean; @@ -46,7 +44,6 @@ const stateMachine = createMachine({ predictableActionArguments: true, context: { projectUri: "", - langClient: null, errors: [], view: MACHINE_VIEW.Welcome, dependenciesResolved: false, @@ -174,14 +171,12 @@ const stateMachine = createMachine({ target: 'ready', cond: (context, event) => context.displayOverview === true, actions: assign({ - langClient: (context, event) => event.data }) }, { target: 'ready.viewReady', cond: (context, event) => context.displayOverview === false, actions: assign({ - langClient: (context, event) => event.data, isLoading: (context, event) => false }) } @@ -461,7 +456,7 @@ const stateMachine = createMachine({ }, findView: (context, event): Promise => { return new Promise(async (resolve, reject) => { - const langClient = context.langClient!; + const langClient = await MILanguageClient.getInstance(context.projectUri!); const viewLocation = context; if (context.view === MACHINE_VIEW.IdpConnectorSchemaGeneratorForm) { @@ -601,7 +596,7 @@ const stateMachine = createMachine({ } } if (viewLocation.view === MACHINE_VIEW.ResourceView) { - const res = await langClient!.getDiagnostics({ documentUri: context.documentUri! }); + const res = await langClient.getDiagnostics({ documentUri: context.documentUri! }); if (res.diagnostics && res.diagnostics.length > 0) { viewLocation.diagnostics = res.diagnostics; } @@ -696,7 +691,6 @@ export const getStateMachine = (projectUri: string, context?: VisualizerLocation stateService = interpret(stateMachine.withContext({ projectUri: projectUri, - langClient: null, errors: [], view: MACHINE_VIEW.Overview, isInWI: vscode.extensions.getExtension(WI_EXTENSION_ID) ? true : false, diff --git a/workspaces/mi/mi-extension/src/util/schemaBuilder.ts b/workspaces/mi/mi-extension/src/util/schemaBuilder.ts index 7b4c58d1a9..b92175abdc 100644 --- a/workspaces/mi/mi-extension/src/util/schemaBuilder.ts +++ b/workspaces/mi/mi-extension/src/util/schemaBuilder.ts @@ -19,6 +19,7 @@ import { JSONSchema3or4 } from "to-json-schema"; import { getStateMachine } from "../stateMachine"; import { IOType } from "@wso2/mi-core"; +import { MILanguageClient } from "../lang-client/activator"; export function convertToJSONSchema(fileContent: JSONSchema3or4): JSONSchema3or4 { let schema = JSON.parse(fileContent); @@ -26,7 +27,7 @@ export function convertToJSONSchema(fileContent: JSONSchema3or4): JSONSchema3or4 } export async function generateSchema(ioType: IOType, schemaType: string, filePath: string, projectUri: string): Promise { - const langClient = getStateMachine(projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(projectUri); const response = await langClient.generateSchema({ filePath: filePath, delimiter: "", @@ -41,7 +42,7 @@ export async function generateSchema(ioType: IOType, schemaType: string, filePat } export async function generateSchemaFromContent(projectUri: string, ioType: IOType, content: string, fileType: string, csvDelimiter?: string): Promise { - const langClient = getStateMachine(projectUri).context().langClient!; + const langClient = await MILanguageClient.getInstance(projectUri); const response = await langClient.generateSchemaFromContent({ fileContent: content, delimiter: csvDelimiter ?? "", diff --git a/workspaces/mi/mi-extension/src/util/swagger.ts b/workspaces/mi/mi-extension/src/util/swagger.ts index fbdb406de3..66f893a72c 100644 --- a/workspaces/mi/mi-extension/src/util/swagger.ts +++ b/workspaces/mi/mi-extension/src/util/swagger.ts @@ -24,6 +24,7 @@ import path from "path"; import * as vscode from 'vscode'; import { deleteRegistryResource } from "./fileOperations"; import { getStateMachine } from "../stateMachine"; +import { MILanguageClient } from "../lang-client/activator"; const fs = require('fs'); @@ -296,7 +297,7 @@ export function generateSwagger(apiPath: string): Promise Date: Tue, 2 Dec 2025 13:19:37 +0530 Subject: [PATCH 10/13] Refactor: Update enableLSForProject to enableLS and return an array of disposables --- workspaces/mi/mi-extension/src/extension.ts | 4 +-- .../mi/mi-extension/src/util/workspace.ts | 32 +++++++++++++++---- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/workspaces/mi/mi-extension/src/extension.ts b/workspaces/mi/mi-extension/src/extension.ts index 411469affc..16b0c474dc 100644 --- a/workspaces/mi/mi-extension/src/extension.ts +++ b/workspaces/mi/mi-extension/src/extension.ts @@ -34,7 +34,7 @@ import { webviews } from './visualizer/webview'; import { v4 as uuidv4 } from 'uuid'; import path from 'path'; import { COMMANDS } from './constants'; -import { enableLSForProject } from './util/workspace'; +import { enableLS } from './util/workspace'; const os = require('os'); export async function activate(context: vscode.ExtensionContext) { @@ -95,7 +95,7 @@ export async function activate(context: vscode.ExtensionContext) { activateAiPanel(context); workspace.workspaceFolders?.forEach(folder => { - context.subscriptions.push(enableLSForProject(folder.uri.fsPath)); + context.subscriptions.push(...enableLS()); }); } diff --git a/workspaces/mi/mi-extension/src/util/workspace.ts b/workspaces/mi/mi-extension/src/util/workspace.ts index 4791dfaa24..2088642bce 100644 --- a/workspaces/mi/mi-extension/src/util/workspace.ts +++ b/workspaces/mi/mi-extension/src/util/workspace.ts @@ -134,25 +134,43 @@ export async function saveIdpSchemaToFile(folderPath: string, fileName: string, return true; } -export function enableLSForProject(projectUri: string): Disposable { - return window.onDidChangeActiveTextEditor(async (event) => { - const hasActiveWebview = webviews.has(projectUri); +export function enableLS(): Disposable[] { + const disposables: Disposable[] = []; - if (hasActiveWebview) { + const disposable1 = window.onDidChangeActiveTextEditor(async (event) => { + if (!event) { return; } - if (!event) { // No text editors - await MILanguageClient.stopInstance(projectUri); + const document = event.document; + const projectUri = workspace.getWorkspaceFolder(document.uri)?.uri.fsPath; + if (!projectUri) { return; } const hasActiveDocument = hasOpenedDocumentInProject(projectUri); if (hasActiveDocument) { await MILanguageClient.getInstance(projectUri); - } else { + } + }); + + const disposable2 = workspace.onDidCloseTextDocument(async (document) => { + const projectUri = workspace.getWorkspaceFolder(document.uri)?.uri.fsPath; + if (!projectUri) { + return; + } + const hasActiveWebview = webviews.has(projectUri); + + if (hasActiveWebview) { + return; + } + const hasActiveDocument = hasOpenedDocumentInProject(projectUri); + + if (!hasActiveDocument) { await MILanguageClient.stopInstance(projectUri); } }); + disposables.push(disposable1, disposable2); + return disposables; } export function hasOpenedDocumentInProject(projectUri: string): boolean { From ca2bafa3c4dd1849ea8e2f51e65faedff0d4a3eb Mon Sep 17 00:00:00 2001 From: gigara Date: Tue, 2 Dec 2025 13:31:48 +0530 Subject: [PATCH 11/13] Implement debounce mechanism for stopping language client instances --- .../mi-extension/src/lang-client/activator.ts | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/workspaces/mi/mi-extension/src/lang-client/activator.ts b/workspaces/mi/mi-extension/src/lang-client/activator.ts index 191d0e583e..8133a4410c 100644 --- a/workspaces/mi/mi-extension/src/lang-client/activator.ts +++ b/workspaces/mi/mi-extension/src/lang-client/activator.ts @@ -102,6 +102,9 @@ const versionRegex = /(\d+\.\d+\.?\d*)/g; export class MILanguageClient { private static _instances: Map = new Map(); private static lsChannelCache: Map = new Map(); + private static stopTimers: Map = new Map(); + private static stoppingInstances: Set = new Set(); + private static readonly STOP_DEBOUNCE_MS = 3000; // 30 seconds private languageClient: ExtendedLanguageClient | undefined; // eslint-disable-next-line @typescript-eslint/naming-convention @@ -111,6 +114,20 @@ export class MILanguageClient { constructor(private projectUri: string) { } public static async getInstance(projectUri: string): Promise { + // Cancel any pending stop operation for this project + const existingTimer = this.stopTimers.get(projectUri); + if (existingTimer) { + clearTimeout(existingTimer); + this.stopTimers.delete(projectUri); + } + + // If instance is currently stopping, wait for it to complete and create a new one + if (this.stoppingInstances.has(projectUri)) { + // Wait a bit for the stop operation to complete + await new Promise(resolve => setTimeout(resolve, 100)); + this.stoppingInstances.delete(projectUri); + } + if (!this._instances.has(projectUri)) { const instance = new MILanguageClient(projectUri); await instance.launch(projectUri); @@ -126,11 +143,25 @@ export class MILanguageClient { } public static async stopInstance(projectUri: string) { - const instance = this._instances.get(projectUri); - if (instance) { - await instance.stop(); - this._instances.delete(projectUri); + // Cancel any existing timer for this project + const existingTimer = this.stopTimers.get(projectUri); + if (existingTimer) { + clearTimeout(existingTimer); } + + // Schedule the stop operation with debounce + const timer = setTimeout(async () => { + this.stoppingInstances.add(projectUri); + const instance = this._instances.get(projectUri); + if (instance) { + await instance.stop(); + this._instances.delete(projectUri); + } + this.stopTimers.delete(projectUri); + this.stoppingInstances.delete(projectUri); + }, this.STOP_DEBOUNCE_MS); + + this.stopTimers.set(projectUri, timer); } public static async getAllInstances(): Promise { From 67362fa834c8de8d27875a37d24f1477870753b0 Mon Sep 17 00:00:00 2001 From: gigara Date: Tue, 2 Dec 2025 13:35:11 +0530 Subject: [PATCH 12/13] Refactor: Rename lsChannelCache to lsChannels for clarity --- workspaces/mi/mi-extension/src/lang-client/activator.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/workspaces/mi/mi-extension/src/lang-client/activator.ts b/workspaces/mi/mi-extension/src/lang-client/activator.ts index 8133a4410c..3831361d2c 100644 --- a/workspaces/mi/mi-extension/src/lang-client/activator.ts +++ b/workspaces/mi/mi-extension/src/lang-client/activator.ts @@ -101,7 +101,7 @@ const versionRegex = /(\d+\.\d+\.?\d*)/g; export class MILanguageClient { private static _instances: Map = new Map(); - private static lsChannelCache: Map = new Map(); + private static lsChannels: Map = new Map(); private static stopTimers: Map = new Map(); private static stoppingInstances: Set = new Set(); private static readonly STOP_DEBOUNCE_MS = 3000; // 30 seconds @@ -173,10 +173,10 @@ export class MILanguageClient { } public static getOrCreateOutputChannel(projectUri: string): vscode.OutputChannel { - let channel = this.lsChannelCache.get(projectUri); + let channel = this.lsChannels.get(projectUri); if (!channel) { channel = vscode.window.createOutputChannel(`Synapse Language Server - ${path.basename(projectUri)}`); - this.lsChannelCache.set(projectUri, channel); + this.lsChannels.set(projectUri, channel); } return channel; } From 905aaecfe04c319edaed9e3eda340cffc81f2b71 Mon Sep 17 00:00:00 2001 From: gigara Date: Tue, 2 Dec 2025 13:44:07 +0530 Subject: [PATCH 13/13] Fix: Increase STOP_DEBOUNCE_MS from 3000 to 30000 for improved stability --- workspaces/mi/mi-extension/src/lang-client/activator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/mi/mi-extension/src/lang-client/activator.ts b/workspaces/mi/mi-extension/src/lang-client/activator.ts index 3831361d2c..e193334d44 100644 --- a/workspaces/mi/mi-extension/src/lang-client/activator.ts +++ b/workspaces/mi/mi-extension/src/lang-client/activator.ts @@ -104,7 +104,7 @@ export class MILanguageClient { private static lsChannels: Map = new Map(); private static stopTimers: Map = new Map(); private static stoppingInstances: Set = new Set(); - private static readonly STOP_DEBOUNCE_MS = 3000; // 30 seconds + private static readonly STOP_DEBOUNCE_MS = 30000; // 30 seconds private languageClient: ExtendedLanguageClient | undefined; // eslint-disable-next-line @typescript-eslint/naming-convention