OutputTerminal.js 1.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  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 () {
  18. this.writeMutex = new Mutex()
  19. this.terminal = new Terminal({
  20. convertEol: true
  21. })
  22. const fitAddon = new FitAddon()
  23. this.terminal.loadAddon(fitAddon)
  24. this.terminal.fit = fitAddon
  25. }
  26. async write (out, then) {
  27. const unlock = await this.writeMutex.lock()
  28. try {
  29. await new Promise(resolve => {
  30. this.terminal.write(out, () => {
  31. resolve()
  32. })
  33. })
  34. } finally {
  35. unlock()
  36. if (then != null && then !== undefined) {
  37. then()
  38. }
  39. }
  40. }
  41. async reset () {
  42. const unlock = await this.writeMutex.lock()
  43. try {
  44. await new Promise(resolve => {
  45. this.terminal.clear()
  46. this.terminal.reset()
  47. resolve()
  48. })
  49. } finally {
  50. unlock()
  51. }
  52. }
  53. fit () {
  54. this.terminal.fit.fit()
  55. }
  56. open (el) {
  57. this.terminal.open(el)
  58. }
  59. close () {
  60. this.terminal.dispose()
  61. }
  62. resize (cols, rows) {
  63. this.terminal.resize(cols, rows)
  64. }
  65. }