Skip to content

Commit d6cef87

Browse files
authored
fix: part of freeze with debug.inlineValues: on in large files (microsoft#201028)
Fixes microsoft#187783 We now limit inline value display to the area around the viewport, which is what we do with an extension-provided InlineValueProvider anyway. This saves having to force tokenization for the entire file, and instead only in the interested range. It can still stall if the tokenizer is slow, but there's not a workaround for that aside from the extension providing their own inline value provider.
1 parent 0e7b4fc commit d6cef87

File tree

1 file changed

+60
-47
lines changed

1 file changed

+60
-47
lines changed

src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts

Lines changed: 60 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ function replaceWsWithNoBreakWs(str: string): string {
125125
return str.replace(/[ \t]/g, strings.noBreakWhitespace);
126126
}
127127

128-
function createInlineValueDecorationsInsideRange(expressions: ReadonlyArray<IExpression>, range: Range, model: ITextModel, wordToLineNumbersMap: Map<string, number[]>): IModelDeltaDecoration[] {
128+
function createInlineValueDecorationsInsideRange(expressions: ReadonlyArray<IExpression>, ranges: Range[], model: ITextModel, wordToLineNumbersMap: Map<string, number[]>): IModelDeltaDecoration[] {
129129
const nameValueMap = new Map<string, string>();
130130
for (const expr of expressions) {
131131
nameValueMap.set(expr.name, expr.value);
@@ -142,7 +142,7 @@ function createInlineValueDecorationsInsideRange(expressions: ReadonlyArray<IExp
142142
const lineNumbers = wordToLineNumbersMap.get(name);
143143
if (lineNumbers) {
144144
for (const lineNumber of lineNumbers) {
145-
if (range.containsPosition(new Position(lineNumber, 0))) {
145+
if (ranges.some(r => lineNumber >= r.startLineNumber && lineNumber <= r.endLineNumber)) {
146146
if (!lineToNamesMap.has(lineNumber)) {
147147
lineToNamesMap.set(lineNumber, []);
148148
}
@@ -168,49 +168,39 @@ function createInlineValueDecorationsInsideRange(expressions: ReadonlyArray<IExp
168168
return decorations;
169169
}
170170

171-
function getWordToLineNumbersMap(model: ITextModel | null): Map<string, number[]> {
172-
const result = new Map<string, number[]>();
173-
if (!model) {
174-
return result;
171+
function getWordToLineNumbersMap(model: ITextModel, lineNumber: number, result: Map<string, number[]>) {
172+
const lineLength = model.getLineLength(lineNumber);
173+
// If line is too long then skip the line
174+
if (lineLength > MAX_TOKENIZATION_LINE_LEN) {
175+
return;
175176
}
176177

177-
// For every word in every line, map its ranges for fast lookup
178-
for (let lineNumber = 1, len = model.getLineCount(); lineNumber <= len; ++lineNumber) {
179-
const lineLength = model.getLineLength(lineNumber);
180-
// If line is too long then skip the line
181-
if (lineLength > MAX_TOKENIZATION_LINE_LEN) {
182-
continue;
183-
}
184-
185-
const lineContent = model.getLineContent(lineNumber);
186-
model.tokenization.forceTokenization(lineNumber);
187-
const lineTokens = model.tokenization.getLineTokens(lineNumber);
188-
for (let tokenIndex = 0, tokenCount = lineTokens.getCount(); tokenIndex < tokenCount; tokenIndex++) {
189-
const tokenType = lineTokens.getStandardTokenType(tokenIndex);
178+
const lineContent = model.getLineContent(lineNumber);
179+
model.tokenization.forceTokenization(lineNumber);
180+
const lineTokens = model.tokenization.getLineTokens(lineNumber);
181+
for (let tokenIndex = 0, tokenCount = lineTokens.getCount(); tokenIndex < tokenCount; tokenIndex++) {
182+
const tokenType = lineTokens.getStandardTokenType(tokenIndex);
190183

191-
// Token is a word and not a comment
192-
if (tokenType === StandardTokenType.Other) {
193-
DEFAULT_WORD_REGEXP.lastIndex = 0; // We assume tokens will usually map 1:1 to words if they match
184+
// Token is a word and not a comment
185+
if (tokenType === StandardTokenType.Other) {
186+
DEFAULT_WORD_REGEXP.lastIndex = 0; // We assume tokens will usually map 1:1 to words if they match
194187

195-
const tokenStartOffset = lineTokens.getStartOffset(tokenIndex);
196-
const tokenEndOffset = lineTokens.getEndOffset(tokenIndex);
197-
const tokenStr = lineContent.substring(tokenStartOffset, tokenEndOffset);
198-
const wordMatch = DEFAULT_WORD_REGEXP.exec(tokenStr);
188+
const tokenStartOffset = lineTokens.getStartOffset(tokenIndex);
189+
const tokenEndOffset = lineTokens.getEndOffset(tokenIndex);
190+
const tokenStr = lineContent.substring(tokenStartOffset, tokenEndOffset);
191+
const wordMatch = DEFAULT_WORD_REGEXP.exec(tokenStr);
199192

200-
if (wordMatch) {
193+
if (wordMatch) {
201194

202-
const word = wordMatch[0];
203-
if (!result.has(word)) {
204-
result.set(word, []);
205-
}
206-
207-
result.get(word)!.push(lineNumber);
195+
const word = wordMatch[0];
196+
if (!result.has(word)) {
197+
result.set(word, []);
208198
}
199+
200+
result.get(word)!.push(lineNumber);
209201
}
210202
}
211203
}
212-
213-
return result;
214204
}
215205

216206
export class DebugEditorContribution implements IDebugEditorContribution {
@@ -310,13 +300,7 @@ export class DebugEditorContribution implements IDebugEditorContribution {
310300
this.updateHoverConfiguration();
311301
}
312302

313-
private _wordToLineNumbersMap: Map<string, number[]> | undefined = undefined;
314-
private get wordToLineNumbersMap(): Map<string, number[]> {
315-
if (!this._wordToLineNumbersMap) {
316-
this._wordToLineNumbersMap = getWordToLineNumbersMap(this.editor.getModel());
317-
}
318-
return this._wordToLineNumbersMap;
319-
}
303+
private _wordToLineNumbersMap: WordsToLineNumbersCache | undefined;
320304

321305
private updateHoverConfiguration(): void {
322306
const model = this.editor.getModel();
@@ -465,6 +449,7 @@ export class DebugEditorContribution implements IDebugEditorContribution {
465449
this.hoverWidget.hide();
466450
}
467451
this.showHoverScheduler.cancel();
452+
this.defaultHoverLockout.clear();
468453
}
469454

470455
// hover business
@@ -686,6 +671,7 @@ export class DebugEditorContribution implements IDebugEditorContribution {
686671

687672
this.removeInlineValuesScheduler.cancel();
688673

674+
const viewRanges = this.editor.getVisibleRangesPlusViewportAboveBelow();
689675
let allDecorations: IModelDeltaDecoration[];
690676

691677
if (this.languageFeaturesService.inlineValuesProvider.has(model)) {
@@ -709,13 +695,12 @@ export class DebugEditorContribution implements IDebugEditorContribution {
709695
};
710696
const token = new CancellationTokenSource().token;
711697

712-
const ranges = this.editor.getVisibleRangesPlusViewportAboveBelow();
713698
const providers = this.languageFeaturesService.inlineValuesProvider.ordered(model).reverse();
714699

715700
allDecorations = [];
716701
const lineDecorations = new Map<number, InlineSegment[]>();
717702

718-
const promises = flatten(providers.map(provider => ranges.map(range => Promise.resolve(provider.provideInlineValues(model, range, ctx, token)).then(async (result) => {
703+
const promises = flatten(providers.map(provider => viewRanges.map(range => Promise.resolve(provider.provideInlineValues(model, range, ctx, token)).then(async (result) => {
719704
if (result) {
720705
for (const iv of result) {
721706

@@ -795,12 +780,18 @@ export class DebugEditorContribution implements IDebugEditorContribution {
795780
const decorationsPerScope = await Promise.all(scopes.map(async scope => {
796781
const variables = await scope.getChildren();
797782

798-
let range = new Range(0, 0, stackFrame.range.startLineNumber, stackFrame.range.startColumn);
783+
let scopeRange = new Range(0, 0, stackFrame.range.startLineNumber, stackFrame.range.startColumn);
799784
if (scope.range) {
800-
range = range.setStartPosition(scope.range.startLineNumber, scope.range.startColumn);
785+
scopeRange = scopeRange.setStartPosition(scope.range.startLineNumber, scope.range.startColumn);
801786
}
802787

803-
return createInlineValueDecorationsInsideRange(variables, range, model, this.wordToLineNumbersMap);
788+
const ownRanges = viewRanges.map(r => r.intersectRanges(scopeRange)).filter(isDefined);
789+
this._wordToLineNumbersMap ??= new WordsToLineNumbersCache(model);
790+
for (const range of ownRanges) {
791+
this._wordToLineNumbersMap.ensureRangePopulated(range);
792+
}
793+
794+
return createInlineValueDecorationsInsideRange(variables, ownRanges, model, this._wordToLineNumbersMap.value);
804795
}));
805796

806797
allDecorations = distinct(decorationsPerScope.reduce((previous, current) => previous.concat(current), []),
@@ -824,6 +815,28 @@ export class DebugEditorContribution implements IDebugEditorContribution {
824815
}
825816
}
826817

818+
class WordsToLineNumbersCache {
819+
// we use this as an array of bits where each 1 bit is a line number that's been parsed
820+
private readonly intervals: Uint8Array;
821+
public readonly value = new Map<string, number[]>();
822+
823+
constructor(private readonly model: ITextModel) {
824+
this.intervals = new Uint8Array(Math.ceil(model.getLineCount() / 8));
825+
}
826+
827+
/** Ensures that variables names in the given range have been identified. */
828+
public ensureRangePopulated(range: Range) {
829+
for (let lineNumber = range.startLineNumber; lineNumber <= range.endLineNumber; lineNumber++) {
830+
const bin = lineNumber >> 3; /* Math.floor(i / 8) */
831+
const bit = 1 << (lineNumber & 0b111); /* 1 << (i % 8) */
832+
if (!(this.intervals[bin] & bit)) {
833+
getWordToLineNumbersMap(this.model, lineNumber, this.value);
834+
this.intervals[bin] |= bit;
835+
}
836+
}
837+
}
838+
}
839+
827840

828841
CommandsRegistry.registerCommand(
829842
'_executeInlineValueProvider',

0 commit comments

Comments
 (0)