import { EditorView, ViewPlugin, ViewUpdate } from '@codemirror/view' import { syntaxTreeAvailable } from '@codemirror/language' import { EditorState } from '@codemirror/state' // Either a number representing the document position the parser needs to have // reached or a function that returns a document position. This covers the case // when the requirements change while waiting for the parser, such as when // scrolling. type UpTo = number | ((view: EditorView) => number) type ParserWait = { upTo?: UpTo resolve: () => void } /** * A custom extension that resolves a Promise when the parser has built the syntax tree up to a given position. */ export const parserWatcher = ViewPlugin.fromClass( class { waits: ParserWait[] = [] // eslint-disable-next-line no-useless-constructor constructor(readonly view: EditorView) {} parserReady(wait: ParserWait, state: EditorState) { const upTo = typeof wait.upTo === 'function' ? wait.upTo(this.view) : wait.upTo return syntaxTreeAvailable(state, upTo) } wait(upTo?: UpTo) { const promise = new Promise(resolve => { const wait = { upTo, resolve, } // Resolve immediately if the parser is ready. Otherwise, watch for // updates. if (this.parserReady(wait, this.view.state)) { wait.resolve() } else { this.waits.push(wait) } }) return promise } update(update: ViewUpdate) { const unresolvedWaits: ParserWait[] = [] for (const wait of this.waits) { if (this.parserReady(wait, update.state)) { wait.resolve() } else { unresolvedWaits.push(wait) } } this.waits = unresolvedWaits } } ) // Returns a promise that is resolved as soon as CM6 reports that the parser is // ready, up to a specified offset in the document or the end if none is // specified. CM6 dispatches a transaction after every chunk of parser work // and the view plugin checks after each, so there is minimal delay export function waitForParser(view: EditorView, upTo?: UpTo) { const pluginInstance = view.plugin(parserWatcher) if (!pluginInstance) { throw new Error('No parser watcher view plugin found') } return pluginInstance.wait(upTo) }