Skip to content

Commit 243807e

Browse files
committed
フォントが初期化されるまでターミナルを初期化しない
1 parent 8a64757 commit 243807e

File tree

1 file changed

+76
-50
lines changed

1 file changed

+76
-50
lines changed

app/terminal/terminal.tsx

Lines changed: 76 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export function getRows(contents: string, cols: number): number {
3838
}
3939

4040
// なぜか term.clear(); が効かない場合がある... これは効く
41-
export function clearTerminal(term: Terminal){
41+
export function clearTerminal(term: Terminal) {
4242
// term.clear();
4343
term.write("\x1b[3J\x1b[2J\x1b[1;1H");
4444
}
@@ -50,6 +50,7 @@ interface TerminalProps {
5050
export function useTerminal(props: TerminalProps) {
5151
const terminalRef = useRef<HTMLDivElement>(null!);
5252
const terminalInstanceRef = useRef<Terminal | null>(null);
53+
const fitAddonRef = useRef<FitAddon | null>(null);
5354
const [termReady, setTermReady] = useState<boolean>(false);
5455

5556
const getRowsRef = useRef<(cols: number) => number>(undefined);
@@ -59,69 +60,94 @@ export function useTerminal(props: TerminalProps) {
5960

6061
// ターミナルの初期化処理
6162
useEffect(() => {
62-
const fromCSS = (varName: string) =>
63-
window.getComputedStyle(document.body).getPropertyValue(varName);
64-
// "--color-" + color_name のように文字列を分割するとTailwindCSSが認識せずCSSの値として出力されない場合があるので注意
65-
const term = new Terminal({
66-
cursorBlink: true,
67-
convertEol: true,
68-
cursorStyle: "bar",
69-
cursorInactiveStyle: "none",
70-
fontSize: 14,
71-
lineHeight: 1.4,
72-
letterSpacing: 0,
73-
fontFamily: "Inconsolata Variable",
74-
theme: {
75-
// DaisyUIの変数を使用してテーマを設定している
76-
// TODO: ダークテーマ/ライトテーマを切り替えたときに再設定する?
77-
background: fromCSS("--color-base-300"),
78-
foreground: fromCSS("--color-base-content"),
79-
cursor: fromCSS("--color-base-content"),
80-
selectionBackground: fromCSS("--color-primary"),
81-
selectionForeground: fromCSS("--color-primary-content"),
82-
black: fromCSS("--color-black"),
83-
brightBlack: fromCSS("--color-neutral-500"),
84-
red: fromCSS("--color-red-600"),
85-
brightRed: fromCSS("--color-red-400"),
86-
green: fromCSS("--color-green-600"),
87-
brightGreen: fromCSS("--color-green-400"),
88-
yellow: fromCSS("--color-yellow-700"),
89-
brightYellow: fromCSS("--color-yellow-400"),
90-
blue: fromCSS("--color-indigo-600"),
91-
brightBlue: fromCSS("--color-indigo-400"),
92-
magenta: fromCSS("--color-fuchsia-600"),
93-
brightMagenta: fromCSS("--color-fuchsia-400"),
94-
cyan: fromCSS("--color-cyan-600"),
95-
brightCyan: fromCSS("--color-cyan-400"),
96-
white: fromCSS("--color-base-100"),
97-
brightWhite: fromCSS("--color-white"),
98-
},
99-
});
100-
terminalInstanceRef.current = term;
63+
const abortController = new AbortController();
64+
65+
(async () => {
66+
// globals.cssでフォントを指定し読み込んでいるが、
67+
// それが読み込まれる前にterminalを初期化してしまうとバグる。
68+
// なのでここでフォントをfetchし成功するまでterminalの初期化は待つ
69+
try {
70+
await fetch(
71+
"https://cdn.jsdelivr.net/fontsource/fonts/inconsolata:vf@latest/latin-wght-normal.woff2",
72+
{ signal: abortController.signal }
73+
);
74+
} catch {
75+
// ignore
76+
}
10177

102-
const fitAddon = new FitAddon();
103-
term.loadAddon(fitAddon);
104-
// fitAddon.fit();
78+
if (!abortController.signal.aborted) {
79+
const fromCSS = (varName: string) =>
80+
window.getComputedStyle(document.body).getPropertyValue(varName);
81+
// "--color-" + color_name のように文字列を分割するとTailwindCSSが認識せずCSSの値として出力されない場合があるので注意
82+
const term = new Terminal({
83+
cursorBlink: true,
84+
convertEol: true,
85+
cursorStyle: "bar",
86+
cursorInactiveStyle: "none",
87+
fontSize: 14,
88+
lineHeight: 1.4,
89+
letterSpacing: 0,
90+
fontFamily: "Inconsolata Variable",
91+
theme: {
92+
// DaisyUIの変数を使用してテーマを設定している
93+
// TODO: ダークテーマ/ライトテーマを切り替えたときに再設定する?
94+
background: fromCSS("--color-base-300"),
95+
foreground: fromCSS("--color-base-content"),
96+
cursor: fromCSS("--color-base-content"),
97+
selectionBackground: fromCSS("--color-primary"),
98+
selectionForeground: fromCSS("--color-primary-content"),
99+
black: fromCSS("--color-black"),
100+
brightBlack: fromCSS("--color-neutral-500"),
101+
red: fromCSS("--color-red-600"),
102+
brightRed: fromCSS("--color-red-400"),
103+
green: fromCSS("--color-green-600"),
104+
brightGreen: fromCSS("--color-green-400"),
105+
yellow: fromCSS("--color-yellow-700"),
106+
brightYellow: fromCSS("--color-yellow-400"),
107+
blue: fromCSS("--color-indigo-600"),
108+
brightBlue: fromCSS("--color-indigo-400"),
109+
magenta: fromCSS("--color-fuchsia-600"),
110+
brightMagenta: fromCSS("--color-fuchsia-400"),
111+
cyan: fromCSS("--color-cyan-600"),
112+
brightCyan: fromCSS("--color-cyan-400"),
113+
white: fromCSS("--color-base-100"),
114+
brightWhite: fromCSS("--color-white"),
115+
},
116+
});
117+
terminalInstanceRef.current = term;
105118

106-
term.open(terminalRef.current);
119+
fitAddonRef.current = new FitAddon();
120+
term.loadAddon(fitAddonRef.current);
121+
// fitAddon.fit();
107122

108-
setTermReady(true);
109-
onReadyRef.current?.();
123+
term.open(terminalRef.current);
124+
125+
setTermReady(true);
126+
onReadyRef.current?.();
127+
}
128+
})();
110129

111130
const observer = new ResizeObserver(() => {
112131
// fitAddon.fit();
113-
const dims = fitAddon.proposeDimensions();
132+
const dims = fitAddonRef.current?.proposeDimensions();
114133
if (dims) {
115134
const rows = Math.max(5, getRowsRef.current?.(dims.cols) ?? 0);
116-
term.resize(dims.cols, rows);
135+
terminalInstanceRef.current?.resize(dims.cols, rows);
117136
}
118137
});
119138
observer.observe(terminalRef.current);
120139

121140
return () => {
141+
abortController.abort("terminal component dismount");
122142
observer.disconnect();
123-
term.dispose();
124-
terminalInstanceRef.current = null;
143+
if (fitAddonRef.current) {
144+
fitAddonRef.current.dispose();
145+
fitAddonRef.current = null;
146+
}
147+
if (terminalInstanceRef.current) {
148+
terminalInstanceRef.current.dispose();
149+
terminalInstanceRef.current = null;
150+
}
125151
};
126152
}, []);
127153

0 commit comments

Comments
 (0)