fix(coding-agent): reload custom themes from disk watcher\n\ncloses #2417\ncloses #2003

This commit is contained in:
Mario Zechner
2026-03-19 21:53:05 +01:00
parent e0f85a3b5e
commit 3620adb3e2

View File

@@ -665,6 +665,7 @@ function setGlobalTheme(t: Theme): void {
let currentThemeName: string | undefined;
let themeWatcher: fs.FSWatcher | undefined;
let themeReloadTimer: NodeJS.Timeout | undefined;
let onThemeChangeCallback: (() => void) | undefined;
const registeredThemes = new Map<string, Theme>();
@@ -730,11 +731,7 @@ export function onThemeChange(callback: () => void): void {
}
function startThemeWatcher(): void {
// Stop existing watcher if any
if (themeWatcher) {
themeWatcher.close();
themeWatcher = undefined;
}
stopThemeWatcher();
// Only watch if it's a custom theme (not built-in)
if (!currentThemeName || currentThemeName === "dark" || currentThemeName === "light") {
@@ -742,45 +739,61 @@ function startThemeWatcher(): void {
}
const customThemesDir = getCustomThemesDir();
const themeFile = path.join(customThemesDir, `${currentThemeName}.json`);
const watchedThemeName = currentThemeName;
const watchedFileName = `${watchedThemeName}.json`;
const themeFile = path.join(customThemesDir, watchedFileName);
// Only watch if the file exists
if (!fs.existsSync(themeFile)) {
return;
}
try {
themeWatcher = fs.watch(themeFile, (eventType) => {
if (eventType === "change") {
// Debounce rapid changes
setTimeout(() => {
try {
// Reload the theme
setGlobalTheme(loadTheme(currentThemeName!));
// Notify callback (to invalidate UI)
if (onThemeChangeCallback) {
onThemeChangeCallback();
}
} catch (_error) {
// Ignore errors (file might be in invalid state while being edited)
}
}, 100);
} else if (eventType === "rename") {
// File was deleted or renamed - fall back to default theme
setTimeout(() => {
if (!fs.existsSync(themeFile)) {
currentThemeName = "dark";
setGlobalTheme(loadTheme("dark"));
if (themeWatcher) {
themeWatcher.close();
themeWatcher = undefined;
}
if (onThemeChangeCallback) {
onThemeChangeCallback();
}
}
}, 100);
const scheduleReload = () => {
if (themeReloadTimer) {
clearTimeout(themeReloadTimer);
}
themeReloadTimer = setTimeout(() => {
themeReloadTimer = undefined;
// Ignore stale timers after switching themes or stopping the watcher
if (currentThemeName !== watchedThemeName) {
return;
}
// Keep the last successfully loaded theme active if the file is temporarily missing
if (!fs.existsSync(themeFile)) {
return;
}
try {
// Reload the theme from disk and refresh the registry cache
const reloadedTheme = loadThemeFromPath(themeFile);
registeredThemes.set(watchedThemeName, reloadedTheme);
setGlobalTheme(reloadedTheme);
// Notify callback (to invalidate UI)
if (onThemeChangeCallback) {
onThemeChangeCallback();
}
} catch (_error) {
// Ignore errors (file might be in invalid state while being edited)
}
}, 100);
};
try {
themeWatcher = fs.watch(customThemesDir, (_eventType, filename) => {
if (currentThemeName !== watchedThemeName) {
return;
}
if (!filename) {
scheduleReload();
return;
}
const changedFile = String(filename);
if (changedFile !== watchedFileName) {
return;
}
scheduleReload();
});
} catch (_error) {
// Ignore errors starting watcher
@@ -788,6 +801,10 @@ function startThemeWatcher(): void {
}
export function stopThemeWatcher(): void {
if (themeReloadTimer) {
clearTimeout(themeReloadTimer);
themeReloadTimer = undefined;
}
if (themeWatcher) {
themeWatcher.close();
themeWatcher = undefined;