Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sharing config between client and server #432

Merged
merged 5 commits into from
May 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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:

Expand Down
9 changes: 7 additions & 2 deletions client/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down
21 changes: 5 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
}
},
Expand Down
3 changes: 3 additions & 0 deletions server/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
76 changes: 68 additions & 8 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
DidOpenTextDocumentNotification,
DidChangeTextDocumentNotification,
DidCloseTextDocumentNotification,
DidChangeConfigurationNotification,
InitializeParams,
} from "vscode-languageserver-protocol";
import * as utils from "./utils";
import * as codeActions from "./codeActions";
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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" });
Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 &&
Expand Down