Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/debug/browser/loadedScriptsView.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 { IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js';
7
import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js';
8
import { TreeFindMode } from '../../../../base/browser/ui/tree/abstractTree.js';
9
import type { ICompressedTreeNode } from '../../../../base/browser/ui/tree/compressedObjectTreeModel.js';
10
import type { ICompressibleTreeRenderer } from '../../../../base/browser/ui/tree/objectTree.js';
11
import { ITreeElement, ITreeFilter, ITreeNode, TreeFilterResult, TreeVisibility } from '../../../../base/browser/ui/tree/tree.js';
12
import { RunOnceScheduler } from '../../../../base/common/async.js';
13
import { Codicon } from '../../../../base/common/codicons.js';
14
import { createMatches, FuzzyScore } from '../../../../base/common/filters.js';
15
import { normalizeDriveLetter, tildify } from '../../../../base/common/labels.js';
16
import { dispose } from '../../../../base/common/lifecycle.js';
17
import { isAbsolute, normalize, posix } from '../../../../base/common/path.js';
18
import { isWindows } from '../../../../base/common/platform.js';
19
import { ltrim } from '../../../../base/common/strings.js';
20
import { URI } from '../../../../base/common/uri.js';
21
import * as nls from '../../../../nls.js';
22
import { MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';
23
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
24
import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
25
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
26
import { FileKind } from '../../../../platform/files/common/files.js';
27
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
28
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
29
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
30
import { ILabelService } from '../../../../platform/label/common/label.js';
31
import { WorkbenchCompressibleObjectTree } from '../../../../platform/list/browser/listService.js';
32
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
33
import { IFileIconTheme, IThemeService } from '../../../../platform/theme/common/themeService.js';
34
import { IWorkspaceContextService, IWorkspaceFolder } from '../../../../platform/workspace/common/workspace.js';
35
import { IResourceLabel, IResourceLabelOptions, IResourceLabelProps, ResourceLabels } from '../../../browser/labels.js';
36
import { ViewAction, ViewPane } from '../../../browser/parts/views/viewPane.js';
37
import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js';
38
import { IViewDescriptorService } from '../../../common/views.js';
39
import { IEditorService } from '../../../services/editor/common/editorService.js';
40
import { IPathService } from '../../../services/path/common/pathService.js';
41
import { CONTEXT_LOADED_SCRIPTS_ITEM_TYPE, IDebugService, IDebugSession, LOADED_SCRIPTS_VIEW_ID } from '../common/debug.js';
42
import { DebugContentProvider } from '../common/debugContentProvider.js';
43
import { Source } from '../common/debugSource.js';
44
import { renderViewTree } from './baseDebugView.js';
45
46
const NEW_STYLE_COMPRESS = true;
47
48
// RFC 2396, Appendix A: https://www.ietf.org/rfc/rfc2396.txt
49
const URI_SCHEMA_PATTERN = /^[a-zA-Z][a-zA-Z0-9\+\-\.]+:/;
50
51
type LoadedScriptsItem = BaseTreeItem;
52
53
class BaseTreeItem {
54
55
private _showedMoreThanOne: boolean;
56
private _children = new Map<string, BaseTreeItem>();
57
private _source: Source | undefined;
58
59
constructor(private _parent: BaseTreeItem | undefined, private _label: string, public readonly isIncompressible = false) {
60
this._showedMoreThanOne = false;
61
}
62
63
updateLabel(label: string) {
64
this._label = label;
65
}
66
67
isLeaf(): boolean {
68
return this._children.size === 0;
69
}
70
71
getSession(): IDebugSession | undefined {
72
if (this._parent) {
73
return this._parent.getSession();
74
}
75
return undefined;
76
}
77
78
setSource(session: IDebugSession, source: Source): void {
79
this._source = source;
80
this._children.clear();
81
if (source.raw && source.raw.sources) {
82
for (const src of source.raw.sources) {
83
if (src.name && src.path) {
84
const s = new BaseTreeItem(this, src.name);
85
this._children.set(src.path, s);
86
const ss = session.getSource(src);
87
s.setSource(session, ss);
88
}
89
}
90
}
91
}
92
93
createIfNeeded<T extends BaseTreeItem>(key: string, factory: (parent: BaseTreeItem, label: string) => T): T {
94
let child = <T>this._children.get(key);
95
if (!child) {
96
child = factory(this, key);
97
this._children.set(key, child);
98
}
99
return child;
100
}
101
102
getChild(key: string): BaseTreeItem | undefined {
103
return this._children.get(key);
104
}
105
106
remove(key: string): void {
107
this._children.delete(key);
108
}
109
110
removeFromParent(): void {
111
if (this._parent) {
112
this._parent.remove(this._label);
113
if (this._parent._children.size === 0) {
114
this._parent.removeFromParent();
115
}
116
}
117
}
118
119
getTemplateId(): string {
120
return 'id';
121
}
122
123
// a dynamic ID based on the parent chain; required for reparenting (see #55448)
124
getId(): string {
125
const parent = this.getParent();
126
return parent ? `${parent.getId()}/${this.getInternalId()}` : this.getInternalId();
127
}
128
129
getInternalId(): string {
130
return this._label;
131
}
132
133
// skips intermediate single-child nodes
134
getParent(): BaseTreeItem | undefined {
135
if (this._parent) {
136
if (this._parent.isSkipped()) {
137
return this._parent.getParent();
138
}
139
return this._parent;
140
}
141
return undefined;
142
}
143
144
isSkipped(): boolean {
145
if (this._parent) {
146
if (this._parent.oneChild()) {
147
return true; // skipped if I'm the only child of my parents
148
}
149
return false;
150
}
151
return true; // roots are never skipped
152
}
153
154
// skips intermediate single-child nodes
155
hasChildren(): boolean {
156
const child = this.oneChild();
157
if (child) {
158
return child.hasChildren();
159
}
160
return this._children.size > 0;
161
}
162
163
// skips intermediate single-child nodes
164
getChildren(): BaseTreeItem[] {
165
const child = this.oneChild();
166
if (child) {
167
return child.getChildren();
168
}
169
const array: BaseTreeItem[] = [];
170
for (const child of this._children.values()) {
171
array.push(child);
172
}
173
return array.sort((a, b) => this.compare(a, b));
174
}
175
176
// skips intermediate single-child nodes
177
getLabel(separateRootFolder = true): string {
178
const child = this.oneChild();
179
if (child) {
180
const sep = (this instanceof RootFolderTreeItem && separateRootFolder) ? ' • ' : posix.sep;
181
return `${this._label}${sep}${child.getLabel()}`;
182
}
183
return this._label;
184
}
185
186
// skips intermediate single-child nodes
187
getHoverLabel(): string | undefined {
188
if (this._source && this._parent && this._parent._source) {
189
return this._source.raw.path || this._source.raw.name;
190
}
191
const label = this.getLabel(false);
192
const parent = this.getParent();
193
if (parent) {
194
const hover = parent.getHoverLabel();
195
if (hover) {
196
return `${hover}/${label}`;
197
}
198
}
199
return label;
200
}
201
202
// skips intermediate single-child nodes
203
getSource(): Source | undefined {
204
const child = this.oneChild();
205
if (child) {
206
return child.getSource();
207
}
208
return this._source;
209
}
210
211
protected compare(a: BaseTreeItem, b: BaseTreeItem): number {
212
if (a._label && b._label) {
213
return a._label.localeCompare(b._label);
214
}
215
return 0;
216
}
217
218
private oneChild(): BaseTreeItem | undefined {
219
if (!this._source && !this._showedMoreThanOne && this.skipOneChild()) {
220
if (this._children.size === 1) {
221
return this._children.values().next().value;
222
}
223
// if a node had more than one child once, it will never be skipped again
224
if (this._children.size > 1) {
225
this._showedMoreThanOne = true;
226
}
227
}
228
return undefined;
229
}
230
231
private skipOneChild(): boolean {
232
if (NEW_STYLE_COMPRESS) {
233
// if the root node has only one Session, don't show the session
234
return this instanceof RootTreeItem;
235
} else {
236
return !(this instanceof RootFolderTreeItem) && !(this instanceof SessionTreeItem);
237
}
238
}
239
}
240
241
class RootFolderTreeItem extends BaseTreeItem {
242
243
constructor(parent: BaseTreeItem, public folder: IWorkspaceFolder) {
244
super(parent, folder.name, true);
245
}
246
}
247
248
class RootTreeItem extends BaseTreeItem {
249
250
constructor(private _pathService: IPathService, private _contextService: IWorkspaceContextService, private _labelService: ILabelService) {
251
super(undefined, 'Root');
252
}
253
254
add(session: IDebugSession): SessionTreeItem {
255
return this.createIfNeeded(session.getId(), () => new SessionTreeItem(this._labelService, this, session, this._pathService, this._contextService));
256
}
257
258
find(session: IDebugSession): SessionTreeItem {
259
return <SessionTreeItem>this.getChild(session.getId());
260
}
261
}
262
263
class SessionTreeItem extends BaseTreeItem {
264
265
private static readonly URL_REGEXP = /^(https?:\/\/[^/]+)(\/.*)$/;
266
267
private _session: IDebugSession;
268
private _map = new Map<string, BaseTreeItem>();
269
private _labelService: ILabelService;
270
271
constructor(labelService: ILabelService, parent: BaseTreeItem, session: IDebugSession, private _pathService: IPathService, private rootProvider: IWorkspaceContextService) {
272
super(parent, session.getLabel(), true);
273
this._labelService = labelService;
274
this._session = session;
275
}
276
277
override getInternalId(): string {
278
return this._session.getId();
279
}
280
281
override getSession(): IDebugSession {
282
return this._session;
283
}
284
285
override getHoverLabel(): string | undefined {
286
return undefined;
287
}
288
289
override hasChildren(): boolean {
290
return true;
291
}
292
293
protected override compare(a: BaseTreeItem, b: BaseTreeItem): number {
294
const acat = this.category(a);
295
const bcat = this.category(b);
296
if (acat !== bcat) {
297
return acat - bcat;
298
}
299
return super.compare(a, b);
300
}
301
302
private category(item: BaseTreeItem): number {
303
304
// workspace scripts come at the beginning in "folder" order
305
if (item instanceof RootFolderTreeItem) {
306
return item.folder.index;
307
}
308
309
// <...> come at the very end
310
const l = item.getLabel();
311
if (l && /^<.+>$/.test(l)) {
312
return 1000;
313
}
314
315
// everything else in between
316
return 999;
317
}
318
319
async addPath(source: Source): Promise<void> {
320
321
let folder: IWorkspaceFolder | null;
322
let url: string;
323
324
let path = source.raw.path;
325
if (!path) {
326
return;
327
}
328
329
if (this._labelService && URI_SCHEMA_PATTERN.test(path)) {
330
path = this._labelService.getUriLabel(URI.parse(path));
331
}
332
333
const match = SessionTreeItem.URL_REGEXP.exec(path);
334
if (match && match.length === 3) {
335
url = match[1];
336
path = decodeURI(match[2]);
337
} else {
338
if (isAbsolute(path)) {
339
const resource = URI.file(path);
340
341
// return early if we can resolve a relative path label from the root folder
342
folder = this.rootProvider ? this.rootProvider.getWorkspaceFolder(resource) : null;
343
if (folder) {
344
// strip off the root folder path
345
path = normalize(ltrim(resource.path.substring(folder.uri.path.length), posix.sep));
346
const hasMultipleRoots = this.rootProvider.getWorkspace().folders.length > 1;
347
if (hasMultipleRoots) {
348
path = posix.sep + path;
349
} else {
350
// don't show root folder
351
folder = null;
352
}
353
} else {
354
// on unix try to tildify absolute paths
355
path = normalize(path);
356
if (isWindows) {
357
path = normalizeDriveLetter(path);
358
} else {
359
path = tildify(path, (await this._pathService.userHome()).fsPath);
360
}
361
}
362
}
363
}
364
365
let leaf: BaseTreeItem = this;
366
path.split(/[\/\\]/).forEach((segment, i) => {
367
if (i === 0 && folder) {
368
const f = folder;
369
leaf = leaf.createIfNeeded(folder.name, parent => new RootFolderTreeItem(parent, f));
370
} else if (i === 0 && url) {
371
leaf = leaf.createIfNeeded(url, parent => new BaseTreeItem(parent, url));
372
} else {
373
leaf = leaf.createIfNeeded(segment, parent => new BaseTreeItem(parent, segment));
374
}
375
});
376
377
leaf.setSource(this._session, source);
378
if (source.raw.path) {
379
this._map.set(source.raw.path, leaf);
380
}
381
}
382
383
removePath(source: Source): boolean {
384
if (source.raw.path) {
385
const leaf = this._map.get(source.raw.path);
386
if (leaf) {
387
leaf.removeFromParent();
388
return true;
389
}
390
}
391
return false;
392
}
393
}
394
395
interface IViewState {
396
readonly expanded: Set<string>;
397
}
398
399
/**
400
* This maps a model item into a view model item.
401
*/
402
function asTreeElement(item: BaseTreeItem, viewState?: IViewState): ITreeElement<LoadedScriptsItem> {
403
const children = item.getChildren();
404
const collapsed = viewState ? !viewState.expanded.has(item.getId()) : !(item instanceof SessionTreeItem);
405
406
return {
407
element: item,
408
collapsed,
409
collapsible: item.hasChildren(),
410
children: children.map(i => asTreeElement(i, viewState))
411
};
412
}
413
414
export class LoadedScriptsView extends ViewPane {
415
416
private treeContainer!: HTMLElement;
417
private loadedScriptsItemType: IContextKey<string>;
418
private tree!: WorkbenchCompressibleObjectTree<LoadedScriptsItem, FuzzyScore>;
419
private treeLabels!: ResourceLabels;
420
private changeScheduler!: RunOnceScheduler;
421
private treeNeedsRefreshOnVisible = false;
422
private filter!: LoadedScriptsFilter;
423
424
constructor(
425
options: IViewletViewOptions,
426
@IContextMenuService contextMenuService: IContextMenuService,
427
@IKeybindingService keybindingService: IKeybindingService,
428
@IInstantiationService instantiationService: IInstantiationService,
429
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
430
@IConfigurationService configurationService: IConfigurationService,
431
@IEditorService private readonly editorService: IEditorService,
432
@IContextKeyService contextKeyService: IContextKeyService,
433
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
434
@IDebugService private readonly debugService: IDebugService,
435
@ILabelService private readonly labelService: ILabelService,
436
@IPathService private readonly pathService: IPathService,
437
@IOpenerService openerService: IOpenerService,
438
@IThemeService themeService: IThemeService,
439
@IHoverService hoverService: IHoverService,
440
) {
441
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);
442
this.loadedScriptsItemType = CONTEXT_LOADED_SCRIPTS_ITEM_TYPE.bindTo(contextKeyService);
443
}
444
445
protected override renderBody(container: HTMLElement): void {
446
super.renderBody(container);
447
448
this.element.classList.add('debug-pane');
449
container.classList.add('debug-loaded-scripts', 'show-file-icons');
450
451
this.treeContainer = renderViewTree(container);
452
453
this.filter = new LoadedScriptsFilter();
454
455
const root = new RootTreeItem(this.pathService, this.contextService, this.labelService);
456
457
this.treeLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility });
458
this._register(this.treeLabels);
459
460
const onFileIconThemeChange = (fileIconTheme: IFileIconTheme) => {
461
this.treeContainer.classList.toggle('align-icons-and-twisties', fileIconTheme.hasFileIcons && !fileIconTheme.hasFolderIcons);
462
this.treeContainer.classList.toggle('hide-arrows', fileIconTheme.hidesExplorerArrows === true);
463
};
464
465
this._register(this.themeService.onDidFileIconThemeChange(onFileIconThemeChange));
466
onFileIconThemeChange(this.themeService.getFileIconTheme());
467
468
this.tree = this.instantiationService.createInstance(WorkbenchCompressibleObjectTree<LoadedScriptsItem, FuzzyScore>,
469
'LoadedScriptsView',
470
this.treeContainer,
471
new LoadedScriptsDelegate(),
472
[new LoadedScriptsRenderer(this.treeLabels)],
473
{
474
compressionEnabled: NEW_STYLE_COMPRESS,
475
collapseByDefault: true,
476
hideTwistiesOfChildlessElements: true,
477
identityProvider: {
478
getId: (element: LoadedScriptsItem) => element.getId()
479
},
480
keyboardNavigationLabelProvider: {
481
getKeyboardNavigationLabel: (element: LoadedScriptsItem) => {
482
return element.getLabel();
483
},
484
getCompressedNodeKeyboardNavigationLabel: (elements: LoadedScriptsItem[]) => {
485
return elements.map(e => e.getLabel()).join('/');
486
}
487
},
488
filter: this.filter,
489
accessibilityProvider: new LoadedSciptsAccessibilityProvider(),
490
overrideStyles: this.getLocationBasedColors().listOverrideStyles
491
}
492
);
493
494
const updateView = (viewState?: IViewState) => this.tree.setChildren(null, asTreeElement(root, viewState).children);
495
496
updateView();
497
498
this.changeScheduler = new RunOnceScheduler(() => {
499
this.treeNeedsRefreshOnVisible = false;
500
if (this.tree) {
501
updateView();
502
}
503
}, 300);
504
this._register(this.changeScheduler);
505
506
this._register(this.tree.onDidOpen(e => {
507
if (e.element instanceof BaseTreeItem) {
508
const source = e.element.getSource();
509
if (source && source.available) {
510
const nullRange = { startLineNumber: 0, startColumn: 0, endLineNumber: 0, endColumn: 0 };
511
source.openInEditor(this.editorService, nullRange, e.editorOptions.preserveFocus, e.sideBySide, e.editorOptions.pinned);
512
}
513
}
514
}));
515
516
this._register(this.tree.onDidChangeFocus(() => {
517
const focus = this.tree.getFocus();
518
if (focus instanceof SessionTreeItem) {
519
this.loadedScriptsItemType.set('session');
520
} else {
521
this.loadedScriptsItemType.reset();
522
}
523
}));
524
525
const scheduleRefreshOnVisible = () => {
526
if (this.isBodyVisible()) {
527
this.changeScheduler.schedule();
528
} else {
529
this.treeNeedsRefreshOnVisible = true;
530
}
531
};
532
533
const addSourcePathsToSession = async (session: IDebugSession) => {
534
if (session.capabilities.supportsLoadedSourcesRequest) {
535
const sessionNode = root.add(session);
536
const paths = await session.getLoadedSources();
537
for (const path of paths) {
538
await sessionNode.addPath(path);
539
}
540
scheduleRefreshOnVisible();
541
}
542
};
543
544
const registerSessionListeners = (session: IDebugSession) => {
545
this._register(session.onDidChangeName(async () => {
546
const sessionRoot = root.find(session);
547
if (sessionRoot) {
548
sessionRoot.updateLabel(session.getLabel());
549
scheduleRefreshOnVisible();
550
}
551
}));
552
this._register(session.onDidLoadedSource(async event => {
553
let sessionRoot: SessionTreeItem;
554
switch (event.reason) {
555
case 'new':
556
case 'changed':
557
sessionRoot = root.add(session);
558
await sessionRoot.addPath(event.source);
559
scheduleRefreshOnVisible();
560
if (event.reason === 'changed') {
561
DebugContentProvider.refreshDebugContent(event.source.uri);
562
}
563
break;
564
case 'removed':
565
sessionRoot = root.find(session);
566
if (sessionRoot && sessionRoot.removePath(event.source)) {
567
scheduleRefreshOnVisible();
568
}
569
break;
570
default:
571
this.filter.setFilter(event.source.name);
572
this.tree.refilter();
573
break;
574
}
575
}));
576
};
577
578
this._register(this.debugService.onDidNewSession(registerSessionListeners));
579
this.debugService.getModel().getSessions().forEach(registerSessionListeners);
580
581
this._register(this.debugService.onDidEndSession(({ session }) => {
582
root.remove(session.getId());
583
this.changeScheduler.schedule();
584
}));
585
586
this.changeScheduler.schedule(0);
587
588
this._register(this.onDidChangeBodyVisibility(visible => {
589
if (visible && this.treeNeedsRefreshOnVisible) {
590
this.changeScheduler.schedule();
591
}
592
}));
593
594
// feature: expand all nodes when filtering (not when finding)
595
let viewState: IViewState | undefined;
596
this._register(this.tree.onDidChangeFindPattern(pattern => {
597
if (this.tree.findMode === TreeFindMode.Highlight) {
598
return;
599
}
600
601
if (!viewState && pattern) {
602
const expanded = new Set<string>();
603
const visit = (node: ITreeNode<BaseTreeItem | null, FuzzyScore>) => {
604
if (node.element && !node.collapsed) {
605
expanded.add(node.element.getId());
606
}
607
608
for (const child of node.children) {
609
visit(child);
610
}
611
};
612
613
visit(this.tree.getNode());
614
viewState = { expanded };
615
this.tree.expandAll();
616
} else if (!pattern && viewState) {
617
this.tree.setFocus([]);
618
updateView(viewState);
619
viewState = undefined;
620
}
621
}));
622
623
// populate tree model with source paths from all debug sessions
624
this.debugService.getModel().getSessions().forEach(session => addSourcePathsToSession(session));
625
}
626
627
protected override layoutBody(height: number, width: number): void {
628
super.layoutBody(height, width);
629
this.tree.layout(height, width);
630
}
631
632
collapseAll(): void {
633
this.tree.collapseAll();
634
}
635
636
override dispose(): void {
637
dispose(this.tree);
638
dispose(this.treeLabels);
639
super.dispose();
640
}
641
}
642
643
class LoadedScriptsDelegate implements IListVirtualDelegate<LoadedScriptsItem> {
644
645
getHeight(element: LoadedScriptsItem): number {
646
return 22;
647
}
648
649
getTemplateId(element: LoadedScriptsItem): string {
650
return LoadedScriptsRenderer.ID;
651
}
652
}
653
654
interface ILoadedScriptsItemTemplateData {
655
label: IResourceLabel;
656
}
657
658
class LoadedScriptsRenderer implements ICompressibleTreeRenderer<BaseTreeItem, FuzzyScore, ILoadedScriptsItemTemplateData> {
659
660
static readonly ID = 'lsrenderer';
661
662
constructor(
663
private labels: ResourceLabels
664
) {
665
}
666
667
get templateId(): string {
668
return LoadedScriptsRenderer.ID;
669
}
670
671
renderTemplate(container: HTMLElement): ILoadedScriptsItemTemplateData {
672
const label = this.labels.create(container, { supportHighlights: true });
673
return { label };
674
}
675
676
renderElement(node: ITreeNode<BaseTreeItem, FuzzyScore>, index: number, data: ILoadedScriptsItemTemplateData): void {
677
678
const element = node.element;
679
const label = element.getLabel();
680
681
this.render(element, label, data, node.filterData);
682
}
683
684
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<BaseTreeItem>, FuzzyScore>, index: number, data: ILoadedScriptsItemTemplateData): void {
685
686
const element = node.element.elements[node.element.elements.length - 1];
687
const labels = node.element.elements.map(e => e.getLabel());
688
689
this.render(element, labels, data, node.filterData);
690
}
691
692
private render(element: BaseTreeItem, labels: string | string[], data: ILoadedScriptsItemTemplateData, filterData: FuzzyScore | undefined) {
693
694
const label: IResourceLabelProps = {
695
name: labels
696
};
697
const options: IResourceLabelOptions = {
698
title: element.getHoverLabel()
699
};
700
701
if (element instanceof RootFolderTreeItem) {
702
703
options.fileKind = FileKind.ROOT_FOLDER;
704
705
} else if (element instanceof SessionTreeItem) {
706
707
options.title = nls.localize('loadedScriptsSession', "Debug Session");
708
options.hideIcon = true;
709
710
} else if (element instanceof BaseTreeItem) {
711
712
const src = element.getSource();
713
if (src && src.uri) {
714
label.resource = src.uri;
715
options.fileKind = FileKind.FILE;
716
} else {
717
options.fileKind = FileKind.FOLDER;
718
}
719
}
720
options.matches = createMatches(filterData);
721
722
data.label.setResource(label, options);
723
}
724
725
disposeTemplate(templateData: ILoadedScriptsItemTemplateData): void {
726
templateData.label.dispose();
727
}
728
}
729
730
class LoadedSciptsAccessibilityProvider implements IListAccessibilityProvider<LoadedScriptsItem> {
731
732
getWidgetAriaLabel(): string {
733
return nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'loadedScriptsAriaLabel' }, "Debug Loaded Scripts");
734
}
735
736
getAriaLabel(element: LoadedScriptsItem): string {
737
738
if (element instanceof RootFolderTreeItem) {
739
return nls.localize('loadedScriptsRootFolderAriaLabel', "Workspace folder {0}, loaded script, debug", element.getLabel());
740
}
741
742
if (element instanceof SessionTreeItem) {
743
return nls.localize('loadedScriptsSessionAriaLabel', "Session {0}, loaded script, debug", element.getLabel());
744
}
745
746
if (element.hasChildren()) {
747
return nls.localize('loadedScriptsFolderAriaLabel', "Folder {0}, loaded script, debug", element.getLabel());
748
} else {
749
return nls.localize('loadedScriptsSourceAriaLabel', "{0}, loaded script, debug", element.getLabel());
750
}
751
}
752
}
753
754
class LoadedScriptsFilter implements ITreeFilter<BaseTreeItem, FuzzyScore> {
755
756
private filterText: string | undefined;
757
758
setFilter(filterText: string) {
759
this.filterText = filterText;
760
}
761
762
filter(element: BaseTreeItem, parentVisibility: TreeVisibility): TreeFilterResult<FuzzyScore> {
763
764
if (!this.filterText) {
765
return TreeVisibility.Visible;
766
}
767
768
if (element.isLeaf()) {
769
const name = element.getLabel();
770
if (name.indexOf(this.filterText) >= 0) {
771
return TreeVisibility.Visible;
772
}
773
return TreeVisibility.Hidden;
774
}
775
return TreeVisibility.Recurse;
776
}
777
}
778
registerAction2(class Collapse extends ViewAction<LoadedScriptsView> {
779
constructor() {
780
super({
781
id: 'loadedScripts.collapse',
782
viewId: LOADED_SCRIPTS_VIEW_ID,
783
title: nls.localize('collapse', "Collapse All"),
784
f1: false,
785
icon: Codicon.collapseAll,
786
menu: {
787
id: MenuId.ViewTitle,
788
order: 30,
789
group: 'navigation',
790
when: ContextKeyExpr.equals('view', LOADED_SCRIPTS_VIEW_ID)
791
}
792
});
793
}
794
795
runInView(_accessor: ServicesAccessor, view: LoadedScriptsView) {
796
view.collapseAll();
797
}
798
});
799
800