OutputTerminal.js 1.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  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. then()
  37. }
  38. }
  39. async reset () {
  40. const unlock = await this.writeMutex.lock()
  41. try {
  42. await new Promise(resolve => {
  43. this.terminal.clear()
  44. this.terminal.reset()
  45. resolve()
  46. })
  47. } finally {
  48. unlock()
  49. }
  50. }
  51. fit () {
  52. this.terminal.fit.fit()
  53. }
  54. open (el) {
  55. this.terminal.open(el)
  56. }
  57. resize (cols, rows) {
  58. this.terminal.resize(cols, rows)
  59. }
  60. }