diff --git a/client/src/commands.ts b/client/src/commands.ts
index a94c424a5..6a795c467 100644
--- a/client/src/commands.ts
+++ b/client/src/commands.ts
@@ -9,7 +9,6 @@ export { createInterface } from "./commands/create_interface";
export { openCompiled } from "./commands/open_compiled";
export { switchImplIntf } from "./commands/switch_impl_intf";
export { dumpDebug, dumpDebugRetrigger } from "./commands/dump_debug";
-export { dumpServerState } from "./commands/dump_server_state";
export { pasteAsRescriptJson } from "./commands/paste_as_rescript_json";
export { pasteAsRescriptJsx } from "./commands/paste_as_rescript_jsx";
diff --git a/client/src/commands/dump_server_state.ts b/client/src/commands/dump_server_state.ts
deleted file mode 100644
index 5b61008c8..000000000
--- a/client/src/commands/dump_server_state.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import {
- ExtensionContext,
- StatusBarItem,
- Uri,
- ViewColumn,
- window,
-} from "vscode";
-import { LanguageClient } from "vscode-languageclient/node";
-import * as fs from "fs";
-import { createFileInTempDir } from "../utils";
-
-export async function dumpServerState(
- client: LanguageClient,
- _context?: ExtensionContext,
- _statusBarItem?: StatusBarItem,
-) {
- try {
- const result = await client.sendRequest("rescript/dumpServerState");
- const outputFile = createFileInTempDir("server_state", ".json");
-
- // Pretty-print JSON with stable ordering where possible
- const replacer = (_key: string, value: any) => {
- if (value instanceof Map) return Object.fromEntries(value);
- if (value instanceof Set) return Array.from(value);
- return value;
- };
-
- const json = JSON.stringify(result, replacer, 2);
- fs.writeFileSync(outputFile, json, { encoding: "utf-8" });
-
- await window.showTextDocument(Uri.parse(outputFile), {
- viewColumn: ViewColumn.Beside,
- preview: false,
- });
- } catch (e) {
- window.showErrorMessage(`Failed to dump server state: ${String(e)}`);
- }
-}
diff --git a/client/src/extension.ts b/client/src/extension.ts
index cd4fee0f8..c31a8d170 100644
--- a/client/src/extension.ts
+++ b/client/src/extension.ts
@@ -13,6 +13,7 @@ import {
WorkspaceEdit,
CodeActionKind,
Diagnostic,
+ ViewColumn,
} from "vscode";
import { ThemeColor } from "vscode";
@@ -373,8 +374,30 @@ export function activate(context: ExtensionContext) {
customCommands.dumpDebug(context, debugDumpStatusBarItem);
});
- commands.registerCommand("rescript-vscode.dump-server-state", () => {
- customCommands.dumpServerState(client, context, debugDumpStatusBarItem);
+ commands.registerCommand("rescript-vscode.dump-server-state", async () => {
+ try {
+ const result = (await client.sendRequest("workspace/executeCommand", {
+ command: "rescript/dumpServerState",
+ })) as { content: string };
+
+ // Create an unsaved document with the server state content
+ const document = await workspace.openTextDocument({
+ content: result.content,
+ language: "json",
+ });
+
+ // Show the document in the editor
+ await window.showTextDocument(document, {
+ viewColumn: ViewColumn.Beside,
+ preview: false,
+ });
+ } catch (e) {
+ outputChannel.appendLine(`Failed to dump server state: ${String(e)}`);
+ window.showErrorMessage(
+ "Failed to dump server state. See 'Output' tab, 'ReScript Language Server' channel for details.",
+ );
+ outputChannel.show();
+ }
});
commands.registerCommand("rescript-vscode.showProblems", async () => {
diff --git a/server/src/server.ts b/server/src/server.ts
index fccd88916..77eaffcda 100644
--- a/server/src/server.ts
+++ b/server/src/server.ts
@@ -5,6 +5,7 @@ import * as rpc from "vscode-jsonrpc/node";
import * as path from "path";
import semver from "semver";
import fs from "fs";
+import fsAsync from "fs/promises";
import {
DidChangeWatchedFilesNotification,
DidOpenTextDocumentNotification,
@@ -1260,6 +1261,80 @@ function openCompiledFile(msg: p.RequestMessage): p.Message {
return response;
}
+async function dumpServerState(
+ msg: p.RequestMessage,
+): Promise
{
+ // Custom debug endpoint: dump current server state (config + projectsFiles)
+ try {
+ // Read the server version from package.json
+ let serverVersion: string | undefined;
+ try {
+ const packageJsonPath = path.join(__dirname, "..", "package.json");
+ const packageJsonContent = await fsAsync.readFile(packageJsonPath, {
+ encoding: "utf-8",
+ });
+ const packageJson: { version?: unknown } = JSON.parse(packageJsonContent);
+ serverVersion =
+ typeof packageJson.version === "string"
+ ? packageJson.version
+ : undefined;
+ } catch (e) {
+ // If we can't read the version, that's okay - we'll just omit it
+ serverVersion = undefined;
+ }
+
+ const projects = Array.from(projectsFiles.entries()).map(
+ ([projectRootPath, pf]) => ({
+ projectRootPath,
+ openFiles: Array.from(pf.openFiles),
+ filesWithDiagnostics: Array.from(pf.filesWithDiagnostics),
+ filesDiagnostics: pf.filesDiagnostics,
+ rescriptVersion: pf.rescriptVersion,
+ bscBinaryLocation: pf.bscBinaryLocation,
+ editorAnalysisLocation: pf.editorAnalysisLocation,
+ namespaceName: pf.namespaceName,
+ hasPromptedToStartBuild: pf.hasPromptedToStartBuild,
+ bsbWatcherByEditor:
+ pf.bsbWatcherByEditor != null
+ ? { pid: pf.bsbWatcherByEditor.pid ?? null }
+ : null,
+ }),
+ );
+
+ const state = {
+ lspServerVersion: serverVersion,
+ config: config.extensionConfiguration,
+ projects,
+ workspaceFolders: Array.from(workspaceFolders),
+ runtimePathCache: utils.getRuntimePathCacheSnapshot(),
+ };
+
+ // Format JSON with pretty-printing (2-space indent) on the server side
+ // This ensures consistent formatting and handles any Maps/Sets that might
+ // have been converted to plain objects/arrays above
+ const formattedJson = JSON.stringify(state, null, 2);
+
+ // Return the content so the client can create an unsaved document
+ // This avoids creating temporary files that would never be cleaned up
+ let response: p.ResponseMessage = {
+ jsonrpc: c.jsonrpcVersion,
+ id: msg.id,
+ result: { content: formattedJson },
+ };
+ return response;
+ } catch (e) {
+ let response: p.ResponseMessage = {
+ jsonrpc: c.jsonrpcVersion,
+ id: msg.id,
+ error: {
+ code: p.ErrorCodes.InternalError,
+ message: `Failed to dump server state: ${String(e)}`,
+ },
+ };
+ return response;
+ }
+}
+
async function onMessage(msg: p.Message) {
if (p.Message.isNotification(msg)) {
// notification message, aka the client ends it and doesn't want a reply
@@ -1458,6 +1533,9 @@ async function onMessage(msg: p.Message) {
retriggerCharacters: ["=", ","],
}
: undefined,
+ executeCommandProvider: {
+ commands: ["rescript/dumpServerState"],
+ },
},
};
let response: p.ResponseMessage = {
@@ -1555,47 +1633,18 @@ async function onMessage(msg: p.Message) {
if (extName === c.resExt) {
send(await signatureHelp(msg));
}
- } else if (msg.method === "rescript/dumpServerState") {
- // Custom debug endpoint: dump current server state (config + projectsFiles)
- try {
- const projects = Array.from(projectsFiles.entries()).map(
- ([projectRootPath, pf]) => ({
- projectRootPath,
- openFiles: Array.from(pf.openFiles),
- filesWithDiagnostics: Array.from(pf.filesWithDiagnostics),
- filesDiagnostics: pf.filesDiagnostics,
- rescriptVersion: pf.rescriptVersion,
- bscBinaryLocation: pf.bscBinaryLocation,
- editorAnalysisLocation: pf.editorAnalysisLocation,
- namespaceName: pf.namespaceName,
- hasPromptedToStartBuild: pf.hasPromptedToStartBuild,
- bsbWatcherByEditor:
- pf.bsbWatcherByEditor != null
- ? { pid: pf.bsbWatcherByEditor.pid ?? null }
- : null,
- }),
- );
-
- const result = {
- config: config.extensionConfiguration,
- projects,
- workspaceFolders: Array.from(workspaceFolders),
- runtimePathCache: utils.getRuntimePathCacheSnapshot(),
- };
-
- let response: p.ResponseMessage = {
- jsonrpc: c.jsonrpcVersion,
- id: msg.id,
- result,
- };
- send(response);
- } catch (e) {
+ } else if (msg.method === p.ExecuteCommandRequest.method) {
+ // Standard LSP executeCommand - supports editor-agnostic command execution
+ const params = msg.params as p.ExecuteCommandParams;
+ if (params.command === "rescript/dumpServerState") {
+ send(await dumpServerState(msg));
+ } else {
let response: p.ResponseMessage = {
jsonrpc: c.jsonrpcVersion,
id: msg.id,
error: {
- code: p.ErrorCodes.InternalError,
- message: `Failed to dump server state: ${String(e)}`,
+ code: p.ErrorCodes.InvalidRequest,
+ message: `Unknown command: ${params.command}`,
},
};
send(response);