Path: blob/main/src/vs/editor/common/model/intervalTree.ts
3294 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Range } from '../core/range.js';6import { TrackedRangeStickiness, TrackedRangeStickiness as ActualTrackedRangeStickiness } from '../model.js';7import { ModelDecorationOptions } from './textModel.js';89//10// The red-black tree is based on the "Introduction to Algorithms" by Cormen, Leiserson and Rivest.11//1213export const enum ClassName {14EditorHintDecoration = 'squiggly-hint',15EditorInfoDecoration = 'squiggly-info',16EditorWarningDecoration = 'squiggly-warning',17EditorErrorDecoration = 'squiggly-error',18EditorUnnecessaryDecoration = 'squiggly-unnecessary',19EditorUnnecessaryInlineDecoration = 'squiggly-inline-unnecessary',20EditorDeprecatedInlineDecoration = 'squiggly-inline-deprecated'21}2223export const enum NodeColor {24Black = 0,25Red = 1,26}2728const enum Constants {29ColorMask = 0b00000001,30ColorMaskInverse = 0b11111110,31ColorOffset = 0,3233IsVisitedMask = 0b00000010,34IsVisitedMaskInverse = 0b11111101,35IsVisitedOffset = 1,3637IsForValidationMask = 0b00000100,38IsForValidationMaskInverse = 0b11111011,39IsForValidationOffset = 2,4041StickinessMask = 0b00011000,42StickinessMaskInverse = 0b11100111,43StickinessOffset = 3,4445CollapseOnReplaceEditMask = 0b00100000,46CollapseOnReplaceEditMaskInverse = 0b11011111,47CollapseOnReplaceEditOffset = 5,4849IsMarginMask = 0b01000000,50IsMarginMaskInverse = 0b10111111,51IsMarginOffset = 6,5253AffectsFontMask = 0b10000000,54AffectsFontMaskInverse = 0b01111111,55AffectsFontOffset = 7,5657/**58* Due to how deletion works (in order to avoid always walking the right subtree of the deleted node),59* the deltas for nodes can grow and shrink dramatically. It has been observed, in practice, that unless60* the deltas are corrected, integer overflow will occur.61*62* The integer overflow occurs when 53 bits are used in the numbers, but we will try to avoid it as63* a node's delta gets below a negative 30 bits number.64*65* MIN SMI (SMall Integer) as defined in v8.66* one bit is lost for boxing/unboxing flag.67* one bit is lost for sign flag.68* See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values69*/70MIN_SAFE_DELTA = -(1 << 30),71/**72* MAX SMI (SMall Integer) as defined in v8.73* one bit is lost for boxing/unboxing flag.74* one bit is lost for sign flag.75* See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values76*/77MAX_SAFE_DELTA = 1 << 30,78}7980export function getNodeColor(node: IntervalNode): NodeColor {81return ((node.metadata & Constants.ColorMask) >>> Constants.ColorOffset);82}83function setNodeColor(node: IntervalNode, color: NodeColor): void {84node.metadata = (85(node.metadata & Constants.ColorMaskInverse) | (color << Constants.ColorOffset)86);87}88function getNodeIsVisited(node: IntervalNode): boolean {89return ((node.metadata & Constants.IsVisitedMask) >>> Constants.IsVisitedOffset) === 1;90}91function setNodeIsVisited(node: IntervalNode, value: boolean): void {92node.metadata = (93(node.metadata & Constants.IsVisitedMaskInverse) | ((value ? 1 : 0) << Constants.IsVisitedOffset)94);95}96function getNodeIsForValidation(node: IntervalNode): boolean {97return ((node.metadata & Constants.IsForValidationMask) >>> Constants.IsForValidationOffset) === 1;98}99function setNodeIsForValidation(node: IntervalNode, value: boolean): void {100node.metadata = (101(node.metadata & Constants.IsForValidationMaskInverse) | ((value ? 1 : 0) << Constants.IsForValidationOffset)102);103}104function getNodeIsInGlyphMargin(node: IntervalNode): boolean {105return ((node.metadata & Constants.IsMarginMask) >>> Constants.IsMarginOffset) === 1;106}107function setNodeIsInGlyphMargin(node: IntervalNode, value: boolean): void {108node.metadata = (109(node.metadata & Constants.IsMarginMaskInverse) | ((value ? 1 : 0) << Constants.IsMarginOffset)110);111}112function getNodeAffectsFont(node: IntervalNode): boolean {113return ((node.metadata & Constants.AffectsFontMask) >>> Constants.AffectsFontOffset) === 1;114}115function setNodeAffectsFont(node: IntervalNode, value: boolean): void {116node.metadata = (117(node.metadata & Constants.AffectsFontMaskInverse) | ((value ? 1 : 0) << Constants.AffectsFontOffset)118);119}120function getNodeStickiness(node: IntervalNode): TrackedRangeStickiness {121return ((node.metadata & Constants.StickinessMask) >>> Constants.StickinessOffset);122}123function _setNodeStickiness(node: IntervalNode, stickiness: TrackedRangeStickiness): void {124node.metadata = (125(node.metadata & Constants.StickinessMaskInverse) | (stickiness << Constants.StickinessOffset)126);127}128function getCollapseOnReplaceEdit(node: IntervalNode): boolean {129return ((node.metadata & Constants.CollapseOnReplaceEditMask) >>> Constants.CollapseOnReplaceEditOffset) === 1;130}131function setCollapseOnReplaceEdit(node: IntervalNode, value: boolean): void {132node.metadata = (133(node.metadata & Constants.CollapseOnReplaceEditMaskInverse) | ((value ? 1 : 0) << Constants.CollapseOnReplaceEditOffset)134);135}136export function setNodeStickiness(node: IntervalNode, stickiness: ActualTrackedRangeStickiness): void {137_setNodeStickiness(node, <number>stickiness);138}139140export class IntervalNode {141142/**143* contains binary encoded information for color, visited, isForValidation and stickiness.144*/145public metadata: number;146147public parent: IntervalNode;148public left: IntervalNode;149public right: IntervalNode;150151public start: number;152public end: number;153public delta: number;154public maxEnd: number;155156public id: string;157public ownerId: number;158public options: ModelDecorationOptions;159160public cachedVersionId: number;161public cachedAbsoluteStart: number;162public cachedAbsoluteEnd: number;163public range: Range | null;164165constructor(id: string, start: number, end: number) {166this.metadata = 0;167168this.parent = this;169this.left = this;170this.right = this;171setNodeColor(this, NodeColor.Red);172173this.start = start;174this.end = end;175// FORCE_OVERFLOWING_TEST: this.delta = start;176this.delta = 0;177this.maxEnd = end;178179this.id = id;180this.ownerId = 0;181this.options = null!;182setNodeIsForValidation(this, false);183setNodeIsInGlyphMargin(this, false);184_setNodeStickiness(this, TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges);185setCollapseOnReplaceEdit(this, false);186setNodeAffectsFont(this, false);187188this.cachedVersionId = 0;189this.cachedAbsoluteStart = start;190this.cachedAbsoluteEnd = end;191this.range = null;192193setNodeIsVisited(this, false);194}195196public reset(versionId: number, start: number, end: number, range: Range): void {197this.start = start;198this.end = end;199this.maxEnd = end;200this.cachedVersionId = versionId;201this.cachedAbsoluteStart = start;202this.cachedAbsoluteEnd = end;203this.range = range;204}205206public setOptions(options: ModelDecorationOptions) {207this.options = options;208const className = this.options.className;209setNodeIsForValidation(this, (210className === ClassName.EditorErrorDecoration211|| className === ClassName.EditorWarningDecoration212|| className === ClassName.EditorInfoDecoration213));214setNodeIsInGlyphMargin(this, this.options.glyphMarginClassName !== null);215_setNodeStickiness(this, <number>this.options.stickiness);216setCollapseOnReplaceEdit(this, this.options.collapseOnReplaceEdit);217setNodeAffectsFont(this, this.options.affectsFont ?? false);218}219220public setCachedOffsets(absoluteStart: number, absoluteEnd: number, cachedVersionId: number): void {221if (this.cachedVersionId !== cachedVersionId) {222this.range = null;223}224this.cachedVersionId = cachedVersionId;225this.cachedAbsoluteStart = absoluteStart;226this.cachedAbsoluteEnd = absoluteEnd;227}228229public detach(): void {230this.parent = null!;231this.left = null!;232this.right = null!;233}234}235236export const SENTINEL: IntervalNode = new IntervalNode(null!, 0, 0);237SENTINEL.parent = SENTINEL;238SENTINEL.left = SENTINEL;239SENTINEL.right = SENTINEL;240setNodeColor(SENTINEL, NodeColor.Black);241242export class IntervalTree {243244public root: IntervalNode;245public requestNormalizeDelta: boolean;246247constructor() {248this.root = SENTINEL;249this.requestNormalizeDelta = false;250}251252public intervalSearch(start: number, end: number, filterOwnerId: number, filterOutValidation: boolean, filterFontDecorations: boolean, cachedVersionId: number, onlyMarginDecorations: boolean): IntervalNode[] {253if (this.root === SENTINEL) {254return [];255}256return intervalSearch(this, start, end, filterOwnerId, filterOutValidation, filterFontDecorations, cachedVersionId, onlyMarginDecorations);257}258259public search(filterOwnerId: number, filterOutValidation: boolean, filterFontDecorations: boolean, cachedVersionId: number, onlyMarginDecorations: boolean): IntervalNode[] {260if (this.root === SENTINEL) {261return [];262}263return search(this, filterOwnerId, filterOutValidation, filterFontDecorations, cachedVersionId, onlyMarginDecorations);264}265266/**267* Will not set `cachedAbsoluteStart` nor `cachedAbsoluteEnd` on the returned nodes!268*/269public collectNodesFromOwner(ownerId: number): IntervalNode[] {270return collectNodesFromOwner(this, ownerId);271}272273/**274* Will not set `cachedAbsoluteStart` nor `cachedAbsoluteEnd` on the returned nodes!275*/276public collectNodesPostOrder(): IntervalNode[] {277return collectNodesPostOrder(this);278}279280public insert(node: IntervalNode): void {281rbTreeInsert(this, node);282this._normalizeDeltaIfNecessary();283}284285public delete(node: IntervalNode): void {286rbTreeDelete(this, node);287this._normalizeDeltaIfNecessary();288}289290public resolveNode(node: IntervalNode, cachedVersionId: number): void {291const initialNode = node;292let delta = 0;293while (node !== this.root) {294if (node === node.parent.right) {295delta += node.parent.delta;296}297node = node.parent;298}299300const nodeStart = initialNode.start + delta;301const nodeEnd = initialNode.end + delta;302initialNode.setCachedOffsets(nodeStart, nodeEnd, cachedVersionId);303}304305public acceptReplace(offset: number, length: number, textLength: number, forceMoveMarkers: boolean): void {306// Our strategy is to remove all directly impacted nodes, and then add them back to the tree.307308// (1) collect all nodes that are intersecting this edit as nodes of interest309const nodesOfInterest = searchForEditing(this, offset, offset + length);310311// (2) remove all nodes that are intersecting this edit312for (let i = 0, len = nodesOfInterest.length; i < len; i++) {313const node = nodesOfInterest[i];314rbTreeDelete(this, node);315}316this._normalizeDeltaIfNecessary();317318// (3) edit all tree nodes except the nodes of interest319noOverlapReplace(this, offset, offset + length, textLength);320this._normalizeDeltaIfNecessary();321322// (4) edit the nodes of interest and insert them back in the tree323for (let i = 0, len = nodesOfInterest.length; i < len; i++) {324const node = nodesOfInterest[i];325node.start = node.cachedAbsoluteStart;326node.end = node.cachedAbsoluteEnd;327nodeAcceptEdit(node, offset, (offset + length), textLength, forceMoveMarkers);328node.maxEnd = node.end;329rbTreeInsert(this, node);330}331this._normalizeDeltaIfNecessary();332}333334public getAllInOrder(): IntervalNode[] {335return search(this, 0, false, false, 0, false);336}337338private _normalizeDeltaIfNecessary(): void {339if (!this.requestNormalizeDelta) {340return;341}342this.requestNormalizeDelta = false;343normalizeDelta(this);344}345}346347//#region Delta Normalization348function normalizeDelta(T: IntervalTree): void {349let node = T.root;350let delta = 0;351while (node !== SENTINEL) {352353if (node.left !== SENTINEL && !getNodeIsVisited(node.left)) {354// go left355node = node.left;356continue;357}358359if (node.right !== SENTINEL && !getNodeIsVisited(node.right)) {360// go right361delta += node.delta;362node = node.right;363continue;364}365366// handle current node367node.start = delta + node.start;368node.end = delta + node.end;369node.delta = 0;370recomputeMaxEnd(node);371372setNodeIsVisited(node, true);373374// going up from this node375setNodeIsVisited(node.left, false);376setNodeIsVisited(node.right, false);377if (node === node.parent.right) {378delta -= node.parent.delta;379}380node = node.parent;381}382383setNodeIsVisited(T.root, false);384}385//#endregion386387//#region Editing388389const enum MarkerMoveSemantics {390MarkerDefined = 0,391ForceMove = 1,392ForceStay = 2393}394395function adjustMarkerBeforeColumn(markerOffset: number, markerStickToPreviousCharacter: boolean, checkOffset: number, moveSemantics: MarkerMoveSemantics): boolean {396if (markerOffset < checkOffset) {397return true;398}399if (markerOffset > checkOffset) {400return false;401}402if (moveSemantics === MarkerMoveSemantics.ForceMove) {403return false;404}405if (moveSemantics === MarkerMoveSemantics.ForceStay) {406return true;407}408return markerStickToPreviousCharacter;409}410411/**412* This is a lot more complicated than strictly necessary to maintain the same behaviour413* as when decorations were implemented using two markers.414*/415export function nodeAcceptEdit(node: IntervalNode, start: number, end: number, textLength: number, forceMoveMarkers: boolean): void {416const nodeStickiness = getNodeStickiness(node);417const startStickToPreviousCharacter = (418nodeStickiness === TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges419|| nodeStickiness === TrackedRangeStickiness.GrowsOnlyWhenTypingBefore420);421const endStickToPreviousCharacter = (422nodeStickiness === TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges423|| nodeStickiness === TrackedRangeStickiness.GrowsOnlyWhenTypingBefore424);425426const deletingCnt = (end - start);427const insertingCnt = textLength;428const commonLength = Math.min(deletingCnt, insertingCnt);429430const nodeStart = node.start;431let startDone = false;432433const nodeEnd = node.end;434let endDone = false;435436if (start <= nodeStart && nodeEnd <= end && getCollapseOnReplaceEdit(node)) {437// This edit encompasses the entire decoration range438// and the decoration has asked to become collapsed439node.start = start;440startDone = true;441node.end = start;442endDone = true;443}444445{446const moveSemantics = forceMoveMarkers ? MarkerMoveSemantics.ForceMove : (deletingCnt > 0 ? MarkerMoveSemantics.ForceStay : MarkerMoveSemantics.MarkerDefined);447if (!startDone && adjustMarkerBeforeColumn(nodeStart, startStickToPreviousCharacter, start, moveSemantics)) {448startDone = true;449}450if (!endDone && adjustMarkerBeforeColumn(nodeEnd, endStickToPreviousCharacter, start, moveSemantics)) {451endDone = true;452}453}454455if (commonLength > 0 && !forceMoveMarkers) {456const moveSemantics = (deletingCnt > insertingCnt ? MarkerMoveSemantics.ForceStay : MarkerMoveSemantics.MarkerDefined);457if (!startDone && adjustMarkerBeforeColumn(nodeStart, startStickToPreviousCharacter, start + commonLength, moveSemantics)) {458startDone = true;459}460if (!endDone && adjustMarkerBeforeColumn(nodeEnd, endStickToPreviousCharacter, start + commonLength, moveSemantics)) {461endDone = true;462}463}464465{466const moveSemantics = forceMoveMarkers ? MarkerMoveSemantics.ForceMove : MarkerMoveSemantics.MarkerDefined;467if (!startDone && adjustMarkerBeforeColumn(nodeStart, startStickToPreviousCharacter, end, moveSemantics)) {468node.start = start + insertingCnt;469startDone = true;470}471if (!endDone && adjustMarkerBeforeColumn(nodeEnd, endStickToPreviousCharacter, end, moveSemantics)) {472node.end = start + insertingCnt;473endDone = true;474}475}476477// Finish478const deltaColumn = (insertingCnt - deletingCnt);479if (!startDone) {480node.start = Math.max(0, nodeStart + deltaColumn);481}482if (!endDone) {483node.end = Math.max(0, nodeEnd + deltaColumn);484}485486if (node.start > node.end) {487node.end = node.start;488}489}490491function searchForEditing(T: IntervalTree, start: number, end: number): IntervalNode[] {492// https://en.wikipedia.org/wiki/Interval_tree#Augmented_tree493// Now, it is known that two intervals A and B overlap only when both494// A.low <= B.high and A.high >= B.low. When searching the trees for495// nodes overlapping with a given interval, you can immediately skip:496// a) all nodes to the right of nodes whose low value is past the end of the given interval.497// b) all nodes that have their maximum 'high' value below the start of the given interval.498let node = T.root;499let delta = 0;500let nodeMaxEnd = 0;501let nodeStart = 0;502let nodeEnd = 0;503const result: IntervalNode[] = [];504let resultLen = 0;505while (node !== SENTINEL) {506if (getNodeIsVisited(node)) {507// going up from this node508setNodeIsVisited(node.left, false);509setNodeIsVisited(node.right, false);510if (node === node.parent.right) {511delta -= node.parent.delta;512}513node = node.parent;514continue;515}516517if (!getNodeIsVisited(node.left)) {518// first time seeing this node519nodeMaxEnd = delta + node.maxEnd;520if (nodeMaxEnd < start) {521// cover case b) from above522// there is no need to search this node or its children523setNodeIsVisited(node, true);524continue;525}526527if (node.left !== SENTINEL) {528// go left529node = node.left;530continue;531}532}533534// handle current node535nodeStart = delta + node.start;536if (nodeStart > end) {537// cover case a) from above538// there is no need to search this node or its right subtree539setNodeIsVisited(node, true);540continue;541}542543nodeEnd = delta + node.end;544if (nodeEnd >= start) {545node.setCachedOffsets(nodeStart, nodeEnd, 0);546result[resultLen++] = node;547}548setNodeIsVisited(node, true);549550if (node.right !== SENTINEL && !getNodeIsVisited(node.right)) {551// go right552delta += node.delta;553node = node.right;554continue;555}556}557558setNodeIsVisited(T.root, false);559560return result;561}562563function noOverlapReplace(T: IntervalTree, start: number, end: number, textLength: number): void {564// https://en.wikipedia.org/wiki/Interval_tree#Augmented_tree565// Now, it is known that two intervals A and B overlap only when both566// A.low <= B.high and A.high >= B.low. When searching the trees for567// nodes overlapping with a given interval, you can immediately skip:568// a) all nodes to the right of nodes whose low value is past the end of the given interval.569// b) all nodes that have their maximum 'high' value below the start of the given interval.570let node = T.root;571let delta = 0;572let nodeMaxEnd = 0;573let nodeStart = 0;574const editDelta = (textLength - (end - start));575while (node !== SENTINEL) {576if (getNodeIsVisited(node)) {577// going up from this node578setNodeIsVisited(node.left, false);579setNodeIsVisited(node.right, false);580if (node === node.parent.right) {581delta -= node.parent.delta;582}583recomputeMaxEnd(node);584node = node.parent;585continue;586}587588if (!getNodeIsVisited(node.left)) {589// first time seeing this node590nodeMaxEnd = delta + node.maxEnd;591if (nodeMaxEnd < start) {592// cover case b) from above593// there is no need to search this node or its children594setNodeIsVisited(node, true);595continue;596}597598if (node.left !== SENTINEL) {599// go left600node = node.left;601continue;602}603}604605// handle current node606nodeStart = delta + node.start;607if (nodeStart > end) {608node.start += editDelta;609node.end += editDelta;610node.delta += editDelta;611if (node.delta < Constants.MIN_SAFE_DELTA || node.delta > Constants.MAX_SAFE_DELTA) {612T.requestNormalizeDelta = true;613}614// cover case a) from above615// there is no need to search this node or its right subtree616setNodeIsVisited(node, true);617continue;618}619620setNodeIsVisited(node, true);621622if (node.right !== SENTINEL && !getNodeIsVisited(node.right)) {623// go right624delta += node.delta;625node = node.right;626continue;627}628}629630setNodeIsVisited(T.root, false);631}632633//#endregion634635//#region Searching636637function collectNodesFromOwner(T: IntervalTree, ownerId: number): IntervalNode[] {638let node = T.root;639const result: IntervalNode[] = [];640let resultLen = 0;641while (node !== SENTINEL) {642if (getNodeIsVisited(node)) {643// going up from this node644setNodeIsVisited(node.left, false);645setNodeIsVisited(node.right, false);646node = node.parent;647continue;648}649650if (node.left !== SENTINEL && !getNodeIsVisited(node.left)) {651// go left652node = node.left;653continue;654}655656// handle current node657if (node.ownerId === ownerId) {658result[resultLen++] = node;659}660661setNodeIsVisited(node, true);662663if (node.right !== SENTINEL && !getNodeIsVisited(node.right)) {664// go right665node = node.right;666continue;667}668}669670setNodeIsVisited(T.root, false);671672return result;673}674675function collectNodesPostOrder(T: IntervalTree): IntervalNode[] {676let node = T.root;677const result: IntervalNode[] = [];678let resultLen = 0;679while (node !== SENTINEL) {680if (getNodeIsVisited(node)) {681// going up from this node682setNodeIsVisited(node.left, false);683setNodeIsVisited(node.right, false);684node = node.parent;685continue;686}687688if (node.left !== SENTINEL && !getNodeIsVisited(node.left)) {689// go left690node = node.left;691continue;692}693694if (node.right !== SENTINEL && !getNodeIsVisited(node.right)) {695// go right696node = node.right;697continue;698}699700// handle current node701result[resultLen++] = node;702setNodeIsVisited(node, true);703}704705setNodeIsVisited(T.root, false);706707return result;708}709710function search(T: IntervalTree, filterOwnerId: number, filterOutValidation: boolean, filterFontDecorations: boolean, cachedVersionId: number, onlyMarginDecorations: boolean): IntervalNode[] {711let node = T.root;712let delta = 0;713let nodeStart = 0;714let nodeEnd = 0;715const result: IntervalNode[] = [];716let resultLen = 0;717while (node !== SENTINEL) {718if (getNodeIsVisited(node)) {719// going up from this node720setNodeIsVisited(node.left, false);721setNodeIsVisited(node.right, false);722if (node === node.parent.right) {723delta -= node.parent.delta;724}725node = node.parent;726continue;727}728729if (node.left !== SENTINEL && !getNodeIsVisited(node.left)) {730// go left731node = node.left;732continue;733}734735// handle current node736nodeStart = delta + node.start;737nodeEnd = delta + node.end;738739node.setCachedOffsets(nodeStart, nodeEnd, cachedVersionId);740741let include = true;742if (filterOwnerId && node.ownerId && node.ownerId !== filterOwnerId) {743include = false;744}745if (filterOutValidation && getNodeIsForValidation(node)) {746include = false;747}748if (filterFontDecorations && getNodeAffectsFont(node)) {749include = false;750}751if (onlyMarginDecorations && !getNodeIsInGlyphMargin(node)) {752include = false;753}754755if (include) {756result[resultLen++] = node;757}758759setNodeIsVisited(node, true);760761if (node.right !== SENTINEL && !getNodeIsVisited(node.right)) {762// go right763delta += node.delta;764node = node.right;765continue;766}767}768769setNodeIsVisited(T.root, false);770771return result;772}773774function intervalSearch(T: IntervalTree, intervalStart: number, intervalEnd: number, filterOwnerId: number, filterOutValidation: boolean, filterFontDecorations: boolean, cachedVersionId: number, onlyMarginDecorations: boolean): IntervalNode[] {775// https://en.wikipedia.org/wiki/Interval_tree#Augmented_tree776// Now, it is known that two intervals A and B overlap only when both777// A.low <= B.high and A.high >= B.low. When searching the trees for778// nodes overlapping with a given interval, you can immediately skip:779// a) all nodes to the right of nodes whose low value is past the end of the given interval.780// b) all nodes that have their maximum 'high' value below the start of the given interval.781782let node = T.root;783let delta = 0;784let nodeMaxEnd = 0;785let nodeStart = 0;786let nodeEnd = 0;787const result: IntervalNode[] = [];788let resultLen = 0;789while (node !== SENTINEL) {790if (getNodeIsVisited(node)) {791// going up from this node792setNodeIsVisited(node.left, false);793setNodeIsVisited(node.right, false);794if (node === node.parent.right) {795delta -= node.parent.delta;796}797node = node.parent;798continue;799}800801if (!getNodeIsVisited(node.left)) {802// first time seeing this node803nodeMaxEnd = delta + node.maxEnd;804if (nodeMaxEnd < intervalStart) {805// cover case b) from above806// there is no need to search this node or its children807setNodeIsVisited(node, true);808continue;809}810811if (node.left !== SENTINEL) {812// go left813node = node.left;814continue;815}816}817818// handle current node819nodeStart = delta + node.start;820if (nodeStart > intervalEnd) {821// cover case a) from above822// there is no need to search this node or its right subtree823setNodeIsVisited(node, true);824continue;825}826827nodeEnd = delta + node.end;828829if (nodeEnd >= intervalStart) {830// There is overlap831node.setCachedOffsets(nodeStart, nodeEnd, cachedVersionId);832833let include = true;834if (filterOwnerId && node.ownerId && node.ownerId !== filterOwnerId) {835include = false;836}837if (filterOutValidation && getNodeIsForValidation(node)) {838include = false;839}840if (filterFontDecorations && getNodeAffectsFont(node)) {841include = false;842}843if (onlyMarginDecorations && !getNodeIsInGlyphMargin(node)) {844include = false;845}846847if (include) {848result[resultLen++] = node;849}850}851852setNodeIsVisited(node, true);853854if (node.right !== SENTINEL && !getNodeIsVisited(node.right)) {855// go right856delta += node.delta;857node = node.right;858continue;859}860}861862setNodeIsVisited(T.root, false);863864return result;865}866867//#endregion868869//#region Insertion870function rbTreeInsert(T: IntervalTree, newNode: IntervalNode): IntervalNode {871if (T.root === SENTINEL) {872newNode.parent = SENTINEL;873newNode.left = SENTINEL;874newNode.right = SENTINEL;875setNodeColor(newNode, NodeColor.Black);876T.root = newNode;877return T.root;878}879880treeInsert(T, newNode);881882recomputeMaxEndWalkToRoot(newNode.parent);883884// repair tree885let x = newNode;886while (x !== T.root && getNodeColor(x.parent) === NodeColor.Red) {887if (x.parent === x.parent.parent.left) {888const y = x.parent.parent.right;889890if (getNodeColor(y) === NodeColor.Red) {891setNodeColor(x.parent, NodeColor.Black);892setNodeColor(y, NodeColor.Black);893setNodeColor(x.parent.parent, NodeColor.Red);894x = x.parent.parent;895} else {896if (x === x.parent.right) {897x = x.parent;898leftRotate(T, x);899}900setNodeColor(x.parent, NodeColor.Black);901setNodeColor(x.parent.parent, NodeColor.Red);902rightRotate(T, x.parent.parent);903}904} else {905const y = x.parent.parent.left;906907if (getNodeColor(y) === NodeColor.Red) {908setNodeColor(x.parent, NodeColor.Black);909setNodeColor(y, NodeColor.Black);910setNodeColor(x.parent.parent, NodeColor.Red);911x = x.parent.parent;912} else {913if (x === x.parent.left) {914x = x.parent;915rightRotate(T, x);916}917setNodeColor(x.parent, NodeColor.Black);918setNodeColor(x.parent.parent, NodeColor.Red);919leftRotate(T, x.parent.parent);920}921}922}923924setNodeColor(T.root, NodeColor.Black);925926return newNode;927}928929function treeInsert(T: IntervalTree, z: IntervalNode): void {930let delta: number = 0;931let x = T.root;932const zAbsoluteStart = z.start;933const zAbsoluteEnd = z.end;934while (true) {935const cmp = intervalCompare(zAbsoluteStart, zAbsoluteEnd, x.start + delta, x.end + delta);936if (cmp < 0) {937// this node should be inserted to the left938// => it is not affected by the node's delta939if (x.left === SENTINEL) {940z.start -= delta;941z.end -= delta;942z.maxEnd -= delta;943x.left = z;944break;945} else {946x = x.left;947}948} else {949// this node should be inserted to the right950// => it is not affected by the node's delta951if (x.right === SENTINEL) {952z.start -= (delta + x.delta);953z.end -= (delta + x.delta);954z.maxEnd -= (delta + x.delta);955x.right = z;956break;957} else {958delta += x.delta;959x = x.right;960}961}962}963964z.parent = x;965z.left = SENTINEL;966z.right = SENTINEL;967setNodeColor(z, NodeColor.Red);968}969//#endregion970971//#region Deletion972function rbTreeDelete(T: IntervalTree, z: IntervalNode): void {973974let x: IntervalNode;975let y: IntervalNode;976977// RB-DELETE except we don't swap z and y in case c)978// i.e. we always delete what's pointed at by z.979980if (z.left === SENTINEL) {981x = z.right;982y = z;983984// x's delta is no longer influenced by z's delta985x.delta += z.delta;986if (x.delta < Constants.MIN_SAFE_DELTA || x.delta > Constants.MAX_SAFE_DELTA) {987T.requestNormalizeDelta = true;988}989x.start += z.delta;990x.end += z.delta;991992} else if (z.right === SENTINEL) {993x = z.left;994y = z;995996} else {997y = leftest(z.right);998x = y.right;9991000// y's delta is no longer influenced by z's delta,1001// but we don't want to walk the entire right-hand-side subtree of x.1002// we therefore maintain z's delta in y, and adjust only x1003x.start += y.delta;1004x.end += y.delta;1005x.delta += y.delta;1006if (x.delta < Constants.MIN_SAFE_DELTA || x.delta > Constants.MAX_SAFE_DELTA) {1007T.requestNormalizeDelta = true;1008}10091010y.start += z.delta;1011y.end += z.delta;1012y.delta = z.delta;1013if (y.delta < Constants.MIN_SAFE_DELTA || y.delta > Constants.MAX_SAFE_DELTA) {1014T.requestNormalizeDelta = true;1015}1016}10171018if (y === T.root) {1019T.root = x;1020setNodeColor(x, NodeColor.Black);10211022z.detach();1023resetSentinel();1024recomputeMaxEnd(x);1025T.root.parent = SENTINEL;1026return;1027}10281029const yWasRed = (getNodeColor(y) === NodeColor.Red);10301031if (y === y.parent.left) {1032y.parent.left = x;1033} else {1034y.parent.right = x;1035}10361037if (y === z) {1038x.parent = y.parent;1039} else {10401041if (y.parent === z) {1042x.parent = y;1043} else {1044x.parent = y.parent;1045}10461047y.left = z.left;1048y.right = z.right;1049y.parent = z.parent;1050setNodeColor(y, getNodeColor(z));10511052if (z === T.root) {1053T.root = y;1054} else {1055if (z === z.parent.left) {1056z.parent.left = y;1057} else {1058z.parent.right = y;1059}1060}10611062if (y.left !== SENTINEL) {1063y.left.parent = y;1064}1065if (y.right !== SENTINEL) {1066y.right.parent = y;1067}1068}10691070z.detach();10711072if (yWasRed) {1073recomputeMaxEndWalkToRoot(x.parent);1074if (y !== z) {1075recomputeMaxEndWalkToRoot(y);1076recomputeMaxEndWalkToRoot(y.parent);1077}1078resetSentinel();1079return;1080}10811082recomputeMaxEndWalkToRoot(x);1083recomputeMaxEndWalkToRoot(x.parent);1084if (y !== z) {1085recomputeMaxEndWalkToRoot(y);1086recomputeMaxEndWalkToRoot(y.parent);1087}10881089// RB-DELETE-FIXUP1090let w: IntervalNode;1091while (x !== T.root && getNodeColor(x) === NodeColor.Black) {10921093if (x === x.parent.left) {1094w = x.parent.right;10951096if (getNodeColor(w) === NodeColor.Red) {1097setNodeColor(w, NodeColor.Black);1098setNodeColor(x.parent, NodeColor.Red);1099leftRotate(T, x.parent);1100w = x.parent.right;1101}11021103if (getNodeColor(w.left) === NodeColor.Black && getNodeColor(w.right) === NodeColor.Black) {1104setNodeColor(w, NodeColor.Red);1105x = x.parent;1106} else {1107if (getNodeColor(w.right) === NodeColor.Black) {1108setNodeColor(w.left, NodeColor.Black);1109setNodeColor(w, NodeColor.Red);1110rightRotate(T, w);1111w = x.parent.right;1112}11131114setNodeColor(w, getNodeColor(x.parent));1115setNodeColor(x.parent, NodeColor.Black);1116setNodeColor(w.right, NodeColor.Black);1117leftRotate(T, x.parent);1118x = T.root;1119}11201121} else {1122w = x.parent.left;11231124if (getNodeColor(w) === NodeColor.Red) {1125setNodeColor(w, NodeColor.Black);1126setNodeColor(x.parent, NodeColor.Red);1127rightRotate(T, x.parent);1128w = x.parent.left;1129}11301131if (getNodeColor(w.left) === NodeColor.Black && getNodeColor(w.right) === NodeColor.Black) {1132setNodeColor(w, NodeColor.Red);1133x = x.parent;11341135} else {1136if (getNodeColor(w.left) === NodeColor.Black) {1137setNodeColor(w.right, NodeColor.Black);1138setNodeColor(w, NodeColor.Red);1139leftRotate(T, w);1140w = x.parent.left;1141}11421143setNodeColor(w, getNodeColor(x.parent));1144setNodeColor(x.parent, NodeColor.Black);1145setNodeColor(w.left, NodeColor.Black);1146rightRotate(T, x.parent);1147x = T.root;1148}1149}1150}11511152setNodeColor(x, NodeColor.Black);1153resetSentinel();1154}11551156function leftest(node: IntervalNode): IntervalNode {1157while (node.left !== SENTINEL) {1158node = node.left;1159}1160return node;1161}11621163function resetSentinel(): void {1164SENTINEL.parent = SENTINEL;1165SENTINEL.delta = 0; // optional1166SENTINEL.start = 0; // optional1167SENTINEL.end = 0; // optional1168}1169//#endregion11701171//#region Rotations1172function leftRotate(T: IntervalTree, x: IntervalNode): void {1173const y = x.right; // set y.11741175y.delta += x.delta; // y's delta is no longer influenced by x's delta1176if (y.delta < Constants.MIN_SAFE_DELTA || y.delta > Constants.MAX_SAFE_DELTA) {1177T.requestNormalizeDelta = true;1178}1179y.start += x.delta;1180y.end += x.delta;11811182x.right = y.left; // turn y's left subtree into x's right subtree.1183if (y.left !== SENTINEL) {1184y.left.parent = x;1185}1186y.parent = x.parent; // link x's parent to y.1187if (x.parent === SENTINEL) {1188T.root = y;1189} else if (x === x.parent.left) {1190x.parent.left = y;1191} else {1192x.parent.right = y;1193}11941195y.left = x; // put x on y's left.1196x.parent = y;11971198recomputeMaxEnd(x);1199recomputeMaxEnd(y);1200}12011202function rightRotate(T: IntervalTree, y: IntervalNode): void {1203const x = y.left;12041205y.delta -= x.delta;1206if (y.delta < Constants.MIN_SAFE_DELTA || y.delta > Constants.MAX_SAFE_DELTA) {1207T.requestNormalizeDelta = true;1208}1209y.start -= x.delta;1210y.end -= x.delta;12111212y.left = x.right;1213if (x.right !== SENTINEL) {1214x.right.parent = y;1215}1216x.parent = y.parent;1217if (y.parent === SENTINEL) {1218T.root = x;1219} else if (y === y.parent.right) {1220y.parent.right = x;1221} else {1222y.parent.left = x;1223}12241225x.right = y;1226y.parent = x;12271228recomputeMaxEnd(y);1229recomputeMaxEnd(x);1230}1231//#endregion12321233//#region max end computation12341235function computeMaxEnd(node: IntervalNode): number {1236let maxEnd = node.end;1237if (node.left !== SENTINEL) {1238const leftMaxEnd = node.left.maxEnd;1239if (leftMaxEnd > maxEnd) {1240maxEnd = leftMaxEnd;1241}1242}1243if (node.right !== SENTINEL) {1244const rightMaxEnd = node.right.maxEnd + node.delta;1245if (rightMaxEnd > maxEnd) {1246maxEnd = rightMaxEnd;1247}1248}1249return maxEnd;1250}12511252export function recomputeMaxEnd(node: IntervalNode): void {1253node.maxEnd = computeMaxEnd(node);1254}12551256function recomputeMaxEndWalkToRoot(node: IntervalNode): void {1257while (node !== SENTINEL) {12581259const maxEnd = computeMaxEnd(node);12601261if (node.maxEnd === maxEnd) {1262// no need to go further1263return;1264}12651266node.maxEnd = maxEnd;1267node = node.parent;1268}1269}12701271//#endregion12721273//#region utils1274export function intervalCompare(aStart: number, aEnd: number, bStart: number, bEnd: number): number {1275if (aStart === bStart) {1276return aEnd - bEnd;1277}1278return aStart - bStart;1279}1280//#endregion128112821283