|
| 1 | +import { CanvasAddon } from '@xterm/addon-canvas' |
| 2 | +import { FitAddon } from '@xterm/addon-fit' |
| 3 | +import { WebglAddon } from '@xterm/addon-webgl' |
1 | 4 | import '@xterm/xterm/css/xterm.css' |
2 | 5 | import { Terminal as XTerm } from '@xterm/xterm' |
3 | 6 | import { useCallback, useEffect, useRef } from 'react' |
@@ -27,12 +30,14 @@ export function useTerminalInstance({ |
27 | 30 | onResize, |
28 | 31 | }: UseTerminalInstanceOptions) { |
29 | 32 | const xtermRef = useRef<XTerm | null>(null) |
| 33 | + const fitAddonRef = useRef<FitAddon | null>(null) |
30 | 34 | const terminalContainerRef = useRef<HTMLDivElement | null>(null) |
31 | 35 | const terminalTranscriptRef = useRef(INITIAL_TERMINAL_TEXT) |
32 | 36 | const terminalSizeRef = useRef({ cols: DEFAULT_COLS, rows: DEFAULT_ROWS }) |
33 | 37 | const decoderRef = useRef(new TextDecoder()) |
34 | 38 |
|
35 | 39 | const resizeTerminal = useCallback(() => { |
| 40 | + fitAddonRef.current?.fit() |
36 | 41 | const nextSize = calculateTerminalSize( |
37 | 42 | terminalContainerRef.current, |
38 | 43 | xtermRef.current |
@@ -99,26 +104,71 @@ export function useTerminalInstance({ |
99 | 104 | theme: TERMINAL_THEME, |
100 | 105 | }) |
101 | 106 |
|
| 107 | + const fitAddon = new FitAddon() |
| 108 | + let rendererAddon: WebglAddon | CanvasAddon | undefined |
| 109 | + let contextLossSubscription: { dispose: () => void } | undefined |
| 110 | + |
102 | 111 | xtermRef.current = terminal |
| 112 | + fitAddonRef.current = fitAddon |
| 113 | + terminal.loadAddon(fitAddon) |
103 | 114 | terminal.open(container) |
104 | | - terminal.write(terminalTranscriptRef.current) |
| 115 | + |
| 116 | + try { |
| 117 | + const webglAddon = new WebglAddon() |
| 118 | + const webglContextLossSubscription = webglAddon.onContextLoss(() => { |
| 119 | + webglContextLossSubscription.dispose() |
| 120 | + webglAddon.dispose() |
| 121 | + if (rendererAddon === webglAddon) { |
| 122 | + rendererAddon = undefined |
| 123 | + } |
| 124 | + if (contextLossSubscription === webglContextLossSubscription) { |
| 125 | + contextLossSubscription = undefined |
| 126 | + } |
| 127 | + }) |
| 128 | + contextLossSubscription = webglContextLossSubscription |
| 129 | + rendererAddon = webglAddon |
| 130 | + terminal.loadAddon(webglAddon) |
| 131 | + } catch { |
| 132 | + contextLossSubscription?.dispose() |
| 133 | + contextLossSubscription = undefined |
| 134 | + rendererAddon?.dispose() |
| 135 | + try { |
| 136 | + rendererAddon = new CanvasAddon() |
| 137 | + terminal.loadAddon(rendererAddon) |
| 138 | + } catch { |
| 139 | + rendererAddon?.dispose() |
| 140 | + rendererAddon = undefined |
| 141 | + } |
| 142 | + } |
| 143 | + |
105 | 144 | const dataSubscription = terminal.onData(onInput) |
| 145 | + terminal.write(terminalTranscriptRef.current, () => { |
| 146 | + terminal.scrollToBottom() |
| 147 | + }) |
106 | 148 |
|
107 | 149 | requestAnimationFrame(() => { |
108 | 150 | resizeTerminal() |
109 | 151 | terminal.focus() |
| 152 | + terminal.scrollToBottom() |
110 | 153 | }) |
111 | 154 | const resizeTimer = window.setTimeout(() => { |
112 | 155 | resizeTerminal() |
| 156 | + terminal.scrollToBottom() |
113 | 157 | }, 100) |
114 | 158 |
|
115 | 159 | return () => { |
116 | 160 | window.clearTimeout(resizeTimer) |
117 | 161 | dataSubscription.dispose() |
| 162 | + contextLossSubscription?.dispose() |
| 163 | + rendererAddon?.dispose() |
| 164 | + fitAddon.dispose() |
118 | 165 | terminal.dispose() |
119 | 166 | if (xtermRef.current === terminal) { |
120 | 167 | xtermRef.current = null |
121 | 168 | } |
| 169 | + if (fitAddonRef.current === fitAddon) { |
| 170 | + fitAddonRef.current = null |
| 171 | + } |
122 | 172 | } |
123 | 173 | }, [onInput, resizeTerminal]) |
124 | 174 |
|
|
0 commit comments