Skip to content

Commit ac33180

Browse files
Merge branch 'main' into bi-dm-e2e-test
2 parents e484f86 + 20f6d27 commit ac33180

File tree

18 files changed

+229
-137
lines changed

18 files changed

+229
-137
lines changed

workspaces/mi/mi-extension/src/debugger/debugHelper.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { serverLog, showServerOutputChannel } from '../util/serverLogger';
3838
import { getJavaHomeFromConfig, getServerPathFromConfig } from '../util/onboardingUtils';
3939
import * as crypto from 'crypto';
4040
import { Uri, workspace } from "vscode";
41+
import { MILanguageClient } from '../lang-client/activator';
4142

4243
const child_process = require('child_process');
4344
const findProcess = require('find-process');
@@ -422,7 +423,8 @@ export async function stopServer(projectUri: string, serverPath: string, isWindo
422423
export async function executeTasks(projectUri: string, serverPath: string, isDebug: boolean): Promise<void> {
423424
const maxTimeout = 120000;
424425
return new Promise<void>(async (resolve, reject) => {
425-
const isTerminated = await getStateMachine(projectUri).context().langClient?.shutdownTryoutServer();
426+
const langClient = await MILanguageClient.getInstance(projectUri);
427+
const isTerminated = await langClient.shutdownTryoutServer();
426428
if (!isTerminated) {
427429
reject('Failed to terminate the tryout server. Kill the server manually and try again.');
428430
}

workspaces/mi/mi-extension/src/debugger/debugger.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { webviews } from '../visualizer/webview';
2727
import { extension } from '../MIExtensionContext';
2828
import { reject } from 'lodash';
2929
import { LogLevel, logDebug } from '../util/logger';
30+
import { MILanguageClient } from '../lang-client/activator';
3031

3132
export interface RuntimeBreakpoint {
3233
id: number;
@@ -87,7 +88,7 @@ export class Debugger extends EventEmitter {
8788
return;
8889
}
8990
const projectUri = workspace.uri.fsPath;
90-
const langClient = getStateMachine(projectUri).context().langClient!;
91+
const langClient = await MILanguageClient.getInstance(this.projectUri);
9192
const breakpointPerFile: RuntimeBreakpoint[] = [];
9293
// To maintain the valid and invalid breakpoints in the vscode
9394
const vscodeBreakpointsPerFile: RuntimeBreakpoint[] = [];
@@ -191,7 +192,7 @@ export class Debugger extends EventEmitter {
191192
return;
192193
}
193194
const projectUri = workspace.uri.fsPath;
194-
const langClient = getStateMachine(projectUri).context().langClient!;
195+
const langClient = await MILanguageClient.getInstance(this.projectUri);
195196
const stepOverBreakpoints: RuntimeBreakpoint[] = [];
196197
if (path) {
197198
// create BreakpointPosition array
@@ -278,7 +279,7 @@ export class Debugger extends EventEmitter {
278279
throw new Error(`No workspace found for path: ${filePath}`);
279280
}
280281
const projectUri = workspace.uri.fsPath;
281-
const langClient = getStateMachine(projectUri).context().langClient!;
282+
const langClient = await MILanguageClient.getInstance(this.projectUri);
282283
// create BreakpointPosition[] array
283284
const breakpointPositions = breakpoints.map((breakpoint) => {
284285
return { line: breakpoint.line, column: breakpoint?.column };
@@ -296,7 +297,7 @@ export class Debugger extends EventEmitter {
296297

297298
public async getNextMediatorBreakpoint(): Promise<StepOverBreakpointResponse> {
298299
try {
299-
const langClient = getStateMachine(this.projectUri).context().langClient;
300+
const langClient = await MILanguageClient.getInstance(this.projectUri);
300301
if (!langClient) {
301302
throw new Error('Language client is not initialized');
302303
}

workspaces/mi/mi-extension/src/extension.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { webviews } from './visualizer/webview';
3434
import { v4 as uuidv4 } from 'uuid';
3535
import path from 'path';
3636
import { COMMANDS } from './constants';
37+
import { enableLS } from './util/workspace';
3738
const os = require('os');
3839

3940
export async function activate(context: vscode.ExtensionContext) {
@@ -92,12 +93,16 @@ export async function activate(context: vscode.ExtensionContext) {
9293
activateRuntimeService(context, firstProject);
9394
activateVisualizer(context, firstProject);
9495
activateAiPanel(context);
96+
97+
workspace.workspaceFolders?.forEach(folder => {
98+
context.subscriptions.push(...enableLS());
99+
});
95100
}
96101

97102
export async function deactivate(): Promise<void> {
98103
const clients = await MILanguageClient.getAllInstances();
99104
clients.forEach(async client => {
100-
await client?.languageClient?.stop();
105+
await client?.stop();
101106
});
102107

103108
// close all webviews

workspaces/mi/mi-extension/src/lang-client/activator.ts

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -101,30 +101,67 @@ const versionRegex = /(\d+\.\d+\.?\d*)/g;
101101

102102
export class MILanguageClient {
103103
private static _instances: Map<string, MILanguageClient> = new Map();
104-
private static lsChannelCache: Map<string, vscode.OutputChannel> = new Map();
105-
public languageClient: ExtendedLanguageClient | undefined;
104+
private static lsChannels: Map<string, vscode.OutputChannel> = new Map();
105+
private static stopTimers: Map<string, NodeJS.Timeout> = new Map();
106+
private static stoppingInstances: Set<string> = new Set();
107+
private static readonly STOP_DEBOUNCE_MS = 30000; // 30 seconds
108+
private languageClient: ExtendedLanguageClient | undefined;
106109

107110
// eslint-disable-next-line @typescript-eslint/naming-convention
108111
private COMPATIBLE_JDK_VERSION = "11"; // Minimum JDK version required to run the language server
109112
private _errorStack: ErrorType[] = [];
110113

111114
constructor(private projectUri: string) { }
112115

113-
public static async getInstance(projectUri: string): Promise<MILanguageClient> {
116+
public static async getInstance(projectUri: string): Promise<ExtendedLanguageClient> {
117+
// Cancel any pending stop operation for this project
118+
const existingTimer = this.stopTimers.get(projectUri);
119+
if (existingTimer) {
120+
clearTimeout(existingTimer);
121+
this.stopTimers.delete(projectUri);
122+
}
123+
124+
// If instance is currently stopping, wait for it to complete and create a new one
125+
if (this.stoppingInstances.has(projectUri)) {
126+
// Wait a bit for the stop operation to complete
127+
await new Promise(resolve => setTimeout(resolve, 100));
128+
this.stoppingInstances.delete(projectUri);
129+
}
130+
114131
if (!this._instances.has(projectUri)) {
115132
const instance = new MILanguageClient(projectUri);
116133
await instance.launch(projectUri);
117134
this._instances.set(projectUri, instance);
118135
}
119-
return this._instances.get(projectUri)!;
136+
const languageClient = this._instances.get(projectUri)!.languageClient;
137+
if (!languageClient) {
138+
const errorMessage = "Language client failed to initialize";
139+
window.showErrorMessage(errorMessage);
140+
throw new Error(errorMessage);
141+
}
142+
return languageClient;
120143
}
121144

122145
public static async stopInstance(projectUri: string) {
123-
const instance = this._instances.get(projectUri);
124-
if (instance) {
125-
await instance.stop();
126-
this._instances.delete(projectUri);
146+
// Cancel any existing timer for this project
147+
const existingTimer = this.stopTimers.get(projectUri);
148+
if (existingTimer) {
149+
clearTimeout(existingTimer);
127150
}
151+
152+
// Schedule the stop operation with debounce
153+
const timer = setTimeout(async () => {
154+
this.stoppingInstances.add(projectUri);
155+
const instance = this._instances.get(projectUri);
156+
if (instance) {
157+
await instance.stop();
158+
this._instances.delete(projectUri);
159+
}
160+
this.stopTimers.delete(projectUri);
161+
this.stoppingInstances.delete(projectUri);
162+
}, this.STOP_DEBOUNCE_MS);
163+
164+
this.stopTimers.set(projectUri, timer);
128165
}
129166

130167
public static async getAllInstances(): Promise<MILanguageClient[]> {
@@ -136,10 +173,10 @@ export class MILanguageClient {
136173
}
137174

138175
public static getOrCreateOutputChannel(projectUri: string): vscode.OutputChannel {
139-
let channel = this.lsChannelCache.get(projectUri);
176+
let channel = this.lsChannels.get(projectUri);
140177
if (!channel) {
141178
channel = vscode.window.createOutputChannel(`Synapse Language Server - ${path.basename(projectUri)}`);
142-
this.lsChannelCache.set(projectUri, channel);
179+
this.lsChannels.set(projectUri, channel);
143180
}
144181
return channel;
145182
}

workspaces/mi/mi-extension/src/project-explorer/activate.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,27 +37,28 @@ import { webviews } from '../visualizer/webview';
3737
import { MILanguageClient } from '../lang-client/activator';
3838

3939
let isProjectExplorerInitialized = false;
40-
export async function activateProjectExplorer(treeviewId: string, context: ExtensionContext, lsClient: ExtendedLanguageClient, isInWI: boolean) {
40+
export async function activateProjectExplorer(treeviewId: string, context: ExtensionContext, projectUri: string, isInWI: boolean) {
4141
if (isProjectExplorerInitialized) {
4242
return;
4343
}
4444
isProjectExplorerInitialized = true;
45+
const lsClient: ExtendedLanguageClient = await MILanguageClient.getInstance(projectUri);
4546

4647
const projectExplorerDataProvider = new ProjectExplorerEntryProvider(context);
4748
await projectExplorerDataProvider.refresh();
4849
let registryExplorerDataProvider;
4950
const projectTree = window.createTreeView(treeviewId, { treeDataProvider: projectExplorerDataProvider });
5051

51-
const projectDetailsRes = await lsClient?.getProjectDetails();
52+
const projectDetailsRes = await lsClient.getProjectDetails();
5253
const runtimeVersion = projectDetailsRes.primaryDetails.runtimeVersion.value;
5354
const isRegistrySupported = compareVersions(runtimeVersion, RUNTIME_VERSION_440) < 0;
5455

55-
commands.registerCommand(COMMANDS.REFRESH_COMMAND, () => {
56+
commands.registerCommand(COMMANDS.REFRESH_COMMAND, () => {
5657
if (isInWI) {
5758
commands.executeCommand(COMMANDS.WI_PROJECT_EXPLORER_VIEW_REFRESH);
5859
return;
5960
}
60-
return projectExplorerDataProvider.refresh();
61+
return projectExplorerDataProvider.refresh();
6162
});
6263

6364
commands.registerCommand(COMMANDS.ADD_ARTIFACT_COMMAND, (entry: ProjectExplorerEntry) => {
@@ -512,7 +513,7 @@ export async function activateProjectExplorer(treeviewId: string, context: Exten
512513
window.showErrorMessage('Cannot find workspace folder');
513514
return;
514515
}
515-
const langClient = getStateMachine(workspace.uri.fsPath).context().langClient;
516+
const langClient = await MILanguageClient.getInstance(workspace.uri.fsPath);
516517
if (!langClient) {
517518
window.showErrorMessage('Language client not found.');
518519
return;
@@ -571,7 +572,7 @@ export async function activateProjectExplorer(treeviewId: string, context: Exten
571572
if (filePath !== "") {
572573
const fileName = path.basename(filePath);
573574
const langClient = await MILanguageClient.getInstance(workspace.uri.fsPath);
574-
const fileUsageIdentifiers = await langClient?.languageClient?.getResourceUsages(filePath);
575+
const fileUsageIdentifiers = await langClient.getResourceUsages(filePath);
575576
const fileUsageMessage = fileUsageIdentifiers?.length && fileUsageIdentifiers?.length > 0 ? "It is used in:\n" + fileUsageIdentifiers.join(", ") : "No usage found";
576577
window.showInformationMessage("Do you want to delete : " + fileName + "\n\n" + fileUsageMessage, { modal: true }, "Yes")
577578
.then(async answer => {
@@ -659,7 +660,7 @@ export async function activateProjectExplorer(treeviewId: string, context: Exten
659660
window.showErrorMessage('Cannot find workspace folder');
660661
throw new Error('Cannot find workspace folder');
661662
}
662-
const langClient = getStateMachine(workspace.uri.fsPath).context().langClient;
663+
const langClient = await MILanguageClient.getInstance(workspace.uri.fsPath);
663664

664665
// Read the POM file
665666
const workspaceFolder = vscode.workspace.getWorkspaceFolder(Uri.file(filePath));

workspaces/mi/mi-extension/src/project-explorer/project-explorer-provider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,8 @@ async function getProjectStructureData(): Promise<ProjectExplorerEntry[]> {
145145
continue;
146146
}
147147
const langClient = await MILanguageClient.getInstance(rootPath);
148-
const resp = await langClient?.languageClient?.getProjectExplorerModel(rootPath);
149-
const projectDetailsRes = await langClient?.languageClient?.getProjectDetails();
148+
const resp = await langClient.getProjectExplorerModel(rootPath);
149+
const projectDetailsRes = await langClient.getProjectDetails();
150150
const runtimeVersion = projectDetailsRes.primaryDetails.runtimeVersion.value;
151151
const projectTree = await generateTreeData(workspace, resp, runtimeVersion);
152152

workspaces/mi/mi-extension/src/rpc-managers/ai-panel/rpc-manager.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import { codeDiagnostics } from "../../ai-panel/copilot/diagnostics/diagnostics"
5050
import { getLoginMethod } from '../../ai-panel/auth';
5151
import { LoginMethod } from '@wso2/mi-core';
5252
import { logInfo, logWarn, logError, logDebug } from '../../ai-panel/copilot/logger';
53+
import { MILanguageClient } from '../../lang-client/activator';
5354

5455
export class MIAIPanelRpcManager implements MIAIPanelAPI {
5556
private eventHandler: CopilotEventHandler;
@@ -374,13 +375,7 @@ export class MIAIPanelRpcManager implements MIAIPanelAPI {
374375
this.eventHandler.handleCodeDiagnosticStart(xmlCodes);
375376

376377
// Get diagnostics using existing RPC infrastructure
377-
const { getStateMachine } = await import('../../stateMachine');
378-
const stateMachine = getStateMachine(this.projectUri);
379-
if (!stateMachine) {
380-
throw new Error('State machine not found for project');
381-
}
382-
383-
const langClient = stateMachine.context().langClient;
378+
const langClient = await MILanguageClient.getInstance(this.projectUri);
384379
if (!langClient) {
385380
throw new Error('Language client not available');
386381
}

workspaces/mi/mi-extension/src/rpc-managers/mi-data-mapper/rpc-manager.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import { DM_OPERATORS_FILE_NAME, DM_OPERATORS_IMPORT_NAME, READONLY_MAPPING_FUNC
5757
import { readTSFile, removeMapFunctionEntry, showMappingEndNotification } from "../../util/ai-datamapper-utils";
5858
import { compareVersions } from "../../util/onboardingUtils";
5959
import { mapDataMapper } from "../../ai-panel/copilot/data-mapper/mapper";
60+
import { MILanguageClient } from "../../lang-client/activator";
6061

6162
const undoRedoManager = new UndoRedoManager();
6263

@@ -331,7 +332,7 @@ export class MiDataMapperRpcManager implements MIDataMapperAPI {
331332
const workspaceFolder = workspace.getWorkspaceFolder(Uri.file(filePath));
332333
let miDiagramRpcManager: MiDiagramRpcManager = new MiDiagramRpcManager(this.projectUri);
333334

334-
const langClient = getStateMachine(this.projectUri).context().langClient;
335+
const langClient = await MILanguageClient.getInstance(this.projectUri);
335336
const projectDetailsRes = await langClient?.getProjectDetails();
336337
const runtimeVersion = projectDetailsRes.primaryDetails.runtimeVersion.value;
337338
const isResourceContentUsed = compareVersions(runtimeVersion, RUNTIME_VERSION_440) >= 0;

workspaces/mi/mi-extension/src/rpc-managers/mi-debugger/rpc-manager.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,14 @@ import {
3333
} from "@wso2/mi-core";
3434
import * as vscode from "vscode";
3535
import { getStateMachine, refreshUI } from "../../stateMachine";
36+
import { MILanguageClient } from "../../lang-client/activator";
3637

3738
export class MiDebuggerRpcManager implements MiDebuggerAPI {
3839
constructor(private projectUri: string) { }
3940

4041
async validateBreakpoints(params: ValidateBreakpointsRequest): Promise<ValidateBreakpointsResponse> {
4142
return new Promise(async (resolve) => {
42-
const langClient = getStateMachine(this.projectUri).context().langClient!;
43+
const langClient = await MILanguageClient.getInstance(this.projectUri);
4344
const definition = await langClient.validateBreakpoints(params);
4445

4546
resolve(definition);
@@ -48,7 +49,7 @@ export class MiDebuggerRpcManager implements MiDebuggerAPI {
4849

4950
async getBreakpointInfo(params: GetBreakpointInfoRequest): Promise<GetBreakpointInfoResponse> {
5051
return new Promise(async (resolve) => {
51-
const langClient = getStateMachine(this.projectUri).context().langClient!;
52+
const langClient = await MILanguageClient.getInstance(this.projectUri);
5253
const breakpointInfo = await langClient.getBreakpointInfo(params);
5354

5455
resolve(breakpointInfo);
@@ -113,7 +114,7 @@ export class MiDebuggerRpcManager implements MiDebuggerAPI {
113114

114115
async getStepOverBreakpoint(params: StepOverBreakpointRequest): Promise<StepOverBreakpointResponse> {
115116
return new Promise(async (resolve) => {
116-
const langClient = getStateMachine(this.projectUri).context().langClient!;
117+
const langClient = await MILanguageClient.getInstance(this.projectUri);
117118
const breakpointInfo = await langClient.getStepOverBreakpoint(params);
118119

119120
resolve(breakpointInfo);

0 commit comments

Comments
 (0)