mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
fix(vscode): complete Agent Manager terminal font sync
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
/**
|
||||
* 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.
|
||||
* VS Code's integrated terminal falls back to the editor font family when
|
||||
* its own family is unset. Font size has a separate platform default. We
|
||||
* replicate those settings for Agent Manager xterm instances.
|
||||
*/
|
||||
|
||||
import * as vscode from "vscode"
|
||||
@@ -11,42 +11,29 @@ 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"
|
||||
const SIZE = process.platform === "darwin" ? 12 : 14
|
||||
|
||||
/** Resolve the user's integrated-terminal font, mirroring VS Code's own
|
||||
* fallback to the editor font when the terminal font is unset. */
|
||||
* family fallback while preserving the terminal's independent size. */
|
||||
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,
|
||||
fontSize: term.get<number>("fontSize") ?? SIZE,
|
||||
}
|
||||
}
|
||||
|
||||
/** True when a config change touches any terminal/editor font setting. */
|
||||
/** True when a config change affects the effective terminal family or size. */
|
||||
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")
|
||||
e.affectsConfiguration("editor.fontFamily")
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import type { KiloClient } from "@kilocode/sdk/v2/client"
|
||||
import { createRoot } from "solid-js"
|
||||
import { affectsTerminalFont, readTerminalFont } from "../../src/agent-manager/terminal-font"
|
||||
import { TerminalRouter } from "../../src/agent-manager/terminal-routing"
|
||||
import type { AgentManagerOutMessage, TerminalFont } from "../../src/agent-manager/types"
|
||||
import { createTerminalMessageHandler, createTerminalState } from "../../webview-ui/agent-manager/terminal/state"
|
||||
import { LOCAL } from "../../webview-ui/agent-manager/navigate"
|
||||
import type { ExtensionMessage } from "../../webview-ui/src/types/messages/extension-messages"
|
||||
|
||||
const font: TerminalFont = {
|
||||
fontFamily: "MesloLGS NF",
|
||||
fontSize: 18,
|
||||
}
|
||||
|
||||
describe("Agent Manager terminal font", () => {
|
||||
it("uses the platform terminal size when configuration is unavailable", () => {
|
||||
expect(readTerminalFont()).toEqual({
|
||||
fontFamily: "Menlo, Monaco, 'Courier New', monospace",
|
||||
fontSize: process.platform === "darwin" ? 12 : 14,
|
||||
})
|
||||
})
|
||||
|
||||
it("watches only settings that affect the terminal family or size", () => {
|
||||
const event = (key: string) =>
|
||||
({
|
||||
affectsConfiguration: (target: string) => target === key,
|
||||
}) as Parameters<typeof affectsTerminalFont>[0]
|
||||
|
||||
expect(affectsTerminalFont(event("terminal.integrated.fontFamily"))).toBe(true)
|
||||
expect(affectsTerminalFont(event("terminal.integrated.fontSize"))).toBe(true)
|
||||
expect(affectsTerminalFont(event("editor.fontFamily"))).toBe(true)
|
||||
expect(affectsTerminalFont(event("editor.fontSize"))).toBe(false)
|
||||
expect(affectsTerminalFont(event("terminal.integrated.letterSpacing"))).toBe(false)
|
||||
})
|
||||
|
||||
it("includes the current font when creating a terminal", async () => {
|
||||
const client = {
|
||||
pty: {
|
||||
create: async () => ({ data: { id: "pty-1", title: "Terminal 1" } }),
|
||||
remove: async () => ({ data: true }),
|
||||
update: async () => ({ data: true }),
|
||||
},
|
||||
} as unknown as KiloClient
|
||||
const message = new Promise<AgentManagerOutMessage>((resolve) => {
|
||||
const router = new TerminalRouter({
|
||||
getClient: () => client,
|
||||
getServerConfig: () => ({ baseUrl: "http://127.0.0.1:4096", password: "secret" }),
|
||||
getRoot: () => "/workspace",
|
||||
getWorktreePath: () => undefined,
|
||||
log: () => undefined,
|
||||
post: resolve,
|
||||
getTerminalFont: () => font,
|
||||
})
|
||||
|
||||
expect(router.handle({ type: "agentManager.terminal.create", worktreeId: null })).toBe(true)
|
||||
})
|
||||
|
||||
const created = await message
|
||||
expect(created.type).toBe("agentManager.terminal.created")
|
||||
if (created.type !== "agentManager.terminal.created") return
|
||||
expect(created.font).toEqual(font)
|
||||
expect(created.worktreeId).toBeNull()
|
||||
expect(created.wsUrl).toContain("/pty/pty-1/connect")
|
||||
})
|
||||
|
||||
it("keeps the created font in terminal state", () => {
|
||||
createRoot((dispose) => {
|
||||
const state = createTerminalState(() => LOCAL)
|
||||
const activated: string[] = []
|
||||
const handler = createTerminalMessageHandler({
|
||||
state,
|
||||
activate: (id) => activated.push(id),
|
||||
saveTabMemory: () => undefined,
|
||||
setSelection: () => undefined,
|
||||
showError: () => undefined,
|
||||
})
|
||||
const message = {
|
||||
type: "agentManager.terminal.created",
|
||||
worktreeId: null,
|
||||
terminalId: "terminal-1",
|
||||
title: "Terminal 1",
|
||||
wsUrl: "ws://127.0.0.1/pty/pty-1/connect",
|
||||
font,
|
||||
} satisfies ExtensionMessage
|
||||
|
||||
expect(handler(message)).toBe(true)
|
||||
expect(state.forSelection(LOCAL)[0]?.font).toEqual(font)
|
||||
expect(activated).toEqual(["terminal-1"])
|
||||
dispose()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1129,7 +1129,7 @@ const AgentManagerContent: Component = () => {
|
||||
onCreated: (contextKey, terminalId) => appendToTabOrder(contextKey, terminalId),
|
||||
})
|
||||
const unsubTerminals = vscode.onMessage((msg) => {
|
||||
terminalDispatch(msg as unknown as { type: string } & Record<string, unknown>)
|
||||
terminalDispatch(msg)
|
||||
})
|
||||
|
||||
const unsub = vscode.onMessage((msg) => {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
*/
|
||||
|
||||
import { Component, createEffect, onCleanup, onMount } from "solid-js"
|
||||
import { Terminal, type FontWeight } from "@xterm/xterm"
|
||||
import { Terminal } from "@xterm/xterm"
|
||||
import { FitAddon } from "@xterm/addon-fit"
|
||||
import { WebLinksAddon } from "@xterm/addon-web-links"
|
||||
import { ClipboardAddon } from "@xterm/addon-clipboard"
|
||||
@@ -136,10 +136,6 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
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).
|
||||
@@ -276,6 +272,7 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
if (!isRenderable()) return
|
||||
try {
|
||||
fit.fit()
|
||||
syncSize()
|
||||
} catch (err) {
|
||||
// Layout not settled yet; ResizeObserver retries on next change.
|
||||
log("repaint fit() threw", err)
|
||||
@@ -297,15 +294,8 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
}
|
||||
|
||||
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()
|
||||
term.options.fontFamily = message.font.fontFamily
|
||||
term.options.fontSize = message.font.fontSize
|
||||
scheduleRepaint()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -10,16 +10,10 @@
|
||||
import { createMemo, createSignal } from "solid-js"
|
||||
import type { Accessor } from "solid-js"
|
||||
import { LOCAL } from "../navigate"
|
||||
import type { ExtensionMessage } from "../../src/types/messages/extension-messages"
|
||||
import type { TerminalFont } from "../../src/types/messages/agent-manager"
|
||||
|
||||
/** 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
|
||||
}
|
||||
export type { TerminalFont }
|
||||
|
||||
/** Prefix used for terminal tab IDs in the webview (mirrors terminal-manager.ts). */
|
||||
export const TERMINAL_PREFIX = "terminal:"
|
||||
@@ -41,14 +35,6 @@ export interface TerminalTabStateWithContext extends TerminalTabState {
|
||||
contextKey: string
|
||||
}
|
||||
|
||||
export interface TerminalCreatedEvent {
|
||||
worktreeId: string | null
|
||||
terminalId: string
|
||||
title: string
|
||||
wsUrl: string
|
||||
font: TerminalFont
|
||||
}
|
||||
|
||||
export interface TerminalStateControls {
|
||||
/** Record received from `terminal.created`. */
|
||||
add(worktreeId: string | null, term: TerminalTabState): void
|
||||
@@ -343,26 +329,28 @@ export interface TerminalMessageHandlerDeps {
|
||||
* out of the main webview component.
|
||||
*/
|
||||
export function createTerminalMessageHandler(deps: TerminalMessageHandlerDeps) {
|
||||
return (msg: { type: string } & Record<string, unknown>): boolean => {
|
||||
return (msg: ExtensionMessage): boolean => {
|
||||
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, font: ev.font })
|
||||
deps.onCreated?.(contextKey, ev.terminalId)
|
||||
const contextKey = msg.worktreeId === null ? LOCAL : msg.worktreeId
|
||||
deps.state.add(msg.worktreeId, {
|
||||
id: msg.terminalId,
|
||||
title: msg.title,
|
||||
wsUrl: msg.wsUrl,
|
||||
font: msg.font,
|
||||
})
|
||||
deps.onCreated?.(contextKey, msg.terminalId)
|
||||
deps.saveTabMemory()
|
||||
deps.setSelection(contextKey)
|
||||
deps.activate(ev.terminalId)
|
||||
deps.activate(msg.terminalId)
|
||||
return true
|
||||
}
|
||||
if (msg.type === "agentManager.terminal.closed") {
|
||||
const ev = msg as unknown as { terminalId: string }
|
||||
deps.state.remove(ev.terminalId)
|
||||
if (deps.state.activeId() === ev.terminalId) deps.state.setActiveId(undefined)
|
||||
deps.state.remove(msg.terminalId)
|
||||
if (deps.state.activeId() === msg.terminalId) deps.state.setActiveId(undefined)
|
||||
return true
|
||||
}
|
||||
if (msg.type === "agentManager.terminal.error") {
|
||||
const ev = msg as unknown as { message: string }
|
||||
deps.showError(ev.message)
|
||||
deps.showError(msg.message)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
export type WorktreeErrorCode = "git_not_found" | "not_git_repo" | "lfs_missing"
|
||||
|
||||
export interface TerminalFont {
|
||||
fontFamily: string
|
||||
fontSize: number
|
||||
}
|
||||
|
||||
// Agent Manager worktree state types (mirrored from WorktreeStateManager)
|
||||
export interface WorktreeState {
|
||||
id: string
|
||||
|
||||
@@ -31,6 +31,7 @@ import type {
|
||||
ReviewComment,
|
||||
RunStatus,
|
||||
SectionState,
|
||||
TerminalFont,
|
||||
WorktreeErrorCode,
|
||||
WorktreeFileDiff,
|
||||
WorktreeGitStats,
|
||||
@@ -582,6 +583,12 @@ export interface AgentManagerTerminalCreatedMessage {
|
||||
terminalId: string
|
||||
title: string
|
||||
wsUrl: string
|
||||
font: TerminalFont
|
||||
}
|
||||
|
||||
export interface AgentManagerTerminalFontChangedMessage {
|
||||
type: "agentManager.terminal.fontChanged"
|
||||
font: TerminalFont
|
||||
}
|
||||
|
||||
export interface AgentManagerTerminalClosedMessage {
|
||||
@@ -1033,6 +1040,7 @@ export type ExtensionMessage =
|
||||
| AgentManagerLocalStatsMessage
|
||||
| AgentManagerPRStatusMessage
|
||||
| AgentManagerTerminalCreatedMessage
|
||||
| AgentManagerTerminalFontChangedMessage
|
||||
| AgentManagerTerminalClosedMessage
|
||||
| AgentManagerTerminalErrorMessage
|
||||
// legacy-migration start
|
||||
|
||||
Reference in New Issue
Block a user