diff --git a/package-lock.json b/package-lock.json index a2621d3..9a219b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1431,7 +1431,6 @@ "integrity": "sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.0", "@typescript-eslint/types": "8.46.0", @@ -1987,7 +1986,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2901,7 +2899,6 @@ "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6377,7 +6374,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7717,7 +7713,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.0.tgz", "integrity": "sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==", "dev": true, - "peer": true, "requires": { "@typescript-eslint/scope-manager": "8.46.0", "@typescript-eslint/types": "8.46.0", @@ -8060,8 +8055,7 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "peer": true + "dev": true }, "acorn-jsx": { "version": "5.3.2", @@ -8671,7 +8665,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz", "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, - "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -10965,8 +10958,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "peer": true + "dev": true }, "underscore": { "version": "1.13.4", diff --git a/src/extension.ts b/src/extension.ts index c383240..58d2dfc 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -4,19 +4,68 @@ import { log, outputChannel, vlsOutputChannel } from "logger" import vscode, { ConfigurationChangeEvent, ExtensionContext, workspace } from "vscode" import { LanguageClient, LanguageClientOptions, ServerOptions } from "vscode-languageclient/node" import { installV, isVInstalled } from "./utils" +import { VDocumentFormattingProvider } from "./formatProvider" export let client: LanguageClient | undefined +// The verified dynamic linker path for Debian 64-bit systems +const linkerPath = "/lib64/ld-linux-x86-64.so.2" +const isLinux = process.platform === "linux" +// The absolute path to your V installation root +const vRootPath = "/home/tshimo/v" +// The required arguments for VLS to use the standard I/O communication method +const vlsArgs = ["--stdio"] + async function createAndStartClient(): Promise { + // getVls() will resolve VLS path now that it's in ~/.local/bin/ const vlsPath = await getVls() - const serverOptions: ServerOptions = { - run: { command: vlsPath }, - debug: { command: vlsPath }, + // CRITICAL FIX: Define Environment variables for VLS execution + const vlsEnvironment = { + // 1. Tell VLS where the V installation is located + V_HOME: vRootPath, + // 2. Ensure the V executable is discoverable in the PATH + PATH: `${vRootPath}:${process.env.PATH || ""}`, + // Preserve existing environment variables + ...process.env, + } + + let serverOptions: ServerOptions + + if (isLinux) { + // FIX: Use the linker path to execute the VLS binary + serverOptions = { + run: { + command: linkerPath, + // Pass the VLS path first, then its arguments + args: [vlsPath, ...vlsArgs], + options: { + env: vlsEnvironment, // Inject environment variables + cwd: vRootPath, // Set working directory + }, + }, + debug: { + command: linkerPath, + args: [vlsPath, ...vlsArgs], + options: { + env: vlsEnvironment, + cwd: vRootPath, + }, + }, + } + } else { + // Original logic for non-Linux systems + serverOptions = { + run: { command: vlsPath, args: vlsArgs }, + debug: { command: vlsPath, args: vlsArgs }, + } } const clientOptions: LanguageClientOptions = { documentSelector: [{ scheme: "file", language: "v" }], + initializationOptions: { + enableHover: true, + }, outputChannel: vlsOutputChannel, synchronize: { fileEvents: vscode.workspace.createFileSystemWatcher("**/*.v"), @@ -30,14 +79,14 @@ async function createAndStartClient(): Promise { } export async function activate(context: ExtensionContext): Promise { - // Register output channels so users can open them even without VLS. + // Register output channels context.subscriptions.push(outputChannel, vlsOutputChannel) - // Check for V only if it's not installed + // Original V installation check logic if (!(await isVInstalled())) { const selection = await vscode.window.showInformationMessage( "The V programming language is not detected on this system. Would you like to install it?", - { modal: true }, // Modal makes the user have to choose before continuing + { modal: true }, "Yes", "No", ) @@ -50,13 +99,22 @@ export async function activate(context: ExtensionContext): Promise { // Register commands regardless of whether VLS is enabled await registerCommands(context) + // --- Register Document Formatter --- + const formattingProvider = new VDocumentFormattingProvider() + context.subscriptions.push( + vscode.languages.registerDocumentFormattingEditProvider( + { scheme: "file", language: "v" }, + formattingProvider, + ), + ) + // --- END Register Document Formatter --- + // Only start the language server if the user enabled it in settings. if (isVlsEnabled()) { try { await createAndStartClient() } catch (err) { - // If starting the client fails, log and continue. Users can still - // use non-LSP features of the extension. + // If starting the client fails, log and continue. console.error("Failed to start VLS:", err) vscode.window.showErrorMessage("Failed to start VLS. See output for details.") outputChannel.show() @@ -67,7 +125,7 @@ export async function activate(context: ExtensionContext): Promise { registerVlsCommands(context, client) - // React to configuration changes: enable/disable or request restart. + // Original configuration change handler logic workspace.onDidChangeConfiguration(async (e: ConfigurationChangeEvent) => { const vlsEnabled = isVlsEnabled() diff --git a/src/formatProvider.ts b/src/formatProvider.ts new file mode 100644 index 0000000..e36780c --- /dev/null +++ b/src/formatProvider.ts @@ -0,0 +1,63 @@ +import * as vscode from "vscode" +import { execSync, ExecSyncOptionsWithStringEncoding } from "child_process" +//import * as path from "path" + +export class VDocumentFormattingProvider implements vscode.DocumentFormattingEditProvider { + public provideDocumentFormattingEdits( + document: vscode.TextDocument, + ): vscode.ProviderResult { + // Use the absolute, known-working path for the V compiler + const vExecutablePath = "/home/tshimo/v/v" // <--- ABSOLUTE PATH + + // Command: v fmt - + const commandToRun = `"${vExecutablePath}" fmt -` + + const execOptions: ExecSyncOptionsWithStringEncoding = { + input: document.getText(), + encoding: "utf8", + timeout: 5000, + // Set CWD to V's source directory to help it find internal tools (Vfmt) + cwd: "/home/tshimo/v", + } + + try { + console.log(`[V Formatter] Running command: ${commandToRun}`) + + const buffer = execSync(commandToRun, execOptions) + + const fullRange = new vscode.Range( + document.lineAt(0).range.start, + document.lineAt(document.lineCount - 1).range.end, + ) + + // Return the formatted text to VS Code + return [vscode.TextEdit.replace(fullRange, buffer)] + } catch (error) { + if (error instanceof Error) { + const childProcessError = error as { + code?: string + stderr?: Buffer | string + path?: string + } + + let errorMessage = "V Formatter failed: Check Debug Console." + + if (childProcessError.stderr) { + const stderrOutput = childProcessError.stderr.toString() + console.error("[V Formatter] V Execution Error (stderr):", stderrOutput) + errorMessage = `V Formatter failed. Output: ${stderrOutput.substring(0, 80)}...` + } else if (childProcessError.code) { + console.error( + "[V Formatter] Error Code/Path:", + childProcessError.code, + childProcessError.path, + ) + errorMessage = `V Formatter failed. Code: ${childProcessError.code}.` + } + + vscode.window.showErrorMessage(errorMessage) + } + return [] + } + } +}