wait-for-parser.ts 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  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. export const parserWatcher = ViewPlugin.fromClass(
  15. class {
  16. waits: ParserWait[] = []
  17. // eslint-disable-next-line no-useless-constructor
  18. constructor(readonly view: EditorView) {}
  19. parserReady(wait: ParserWait, state: EditorState) {
  20. const upTo =
  21. typeof wait.upTo === 'function' ? wait.upTo(this.view) : wait.upTo
  22. return syntaxTreeAvailable(state, upTo)
  23. }
  24. wait(upTo?: UpTo) {
  25. const promise = new Promise<void>(resolve => {
  26. const wait = {
  27. promise,
  28. upTo,
  29. resolve,
  30. }
  31. // Resolve immediately if the parser is ready. Otherwise, watch for
  32. // updates.
  33. if (this.parserReady(wait, this.view.state)) {
  34. wait.resolve()
  35. } else {
  36. this.waits.push(wait)
  37. }
  38. })
  39. return promise
  40. }
  41. update(update: ViewUpdate) {
  42. const unresolvedWaits: ParserWait[] = []
  43. for (const wait of this.waits) {
  44. if (this.parserReady(wait, update.state)) {
  45. wait.resolve()
  46. } else {
  47. unresolvedWaits.push(wait)
  48. }
  49. }
  50. this.waits = unresolvedWaits
  51. }
  52. }
  53. )
  54. // Returns a promise that is resolved as soon as CM6 reports that the parser is
  55. // ready, up to a specified offset in the document or the end if none is
  56. // specified. CM6 dispatches a transaction after every chunk of parser work
  57. // and the view plugin checks after each, so there is minimal delay
  58. export function waitForParser(view: EditorView, upTo?: UpTo) {
  59. const pluginInstance = view.plugin(parserWatcher)
  60. if (!pluginInstance) {
  61. throw new Error('No parser watcher view plugin found')
  62. }
  63. return pluginInstance.wait(upTo)
  64. }