дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 19:25:54 +02:00
Root cause: the editor had no font weight settings, and the WebView rendering of JetBrains Mono looks lighter than IDEA (#32). Fix (IDEA's Settings › Editor › Font › Typography Settings): configurable Main font weight and Bold font weight (defaults Regular 400 / Bold 700) via --fw-code / --fw-code-bold; colour-scheme bold/regular overrides still win; settings preview updates live. Replaces the Light-only 450/550 hard-coding. Siblings covered: sticky lines, diff/merge, run console (ANSI bold), Find in Files, completion/parameter info/docs, database editor, debugger value popup, matched braces, terminal with "Use editor font". Verified: scoped vitest, ide-core tests, in-app on macOS (settings UI, editor weight follows the setting end to end). Claude-Session: https://claude.ai/code/session_01VUYsdXA2nkF5v6zsC93dpn
113 рядки
4.0 KiB
TypeScript
113 рядки
4.0 KiB
TypeScript
/**
|
|
* Terminal widgets embedded in other views (the `embeddedTerminal` service):
|
|
* an xterm.js instance with the terminal's font, theme and links, whose input
|
|
* and size go to the owner instead of a terminal PTY (a Run console emulating
|
|
* a terminal feeds its process).
|
|
*/
|
|
import { Terminal, type IDisposable } from "@xterm/xterm";
|
|
import { FitAddon } from "@xterm/addon-fit";
|
|
import "@xterm/xterm/css/xterm.css";
|
|
import type { EmbeddedTerminal, EmbeddedTerminalOptions } from "@/workbench/contributions";
|
|
import { useProject } from "@/platform/project";
|
|
import { workflowSettings } from "@/platform/workflowSettings";
|
|
import { findTerminalLinks } from "./links";
|
|
import { fontFor, openExternal, openFileLink } from "./session";
|
|
import { readTerminalTheme, remeasureWhenFontsLoad } from "./theme";
|
|
|
|
const live = new Set<Terminal>();
|
|
|
|
/** Follows a theme change (called with the terminal sessions' own update). */
|
|
export function applyThemeToEmbedded() {
|
|
const theme = readTerminalTheme();
|
|
for (const t of live) t.options.theme = theme;
|
|
}
|
|
|
|
export function createEmbeddedTerminal(host: HTMLElement, opts: EmbeddedTerminalOptions): EmbeddedTerminal {
|
|
const settings = workflowSettings().terminal;
|
|
const font = fontFor(settings);
|
|
const term = new Terminal({
|
|
fontFamily: font.family,
|
|
fontSize: font.size,
|
|
lineHeight: font.lineHeight,
|
|
letterSpacing: font.letterSpacing,
|
|
fontWeight: font.weight,
|
|
fontWeightBold: font.boldWeight,
|
|
cursorBlink: settings.cursorBlink,
|
|
cursorWidth: 2,
|
|
cursorInactiveStyle: "outline",
|
|
allowProposedApi: true,
|
|
scrollback: 10_000,
|
|
macOptionIsMeta: settings.optionAsMeta,
|
|
macOptionClickForcesSelection: true,
|
|
drawBoldTextInBrightColors: false,
|
|
minimumContrastRatio: 1,
|
|
smoothScrollDuration: 0,
|
|
convertEol: false,
|
|
theme: readTerminalTheme(),
|
|
});
|
|
const fit = new FitAddon();
|
|
term.loadAddon(fit);
|
|
live.add(term);
|
|
const disposables: IDisposable[] = [
|
|
term.onData((d) => opts.onData(d)),
|
|
term.onBinary((d) => opts.onData(d)),
|
|
term.onResize(({ cols, rows }) => opts.onResize(cols, rows)),
|
|
term.registerLinkProvider({
|
|
provideLinks(y, callback) {
|
|
const line = term.buffer.active.getLine(y - 1);
|
|
const found = line ? findTerminalLinks(line.translateToString(true)) : [];
|
|
if (!found.length) return callback(undefined);
|
|
callback(
|
|
found.map((l) => ({
|
|
text: l.text,
|
|
range: { start: { x: l.start + 1, y }, end: { x: l.end, y } },
|
|
decorations: { underline: true, pointerCursor: true },
|
|
activate: (event: MouseEvent) => {
|
|
event.preventDefault();
|
|
if (l.kind === "url") void openExternal(l.url);
|
|
else void openFileLink(l, opts.cwd ?? useProject.getState().project?.root ?? "");
|
|
},
|
|
})),
|
|
);
|
|
},
|
|
}),
|
|
];
|
|
term.open(host);
|
|
if (opts["aria-label"]) term.textarea?.setAttribute("aria-label", opts["aria-label"]);
|
|
|
|
let raf = 0;
|
|
const doFit = () => {
|
|
raf = 0;
|
|
if (!host.isConnected || host.offsetWidth === 0 || host.offsetHeight === 0) return;
|
|
const dims = fit.proposeDimensions();
|
|
if (!dims || !Number.isFinite(dims.cols) || !Number.isFinite(dims.rows)) return;
|
|
if (dims.cols !== term.cols || dims.rows !== term.rows) term.resize(dims.cols, dims.rows);
|
|
};
|
|
const scheduleFit = () => {
|
|
if (!raf) raf = requestAnimationFrame(doFit);
|
|
};
|
|
const observer = new ResizeObserver(scheduleFit);
|
|
observer.observe(host);
|
|
scheduleFit();
|
|
remeasureWhenFontsLoad(term, font, () => live.has(term), scheduleFit);
|
|
// The owner learns the initial size even when it matches xterm's default.
|
|
queueMicrotask(() => opts.onResize(term.cols, term.rows));
|
|
|
|
return {
|
|
write: (data) => term.write(data),
|
|
clear: () => term.reset(),
|
|
focus: () => term.focus(),
|
|
fit: () => {
|
|
cancelAnimationFrame(raf);
|
|
doFit();
|
|
},
|
|
dispose: () => {
|
|
cancelAnimationFrame(raf);
|
|
observer.disconnect();
|
|
for (const d of disposables) d.dispose();
|
|
live.delete(term);
|
|
term.dispose();
|
|
},
|
|
};
|
|
}
|