Skip to content

Add status bar compilation status item #1119

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

Merged
merged 3 commits into from
Aug 21, 2025
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
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` |
| Compile Status Indicator | Shows compile status in the status bar (Compiling, Errors, Warnings, Success). Toggle via `rescript.settings.compileStatus.enable`. Clicking in Error/Warning modes focuses the Problems view. |

**Default settings:**

Expand All @@ -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

// Show compile status in the status bar (compiling/errors/warnings/success)
"rescript.settings.compileStatus.enable": true
```

## 🚀 Code Analyzer
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