Skip to content

Commit

Permalink
avoid recomputing project roots and ReScript versions as much as poss…
Browse files Browse the repository at this point in the history
…ible
  • Loading branch information
zth committed Jun 4, 2024
1 parent 7c9f1bd commit 3b1d2a9
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 34 deletions.
25 changes: 25 additions & 0 deletions server/src/projectFiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as cp from "node:child_process";
import * as p from "vscode-languageserver-protocol";

export type filesDiagnostics = {
[key: string]: p.Diagnostic[];
};

interface projectFiles {
openFiles: Set<string>;
filesWithDiagnostics: Set<string>;
filesDiagnostics: filesDiagnostics;
rescriptVersion: string | undefined;

bsbWatcherByEditor: null | cp.ChildProcess;

// This keeps track of whether we've prompted the user to start a build
// automatically, if there's no build currently running for the project. We
// only want to prompt the user about this once, or it becomes
// annoying.
// The type `never` means that we won't show the prompt if the project is inside node_modules
hasPromptedToStartBuild: boolean | "never";
}

export let projectsFiles: Map<string, projectFiles> = // project root path
new Map();
22 changes: 3 additions & 19 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,11 @@ import * as c from "./constants";
import * as chokidar from "chokidar";
import { assert } from "console";
import { fileURLToPath } from "url";
import * as cp from "node:child_process";
import { WorkspaceEdit } from "vscode-languageserver";
import { filesDiagnostics } from "./utils";
import { onErrorReported } from "./errorReporter";
import * as ic from "./incrementalCompilation";
import config, { extensionConfiguration } from "./config";
import { projectsFiles } from "./projectFiles";

// This holds client capabilities specific to our extension, and not necessarily
// related to the LS protocol. It's for enabling/disabling features that might
Expand All @@ -49,23 +48,7 @@ let serverSentRequestIdCounter = 0;
// https://microsoft.github.io/language-server-protocol/specification#exit
let shutdownRequestAlreadyReceived = false;
let stupidFileContentCache: Map<string, string> = new Map();
let projectsFiles: Map<
string, // project root path
{
openFiles: Set<string>;
filesWithDiagnostics: Set<string>;
filesDiagnostics: filesDiagnostics;

bsbWatcherByEditor: null | cp.ChildProcess;

// This keeps track of whether we've prompted the user to start a build
// automatically, if there's no build currently running for the project. We
// only want to prompt the user about this once, or it becomes
// annoying.
// The type `never` means that we won't show the prompt if the project is inside node_modules
hasPromptedToStartBuild: boolean | "never";
}
> = new Map();

// ^ caching AND states AND distributed system. Why does LSP has to be stupid like this

// This keeps track of code actions extracted from diagnostics.
Expand Down Expand Up @@ -279,6 +262,7 @@ let openedFile = (fileUri: string, fileContent: string) => {
openFiles: new Set(),
filesWithDiagnostics: new Set(),
filesDiagnostics: {},
rescriptVersion: utils.findReScriptVersion(projectRootPath),
bsbWatcherByEditor: null,
hasPromptedToStartBuild: /(\/|\\)node_modules(\/|\\)/.test(
projectRootPath
Expand Down
60 changes: 45 additions & 15 deletions server/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import * as c from "./constants";
import * as lookup from "./lookup";
import { reportError } from "./errorReporter";
import config from "./config";
import { filesDiagnostics, projectsFiles } from "./projectFiles";

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

// TODO: races here?
// TODO: this doesn't handle file:/// scheme
export let findProjectRootOfFile = (
let findProjectRootOfFileInDir = (
source: p.DocumentUri
): null | p.DocumentUri => {
let dir = path.dirname(source);
Expand All @@ -40,11 +39,37 @@ export let findProjectRootOfFile = (
// reached top
return null;
} else {
return findProjectRootOfFile(dir);
return findProjectRootOfFileInDir(dir);
}
}
};

// TODO: races here?
// TODO: this doesn't handle file:/// scheme
export let findProjectRootOfFile = (
source: p.DocumentUri
): null | p.DocumentUri => {
// First look in project files
let foundRootFromProjectFiles: string | null = null;

for (const rootPath of projectsFiles.keys()) {
if (source.startsWith(rootPath)) {
// Prefer the longest path (most nested)
if (
foundRootFromProjectFiles == null ||
rootPath.length > foundRootFromProjectFiles.length
)
foundRootFromProjectFiles = rootPath;
}
}

if (foundRootFromProjectFiles != null) {
return foundRootFromProjectFiles;
} else {
return findProjectRootOfFileInDir(source);
}
};

// Check if binaryName exists inside binaryDirPath and return the joined path.
export let findBinary = (
binaryDirPath: p.DocumentUri | null,
Expand Down Expand Up @@ -138,7 +163,9 @@ export let formatCode = (
}
};

let findReScriptVersion = (filePath: p.DocumentUri): string | undefined => {
export let findReScriptVersion = (
filePath: p.DocumentUri
): string | undefined => {
let projectRoot = findProjectRootOfFile(filePath);
if (projectRoot == null) {
return undefined;
Expand All @@ -161,25 +188,31 @@ let findReScriptVersion = (filePath: p.DocumentUri): string | undefined => {
}
};

let binaryPath: string | null = null;
if (fs.existsSync(c.analysisDevPath)) {
binaryPath = c.analysisDevPath;
} else if (fs.existsSync(c.analysisProdPath)) {
binaryPath = c.analysisProdPath;
} else {
}

export let runAnalysisAfterSanityCheck = (
filePath: p.DocumentUri,
args: Array<any>,
projectRequired = false
) => {
let binaryPath;
if (fs.existsSync(c.analysisDevPath)) {
binaryPath = c.analysisDevPath;
} else if (fs.existsSync(c.analysisProdPath)) {
binaryPath = c.analysisProdPath;
} else {
if (binaryPath == null) {
return null;
}

let projectRootPath = findProjectRootOfFile(filePath);
if (projectRootPath == null && projectRequired) {
return null;
}
let rescriptVersion = findReScriptVersion(filePath);
let rescriptVersion =
projectsFiles.get(projectRootPath ?? "")?.rescriptVersion ??
findReScriptVersion(filePath);

let options: childProcess.ExecFileSyncOptions = {
cwd: projectRootPath || undefined,
maxBuffer: Infinity,
Expand Down Expand Up @@ -449,9 +482,6 @@ let parseFileAndRange = (fileAndRange: string) => {
};

// main parsing logic
export type filesDiagnostics = {
[key: string]: p.Diagnostic[];
};
type parsedCompilerLogResult = {
done: boolean;
result: filesDiagnostics;
Expand Down

0 comments on commit 3b1d2a9

Please sign in to comment.