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: 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/package.json b/package.json index a57217751..3d58fb5a7 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..1799b2b9e 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -49,3 +49,6 @@ 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"; +export let pullConfigurationInterval = 10_000; diff --git a/server/src/server.ts b/server/src/server.ts index 946778718..718c5c154 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -10,6 +10,8 @@ import { DidOpenTextDocumentNotification, DidChangeTextDocumentNotification, DidCloseTextDocumentNotification, + DidChangeConfigurationNotification, + InitializeParams, } from "vscode-languageserver-protocol"; import * as utils from "./utils"; import * as codeActions from "./codeActions"; @@ -20,6 +22,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 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. let initialized = false; @@ -183,7 +193,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 @@ -412,6 +426,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 +466,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 +774,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 +793,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 +853,21 @@ function onMessage(msg: m.Message) { result: result, }; initialized = true; + + // Periodically pull configuration from the client. + pullConfigurationPeriodically = setInterval(() => { + askForAllCurrentConfiguration(); + }, c.pullConfigurationInterval); + + // 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") { // sent from client after initialize. Nothing to do for now @@ -847,6 +895,10 @@ function onMessage(msg: m.Message) { stopWatchingCompilerLog(); // TODO: delete bsb watchers + if (pullConfigurationPeriodically != null) { + clearInterval(pullConfigurationPeriodically); + } + let response: m.ResponseMessage = { jsonrpc: c.jsonrpcVersion, id: msg.id, @@ -893,11 +945,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 &&