Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

- Protect against trying to read non-existant `.compiler.log`. https://github.com/rescript-lang/rescript-vscode/pull/1116

#### :rocket: New Feature

- Add status bar item tracking compilation state. https://github.com/rescript-lang/rescript-vscode/pull/1119

## 1.64.0

#### :rocket: New Feature
Expand Down
129 changes: 129 additions & 0 deletions client/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
CodeActionKind,
Diagnostic,
} from "vscode";
import { ThemeColor } from "vscode";

import {
LanguageClient,
Expand Down Expand Up @@ -188,6 +189,120 @@ export function activate(context: ExtensionContext) {
StatusBarAlignment.Right,
);

let compilationStatusBarItem = window.createStatusBarItem(
StatusBarAlignment.Right,
);
context.subscriptions.push(compilationStatusBarItem);

let compileStatusEnabled: boolean = workspace
.getConfiguration("rescript.settings")
.get<boolean>("compileStatus.enable", true);

type ClientCompileStatus = {
status: "compiling" | "success" | "error" | "warning";
project: string;
errorCount: number;
warningCount: number;
};
const projectStatuses: Map<string, ClientCompileStatus> = new Map();

const refreshCompilationStatusItem = () => {
if (!compileStatusEnabled) {
compilationStatusBarItem.hide();
compilationStatusBarItem.tooltip = undefined;
compilationStatusBarItem.backgroundColor = undefined;
compilationStatusBarItem.command = undefined;
return;
}
const entries = [...projectStatuses.values()];
const compiling = entries.filter((e) => e.status === "compiling");
const errors = entries.filter((e) => e.status === "error");
const warnings = entries.filter((e) => e.status === "warning");

if (compiling.length > 0) {
compilationStatusBarItem.text = `$(loading~spin) ReScript`;
compilationStatusBarItem.tooltip = compiling
.map((e) => e.project)
.join(", ");
compilationStatusBarItem.backgroundColor = undefined;
compilationStatusBarItem.command = undefined;
compilationStatusBarItem.show();
return;
}

if (errors.length > 0) {
compilationStatusBarItem.text = `$(alert) ReScript: Failed`;
compilationStatusBarItem.backgroundColor = new ThemeColor(
"statusBarItem.errorBackground",
);
compilationStatusBarItem.command = "rescript-vscode.showProblems";
const byProject = errors.map((e) => `${e.project} (${e.errorCount})`);
compilationStatusBarItem.tooltip = `Failed: ${byProject.join(", ")}`;
compilationStatusBarItem.show();
return;
}

if (warnings.length > 0) {
compilationStatusBarItem.text = `$(warning) ReScript: Warnings`;
compilationStatusBarItem.backgroundColor = undefined;
compilationStatusBarItem.color = new ThemeColor(
"statusBarItem.warningBackground",
);
compilationStatusBarItem.command = "rescript-vscode.showProblems";
const byProject = warnings.map((e) => `${e.project} (${e.warningCount})`);
compilationStatusBarItem.tooltip = `Warnings: ${byProject.join(", ")}`;
compilationStatusBarItem.show();
return;
}

const successes = entries.filter((e) => e.status === "success");
if (successes.length > 0) {
// Compact success display: project label plus a green check emoji
compilationStatusBarItem.text = `$(check) ReScript: Ok`;
compilationStatusBarItem.backgroundColor = undefined;
compilationStatusBarItem.color = null;
compilationStatusBarItem.command = undefined;
const projects = successes.map((e) => e.project).join(", ");
compilationStatusBarItem.tooltip = projects
? `Compilation Succeeded: ${projects}`
: `Compilation Succeeded`;
compilationStatusBarItem.show();
return;
}

compilationStatusBarItem.hide();
compilationStatusBarItem.tooltip = undefined;
compilationStatusBarItem.backgroundColor = undefined;
compilationStatusBarItem.command = undefined;
};

context.subscriptions.push(
client.onDidChangeState(({ newState }) => {
if (newState === State.Running) {
context.subscriptions.push(
client.onNotification(
"rescript/compilationStatus",
(payload: {
project: string;
projectRootPath: string;
status: "compiling" | "success" | "error" | "warning";
errorCount: number;
warningCount: number;
}) => {
projectStatuses.set(payload.projectRootPath, {
status: payload.status,
project: payload.project,
errorCount: payload.errorCount,
warningCount: payload.warningCount,
});
refreshCompilationStatusItem();
},
),
);
}
}),
);

let inCodeAnalysisState: {
active: boolean;
activatedFromDirectory: string | null;
Expand Down Expand Up @@ -256,6 +371,14 @@ export function activate(context: ExtensionContext) {
customCommands.dumpDebug(context, debugDumpStatusBarItem);
});

commands.registerCommand("rescript-vscode.showProblems", async () => {
try {
await commands.executeCommand("workbench.actions.view.problems");
} catch {
outputChannel.show();
}
});

commands.registerCommand("rescript-vscode.debug-dump-retrigger", () => {
customCommands.dumpDebugRetrigger();
});
Expand Down Expand Up @@ -346,6 +469,12 @@ export function activate(context: ExtensionContext) {
) {
commands.executeCommand("rescript-vscode.restart_language_server");
} else {
if (affectsConfiguration("rescript.settings.compileStatus.enable")) {
compileStatusEnabled = workspace
.getConfiguration("rescript.settings")
.get<boolean>("compileStatus.enable", true);
refreshCompilationStatusItem();
}
// Send a general message that configuration has updated. Clients
// interested can then pull the new configuration as they see fit.
client
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@
],
"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.compileStatus.enable": {
"type": "boolean",
"default": true,
"description": "Show compile status in the status bar (compiling/errors/warnings/success)."
}
}
},
Expand Down
111 changes: 111 additions & 0 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,36 @@ let codeActionsFromDiagnostics: codeActions.filesCodeActions = {};
// will be properly defined later depending on the mode (stdio/node-rpc)
let send: (msg: p.Message) => void = (_) => {};

type ProjectCompilationState = {
active: boolean;
startAt: number | null;
lastSent: {
status: "compiling" | "success" | "error" | "warning";
errorCount: number;
warningCount: number;
} | null;
timer: NodeJS.Timeout | null;
};
const projectCompilationStates: Map<string, ProjectCompilationState> =
new Map();

type CompilationStatusPayload = {
project: string;
projectRootPath: string;
status: "compiling" | "success" | "error" | "warning";
errorCount: number;
warningCount: number;
};

const sendCompilationStatus = (payload: CompilationStatusPayload) => {
const message: p.NotificationMessage = {
jsonrpc: c.jsonrpcVersion,
method: "rescript/compilationStatus",
params: payload,
};
send(message);
};

let findRescriptBinary = async (
projectRootPath: p.DocumentUri | null,
): Promise<string | null> => {
Expand Down Expand Up @@ -168,6 +198,86 @@ let sendUpdatedDiagnostics = async () => {
}
});
}

try {
const state = projectCompilationStates.get(projectRootPath) ?? {
active: false,
startAt: null,
lastSent: null,
timer: null,
};

const lastStart = content.lastIndexOf("#Start");
const lastDone = content.lastIndexOf("#Done");
const isActive = lastStart > lastDone;

let errorCount = 0;
let warningCount = 0;
for (const [fileUri, diags] of Object.entries(filesAndErrors)) {
const filePath = fileURLToPath(fileUri);
if (filePath.startsWith(projectRootPath)) {
for (const d of diags as v.Diagnostic[]) {
if (d.severity === v.DiagnosticSeverity.Error) errorCount++;
else if (d.severity === v.DiagnosticSeverity.Warning)
warningCount++;
}
}
}

const projectName = path.basename(projectRootPath);

const sendIfChanged = (
status: "compiling" | "success" | "error" | "warning",
) => {
const last = state.lastSent;
if (
last == null ||
last.status !== status ||
last.errorCount !== errorCount ||
last.warningCount !== warningCount
) {
sendCompilationStatus({
project: projectName,
projectRootPath,
status,
errorCount,
warningCount,
});
state.lastSent = { status, errorCount, warningCount };
}
};

if (isActive) {
if (!state.active) {
state.active = true;
state.startAt = Date.now();
if (state.timer) clearTimeout(state.timer);
state.timer = setTimeout(() => {
const cur = projectCompilationStates.get(projectRootPath);
if (cur && cur.active) {
sendIfChanged("compiling");
}
}, 100);
}
} else {
if (state.timer) {
clearTimeout(state.timer);
state.timer = null;
}
state.active = false;
state.startAt = null;

if (errorCount > 0) {
sendIfChanged("error");
} else if (warningCount > 0) {
sendIfChanged("warning");
} else {
sendIfChanged("success");
}
}

projectCompilationStates.set(projectRootPath, state);
} catch {}
}
};

Expand All @@ -188,6 +298,7 @@ let deleteProjectDiagnostics = (projectRootPath: string) => {
});

projectsFiles.delete(projectRootPath);
projectCompilationStates.delete(projectRootPath);
if (config.extensionConfiguration.incrementalTypechecking?.enable) {
ic.removeIncrementalFileFolder(projectRootPath);
}
Expand Down
Loading