From 76e2603caeff9a10aa70ab452e74fb56d18a7ee8 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Tue, 30 Aug 2022 21:03:11 +0200 Subject: [PATCH 1/3] prompt user to explicitly allow formatting using the built in formatter --- package.json | 6 ++++++ server/src/server.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/package.json b/package.json index fbc067789..e06bf5d2d 100644 --- a/package.json +++ b/package.json @@ -121,6 +121,12 @@ "type": "object", "title": "ReScript", "properties": { + "rescript.settings.allowBuiltInFormatter": { + "scope": "language-overridable", + "type": "boolean", + "default": false, + "description": "Whether you want to allow the extension to format your code using its built in formatter when it cannot find a ReScript compiler version in your current project to use for formatting." + }, "rescript.settings.askToStartBuild": { "scope": "language-overridable", "type": "boolean", diff --git a/server/src/server.ts b/server/src/server.ts index 4e5dcbe3f..e9f0c864d 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -25,6 +25,7 @@ import { WorkspaceEdit } from "vscode-languageserver"; import { filesDiagnostics } from "./utils"; interface extensionConfiguration { + allowBuiltInFormatter: boolean; askToStartBuild: boolean; inlayHints: { enable: boolean; @@ -37,6 +38,7 @@ interface extensionConfiguration { // All values here are temporary, and will be overridden as the server is // initialized, and the current config is received from the client. let extensionConfiguration: extensionConfiguration = { + allowBuiltInFormatter: false, askToStartBuild: true, inlayHints: { enable: false, @@ -45,6 +47,8 @@ let extensionConfiguration: extensionConfiguration = { codeLens: false, binaryPath: null, }; +// Below here is some state that's not important exactly how long it lives. +let hasPromptedAboutBuiltInFormatter = false; let pullConfigurationPeriodically: NodeJS.Timeout | null = null; // https://microsoft.github.io/language-server-protocol/specification#initialize @@ -736,6 +740,28 @@ function format(msg: p.RequestMessage): Array { let projectRootPath = utils.findProjectRootOfFile(filePath); let bscBinaryPath = projectRootPath === null ? null : findBscBinary(projectRootPath); + + if ( + bscBinaryPath == null && + !extensionConfiguration.allowBuiltInFormatter && + !hasPromptedAboutBuiltInFormatter + ) { + // Let's only prompt the user once about this, or things might become annoying. + hasPromptedAboutBuiltInFormatter = true; + let params: p.ShowMessageParams = { + type: p.MessageType.Warning, + message: `Formatting not applied! Could not find the ReScript compiler in the current project, and you haven't configured the extension to allow formatting using the built in formatter. To allow formatting files not strictly part of a ReScript project using the built in formatter, [please configure the extension to allow that.](command:workbench.action.openSettings?${encodeURIComponent( + "rescript.settings.allowBuiltInFormatter" + )})`, + }; + let response: p.NotificationMessage = { + jsonrpc: c.jsonrpcVersion, + method: "window/showMessage", + params: params, + }; + return [fakeSuccessResponse, response]; + } + let formattedResult = utils.formatCode(bscBinaryPath, filePath, code); if (formattedResult.kind === "success") { let max = code.length; From 5c1abc44f80fb9f11f5e025338a450d1713adc15 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Tue, 30 Aug 2022 21:10:19 +0200 Subject: [PATCH 2/3] logic is fun but hard --- server/src/server.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/server/src/server.ts b/server/src/server.ts index e9f0c864d..25d90be64 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -743,15 +743,18 @@ function format(msg: p.RequestMessage): Array { if ( bscBinaryPath == null && - !extensionConfiguration.allowBuiltInFormatter && - !hasPromptedAboutBuiltInFormatter + !extensionConfiguration.allowBuiltInFormatter ) { // Let's only prompt the user once about this, or things might become annoying. + if (hasPromptedAboutBuiltInFormatter) { + return [fakeSuccessResponse]; + } hasPromptedAboutBuiltInFormatter = true; + let params: p.ShowMessageParams = { type: p.MessageType.Warning, message: `Formatting not applied! Could not find the ReScript compiler in the current project, and you haven't configured the extension to allow formatting using the built in formatter. To allow formatting files not strictly part of a ReScript project using the built in formatter, [please configure the extension to allow that.](command:workbench.action.openSettings?${encodeURIComponent( - "rescript.settings.allowBuiltInFormatter" + JSON.stringify(["rescript.settings.allowBuiltInFormatter"]) )})`, }; let response: p.NotificationMessage = { From 6e9379b6154169da3d9c5b8f5aed445e9b310fe9 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Tue, 30 Aug 2022 21:38:00 +0200 Subject: [PATCH 3/3] try to not break encapsulation --- client/src/extension.ts | 8 +++++++ server/src/server.ts | 48 ++++++++++++++++++++--------------------- server/src/utils.ts | 17 +++++++++++++-- 3 files changed, 47 insertions(+), 26 deletions(-) diff --git a/client/src/extension.ts b/client/src/extension.ts index 33f1335ad..f324e4b11 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -248,6 +248,14 @@ export function activate(context: ExtensionContext) { // language client, and because of that requires a full restart. context.subscriptions.push( workspace.onDidChangeConfiguration(({ affectsConfiguration }) => { + // Send a general message that configuration has updated. Clients + // interested can then pull the new configuration as they see fit. + client + .sendNotification("workspace/didChangeConfiguration") + .catch((err) => { + window.showErrorMessage(String(err)); + }); + // Put any configuration that, when changed, requires a full restart of // the server here. That will typically be any configuration that affects // the capabilities declared by the server, since those cannot be updated diff --git a/server/src/server.ts b/server/src/server.ts index 25d90be64..1c5e6c2e8 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -741,10 +741,30 @@ function format(msg: p.RequestMessage): Array { let bscBinaryPath = projectRootPath === null ? null : findBscBinary(projectRootPath); - if ( - bscBinaryPath == null && - !extensionConfiguration.allowBuiltInFormatter - ) { + let formattedResult = utils.formatCode( + bscBinaryPath, + filePath, + code, + extensionConfiguration.allowBuiltInFormatter + ); + if (formattedResult.kind === "success") { + let max = code.length; + let result: p.TextEdit[] = [ + { + range: { + start: { line: 0, character: 0 }, + end: { line: max, character: max }, + }, + newText: formattedResult.result, + }, + ]; + let response: p.ResponseMessage = { + jsonrpc: c.jsonrpcVersion, + id: msg.id, + result: result, + }; + return [response]; + } else if (formattedResult.kind === "blocked-using-built-in-formatter") { // Let's only prompt the user once about this, or things might become annoying. if (hasPromptedAboutBuiltInFormatter) { return [fakeSuccessResponse]; @@ -763,26 +783,6 @@ function format(msg: p.RequestMessage): Array { params: params, }; return [fakeSuccessResponse, response]; - } - - let formattedResult = utils.formatCode(bscBinaryPath, filePath, code); - if (formattedResult.kind === "success") { - let max = code.length; - let result: p.TextEdit[] = [ - { - range: { - start: { line: 0, character: 0 }, - end: { line: max, character: max }, - }, - newText: formattedResult.result, - }, - ]; - let response: p.ResponseMessage = { - jsonrpc: c.jsonrpcVersion, - id: msg.id, - result: result, - }; - return [response]; } else { // let the diagnostics logic display the updated syntax errors, // from the build. diff --git a/server/src/utils.ts b/server/src/utils.ts index e5ec43b8e..11c41c11c 100644 --- a/server/src/utils.ts +++ b/server/src/utils.ts @@ -75,11 +75,18 @@ type execResult = error: string; }; +type formatCodeResult = + | execResult + | { + kind: "blocked-using-built-in-formatter"; + }; + export let formatCode = ( bscPath: p.DocumentUri | null, filePath: string, - code: string -): execResult => { + code: string, + allowBuiltInFormatter: boolean +): formatCodeResult => { let extension = path.extname(filePath); let formatTempFileFullPath = createFileInTempDir(extension); fs.writeFileSync(formatTempFileFullPath, code, { @@ -100,6 +107,12 @@ export let formatCode = ( result: result.toString(), }; } else { + if (!allowBuiltInFormatter) { + return { + kind: "blocked-using-built-in-formatter", + }; + } + let result = runAnalysisAfterSanityCheck( formatTempFileFullPath, ["format", formatTempFileFullPath],