diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/interfaces.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/interfaces.ts index 8742990e418..8173d0c0212 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/interfaces.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/interfaces.ts @@ -168,7 +168,7 @@ export interface DataMappingRecord { } export interface GenerateTypesFromRecordRequest { - attachment?: Attachment[] + attachment: Attachment[] } export interface GenerateTypesFromRecordResponse { diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/lang-client/index.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/lang-client/index.ts index 9bc819ee589..da659413ea5 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/lang-client/index.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/lang-client/index.ts @@ -17,7 +17,7 @@ */ import { BallerinaPackagesParams, BallerinaProjectComponents, BallerinaSTParams, ComponentModels, ComponentModelsParams, ExecutorPositions, PartialST, PartialSTParams, ProjectDiagnosticsRequest, ProjectDiagnosticsResponse, STModifyParams, SymbolInfo, SymbolInfoParams, SyntaxTree, SyntaxTreeParams, TypeFromExpressionParams, TypeFromSymbolParams, TypesFromFnDefinitionParams } from "../../interfaces/extended-lang-client"; -import { BallerinaVersionResponse, CompletionRequest, CompletionResponse, DiagnosticsResponse, CodeActionRequest, CodeActionResponse, RenameRequest, RenameResponse, DefinitionPositionRequest, UpdateFileContentRequest, UpdateFileContentResponse, DefinitionResponse, ExecutorPositionsRequest, DidCloseRequest, TypesFromExpressionResponse, TypesFromSymbolResponse, DidOpenRequest, DidChangeRequest } from "./interfaces"; +import { BallerinaVersionResponse, CompletionRequest, CompletionResponse, DiagnosticsResponse, CodeActionRequest, CodeActionResponse, RenameRequest, RenameResponse, DefinitionPositionRequest, UpdateFileContentRequest, UpdateFileContentResponse, DefinitionResponse, ExecutorPositionsRequest, DidCloseRequest, TypesFromExpressionResponse, TypesFromSymbolResponse, DidOpenRequest, DidChangeRequest, SemanticVersion } from "./interfaces"; export interface LangClientAPI { getSyntaxTree: () => Promise; @@ -25,6 +25,7 @@ export interface LangClientAPI { getSTByRange: (params: BallerinaSTParams) => Promise; getBallerinaProjectComponents: (params: BallerinaPackagesParams) => Promise; getBallerinaVersion: () => Promise; + isSupportedSLVersion: (params: SemanticVersion) => Promise; getCompletion: (params: CompletionRequest) => Promise; getDiagnostics: (params: SyntaxTreeParams) => Promise; getProjectDiagnostics: (params: ProjectDiagnosticsRequest) => Promise; diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/lang-client/interfaces.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/lang-client/interfaces.ts index f9724336ad6..7ba401686ce 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/lang-client/interfaces.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/lang-client/interfaces.ts @@ -163,4 +163,10 @@ export interface TypesFromSymbolResponse { export interface ExecutorPositionsResponse { executorPositions?: ExecutorPosition[]; -} \ No newline at end of file +} + +export interface SemanticVersion { + major: number; + minor: number; + patch: number; +} diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/lang-client/rpc-type.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/lang-client/rpc-type.ts index bfb481eef1b..9aa69f5dea8 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/lang-client/rpc-type.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/lang-client/rpc-type.ts @@ -18,7 +18,7 @@ * THIS FILE INCLUDES AUTO GENERATED CODE */ import { BallerinaPackagesParams, BallerinaProjectComponents, BallerinaSTParams, ComponentModels, ComponentModelsParams, ExecutorPositions, PartialST, PartialSTParams, ProjectDiagnosticsRequest, ProjectDiagnosticsResponse, STModifyParams, SymbolInfo, SymbolInfoParams, SyntaxTree, SyntaxTreeParams, TypeFromExpressionParams, TypeFromSymbolParams, TypesFromFnDefinitionParams } from "../../interfaces/extended-lang-client"; -import { BallerinaVersionResponse, CompletionRequest, CompletionResponse, DiagnosticsResponse, CodeActionRequest, CodeActionResponse, RenameRequest, RenameResponse, DefinitionPositionRequest, UpdateFileContentRequest, UpdateFileContentResponse, DefinitionResponse, ExecutorPositionsRequest, DidCloseRequest, TypesFromExpressionResponse, TypesFromSymbolResponse, DidOpenRequest, DidChangeRequest } from "./interfaces"; +import { BallerinaVersionResponse, CompletionRequest, CompletionResponse, DiagnosticsResponse, CodeActionRequest, CodeActionResponse, RenameRequest, RenameResponse, DefinitionPositionRequest, UpdateFileContentRequest, UpdateFileContentResponse, DefinitionResponse, ExecutorPositionsRequest, DidCloseRequest, TypesFromExpressionResponse, TypesFromSymbolResponse, DidOpenRequest, DidChangeRequest, SemanticVersion } from "./interfaces"; import { RequestType, NotificationType } from "vscode-messenger-common"; const _preFix = "lang-client"; @@ -27,6 +27,7 @@ export const getST: RequestType = { method: `${_pr export const getSTByRange: RequestType = { method: `${_preFix}/getSTByRange` }; export const getBallerinaProjectComponents: RequestType = { method: `${_preFix}/getBallerinaProjectComponents` }; export const getBallerinaVersion: RequestType = { method: `${_preFix}/getBallerinaVersion` }; +export const isSupportedSLVersion: RequestType = { method: `${_preFix}/isSupportedSLVersion` }; export const getCompletion: RequestType = { method: `${_preFix}/getCompletion` }; export const getDiagnostics: RequestType = { method: `${_preFix}/getDiagnostics` }; export const getProjectDiagnostics: RequestType = { method: `${_preFix}/getProjectDiagnostics` }; diff --git a/workspaces/ballerina/ballerina-extension/package.json b/workspaces/ballerina/ballerina-extension/package.json index 36347194d47..526952b3fea 100644 --- a/workspaces/ballerina/ballerina-extension/package.json +++ b/workspaces/ballerina/ballerina-extension/package.json @@ -2,7 +2,7 @@ "name": "ballerina", "displayName": "Ballerina", "description": "Ballerina Language support, debugging, graphical visualization, AI-based data-mapping and many more.", - "version": "5.6.0", + "version": "5.6.1", "publisher": "wso2", "icon": "resources/images/ballerina.png", "homepage": "https://wso2.com/ballerina/vscode/docs", diff --git a/workspaces/ballerina/ballerina-extension/src/core/extension.ts b/workspaces/ballerina/ballerina-extension/src/core/extension.ts index c27824327eb..a137d0b0189 100644 --- a/workspaces/ballerina/ballerina-extension/src/core/extension.ts +++ b/workspaces/ballerina/ballerina-extension/src/core/extension.ts @@ -33,7 +33,18 @@ import { exec, spawnSync, execSync } from 'child_process'; import { LanguageClientOptions, State as LS_STATE, RevealOutputChannelOn, ServerOptions } from "vscode-languageclient/node"; import { getServerOptions } from '../utils/server/server'; import { ExtendedLangClient } from './extended-language-client'; -import { debug, log, getOutputChannel, outputChannel, isWindows, isWSL, isSupportedVersion, VERSION, isSupportedSLVersion } from '../utils'; +import { + debug, + log, + getOutputChannel, + outputChannel, + isWindows, + isWSL, + isSupportedVersion, + VERSION, + isSupportedSLVersion, + createVersionNumber +} from '../utils'; import { AssertionError } from "assert"; import { BALLERINA_HOME, ENABLE_ALL_CODELENS, ENABLE_TELEMETRY, ENABLE_SEMANTIC_HIGHLIGHTING, OVERRIDE_BALLERINA_HOME, @@ -142,6 +153,7 @@ export class BallerinaExtension { public ballerinaVersion: string; public biSupported: boolean; public isNPSupported: boolean; + public isWorkspaceSupported: boolean; public extension: Extension; private clientOptions: LanguageClientOptions; public langClient?: ExtendedLangClient; @@ -172,6 +184,7 @@ export class BallerinaExtension { this.ballerinaVersion = ''; this.biSupported = false; this.isNPSupported = false; + this.isWorkspaceSupported = false; this.isPersist = false; this.ballerinaUserHomeName = '.ballerina'; @@ -442,9 +455,10 @@ export class BallerinaExtension { } try { - this.biSupported = isSupportedSLVersion(this, 2201123); // Minimum supported version for BI - this.isNPSupported = isSupportedSLVersion(this, 2201130) && this.enabledExperimentalFeatures(); // Minimum supported requirements for NP - debug(`[INIT] Feature support calculated - BI: ${this.biSupported}, NP: ${this.isNPSupported}`); + this.biSupported = isSupportedSLVersion(this, createVersionNumber(2201, 12, 3)); // Minimum supported version for BI: 2201.12.3 + this.isNPSupported = isSupportedSLVersion(this, createVersionNumber(2201, 13, 0)) && this.enabledExperimentalFeatures(); // Minimum supported requirements for NP: 2201.13.0 + this.isWorkspaceSupported = isSupportedSLVersion(this, createVersionNumber(2201, 13, 0)); // Minimum supported requirements for Workspace: 2201.13.0 + debug(`[INIT] Feature support calculated - BI: ${this.biSupported}, NP: ${this.isNPSupported}, Workspace: ${this.isWorkspaceSupported}`); } catch (error) { debug(`[INIT] Error calculating feature support: ${error}`); // Don't throw here, we can continue without these features @@ -464,7 +478,7 @@ export class BallerinaExtension { debug(`[INIT] Final Ballerina Home: ${this.ballerinaHome}`); debug(`[INIT] Plugin Dev Mode: ${this.overrideBallerinaHome()}`); debug(`[INIT] Debug Mode: ${this.enableLSDebug()}`); - debug(`[INIT] Feature flags - Experimental: ${this.enabledExperimentalFeatures()}, BI: ${this.biSupported}, NP: ${this.isNPSupported}`); + debug(`[INIT] Feature flags - Experimental: ${this.enabledExperimentalFeatures()}, BI: ${this.biSupported}, NP: ${this.isNPSupported}, Workspace: ${this.isWorkspaceSupported}`); // Check version compatibility try { @@ -2283,7 +2297,7 @@ export class BallerinaExtension { } public enabledLiveReload(): boolean { - return isSupportedSLVersion(this, 2201100) && workspace.getConfiguration().get(ENABLE_LIVE_RELOAD); + return isSupportedSLVersion(this, createVersionNumber(2201, 10, 0)) && workspace.getConfiguration().get(ENABLE_LIVE_RELOAD); } public enabledPerformanceForecasting(): boolean { diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/dataMapping.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/dataMapping.ts index 207687f30c9..725c66ce8e3 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/dataMapping.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/dataMapping.ts @@ -477,11 +477,10 @@ export async function generateMappings( ): Promise { const targetFilePath = metadataWithAttachments.metadata.codeData.lineRange.fileName || context.documentUri; - const optionalMappingInstructionFile = metadataWithAttachments.attachments && metadataWithAttachments.attachments.length > 0 - ? metadataWithAttachments.attachments[0] - : undefined; - - const generatedMappings = await generateMappingExpressionsFromModel(metadataWithAttachments.metadata.mappingsModel as DMModel, optionalMappingInstructionFile); + const generatedMappings = await generateMappingExpressionsFromModel( + metadataWithAttachments.metadata.mappingsModel as DMModel, + metadataWithAttachments.attachments || [] + ); const customFunctionMappings = generatedMappings.filter(mapping => mapping.isFunctionCall); let customFunctionsFilePath: string | undefined; @@ -1123,20 +1122,47 @@ export async function generateInlineMappingsSource( // processContextTypeCreation - Functions for processing context type creation // ================================================================================================ -// Extract record types from Ballerina code -export function extractRecordTypes(typesCode: string): { name: string; code: string }[] { - const recordPattern = /\b(?:public|private)?\s*type\s+(\w+)\s+record\s+(?:{[|]?|[|]?{)[\s\S]*?;?\s*[}|]?;/g; - const matches = [...typesCode.matchAll(recordPattern)]; - return matches.map((match) => ({ - name: match[1], - code: match[0].trim(), - })); +// Extract record and enum types from syntax tree +export async function extractRecordTypesFromSyntaxTree( + langClient: ExtendedLangClient, + filePath: string +): Promise<{ records: string[]; enums: string[] }> { + const st = (await langClient.getSyntaxTree({ + documentIdentifier: { + uri: Uri.file(filePath).toString(), + }, + })) as SyntaxTree; + + if (!st.syntaxTree) { + throw new Error("Failed to retrieve syntax tree for file: " + filePath); + } + + const modulePart = st.syntaxTree as ModulePart; + const records: string[] = []; + const enums: string[] = []; + + for (const member of modulePart.members) { + if (STKindChecker.isTypeDefinition(member)) { + const typeName = member.typeName?.value; + if (typeName) { + records.push(typeName); + } + } else if (STKindChecker.isEnumDeclaration(member)) { + const enumName = member.identifier?.value; + if (enumName) { + enums.push(enumName); + } + } + } + + return { records, enums }; } // Generate Ballerina record types from context attachments and validate against existing records export async function generateTypesFromContext( sourceAttachments: Attachment[], - projectComponents: ProjectComponentsResponse + projectComponents: ProjectComponentsResponse, + langClient: ExtendedLangClient ): Promise { if (!sourceAttachments || sourceAttachments.length === 0) { throw new Error("Source attachments are required for type generation"); @@ -1163,10 +1189,14 @@ export async function generateTypesFromContext( const typeFilePath = baseFilePath + typeComponent.filePath; existingRecordTypesMap.set(typeComponent.name, { type: typeComponent.name, isArray: false, filePath: typeFilePath }); }); + moduleSummary.enums.forEach((enumComponent: ComponentInfo) => { + const enumFilePath = baseFilePath + enumComponent.filePath; + existingRecordTypesMap.set(enumComponent.name, { type: enumComponent.name, isArray: false, filePath: enumFilePath }); + }); }); }); - // Generate type definitions from attachments + // Generate type definitions from all attachments together const typeGenerationRequest: GenerateTypesFromRecordRequest = { attachment: sourceAttachments }; @@ -1174,6 +1204,29 @@ export async function generateTypesFromContext( const typeGenerationResponse = await generateTypeCreation(typeGenerationRequest); const generatedTypesCode = typeGenerationResponse.typesCode; + // Create temp directory and file to validate generated types + const tempDirectory = await createTempBallerinaDir(); + const tempTypesFilePath = path.join(tempDirectory, outputFileName); + + writeBallerinaFileDidOpenTemp(tempTypesFilePath, generatedTypesCode); + + // Extract record and enum names from syntax tree + const { records: generatedRecords, enums: generatedEnums } = await extractRecordTypesFromSyntaxTree(langClient, tempTypesFilePath); + + // Check for duplicate record names + for (const recordName of generatedRecords) { + if (existingRecordTypesMap.has(recordName)) { + throw new Error(`Record "${recordName}" already exists in the workspace`); + } + } + + // Check for duplicate enum names + for (const enumName of generatedEnums) { + if (existingRecordTypesMap.has(enumName)) { + throw new Error(`Enum "${enumName}" already exists in the workspace`); + } + } + return { typesCode: generatedTypesCode, filePath: outputFileName, @@ -1181,13 +1234,16 @@ export async function generateTypesFromContext( }; } -// Generate Ballerina record type definitions from an attachment file +// Generate Ballerina record type definitions from attachment files export async function generateTypeCreation( typeGenerationRequest: GenerateTypesFromRecordRequest ): Promise { - const sourceFile = typeGenerationRequest.attachment?.[0]; + if (typeGenerationRequest.attachment.length === 0) { + throw new Error('No attachments provided for type generation'); + } - const generatedTypeDefinitions = await extractRecordTypeDefinitionsFromFile(sourceFile); + // Process all attachments together to understand correlations + const generatedTypeDefinitions = await extractRecordTypeDefinitionsFromFile(typeGenerationRequest.attachment); if (typeof generatedTypeDefinitions !== 'string') { throw new Error(`Failed to generate types: ${JSON.stringify(generatedTypeDefinitions)}`); } diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/context_api.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/context_api.ts index 0f8a331f16a..14cf1183c78 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/context_api.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/context_api.ts @@ -17,111 +17,32 @@ import { generateText, ModelMessage } from "ai"; import { getAnthropicClient, ANTHROPIC_SONNET_4 } from "../connection"; import { AIPanelAbortController } from "../../../../../src/rpc-managers/ai-panel/utils"; +import { ContentPart, DataMapperRequest, DataMapperResponse, FileData, FileTypeHandler, ProcessType } from "./types"; -// Types -export type FileData = { - fileName: string; - content: string; -}; - -export type ProcessType = "mapping_instruction" | "records" | "requirements"; - -export type DataMapperRequest = { - file?: FileData; - text?: string; - processType: ProcessType; - isRequirementAnalysis?: boolean; //TODO: Why is this -}; - -export type DataMapperResponse = { - fileContent: string; -}; - -export type SupportedFileExtension = "pdf" | "jpg" | "jpeg" | "png" | "txt"; // Maybe have better names and types? export async function processDataMapperInput(request: DataMapperRequest): Promise { - if (request.file) { - return await processFile(request.file, request.processType, request.isRequirementAnalysis); + if (request.files.length > 0) { + return await processFiles(request.files, request.processType, request.isRequirementAnalysis); } else if (request.text) { - const message = await processText(request.text, request.processType); - const fileContent = request.isRequirementAnalysis - ? message - : extractBallerinaCode(message, request.processType); - return { fileContent }; + return await processFiles([{ fileName: 'text', content: btoa(request.text) }], request.processType, request.isRequirementAnalysis); } else { - throw new Error("No file or text provided. Please provide file data or text input."); + throw new Error("No files or text provided. Please provide file data or text input."); } } -// Process file data -async function processFile(file: FileData, processType: ProcessType, isRequirementAnalysis: boolean = false): Promise { - let message: string; - - const extension = getFileExtension(file.fileName); - +// Process files (single or multiple) +async function processFiles(files: FileData[], processType: ProcessType, isRequirementAnalysis: boolean = false): Promise { try { - //TODO: I think we should handle supported files from one place. - if (extension === "pdf") { - message = await processPdf(file.content, processType); - } else if (extension === "jpeg" || extension === "jpg" || extension === "png") { - message = await processImage(file.content, processType, extension); - } else if (extension === "txt" || extension === "csv" || !extension) { - const txtContent = atob(file.content); - message = await processText(txtContent, processType); - } else { - throw new Error(`Unsupported file type: ${extension}`); - } + const message = await processFilesWithClaude(files, processType); - const fileContent = isRequirementAnalysis + const fileContent = isRequirementAnalysis ? getRequirementsContent(message) : extractBallerinaCode(message, processType); return { fileContent }; } catch (error) { - throw new Error(`Error processing file: ${error instanceof Error ? error.message : String(error)}`); - } -} - -// Process PDF content -async function processPdf(base64Content: string, processType: ProcessType): Promise { - try { - return await extractionUsingClaude({ - pdfData: base64Content, - processType - }); - } catch (error) { - throw new Error(`PDF processing error: ${error instanceof Error ? error.message : String(error)}`); - } -} - -// Process image content -async function processImage(base64Content: string, processType: ProcessType, extension: string): Promise { - // Only process actual image extensions - if (extension !== "jpeg" && extension !== "jpg" && extension !== "png") { - throw new Error(`Unsupported image extension: ${extension}`); - } - - try { - return await imageExtractionUsingClaude({ - imgData: base64Content, - processType, - extension - }); - } catch (error) { - throw new Error(`Image processing error: ${error instanceof Error ? error.message : String(error)}`); - } -} - -// Process text content -async function processText(text: string, processType: ProcessType): Promise { - try { - return await textExtractionUsingClaude({ - textContent: text, - processType - }); - } catch (error) { - throw new Error(`Error processing text: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Error processing ${files.length === 1 ? 'file' : 'files'}: ${error instanceof Error ? error.message : String(error)}`); } } @@ -158,12 +79,73 @@ function getRequirementsContent(message: any): string { return String(message); } +// Supported file types configuration +const SUPPORTED_FILE_TYPES: Record = { + pdf: (file: FileData) => ({ + type: "file", + data: file.content, + mediaType: "application/pdf" + }), + jpeg: (file: FileData) => ({ + type: "image", + image: file.content, + mediaType: "image/jpeg" + }), + jpg: (file: FileData) => ({ + type: "image", + image: file.content, + mediaType: "image/jpeg" + }), + png: (file: FileData) => ({ + type: "image", + image: file.content, + mediaType: "image/png" + }), + txt: (file: FileData, includeFileName: boolean) => { + const txtContent = atob(file.content); + return { + type: "text", + text: includeFileName ? `File: ${file.fileName}\n\n${txtContent}` : txtContent + }; + }, + csv: (file: FileData, includeFileName: boolean) => { + const txtContent = atob(file.content); + return { + type: "text", + text: includeFileName ? `File: ${file.fileName}\n\n${txtContent}` : txtContent + }; + } +}; + // Get file extension from filename function getFileExtension(fileName: string): string { const extension = fileName.toLowerCase().split('.').pop(); return extension || ""; } +// Convert file to content part for Claude API +function convertFileToContentPart(file: FileData, includeFileName: boolean = false): ContentPart { + const extension = getFileExtension(file.fileName); + + const handler = SUPPORTED_FILE_TYPES[extension]; + + if (handler) { + return handler(file, includeFileName); + } + + // Fallback for files without extension + if (!extension) { + const txtContent = atob(file.content); + return { + type: "text", + text: includeFileName ? `File: ${file.fileName}\n\n${txtContent}` : txtContent + }; + } + + const supportedTypes = Object.keys(SUPPORTED_FILE_TYPES).join(', '); + throw new Error(`Unsupported file type: ${extension}. Supported types are: ${supportedTypes}`); +} + // Prompt generation functions function getMappingInstructionPrompt(): string { return `You are an AI assistant specialized in generating data field mappings for data integration and transformation tasks. @@ -256,96 +238,48 @@ Generate only Ballerina code with in tags based on the provided } function getRecordsPrompt(): string { - return `You are an AI assistant specializing in the Ballerina programming language. -Your task is to analyze given content and create Ballerina code for type records based on the content provided. - -IMPORTANT: - - Do not take any assumptions based on data types or records. - - Do not include any comments in the code - - Final output has only Ballerina code within tags. - - Extract as much as all possible records and fields - -Please follow these steps to create the Ballerina code: - - 1. Analyze the content: - a) If it is an image, Input records appear on the left side of the image, and output records appear on the right side. - b) All subfields of nested fields or subfields should be structured hierarchically, expanding downwards recursively within their respective parent fields. This hierarchy should reflect nested relationships. - c) Must extract all records and their all fields and their data types in the content. - d) Using and refer to all links or hyperlinks that provide additional information about records and data types in the content. - e) Quote and number specific parts of the content that mention record types and data types. - f) List all record types mentioned in the content, numbering them (e.g., 1. RecordType1, 2. RecordType2, ...). - g) For each record type, list it's all fields and their exact data types as mentioned in the content, also numbering them (e.g., 1.1 field1: SI, 1.2 field2: int, ... ). - h) Identify any nested structures and explain how they relate to the main records. - i) Summarize and use relevant comments or conditions or additional information about the records or data types in the content. - - 2. Define the record types: - Based on your analysis: - - Create a type record for each identified record with its sub-fields - - Consider all records and fields with optional and nullable feature - - Use only the exact data types you identified in step 1 for each field and record - - Apply these naming conventions: PascalCase for record names, camelCase for field names - - For nested fields, create recursive record types, stopping at simple data types - -After your analysis, provide the Ballerina code within tags. The code should include: - - Type record definitions for all identified records with its all fields - -Example output structure (generic, without specific content): - - -type RecordName1 record { - FieldDataType1 fieldName1; - FieldDataType2 fieldName2; - }; - -type RecordName2 record { - FieldDataType3 fieldName3; - RecordName1 nestedField; -}; + return `You are an AI assistant specializing in the Ballerina programming language. Your task is to analyze provided content and generate comprehensive Ballerina type record definitions based on all the information present in that content. -type RecordName3 record { - FieldDataType4 fieldName4; - RecordName4 nestedField; -}; - +Your goal is to extract every possible record type and field from this content and convert them into proper Ballerina type record definitions. You must capture all the information mentioned - leave nothing out. -Sample example for the required format: +## Code Generation Requirements -type Person record { - int? id?; - string firstName; - string? lastName; - int? age; - string country?; - College? college?; -}; +Generate Ballerina code that includes: -type College record { - Course[] courses; -}; +- Type record definitions for ALL identified records with ALL their fields +- Proper handling of optional (\`?\`) and nullable features - but ONLY when explicitly mentioned as optional or nullable in the content +- Correct Ballerina naming conventions +- No comments in the generated code +- No assumptions beyond what's explicitly stated in the content -type Course record { - string? id?; - decimal credits?; - Address? address; -}; +## Enum Declaration Format -type Student record { - string id; - string firstName; - float? age; - record { - int id; - float credits?; - Address address; - }[] courses; -}; +When you encounter enumerated types, use this specific syntax: -type A record { - Person[] person; +\`\`\`ballerina +enum EnumName { + VALUE1, + VALUE2, + VALUE3 }; +\`\`\` + +## Output Format + +Present your final code within \`\` tags. Structure your code as follows: + +- Place all enum definitions first +- Follow with type record definitions +- Use proper Ballerina syntax throughout -type B record { - Student[] student?; +Example structure (generic structure only): + +type Person record { + int? id?; + string firstName; + string? lastName; + int? age; + Address? address?; }; type Address record { @@ -354,7 +288,14 @@ type Address record { string? zipcode; }; -Generate only Ballerina code with in tags based on the provided content.`; +enum Gender { + MALE, + FEMALE, + OTHER +}; + +Generate only Ballerina code with in tags based on the provided content. +`; } function getRequirementsPrompt(): string { @@ -403,93 +344,28 @@ function getPromptForProcessType(processType: ProcessType): string { } } -// Claude API integration functions -async function extractionUsingClaude({ pdfData, processType }: { pdfData: string; processType: ProcessType }): Promise { +// Process files with Claude (handles both single and multiple files) +async function processFilesWithClaude(files: FileData[], processType: ProcessType): Promise { const promptText = getPromptForProcessType(processType); - - const messages: ModelMessage[] = [ - { - role: "user", - content: [ - { - type: "file", - data: pdfData, - mediaType: "application/pdf" - }, - { - type: "text", - text: promptText - } - ] - } - ]; - const { text } = await generateText({ - model: await getAnthropicClient(ANTHROPIC_SONNET_4), - maxOutputTokens: 8192, - temperature: 0, - messages: messages, - abortSignal: AIPanelAbortController.getInstance().signal - }); - - return text; -} + // Build content array with all files + const contentParts: Array = []; + const includeFileName = files.length > 1; -async function imageExtractionUsingClaude({ - imgData, - processType, - extension -}: { - imgData: string; - processType: ProcessType; - extension: string; -}): Promise { - const promptText = getPromptForProcessType(processType); - - // Convert extension to proper media type - const mimeType = extension === "png" ? "image/png" : "image/jpeg"; - - const messages: ModelMessage[] = [ - { - role: "user", - content: [ - { - type: "image", - image: imgData, - mediaType: mimeType - }, - { - type: "text", - text: promptText - } - ] - } - ]; + for (const file of files) { + contentParts.push(convertFileToContentPart(file, includeFileName)); + } - const { text } = await generateText({ - model: await getAnthropicClient(ANTHROPIC_SONNET_4), - maxOutputTokens: 8192, - temperature: 0, - messages: messages, - abortSignal: AIPanelAbortController.getInstance().signal + // Add the prompt at the end + contentParts.push({ + type: "text", + text: promptText }); - return text; -} - -async function textExtractionUsingClaude({ - textContent, - processType -}: { - textContent: string; - processType: ProcessType; -}): Promise { - const promptText = getPromptForProcessType(processType); - const messages: ModelMessage[] = [ { role: "user", - content: promptText + "\n\n" + textContent + content: contentParts } ]; @@ -505,21 +381,21 @@ async function textExtractionUsingClaude({ } // Utility functions for specific use cases -export async function generateMappingInstruction(input: { file?: FileData; text?: string }): Promise { +export async function generateMappingInstruction(input: { files: FileData[]; text?: string }): Promise { return await processDataMapperInput({ ...input, processType: "mapping_instruction" }); } -export async function generateRecord(input: { file?: FileData; text?: string }): Promise { +export async function generateRecord(input: { files: FileData[]; text?: string }): Promise { return await processDataMapperInput({ ...input, processType: "records" }); } -export async function extractRequirements(input: { file?: FileData; text?: string }): Promise { +export async function extractRequirements(input: { files: FileData[]; text?: string }): Promise { return await processDataMapperInput({ ...input, processType: "requirements", diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/datamapper.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/datamapper.ts index f52c144b3f0..b2c105a1be4 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/datamapper.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/datamapper.ts @@ -29,7 +29,7 @@ import { getDataMappingPrompt } from "./dataMappingPrompt"; import { getBallerinaCodeRepairPrompt } from "./codeRepairPrompt"; import { CopilotEventHandler, createWebviewEventHandler } from "../event"; import { getErrorMessage } from "../utils"; -import { buildMappingFileArray, buildRecordMap, collectExistingFunctions, collectModuleInfo, createTempBallerinaDir, createTempFileAndGenerateMetadata, getFunctionDefinitionFromSyntaxTree, getUniqueFunctionFilePaths, prepareMappingContext, generateInlineMappingsSource, generateTypesFromContext, extractRecordTypes, repairCodeAndGetUpdatedContent, extractImports, generateDataMapperModel, determineCustomFunctionsPath, generateMappings, getCustomFunctionsContent } from "../../dataMapping"; +import { buildMappingFileArray, buildRecordMap, collectExistingFunctions, collectModuleInfo, createTempBallerinaDir, createTempFileAndGenerateMetadata, getFunctionDefinitionFromSyntaxTree, getUniqueFunctionFilePaths, prepareMappingContext, generateInlineMappingsSource, generateTypesFromContext, repairCodeAndGetUpdatedContent, extractImports, generateDataMapperModel, determineCustomFunctionsPath, generateMappings, getCustomFunctionsContent } from "../../dataMapping"; import { BiDiagramRpcManager, getBallerinaFiles } from "../../../../../src/rpc-managers/bi-diagram/rpc-manager"; import { updateSourceCode } from "../../../../../src/utils/source-utils"; import { StateMachine } from "../../../../stateMachine"; @@ -764,24 +764,20 @@ export async function generateContextTypesCore(typeCreationRequest: ProcessConte try { const biDiagramRpcManager = new BiDiagramRpcManager(); + const langClient = StateMachine.langClient(); const projectComponents = await biDiagramRpcManager.getProjectComponents(); - // Generate types from context - const { typesCode, filePath, recordMap } = await generateTypesFromContext( + // Generate types from context with validation + const { typesCode, filePath } = await generateTypesFromContext( typeCreationRequest.attachments, - projectComponents + projectComponents, + langClient ); - const extractedNewRecords = extractRecordTypes(typesCode); - for (const newRecord of extractedNewRecords) { - if (recordMap.has(newRecord.name)) { - throw new Error(`Record "${newRecord.name}" already exists in the workspace.`); - } - } - // Build assistant response - const sourceAttachmentName = typeCreationRequest.attachments?.[0]?.name || "attachment"; - assistantResponse = `Record types generated from the ${sourceAttachmentName} file shown below.\n`; + const sourceAttachmentNames = typeCreationRequest.attachments?.map(a => a.name).join(", ") || "attachment"; + const fileText = typeCreationRequest.attachments?.length === 1 ? "file" : "files"; + assistantResponse = `Record types generated from the ${sourceAttachmentNames} ${fileText} shown below.\n`; assistantResponse += `\n\`\`\`ballerina\n${typesCode}\n\`\`\`\n`; // Send assistant response through event handler diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/types.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/types.ts index 34c28c33ed3..830839612a8 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/types.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/types.ts @@ -87,3 +87,35 @@ export interface ChatResponse { total_tokens: number; }; } + +// ============================================================================= +// CONTEXT API CONTENT TYPES +// ============================================================================= + +export type FileData = { + fileName: string; + content: string; +}; + +export type ContentPart = { + type: "file" | "image" | "text"; + data?: string; + image?: string; + text?: string; + mediaType?: string; +}; + +export type FileTypeHandler = (file: FileData, includeFileName: boolean) => ContentPart; + +export type ProcessType = "mapping_instruction" | "records" | "requirements"; + +export type DataMapperRequest = { + files: FileData[]; + text?: string; + processType: ProcessType; + isRequirementAnalysis?: boolean; //TODO: Why is this +}; + +export type DataMapperResponse = { + fileContent: string; +}; diff --git a/workspaces/ballerina/ballerina-extension/src/features/debugger/config-provider.ts b/workspaces/ballerina/ballerina-extension/src/features/debugger/config-provider.ts index ce7e26801af..18b6c9890b4 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/debugger/config-provider.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/debugger/config-provider.ts @@ -40,7 +40,13 @@ import { TM_EVENT_START_DEBUG_SESSION, CMP_DEBUGGER, sendTelemetryEvent, sendTelemetryException, CMP_NOTEBOOK, TM_EVENT_START_NOTEBOOK_DEBUG } from '../telemetry'; -import { log, debug as debugLog, isSupportedSLVersion, isWindows, checkIsBallerinaPackage } from "../../utils"; +import { + log, + debug as debugLog, + isSupportedSLVersion, + isWindows, + createVersionNumber +} from "../../utils"; import { getProjectWorkingDirectory } from "../../utils/file-utils"; import { decimal, ExecutableOptions } from 'vscode-languageclient/node'; import { BAL_NOTEBOOK, getTempFile, NOTEBOOK_CELL_SCHEME } from '../../views/notebook'; @@ -654,7 +660,7 @@ class BIRunAdapter extends LoggingDebugSession { runCommand = `${runCommand} -- ${programArgs.join(' ')}`; } - if (isSupportedSLVersion(extension.ballerinaExtInstance, 2201130) && extension.ballerinaExtInstance.enabledExperimentalFeatures()) { + if (isSupportedSLVersion(extension.ballerinaExtInstance, createVersionNumber(2201, 13, 0)) && extension.ballerinaExtInstance.enabledExperimentalFeatures()) { runCommand = `${runCommand} --experimental`; } diff --git a/workspaces/ballerina/ballerina-extension/src/features/natural-programming/activator.ts b/workspaces/ballerina/ballerina-extension/src/features/natural-programming/activator.ts index f27851bbb6b..2c8397cc9ef 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/natural-programming/activator.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/natural-programming/activator.ts @@ -28,11 +28,11 @@ import { MONITERED_EXTENSIONS, COMMAND_SHOW_TEXT } from './constants'; -import { isSupportedSLVersion } from "../../utils"; +import { isSupportedSLVersion, createVersionNumber } from "../../utils"; import { CustomDiagnostic } from './custom-diagnostics'; let diagnosticCollection: vscode.DiagnosticCollection; -const BALLERINA_UPDATE_13 = 2201130; +const BALLERINA_UPDATE_13 = createVersionNumber(2201, 13, 0); // Version 2201.13.0 export function activate(ballerinaExtInstance: BallerinaExtension) { const backgroundDriftCheckConfig = vscode.workspace.getConfiguration().get(ENABLE_BACKGROUND_DRIFT_CHECK); diff --git a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/build.ts b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/build.ts index 50eb77e5998..ca08706b1d5 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/build.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/build.ts @@ -26,7 +26,7 @@ import { runCommand, BALLERINA_COMMANDS, PROJECT_TYPE, PALETTE_COMMANDS, MESSAGE from "./cmd-runner"; import { getCurrentBallerinaProject, getCurrenDirectoryPath, getCurrentBallerinaFile } from "../../../utils/project-utils"; -import { isSupportedSLVersion } from "../../../utils"; +import { isSupportedSLVersion, createVersionNumber } from "../../../utils"; export function activateBuildCommand() { // register run project build handler @@ -45,7 +45,7 @@ export function activateBuildCommand() { let balCommand = BALLERINA_COMMANDS.BUILD; - if (isSupportedSLVersion(extension.ballerinaExtInstance, 2201130) && extension.ballerinaExtInstance.enabledExperimentalFeatures()) { + if (isSupportedSLVersion(extension.ballerinaExtInstance, createVersionNumber(2201, 13, 0)) && extension.ballerinaExtInstance.enabledExperimentalFeatures()) { balCommand = BALLERINA_COMMANDS.BUILD_WITH_EXPERIMENTAL; } diff --git a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/cmd-runner.ts b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/cmd-runner.ts index 74b52546772..13daaab78aa 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/cmd-runner.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/cmd-runner.ts @@ -18,7 +18,7 @@ import { BallerinaProject } from "@wso2/ballerina-core"; import { Terminal, window, workspace } from "vscode"; -import { isSupportedSLVersion, isWindows } from "../../../utils"; +import { isSupportedSLVersion, isWindows, createVersionNumber } from "../../../utils"; import { extension } from "../../../BalExtensionContext"; import { TracerMachine } from "../../../features/tracing"; @@ -197,7 +197,7 @@ export function createTerminal(path: string, env?: { [key: string]: string }): v } export function getRunCommand(): BALLERINA_COMMANDS { - if (isSupportedSLVersion(extension.ballerinaExtInstance, 2201130) && extension.ballerinaExtInstance.enabledExperimentalFeatures()) { + if (isSupportedSLVersion(extension.ballerinaExtInstance, createVersionNumber(2201, 13, 0)) && extension.ballerinaExtInstance.enabledExperimentalFeatures()) { return BALLERINA_COMMANDS.RUN_WITH_EXPERIMENTAL; } else if (extension.ballerinaExtInstance.enabledLiveReload()) { return BALLERINA_COMMANDS.RUN_WITH_WATCH; diff --git a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/xml-to-record.ts b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/xml-to-record.ts index 294569cef24..4b3801cf125 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/xml-to-record.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/xml-to-record.ts @@ -22,7 +22,7 @@ import { import { commands, window, env } from "vscode"; import { extension } from "../../../BalExtensionContext"; import { PALETTE_COMMANDS, MESSAGES } from "./cmd-runner"; -import { isSupportedSLVersion } from "../../../utils"; +import { isSupportedSLVersion, createVersionNumber } from "../../../utils"; import { DIAGNOSTIC_SEVERITY, XMLToRecord } from "@wso2/ballerina-core"; const MSG_NOT_SUPPORT = "Paste XML as a Ballerina record feature is not supported"; @@ -34,8 +34,8 @@ export function activatePasteXMLAsRecord() { } commands.registerCommand(PALETTE_COMMANDS.PASTE_XML_AS_RECORD, () => { - // This command is only available since Swan Lake Update 7 patch 2 - if (!isSupportedSLVersion(extension.ballerinaExtInstance, 220172)) { + // This command is only available since Swan Lake Update 7 patch 2 (2201.7.2) + if (!isSupportedSLVersion(extension.ballerinaExtInstance, createVersionNumber(2201, 7, 2))) { window.showErrorMessage(`${MSG_NOT_SUPPORT} in ${extension.ballerinaExtInstance.ballerinaVersion}`); return; } diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/utils.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/utils.ts index 904c00faf7b..f2b6341e81e 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/utils.ts @@ -22,7 +22,8 @@ import { Position, Range, Uri, workspace, WorkspaceEdit } from 'vscode'; import path from "path"; import * as fs from 'fs'; import { AIChatError } from "./utils/errors"; -import { DataMapperRequest, DataMapperResponse, FileData, processDataMapperInput } from "../../../src/features/ai/service/datamapper/context_api"; +import { processDataMapperInput } from "../../../src/features/ai/service/datamapper/context_api"; +import { DataMapperRequest, DataMapperResponse, FileData } from "../../../src/features/ai/service/datamapper/types"; import { getAskResponse } from "../../../src/features/ai/service/ask/ask"; import { MappingFileRecord} from "./types"; import { generateAutoMappings, generateRepairCode } from "../../../src/features/ai/service/datamapper/datamapper"; @@ -150,16 +151,16 @@ async function convertAttachmentToFileData(attachment: Attachment): Promise { let dataMapperResponse: DataMapperModelResponse = { mappingsModel: dataMapperModel as DMModel }; - if (mappingInstructionFile) { - const enhancedResponse = await enrichModelWithMappingInstructions(mappingInstructionFile, dataMapperResponse); + if (mappingInstructionFiles.length > 0) { + const enhancedResponse = await enrichModelWithMappingInstructions(mappingInstructionFiles, dataMapperResponse); dataMapperResponse = enhancedResponse as DataMapperModelResponse; } @@ -172,12 +173,16 @@ export async function generateMappingExpressionsFromModel( })); } -// Processes a mapping instruction file and merges it with the existing data mapper model -export async function enrichModelWithMappingInstructions(mappingInstructionFile: Attachment, currentDataMapperResponse: DataMapperModelResponse): Promise { - if (!mappingInstructionFile) { return currentDataMapperResponse; } - const fileData = await convertAttachmentToFileData(mappingInstructionFile); +// Processes mapping instruction files and merges them with the existing data mapper model +export async function enrichModelWithMappingInstructions(mappingInstructionFiles: Attachment[], currentDataMapperResponse: DataMapperModelResponse): Promise { + if (!mappingInstructionFiles || mappingInstructionFiles.length === 0) { return currentDataMapperResponse; } + + const fileDataArray = await Promise.all( + mappingInstructionFiles.map(file => convertAttachmentToFileData(file)) + ); + const requestParams: DataMapperRequest = { - file: fileData, + files: fileDataArray, processType: "mapping_instruction" }; const response: DataMapperResponse = await processDataMapperInput(requestParams); @@ -206,12 +211,18 @@ export async function repairSourceFilesWithAI(codeRepairRequest: repairCodeReque // Type Creator related functions // Extracts type definitions from a file attachment and generates Ballerina record definitions -export async function extractRecordTypeDefinitionsFromFile(sourceFile: Attachment): Promise { - if (!sourceFile) { throw new Error("File is undefined"); } +export async function extractRecordTypeDefinitionsFromFile(sourceFiles: Attachment[]): Promise { + if (sourceFiles.length === 0) { + throw new Error("No files provided"); + } + + // Process all files together to understand correlations + const fileDataArray = await Promise.all( + sourceFiles.map(attachment => convertAttachmentToFileData(attachment)) + ); - const fileData = await convertAttachmentToFileData(sourceFile); const requestParams: DataMapperRequest = { - file: fileData, + files: fileDataArray, processType: "records" }; const response: DataMapperResponse = await processDataMapperInput(requestParams); @@ -230,7 +241,7 @@ export async function requirementsSpecification(filepath: string): Promise rpcManger.getSTByRange(args)); messenger.onRequest(getBallerinaProjectComponents, (args: BallerinaPackagesParams) => rpcManger.getBallerinaProjectComponents(args)); messenger.onRequest(getBallerinaVersion, () => rpcManger.getBallerinaVersion()); + messenger.onRequest(isSupportedSLVersion, (args: SemanticVersion) => rpcManger.isSupportedSLVersion(args)); messenger.onRequest(getCompletion, (args: CompletionRequest) => rpcManger.getCompletion(args)); messenger.onRequest(getDiagnostics, (args: SyntaxTreeParams) => rpcManger.getDiagnostics(args)); messenger.onRequest(getProjectDiagnostics, (args: ProjectDiagnosticsRequest) => rpcManger.getProjectDiagnostics(args)); diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/lang-client/rpc-manager.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/lang-client/rpc-manager.ts index c6d7f63b837..ce3f5d9ab22 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/lang-client/rpc-manager.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/lang-client/rpc-manager.ts @@ -22,6 +22,7 @@ import { BallerinaProjectComponents, BallerinaSTParams, BallerinaVersionResponse, + SemanticVersion, CodeActionRequest, CodeActionResponse, CompletionRequest, @@ -63,6 +64,7 @@ import { URI } from "vscode-uri"; import { extension } from "../../BalExtensionContext"; import { StateMachine } from "../../stateMachine"; import { modifyFileContent } from "../../utils/modification"; +import { isSupportedSLVersion as isSupportedSLVersionUtil } from "../../utils/config"; export class LangClientRpcManager implements LangClientAPI { @@ -283,6 +285,10 @@ export class LangClientRpcManager implements LangClientAPI { }); } + async isSupportedSLVersion(params: SemanticVersion): Promise { + return isSupportedSLVersionUtil(extension.ballerinaExtInstance, params); + } + async getPackageComponentModels(params: ComponentModelsParams): Promise { return new Promise(async (resolve) => { const components = await StateMachine.langClient().getPackageComponentModels(params) as ComponentModels; diff --git a/workspaces/ballerina/ballerina-extension/src/utils/config.ts b/workspaces/ballerina/ballerina-extension/src/utils/config.ts index b5f4f8cfebf..16407d567ae 100644 --- a/workspaces/ballerina/ballerina-extension/src/utils/config.ts +++ b/workspaces/ballerina/ballerina-extension/src/utils/config.ts @@ -16,7 +16,7 @@ * under the License. */ -import { PackageTomlValues, SCOPE, WorkspaceTomlValues } from '@wso2/ballerina-core'; +import { SemanticVersion, PackageTomlValues, SCOPE, WorkspaceTomlValues } from '@wso2/ballerina-core'; import { BallerinaExtension } from '../core'; import { WorkspaceConfiguration, workspace, Uri, RelativePattern } from 'vscode'; import * as fs from 'fs'; @@ -98,18 +98,92 @@ export function isSupportedVersion(ballerinaExtInstance: BallerinaExtension, sup return false; } -export function isSupportedSLVersion(ballerinaExtInstance: BallerinaExtension, minSupportedVersion: number) { +/** + * Creates a version object for comparison. + * + * @param major Major version number + * @param minor Minor version number + * @param patch Patch version number + * @returns A version object with major, minor, and patch components + * + * @example + * // Version 2201.1.30 + * createVersionNumber(2201, 1, 30) + * // Version 2201.12.10 + * createVersionNumber(2201, 12, 10) + */ +export function createVersionNumber( + major: number, + minor: number, + patch: number +): SemanticVersion { + return { major, minor, patch }; +} + +/** + * Compares two versions using semantic versioning rules. + * Returns true if current version >= minimum version. + * + * @param current Current version components + * @param minimum Minimum required version components + * @returns true if current >= minimum + */ +function compareVersions( + current: SemanticVersion, + minimum: SemanticVersion +): boolean { + // Compare major version first + if (current.major !== minimum.major) { + return current.major > minimum.major; + } + + // Major versions are equal, compare minor + if (current.minor !== minimum.minor) { + return current.minor > minimum.minor; + } + + // Major and minor are equal, compare patch + return current.patch >= minimum.patch; +} + +/** + * Compares the current Ballerina version against a minimum required version. + * Only returns true for GA (non-preview/alpha/beta) versions that meet or exceed the minimum. + * + * @param ballerinaExtInstance The Ballerina extension instance + * @param minSupportedVersion Minimum version (use createVersionNumber helper to generate) + * @returns true if current version is GA and meets minimum requirement + * + * @example + * // Check if version is at least 2201.1.30 + * isSupportedSLVersion(ext, createVersionNumber(2201, 1, 30)) + */ +export function isSupportedSLVersion( + ballerinaExtInstance: BallerinaExtension, + minSupportedVersion: SemanticVersion +) { const ballerinaVersion: string = ballerinaExtInstance.ballerinaVersion.toLocaleLowerCase(); const isGA: boolean = !ballerinaVersion.includes(VERSION.ALPHA) && !ballerinaVersion.includes(VERSION.BETA) && !ballerinaVersion.includes(VERSION.PREVIEW); + if (!isGA) { + return false; + } + + // Parse current version const regex = /(\d+)\.(\d+)\.(\d+)/; const match = ballerinaVersion.match(regex); - const currentVersionNumber = match ? Number(match.slice(1).join("")) : 0; - - if (minSupportedVersion <= currentVersionNumber && isGA) { - return true; + if (!match) { + return false; } - return false; + + const currentVersion = { + major: Number(match[1]), + minor: Number(match[2]), + patch: Number(match[3]) + }; + + // Compare versions component by component + return compareVersions(currentVersion, minSupportedVersion); } export function checkIsBI(uri: Uri): boolean { diff --git a/workspaces/ballerina/ballerina-extension/src/utils/server/server.ts b/workspaces/ballerina/ballerina-extension/src/utils/server/server.ts index df95ea9b0e0..d817676ef4c 100644 --- a/workspaces/ballerina/ballerina-extension/src/utils/server/server.ts +++ b/workspaces/ballerina/ballerina-extension/src/utils/server/server.ts @@ -21,7 +21,7 @@ import { debug, log } from '../logger'; import { ServerOptions, ExecutableOptions } from 'vscode-languageclient/node'; import { isWindows } from '..'; import { BallerinaExtension } from '../../core'; -import { isSupportedSLVersion } from '../config'; +import { isSupportedSLVersion, createVersionNumber } from '../config'; import * as fs from 'fs'; import * as path from 'path'; import { orderBy } from 'lodash'; @@ -150,7 +150,7 @@ export function findHighestVersionJdk(directory: string): string | null { export function getServerOptions(extension: BallerinaExtension): ServerOptions { debug('Getting server options.'); // Check if user wants to use Ballerina CLI language server or if version requires it - const BI_SUPPORTED_MINIMUM_VERSION = 2201123; // 2201.12.3 + const BI_SUPPORTED_MINIMUM_VERSION = createVersionNumber(2201, 12, 3); // Version 2201.12.3 if (extension?.useDistributionLanguageServer() || !isSupportedSLVersion(extension, BI_SUPPORTED_MINIMUM_VERSION)) { return getServerOptionsUsingCLI(extension); } else { diff --git a/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/lang-client/rpc-client.ts b/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/lang-client/rpc-client.ts index f8ca9980d51..d32b268b933 100644 --- a/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/lang-client/rpc-client.ts +++ b/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/lang-client/rpc-client.ts @@ -62,6 +62,7 @@ import { didOpen, getBallerinaProjectComponents, getBallerinaVersion, + isSupportedSLVersion, getCompletion, getDefinitionPosition, getDiagnostics, @@ -83,7 +84,8 @@ import { getTypesFromFnDefinition, rename, stModify, - updateFileContent + updateFileContent, + SemanticVersion } from "@wso2/ballerina-core"; import { HOST_EXTENSION } from "vscode-messenger-common"; import { Messenger } from "vscode-messenger-webview"; @@ -115,6 +117,10 @@ export class LangClientRpcClient implements LangClientAPI { return this._messenger.sendRequest(getBallerinaVersion, HOST_EXTENSION); } + isSupportedSLVersion(params: SemanticVersion): Promise { + return this._messenger.sendRequest(isSupportedSLVersion, HOST_EXTENSION, params); + } + getCompletion(params: CompletionRequest): Promise { return this._messenger.sendRequest(getCompletion, HOST_EXTENSION, params); } diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/commandTemplates/data/commandTemplates.const.ts b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/commandTemplates/data/commandTemplates.const.ts index f6ba0905cbe..7b29a6be323 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/commandTemplates/data/commandTemplates.const.ts +++ b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/commandTemplates/data/commandTemplates.const.ts @@ -111,7 +111,7 @@ export const commandTemplates = { [Command.TypeCreator]: [ { id: TemplateId.TypesForAttached, - text: 'generate types using the attached file', + text: 'generate types using the attached files', placeholders: [] } ], diff --git a/workspaces/ballerina/record-creator/src/CreateRecord/index.tsx b/workspaces/ballerina/record-creator/src/CreateRecord/index.tsx index d63249d496f..ea7bb748fe8 100644 --- a/workspaces/ballerina/record-creator/src/CreateRecord/index.tsx +++ b/workspaces/ballerina/record-creator/src/CreateRecord/index.tsx @@ -23,7 +23,6 @@ import { RecordConfigTypeSelector } from "../RecordConfigTypeSelector"; import { RecordFromJson } from "../RecordFromJson"; import { RecordFromXml } from "../RecordFromXml"; import { Context } from "../Context"; -import { isSupportedSLVersion } from "../components/FormComponents/Utils"; import { FormContainer } from "../style"; enum ConfigState { @@ -43,9 +42,7 @@ export interface CreateRecordProps { export function CreateRecord(props: CreateRecordProps) { const { isDataMapper, showHeader, onSave, onCancel, onUpdate } = props; - const { - props: { targetPosition, ballerinaVersion }, - } = useContext(Context); + const { props: { isVersionCompatible } } = useContext(Context); const [editorState, setEditorState] = useState(ConfigState.STATE_SELECTOR); @@ -65,20 +62,13 @@ export function CreateRecord(props: CreateRecordProps) { onSave(value, pos); }; - const checkBallerinVersion = () => { - if (ballerinaVersion) { - return isSupportedSLVersion(ballerinaVersion, 220172); - } - return false; - }; - return ( <> {editorState === ConfigState.STATE_SELECTOR && ( diff --git a/workspaces/ballerina/record-creator/src/Hooks/index.tsx b/workspaces/ballerina/record-creator/src/Hooks/index.tsx index 5d1b9271b46..3fdc22e71df 100644 --- a/workspaces/ballerina/record-creator/src/Hooks/index.tsx +++ b/workspaces/ballerina/record-creator/src/Hooks/index.tsx @@ -20,13 +20,15 @@ import { BallerinaProjectComponents, SyntaxTreeResponse } from "@wso2/ballerina- import { LangClientRpcClient } from "@wso2/ballerina-rpc-client"; import { URI } from "vscode-uri"; +const RECORD_CREATOR_SUPPORTED_VERSION = {major: 2201, minor: 7, patch: 2}; + export const useBallerinaVersion = ( langServerRpcClient: LangClientRpcClient ): { ballerinaVersion: string; isFetching: boolean; isError: boolean; - refetch: any; + refetch: () => void; } => { const fetchBallerinaVersion = async () => { try { @@ -51,6 +53,37 @@ export const useBallerinaVersion = ( return { ballerinaVersion, isFetching, isError, refetch }; }; +export const useVersionCompatibility = ( + langServerRpcClient: LangClientRpcClient +): { + isVersionCompatible: boolean; + isFetching: boolean; + isError: boolean; + refetch: () => void; +} => { + const checkVersionCompatibility = async () => { + try { + const isCompatible = await langServerRpcClient.isSupportedSLVersion(RECORD_CREATOR_SUPPORTED_VERSION); + return isCompatible; + } catch (networkError: any) { + console.error("Error while checking version compatibility in record creator", networkError); + } + }; + + const { + data: isVersionCompatible, + isFetching, + isError, + refetch, + } = useQuery({ + queryKey: ["checkVersionCompatibility"], + queryFn: checkVersionCompatibility, + networkMode: 'always' + }); + + return { isVersionCompatible, isFetching, isError, refetch }; +}; + export const useFullST = ( filePath: string, langServerRpcClient: LangClientRpcClient diff --git a/workspaces/ballerina/record-creator/src/RecordEditor/RecordEditorWrapper.tsx b/workspaces/ballerina/record-creator/src/RecordEditor/RecordEditorWrapper.tsx index 4c365d880b6..715e001caa4 100644 --- a/workspaces/ballerina/record-creator/src/RecordEditor/RecordEditorWrapper.tsx +++ b/workspaces/ballerina/record-creator/src/RecordEditor/RecordEditorWrapper.tsx @@ -18,7 +18,7 @@ // tslint:disable: jsx-no-multiline-js import React, { useMemo } from "react"; import { Context } from "../Context"; -import { useBallerinaProjectComponent, useBallerinaVersion, useFullST } from "../Hooks"; +import { useBallerinaProjectComponent, useBallerinaVersion, useFullST, useVersionCompatibility } from "../Hooks"; import { RecordEditorC } from "./RecordEditorC"; import { RecordEditorProps } from "."; @@ -42,12 +42,17 @@ export function RecordEditorWrapper(props: RecordEditorProps) { onUpdate, } = props; const { ballerinaVersion, isFetching: isFetchingBallerinaVersion } = useBallerinaVersion(langServerRpcClient); + const { isVersionCompatible, isFetching: isFetchingVersionCompatibility } = useVersionCompatibility(langServerRpcClient); const { fullST, isFetching: isFetchingFullST } = useFullST(currentFile.path, langServerRpcClient); const { ballerinaProjectComponents, isFetching: isFetchingBallerinaProjectComponents } = useBallerinaProjectComponent(currentFile.path, langServerRpcClient); const contextValue = useMemo(() => { - if (isFetchingBallerinaVersion || isFetchingFullST || isFetchingBallerinaProjectComponents) { + if (isFetchingBallerinaVersion || + isFetchingFullST || + isFetchingBallerinaProjectComponents || + isFetchingVersionCompatibility + ) { return undefined; } @@ -61,6 +66,7 @@ export function RecordEditorWrapper(props: RecordEditorProps) { importStatements, currentReferences, ballerinaVersion, + isVersionCompatible, fullST : fullSyntaxTree || fullST.syntaxTree, ballerinaProjectComponents, }, @@ -70,7 +76,14 @@ export function RecordEditorWrapper(props: RecordEditorProps) { onClose, }, }; - }, [isFetchingBallerinaVersion, isFetchingFullST, isFetchingBallerinaProjectComponents, fullSyntaxTree]); + }, [ + isFetchingBallerinaVersion, + isFetchingVersionCompatibility, + isFetchingFullST, + isFetchingBallerinaProjectComponents, + fullSyntaxTree, + isVersionCompatible, + ]); return ( diff --git a/workspaces/ballerina/record-creator/src/components/FormComponents/Utils/index.tsx b/workspaces/ballerina/record-creator/src/components/FormComponents/Utils/index.tsx index 8ff687ef61a..5242193147a 100644 --- a/workspaces/ballerina/record-creator/src/components/FormComponents/Utils/index.tsx +++ b/workspaces/ballerina/record-creator/src/components/FormComponents/Utils/index.tsx @@ -22,7 +22,6 @@ import { LangClientRpcClient } from "@wso2/ballerina-rpc-client"; import { DiagnosticData, DiagnosticsResponse } from "@wso2/ballerina-core"; import { Codicon, Tooltip, Typography } from "@wso2/ui-toolkit"; import * as monaco from "monaco-editor"; -import { VERSION } from "../../../types"; export const FILE_SCHEME = "file://"; export const EXPR_SCHEME = "expr://"; @@ -109,23 +108,6 @@ async function sendDidChange(docUri: string, content: string, langServerRpcClien }); } -export function isSupportedSLVersion(balVersion: string, minSupportedVersion: number) { - const ballerinaVersion: string = balVersion.toLocaleLowerCase(); - const isGA: boolean = - !ballerinaVersion.includes(VERSION.ALPHA) && - !ballerinaVersion.includes(VERSION.BETA) && - !ballerinaVersion.includes(VERSION.PREVIEW); - - const regex = /(\d+)\.(\d+)\.(\d+)/; - const match = ballerinaVersion.match(regex); - const currentVersionNumber = match ? Number(match.slice(1).join("")) : 0; - - if (minSupportedVersion <= currentVersionNumber && isGA) { - return true; - } - return false; -} - export function getTooltipIconComponent(title: string): React.ReactNode { return ( {title}} position="bottom-end"> diff --git a/workspaces/ballerina/record-creator/src/types.ts b/workspaces/ballerina/record-creator/src/types.ts index 8320cda9e97..7c50d693321 100644 --- a/workspaces/ballerina/record-creator/src/types.ts +++ b/workspaces/ballerina/record-creator/src/types.ts @@ -88,6 +88,7 @@ export interface RecordCreatorContext { props: IStatementEditorComponentProps & { recordCreatorRpcClient: RecordCreatorRpcClient; ballerinaVersion: string; + isVersionCompatible: boolean; fullST: STNode; ballerinaProjectComponents: BallerinaProjectComponents; }; diff --git a/workspaces/ballerina/type-editor/src/CreateRecord/index.tsx b/workspaces/ballerina/type-editor/src/CreateRecord/index.tsx index d63249d496f..ea7bb748fe8 100644 --- a/workspaces/ballerina/type-editor/src/CreateRecord/index.tsx +++ b/workspaces/ballerina/type-editor/src/CreateRecord/index.tsx @@ -23,7 +23,6 @@ import { RecordConfigTypeSelector } from "../RecordConfigTypeSelector"; import { RecordFromJson } from "../RecordFromJson"; import { RecordFromXml } from "../RecordFromXml"; import { Context } from "../Context"; -import { isSupportedSLVersion } from "../components/FormComponents/Utils"; import { FormContainer } from "../style"; enum ConfigState { @@ -43,9 +42,7 @@ export interface CreateRecordProps { export function CreateRecord(props: CreateRecordProps) { const { isDataMapper, showHeader, onSave, onCancel, onUpdate } = props; - const { - props: { targetPosition, ballerinaVersion }, - } = useContext(Context); + const { props: { isVersionCompatible } } = useContext(Context); const [editorState, setEditorState] = useState(ConfigState.STATE_SELECTOR); @@ -65,20 +62,13 @@ export function CreateRecord(props: CreateRecordProps) { onSave(value, pos); }; - const checkBallerinVersion = () => { - if (ballerinaVersion) { - return isSupportedSLVersion(ballerinaVersion, 220172); - } - return false; - }; - return ( <> {editorState === ConfigState.STATE_SELECTOR && ( diff --git a/workspaces/ballerina/type-editor/src/Hooks/index.tsx b/workspaces/ballerina/type-editor/src/Hooks/index.tsx index 5d1b9271b46..703499e7b7a 100644 --- a/workspaces/ballerina/type-editor/src/Hooks/index.tsx +++ b/workspaces/ballerina/type-editor/src/Hooks/index.tsx @@ -20,13 +20,15 @@ import { BallerinaProjectComponents, SyntaxTreeResponse } from "@wso2/ballerina- import { LangClientRpcClient } from "@wso2/ballerina-rpc-client"; import { URI } from "vscode-uri"; +const TYPE_EDITOR_SUPPORTED_VERSION = {major: 2201, minor: 7, patch: 2}; + export const useBallerinaVersion = ( langServerRpcClient: LangClientRpcClient ): { ballerinaVersion: string; isFetching: boolean; isError: boolean; - refetch: any; + refetch: () => void; } => { const fetchBallerinaVersion = async () => { try { @@ -51,6 +53,37 @@ export const useBallerinaVersion = ( return { ballerinaVersion, isFetching, isError, refetch }; }; +export const useVersionCompatibility = ( + langServerRpcClient: LangClientRpcClient +): { + isVersionCompatible: boolean; + isFetching: boolean; + isError: boolean; + refetch: () => void; +} => { + const checkVersionCompatibility = async () => { + try { + const isCompatible = await langServerRpcClient.isSupportedSLVersion(TYPE_EDITOR_SUPPORTED_VERSION); + return isCompatible; + } catch (networkError: any) { + console.error("Error while checking version compatibility in type editor", networkError); + } + }; + + const { + data: isVersionCompatible, + isFetching, + isError, + refetch, + } = useQuery({ + queryKey: ["checkVersionCompatibility"], + queryFn: checkVersionCompatibility, + networkMode: 'always' + }); + + return { isVersionCompatible, isFetching, isError, refetch }; +}; + export const useFullST = ( filePath: string, langServerRpcClient: LangClientRpcClient diff --git a/workspaces/ballerina/type-editor/src/RecordEditor/RecordEditorWrapper.tsx b/workspaces/ballerina/type-editor/src/RecordEditor/RecordEditorWrapper.tsx index 4c365d880b6..715e001caa4 100644 --- a/workspaces/ballerina/type-editor/src/RecordEditor/RecordEditorWrapper.tsx +++ b/workspaces/ballerina/type-editor/src/RecordEditor/RecordEditorWrapper.tsx @@ -18,7 +18,7 @@ // tslint:disable: jsx-no-multiline-js import React, { useMemo } from "react"; import { Context } from "../Context"; -import { useBallerinaProjectComponent, useBallerinaVersion, useFullST } from "../Hooks"; +import { useBallerinaProjectComponent, useBallerinaVersion, useFullST, useVersionCompatibility } from "../Hooks"; import { RecordEditorC } from "./RecordEditorC"; import { RecordEditorProps } from "."; @@ -42,12 +42,17 @@ export function RecordEditorWrapper(props: RecordEditorProps) { onUpdate, } = props; const { ballerinaVersion, isFetching: isFetchingBallerinaVersion } = useBallerinaVersion(langServerRpcClient); + const { isVersionCompatible, isFetching: isFetchingVersionCompatibility } = useVersionCompatibility(langServerRpcClient); const { fullST, isFetching: isFetchingFullST } = useFullST(currentFile.path, langServerRpcClient); const { ballerinaProjectComponents, isFetching: isFetchingBallerinaProjectComponents } = useBallerinaProjectComponent(currentFile.path, langServerRpcClient); const contextValue = useMemo(() => { - if (isFetchingBallerinaVersion || isFetchingFullST || isFetchingBallerinaProjectComponents) { + if (isFetchingBallerinaVersion || + isFetchingFullST || + isFetchingBallerinaProjectComponents || + isFetchingVersionCompatibility + ) { return undefined; } @@ -61,6 +66,7 @@ export function RecordEditorWrapper(props: RecordEditorProps) { importStatements, currentReferences, ballerinaVersion, + isVersionCompatible, fullST : fullSyntaxTree || fullST.syntaxTree, ballerinaProjectComponents, }, @@ -70,7 +76,14 @@ export function RecordEditorWrapper(props: RecordEditorProps) { onClose, }, }; - }, [isFetchingBallerinaVersion, isFetchingFullST, isFetchingBallerinaProjectComponents, fullSyntaxTree]); + }, [ + isFetchingBallerinaVersion, + isFetchingVersionCompatibility, + isFetchingFullST, + isFetchingBallerinaProjectComponents, + fullSyntaxTree, + isVersionCompatible, + ]); return ( diff --git a/workspaces/ballerina/type-editor/src/components/FormComponents/Utils/index.tsx b/workspaces/ballerina/type-editor/src/components/FormComponents/Utils/index.tsx index 8ff687ef61a..5242193147a 100644 --- a/workspaces/ballerina/type-editor/src/components/FormComponents/Utils/index.tsx +++ b/workspaces/ballerina/type-editor/src/components/FormComponents/Utils/index.tsx @@ -22,7 +22,6 @@ import { LangClientRpcClient } from "@wso2/ballerina-rpc-client"; import { DiagnosticData, DiagnosticsResponse } from "@wso2/ballerina-core"; import { Codicon, Tooltip, Typography } from "@wso2/ui-toolkit"; import * as monaco from "monaco-editor"; -import { VERSION } from "../../../types"; export const FILE_SCHEME = "file://"; export const EXPR_SCHEME = "expr://"; @@ -109,23 +108,6 @@ async function sendDidChange(docUri: string, content: string, langServerRpcClien }); } -export function isSupportedSLVersion(balVersion: string, minSupportedVersion: number) { - const ballerinaVersion: string = balVersion.toLocaleLowerCase(); - const isGA: boolean = - !ballerinaVersion.includes(VERSION.ALPHA) && - !ballerinaVersion.includes(VERSION.BETA) && - !ballerinaVersion.includes(VERSION.PREVIEW); - - const regex = /(\d+)\.(\d+)\.(\d+)/; - const match = ballerinaVersion.match(regex); - const currentVersionNumber = match ? Number(match.slice(1).join("")) : 0; - - if (minSupportedVersion <= currentVersionNumber && isGA) { - return true; - } - return false; -} - export function getTooltipIconComponent(title: string): React.ReactNode { return ( {title}} position="bottom-end"> diff --git a/workspaces/ballerina/type-editor/src/types.ts b/workspaces/ballerina/type-editor/src/types.ts index 8320cda9e97..7c50d693321 100644 --- a/workspaces/ballerina/type-editor/src/types.ts +++ b/workspaces/ballerina/type-editor/src/types.ts @@ -88,6 +88,7 @@ export interface RecordCreatorContext { props: IStatementEditorComponentProps & { recordCreatorRpcClient: RecordCreatorRpcClient; ballerinaVersion: string; + isVersionCompatible: boolean; fullST: STNode; ballerinaProjectComponents: BallerinaProjectComponents; }; diff --git a/workspaces/bi/bi-extension/package.json b/workspaces/bi/bi-extension/package.json index 97acac33da8..0d460f33f68 100644 --- a/workspaces/bi/bi-extension/package.json +++ b/workspaces/bi/bi-extension/package.json @@ -2,7 +2,7 @@ "name": "ballerina-integrator", "displayName": "WSO2 Integrator: BI", "description": "An extension which gives a development environment for designing, developing, debugging, and testing integration solutions.", - "version": "1.5.0", + "version": "1.5.1", "publisher": "wso2", "icon": "resources/images/wso2-ballerina-integrator-logo.png", "repository": { @@ -142,7 +142,7 @@ }, { "command": "BI.project-explorer.add", - "when": "view == BI.project-explorer && BI.project == true", + "when": "view == BI.project-explorer && BI.project == true && BI.isWorkspaceSupported == true", "group": "navigation" }, { diff --git a/workspaces/bi/bi-extension/src/biExtentionContext.ts b/workspaces/bi/bi-extension/src/biExtentionContext.ts index dfa13fe3303..91022f034e4 100644 --- a/workspaces/bi/bi-extension/src/biExtentionContext.ts +++ b/workspaces/bi/bi-extension/src/biExtentionContext.ts @@ -25,6 +25,7 @@ export class ExtensionVariables { public langClient!: ExtendedLangClientInterface; public biSupported?: boolean; public isNPSupported?: boolean; + public isWorkspaceSupported?: boolean; } export const extension = new ExtensionVariables(); diff --git a/workspaces/bi/bi-extension/src/extension.ts b/workspaces/bi/bi-extension/src/extension.ts index e7c354e0972..993ee4f845f 100644 --- a/workspaces/bi/bi-extension/src/extension.ts +++ b/workspaces/bi/bi-extension/src/extension.ts @@ -27,6 +27,7 @@ export function activate(context: vscode.ExtensionContext) { extension.langClient = ballerinaExt.exports.ballerinaExtInstance.langClient; extension.biSupported = ballerinaExt.exports.ballerinaExtInstance.biSupported; extension.isNPSupported = ballerinaExt.exports.ballerinaExtInstance.isNPSupported; + extension.isWorkspaceSupported = ballerinaExt.exports.ballerinaExtInstance?.isWorkspaceSupported; StateMachine.initialize(); return; } diff --git a/workspaces/bi/bi-extension/src/project-explorer/activate.ts b/workspaces/bi/bi-extension/src/project-explorer/activate.ts index 67abc97fe9c..5b5aec60337 100644 --- a/workspaces/bi/bi-extension/src/project-explorer/activate.ts +++ b/workspaces/bi/bi-extension/src/project-explorer/activate.ts @@ -56,8 +56,9 @@ function createProjectTree(dataProvider: ProjectExplorerEntryProvider) { function registerBallerinaCommands(dataProvider: ProjectExplorerEntryProvider, isBI: boolean, isBalWorkspace?: boolean) { commands.registerCommand(BI_COMMANDS.REFRESH_COMMAND, () => dataProvider.refresh()); + commands.executeCommand('setContext', 'BI.isWorkspaceSupported', extension.isWorkspaceSupported ?? false); - if (isBalWorkspace) { + if (extension.isWorkspaceSupported && isBalWorkspace) { commands.executeCommand('setContext', 'BI.isBalWorkspace', true); } if (isBI) {