diff --git a/client/package-lock.json b/client/package-lock.json index 7886cf10..e0dfcb06 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "Apache-2.0", "dependencies": { + "adm-zip": "^0.5.10", "vscode-languageclient": "^8.0.1" }, "devDependencies": { @@ -25,6 +26,15 @@ "dev": true, "license": "MIT" }, + "node_modules/adm-zip": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", + "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", + "license": "MIT", + "engines": { + "node": ">=12.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", diff --git a/client/package.json b/client/package.json index 8459f084..e488bbfc 100644 --- a/client/package.json +++ b/client/package.json @@ -13,6 +13,7 @@ "vscode": "^1.43.0" }, "dependencies": { + "adm-zip": "^0.5.10", "vscode-languageclient": "^8.0.1" }, "devDependencies": { diff --git a/client/src/extension.ts b/client/src/extension.ts index 778062c9..68966196 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -13,6 +13,7 @@ import initialiseState from './services/state' import * as handlers from './handlers' import { callResolversAndEmptyList } from './services/timers' import { registerFlixReleaseDocumentProvider } from './services/releaseVirtualDocument' +import { registerFlixStdlibDocumentProvider } from './services/stdlibVirtualDocument' import { USER_MESSAGE } from './util/userMessages' import { StatusCode } from './util/statusCodes' @@ -110,6 +111,7 @@ export async function activate(context: vscode.ExtensionContext, launchOptions: initialiseState(context) registerFlixReleaseDocumentProvider(context) + registerFlixStdlibDocumentProvider(context) // create output channels outputChannel = vscode.window.createOutputChannel('Flix Compiler') diff --git a/client/src/handlers/handlers.ts b/client/src/handlers/handlers.ts index 784dcd74..ea499172 100644 --- a/client/src/handlers/handlers.ts +++ b/client/src/handlers/handlers.ts @@ -158,6 +158,11 @@ export function handleChangeEditor(editor: vscode.TextEditor | undefined) { return } + // Skip validation for virtual URIs (like stdlib files from JAR) + if (editor.document.uri.scheme !== 'file') { + return + } + const included = vscode.languages.match({ pattern: FLIX_GLOB_PATTERN }, editor.document) if (!included) { vscode.window.showWarningMessage(USER_MESSAGE.FILE_NOT_PART_OF_PROJECT()) diff --git a/client/src/services/stdlibVirtualDocument.ts b/client/src/services/stdlibVirtualDocument.ts new file mode 100644 index 00000000..1c4ec079 --- /dev/null +++ b/client/src/services/stdlibVirtualDocument.ts @@ -0,0 +1,76 @@ +/* + * Copyright 2025 Valentin Erokhin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as vscode from 'vscode' +import AdmZip from 'adm-zip' +import * as fs from 'fs' + +/** + * The scheme used to distinguish stdlib documents from jar files. + */ +const scheme = 'flixstdlib' + +/** + * Handle documents where the uri scheme is [[scheme]] (flixstdlib): + * - Extract the jar path and file path from the URI + * - Read the file content from inside the jar + * - Return the file content for display + */ +const flixStdlibDocumentProvider = new (class implements vscode.TextDocumentContentProvider { + provideTextDocumentContent(uri: vscode.Uri): string { + try { + const jarPath = decodeURIComponent(uri.authority) + const filePath = uri.path.startsWith('/') ? uri.path.substring(1) : uri.path + + if (!fs.existsSync(jarPath)) { + return `// Error: Flix jar file not found at: ${jarPath}` + } + + const zip = new AdmZip(jarPath) + let entry = zip.getEntry(filePath) + let searchedPaths = [filePath] + + // If not found at root level, try common stdlib directories + if (!entry && !filePath.startsWith('src/')) { + const commonPaths = [ + `src/library/${filePath}`, + `src/resources/${filePath}`, + ] + + for (const path of commonPaths) { + entry = zip.getEntry(path) + searchedPaths.push(path) + if (entry) break + } + } + + if (!entry) { + return `// Error: File '${filePath}' not found in jar: ${jarPath}\n// Searched paths: ${searchedPaths.join(', ')}` + } + + return entry.getData().toString('utf8') + } catch (error) { + return `// Error reading stdlib file: ${error.message}` + } + } +})() + +/** + * Register the stdlib document provider. + */ +export function registerFlixStdlibDocumentProvider({ subscriptions }: vscode.ExtensionContext) { + subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(scheme, flixStdlibDocumentProvider)) +} \ No newline at end of file diff --git a/server/src/handlers/handlers.ts b/server/src/handlers/handlers.ts index 23c1c0d7..75866901 100644 --- a/server/src/handlers/handlers.ts +++ b/server/src/handlers/handlers.ts @@ -208,6 +208,14 @@ function makeGotoDefinitionResponseHandler(promiseResolver: (result?: socket.Fli if (status === StatusCode.Success) { if (targetUri?.startsWith('file://')) { return promiseResolver(result) + } else if (targetUri && isStdlibFile(targetUri)) { + const flixJarPath = engine.getFlixFilename() + const virtualUri = `flixstdlib://${encodeURIComponent(flixJarPath)}/${targetUri}` + + return promiseResolver({ + ...result, + targetUri: virtualUri + }) } else { sendNotification(jobs.Request.internalMessage, USER_MESSAGE.FILE_NOT_AVAILABLE(targetUri!)) } @@ -216,6 +224,24 @@ function makeGotoDefinitionResponseHandler(promiseResolver: (result?: socket.Fli } } +/** + * Determines if a target URI represents a stdlib file that should be loaded from the flix.jar + */ +function isStdlibFile(targetUri: string): boolean { + // Stdlib files are typically: + // 1. Flix source files (end with .flix) + // 2. Just filenames without directory paths (no '/' characters) + // 3. Not absolute paths or URLs + return ( + targetUri.endsWith('.flix') && + !targetUri.includes('/') && + !targetUri.includes('\\') && + !targetUri.startsWith('http') && + !targetUri.includes(':') && + targetUri.length > 0 + ) +} + /** * @function */