feat(coding-agent): add automatic theme mode (#5874)
This commit is contained in:
@@ -128,22 +128,19 @@ import { UserMessageComponent } from "./components/user-message.ts";
|
||||
import { UserMessageSelectorComponent } from "./components/user-message-selector.ts";
|
||||
import { getModelSearchText } from "./model-search.ts";
|
||||
import {
|
||||
detectTerminalBackgroundTheme,
|
||||
getAvailableThemes,
|
||||
getAvailableThemesWithPaths,
|
||||
getEditorTheme,
|
||||
getMarkdownTheme,
|
||||
getThemeByName,
|
||||
initTheme,
|
||||
onThemeChange,
|
||||
setRegisteredThemes,
|
||||
setTheme,
|
||||
setThemeInstance,
|
||||
stopThemeWatcher,
|
||||
Theme,
|
||||
type ThemeColor,
|
||||
theme,
|
||||
} from "./theme/theme.ts";
|
||||
import { InteractiveThemeController } from "./theme/theme-controller.ts";
|
||||
|
||||
/** Interface for components that can be expanded/collapsed */
|
||||
interface Expandable {
|
||||
@@ -374,6 +371,7 @@ export class InteractiveMode {
|
||||
|
||||
private options: InteractiveModeOptions;
|
||||
private autoTrustOnReloadCwd: string | undefined;
|
||||
private themeController: InteractiveThemeController;
|
||||
|
||||
// Convenience accessors
|
||||
private get session(): AgentSession {
|
||||
@@ -428,26 +426,12 @@ export class InteractiveMode {
|
||||
|
||||
// Register themes from resource loader and initialize
|
||||
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
||||
initTheme(this.settingsManager.getTheme(), true);
|
||||
}
|
||||
|
||||
private async detectThemeIfUnset(): Promise<void> {
|
||||
if (this.settingsManager.getTheme()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const detection = await detectTerminalBackgroundTheme({ ui: this.ui, timeoutMs: 100 });
|
||||
const result = setTheme(detection.theme, true);
|
||||
if (!result.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (detection.confidence === "high") {
|
||||
this.settingsManager.setTheme(detection.theme);
|
||||
await this.settingsManager.flush();
|
||||
}
|
||||
this.updateEditorBorderColor();
|
||||
this.ui.requestRender();
|
||||
this.themeController = new InteractiveThemeController(
|
||||
this.ui,
|
||||
this.settingsManager,
|
||||
(message) => this.showError(message),
|
||||
() => this.updateEditorBorderColor(),
|
||||
);
|
||||
}
|
||||
|
||||
private getAutocompleteSourceTag(sourceInfo?: SourceInfo): string | undefined {
|
||||
@@ -672,7 +656,7 @@ export class InteractiveMode {
|
||||
this.ui.start();
|
||||
this.isInitialized = true;
|
||||
|
||||
await this.detectThemeIfUnset();
|
||||
await this.themeController.applyFromSettings();
|
||||
|
||||
// Add header with keybindings from config (unless silenced)
|
||||
if (this.options.verbose || !this.settingsManager.getQuietStartup()) {
|
||||
@@ -2080,16 +2064,13 @@ export class InteractiveMode {
|
||||
getTheme: (name) => getThemeByName(name),
|
||||
setTheme: (themeOrName) => {
|
||||
if (themeOrName instanceof Theme) {
|
||||
setThemeInstance(themeOrName);
|
||||
this.ui.requestRender();
|
||||
return { success: true };
|
||||
return this.themeController.setThemeInstance(themeOrName);
|
||||
}
|
||||
const result = setTheme(themeOrName, true);
|
||||
const result = this.themeController.setThemeName(themeOrName);
|
||||
if (result.success) {
|
||||
if (this.settingsManager.getTheme() !== themeOrName) {
|
||||
this.settingsManager.setTheme(themeOrName);
|
||||
}
|
||||
this.ui.requestRender();
|
||||
}
|
||||
return result;
|
||||
},
|
||||
@@ -3378,6 +3359,7 @@ export class InteractiveMode {
|
||||
// which the stdout/stderr error handler turns into emergencyTerminalExit;
|
||||
// the render loop is already idle, so this cannot hot-spin (see #4144).
|
||||
await this.runtimeHost.dispose();
|
||||
this.themeController.disableAutoSync();
|
||||
await this.ui.terminal.drainInput(1000);
|
||||
this.stop();
|
||||
process.exit(0);
|
||||
@@ -3388,6 +3370,7 @@ export class InteractiveMode {
|
||||
// the final frame while the process is exiting.
|
||||
// Drain any in-flight Kitty key release events before stopping.
|
||||
// This prevents escape sequences from leaking to the parent shell over slow SSH.
|
||||
this.themeController.disableAutoSync();
|
||||
await this.ui.terminal.drainInput(1000);
|
||||
|
||||
this.stop();
|
||||
@@ -3991,7 +3974,8 @@ export class InteractiveMode {
|
||||
httpIdleTimeoutMs: this.settingsManager.getHttpIdleTimeoutMs(),
|
||||
thinkingLevel: this.session.thinkingLevel,
|
||||
availableThinkingLevels: this.session.getAvailableThinkingLevels(),
|
||||
currentTheme: this.settingsManager.getTheme() || "dark",
|
||||
currentTheme: this.settingsManager.getThemeSetting() || "dark",
|
||||
terminalTheme: this.themeController.getTerminalTheme(),
|
||||
availableThemes: getAvailableThemes(),
|
||||
hideThinkingBlock: this.hideThinkingBlock,
|
||||
collapseChangelog: this.settingsManager.getCollapseChangelog(),
|
||||
@@ -4058,21 +4042,11 @@ export class InteractiveMode {
|
||||
this.footer.invalidate();
|
||||
this.updateEditorBorderColor();
|
||||
},
|
||||
onThemeChange: (themeName) => {
|
||||
const result = setTheme(themeName, true);
|
||||
this.settingsManager.setTheme(themeName);
|
||||
this.ui.invalidate();
|
||||
if (!result.success) {
|
||||
this.showError(`Failed to load theme "${themeName}": ${result.error}\nFell back to dark theme.`);
|
||||
}
|
||||
},
|
||||
onThemePreview: (themeName) => {
|
||||
const result = setTheme(themeName, true);
|
||||
if (result.success) {
|
||||
this.ui.invalidate();
|
||||
this.ui.requestRender();
|
||||
}
|
||||
onThemeChange: (themeSetting) => {
|
||||
this.settingsManager.setTheme(themeSetting);
|
||||
void this.themeController.applyFromSettings();
|
||||
},
|
||||
onThemePreview: (themeName) => this.themeController.preview(themeName),
|
||||
onHideThinkingBlockChange: (hidden) => {
|
||||
this.hideThinkingBlock = hidden;
|
||||
this.settingsManager.setHideThinkingBlock(hidden);
|
||||
@@ -5109,11 +5083,7 @@ export class InteractiveMode {
|
||||
}
|
||||
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
||||
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
||||
const themeName = this.settingsManager.getTheme();
|
||||
const themeResult = themeName ? setTheme(themeName, true) : { success: true };
|
||||
if (!themeResult.success) {
|
||||
this.showError(`Failed to load theme "${themeName}": ${themeResult.error}\nFell back to dark theme.`);
|
||||
}
|
||||
await this.themeController.applyFromSettings();
|
||||
const editorPaddingX = this.settingsManager.getEditorPaddingX();
|
||||
const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
|
||||
this.defaultEditor.setPaddingX(editorPaddingX);
|
||||
@@ -5754,6 +5724,7 @@ export class InteractiveMode {
|
||||
this.loadingAnimation.stop();
|
||||
this.loadingAnimation = undefined;
|
||||
}
|
||||
this.themeController.disableAutoSync();
|
||||
this.clearExtensionTerminalInputListeners();
|
||||
this.footer.dispose();
|
||||
this.footerDataProvider.dispose();
|
||||
|
||||
Reference in New Issue
Block a user