import { Terminal } from '@xterm/xterm' import { FitAddon } from '@xterm/addon-fit' import { WebLinksAddon } from '@xterm/addon-web-links' import { Mutex } from './Mutex.js' /** * xterm.js based terminal output for the execution dialog. * * the xterm.js methods for write(), reset() and clear() appear to be async, * but they do not return a Promise and instead use a callback. When calling * these methods in quick succession, the output can get garbled due to race * conditions. * * To avoid this, this class uses Mutex around those methods to ensure that * only one write OR reset is executed at a time, is completed, and the calls * occour in sequential order. */ export class OutputTerminal { constructor (executionTrackingId) { this.executionTrackingId = executionTrackingId this.writeMutex = new Mutex() const linkHandler = { activate (event, text, _range) { event.preventDefault() window.open(text, '_blank') } } this.terminal = new Terminal({ convertEol: true, linkHandler, scrollback: 10000 }) const fitAddon = new FitAddon() this.terminal.loadAddon(fitAddon) this.terminal.fit = fitAddon this.terminal.loadAddon(new WebLinksAddon((event, uri) => linkHandler.activate(event, uri))) this.linkHandlerConfigured = true } async write (out, then) { const unlock = await this.writeMutex.lock() try { await new Promise(resolve => { this.terminal.write(out, () => { resolve() }) }) } finally { unlock() if (then != null && then !== undefined) { then() } } } async reset () { const unlock = await this.writeMutex.lock() try { await new Promise(resolve => { this.terminal.clear() this.terminal.reset() resolve() }) } finally { unlock() } } fit () { this.terminal.fit.fit() } open (el) { this.terminal.open(el) } close () { this.terminal.dispose() } resize (cols, rows) { this.terminal.resize(cols, rows) } /** * Get the terminal buffer content as a string. * This method is intended for use in integration tests to verify output. * @returns {string} The terminal buffer content as a string */ getBufferAsString () { if (!this.terminal) { return '' } const buffer = this.terminal.buffer.active let text = '' for (let i = 0; i < buffer.length; i++) { const line = buffer.getLine(i) if (line) { text += line.translateToString(true) + '\n' } } return text } }