From 005edc788a8522f020e12f44e54aeb7b60e6a678 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sat, 28 May 2022 22:39:39 +0200 Subject: [PATCH 1/5] basics for pulling config from the client --- package.json | 21 +++--------- server/src/constants.ts | 2 ++ server/src/server.ts | 71 ++++++++++++++++++++++++++++++++++++----- 3 files changed, 70 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index a57217751..214541fc5 100644 --- a/package.json +++ b/package.json @@ -121,22 +121,11 @@ "type": "object", "title": "ReScript", "properties": { - "languageServerExample.maxNumberOfProblems": { - "scope": "resource", - "type": "number", - "default": 100, - "description": "Controls the maximum number of problems produced by the server." - }, - "languageServerExample.trace.server": { - "scope": "window", - "type": "string", - "enum": [ - "off", - "messages", - "verbose" - ], - "default": "off", - "description": "Traces the communication between VS Code and the language server." + "rescript.settings.askToStartBuild": { + "scope": "language-overridable", + "type": "boolean", + "default": true, + "description": "Whether you want the extension to prompt for autostarting a ReScript build if a project is opened with no build running." } } }, diff --git a/server/src/constants.ts b/server/src/constants.ts index 498b98b6a..2366a946b 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -49,3 +49,5 @@ export let startBuildAction = "Start Build"; // bsconfig defaults according configuration schema (https://rescript-lang.org/docs/manual/latest/build-configuration-schema) export let bsconfigModuleDefault = "commonjs"; export let bsconfigSuffixDefault = ".js"; + +export let configurationRequestId = "rescript_configuration_request"; diff --git a/server/src/server.ts b/server/src/server.ts index 946778718..b47c3a5d9 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -10,6 +10,7 @@ import { DidOpenTextDocumentNotification, DidChangeTextDocumentNotification, DidCloseTextDocumentNotification, + DidChangeConfigurationNotification, } from "vscode-languageserver-protocol"; import * as utils from "./utils"; import * as codeActions from "./codeActions"; @@ -20,6 +21,14 @@ import { fileURLToPath } from "url"; import { ChildProcess } from "child_process"; import { WorkspaceEdit } from "vscode-languageserver"; +interface extensionConfiguration { + askToStartBuild: boolean; +} +let extensionConfiguration: extensionConfiguration = { + askToStartBuild: true, +}; +let pullConfigurationInterval: NodeJS.Timeout | null = null; + // https://microsoft.github.io/language-server-protocol/specification#initialize // According to the spec, there could be requests before the 'initialize' request. Link in comment tells how to handle them. let initialized = false; @@ -183,7 +192,11 @@ let openedFile = (fileUri: string, fileContent: string) => { // check if .bsb.lock is still there. If not, start a bsb -w ourselves // because otherwise the diagnostics info we'll display might be stale let bsbLockPath = path.join(projectRootPath, c.bsbLock); - if (firstOpenFileOfProject && !fs.existsSync(bsbLockPath)) { + if ( + extensionConfiguration.askToStartBuild === true && + firstOpenFileOfProject && + !fs.existsSync(bsbLockPath) + ) { // TODO: sometime stale .bsb.lock dangling. bsb -w knows .bsb.lock is // stale. Use that logic // TODO: close watcher when lang-server shuts down @@ -268,6 +281,8 @@ if (process.argv.includes("--stdio")) { process.on("message", onMessage); } +askForAllCurrentConfiguration(); + function hover(msg: p.RequestMessage) { let params = msg.params as p.HoverParams; let filePath = fileURLToPath(params.textDocument.uri); @@ -412,6 +427,24 @@ function documentSymbol(msg: p.RequestMessage) { return response; } +function askForAllCurrentConfiguration() { + // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_configuration + let params: p.ConfigurationParams = { + items: [ + { + section: "rescript.settings", + }, + ], + }; + let req: p.RequestMessage = { + jsonrpc: c.jsonrpcVersion, + id: c.configurationRequestId, + method: p.ConfigurationRequest.type.method, + params, + }; + send(req); +} + function semanticTokens(msg: p.RequestMessage) { // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens let params = msg.params as p.SemanticTokensParams; @@ -434,7 +467,6 @@ function completion(msg: p.RequestMessage) { // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion let params = msg.params as p.ReferenceParams; let filePath = fileURLToPath(params.textDocument.uri); - let extension = path.extname(params.textDocument.uri); let code = getOpenedFileContent(params.textDocument.uri); let tmpname = utils.createFileInTempDir(); fs.writeFileSync(tmpname, code, { encoding: "utf-8" }); @@ -743,7 +775,6 @@ function onMessage(msg: m.Message) { } } else if (msg.method === DidOpenTextDocumentNotification.method) { let params = msg.params as p.DidOpenTextDocumentParams; - let extName = path.extname(params.textDocument.uri); openedFile(params.textDocument.uri, params.textDocument.text); } else if (msg.method === DidChangeTextDocumentNotification.method) { let params = msg.params as p.DidChangeTextDocumentParams; @@ -763,6 +794,9 @@ function onMessage(msg: m.Message) { } else if (msg.method === DidCloseTextDocumentNotification.method) { let params = msg.params as p.DidCloseTextDocumentParams; closedFile(params.textDocument.uri); + } else if (msg.method === DidChangeConfigurationNotification.type.method) { + // Can't seem to get this notification to trigger, but if it does this will be here and ensure we're synced up at the server. + askForAllCurrentConfiguration(); } } else if (m.isRequestMessage(msg)) { // request message, aka client sent request and waits for our mandatory reply @@ -820,6 +854,15 @@ function onMessage(msg: m.Message) { result: result, }; initialized = true; + + // Periodically pull configuration from the client. + pullConfigurationInterval = setInterval(() => { + askForAllCurrentConfiguration(); + }, 10_000); + + // Pull config right away as we've initied. + askForAllCurrentConfiguration(); + send(response); } else if (msg.method === "initialized") { // sent from client after initialize. Nothing to do for now @@ -847,6 +890,10 @@ function onMessage(msg: m.Message) { stopWatchingCompilerLog(); // TODO: delete bsb watchers + if (pullConfigurationInterval != null) { + clearInterval(pullConfigurationInterval); + } + let response: m.ResponseMessage = { jsonrpc: c.jsonrpcVersion, id: msg.id, @@ -893,11 +940,19 @@ function onMessage(msg: m.Message) { send(response); } } else if (m.isResponseMessage(msg)) { - // response message. Currently the client should have only sent a response - // for asking us to start the build (see window/showMessageRequest in this - // file) - - if ( + if (msg.id === c.configurationRequestId) { + if (msg.result != null) { + // This is a response from a request to get updated configuration. Note + // that it seems to return the configuration in a way that lets the + // current workspace settings override the user settings. This is good + // as we get started, but _might_ be problematic further down the line + // if we want to support having several projects open at the same time + // without their settings overriding eachother. Not a problem now though + // as we'll likely only have "global" settings starting out. + let [configuration] = msg.result as [extensionConfiguration]; + extensionConfiguration = configuration; + } + } else if ( msg.result != null && // @ts-ignore msg.result.title != null && From eb2e459264b19e540fa9c2f78421cc398cc68f32 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sun, 29 May 2022 18:30:04 +0200 Subject: [PATCH 2/5] ensure existing config is synced up on init --- client/src/extension.ts | 9 +++++++-- server/src/server.ts | 13 +++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/client/src/extension.ts b/client/src/extension.ts index 2f6a76f02..01e9c664a 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -130,6 +130,12 @@ export function activate(context: ExtensionContext) { // Notify the server about file changes to '.clientrc files contained in the workspace fileEvents: workspace.createFileSystemWatcher("**/.clientrc"), }, + // We'll send the initial configuration in here, but this might be + // problematic because every consumer of the LS will need to mimic this. + // We'll leave it like this for now, but might be worth revisiting later on. + initializationOptions: { + extensionConfiguration: workspace.getConfiguration("rescript.settings"), + }, }; // Create the language client and start the client. @@ -199,8 +205,7 @@ export function activate(context: ExtensionContext) { codeAnalysisRunningStatusBarItem.command = "rescript-vscode.stop_code_analysis"; codeAnalysisRunningStatusBarItem.show(); - codeAnalysisRunningStatusBarItem.text = - "$(debug-stop) Stop Code Analyzer"; + codeAnalysisRunningStatusBarItem.text = "$(debug-stop) Stop Code Analyzer"; customCommands.codeAnalysisWithReanalyze( inCodeAnalysisState.activatedFromDirectory, diff --git a/server/src/server.ts b/server/src/server.ts index b47c3a5d9..b90982fc5 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -11,6 +11,7 @@ import { DidChangeTextDocumentNotification, DidCloseTextDocumentNotification, DidChangeConfigurationNotification, + InitializeParams, } from "vscode-languageserver-protocol"; import * as utils from "./utils"; import * as codeActions from "./codeActions"; @@ -281,8 +282,6 @@ if (process.argv.includes("--stdio")) { process.on("message", onMessage); } -askForAllCurrentConfiguration(); - function hover(msg: p.RequestMessage) { let params = msg.params as p.HoverParams; let filePath = fileURLToPath(params.textDocument.uri); @@ -860,8 +859,14 @@ function onMessage(msg: m.Message) { askForAllCurrentConfiguration(); }, 10_000); - // Pull config right away as we've initied. - askForAllCurrentConfiguration(); + // Save initial configuration, if present + let initParams = msg.params as InitializeParams; + let initialConfiguration = initParams.initializationOptions + ?.extensionConfiguration as extensionConfiguration | undefined; + + if (initialConfiguration != null) { + extensionConfiguration = initialConfiguration; + } send(response); } else if (msg.method === "initialized") { From 976bb4826294853bf1e5f248295ce51d5e400007 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sun, 29 May 2022 18:45:47 +0200 Subject: [PATCH 3/5] cleanup --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 214541fc5..3d58fb5a7 100644 --- a/package.json +++ b/package.json @@ -124,7 +124,7 @@ "rescript.settings.askToStartBuild": { "scope": "language-overridable", "type": "boolean", - "default": true, + "default": true, "description": "Whether you want the extension to prompt for autostarting a ReScript build if a project is opened with no build running." } } From b94e66d2efaa7f6a1bba3a088cd44d13392d5120 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Mon, 30 May 2022 07:59:42 +0200 Subject: [PATCH 4/5] fixes --- server/src/constants.ts | 1 + server/src/server.ts | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/server/src/constants.ts b/server/src/constants.ts index 2366a946b..1799b2b9e 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -51,3 +51,4 @@ export let bsconfigModuleDefault = "commonjs"; export let bsconfigSuffixDefault = ".js"; export let configurationRequestId = "rescript_configuration_request"; +export let pullConfigurationInterval = 10_000; diff --git a/server/src/server.ts b/server/src/server.ts index b90982fc5..718c5c154 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -28,7 +28,7 @@ interface extensionConfiguration { let extensionConfiguration: extensionConfiguration = { askToStartBuild: true, }; -let pullConfigurationInterval: NodeJS.Timeout | null = null; +let pullConfigurationPeriodically: NodeJS.Timeout | null = null; // https://microsoft.github.io/language-server-protocol/specification#initialize // According to the spec, there could be requests before the 'initialize' request. Link in comment tells how to handle them. @@ -855,9 +855,9 @@ function onMessage(msg: m.Message) { initialized = true; // Periodically pull configuration from the client. - pullConfigurationInterval = setInterval(() => { + pullConfigurationPeriodically = setInterval(() => { askForAllCurrentConfiguration(); - }, 10_000); + }, c.pullConfigurationInterval); // Save initial configuration, if present let initParams = msg.params as InitializeParams; @@ -895,8 +895,8 @@ function onMessage(msg: m.Message) { stopWatchingCompilerLog(); // TODO: delete bsb watchers - if (pullConfigurationInterval != null) { - clearInterval(pullConfigurationInterval); + if (pullConfigurationPeriodically != null) { + clearInterval(pullConfigurationPeriodically); } let response: m.ResponseMessage = { From 1fdb47cfff490a7ef03d47297a57ed899d80b664 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Mon, 30 May 2022 08:06:18 +0200 Subject: [PATCH 5/5] readme --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c72af86a3..7a4135e9f 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,12 @@ Currently does not work for full monorepo dead code analysis (although it should ## Configuration +You'll find all ReScript specific settings under the scope `rescript.settings`. Open your VSCode settings and type `rescript.settings` to see them. + +### Autostarting ReScript builds + +If there's no ReScript build running already in the opened project, the extension will prompt you and ask if you want to start a build automatically. You can turn off this automatic prompt via the setting `rescript.settings.askToStartBuild`. + ### Hide generated files You can configure VSCode to collapse the JavaScript files ReScript generates under its source ReScript file. This will "hide" the generated files in the VSCode file explorer, but still leaving them accessible by expanding the source ReScript file they belong to. @@ -97,7 +103,7 @@ The example has two patterns added: ![Shows configuration of file nesting patterns in VSCode.](https://user-images.githubusercontent.com/1457626/168123605-43ef53cf-f371-4f38-b488-d3cd081879de.png) -This nests implementations under interfaces if they're present and nests all generated files under the main ReScript file. Adapt and tweak to your liking. +This nests implementations under interfaces if they're present and nests all generated files under the main ReScript file. Adapt and tweak to your liking. A screenshot of the result: