From 0b950aab1278a71de6192f6ea36008f874f28b3f Mon Sep 17 00:00:00 2001 From: madushajg Date: Thu, 20 Nov 2025 18:51:24 +0530 Subject: [PATCH 1/7] Refactor project explorer command registration to ensure core commands are always available, including notify and refresh commands for the Ballerina extension --- .../ballerina-extension/src/stateMachine.ts | 23 +++++++++++++------ .../src/project-explorer/activate.ts | 23 +++++++++++++------ 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/stateMachine.ts b/workspaces/ballerina/ballerina-extension/src/stateMachine.ts index 9c266e17ad1..e0cec38ecff 100644 --- a/workspaces/ballerina/ballerina-extension/src/stateMachine.ts +++ b/workspaces/ballerina/ballerina-extension/src/stateMachine.ts @@ -1020,18 +1020,27 @@ async function handleSingleWorkspaceFolder(workspaceURI: Uri): Promise dataProvider.refresh()); +function registerCoreCommands(dataProvider: ProjectExplorerEntryProvider) { + // Register the notify command that's called by the Ballerina extension commands.registerCommand( BI_COMMANDS.NOTIFY_PROJECT_EXPLORER, (event: { @@ -81,6 +79,17 @@ function registerBallerinaCommands( dataProvider.revealInTreeView(event.documentUri, event.projectPath, event.position, event.view); } ); + + // Register the refresh command + commands.registerCommand(BI_COMMANDS.REFRESH_COMMAND, () => dataProvider.refresh()); +} + +function registerBallerinaCommands( + dataProvider: ProjectExplorerEntryProvider, + isBI: boolean, + isBallerinaWorkspace?: boolean, + isEmptyWorkspace?: boolean +) { commands.executeCommand('setContext', 'BI.isWorkspaceSupported', extension.isWorkspaceSupported ?? false); if (isBallerinaWorkspace) { From aeb8f19b5810408b72e608579d6a5cf393ece5c0 Mon Sep 17 00:00:00 2001 From: madushajg Date: Thu, 20 Nov 2025 21:46:51 +0530 Subject: [PATCH 2/7] Implement workspace validation in test explorer activation to ensure proper project type before creating test profiles --- .../src/features/test-explorer/activator.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/workspaces/ballerina/ballerina-extension/src/features/test-explorer/activator.ts b/workspaces/ballerina/ballerina-extension/src/features/test-explorer/activator.ts index 96d792c193b..643f4b7d8e6 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/test-explorer/activator.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/test-explorer/activator.ts @@ -17,17 +17,31 @@ * under the License. */ -import { tests, workspace, TestRunProfileKind, TestController } from "vscode"; +import { tests, workspace, TestRunProfileKind, TestController, Uri } from "vscode"; import { BallerinaExtension } from "../../core"; import { runHandler } from "./runner"; import { activateEditBiTest } from "./commands"; import { discoverTestFunctionsInProject, handleFileChange as handleTestFileUpdate, handleFileDelete as handleTestFileDelete } from "./discover"; +import { getCurrentBallerinaProject, getWorkspaceRoot } from "../../utils/project-utils"; +import { checkIsBallerinaPackage, checkIsBallerinaWorkspace } from "../../utils"; +import { PROJECT_TYPE } from "../project"; export let testController: TestController; export async function activate(ballerinaExtInstance: BallerinaExtension) { testController = tests.createTestController('ballerina-integrator-tests', 'WSO2 Integrator: BI Tests'); + const workspaceRoot = getWorkspaceRoot(); + + const isBallerinaWorkspace = checkIsBallerinaWorkspace(Uri.file(workspaceRoot)); + const isBallerinaProject = !isBallerinaWorkspace && await checkIsBallerinaPackage(Uri.file(workspaceRoot)); + const currentProject = !isBallerinaWorkspace && !isBallerinaProject && await getCurrentBallerinaProject(); + const isSingleFile = currentProject && currentProject.kind === PROJECT_TYPE.SINGLE_FILE; + + if (!isBallerinaWorkspace || !isBallerinaProject || !isSingleFile) { + return; + } + // Create test profiles to display. testController.createRunProfile('Run Tests', TestRunProfileKind.Run, runHandler, true); testController.createRunProfile('Debug Tests', TestRunProfileKind.Debug, runHandler, true); From a9d83c39ddac2eb27b4e2cbb95d3905538da742a Mon Sep 17 00:00:00 2001 From: madushajg Date: Thu, 20 Nov 2025 23:41:57 +0530 Subject: [PATCH 3/7] Enable UI features for multi-package directory setups --- .../src/features/bi/activator.ts | 3 +++ .../ballerina-extension/src/stateMachine.ts | 27 ++++++++++++------- .../src/views/ai-panel/activate.ts | 1 - .../src/views/visualizer/activate.ts | 4 +-- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/features/bi/activator.ts b/workspaces/ballerina/ballerina-extension/src/features/bi/activator.ts index 9e953006eaa..a1ada5a98bb 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/bi/activator.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/bi/activator.ts @@ -198,6 +198,9 @@ export function activate(context: BallerinaExtension) { function openBallerinaTomlFile(context: BallerinaExtension) { const projectPath = StateMachine.context().projectPath || StateMachine.context().workspacePath; + if (!projectPath) { + return; + } const ballerinaTomlFile = path.join(projectPath, "Ballerina.toml"); try { const content = readFileSync(ballerinaTomlFile, "utf8"); diff --git a/workspaces/ballerina/ballerina-extension/src/stateMachine.ts b/workspaces/ballerina/ballerina-extension/src/stateMachine.ts index e0cec38ecff..2b63f5b945b 100644 --- a/workspaces/ballerina/ballerina-extension/src/stateMachine.ts +++ b/workspaces/ballerina/ballerina-extension/src/stateMachine.ts @@ -20,7 +20,8 @@ import { Type, dependencyPullProgress, BI_COMMANDS, - NodePosition + NodePosition, + ProjectInfo } from "@wso2/ballerina-core"; import { fetchAndCacheLibraryData } from './features/library-browser'; import { VisualizerWebview } from './views/visualizer/webview'; @@ -107,13 +108,14 @@ const stateMachine = createMachine( } ] }, - UPDATE_PROJECT_ROOT: { + UPDATE_PROJECT_ROOT_AND_INFO: { actions: [ assign({ - projectPath: (context, event) => event.projectPath + projectPath: (context, event) => event.projectPath, + projectInfo: (context, event) => event.projectInfo }), async (context, event) => { - await buildProjectsStructure(context.projectInfo, StateMachine.langClient(), true); + await buildProjectsStructure(event.projectInfo, StateMachine.langClient(), true); notifyCurrentWebview(); notifyTreeView(event.projectPath, context.documentUri, context.position, context.view); // Resolve the next pending promise waiting for project root update completion @@ -468,10 +470,15 @@ const stateMachine = createMachine( fetchProjectInfo: (context, event) => { return new Promise(async (resolve, reject) => { try { - const projectInfo = await context.langClient.getProjectInfo({ - projectPath: context.workspacePath || context.projectPath - }); - resolve({ projectInfo }); + const projectPath = context.workspacePath || context.projectPath; + if (!projectPath) { + resolve({ projectInfo: undefined }); + } else { + const projectInfo = await context.langClient.getProjectInfo({ + projectPath: context.workspacePath || context.projectPath + }); + resolve({ projectInfo }); + } } catch (error) { throw new Error("Error occurred while fetching project info.", error); } @@ -799,10 +806,10 @@ export const StateMachine = { }, sendEvent: (eventType: EVENT_TYPE) => { stateService.send({ type: eventType }); }, updateProjectStructure: (payload: ProjectStructureResponse) => { stateService.send({ type: "UPDATE_PROJECT_STRUCTURE", payload }); }, - updateProjectRoot: (projectPath: string): Promise => { + updateProjectRootAndInfo: (projectPath: string, projectInfo: ProjectInfo): Promise => { return new Promise((resolve) => { pendingProjectRootUpdateResolvers.push(resolve); - stateService.send({ type: "UPDATE_PROJECT_ROOT", projectPath }); + stateService.send({ type: "UPDATE_PROJECT_ROOT_AND_INFO", projectPath, projectInfo }); }); }, refreshProjectInfo: () => { diff --git a/workspaces/ballerina/ballerina-extension/src/views/ai-panel/activate.ts b/workspaces/ballerina/ballerina-extension/src/views/ai-panel/activate.ts index df064c0e375..4dab56881ab 100644 --- a/workspaces/ballerina/ballerina-extension/src/views/ai-panel/activate.ts +++ b/workspaces/ballerina/ballerina-extension/src/views/ai-panel/activate.ts @@ -54,7 +54,6 @@ export function activateAiPanel(ballerinaExtInstance: BallerinaExtension) { return; } - // StateMachine.updateProjectRoot(selectedPackage); openView(EVENT_TYPE.OPEN_VIEW, { view: MACHINE_VIEW.PackageOverview, projectPath: selectedPackage }); } catch (error) { console.error("Error selecting package:", error); diff --git a/workspaces/ballerina/ballerina-extension/src/views/visualizer/activate.ts b/workspaces/ballerina/ballerina-extension/src/views/visualizer/activate.ts index ef92daa4f04..f46d04c39c8 100644 --- a/workspaces/ballerina/ballerina-extension/src/views/visualizer/activate.ts +++ b/workspaces/ballerina/ballerina-extension/src/views/visualizer/activate.ts @@ -95,8 +95,8 @@ export function activateSubscriptions() { // Initialize project structure if not already set by finding and loading the Ballerina project root // Can happen when the user opens a directory containing multiple Ballerina projects if (projectRoot) { - // TODO: Need to create the project structure for the workspace - await StateMachine.updateProjectRoot(projectRoot); + const projectInfo = await StateMachine.langClient().getProjectInfo({ projectPath: projectRoot }); + await StateMachine.updateProjectRootAndInfo(projectRoot, projectInfo); } } From ad80404863980fc0745464f71a75bc979af05f3a Mon Sep 17 00:00:00 2001 From: madushajg Date: Thu, 20 Nov 2025 23:45:21 +0530 Subject: [PATCH 4/7] Skip welcome form from topNavBar --- .../src/components/TopNavigationBar/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/components/TopNavigationBar/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/components/TopNavigationBar/index.tsx index b3d873410f8..aa133b51668 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/components/TopNavigationBar/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/components/TopNavigationBar/index.tsx @@ -157,7 +157,8 @@ export function TopNavigationBar(props: TopNavigationBarProps) { "data mapper", "connection", "add project", - "bi add project skip" + "bi add project skip", + "welcome" ]; if (workspaceType?.type !== "BALLERINA_WORKSPACE") { From 091e5251a719c7d03418413161e99f3d3cfc4096 Mon Sep 17 00:00:00 2001 From: madushajg Date: Fri, 21 Nov 2025 10:04:57 +0530 Subject: [PATCH 5/7] Fix visualizing constructs using the code lens when no webview is open --- workspaces/ballerina/ballerina-extension/src/stateMachine.ts | 4 ++-- .../src/project-explorer/project-explorer-provider.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/stateMachine.ts b/workspaces/ballerina/ballerina-extension/src/stateMachine.ts index 2b63f5b945b..c38afb8c40a 100644 --- a/workspaces/ballerina/ballerina-extension/src/stateMachine.ts +++ b/workspaces/ballerina/ballerina-extension/src/stateMachine.ts @@ -1027,7 +1027,7 @@ async function handleSingleWorkspaceFolder(workspaceURI: Uri): Promise Date: Fri, 21 Nov 2025 11:52:52 +0530 Subject: [PATCH 6/7] Fix setting projectPath when swiching between artifacts in different packages using tree view --- .../src/views/visualizer/activate.ts | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/views/visualizer/activate.ts b/workspaces/ballerina/ballerina-extension/src/views/visualizer/activate.ts index f46d04c39c8..2d1b135e4da 100644 --- a/workspaces/ballerina/ballerina-extension/src/views/visualizer/activate.ts +++ b/workspaces/ballerina/ballerina-extension/src/views/visualizer/activate.ts @@ -80,13 +80,27 @@ export function activateSubscriptions() { const projectRoot = await findBallerinaPackageRoot(documentPath); const isBallerinaWorkspace = !!StateMachine.context().workspacePath; - if (isBallerinaWorkspace && pathOrItem instanceof vscode.TreeItem) { + if (isBallerinaWorkspace) { + if (pathOrItem instanceof vscode.TreeItem) { + openView( + EVENT_TYPE.OPEN_VIEW, + { + projectPath: pathOrItem.resourceUri?.fsPath, + view: MACHINE_VIEW.PackageOverview + }, + true + ); + return; + } + const documentUri = documentPath || vscode.window.activeTextEditor?.document.uri.fsPath openView( EVENT_TYPE.OPEN_VIEW, { - projectPath: pathOrItem.resourceUri?.fsPath, - view: MACHINE_VIEW.PackageOverview + projectPath: projectRoot, + documentUri: documentUri, + position: nodePosition }, + true ); return; } From 788112485cd023f09c163545830a8c72656b4ad2 Mon Sep 17 00:00:00 2001 From: madushajg Date: Fri, 21 Nov 2025 12:54:22 +0530 Subject: [PATCH 7/7] Address review suggestions --- .../src/features/test-explorer/activator.ts | 4 ++-- workspaces/ballerina/ballerina-extension/src/stateMachine.ts | 4 +--- .../ballerina-extension/src/views/visualizer/activate.ts | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/features/test-explorer/activator.ts b/workspaces/ballerina/ballerina-extension/src/features/test-explorer/activator.ts index 643f4b7d8e6..036df018c82 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/test-explorer/activator.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/test-explorer/activator.ts @@ -33,12 +33,12 @@ export async function activate(ballerinaExtInstance: BallerinaExtension) { const workspaceRoot = getWorkspaceRoot(); - const isBallerinaWorkspace = checkIsBallerinaWorkspace(Uri.file(workspaceRoot)); + const isBallerinaWorkspace = await checkIsBallerinaWorkspace(Uri.file(workspaceRoot)); const isBallerinaProject = !isBallerinaWorkspace && await checkIsBallerinaPackage(Uri.file(workspaceRoot)); const currentProject = !isBallerinaWorkspace && !isBallerinaProject && await getCurrentBallerinaProject(); const isSingleFile = currentProject && currentProject.kind === PROJECT_TYPE.SINGLE_FILE; - if (!isBallerinaWorkspace || !isBallerinaProject || !isSingleFile) { + if (!isBallerinaWorkspace && !isBallerinaProject && !isSingleFile) { return; } diff --git a/workspaces/ballerina/ballerina-extension/src/stateMachine.ts b/workspaces/ballerina/ballerina-extension/src/stateMachine.ts index c38afb8c40a..c3003ca8d70 100644 --- a/workspaces/ballerina/ballerina-extension/src/stateMachine.ts +++ b/workspaces/ballerina/ballerina-extension/src/stateMachine.ts @@ -474,9 +474,7 @@ const stateMachine = createMachine( if (!projectPath) { resolve({ projectInfo: undefined }); } else { - const projectInfo = await context.langClient.getProjectInfo({ - projectPath: context.workspacePath || context.projectPath - }); + const projectInfo = await context.langClient.getProjectInfo({ projectPath }); resolve({ projectInfo }); } } catch (error) { diff --git a/workspaces/ballerina/ballerina-extension/src/views/visualizer/activate.ts b/workspaces/ballerina/ballerina-extension/src/views/visualizer/activate.ts index 2d1b135e4da..0a59af5ea2a 100644 --- a/workspaces/ballerina/ballerina-extension/src/views/visualizer/activate.ts +++ b/workspaces/ballerina/ballerina-extension/src/views/visualizer/activate.ts @@ -92,7 +92,7 @@ export function activateSubscriptions() { ); return; } - const documentUri = documentPath || vscode.window.activeTextEditor?.document.uri.fsPath + const documentUri = documentPath || vscode.window.activeTextEditor?.document.uri.fsPath; openView( EVENT_TYPE.OPEN_VIEW, {