wait-for-parser.ts 2.3 KB

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