Skip to content

Conversation

@gigara
Copy link
Contributor

@gigara gigara commented Dec 1, 2025

Purpose

$subject
Resolves wso2/mi-vscode#1347

Goals

Describe the solutions that this feature/fix will introduce to resolve the problems described above

Approach

Describe how you are implementing the solutions. Include an animated GIF or screenshot if the change affects the UI (email [email protected] to review all UI text). Include a link to a Markdown file or Google doc if the feature write-up is too long to paste here.

UI Component Development

Specify the reason if following are not followed.

  • Added reusable UI components to the ui-toolkit. Follow the intructions when adding the componenent.
  • Use ui-toolkit components wherever possible. Run npm run storybook from the root directory to view current components.
  • Matches with the native VSCode look and feel.

Manage Icons

Specify the reason if following are not followed.

  • Added Icons to the font-wso2-vscode. Follow the instructions.

User stories

Summary of user stories addressed by this change>

Release note

Brief description of the new feature or bug fix as it will appear in the release notes

Documentation

Link(s) to product documentation that addresses the changes of this PR. If no doc impact, enter “N/A” plus brief explanation of why there’s no doc impact

Training

Link to the PR for changes to the training content in https://github.com/wso2/WSO2-Training, if applicable

Certification

Type “Sent” when you have provided new/updated certification questions, plus four answers for each question (correct answer highlighted in bold), based on this change. Certification questions/answers should be sent to [email protected] and NOT pasted in this PR. If there is no impact on certification exams, type “N/A” and explain why.

Marketing

Link to drafts of marketing content that will describe and promote this feature, including product page changes, technical articles, blog posts, videos, etc., if applicable

Automation tests

  • Unit tests

    Code coverage information

  • Integration tests

    Details about the test cases and coverage

Security checks

Samples

Provide high-level details about the samples related to this feature

Related PRs

List any other related PRs

Migrations (if applicable)

Describe migration steps and platforms on which migration has been tested

Test environment

List all JDK versions, operating systems, databases, and browser/versions on which this feature/fix was tested

Learning

Describe the research phase and any blog posts, patterns, libraries, or add-ons you used to solve the problem.

Summary by CodeRabbit

  • Refactor

    • Language services now start/stop per workspace with debounce and smarter lifecycle handling to avoid premature shutdowns.
    • Project-specific language client access unified across explorer, debugger, RPCs, visualizer and generators for more reliable, awaited initialization.
  • Bug Fixes

    • Saving artifacts now cleans up old files, creates missing folders and waits for readiness before refresh, improving save and cleanup reliability.

✏️ Tip: You can customize this high-level summary in your review settings.

@gigara gigara requested a review from hevayo as a code owner December 1, 2025 16:11
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 1, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Registers per-project lifecycle listeners that start/stop MILanguageClient based on open editors and visualizer presence; removes stored langClient from MachineContext and replaces state-machine–provided client usage with awaited MILanguageClient.getInstance(projectUri) across many modules; activates project explorer by projectUri.

Changes

Cohort / File(s) Summary
Extension activation
workspaces/mi/mi-extension/src/extension.ts
Invokes enableLS() for workspace folders during activation to wire per-project LS lifecycle listeners.
Workspace utilities
workspaces/mi/mi-extension/src/util/workspace.ts
Adds enableLS() and hasOpenedDocumentInProject(projectUri: string); adds file-creation retry logic and artifact cleanup; manages MILanguageClient start/stop based on editor/webview activity.
Visualizer / Webview
workspaces/mi/mi-extension/src/visualizer/webview.ts
Imports hasOpenedDocumentInProject and conditions MILanguageClient.stopInstance(projectUri) on absence of open project documents when disposing panels.
Language client activator
workspaces/mi/mi-extension/src/lang-client/activator.ts
Converts getInstance to async Promise<ExtendedLanguageClient>; adds debounced stopInstance (stop timers, stoppingInstances) and per-project output channel map.
State machine
workspaces/mi/mi-extension/src/stateMachine.ts
Removes langClient from MachineContext and replaces stored-client usage with MILanguageClient.getInstance(context.projectUri!); passes projectUri into activation flows.
Project explorer activation & provider
workspaces/mi/mi-extension/src/project-explorer/activate.ts, workspaces/mi/mi-extension/src/project-explorer/project-explorer-provider.ts
activateProjectExplorer now accepts projectUri: string and resolves MILanguageClient.getInstance(projectUri) internally; provider calls moved to direct MILanguageClient methods.
RPC managers & consumers
workspaces/mi/mi-extension/src/rpc-managers/..., src/debugger/*, src/schemaBuilder.ts, src/util/swagger.ts, src/test-explorer/activator.ts, src/visualizer/activate.ts, src/project-explorer/*
Replaces many usages of getStateMachine(...).context().langClient with await MILanguageClient.getInstance(projectUri) (or direct MILanguageClient.getInstance(projectUri)), changing synchronous access to awaited singleton-access patterns and invoking client methods directly.
Debugger & helpers
workspaces/mi/mi-extension/src/debugger/debugHelper.ts, workspaces/mi/mi-extension/src/debugger/debugger.ts
Resolve language client via MILanguageClient.getInstance(projectUri) and call client methods (e.g., shutdownTryoutServer, validate/get breakpoint info) on returned instance.

Sequence Diagram(s)

sequenceDiagram
    participant Editor as VSCode Editor / Tabs
    participant LSReg as enableLS
    participant Checker as hasOpenedDocumentInProject
    participant Webview as Visualizer Panel
    participant LC as MILanguageClient

    Editor->>LSReg: onDidChangeActiveTextEditor(event)
    LSReg->>Checker: hasOpenedDocumentInProject(projectUri)
    alt document exists
        Checker-->>LSReg: true
        LSReg->>LC: MILanguageClient.getInstance(projectUri)
        LC-->>LSReg: start / return instance
    else no document
        Checker-->>LSReg: false
        LSReg->>LC: stopInstance(projectUri) (debounced)
    end

    Webview->>Checker: onDispose(projectUri)
    alt document exists
        Checker-->>Webview: true
        Webview-->>LC: keep running
    else none
        Checker-->>Webview: false
        Webview->>LC: stopInstance(projectUri)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Inspect lifecycle/race conditions in util/workspace.ts (listeners, WorkspaceEdit retry).
  • Verify debounced stop logic and concurrency in lang-client/activator.ts (stopTimers, stoppingInstances).
  • Confirm all updated call sites handle awaited getInstance correctly and that activateProjectExplorer signature changes are propagated.

Possibly related PRs

Suggested reviewers

  • hevayo

Poem

🐰 I hopped through code and tabs tonight,

Started servers only when the view was bright,
If a doc stays open, I keep them warm,
Close the last tab — I gently calm the swarm,
Tiny paws, keeping language servers light.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is largely incomplete, containing only a Purpose section with a placeholder and an issue link, while all other required template sections (Goals, Approach, Release note, etc.) are empty or contain template placeholder text. Fill in all required sections: Goals describing the solutions, Approach detailing implementation, Release note, Documentation impact, and other applicable sections from the template.
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'FIx LS not working when webview closed issue' contains a typo ('FIx' instead of 'Fix') and is grammatically awkward with 'issue' at the end, making it unclear and unprofessional. Correct the typo to 'Fix' and rephrase to 'Fix language server not working when webview is closed' for clarity and professionalism.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The code changes successfully address the linked issue #1347 by introducing lifecycle management for the language server that remains active even when webview tabs are closed, enabling Synapse suggestions in the text editor.
Out of Scope Changes check ✅ Passed All code changes are directly scoped to fixing the language server lifecycle issue: refactoring language client access patterns, introducing per-project debouncing, and implementing listener-based activation tied to document/webview state.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a333bcf and 2a565f5.

📒 Files selected for processing (5)
  • workspaces/mi/mi-extension/src/extension.ts (2 hunks)
  • workspaces/mi/mi-extension/src/project-explorer/activate.ts (2 hunks)
  • workspaces/mi/mi-extension/src/stateMachine.ts (1 hunks)
  • workspaces/mi/mi-extension/src/util/workspace.ts (4 hunks)
  • workspaces/mi/mi-extension/src/visualizer/webview.ts (3 hunks)
🧰 Additional context used
🧠 Learnings (10)
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/**/*.{ts,tsx} : Use TypeScript 5.8.3 with strict type checking enabled for all source files

Applied to files:

  • workspaces/mi/mi-extension/src/extension.ts
  • workspaces/mi/mi-extension/src/util/workspace.ts
📚 Learning: 2025-11-10T15:05:11.309Z
Learnt from: madushajg
Repo: wso2/vscode-extensions PR: 868
File: workspaces/bi/bi-extension/src/utils.ts:224-242
Timestamp: 2025-11-10T15:05:11.309Z
Learning: The workspaces/bi/bi-extension and workspaces/ballerina/ballerina-extension are separate VS Code extensions that are packaged and distributed independently, so they cannot share code via imports and must maintain their own implementations of common utilities.

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/**/*.{ts,tsx} : Define all constants (node types, sizing, spacing) in src/resources/constants.ts and import them where needed instead of hardcoding values

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
📚 Learning: 2025-11-26T06:35:19.217Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:173-178
Timestamp: 2025-11-26T06:35:19.217Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the commented-out debugging block in the verifyFileContent function (lines 172-177 containing console.log, page.pause, and updateDataFileSync) is intentionally kept as a developer utility for updating test data files when needed. It should not be removed.

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
  • workspaces/mi/mi-extension/src/visualizer/webview.ts
📚 Learning: 2025-11-26T06:34:09.752Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:128-134
Timestamp: 2025-11-26T06:34:09.752Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the goPrevViewBreadcrumb() method is only called when in a focused view, ensuring breadcrumbs are always present. No guard for empty breadcrumbs is needed.

Applied to files:

  • workspaces/mi/mi-extension/src/visualizer/webview.ts
📚 Learning: 2025-11-26T07:49:56.428Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:136-141
Timestamp: 2025-11-26T07:49:56.428Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the goPrevViewBackButton() method is only called when in a focused view, ensuring breadcrumbs are always present. No guard for empty breadcrumbs is needed.

Applied to files:

  • workspaces/mi/mi-extension/src/visualizer/webview.ts
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/index.ts : Export the main Diagram component as the default export from the package entry point (index.ts)

Applied to files:

  • workspaces/mi/mi-extension/src/visualizer/webview.ts
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/(Diagram|DiagramCanvas|Controls)*.tsx : Store diagram zoom and offset positions in state; optionally cache them in localStorage for session persistence

Applied to files:

  • workspaces/mi/mi-extension/src/visualizer/webview.ts
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/Diagram.tsx : Debounce diagram repaints to avoid excessive re-renders when state changes occur

Applied to files:

  • workspaces/mi/mi-extension/src/visualizer/webview.ts
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/DiagramContext.tsx : Use DiagramContext (React Context API) for state management across the diagram component, including expandedNodes and graphQLGroupOpen state

Applied to files:

  • workspaces/mi/mi-extension/src/visualizer/webview.ts
🧬 Code graph analysis (3)
workspaces/mi/mi-extension/src/project-explorer/activate.ts (2)
workspaces/bi/bi-extension/src/project-explorer/activate.ts (1)
  • activateProjectExplorer (34-62)
workspaces/mi/mi-extension/src/lang-client/ExtendedLanguageClient.ts (1)
  • ExtendedLanguageClient (160-474)
workspaces/mi/mi-extension/src/extension.ts (1)
workspaces/mi/mi-extension/src/util/workspace.ts (1)
  • enableLSForWorkspace (138-161)
workspaces/mi/mi-extension/src/visualizer/webview.ts (1)
workspaces/mi/mi-extension/src/util/workspace.ts (1)
  • hasActiveDocumentInWorkspace (163-173)
🔇 Additional comments (5)
workspaces/mi/mi-extension/src/extension.ts (1)

97-99: LGTM: Per-workspace LS enablement.

The loop correctly enables language services for each workspace folder, aligning with the PR objective to fix LS availability when webviews are closed.

workspaces/mi/mi-extension/src/visualizer/webview.ts (1)

281-285: LGTM: Conditional LS lifecycle management.

This change directly addresses the reported issue. By checking for active documents before stopping the language server, the LS now remains available when switching between diagram and source views.

workspaces/mi/mi-extension/src/project-explorer/activate.ts (1)

45-46: LGTM: Refactored to derive language client internally.

The signature change aligns with workspace-aware activation. The code properly awaits getInstance and derives the language client.

workspaces/mi/mi-extension/src/util/workspace.ts (1)

163-173: LGTM: Clean implementation of active document check.

The function correctly iterates through all tab groups and checks for text documents in the artifacts path. This utility properly supports the workspace-aware LS lifecycle management.

workspaces/mi/mi-extension/src/stateMachine.ts (1)

662-662: Verify projectUri is always defined in ready.activateOtherFeatures state.

The non-null assertion (context.projectUri!) assumes projectUri is set before reaching this state. While the state machine flow likely ensures this, consider adding a guard or type refinement for safety.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
workspaces/mi/mi-extension/src/util/workspace.ts (1)

95-136: Tighten up saveIdpSchemaToFile edge cases: file content guard and validation ordering for data safety

Two concerns worth addressing:

  1. Empty schema content behavior is ambiguous
    The if (fileContent) guard treats empty string the same as undefined, skipping the write. If clearing schema JSON to an empty string is valid, use fileContent !== undefined instead:
-    if (fileContent) {
+    if (fileContent !== undefined) {
         fs.writeFileSync(documentUri, fileContent, "utf-8");
     }
  1. File deletion happens before validation—risking data loss
    The code deletes existing image/PDF files (lines 105–112) before validating the base64 string and MIME type. If imageOrPdf is malformed or has an unsupported type, files are already lost when the error is returned.

    Reorder to validate first, then delete only if the new data is valid:

     if (imageOrPdf) {
+        const mimeTypeMatch = imageOrPdf.match(/^data:(.*?);base64,/);
+        if (mimeTypeMatch) {
+            const mimeType = mimeTypeMatch[1];
+            let extension = "";
+            if (mimeType === "application/pdf") {
+                extension = ".pdf";
+            } else if (mimeType.startsWith("image/")) {
+                extension = mimeType.split("/")[1];
+                extension = `.${extension}`;
+            } else {
+                console.error("Unsupported MIME type:", mimeType);
+                return false;
+            }
+            const base64Data = imageOrPdf.replace(/^data:.*;base64,/, "");
+            const binaryData = Buffer.from(base64Data, "base64");
+            const filePath = path.join(folderPath, fileName + extension);
         const extensionsToDelete = [".pdf", ".jpg", ".jpeg", ".png", ".gif", ".webp"];
         const filesInFolder = await fs.promises.readdir(folderPath);
         for (const file of filesInFolder) {
             const ext = path.extname(file).toLowerCase();
             if (extensionsToDelete.includes(ext)) {
                 await fs.promises.unlink(path.join(folderPath, file));
             }
         }
-        const mimeTypeMatch = imageOrPdf.match(/^data:(.*?);base64,/);
-        if (mimeTypeMatch) {
-            const mimeType = mimeTypeMatch[1];
-            let extension = "";
-            if (mimeType === "application/pdf") {
-                extension = ".pdf";
-            } else if (mimeType.startsWith("image/")) {
-                extension = mimeType.split("/")[1];
-                extension = `.${extension}`;
-            } else {
-                console.error("Unsupported MIME type:", mimeType);
-                return false;
-            }
-            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;
         }
     }

This ensures existing assets remain intact if the new data fails validation.

🧹 Nitpick comments (2)
workspaces/mi/mi-extension/src/util/workspace.ts (2)

19-26: Consolidate vscode imports and avoid namespace + named style mix

You’re importing both specific symbols and the vscode namespace. Since window and TabInputText are the only reasons to use the namespace, you can keep everything consistent with a single named import and drop the namespace import:

-import { Position, Range, Uri, WorkspaceEdit, commands, workspace, window } from "vscode";
+import { Position, Range, Uri, WorkspaceEdit, commands, workspace, window, TabInputText } from "vscode";
@@
-import * as vscode from 'vscode';
 import { webviews } from "../visualizer/webview";
@@
-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;
-}
+export function hasActiveDocumentInWorkspace(workspacePath: string): boolean {
+    const artifactsPath = path.join(workspacePath, "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)) {
+                return true;
+            }
+        }
+    }
+    return false;
+}

Also note this file now depends on webviews while visualizer/webview.ts depends on hasActiveDocumentInWorkspace, which introduces a circular dependency. It’s probably fine given both sides only use exported values in callbacks, but keep this in mind if you ever see initialization‑order issues; refactoring the shared LS state into a lighter‑weight module would break the cycle.

Please confirm this still compiles and runs against your targeted VS Code engine (i.e., TabInputText is available as a runtime export) and that there are no circular‑init warnings at extension activation.


138-157: LS lifecycle logic now matches desired behavior; consider keeping the Disposable

This handler now:

  • Ignores events while a webview is active (webviews.has(workspacePath)).
  • Stops the LS when there are no text editors (!event).
  • Otherwise uses hasActiveDocumentInWorkspace(workspacePath) to decide whether to start or stop the LS, independent of which file is currently active.

That fixes the earlier issue where switching away from artifacts or closing the diagram could leave the LS running or stopped incorrectly, and should address the “no suggestions after closing diagram tab” scenario.

One small robustness improvement would be to keep and dispose the onDidChangeActiveTextEditor subscription when the workspace is torn down (e.g., from your activation/deactivation plumbing), to avoid accidental double‑subscriptions in future refactors:

export function enableLSForWorkspace(workspacePath: string): vscode.Disposable {
    return window.onDidChangeActiveTextEditor(async (event) => {
        // existing body...
    });
}

Please verify manually (or via MI UI tests) that:

  1. Synapse suggestions remain available in the source view after closing the diagram tab, and
  2. The LS stops once all MI artifacts editors and MI webviews for a workspace are closed.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a565f5 and 9b3467f.

📒 Files selected for processing (1)
  • workspaces/mi/mi-extension/src/util/workspace.ts (4 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-11-10T15:05:11.309Z
Learnt from: madushajg
Repo: wso2/vscode-extensions PR: 868
File: workspaces/bi/bi-extension/src/utils.ts:224-242
Timestamp: 2025-11-10T15:05:11.309Z
Learning: The workspaces/bi/bi-extension and workspaces/ballerina/ballerina-extension are separate VS Code extensions that are packaged and distributed independently, so they cannot share code via imports and must maintain their own implementations of common utilities.

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
📚 Learning: 2025-11-26T06:35:19.217Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:173-178
Timestamp: 2025-11-26T06:35:19.217Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the commented-out debugging block in the verifyFileContent function (lines 172-177 containing console.log, page.pause, and updateDataFileSync) is intentionally kept as a developer utility for updating test data files when needed. It should not be removed.

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/**/*.{ts,tsx} : Define all constants (node types, sizing, spacing) in src/resources/constants.ts and import them where needed instead of hardcoding values

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/**/*.{ts,tsx} : Use TypeScript 5.8.3 with strict type checking enabled for all source files

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
🧬 Code graph analysis (1)
workspaces/mi/mi-extension/src/util/workspace.ts (1)
workspaces/mi/mi-extension/src/visualizer/webview.ts (1)
  • webviews (34-34)
🔇 Additional comments (2)
workspaces/mi/mi-extension/src/util/workspace.ts (2)

47-65: Retry loop after file creation is bounded and reasonable

The access‑retry loop after creating a new file is bounded (maxRetries = 5, retryDelay = 100) and will at worst wait ~500 ms, which is acceptable for extension flows like this. No changes needed here from a correctness perspective.

Please re‑check any flows that rely on replaceFullContentToFile (especially on slower disks / network filesystems) to ensure the file is reliably available before the refresh command runs.


159-169: hasActiveDocumentInWorkspace logic looks correct; double‑check multi‑root and path normalization

The tab‑based scan for any src/main/wso2mi/artifacts document:

  • Correctly checks all tab groups and only considers TabInputText tabs.
  • Uses a simple startsWith(artifactsPath) prefix to bind tabs to the workspace.

Two things to keep an eye on:

  • Ensure workspacePath passed here is exactly workspaceFolder.uri.fsPath so that the computed artifactsPath matches the actual on‑disk paths on all OSes.
  • In multi‑root workspaces, you’ll be calling this once per workspace folder; the current implementation should behave well there, but it’s worth testing with MI projects in multiple folders.

Please test this helper in a multi‑root workspace (two MI projects open) and on at least one Windows environment to confirm the startsWith(artifactsPath) check behaves as expected with OS‑specific path casing and separators.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes an issue where the language server (LS) stops working when the webview is closed. The solution implements automatic language server lifecycle management based on open documents and webviews, ensuring the LS remains active when relevant files are still open even after closing the visualizer webview.

Key Changes:

  • Language server now only stops on webview disposal if no project documents remain open
  • New event-based system enables/disables language server per workspace based on active editor changes
  • Project explorer initialization refactored to fetch language client internally rather than receiving it as a parameter

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
workspaces/mi/mi-extension/src/visualizer/webview.ts Conditionally stops language server on disposal only when no active documents remain in project
workspaces/mi/mi-extension/src/util/workspace.ts Adds enableLSForProject to manage LS lifecycle via editor change events and hasActiveDocumentInProject helper to check for open project documents
workspaces/mi/mi-extension/src/stateMachine.ts Passes project URI instead of language client to project explorer activation
workspaces/mi/mi-extension/src/project-explorer/activate.ts Accepts project URI parameter and fetches language client internally
workspaces/mi/mi-extension/src/extension.ts Enables LS management for all workspace folders on activation
Comments suppressed due to low confidence (1)

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

  • Avoid automated semicolon insertion (98% of all statements in the enclosing function have an explicit semicolon).
	})

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@gigara gigara removed the Checks/Run MI UI Tests Force run MI UI tests label Dec 2, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
workspaces/mi/mi-extension/src/util/workspace.ts (1)

19-19: Per‑project LS lifecycle logic is correct; consider returning and tracking the Disposable

The new enableLSForProject + hasOpenedDocumentInProject pairing looks logically solid:

  • Skips LS decisions while a project visualizer webview is open via webviews.has(projectUri).
  • Stops the LS when there are no text editors (!event), and otherwise bases start/stop solely on whether any artifacts tab from that project is open (hasOpenedDocumentInProject scanning window.tabGroups with TabInputText), which means the LS stays alive even when the active editor is elsewhere as long as an artifacts tab exists.
  • This aligns with the fix in VisualizerWebview.dispose, so closing the diagram alone no longer stops the LS while source tabs are open.

Two non‑blocking improvements:

  1. Return and track the Disposable (already raised in an earlier review): window.onDidChangeActiveTextEditor returns a Disposable. If enableLSForProject can be called more than once for a given project (e.g., dynamic workspace folder changes, re‑activation), you should return that disposable and push it into context.subscriptions to avoid accumulating listeners.

  • export function enableLSForProject(projectUri: string): void {
  • window.onDidChangeActiveTextEditor(async (event) => {
  • export function enableLSForProject(projectUri: string): vscode.Disposable {
  • return window.onDidChangeActiveTextEditor(async (event) => {
    // ...
    });
    }

2. **Slight behaviour note:** because `hasOpenedDocumentInProject` checks all tab groups, the LS will remain running while any artifacts tab for that project is open, even if the active editor is another project or a non‑artifacts file. That seems desirable for this MI use‑case, but just be aware this differs from “active editor only” semantics.







Also applies to: 24-24, 137-168

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>workspaces/mi/mi-extension/src/util/workspace.ts (1)</summary><blockquote>

`94-135`: **IdP schema save helper logic is sound; consider guarding the “no content” case**

The updated `saveIdpSchemaToFile` correctly:
- Ensures the folder exists before writes.
- Writes the JSON schema when `fileContent` is provided.
- Cleans up previous artifacts and writes the decoded image/PDF when `imageOrPdf` is provided.
- Returns `false` for unsupported MIME types or invalid base64, which is useful to callers.

One optional edge‑case improvement: if both `fileContent` and `imageOrPdf` are omitted, the function currently does nothing and still returns `true`. If that scenario is possible, you might want to fail fast instead:

```diff
export async function saveIdpSchemaToFile(folderPath: string, fileName: string, fileContent?: string, imageOrPdf?: string): Promise<boolean> {
  const documentUri = path.join(folderPath, fileName + ".json");
  if (!fs.existsSync(folderPath)) {
      fs.mkdirSync(folderPath, { recursive: true });
  }
+    if (!fileContent && !imageOrPdf) {
+        console.warn("saveIdpSchemaToFile called without content or imageOrPdf");
+        return false;
+    }
  if (fileContent) {
      fs.writeFileSync(documentUri, fileContent, 'utf-8');
  }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b5ba1bc and d7db138.

📒 Files selected for processing (2)
  • workspaces/mi/mi-extension/src/util/workspace.ts (4 hunks)
  • workspaces/mi/mi-extension/src/visualizer/webview.ts (3 hunks)
🧰 Additional context used
🧠 Learnings (12)
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/index.ts : Export the main Diagram component as the default export from the package entry point (index.ts)

Applied to files:

  • workspaces/mi/mi-extension/src/visualizer/webview.ts
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/(Diagram|DiagramCanvas|Controls)*.tsx : Store diagram zoom and offset positions in state; optionally cache them in localStorage for session persistence

Applied to files:

  • workspaces/mi/mi-extension/src/visualizer/webview.ts
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/DiagramContext.tsx : Use DiagramContext (React Context API) for state management across the diagram component, including expandedNodes and graphQLGroupOpen state

Applied to files:

  • workspaces/mi/mi-extension/src/visualizer/webview.ts
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/Diagram.tsx : Debounce diagram repaints to avoid excessive re-renders when state changes occur

Applied to files:

  • workspaces/mi/mi-extension/src/visualizer/webview.ts
📚 Learning: 2025-11-26T06:35:19.217Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:173-178
Timestamp: 2025-11-26T06:35:19.217Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the commented-out debugging block in the verifyFileContent function (lines 172-177 containing console.log, page.pause, and updateDataFileSync) is intentionally kept as a developer utility for updating test data files when needed. It should not be removed.

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
📚 Learning: 2025-11-10T15:05:11.309Z
Learnt from: madushajg
Repo: wso2/vscode-extensions PR: 868
File: workspaces/bi/bi-extension/src/utils.ts:224-242
Timestamp: 2025-11-10T15:05:11.309Z
Learning: The workspaces/bi/bi-extension and workspaces/ballerina/ballerina-extension are separate VS Code extensions that are packaged and distributed independently, so they cannot share code via imports and must maintain their own implementations of common utilities.

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/**/*.{ts,tsx} : Define all constants (node types, sizing, spacing) in src/resources/constants.ts and import them where needed instead of hardcoding values

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/**/*.{ts,tsx} : Use TypeScript 5.8.3 with strict type checking enabled for all source files

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
📚 Learning: 2025-11-05T10:31:47.583Z
Learnt from: madushajg
Repo: wso2/vscode-extensions PR: 830
File: workspaces/ballerina/ballerina-extension/test/ai/post_proccess/post.test.ts:0-0
Timestamp: 2025-11-05T10:31:47.583Z
Learning: In the workspaces/ballerina/ballerina-extension project, the tsconfig.json uses "rootDirs": ["src", "test"], which allows test files to import from src using shorter relative paths. For example, from test/ai/post_proccess/, the import '../../stateMachine' correctly resolves to src/stateMachine.ts due to this configuration.

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/**/*.config.{js,ts} : Use minimatch-compatible glob patterns for file matching in build and test configuration files

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/resources/icons/**/*.tsx : Create separate SVG icon components in src/resources/icons/ for all diagram icons and import them as React components

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
📚 Learning: 2025-11-10T15:04:50.474Z
Learnt from: madushajg
Repo: wso2/vscode-extensions PR: 868
File: workspaces/bi/bi-extension/src/utils.ts:153-169
Timestamp: 2025-11-10T15:04:50.474Z
Learning: The workspaces/bi/bi-extension and workspaces/ballerina/ballerina-extension are separate, independently deployable VSCode extensions in the wso2/vscode-extensions repository. Code duplication between these extensions is acceptable and often necessary to maintain their independence, rather than creating cross-extension dependencies.

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
🧬 Code graph analysis (2)
workspaces/mi/mi-extension/src/visualizer/webview.ts (1)
workspaces/mi/mi-extension/src/util/workspace.ts (1)
  • hasOpenedDocumentInProject (158-168)
workspaces/mi/mi-extension/src/util/workspace.ts (2)
workspaces/bi/bi-extension/src/biExtentionContext.ts (1)
  • extension (32-32)
workspaces/mi/mi-extension/src/visualizer/webview.ts (1)
  • webviews (34-34)
🔇 Additional comments (2)
workspaces/mi/mi-extension/src/visualizer/webview.ts (1)

32-32: Guarded LS shutdown on webview dispose correctly respects open project documents

Using hasOpenedDocumentInProject(this.projectUri) before calling MILanguageClient.stopInstance avoids tearing down the language server when closing the visualizer while artifacts are still open in text editors, which directly addresses the reported “no suggestions after closing diagram” issue. The check is cheap and consistent with the new per‑project LS lifecycle in enableLSForProject.

Also applies to: 281-285

workspaces/mi/mi-extension/src/util/workspace.ts (1)

49-63: File readiness retry loop is bounded and reasonable

The retry loop after creating a new file (maxRetries = 5, retryDelay = 100) gives the FS up to ~500 ms to settle before logging a warning and proceeding, which is a good balance between robustness and not blocking the extension thread for too long. No changes needed here.

@gigara gigara added the Checks/Run MI UI Tests Force run MI UI tests label Dec 2, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
workspaces/mi/mi-extension/src/debugger/debugger.ts (1)

187-273: createStepOverBreakpoint swallows errors due to using lodash reject instead of rejecting the Promise

In the catch of createStepOverBreakpoint, you call reject(\${error}`), which is the lodash rejectimport, not the Promise reject function. This silently discards the error and resolves theasync` function instead of propagating the failure to callers.

Consider changing the catch block to actually reject/throw, and then removing the lodash reject import:

-    public async createStepOverBreakpoint(path: string, breakpoints: DebugProtocol.SourceBreakpoint[]): Promise<void> {
-        try {
+    public async createStepOverBreakpoint(path: string, breakpoints: DebugProtocol.SourceBreakpoint[]): Promise<void> {
+        try {
             // ... existing logic ...
-        } catch (error) {
-            reject(`${error}`);
-        }
+        } catch (error) {
+            logDebug(`Error creating step-over breakpoints: ${error}`, LogLevel.ERROR);
+            throw error;
+        }
     }

And at the top of the file:

-import { reject } from 'lodash';

This ensures debugger failures are visible to callers instead of being silently ignored.

🧹 Nitpick comments (11)
workspaces/mi/mi-extension/src/test-explorer/activator.ts (3)

99-110: MILanguageClient usage for syntax trees is consistent; consider tightening project-root handling and DRY-ing

Using await MILanguageClient.getInstance(...).getSyntaxTree(...) in the EDIT/DELETE test case handlers and in getTestCases() is aligned with the new architecture and should keep the LS available regardless of webview state. You now have three very similar call sites (edit, delete, and discovery) that all:

  • Derive a project root from a URI.
  • Call MILanguageClient.getInstance(projectRoot!).
  • Immediately request a syntax tree for a given XML file.

To reduce future edge-case bugs and make behavior consistent:

  • Consider centralizing this into a small helper like getUnitTestSyntaxTree(uri: Uri | string) that:
    • Resolves projectRoot and returns early if it’s missing.
    • Wraps getInstance/getSyntaxTree in a try/catch and maps failures to the user-facing “Syntax tree is not available” message.
  • This will avoid repeating the projectRoot! pattern and keep error handling behavior uniform across edit/delete/list flows.

Also applies to: 160-176, 395-405


271-279: Avoid calling MILanguageClient.getInstance when projectRoot is falsy in createTests

Right now createTests() calls MILanguageClient.getInstance(projectRoot!) and only afterwards checks !projectRoot:

const projectRoot = getProjectRoot(uri);
const langClient = await MILanguageClient.getInstance(projectRoot!);
const projectDetails = await langClient.getProjectDetails();
const projectName = projectDetails?.primaryDetails?.projectName?.value ?? getProjectName(uri);

if (!testController || !projectRoot || !projectName) {
    return;
}

Reordering the guards avoids ever passing undefined into getInstance in edge cases (e.g., a URI outside a recognized MI project) and makes the nullability story clearer:

 export async function createTests(uri: Uri) {
-    const projectRoot = getProjectRoot(uri);
-    const langClient = await MILanguageClient.getInstance(projectRoot!);
-    const projectDetails = await langClient.getProjectDetails();
-    const projectName = projectDetails?.primaryDetails?.projectName?.value ?? getProjectName(uri);
-
-    if (!testController || !projectRoot || !projectName) {
-        return;
-    }
+    const projectRoot = getProjectRoot(uri);
+    if (!testController || !projectRoot) {
+        return;
+    }
+
+    const langClient = await MILanguageClient.getInstance(projectRoot);
+    const projectDetails = await langClient.getProjectDetails();
+    const projectName = projectDetails?.primaryDetails?.projectName?.value ?? getProjectName(uri);
+    if (!projectName) {
+        return;
+    }

This keeps behavior the same in the happy path while being safer in unusual workspace setups.


353-379: Harden getTestCaseNamesAndTestSuiteType against missing artifacts and artifact paths

The switch to MILanguageClient here looks good, but this block can still blow up at runtime if parts of the project structure are missing:

const projectStructure = await langClient!.getProjectStructure(projectUri);

const artifacts = projectStructure.directoryMap.src?.main?.wso2mi?.artifacts;
const apis = artifacts?.apis?.map(/* ... */);
const sequences = artifacts?.sequences?.map(/* ... */);
const templates = artifacts?.templates?.map(/* ... */);
const allArtifacts = [...apis, ...sequences, ...templates];

If any of artifacts?.apis, artifacts?.sequences, or artifacts?.templates is undefined, the corresponding apis/sequences/templates will be undefined, and the spread [...apis, ...sequences, ...templates] will throw (undefined is not iterable).

Similarly, testArtifact can be falsy, in which case normalize(testArtifact) is suspect and the relative-path comparison may not behave as intended.

You can make this more robust without changing behavior when all data is present:

-    const unitTestST: UnitTest = st?.syntaxTree["unit-test"];
-    const testArtifact = unitTestST?.unitTestArtifacts?.testArtifact?.artifact?.textNode;
-
-    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" } });
-    const sequences = artifacts?.sequences?.map((sequence: ProjectStructureArtifactResponse) => { return { name: sequence.name, path: sequence.path.split(projectUri)[1], type: "Sequence" } });
-    const templates = artifacts?.templates?.map((template: ProjectStructureArtifactResponse) => { return { name: template.name, path: template.path.split(projectUri)[1], type: "Template" } });
-    const allArtifacts = [...apis, ...sequences, ...templates];
+    const unitTestST: UnitTest = st?.syntaxTree["unit-test"];
+    const testArtifact = unitTestST?.unitTestArtifacts?.testArtifact?.artifact?.textNode;
+    if (!testArtifact) {
+        window.showErrorMessage("Test suite artifact path is not available");
+        return;
+    }
+
+    const projectStructure = await langClient.getProjectStructure(projectUri);
+
+    const artifacts = projectStructure.directoryMap.src?.main?.wso2mi?.artifacts;
+    const apis =
+        artifacts?.apis?.map((api: ProjectStructureArtifactResponse) => ({
+            name: api.name,
+            path: api.path.split(projectUri)[1],
+            type: "Api",
+        })) ?? [];
+    const sequences =
+        artifacts?.sequences?.map((sequence: ProjectStructureArtifactResponse) => ({
+            name: sequence.name,
+            path: sequence.path.split(projectUri)[1],
+            type: "Sequence",
+        })) ?? [];
+    const templates =
+        artifacts?.templates?.map((template: ProjectStructureArtifactResponse) => ({
+            name: template.name,
+            path: template.path.split(projectUri)[1],
+            type: "Template",
+        })) ?? [];
+    const allArtifacts = [...apis, ...sequences, ...templates];

Also note: because MILanguageClient.getInstance already guarantees a client here (post projectUri check), the non-null assertion on langClient! is probably unnecessary and can be dropped for cleaner types.

workspaces/mi/mi-extension/src/lang-client/activator.ts (1)

124-129: Potential race condition with 100ms wait.

If instance.stop() takes longer than 100ms, getInstance proceeds and may create a new instance while the old one is still stopping. Consider waiting for the actual stop completion rather than a fixed timeout, or increase the wait with exponential backoff.

         // 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);
+            // Wait for the stop operation to complete with retries
+            let retries = 0;
+            while (this.stoppingInstances.has(projectUri) && retries < 50) {
+                await new Promise(resolve => setTimeout(resolve, 100));
+                retries++;
+            }
         }
workspaces/mi/mi-extension/src/rpc-managers/ai-panel/rpc-manager.ts (1)

378-381: Null check is now redundant but harmless.

Since MILanguageClient.getInstance() throws an error if the language client fails to initialize, the null check on line 379 is technically unnecessary. However, keeping it as a defensive check is acceptable.

If you prefer cleaner code, you could remove the redundant check:

             const langClient = await MILanguageClient.getInstance(this.projectUri);
-            if (!langClient) {
-                throw new Error('Language client not available');
-            }
workspaces/mi/mi-extension/src/rpc-managers/mi-debugger/rpc-manager.ts (1)

36-37: MILanguageClient migration looks correct; consider simplifying async wrappers

Using await MILanguageClient.getInstance(this.projectUri) in validateBreakpoints, getBreakpointInfo, and getStepOverBreakpoint is consistent with the new activator contract and preserves previous behavior. The outer return new Promise(async (resolve) => { … }) layers are redundant with async and can be safely collapsed to plain async functions that just return await langClient.<method>(…) to reduce noise and avoid any risk of hanging promises if an awaited call throws.

Also applies to: 41-48, 50-57, 115-122

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

60-60: Language client retrieval in createDMFiles is aligned with new lifecycle

Switching from state-machine access to await MILanguageClient.getInstance(this.projectUri) before calling getProjectDetails() fits the per-project LS lifecycle and is correctly wrapped in a try/catch that rejects on failure. The logic for deriving runtimeVersion and isResourceContentUsed remains unchanged.

Also applies to: 327-339

workspaces/mi/mi-extension/src/debugger/debugger.ts (1)

83-185: MILanguageClient usage in debugger is consistent; minor cleanups possible

The migration to await MILanguageClient.getInstance(this.projectUri) in createRuntimeBreakpoints, createStepOverBreakpoint, getBreakpointInformation, and getNextMediatorBreakpoint is consistent with the new activator behavior and keeps LS access centralized. You could optionally (a) drop the unused projectUri locals derived from workspace where they are no longer used, and (b) remove the if (!langClient) guard after getInstance in getNextMediatorBreakpoint, since getInstance either returns a client or throws.

Also applies to: 275-295

workspaces/mi/mi-extension/src/visualizer/activate.ts (1)

39-40: Pom.xml change flow now correctly ensures an active language client

Using await MILanguageClient.getInstance(projectUri!) in the pom.xml change handler is consistent with the new LS lifecycle and guarantees updateConnectorDependencies / loadDependentCAppResources have a live client, which should help with the original “suggestions stop after closing webview” issue. You could optionally reuse the earlier projectUri computed at the top of the handler instead of recomputing it, but that’s purely cosmetic.

Also applies to: 209-255

workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-manager.ts (1)

95-96: Visualizer RPCs now use MILanguageClient consistently; consider simplifying async patterns later

The refactor to obtain the language client via await MILanguageClient.getInstance(this.projectUri) across visualizer RPCs (project structure/details, dependency ops, overview, OpenAPI import, AI dependencies, etc.) is coherent with the new per-project LS lifecycle and should behave the same as the old stateMachine-based access, while auto-starting LS when needed. Longer-term, you might want to replace the repeated return new Promise(async (resolve) => { … }) wrappers with plain async functions that directly return the awaited results, which will also make error propagation from language-client calls more predictable.

Also applies to: 147-152, 156-161, 164-189, 198-205, 213-323, 326-367, 369-375, 425-432, 434-441, 777-782, 863-872, 905-951

workspaces/mi/mi-extension/src/project-explorer/activate.ts (1)

663-685: Consider checking language client availability earlier.

The language client is obtained at line 663 but not checked for null until lines 682-685. While this works because the client isn't used in between, it would be clearer and more maintainable to check immediately after obtaining it.

Consider this refactor:

 		const langClient = await MILanguageClient.getInstance(workspace.uri.fsPath);
+		if (!langClient) {
+			window.showErrorMessage('Language client is not available');
+			throw new Error('Language client is not available');
+		}

 		// Read the POM file
 		const workspaceFolder = vscode.workspace.getWorkspaceFolder(Uri.file(filePath));
 		if (!workspaceFolder) {
 			window.showErrorMessage('Cannot find workspace folder');
 			throw new Error('Cannot find workspace folder');
 		}

 		const pomPath = path.join(workspaceFolder.uri.fsPath, 'pom.xml');
 		const pomContent = fs.readFileSync(pomPath, 'utf-8');

 		if (remove) {
 			// Remove the <mainSequence> tag from the POM
 			const updatedPomContent = pomContent.replace(/\s*<mainSequence>.*?<\/mainSequence>/, '');
 			fs.writeFileSync(pomPath, updatedPomContent, 'utf-8');
 			return;
 		}
-
-		if (!langClient) {
-			window.showErrorMessage('Language client is not available');
-			throw new Error('Language client is not available');
-		}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7db138 and 67362fa.

📒 Files selected for processing (17)
  • workspaces/mi/mi-extension/src/debugger/debugHelper.ts (2 hunks)
  • workspaces/mi/mi-extension/src/debugger/debugger.ts (5 hunks)
  • workspaces/mi/mi-extension/src/extension.ts (2 hunks)
  • workspaces/mi/mi-extension/src/lang-client/activator.ts (2 hunks)
  • workspaces/mi/mi-extension/src/project-explorer/activate.ts (4 hunks)
  • workspaces/mi/mi-extension/src/project-explorer/project-explorer-provider.ts (1 hunks)
  • workspaces/mi/mi-extension/src/rpc-managers/ai-panel/rpc-manager.ts (2 hunks)
  • workspaces/mi/mi-extension/src/rpc-managers/mi-data-mapper/rpc-manager.ts (2 hunks)
  • workspaces/mi/mi-extension/src/rpc-managers/mi-debugger/rpc-manager.ts (3 hunks)
  • workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-manager.ts (36 hunks)
  • workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-manager.ts (13 hunks)
  • workspaces/mi/mi-extension/src/stateMachine.ts (4 hunks)
  • workspaces/mi/mi-extension/src/test-explorer/activator.ts (6 hunks)
  • workspaces/mi/mi-extension/src/util/schemaBuilder.ts (2 hunks)
  • workspaces/mi/mi-extension/src/util/swagger.ts (2 hunks)
  • workspaces/mi/mi-extension/src/util/workspace.ts (5 hunks)
  • workspaces/mi/mi-extension/src/visualizer/activate.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • workspaces/mi/mi-extension/src/extension.ts
  • workspaces/mi/mi-extension/src/stateMachine.ts
🧰 Additional context used
🧠 Learnings (11)
📚 Learning: 2025-11-26T06:34:09.752Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:128-134
Timestamp: 2025-11-26T06:34:09.752Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the goPrevViewBreadcrumb() method is only called when in a focused view, ensuring breadcrumbs are always present. No guard for empty breadcrumbs is needed.

Applied to files:

  • workspaces/mi/mi-extension/src/test-explorer/activator.ts
📚 Learning: 2025-11-26T07:49:56.428Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:136-141
Timestamp: 2025-11-26T07:49:56.428Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the goPrevViewBackButton() method is only called when in a focused view, ensuring breadcrumbs are always present. No guard for empty breadcrumbs is needed.

Applied to files:

  • workspaces/mi/mi-extension/src/test-explorer/activator.ts
📚 Learning: 2025-11-05T10:31:47.583Z
Learnt from: madushajg
Repo: wso2/vscode-extensions PR: 830
File: workspaces/ballerina/ballerina-extension/test/ai/post_proccess/post.test.ts:0-0
Timestamp: 2025-11-05T10:31:47.583Z
Learning: In the workspaces/ballerina/ballerina-extension project, the tsconfig.json uses "rootDirs": ["src", "test"], which allows test files to import from src using shorter relative paths. For example, from test/ai/post_proccess/, the import '../../stateMachine' correctly resolves to src/stateMachine.ts due to this configuration.

Applied to files:

  • workspaces/mi/mi-extension/src/test-explorer/activator.ts
📚 Learning: 2025-11-26T06:35:19.217Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:173-178
Timestamp: 2025-11-26T06:35:19.217Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the commented-out debugging block in the verifyFileContent function (lines 172-177 containing console.log, page.pause, and updateDataFileSync) is intentionally kept as a developer utility for updating test data files when needed. It should not be removed.

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
  • workspaces/mi/mi-extension/src/rpc-managers/mi-data-mapper/rpc-manager.ts
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/**/*.{ts,tsx} : Define all constants (node types, sizing, spacing) in src/resources/constants.ts and import them where needed instead of hardcoding values

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
📚 Learning: 2025-11-10T15:05:11.309Z
Learnt from: madushajg
Repo: wso2/vscode-extensions PR: 868
File: workspaces/bi/bi-extension/src/utils.ts:224-242
Timestamp: 2025-11-10T15:05:11.309Z
Learning: The workspaces/bi/bi-extension and workspaces/ballerina/ballerina-extension are separate VS Code extensions that are packaged and distributed independently, so they cannot share code via imports and must maintain their own implementations of common utilities.

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/**/*.{ts,tsx} : Use TypeScript 5.8.3 with strict type checking enabled for all source files

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/**/*.config.{js,ts} : Use minimatch-compatible glob patterns for file matching in build and test configuration files

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/**/*.tsx : Use React 18.2.0 features including concurrent rendering and automatic batching; avoid class components in favor of functional components with hooks

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
📚 Learning: 2025-11-10T15:04:50.474Z
Learnt from: madushajg
Repo: wso2/vscode-extensions PR: 868
File: workspaces/bi/bi-extension/src/utils.ts:153-169
Timestamp: 2025-11-10T15:04:50.474Z
Learning: The workspaces/bi/bi-extension and workspaces/ballerina/ballerina-extension are separate, independently deployable VSCode extensions in the wso2/vscode-extensions repository. Code duplication between these extensions is acceptable and often necessary to maintain their independence, rather than creating cross-extension dependencies.

Applied to files:

  • workspaces/mi/mi-extension/src/util/workspace.ts
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/**/*.{ts,tsx} : All async operations and promise handling in diagram utilities should use async/await syntax instead of .then() callbacks

Applied to files:

  • workspaces/mi/mi-extension/src/rpc-managers/mi-data-mapper/rpc-manager.ts
🧬 Code graph analysis (13)
workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-manager.ts (1)
workspaces/mi/mi-extension/src/lang-client/activator.ts (1)
  • MILanguageClient (102-502)
workspaces/mi/mi-extension/src/rpc-managers/mi-debugger/rpc-manager.ts (1)
workspaces/mi/mi-extension/src/lang-client/activator.ts (1)
  • MILanguageClient (102-502)
workspaces/mi/mi-extension/src/rpc-managers/ai-panel/rpc-manager.ts (1)
workspaces/mi/mi-extension/src/lang-client/activator.ts (1)
  • MILanguageClient (102-502)
workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-manager.ts (1)
workspaces/mi/mi-extension/src/lang-client/activator.ts (1)
  • MILanguageClient (102-502)
workspaces/mi/mi-extension/src/util/swagger.ts (1)
workspaces/mi/mi-extension/src/lang-client/activator.ts (1)
  • MILanguageClient (102-502)
workspaces/mi/mi-extension/src/debugger/debugHelper.ts (1)
workspaces/mi/mi-extension/src/lang-client/activator.ts (1)
  • MILanguageClient (102-502)
workspaces/mi/mi-extension/src/test-explorer/activator.ts (1)
workspaces/ballerina/ballerina-extension/src/features/ai/activator.ts (1)
  • langClient (33-33)
workspaces/mi/mi-extension/src/util/schemaBuilder.ts (1)
workspaces/mi/mi-extension/src/lang-client/activator.ts (1)
  • MILanguageClient (102-502)
workspaces/mi/mi-extension/src/debugger/debugger.ts (1)
workspaces/mi/mi-extension/src/lang-client/activator.ts (1)
  • MILanguageClient (102-502)
workspaces/mi/mi-extension/src/project-explorer/project-explorer-provider.ts (1)
workspaces/ballerina/ballerina-extension/src/features/ai/activator.ts (1)
  • langClient (33-33)
workspaces/mi/mi-extension/src/visualizer/activate.ts (1)
workspaces/mi/mi-extension/src/lang-client/activator.ts (1)
  • MILanguageClient (102-502)
workspaces/mi/mi-extension/src/util/workspace.ts (2)
workspaces/mi/mi-extension/src/lang-client/activator.ts (1)
  • MILanguageClient (102-502)
workspaces/mi/mi-extension/src/visualizer/webview.ts (1)
  • webviews (34-34)
workspaces/mi/mi-extension/src/rpc-managers/mi-data-mapper/rpc-manager.ts (1)
workspaces/mi/mi-extension/src/lang-client/activator.ts (1)
  • MILanguageClient (102-502)
🪛 Biome (2.1.2)
workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-manager.ts

[error] 4627-4631: Promise executor functions should not be async.

(lint/suspicious/noAsyncPromiseExecutor)


[error] 4635-4639: Promise executor functions should not be async.

(lint/suspicious/noAsyncPromiseExecutor)


[error] 4643-4647: Promise executor functions should not be async.

(lint/suspicious/noAsyncPromiseExecutor)


[error] 5619-5623: Promise executor functions should not be async.

(lint/suspicious/noAsyncPromiseExecutor)


[error] 5627-5631: Promise executor functions should not be async.

(lint/suspicious/noAsyncPromiseExecutor)


[error] 5688-5692: Promise executor functions should not be async.

(lint/suspicious/noAsyncPromiseExecutor)

🔇 Additional comments (10)
workspaces/mi/mi-extension/src/util/swagger.ts (1)

300-301: LGTM!

The migration to MILanguageClient.getInstance(projectUri) is correctly implemented. The async/await pattern is properly used, and the projectUri is validated before use.

workspaces/mi/mi-extension/src/project-explorer/project-explorer-provider.ts (1)

147-156: LGTM!

The removal of optional chaining (?.) is correct since MILanguageClient.getInstance() now throws if the language client fails to initialize. The surrounding try-catch ensures errors are handled gracefully.

workspaces/mi/mi-extension/src/lang-client/activator.ts (1)

145-165: LGTM on debounced stop logic.

The debounced stop implementation correctly prevents rapid start/stop cycles when switching between editors quickly. The timer management and state tracking are well-implemented.

workspaces/mi/mi-extension/src/util/workspace.ts (2)

137-174: LGTM on the LS lifecycle management.

The implementation correctly addresses the original issue:

  1. Starts LS when an artifacts document becomes active
  2. Stops LS (with debounce) when no artifacts documents remain open AND no webview is active
  3. Returns disposables for proper cleanup

The separation between onDidChangeActiveTextEditor (for starting) and onDidCloseTextDocument (for stopping) ensures proper lifecycle management.


176-186: LGTM on open document detection.

Using window.tabGroups.all to check all open tabs (not just the active editor) is the correct approach here. This ensures the LS stays running as long as any artifacts file is open, regardless of which tab is currently active.

workspaces/mi/mi-extension/src/util/schemaBuilder.ts (1)

22-23: Schema generation now uses MILanguageClient in a clean async flow

Switching generateSchema and generateSchemaFromContent to call await MILanguageClient.getInstance(projectUri) is consistent with the new LS lifecycle, and keeping these as plain async functions (without extra Promise wrappers) gives straightforward error propagation and readability. Call sites passing projectUri (e.g., data-mapper browseSchema) are in line with the new signatures.

Also applies to: 29-42, 44-57

workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-manager.ts (2)

342-343: MILanguageClient migration looks consistent and should fix LS lifetime coupling

Using MILanguageClient.getInstance(this.projectUri) across the diagram RPC manager (instead of getStateMachine(this.projectUri).context().langClient) is applied consistently and awaited everywhere before invoking LS methods (e.g., tryOutMediator, getSyntaxTree, getProjectStructure, Swagger helpers, DB helpers, mediator helpers, Devant deployment, and the new exposeVersionedServices helper). This aligns with the activator’s per‑project singleton design and should decouple LS lifecycle from the webview/state machine, which matches the PR goal of keeping LS functional after webview closure. I don’t see functional regressions in the updated call patterns.

Also applies to: 475-507, 529-538, 640-696, 1511-1551, 2625-2631, 3127-3137, 3779-3787, 3838-3840, 4203-4223, 4250-4263, 4304-4351, 4385-4397, 4548-4552, 4619-4655, 4758-4773, 4810-4832, 4870-4916, 5422-5428, 5611-5640, 5670-5692, 5720-5733, 5735-5758, 5765-5770, 5819-5834, 5889-5900, 6125-6153


4626-4655: Unable to verify review comment due to repository access failure

The review cannot be rewritten without access to the codebase to confirm the code patterns, validate line numbers, and ensure the refactoring suggestions are accurate.

workspaces/mi/mi-extension/src/project-explorer/activate.ts (2)

516-520: LGTM! Good defensive null handling.

The language client is properly checked for null before use, with an appropriate error message displayed to the user. This pattern should be applied consistently throughout the file.


40-40: Verify all callers have been updated for the new function signature.

The function signature changed from accepting lsClient: ExtendedLanguageClient to projectUri: string. Search the codebase for all invocations of activateProjectExplorer to ensure they pass projectUri instead of lsClient. Check that no callsites still reference the old parameter pattern.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67362fa and 905aaec.

📒 Files selected for processing (1)
  • workspaces/mi/mi-extension/src/lang-client/activator.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
workspaces/mi/mi-extension/src/lang-client/activator.ts (2)
workspaces/mi/mi-extension/src/lang-client/ExtendedLanguageClient.ts (1)
  • ExtendedLanguageClient (160-474)
workspaces/mi/mi-core/src/state-machine-types.ts (1)
  • ErrorType (238-241)
🔇 Additional comments (1)
workspaces/mi/mi-extension/src/lang-client/activator.ts (1)

102-109: Per-project lifecycle fields and OutputChannel caching look consistent.

The introduction of lsChannels, stopTimers, stoppingInstances, STOP_DEBOUNCE_MS, and the ExtendedLanguageClient-typed languageClient together with getOrCreateOutputChannel keyed by projectUri gives you a clean per-project lifecycle and logging story. The constant value and comment for STOP_DEBOUNCE_MS are now consistent, and the per-project channel naming is reasonable.

Also applies to: 175-182

Comment on lines +116 to 165
public static async getInstance(projectUri: string): Promise<ExtendedLanguageClient> {
// 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);
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) {
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);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "activator.ts" | grep -E "mi-extension.*lang-client"

Repository: wso2/vscode-extensions

Length of output: 188


Fix race between debounced stopInstance and getInstance returning a stopping client.

A real race exists in the current implementation:

  • stopInstance's timer callback adds projectUri to stoppingInstances, awaits instance.stop(), then deletes the _instances entry.
  • If getInstance runs while await instance.stop() is in progress:
    • It detects stoppingInstances.has(projectUri), sleeps 100ms, then unconditionally deletes the flag.
    • _instances still holds the old instance (because stop() has not finished), so !this._instances.has(projectUri) is false.
    • getInstance returns this._instances.get(projectUri)!.languageClient, which is the same client currently being stopped.
  • When stop() eventually completes, the timer callback deletes _instances, leaving callers holding a reference to a stopped ExtendedLanguageClient.

Replace the 100ms delay and bare Set with tracking the actual in-flight stop Promise per project. Change stoppingInstances: Set<string> to stoppingInstances: Map<string, Promise<void>> and have getInstance await the actual Promise instead of using a time-based delay. Update stopInstance to store the instance.stop() Promise in the map so getInstance can wait for it to complete before proceeding.

🤖 Prompt for AI Agents
In workspaces/mi/mi-extension/src/lang-client/activator.ts around lines 116-165,
the current use of stoppingInstances as a Set plus a 100ms sleep allows
getInstance to return a client that is still being stopped; replace the Set with
stoppingInstances: Map<string, Promise<void>> and in stopInstance store the
actual in-flight stop Promise (e.g. const stopPromise = instance.stop();
this.stoppingInstances.set(projectUri, stopPromise); await stopPromise; then
cleanup the map entry), and in getInstance if a stop promise exists await that
exact Promise (await this.stoppingInstances.get(projectUri)) before deleting the
map entry and proceeding to check/create the instance, ensuring getInstance
never returns a client that is mid-stop.

@gigara gigara merged commit 20f6d27 into wso2:main Dec 2, 2025
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Text editor does not provide synapse suggestions

2 participants