feat(vscode): use terminal.integrated font settings in Agent Manager xterm terminals

Reads terminal.integrated.fontFamily and terminal.integrated.fontSize (with
editor font fallback) instead of the editor font CSS var. A config watcher
broadcasts fontChanged messages so live terminals update without a reload.
Nerd Font glyphs now render correctly in Agent Manager terminal tabs.
This commit is contained in:
kiloconnect[bot]
2026-06-11 21:16:26 +00:00
parent 43f783b7dc
commit 287f75aa2b
9 changed files with 131 additions and 9 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"kilo-vscode": patch
---
Agent Manager terminals now use `terminal.integrated.fontFamily` and `terminal.integrated.fontSize` (including Nerd Font glyphs) instead of the editor font.
@@ -36,6 +36,7 @@ import { parseToolRequest, startFromTool, type ToolRequest } from "./tool-start"
import { stopSessionProcesses } from "../kilo-provider/background-process"
import { startSession } from "./mcp-warmup"
import { readTerminalFont, watchTerminalFont } from "./terminal-font"
import { buildKeybindingMap } from "./format-keybinding"
import { resolveVersionModels, buildInitialMessages, type CreatedVersion } from "./multi-version"
import { Semaphore } from "./semaphore"
@@ -73,6 +74,7 @@ export class AgentManagerProvider implements Disposable {
private cachedWorktreeStats: { type: "agentManager.worktreeStats"; stats: WorktreeStats[] } | undefined
private cachedLocalStats: { type: "agentManager.localStats"; stats: LocalStats } | undefined
private unsubTool: (() => void) | undefined
private unsubFont: (() => void) | undefined
private closing: Promise<void> | undefined
private onVisibilityChange: ((visible: boolean) => void) | undefined
@@ -96,6 +98,10 @@ export class AgentManagerProvider implements Disposable {
getWorktreePath: (id) => this.getStateManager()?.getWorktree(id)?.path,
log: (...args) => this.log("[XTerm]", ...args),
post: (msg) => this.postToWebview(msg),
getTerminalFont: () => readTerminalFont(),
})
this.unsubFont = watchTerminalFont((font) => {
this.postToWebview({ type: "agentManager.terminal.fontChanged", font })
})
this.run = new RunController({
root: () => this.getRoot(),
@@ -1791,6 +1797,7 @@ export class AgentManagerProvider implements Disposable {
await this.stateReady?.catch((err) => this.log("dispose: stateReady rejected:", err))
await this.state?.flush().catch((err) => this.log("dispose: state flush failed:", err))
this.unsubTool?.()
this.unsubFont?.()
this.connectionService.unregisterFocused("agent-manager")
this.connectionService.registerOpen("agent-manager", [])
this.diffs.stop()
@@ -0,0 +1,59 @@
/**
* Helpers to read and watch the user's integrated-terminal font settings.
*
* VS Code's integrated terminal mirrors editor font settings when terminal
* font settings are unset. We replicate that same logic here so the Agent
* Manager xterm instances look identical to VS Code's own terminal.
*/
import * as vscode from "vscode"
export interface TerminalFont {
fontFamily: string
fontSize: number
fontWeight?: string
fontWeightBold?: string
lineHeight?: number
letterSpacing?: number
}
const FALLBACK = "Menlo, Monaco, 'Courier New', monospace"
/** Resolve the user's integrated-terminal font, mirroring VS Code's own
* fallback to the editor font when the terminal font is unset. */
export function readTerminalFont(): TerminalFont {
const term = vscode.workspace.getConfiguration("terminal.integrated")
const editor = vscode.workspace.getConfiguration("editor")
const family = term.get<string>("fontFamily")?.trim() || editor.get<string>("fontFamily")?.trim() || FALLBACK
const size = term.get<number>("fontSize") || editor.get<number>("fontSize") || 13
return {
fontFamily: family,
fontSize: size,
fontWeight: term.get<string>("fontWeight") || undefined,
fontWeightBold: term.get<string>("fontWeightBold") || undefined,
lineHeight: term.get<number>("lineHeight") || undefined,
letterSpacing: term.get<number>("letterSpacing") || undefined,
}
}
/** True when a config change touches any terminal/editor font setting. */
export function affectsTerminalFont(e: vscode.ConfigurationChangeEvent): boolean {
return (
e.affectsConfiguration("terminal.integrated.fontFamily") ||
e.affectsConfiguration("terminal.integrated.fontSize") ||
e.affectsConfiguration("terminal.integrated.fontWeight") ||
e.affectsConfiguration("terminal.integrated.fontWeightBold") ||
e.affectsConfiguration("terminal.integrated.lineHeight") ||
e.affectsConfiguration("terminal.integrated.letterSpacing") ||
e.affectsConfiguration("editor.fontFamily") ||
e.affectsConfiguration("editor.fontSize")
)
}
/** Subscribe to terminal-font config changes. Returns a cleanup function. */
export function watchTerminalFont(callback: (font: TerminalFont) => void): () => void {
const sub = vscode.workspace.onDidChangeConfiguration((e) => {
if (affectsTerminalFont(e)) callback(readTerminalFont())
})
return () => sub.dispose()
}
@@ -15,7 +15,7 @@
*/
import type { KiloClient } from "@kilocode/sdk/v2/client"
import type { AgentManagerInMessage, AgentManagerOutMessage } from "./types"
import type { AgentManagerInMessage, AgentManagerOutMessage, TerminalFont } from "./types"
import { TerminalManager } from "./terminal-manager"
interface ServerConfig {
@@ -36,6 +36,8 @@ export interface TerminalRoutingDeps {
log(...args: unknown[]): void
/** Send a message back to the webview. */
post(message: AgentManagerOutMessage): void
/** Return the current terminal font settings. */
getTerminalFont(): TerminalFont
}
/** True iff the message belongs to the terminal-tab subsystem. */
@@ -106,6 +108,7 @@ export class TerminalRouter {
terminalId: created.terminalId,
title: created.title,
wsUrl: created.wsUrl,
font: this.deps.getTerminalFont(),
})
} catch (err) {
const message = err instanceof Error ? err.message : String(err)
@@ -14,6 +14,9 @@ import type { ApplyConflict } from "./GitOps"
import type { BranchListItem, WorktreeSetupErrorCode } from "./git-import"
import type { ExternalWorktreeItem } from "./WorktreeManager"
import type { RunStatus } from "./run/manager"
import type { TerminalFont } from "./terminal-font"
export type { TerminalFont }
// ---------------------------------------------------------------------------
// Shared payload types
@@ -147,6 +150,7 @@ interface TerminalCreatedMessage {
terminalId: string
title: string
wsUrl: string
font: TerminalFont
}
interface TerminalClosedMessage {
@@ -160,6 +164,11 @@ interface TerminalErrorMessage {
message: string
}
interface TerminalFontChangedMessage {
type: "agentManager.terminal.fontChanged"
font: TerminalFont
}
interface ErrorOutMessage {
type: "error"
message: string
@@ -314,6 +323,7 @@ export type AgentManagerOutMessage =
| TerminalCreatedMessage
| TerminalClosedMessage
| TerminalErrorMessage
| TerminalFontChangedMessage
// ---------------------------------------------------------------------------
// Webview → Extension messages (onMessage)
@@ -653,6 +653,10 @@ const VSCODE_ALLOWED: Record<string, { note: string }> = {
"run/task.ts": {
note: "vscode adapter for Agent Manager run scripts",
},
// Reads terminal.integrated.* and editor.font* config for xterm font settings
"terminal-font.ts": {
note: "vscode config reader for integrated terminal font settings",
},
}
/**
@@ -11,7 +11,7 @@
*/
import { Component, createEffect, onCleanup, onMount } from "solid-js"
import { Terminal } from "@xterm/xterm"
import { Terminal, type FontWeight } from "@xterm/xterm"
import { FitAddon } from "@xterm/addon-fit"
import { WebLinksAddon } from "@xterm/addon-web-links"
import { ClipboardAddon } from "@xterm/addon-clipboard"
@@ -20,10 +20,14 @@ import "@xterm/xterm/css/xterm.css"
import { useVSCode } from "../../src/context/vscode"
import { useLanguage } from "../../src/context/language"
import { formatReviewCommentsMarkdown } from "../../src/utils/review-comment-markdown"
import type { TerminalFont } from "./state"
interface Props {
terminalId: string
wsUrl: string
/** Terminal font settings forwarded from the extension host. Used on
* initial mount; live changes arrive via `agentManager.terminal.fontChanged`. */
font: TerminalFont
/** Whether this terminal is currently the focused tab.
*
* The xterm subtree always stays in the paint tree (see the layer /
@@ -126,12 +130,16 @@ export const TerminalTab: Component<Props> = (props) => {
const term = new Terminal({
convertEol: true,
cursorBlink: true,
fontFamily: cssVar("--vscode-editor-font-family", "Menlo, Monaco, 'Courier New', monospace"),
fontSize: Number.parseFloat(cssVar("--font-size-base", "13px")),
fontFamily: props.font.fontFamily,
fontSize: props.font.fontSize,
scrollback: 5000,
theme: readTheme(),
allowProposedApi: true,
})
if (props.font.fontWeight) term.options.fontWeight = props.font.fontWeight as FontWeight
if (props.font.fontWeightBold) term.options.fontWeightBold = props.font.fontWeightBold as FontWeight
if (props.font.lineHeight) term.options.lineHeight = props.font.lineHeight
if (props.font.letterSpacing) term.options.letterSpacing = props.font.letterSpacing
const fit = new FitAddon()
term.loadAddon(fit)
// Clickable URLs in terminal output (Cmd/Ctrl+click to open).
@@ -288,12 +296,26 @@ export const TerminalTab: Component<Props> = (props) => {
return
}
if (message.type === "agentManager.terminal.fontChanged") {
const font = message.font as TerminalFont
term.options.fontFamily = font.fontFamily
term.options.fontSize = font.fontSize
if (font.fontWeight) term.options.fontWeight = font.fontWeight as FontWeight
if (font.fontWeightBold) term.options.fontWeightBold = font.fontWeightBold as FontWeight
if (font.lineHeight) term.options.lineHeight = font.lineHeight
if (font.letterSpacing) term.options.letterSpacing = font.letterSpacing
fit.fit()
syncSize()
scheduleRepaint()
return
}
// fontSizeChanged/ready control the Kilo chat UI font — do not apply
// them to the terminal, which has its own independent font settings.
// Keep the repaint for any downstream layout side-effects.
const size =
message.type === "fontSizeChanged" ? message.fontSize : message.type === "ready" ? message.fontSize : undefined
if (size === undefined) return
term.options.fontSize = size
fit.fit()
syncSize()
scheduleRepaint()
})
@@ -85,7 +85,7 @@ export function renderTerminalLayer(props: { state: TerminalStateControls }): JS
const visible = () => slotVisible(term.id, term.contextKey)
return (
<div class={`am-terminal-slot ${visible() ? "am-terminal-slot-visible" : ""}`}>
<TerminalTab terminalId={term.id} wsUrl={term.wsUrl} active={visible()} />
<TerminalTab terminalId={term.id} wsUrl={term.wsUrl} active={visible()} font={term.font} />
</div>
)
}}
@@ -11,6 +11,16 @@ import { createMemo, createSignal } from "solid-js"
import type { Accessor } from "solid-js"
import { LOCAL } from "../navigate"
/** Font settings forwarded from the extension host (mirrors terminal-font.ts). */
export interface TerminalFont {
fontFamily: string
fontSize: number
fontWeight?: string
fontWeightBold?: string
lineHeight?: number
letterSpacing?: number
}
/** Prefix used for terminal tab IDs in the webview (mirrors terminal-manager.ts). */
export const TERMINAL_PREFIX = "terminal:"
@@ -21,6 +31,7 @@ export interface TerminalTabState {
id: string
title: string
wsUrl: string
font: TerminalFont
}
/** Terminal row enriched with the sidebar context it belongs to. Used by
@@ -35,6 +46,7 @@ export interface TerminalCreatedEvent {
terminalId: string
title: string
wsUrl: string
font: TerminalFont
}
export interface TerminalStateControls {
@@ -335,7 +347,7 @@ export function createTerminalMessageHandler(deps: TerminalMessageHandlerDeps) {
if (msg.type === "agentManager.terminal.created") {
const ev = msg as unknown as TerminalCreatedEvent
const contextKey = ev.worktreeId === null ? LOCAL : ev.worktreeId
deps.state.add(ev.worktreeId, { id: ev.terminalId, title: ev.title, wsUrl: ev.wsUrl })
deps.state.add(ev.worktreeId, { id: ev.terminalId, title: ev.title, wsUrl: ev.wsUrl, font: ev.font })
deps.onCreated?.(contextKey, ev.terminalId)
deps.saveTabMemory()
deps.setSelection(contextKey)