Skip to content

Commit d953d84

Browse files
authored
debug: allow filter/search on debug values (#236768)
* debug: allow filter/search on debug values I think this is something that never worked, or at least not for a long while. Implementing match highlighting in values in the era of linkification and ANSI support was a little more complex, but this works well now. ![](https://memes.peet.io/img/24-12-b1f699dc-f20c-4c93-a5ce-f768473fff62.png) Fixes #230945 * fix test
1 parent d6a59b7 commit d953d84

9 files changed

+208
-39
lines changed

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

+29-1
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,21 @@ import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';
99
import { HighlightedLabel, IHighlight } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel.js';
1010
import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';
1111
import { IInputValidationOptions, InputBox } from '../../../../base/browser/ui/inputbox/inputBox.js';
12+
import { IKeyboardNavigationLabelProvider } from '../../../../base/browser/ui/list/list.js';
1213
import { IAsyncDataSource, ITreeNode, ITreeRenderer } from '../../../../base/browser/ui/tree/tree.js';
1314
import { Codicon } from '../../../../base/common/codicons.js';
1415
import { FuzzyScore, createMatches } from '../../../../base/common/filters.js';
1516
import { createSingleCallFunction } from '../../../../base/common/functional.js';
1617
import { KeyCode } from '../../../../base/common/keyCodes.js';
1718
import { DisposableStore, IDisposable, dispose, toDisposable } from '../../../../base/common/lifecycle.js';
19+
import { removeAnsiEscapeCodes } from '../../../../base/common/strings.js';
1820
import { ThemeIcon } from '../../../../base/common/themables.js';
1921
import { localize } from '../../../../nls.js';
2022
import { ICommandService } from '../../../../platform/commands/common/commands.js';
2123
import { IContextViewService } from '../../../../platform/contextview/browser/contextView.js';
2224
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
2325
import { defaultInputBoxStyles } from '../../../../platform/theme/browser/defaultStyles.js';
24-
import { IDebugService, IExpression } from '../common/debug.js';
26+
import { IDebugService, IExpression, IScope } from '../common/debug.js';
2527
import { Variable } from '../common/debugModel.js';
2628
import { IDebugVisualizerService } from '../common/debugVisualizers.js';
2729
import { LinkDetector } from './linkDetector.js';
@@ -78,6 +80,32 @@ export interface IExpressionTemplateData {
7880
currentElement: IExpression | undefined;
7981
}
8082

83+
/** Splits highlights based on matching of the {@link expressionAndScopeLabelProvider} */
84+
export const splitExpressionOrScopeHighlights = (e: IExpression | IScope, highlights: IHighlight[]) => {
85+
const nameEndsAt = e.name.length;
86+
const labelBeginsAt = e.name.length + 2;
87+
const name: IHighlight[] = [];
88+
const value: IHighlight[] = [];
89+
for (const hl of highlights) {
90+
if (hl.start < nameEndsAt) {
91+
name.push({ start: hl.start, end: Math.min(hl.end, nameEndsAt) });
92+
}
93+
if (hl.end > labelBeginsAt) {
94+
value.push({ start: Math.max(hl.start - labelBeginsAt, 0), end: hl.end - labelBeginsAt });
95+
}
96+
}
97+
98+
return { name, value };
99+
};
100+
101+
/** Keyboard label provider for expression and scope tree elements. */
102+
export const expressionAndScopeLabelProvider: IKeyboardNavigationLabelProvider<IExpression | IScope> = {
103+
getKeyboardNavigationLabel(e) {
104+
const stripAnsi = e.getSession()?.rememberedCapabilities?.supportsANSIStyling;
105+
return `${e.name}: ${stripAnsi ? removeAnsiEscapeCodes(e.value) : e.value}`;
106+
},
107+
};
108+
81109
export abstract class AbstractExpressionDataSource<Input, Element extends IExpression> implements IAsyncDataSource<Input, Element> {
82110
constructor(
83111
@IDebugService protected debugService: IDebugService,

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

+22-7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { IHighlight } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel.js';
67
import { Color, RGBA } from '../../../../base/common/color.js';
78
import { isDefined } from '../../../../base/common/types.js';
89
import { editorHoverBackground, listActiveSelectionBackground, listFocusBackground, listInactiveFocusBackground, listInactiveSelectionBackground } from '../../../../platform/theme/common/colorRegistry.js';
@@ -16,7 +17,7 @@ import { ILinkDetector } from './linkDetector.js';
1617
* @param text The content to stylize.
1718
* @returns An {@link HTMLSpanElement} that contains the potentially stylized text.
1819
*/
19-
export function handleANSIOutput(text: string, linkDetector: ILinkDetector, workspaceFolder: IWorkspaceFolder | undefined): HTMLSpanElement {
20+
export function handleANSIOutput(text: string, linkDetector: ILinkDetector, workspaceFolder: IWorkspaceFolder | undefined, highlights: IHighlight[] | undefined): HTMLSpanElement {
2021

2122
const root: HTMLSpanElement = document.createElement('span');
2223
const textLength: number = text.length;
@@ -27,6 +28,7 @@ export function handleANSIOutput(text: string, linkDetector: ILinkDetector, work
2728
let customUnderlineColor: RGBA | string | undefined;
2829
let colorsInverted: boolean = false;
2930
let currentPos: number = 0;
31+
let unprintedChars = 0;
3032
let buffer: string = '';
3133

3234
while (currentPos < textLength) {
@@ -58,8 +60,10 @@ export function handleANSIOutput(text: string, linkDetector: ILinkDetector, work
5860

5961
if (sequenceFound) {
6062

63+
unprintedChars += 2 + ansiSequence.length;
64+
6165
// Flush buffer with previous styles.
62-
appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, workspaceFolder, customFgColor, customBgColor, customUnderlineColor);
66+
appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, workspaceFolder, customFgColor, customBgColor, customUnderlineColor, highlights, currentPos - buffer.length - unprintedChars);
6367

6468
buffer = '';
6569

@@ -105,7 +109,7 @@ export function handleANSIOutput(text: string, linkDetector: ILinkDetector, work
105109

106110
// Flush remaining text buffer if not empty.
107111
if (buffer) {
108-
appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, workspaceFolder, customFgColor, customBgColor, customUnderlineColor);
112+
appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, workspaceFolder, customFgColor, customBgColor, customUnderlineColor, highlights, currentPos - buffer.length);
109113
}
110114

111115
return root;
@@ -395,22 +399,33 @@ export function handleANSIOutput(text: string, linkDetector: ILinkDetector, work
395399
* @param customTextColor If provided, will apply custom color with inline style.
396400
* @param customBackgroundColor If provided, will apply custom backgroundColor with inline style.
397401
* @param customUnderlineColor If provided, will apply custom textDecorationColor with inline style.
402+
* @param highlights The ranges to highlight.
403+
* @param offset The starting index of the stringContent in the original text.
398404
*/
399405
export function appendStylizedStringToContainer(
400406
root: HTMLElement,
401407
stringContent: string,
402408
cssClasses: string[],
403409
linkDetector: ILinkDetector,
404410
workspaceFolder: IWorkspaceFolder | undefined,
405-
customTextColor?: RGBA | string,
406-
customBackgroundColor?: RGBA | string,
407-
customUnderlineColor?: RGBA | string,
411+
customTextColor: RGBA | string | undefined,
412+
customBackgroundColor: RGBA | string | undefined,
413+
customUnderlineColor: RGBA | string | undefined,
414+
highlights: IHighlight[] | undefined,
415+
offset: number,
408416
): void {
409417
if (!root || !stringContent) {
410418
return;
411419
}
412420

413-
const container = linkDetector.linkify(stringContent, true, workspaceFolder);
421+
const container = linkDetector.linkify(
422+
stringContent,
423+
true,
424+
workspaceFolder,
425+
undefined,
426+
undefined,
427+
highlights?.map(h => ({ start: h.start - offset, end: h.end - offset, extraClasses: h.extraClasses })),
428+
);
414429

415430
container.className = cssClasses.join(' ');
416431
if (customTextColor) {

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

+8-5
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { observableConfigValue } from '../../../../platform/observable/common/pl
1616
import { IDebugSession, IExpressionValue } from '../common/debug.js';
1717
import { Expression, ExpressionContainer, Variable } from '../common/debugModel.js';
1818
import { ReplEvaluationResult } from '../common/replModel.js';
19-
import { IVariableTemplateData } from './baseDebugView.js';
19+
import { IVariableTemplateData, splitExpressionOrScopeHighlights } from './baseDebugView.js';
2020
import { handleANSIOutput } from './debugANSIHandling.js';
2121
import { COPY_EVALUATE_PATH_ID, COPY_VALUE_ID } from './debugCommands.js';
2222
import { DebugLinkHoverBehavior, DebugLinkHoverBehaviorTypeData, ILinkDetector, LinkDetector } from './linkDetector.js';
@@ -32,6 +32,7 @@ export interface IRenderValueOptions {
3232
/** If not false, a rich hover will be shown on the element. */
3333
hover?: false | IValueHoverOptions;
3434
colorize?: boolean;
35+
highlights?: IHighlight[];
3536

3637
/**
3738
* Indicates areas where VS Code implicitly always supported ANSI escape
@@ -90,6 +91,7 @@ export class DebugExpressionRenderer {
9091

9192
renderVariable(data: IVariableTemplateData, variable: Variable, options: IRenderVariableOptions = {}): IDisposable {
9293
const displayType = this.displayType.get();
94+
const highlights = splitExpressionOrScopeHighlights(variable, options.highlights || []);
9395

9496
if (variable.available) {
9597
data.type.textContent = '';
@@ -103,7 +105,7 @@ export class DebugExpressionRenderer {
103105
}
104106
}
105107

106-
data.label.set(text, options.highlights, variable.type && !displayType ? variable.type : variable.name);
108+
data.label.set(text, highlights.name, variable.type && !displayType ? variable.type : variable.name);
107109
data.name.classList.toggle('virtual', variable.presentationHint?.kind === 'virtual');
108110
data.name.classList.toggle('internal', variable.presentationHint?.visibility === 'internal');
109111
} else if (variable.value && typeof variable.name === 'string' && variable.name) {
@@ -122,6 +124,7 @@ export class DebugExpressionRenderer {
122124
showChanged: options.showChanged,
123125
maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET,
124126
hover: { commands },
127+
highlights: highlights.value,
125128
colorize: true,
126129
session: variable.getSession(),
127130
});
@@ -184,9 +187,9 @@ export class DebugExpressionRenderer {
184187
}
185188

186189
if (supportsANSI) {
187-
container.appendChild(handleANSIOutput(value, linkDetector, session ? session.root : undefined));
190+
container.appendChild(handleANSIOutput(value, linkDetector, session ? session.root : undefined, options.highlights));
188191
} else {
189-
container.appendChild(linkDetector.linkify(value, false, session?.root, true, hoverBehavior));
192+
container.appendChild(linkDetector.linkify(value, false, session?.root, true, hoverBehavior, options.highlights));
190193
}
191194

192195
if (options.hover !== false) {
@@ -199,7 +202,7 @@ export class DebugExpressionRenderer {
199202
if (supportsANSI) {
200203
// note: intentionally using `this.linkDetector` so we don't blindly linkify the
201204
// entire contents and instead only link file paths that it contains.
202-
hoverContentsPre.appendChild(handleANSIOutput(value, this.linkDetector, session ? session.root : undefined));
205+
hoverContentsPre.appendChild(handleANSIOutput(value, this.linkDetector, session ? session.root : undefined, options.highlights));
203206
} else {
204207
hoverContentsPre.textContent = value;
205208
}

0 commit comments

Comments
 (0)