Skip to content

Commit db334b2

Browse files
committed
debug: inline values use map, set. Polish
1 parent 07b8362 commit db334b2

4 files changed

Lines changed: 176 additions & 227 deletions

File tree

src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@ import { visit } from 'vs/base/common/json';
1414
import { IAction, Action } from 'vs/base/common/actions';
1515
import { KeyCode } from 'vs/base/common/keyCodes';
1616
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
17-
import { IStringDictionary } from 'vs/base/common/collections';
1817
import { ICodeEditor, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser';
1918
import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions';
20-
import { IRange, IModelDecorationOptions, MouseTargetType, IModelDeltaDecoration, TrackedRangeStickiness, IPosition } from 'vs/editor/common/editorCommon';
19+
import { IModelDecorationOptions, MouseTargetType, IModelDeltaDecoration, TrackedRangeStickiness, IPosition } from 'vs/editor/common/editorCommon';
2120
import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService';
2221
import { Range } from 'vs/editor/common/core/range';
2322
import { Selection } from 'vs/editor/common/core/selection';
@@ -32,7 +31,7 @@ import { RemoveBreakpointAction, EditConditionalBreakpointAction, EnableBreakpoi
3231
import { IDebugEditorContribution, IDebugService, State, IBreakpoint, EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, IStackFrame, IDebugConfiguration } from 'vs/workbench/parts/debug/common/debug';
3332
import { BreakpointWidget } from 'vs/workbench/parts/debug/browser/breakpointWidget';
3433
import { FloatingClickWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
35-
import { getNameValueMapFromScopeChildren, getDecorators, getEditorWordRangeMap } from 'vs/workbench/parts/debug/electron-browser/debugInlineDecorators';
34+
import { toNameValueMap, getDecorations, getWordToLineNumbersMap } from 'vs/workbench/parts/debug/electron-browser/debugInlineValues';
3635

3736
const HOVER_DELAY = 300;
3837
const LAUNCH_JSON_REGEX = /launch\.json$/;
@@ -52,7 +51,7 @@ export class DebugEditorContribution implements IDebugEditorContribution {
5251
private breakpointWidget: BreakpointWidget;
5352
private breakpointWidgetVisible: IContextKey<boolean>;
5453
private removeDecorationsTimeoutId = 0;
55-
private editorModelWordRangeMap: IStringDictionary<IRange[]>;
54+
private wordToLineNumbersMap: Map<string, number[]>;
5655

5756
private configurationWidget: FloatingClickWidget;
5857

@@ -226,14 +225,14 @@ export class DebugEditorContribution implements IDebugEditorContribution {
226225
if (!stackFrame) {
227226
this.removeDecorationsTimeoutId = setTimeout(() => {
228227
this.editor.removeDecorations(INLINE_DECORATOR_KEY);
229-
this.editorModelWordRangeMap = null;
228+
this.wordToLineNumbersMap = null;
230229
}, REMOVE_DECORATORS_DEBOUNCE_INTERVAL);
231230
return;
232231
}
233232

234233
// URI has changed, invalidate the editorWordRangeMap so its re-computed for the current model
235234
if (stackFrame.source.uri.toString() !== this.editor.getModel().uri.toString()) {
236-
this.editorModelWordRangeMap = null;
235+
this.wordToLineNumbersMap = null;
237236
}
238237

239238
stackFrame.getScopes()
@@ -243,13 +242,13 @@ export class DebugEditorContribution implements IDebugEditorContribution {
243242
const editorModel = this.editor.getModel();
244243
// Compute name-value map for all variables in scope chain
245244
const expressions = [].concat.apply([], children);
246-
const nameValueMap = getNameValueMapFromScopeChildren(expressions);
245+
const nameValueMap = toNameValueMap(expressions);
247246
// Build wordRangeMap if not already computed for the editor model
248-
if (!this.editorModelWordRangeMap) {
249-
this.editorModelWordRangeMap = getEditorWordRangeMap(editorModel);
247+
if (!this.wordToLineNumbersMap) {
248+
this.wordToLineNumbersMap = getWordToLineNumbersMap(editorModel);
250249
}
251250
// Compute decorators from nameValueMap and wordRangeMap and apply to editor
252-
const decorators = getDecorators(nameValueMap, this.editorModelWordRangeMap, editorModel.getLinesContent());
251+
const decorators = getDecorations(nameValueMap, this.wordToLineNumbersMap);
253252
this.editor.setDecorations(INLINE_DECORATOR_KEY, decorators);
254253
});
255254
}

src/vs/workbench/parts/debug/electron-browser/debugInlineDecorators.ts

Lines changed: 0 additions & 166 deletions
This file was deleted.
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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 { IDecorationOptions, IModel } from 'vs/editor/common/editorCommon';
7+
import { StandardTokenType } from 'vs/editor/common/modes';
8+
import { IExpression } from 'vs/workbench/parts/debug/common/debug';
9+
10+
export const MAX_INLINE_VALUE_LENGTH = 50; // Max string length of each inline 'x = y' string. If exceeded ... is added
11+
export const MAX_INLINE_DECORATOR_LENGTH = 150; // Max string length of each inline decorator when debugging. If exceeded ... is added
12+
export const MAX_NUM_INLINE_VALUES = 100; // JS Global scope can have 700+ entries. We want to limit ourselves for perf reasons
13+
export const MAX_TOKENIZATION_LINE_LEN = 500; // If line is too long, then inline values for the line are skipped
14+
// LanguageConfigurationRegistry.getWordDefinition() return regexes that allow spaces and punctuation characters for languages like python
15+
// Using that approach is not viable so we are using a simple regex to look for word tokens.
16+
export const WORD_REGEXP = /[\$\_A-Za-z][\$\_A-Za-z0-9]*/g;
17+
18+
export function toNameValueMap(expressions: IExpression[]): Map<string, string> {
19+
const result = new Map<string, string>();
20+
let valueCount = 0;
21+
22+
for (let expr of expressions) {
23+
// Put ellipses in value if its too long. Preserve last char e.g "longstr…" or {a:true, b:true, …}
24+
let value = expr.value;
25+
if (value && value.length > MAX_INLINE_VALUE_LENGTH) {
26+
value = value.substr(0, MAX_INLINE_VALUE_LENGTH) + '…' + value[value.length - 1];
27+
}
28+
29+
result.set(expr.name, value);
30+
31+
// Limit the size of map. Too large can have a perf impact
32+
if (++valueCount >= MAX_NUM_INLINE_VALUES) {
33+
break;
34+
}
35+
}
36+
37+
return result;
38+
}
39+
40+
export function getDecorations(nameValueMap: Map<string, string>, wordToLineNumbersMap: Map<string, number[]>): IDecorationOptions[] {
41+
const lineToNamesMap: Map<number, string[]> = new Map<number, string[]>();
42+
const decorations: IDecorationOptions[] = [];
43+
44+
// Compute unique set of names on each line
45+
nameValueMap.forEach((value, name) => {
46+
if (wordToLineNumbersMap.has(name)) {
47+
for (let lineNumber of wordToLineNumbersMap.get(name)) {
48+
if (!lineToNamesMap.has(lineNumber)) {
49+
lineToNamesMap.set(lineNumber, []);
50+
}
51+
52+
lineToNamesMap.get(lineNumber).push(name);
53+
}
54+
}
55+
});
56+
57+
// Compute decorators for each line
58+
lineToNamesMap.forEach((names, line) => {
59+
// Wrap with 1em unicode space for readability
60+
const contentText = '\u2003' + names.map(name => `${name} = ${nameValueMap.get(name)}`).join(', ') + '\u2003';
61+
decorations.push(createDecoration(line, contentText));
62+
});
63+
64+
return decorations;
65+
}
66+
67+
function createDecoration(lineNumber: number, contentText: string): IDecorationOptions {
68+
const margin = '10px';
69+
const backgroundColor = 'rgba(255, 200, 0, 0.2)';
70+
const lightForegroundColor = 'rgba(0, 0, 0, 0.5)';
71+
const darkForegroundColor = 'rgba(255, 255, 255, 0.5)';
72+
73+
// If decoratorText is too long, trim and add ellipses. This could happen for minified files with everything on a single line
74+
if (contentText.length > MAX_INLINE_DECORATOR_LENGTH) {
75+
contentText = contentText.substr(0, MAX_INLINE_DECORATOR_LENGTH) + '...';
76+
}
77+
78+
return {
79+
range: {
80+
startLineNumber: lineNumber,
81+
endLineNumber: lineNumber,
82+
startColumn: Number.MAX_VALUE,
83+
endColumn: Number.MAX_VALUE
84+
},
85+
renderOptions: {
86+
dark: {
87+
after: {
88+
contentText,
89+
backgroundColor,
90+
color: darkForegroundColor,
91+
margin
92+
}
93+
},
94+
light: {
95+
after: {
96+
contentText,
97+
backgroundColor,
98+
color: lightForegroundColor,
99+
margin
100+
}
101+
}
102+
}
103+
};
104+
}
105+
106+
export function getWordToLineNumbersMap(model: IModel): Map<string, number[]> {
107+
const result = new Map<string, number[]>();
108+
109+
// For every word in every line, map its ranges for fast lookup
110+
for (let lineNumber = 1, len = model.getLineCount(); lineNumber <= len; ++lineNumber) {
111+
const lineContent = model.getLineContent(lineNumber);
112+
113+
// If line is too long then skip the line
114+
if (lineContent.length > MAX_TOKENIZATION_LINE_LEN) {
115+
continue;
116+
}
117+
118+
const lineTokens = model.getLineTokens(lineNumber);
119+
for (let token = lineTokens.firstToken(); !!token; token = token.next()) {
120+
const tokenStr = lineContent.substring(token.startOffset, token.endOffset);
121+
122+
// Token is a word and not a comment
123+
if (token.tokenType === StandardTokenType.Other) {
124+
WORD_REGEXP.lastIndex = 0; // We assume tokens will usually map 1:1 to words if they match
125+
const wordMatch = WORD_REGEXP.exec(tokenStr);
126+
127+
if (wordMatch) {
128+
const word = wordMatch[0];
129+
if (!result.has(word)) {
130+
result.set(word, []);
131+
}
132+
133+
result.get(word).push(lineNumber);
134+
}
135+
}
136+
}
137+
}
138+
139+
return result;
140+
}

0 commit comments

Comments
 (0)