wait-for-parser.ts 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. import { EditorView, ViewPlugin, ViewUpdate } from '@codemirror/view'
  2. import { syntaxTreeAvailable } from '@codemirror/language'
  3. import { EditorState } from '@codemirror/state'
  4. // Either a number representing the document position the parser needs to have
  5. // reached or a function that returns a document position. This covers the case
  6. // when the requirements change while waiting for the parser, such as when
  7. // scrolling.
  8. type UpTo = number | ((view: EditorView) => number)
  9. type ParserWait = {
  10. promise: Promise<void>
  11. upTo?: UpTo
  12. resolve: () => void
  13. }
  14. /**
  15. * A custom extension that resolves a Promise when the parser has built the syntax tree up to a given position.
  16. */
  17. export const parserWatcher = ViewPlugin.fromClass(
  18. class {
  19. waits: ParserWait[] = []
  20. // eslint-disable-next-line no-useless-constructor
  21. constructor(readonly view: EditorView) {}
  22. parserReady(wait: ParserWait, state: EditorState) {
  23. const upTo =
  24. typeof wait.upTo === 'function' ? wait.upTo(this.view) : wait.upTo
  25. return syntaxTreeAvailable(state, upTo)
  26. }
  27. wait(upTo?: UpTo) {
  28. const promise = new Promise<void>(resolve => {
  29. const wait = {
  30. promise,
  31. upTo,
  32. resolve,
  33. }
  34. // Resolve immediately if the parser is ready. Otherwise, watch for
  35. // updates.
  36. if (this.parserReady(wait, this.view.state)) {
  37. wait.resolve()
  38. } else {
  39. this.waits.push(wait)
  40. }
  41. })
  42. return promise
  43. }
  44. update(update: ViewUpdate) {
  45. const unresolvedWaits: ParserWait[] = []
  46. for (const wait of this.waits) {
  47. if (this.parserReady(wait, update.state)) {
  48. wait.resolve()
  49. } else {
  50. unresolvedWaits.push(wait)
  51. }
  52. }
  53. this.waits = unresolvedWaits
  54. }
  55. }
  56. )
  57. // Returns a promise that is resolved as soon as CM6 reports that the parser is
  58. // ready, up to a specified offset in the document or the end if none is
  59. // specified. CM6 dispatches a transaction after every chunk of parser work
  60. // and the view plugin checks after each, so there is minimal delay
  61. export function waitForParser(view: EditorView, upTo?: UpTo) {
  62. const pluginInstance = view.plugin(parserWatcher)
  63. if (!pluginInstance) {
  64. throw new Error('No parser watcher view plugin found')
  65. }
  66. return pluginInstance.wait(upTo)
  67. }