wait-for-parser.ts 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  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. const plugin = 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. export function parserWatcher() {
  55. return plugin
  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(plugin)
  63. if (!pluginInstance) {
  64. throw new Error('No parser watcher view plugin found')
  65. }
  66. return pluginInstance.wait(upTo)
  67. }