Skip to content

Commit 4b90413

Browse files
authored
Merge pull request microsoft#102704 from arhelmus/master
Implemented filter for debug console output
2 parents 84af918 + bbbc314 commit 4b90413

6 files changed

Lines changed: 385 additions & 2 deletions

File tree

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,14 @@ import { ReplGroup } from 'vs/workbench/contrib/debug/common/replModel';
5959
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
6060
import { EDITOR_FONT_DEFAULTS, EditorOption } from 'vs/editor/common/config/editorOptions';
6161
import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor';
62+
import { TreeFilterPanelActionViewItem, TreeFilterState } from 'vs/workbench/contrib/treeFilter/browser/treeFilterView';
63+
import { ReplFilter } from 'vs/workbench/contrib/debug/browser/replFilter';
6264

6365
const $ = dom.$;
6466

6567
const HISTORY_STORAGE_KEY = 'debug.repl.history';
6668
const DECORATION_KEY = 'replinputdecoration';
67-
69+
const FILTER_ACTION_ID = `workbench.actions.treeView.repl.filter`;
6870

6971
function revealLastElement(tree: WorkbenchAsyncDataTree<any, any, any>) {
7072
tree.scrollTop = tree.scrollHeight - tree.renderHeight;
@@ -93,6 +95,8 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
9395
private styleElement: HTMLStyleElement | undefined;
9496
private completionItemProvider: IDisposable | undefined;
9597
private modelChangeListener: IDisposable = Disposable.None;
98+
private filter: ReplFilter;
99+
private filterState: TreeFilterState;
96100

97101
constructor(
98102
options: IViewPaneOptions,
@@ -116,6 +120,13 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
116120
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
117121

118122
this.history = new HistoryNavigator(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')), 50);
123+
this.filter = new ReplFilter();
124+
this.filterState = this._register(new TreeFilterState({
125+
filterText: '',
126+
filterHistory: [],
127+
layout: new dom.Dimension(0, 0),
128+
}));
129+
119130
codeEditorService.registerDecorationType(DECORATION_KEY, {});
120131
this.registerListeners();
121132
}
@@ -237,6 +248,15 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
237248
this._register(this.editorService.onDidActiveEditorChange(() => {
238249
this.setMode();
239250
}));
251+
252+
this._register(this.filterState.onDidChange((e) => {
253+
if (e.filterText) {
254+
this.filter.filterQuery = this.filterState.filterText;
255+
if (this.tree) {
256+
this.tree.refilter();
257+
}
258+
}
259+
}));
240260
}
241261

242262
get isReadonly(): boolean {
@@ -428,6 +448,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
428448
this.replInputContainer.style.height = `${replInputHeight}px`;
429449

430450
this.replInput.layout({ width: width - 30, height: replInputHeight });
451+
this.filterState.layout = new dom.Dimension(width, height);
431452
}
432453

433454
focus(): void {
@@ -437,13 +458,16 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
437458
getActionViewItem(action: IAction): IActionViewItem | undefined {
438459
if (action.id === SelectReplAction.ID) {
439460
return this.instantiationService.createInstance(SelectReplActionViewItem, this.selectReplAction);
461+
} else if (action.id === FILTER_ACTION_ID) {
462+
return this.instantiationService.createInstance(TreeFilterPanelActionViewItem, action, localize('workbench.debug.filter.placeholder', "Filter. E.g.: text, !exclude"), this.filterState);
440463
}
441464

442465
return super.getActionViewItem(action);
443466
}
444467

445468
getActions(): IAction[] {
446469
const result: IAction[] = [];
470+
result.push(new Action(FILTER_ACTION_ID));
447471
if (this.debugService.getModel().getSessions(true).filter(s => s.hasSeparateRepl() && !sessionsToIgnore.has(s)).length > 1) {
448472
result.push(this.selectReplAction);
449473
}
@@ -532,6 +556,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
532556
// https://github.com/microsoft/TypeScript/issues/32526
533557
new ReplDataSource() as IAsyncDataSource<IDebugSession, IReplElement>,
534558
{
559+
filter: this.filter,
535560
accessibilityProvider: new ReplAccessibilityProvider(),
536561
identityProvider: { getId: (element: IReplElement) => element.getId() },
537562
mouseSupport: false,
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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 { matchesFuzzy } from 'vs/base/common/filters';
7+
import { splitGlobAware } from 'vs/base/common/glob';
8+
import * as strings from 'vs/base/common/strings';
9+
import { ITreeFilter, TreeVisibility, TreeFilterResult } from 'vs/base/browser/ui/tree/tree';
10+
import { IReplElement } from 'vs/workbench/contrib/debug/common/debug';
11+
12+
type ParsedQuery = {
13+
type: 'include' | 'exclude',
14+
query: string,
15+
};
16+
17+
export class ReplFilter implements ITreeFilter<IReplElement> {
18+
19+
static matchQuery = matchesFuzzy;
20+
21+
private _parsedQueries: ParsedQuery[] = [];
22+
set filterQuery(query: string) {
23+
this._parsedQueries = [];
24+
query = query.trim();
25+
26+
if (query && query !== '') {
27+
const filters = splitGlobAware(query, ',').map(s => s.trim()).filter(s => !!s.length);
28+
for (const f of filters) {
29+
if (strings.startsWith(f, '!')) {
30+
this._parsedQueries.push({ type: 'exclude', query: f.slice(1) });
31+
} else {
32+
this._parsedQueries.push({ type: 'include', query: f });
33+
}
34+
}
35+
}
36+
}
37+
38+
filter(element: IReplElement, parentVisibility: TreeVisibility): TreeFilterResult<void> {
39+
if (this._parsedQueries.length === 0) {
40+
return parentVisibility;
41+
}
42+
43+
let includeQueryPresent = false;
44+
let includeQueryMatched = false;
45+
46+
const text = element.toString();
47+
48+
for (let { type, query } of this._parsedQueries) {
49+
if (type === 'exclude' && ReplFilter.matchQuery(query, text)) {
50+
// If exclude query matches, ignore all other queries and hide
51+
return false;
52+
} else if (type === 'include') {
53+
includeQueryPresent = true;
54+
if (ReplFilter.matchQuery(query, text)) {
55+
includeQueryMatched = true;
56+
}
57+
}
58+
}
59+
60+
return includeQueryPresent ? includeQueryMatched : parentVisibility;
61+
}
62+
}

src/vs/workbench/contrib/debug/common/replModel.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,18 @@ export class ReplGroup implements IReplElement {
174174
}
175175
}
176176

177+
type FilterFunc = ((element: IReplElement) => void);
178+
177179
export class ReplModel {
178180
private replElements: IReplElement[] = [];
179181
private readonly _onDidChangeElements = new Emitter<void>();
180182
readonly onDidChangeElements = this._onDidChangeElements.event;
183+
private filterFunc: FilterFunc | undefined;
181184

182185
getReplElements(): IReplElement[] {
183-
return this.replElements;
186+
return this.replElements.filter(element =>
187+
this.filterFunc ? this.filterFunc(element) : true
188+
);
184189
}
185190

186191
async addReplExpression(session: IDebugSession, stackFrame: IStackFrame | undefined, name: string): Promise<void> {
@@ -315,6 +320,10 @@ export class ReplModel {
315320
}
316321
}
317322

323+
setFilter(filterFunc: FilterFunc): void {
324+
this.filterFunc = filterFunc;
325+
}
326+
318327
removeReplExpressions(): void {
319328
if (this.replElements.length > 0) {
320329
this.replElements = [];

src/vs/workbench/contrib/debug/test/browser/repl.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplModel
1212
import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSession';
1313
import { timeout } from 'vs/base/common/async';
1414
import { createMockSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test';
15+
import { ReplFilter } from 'vs/workbench/contrib/debug/browser/replFilter';
16+
import { TreeVisibility } from 'vs/base/browser/ui/tree/tree';
1517

1618
suite('Debug - REPL', () => {
1719
let model: DebugModel;
@@ -189,4 +191,59 @@ suite('Debug - REPL', () => {
189191
assert.equal(repl.getReplElements().length, 3);
190192
assert.equal((<SimpleReplElement>repl.getReplElements()[2]).value, 'second global line');
191193
});
194+
195+
test('repl filter', async () => {
196+
const session = createMockSession(model);
197+
const repl = new ReplModel();
198+
const replFilter = new ReplFilter();
199+
200+
repl.setFilter((element) => {
201+
const filterResult = replFilter.filter(element, TreeVisibility.Visible);
202+
return filterResult === true || filterResult === TreeVisibility.Visible;
203+
});
204+
205+
repl.appendToRepl(session, 'first line\n', severity.Info);
206+
repl.appendToRepl(session, 'second line\n', severity.Info);
207+
repl.appendToRepl(session, 'third line\n', severity.Info);
208+
repl.appendToRepl(session, 'fourth line\n', severity.Info);
209+
210+
replFilter.filterQuery = 'first';
211+
let r1 = <SimpleReplElement[]>repl.getReplElements();
212+
assert.equal(r1.length, 1);
213+
assert.equal(r1[0].value, 'first line\n');
214+
215+
replFilter.filterQuery = '!first';
216+
let r2 = <SimpleReplElement[]>repl.getReplElements();
217+
assert.equal(r1.length, 1);
218+
assert.equal(r2[0].value, 'second line\n');
219+
assert.equal(r2[1].value, 'third line\n');
220+
assert.equal(r2[2].value, 'fourth line\n');
221+
222+
replFilter.filterQuery = 'first, line';
223+
let r3 = <SimpleReplElement[]>repl.getReplElements();
224+
assert.equal(r3.length, 4);
225+
assert.equal(r3[0].value, 'first line\n');
226+
assert.equal(r3[1].value, 'second line\n');
227+
assert.equal(r3[2].value, 'third line\n');
228+
assert.equal(r3[3].value, 'fourth line\n');
229+
230+
replFilter.filterQuery = 'line, !second';
231+
let r4 = <SimpleReplElement[]>repl.getReplElements();
232+
assert.equal(r4.length, 3);
233+
assert.equal(r4[0].value, 'first line\n');
234+
assert.equal(r4[1].value, 'third line\n');
235+
assert.equal(r4[2].value, 'fourth line\n');
236+
237+
replFilter.filterQuery = '!second, line';
238+
let r4_same = <SimpleReplElement[]>repl.getReplElements();
239+
assert.equal(r4.length, r4_same.length);
240+
241+
replFilter.filterQuery = '!line';
242+
let r5 = <SimpleReplElement[]>repl.getReplElements();
243+
assert.equal(r5.length, 0);
244+
245+
replFilter.filterQuery = 'smth';
246+
let r6 = <SimpleReplElement[]>repl.getReplElements();
247+
assert.equal(r6.length, 0);
248+
});
192249
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
.monaco-action-bar .action-item.panel-action-tree-filter-container {
7+
cursor: default;
8+
display: flex;
9+
}
10+
11+
.monaco-action-bar .panel-action-tree-filter{
12+
display: flex;
13+
align-items: center;
14+
flex: 1;
15+
}
16+
17+
.monaco-action-bar .panel-action-tree-filter .monaco-inputbox {
18+
height: 24px;
19+
font-size: 12px;
20+
flex: 1;
21+
}
22+
23+
.pane-header .monaco-action-bar .panel-action-tree-filter .monaco-inputbox {
24+
height: 20px;
25+
line-height: 18px;
26+
}
27+
28+
.monaco-workbench.vs .monaco-action-bar .panel-action-tree-filter .monaco-inputbox {
29+
height: 25px;
30+
}
31+
32+
.panel > .title .monaco-action-bar .action-item.panel-action-tree-filter-container {
33+
max-width: 600px;
34+
min-width: 300px;
35+
margin-right: 10px;
36+
}
37+
38+
.monaco-action-bar .action-item.panel-action-tree-filter-container,
39+
.panel > .title .monaco-action-bar .action-item.panel-action-tree-filter-container.grow {
40+
flex: 1;
41+
}

0 commit comments

Comments
 (0)