OutputTerminal.js 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import { Terminal } from '@xterm/xterm'
  2. import { FitAddon } from '@xterm/addon-fit'
  3. import { Mutex } from './Mutex.js'
  4. /**
  5. * xterm.js based terminal output for the execution dialog.
  6. *
  7. * the xterm.js methods for write(), reset() and clear() appear to be async,
  8. * but they do not return a Promise and instead use a callback. When calling
  9. * these methods in quick succession, the output can get garbled due to race
  10. * conditions.
  11. *
  12. * To avoid this, this class uses Mutex around those methods to ensure that
  13. * only one write OR reset is executed at a time, is completed, and the calls
  14. * occour in sequential order.
  15. */
  16. export class OutputTerminal {
  17. constructor (executionTrackingId) {
  18. this.executionTrackingId = executionTrackingId
  19. this.writeMutex = new Mutex()
  20. this.terminal = new Terminal({
  21. convertEol: true
  22. })
  23. const fitAddon = new FitAddon()
  24. this.terminal.loadAddon(fitAddon)
  25. this.terminal.fit = fitAddon
  26. }
  27. async write (out, then) {
  28. const unlock = await this.writeMutex.lock()
  29. try {
  30. await new Promise(resolve => {
  31. this.terminal.write(out, () => {
  32. resolve()
  33. })
  34. })
  35. } finally {
  36. unlock()
  37. if (then != null && then !== undefined) {
  38. then()
  39. }
  40. }
  41. }
  42. async reset () {
  43. const unlock = await this.writeMutex.lock()
  44. try {
  45. await new Promise(resolve => {
  46. this.terminal.clear()
  47. this.terminal.reset()
  48. resolve()
  49. })
  50. } finally {
  51. unlock()
  52. }
  53. }
  54. fit () {
  55. this.terminal.fit.fit()
  56. }
  57. open (el) {
  58. this.terminal.open(el)
  59. }
  60. close () {
  61. this.terminal.dispose()
  62. }
  63. resize (cols, rows) {
  64. this.terminal.resize(cols, rows)
  65. }
  66. /**
  67. * Get the terminal buffer content as a string.
  68. * This method is intended for use in integration tests to verify output.
  69. * @returns {string} The terminal buffer content as a string
  70. */
  71. getBufferAsString () {
  72. if (!this.terminal) {
  73. return ''
  74. }
  75. const buffer = this.terminal.buffer.active
  76. let text = ''
  77. for (let i = 0; i < buffer.length; i++) {
  78. const line = buffer.getLine(i)
  79. if (line) {
  80. text += line.translateToString(true) + '\n'
  81. }
  82. }
  83. return text
  84. }
  85. }