Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/nodeReader.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { AstNode } from './ast.js';
7
import { lengthAdd, lengthZero, Length, lengthLessThan } from './length.js';
8
9
/**
10
* Allows to efficiently find a longest child at a given offset in a fixed node.
11
* The requested offsets must increase monotonously.
12
*/
13
export class NodeReader {
14
private readonly nextNodes: AstNode[];
15
private readonly offsets: Length[];
16
private readonly idxs: number[];
17
private lastOffset: Length = lengthZero;
18
19
constructor(node: AstNode) {
20
this.nextNodes = [node];
21
this.offsets = [lengthZero];
22
this.idxs = [];
23
}
24
25
/**
26
* Returns the longest node at `offset` that satisfies the predicate.
27
* @param offset must be greater than or equal to the last offset this method has been called with!
28
*/
29
readLongestNodeAt(offset: Length, predicate: (node: AstNode) => boolean): AstNode | undefined {
30
if (lengthLessThan(offset, this.lastOffset)) {
31
throw new Error('Invalid offset');
32
}
33
this.lastOffset = offset;
34
35
// Find the longest node of all those that are closest to the current offset.
36
while (true) {
37
const curNode = lastOrUndefined(this.nextNodes);
38
39
if (!curNode) {
40
return undefined;
41
}
42
const curNodeOffset = lastOrUndefined(this.offsets)!;
43
44
if (lengthLessThan(offset, curNodeOffset)) {
45
// The next best node is not here yet.
46
// The reader must advance before a cached node is hit.
47
return undefined;
48
}
49
50
if (lengthLessThan(curNodeOffset, offset)) {
51
// The reader is ahead of the current node.
52
if (lengthAdd(curNodeOffset, curNode.length) <= offset) {
53
// The reader is after the end of the current node.
54
this.nextNodeAfterCurrent();
55
} else {
56
// The reader is somewhere in the current node.
57
const nextChildIdx = getNextChildIdx(curNode);
58
if (nextChildIdx !== -1) {
59
// Go to the first child and repeat.
60
this.nextNodes.push(curNode.getChild(nextChildIdx)!);
61
this.offsets.push(curNodeOffset);
62
this.idxs.push(nextChildIdx);
63
} else {
64
// We don't have children
65
this.nextNodeAfterCurrent();
66
}
67
}
68
} else {
69
// readerOffsetBeforeChange === curNodeOffset
70
if (predicate(curNode)) {
71
this.nextNodeAfterCurrent();
72
return curNode;
73
} else {
74
const nextChildIdx = getNextChildIdx(curNode);
75
// look for shorter node
76
if (nextChildIdx === -1) {
77
// There is no shorter node.
78
this.nextNodeAfterCurrent();
79
return undefined;
80
} else {
81
// Descend into first child & repeat.
82
this.nextNodes.push(curNode.getChild(nextChildIdx)!);
83
this.offsets.push(curNodeOffset);
84
this.idxs.push(nextChildIdx);
85
}
86
}
87
}
88
}
89
}
90
91
// Navigates to the longest node that continues after the current node.
92
private nextNodeAfterCurrent(): void {
93
while (true) {
94
const currentOffset = lastOrUndefined(this.offsets);
95
const currentNode = lastOrUndefined(this.nextNodes);
96
this.nextNodes.pop();
97
this.offsets.pop();
98
99
if (this.idxs.length === 0) {
100
// We just popped the root node, there is no next node.
101
break;
102
}
103
104
// Parent is not undefined, because idxs is not empty
105
const parent = lastOrUndefined(this.nextNodes)!;
106
const nextChildIdx = getNextChildIdx(parent, this.idxs[this.idxs.length - 1]);
107
108
if (nextChildIdx !== -1) {
109
this.nextNodes.push(parent.getChild(nextChildIdx)!);
110
this.offsets.push(lengthAdd(currentOffset!, currentNode!.length));
111
this.idxs[this.idxs.length - 1] = nextChildIdx;
112
break;
113
} else {
114
this.idxs.pop();
115
}
116
// We fully consumed the parent.
117
// Current node is now parent, so call nextNodeAfterCurrent again
118
}
119
}
120
}
121
122
function getNextChildIdx(node: AstNode, curIdx: number = -1): number | -1 {
123
while (true) {
124
curIdx++;
125
if (curIdx >= node.childrenLength) {
126
return -1;
127
}
128
if (node.getChild(curIdx)) {
129
return curIdx;
130
}
131
}
132
}
133
134
function lastOrUndefined<T>(arr: readonly T[]): T | undefined {
135
return arr.length > 0 ? arr[arr.length - 1] : undefined;
136
}
137
138