Skip to content

Commit d26c253

Browse files
committed
avoid recomputing project roots and ReScript versions as much as possible
1 parent 169da71 commit d26c253

File tree

3 files changed

+73
-34
lines changed

3 files changed

+73
-34
lines changed

server/src/projectFiles.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import * as cp from "node:child_process";
2+
import * as p from "vscode-languageserver-protocol";
3+
4+
export type filesDiagnostics = {
5+
[key: string]: p.Diagnostic[];
6+
};
7+
8+
interface projectFiles {
9+
openFiles: Set<string>;
10+
filesWithDiagnostics: Set<string>;
11+
filesDiagnostics: filesDiagnostics;
12+
rescriptVersion: string | undefined;
13+
14+
bsbWatcherByEditor: null | cp.ChildProcess;
15+
16+
// This keeps track of whether we've prompted the user to start a build
17+
// automatically, if there's no build currently running for the project. We
18+
// only want to prompt the user about this once, or it becomes
19+
// annoying.
20+
// The type `never` means that we won't show the prompt if the project is inside node_modules
21+
hasPromptedToStartBuild: boolean | "never";
22+
}
23+
24+
export let projectsFiles: Map<string, projectFiles> = // project root path
25+
new Map();

server/src/server.ts

+3-19
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,11 @@ import * as c from "./constants";
2222
import * as chokidar from "chokidar";
2323
import { assert } from "console";
2424
import { fileURLToPath } from "url";
25-
import * as cp from "node:child_process";
2625
import { WorkspaceEdit } from "vscode-languageserver";
27-
import { filesDiagnostics } from "./utils";
2826
import { onErrorReported } from "./errorReporter";
2927
import * as ic from "./incrementalCompilation";
3028
import config, { extensionConfiguration } from "./config";
29+
import { projectsFiles } from "./projectFiles";
3130

3231
// This holds client capabilities specific to our extension, and not necessarily
3332
// related to the LS protocol. It's for enabling/disabling features that might
@@ -49,23 +48,7 @@ let serverSentRequestIdCounter = 0;
4948
// https://microsoft.github.io/language-server-protocol/specification#exit
5049
let shutdownRequestAlreadyReceived = false;
5150
let stupidFileContentCache: Map<string, string> = new Map();
52-
let projectsFiles: Map<
53-
string, // project root path
54-
{
55-
openFiles: Set<string>;
56-
filesWithDiagnostics: Set<string>;
57-
filesDiagnostics: filesDiagnostics;
58-
59-
bsbWatcherByEditor: null | cp.ChildProcess;
60-
61-
// This keeps track of whether we've prompted the user to start a build
62-
// automatically, if there's no build currently running for the project. We
63-
// only want to prompt the user about this once, or it becomes
64-
// annoying.
65-
// The type `never` means that we won't show the prompt if the project is inside node_modules
66-
hasPromptedToStartBuild: boolean | "never";
67-
}
68-
> = new Map();
51+
6952
// ^ caching AND states AND distributed system. Why does LSP has to be stupid like this
7053

7154
// This keeps track of code actions extracted from diagnostics.
@@ -246,6 +229,7 @@ let openedFile = (fileUri: string, fileContent: string) => {
246229
openFiles: new Set(),
247230
filesWithDiagnostics: new Set(),
248231
filesDiagnostics: {},
232+
rescriptVersion: utils.findReScriptVersion(projectRootPath),
249233
bsbWatcherByEditor: null,
250234
hasPromptedToStartBuild: /(\/|\\)node_modules(\/|\\)/.test(
251235
projectRootPath

server/src/utils.ts

+45-15
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import * as c from "./constants";
1414
import * as lookup from "./lookup";
1515
import { reportError } from "./errorReporter";
1616
import config from "./config";
17+
import { filesDiagnostics, projectsFiles } from "./projectFiles";
1718

1819
let tempFilePrefix = "rescript_format_file_" + process.pid + "_";
1920
let tempFileId = 0;
@@ -24,9 +25,7 @@ export let createFileInTempDir = (extension = "") => {
2425
return path.join(os.tmpdir(), tempFileName);
2526
};
2627

27-
// TODO: races here?
28-
// TODO: this doesn't handle file:/// scheme
29-
export let findProjectRootOfFile = (
28+
let findProjectRootOfFileInDir = (
3029
source: p.DocumentUri
3130
): null | p.DocumentUri => {
3231
let dir = path.dirname(source);
@@ -40,11 +39,37 @@ export let findProjectRootOfFile = (
4039
// reached top
4140
return null;
4241
} else {
43-
return findProjectRootOfFile(dir);
42+
return findProjectRootOfFileInDir(dir);
4443
}
4544
}
4645
};
4746

47+
// TODO: races here?
48+
// TODO: this doesn't handle file:/// scheme
49+
export let findProjectRootOfFile = (
50+
source: p.DocumentUri
51+
): null | p.DocumentUri => {
52+
// First look in project files
53+
let foundRootFromProjectFiles: string | null = null;
54+
55+
for (const rootPath of projectsFiles.keys()) {
56+
if (source.startsWith(rootPath)) {
57+
// Prefer the longest path (most nested)
58+
if (
59+
foundRootFromProjectFiles == null ||
60+
rootPath.length > foundRootFromProjectFiles.length
61+
)
62+
foundRootFromProjectFiles = rootPath;
63+
}
64+
}
65+
66+
if (foundRootFromProjectFiles != null) {
67+
return foundRootFromProjectFiles;
68+
} else {
69+
return findProjectRootOfFileInDir(source);
70+
}
71+
};
72+
4873
// Check if binaryName exists inside binaryDirPath and return the joined path.
4974
export let findBinary = (
5075
binaryDirPath: p.DocumentUri | null,
@@ -138,7 +163,9 @@ export let formatCode = (
138163
}
139164
};
140165

141-
let findReScriptVersion = (filePath: p.DocumentUri): string | undefined => {
166+
export let findReScriptVersion = (
167+
filePath: p.DocumentUri
168+
): string | undefined => {
142169
let projectRoot = findProjectRootOfFile(filePath);
143170
if (projectRoot == null) {
144171
return undefined;
@@ -161,25 +188,31 @@ let findReScriptVersion = (filePath: p.DocumentUri): string | undefined => {
161188
}
162189
};
163190

191+
let binaryPath: string | null = null;
192+
if (fs.existsSync(c.analysisDevPath)) {
193+
binaryPath = c.analysisDevPath;
194+
} else if (fs.existsSync(c.analysisProdPath)) {
195+
binaryPath = c.analysisProdPath;
196+
} else {
197+
}
198+
164199
export let runAnalysisAfterSanityCheck = (
165200
filePath: p.DocumentUri,
166201
args: Array<any>,
167202
projectRequired = false
168203
) => {
169-
let binaryPath;
170-
if (fs.existsSync(c.analysisDevPath)) {
171-
binaryPath = c.analysisDevPath;
172-
} else if (fs.existsSync(c.analysisProdPath)) {
173-
binaryPath = c.analysisProdPath;
174-
} else {
204+
if (binaryPath == null) {
175205
return null;
176206
}
177207

178208
let projectRootPath = findProjectRootOfFile(filePath);
179209
if (projectRootPath == null && projectRequired) {
180210
return null;
181211
}
182-
let rescriptVersion = findReScriptVersion(filePath);
212+
let rescriptVersion =
213+
projectsFiles.get(projectRootPath ?? "")?.rescriptVersion ??
214+
findReScriptVersion(filePath);
215+
183216
let options: childProcess.ExecFileSyncOptions = {
184217
cwd: projectRootPath || undefined,
185218
maxBuffer: Infinity,
@@ -445,9 +478,6 @@ let parseFileAndRange = (fileAndRange: string) => {
445478
};
446479

447480
// main parsing logic
448-
export type filesDiagnostics = {
449-
[key: string]: p.Diagnostic[];
450-
};
451481
type parsedCompilerLogResult = {
452482
done: boolean;
453483
result: filesDiagnostics;

0 commit comments

Comments
 (0)