Spaces:
Sleeping
Sleeping
import { FitAddon } from '@xterm/addon-fit'; | |
import { WebLinksAddon } from '@xterm/addon-web-links'; | |
import { Terminal as XTerm } from '@xterm/xterm'; | |
import { forwardRef, memo, useEffect, useImperativeHandle, useRef } from 'react'; | |
import type { Theme } from '~/lib/stores/theme'; | |
import { createScopedLogger } from '~/utils/logger'; | |
import { getTerminalTheme } from './theme'; | |
const logger = createScopedLogger('Terminal'); | |
export interface TerminalRef { | |
reloadStyles: () => void; | |
} | |
export interface TerminalProps { | |
className?: string; | |
theme: Theme; | |
readonly?: boolean; | |
id: string; | |
onTerminalReady?: (terminal: XTerm) => void; | |
onTerminalResize?: (cols: number, rows: number) => void; | |
} | |
export const Terminal = memo( | |
forwardRef<TerminalRef, TerminalProps>( | |
({ className, theme, readonly, id, onTerminalReady, onTerminalResize }, ref) => { | |
const terminalElementRef = useRef<HTMLDivElement>(null); | |
const terminalRef = useRef<XTerm>(); | |
useEffect(() => { | |
const element = terminalElementRef.current!; | |
const fitAddon = new FitAddon(); | |
const webLinksAddon = new WebLinksAddon(); | |
const terminal = new XTerm({ | |
cursorBlink: true, | |
convertEol: true, | |
disableStdin: readonly, | |
theme: getTerminalTheme(readonly ? { cursor: '#00000000' } : {}), | |
fontSize: 12, | |
fontFamily: 'Menlo, courier-new, courier, monospace', | |
}); | |
terminalRef.current = terminal; | |
terminal.loadAddon(fitAddon); | |
terminal.loadAddon(webLinksAddon); | |
terminal.open(element); | |
const resizeObserver = new ResizeObserver(() => { | |
fitAddon.fit(); | |
onTerminalResize?.(terminal.cols, terminal.rows); | |
}); | |
resizeObserver.observe(element); | |
logger.debug(`Attach [${id}]`); | |
onTerminalReady?.(terminal); | |
return () => { | |
resizeObserver.disconnect(); | |
terminal.dispose(); | |
}; | |
}, []); | |
useEffect(() => { | |
const terminal = terminalRef.current!; | |
// we render a transparent cursor in case the terminal is readonly | |
terminal.options.theme = getTerminalTheme(readonly ? { cursor: '#00000000' } : {}); | |
terminal.options.disableStdin = readonly; | |
}, [theme, readonly]); | |
useImperativeHandle(ref, () => { | |
return { | |
reloadStyles: () => { | |
const terminal = terminalRef.current!; | |
terminal.options.theme = getTerminalTheme(readonly ? { cursor: '#00000000' } : {}); | |
}, | |
}; | |
}, []); | |
return <div className={className} ref={terminalElementRef} />; | |
}, | |
), | |
); | |