diff --git a/README.md b/README.md index a46aa1cc5..cf243eab9 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,7 @@ You'll find all ReScript specific settings under the scope `rescript.settings`. | Inlay Hints (experimental) | This allows an editor to place annotations inline with text to display type hints. Enable using `rescript.settings.inlayHints.enable: true` | | Code Lens (experimental) | This tells the editor to add code lenses to function definitions, showing its full type above the definition. Enable using `rescript.settings.codeLens: true` | | Signature Help | This tells the editor to show signature help when you're writing function calls. Enable using `rescript.settings.signatureHelp.enabled: true` | +| Log level | This tells the editor which log messages to show in the Output window. The options are `"error"`, `"warning"`, `"info"`, `"log"` with default `"info"`. Set via `"rescript.settings.logLevel"`. | **Default settings:** @@ -126,6 +127,9 @@ You'll find all ReScript specific settings under the scope `rescript.settings`. // Enable (experimental) code lens for function definitions. "rescript.settings.codeLens": true + +// Set log level ("error", "warning", "info", "log"). +"rescript.settings.logLevel": "info" ``` ## 🚀 Code Analyzer diff --git a/package.json b/package.json index 5f9ae3e91..cf3f318c7 100644 --- a/package.json +++ b/package.json @@ -206,6 +206,12 @@ ], "default": null, "description": "Path to the directory where platform-specific ReScript binaries are. You can use it if you haven't or don't want to use the installed ReScript from node_modules in your project." + }, + "rescript.settings.logLevel": { + "type": "string", + "enum": ["error", "warning", "info", "log"], + "default": "info", + "description": "Controls the log level of the language server. Logs below this level will be filtered out." } } }, diff --git a/server/src/config.ts b/server/src/config.ts index e8055e379..af21407d5 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -25,6 +25,7 @@ export interface extensionConfiguration { enable?: boolean; }; }; + logLevel: "error" | "warning" | "info" | "log" } // All values here are temporary, and will be overridden as the server is @@ -53,6 +54,7 @@ let config: { extensionConfiguration: extensionConfiguration } = { enable: true, }, }, + logLevel: "info" }, }; diff --git a/server/src/server.ts b/server/src/server.ts index 1c74132e7..09f612831 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -28,6 +28,38 @@ import * as ic from "./incrementalCompilation"; import config, { extensionConfiguration } from "./config"; import { projectsFiles } from "./projectFiles"; +const notificationLevelMap = new Map([ + ["error", p.MessageType.Error], + ["warning", p.MessageType.Warning], + ["info", p.MessageType.Info], + ["log", p.MessageType.Log] +]) + +/** + * Sends an LSP log notification that will appear in the ReScript Language Server Output window in VSCode. + * Other LSP clients will also receive this notification, + * as we utilize LanguageClient in the VSCode extension, providing this functionality at no extra cost. + */ +function sendLogNotification(level: p.MessageType, message: string) { + const currentLogLevel = notificationLevelMap.get(config.extensionConfiguration.logLevel) || p.MessageType.Info; + + if (currentLogLevel >= level) { + const logMessageParams: p.LogMessageParams = { + type: level, + message + } + const notificationMessage: p.NotificationMessage = { + method: "window/logMessage", + jsonrpc: c.jsonrpcVersion, + params: logMessageParams + } + + if (send) { + send(notificationMessage); + } + } +} + // This holds client capabilities specific to our extension, and not necessarily // related to the LS protocol. It's for enabling/disabling features that might // work in one client, like VSCode, but perhaps not in others, like vim. @@ -54,18 +86,18 @@ let stupidFileContentCache: Map = new Map(); let codeActionsFromDiagnostics: codeActions.filesCodeActions = {}; // will be properly defined later depending on the mode (stdio/node-rpc) -let send: (msg: p.Message) => void = (_) => {}; +let send: (msg: p.Message) => void = (_) => { }; let findRescriptBinary = (projectRootPath: p.DocumentUri | null) => config.extensionConfiguration.binaryPath == null ? lookup.findFilePathFromProjectRoot( - projectRootPath, - path.join(c.nodeModulesBinDir, c.rescriptBinName) - ) + projectRootPath, + path.join(c.nodeModulesBinDir, c.rescriptBinName) + ) : utils.findBinary( - config.extensionConfiguration.binaryPath, - c.rescriptBinName - ); + config.extensionConfiguration.binaryPath, + c.rescriptBinName + ); let createInterfaceRequest = new v.RequestType< p.TextDocumentIdentifier, @@ -332,9 +364,9 @@ let openedFile = (fileUri: string, fileContent: string) => { message: config.extensionConfiguration.binaryPath == null ? `Can't find ReScript binary in ${path.join( - projectRootPath, - c.nodeModulesBinDir - )} or parent directories. Did you install it? It's required to use "rescript" > 9.1` + projectRootPath, + c.nodeModulesBinDir + )} or parent directories. Did you install it? It's required to use "rescript" > 9.1` : `Can't find ReScript binary in the directory ${config.extensionConfiguration.binaryPath}`, }, }; @@ -418,6 +450,7 @@ export default function listen(useStdio = false) { send = (msg: p.Message) => process.send!(msg); process.on("message", onMessage); } + utils.setSendLogNotification(sendLogNotification); } function hover(msg: p.RequestMessage) { @@ -1158,15 +1191,15 @@ function onMessage(msg: p.Message) { inlayHintProvider: config.extensionConfiguration.inlayHints?.enable, codeLensProvider: config.extensionConfiguration.codeLens ? { - workDoneProgress: false, - } + workDoneProgress: false, + } : undefined, signatureHelpProvider: config.extensionConfiguration.signatureHelp ?.enabled ? { - triggerCharacters: ["("], - retriggerCharacters: ["=", ","], - } + triggerCharacters: ["("], + retriggerCharacters: ["=", ","], + } : undefined, }, }; @@ -1177,6 +1210,12 @@ function onMessage(msg: p.Message) { }; initialized = true; + let rescriptVersion = undefined; + if (initParams.workspaceFolders && initParams.workspaceFolders.length > 0) { + rescriptVersion = utils.findReScriptVersion(initParams.workspaceFolders[0].uri); + } + sendLogNotification(p.MessageType.Info, `LSP Server started! Rescript Version ${rescriptVersion}`); + // Periodically pull configuration from the client. pullConfigurationPeriodically = setInterval(() => { askForAllCurrentConfiguration(); diff --git a/server/src/utils.ts b/server/src/utils.ts index 5de85abfc..5ce7c8569 100644 --- a/server/src/utils.ts +++ b/server/src/utils.ts @@ -16,6 +16,13 @@ import { reportError } from "./errorReporter"; import config from "./config"; import { filesDiagnostics, projectsFiles } from "./projectFiles"; +// This is a bit dirty but avoids passing down this function each time. +export type SendLogNotification = (level: p.MessageType, message: string) => void +let sendLogNotification: SendLogNotification = () => { }; +export function setSendLogNotification(send: SendLogNotification) { + sendLogNotification = send; +} + let tempFilePrefix = "rescript_format_file_" + process.pid + "_"; let tempFileId = 0; @@ -92,15 +99,19 @@ export let findBinary = ( type execResult = | { - kind: "success"; - result: string; - } + kind: "success"; + result: string; + } | { - kind: "error"; - error: string; - }; + kind: "error"; + error: string; + }; -type formatCodeResult = execResult; +type formatCodeResult = + | execResult + | { + kind: "blocked-using-built-in-formatter"; + }; export let formatCode = ( bscPath: p.DocumentUri | null, @@ -238,6 +249,9 @@ export let runAnalysisAfterSanityCheck = ( let stdout = ""; try { + if(args.includes("completion")){ + sendLogNotification(p.MessageType.Log, `Running completion: ${binaryPath} ${args.join(" ")}`); + } stdout = childProcess.execFileSync(binaryPath, args, options).toString(); return JSON.parse(stdout); } catch (e) { @@ -666,7 +680,7 @@ export let parseCompilerLogOutput = ( diagnostic, diagnosticMessage, file, - range, + range }); result[file].push(diagnostic);