UNPKG

cm-tarnation

Version:

An alternative parser for CodeMirror 6

211 lines 8.5 kB
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // disabled as it doesn't seem to be needed for performance const LIMIT_TO_VIEWPORT = false; /** * The region of a document that should be parsed, along with other * information such as what the edited range of the document was. */ export class ParseRegion { /** * @param input - The input to get the parse region for. * @param ranges - The ranges of the document that should be parsed. * @param fragments - Fragments that are used to compute the edited range. */ constructor(input, ranges, fragments, viewport) { this.input = input; this.from = ranges[0].from; this.to = Math.min(input.length, ranges[ranges.length - 1].to); this.original = { from: this.from, to: this.to, length: this.length }; this.ranges = ranges; // get the edited range of the document, // spanning from the start of the first edit to the end of the last edit if (fragments?.length) { let from, to, offset; if (fragments.length === 1) { const fragment = fragments[0]; // special case that seems to happen when scrolling, // the fragment is the entire parsed range if (fragment.offset === 0 && !fragment.openStart && fragment.openEnd) { from = input.length; to = input.length; offset = 0; } else { from = fragment.openStart ? this.from : fragment.to; to = fragment.openStart ? fragment.from : this.to; offset = -fragment.offset; } } else { const reversed = [...fragments].reverse(); const first = reversed.find(f => !f.openStart && f.openEnd) || fragments[0]; const last = fragments.find(f => f.openStart && !f.openEnd) || reversed[0]; from = first.openStart && first.openEnd ? first.from : first.to; to = last.openStart && last.openEnd ? last.to : last.from; offset = -last.offset; // not sure why this is needed, something I don't understand about fragments // usually if this is the case the parse was interrupted, and is being continued if (from > to) { from = this.to; to = this.to; offset = 0; } } this.edit = { from, to, offset }; } if (viewport) { this.viewport = viewport; if (LIMIT_TO_VIEWPORT) { // we're gonna try to only parse just a bit past the viweport // basically doubles the height of the viewport // this adds a bit of a buffer between the actual end and the end of parsing // otherwise if you scrolled too fast you'd see unparsed sections easily const end = viewport.to + (viewport.to - viewport.from); if (viewport.from < this.to && this.to > end) this.to = end; } } } /** The length of the region. */ get length() { return this.to - this.from; } /** True if we don't need to care about range handling. */ get contiguous() { return this.ranges.length === 1; } /** * Compensates for an adjustment to a position. That is, given the range * `pos` is inside of, what position should adding `addition` return? * This is for skipping past the gaps inbetween ranges. * * @param pos - The position to start from. * @param addition - The amount to add to `pos`. */ compensate(pos, addition) { const desired = pos + addition; if (this.ranges.length === 1) return desired; const range = this.posRange(pos); if (!range) return desired; // forwards compensation if (desired > range.to) { const next = this.posRange(pos, 1); if (!next) return desired; const nextDesired = next.from + (desired - range.to) - 1; // recursively compensate, if needed if (nextDesired > next.to) { return this.compensate(next.to, nextDesired - next.to); } return nextDesired; } // backwards compensation else if (desired < range.from) { const prev = this.posRange(pos, -1); if (!prev) return desired; const prevDesired = prev.to + (desired - range.from) + 1; // recursively compensate, if needed if (prevDesired < prev.from) { return this.compensate(prev.from, prevDesired - prev.from); } return prevDesired; } // no compensation needed return desired; } /** * Clamps a `to` value for a range to the end of the parse range that * `from` is inside of. * * @param from - The `from` position, for which the `to` position will be * clamped relative to. * @param to - The `to` position, which will be clamped to the end of the * range that `from` is inside of. */ clamp(from, to) { const range = this.posRange(from); if (!range) return to; return range.to; } /** * Gets what range the given position is inside of. Returns `null` if the * position can't be found inside of any range. * * @param pos - The position to get the range for. * @param side - The side of the range to get. -1 returns the range * previous, 1 returns the range after. Defaults to 0. */ posRange(pos, side = 0) { if (this.ranges.length === 1) return this.ranges[0]; for (let i = 0; i < this.ranges.length; i++) { const range = this.ranges[i]; if (pos >= range.from && pos <= range.to) { let final; // prettier-ignore switch (side) { case -1: final = this.ranges[i - 1]; break; case 0: final = range; break; case 1: final = this.ranges[i + 1]; break; } return final ?? null; } } return null; } /** * Gets a substring of the current input, accounting for range handling * automatically. * * @param pos - The position to start at. * @param min - The minimum length of the substring. * @param max - The maximum position of the end of the substring. */ read(pos, min, max) { let str = ""; while (str.length <= min) { str += this.input.chunk(pos + str.length); const relative = pos + str.length; if (relative >= max) { const diff = relative - max; if (diff) str = str.slice(0, -diff); break; } if (this.ranges.length !== 1) { const actual = this.compensate(pos, str.length); // end of input if (actual >= max) { const diff = actual - max; if (diff) str = str.slice(0, -diff); break; } const clamped = this.clamp(pos, relative); if (relative >= clamped) { const diff = relative - clamped; if (diff) str = str.slice(0, -diff); const next = this.posRange(clamped, 1); if (!next) break; pos = next.from; } } } return str; } } //# sourceMappingURL=region.js.map