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
5241 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, DisposableMap, DisposableStore } 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
// Track listeners per session to avoid leaking disposables
545
const sessionListeners = this._register(new DisposableMap<string, DisposableStore>());
546
547
const registerSessionListeners = (session: IDebugSession) => {
548
const store = new DisposableStore();
549
sessionListeners.set(session.getId(), store);
550
551
store.add(session.onDidChangeName(async () => {
552
const sessionRoot = root.find(session);
553
if (sessionRoot) {
554
sessionRoot.updateLabel(session.getLabel());
555
scheduleRefreshOnVisible();
556
}
557
}));
558
store.add(session.onDidLoadedSource(async event => {
559
let sessionRoot: SessionTreeItem;
560
switch (event.reason) {
561
case 'new':
562
case 'changed':
563
sessionRoot = root.add(session);
564
await sessionRoot.addPath(event.source);
565
scheduleRefreshOnVisible();
566
if (event.reason === 'changed') {
567
DebugContentProvider.refreshDebugContent(event.source.uri);
568
}
569
break;
570
case 'removed':
571
sessionRoot = root.find(session);
572
if (sessionRoot && sessionRoot.removePath(event.source)) {
573
scheduleRefreshOnVisible();
574
}
575
break;
576
default:
577
this.filter.setFilter(event.source.name);
578
this.tree.refilter();
579
break;
580
}
581
}));
582
};
583
584
this._register(this.debugService.onDidNewSession(registerSessionListeners));
585
this.debugService.getModel().getSessions().forEach(registerSessionListeners);
586
587
this._register(this.debugService.onDidEndSession(({ session }) => {
588
sessionListeners.deleteAndDispose(session.getId());
589
root.remove(session.getId());
590
this.changeScheduler.schedule();
591
}));
592
593
this.changeScheduler.schedule(0);
594
595
this._register(this.onDidChangeBodyVisibility(visible => {
596
if (visible && this.treeNeedsRefreshOnVisible) {
597
this.changeScheduler.schedule();
598
}
599
}));
600
601
// feature: expand all nodes when filtering (not when finding)
602
let viewState: IViewState | undefined;
603
this._register(this.tree.onDidChangeFindPattern(pattern => {
604
if (this.tree.findMode === TreeFindMode.Highlight) {
605
return;
606
}
607
608
if (!viewState && pattern) {
609
const expanded = new Set<string>();
610
const visit = (node: ITreeNode<BaseTreeItem | null, FuzzyScore>) => {
611
if (node.element && !node.collapsed) {
612
expanded.add(node.element.getId());
613
}
614
615
for (const child of node.children) {
616
visit(child);
617
}
618
};
619
620
visit(this.tree.getNode());
621
viewState = { expanded };
622
this.tree.expandAll();
623
} else if (!pattern && viewState) {
624
this.tree.setFocus([]);
625
updateView(viewState);
626
viewState = undefined;
627
}
628
}));
629
630
// populate tree model with source paths from all debug sessions
631
this.debugService.getModel().getSessions().forEach(session => addSourcePathsToSession(session));
632
}
633
634
protected override layoutBody(height: number, width: number): void {
635
super.layoutBody(height, width);
636
this.tree.layout(height, width);
637
}
638
639
collapseAll(): void {
640
this.tree.collapseAll();
641
}
642
643
override dispose(): void {
644
dispose(this.tree);
645
dispose(this.treeLabels);
646
super.dispose();
647
}
648
}
649
650
class LoadedScriptsDelegate implements IListVirtualDelegate<LoadedScriptsItem> {
651
652
getHeight(element: LoadedScriptsItem): number {
653
return 22;
654
}
655
656
getTemplateId(element: LoadedScriptsItem): string {
657
return LoadedScriptsRenderer.ID;
658
}
659
}
660
661
interface ILoadedScriptsItemTemplateData {
662
label: IResourceLabel;
663
}
664
665
class LoadedScriptsRenderer implements ICompressibleTreeRenderer<BaseTreeItem, FuzzyScore, ILoadedScriptsItemTemplateData> {
666
667
static readonly ID = 'lsrenderer';
668
669
constructor(
670
private labels: ResourceLabels
671
) {
672
}
673
674
get templateId(): string {
675
return LoadedScriptsRenderer.ID;
676
}
677
678
renderTemplate(container: HTMLElement): ILoadedScriptsItemTemplateData {
679
const label = this.labels.create(container, { supportHighlights: true });
680
return { label };
681
}
682
683
renderElement(node: ITreeNode<BaseTreeItem, FuzzyScore>, index: number, data: ILoadedScriptsItemTemplateData): void {
684
685
const element = node.element;
686
const label = element.getLabel();
687
688
this.render(element, label, data, node.filterData);
689
}
690
691
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<BaseTreeItem>, FuzzyScore>, index: number, data: ILoadedScriptsItemTemplateData): void {
692
693
const element = node.element.elements[node.element.elements.length - 1];
694
const labels = node.element.elements.map(e => e.getLabel());
695
696
this.render(element, labels, data, node.filterData);
697
}
698
699
private render(element: BaseTreeItem, labels: string | string[], data: ILoadedScriptsItemTemplateData, filterData: FuzzyScore | undefined) {
700
701
const label: IResourceLabelProps = {
702
name: labels
703
};
704
const options: IResourceLabelOptions = {
705
title: element.getHoverLabel()
706
};
707
708
if (element instanceof RootFolderTreeItem) {
709
710
options.fileKind = FileKind.ROOT_FOLDER;
711
712
} else if (element instanceof SessionTreeItem) {
713
714
options.title = nls.localize('loadedScriptsSession', "Debug Session");
715
options.hideIcon = true;
716
717
} else if (element instanceof BaseTreeItem) {
718
719
const src = element.getSource();
720
if (src && src.uri) {
721
label.resource = src.uri;
722
options.fileKind = FileKind.FILE;
723
} else {
724
options.fileKind = FileKind.FOLDER;
725
}
726
}
727
options.matches = createMatches(filterData);
728
729
data.label.setResource(label, options);
730
}
731
732
disposeTemplate(templateData: ILoadedScriptsItemTemplateData): void {
733
templateData.label.dispose();
734
}
735
}
736
737
class LoadedSciptsAccessibilityProvider implements IListAccessibilityProvider<LoadedScriptsItem> {
738
739
getWidgetAriaLabel(): string {
740
return nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'loadedScriptsAriaLabel' }, "Debug Loaded Scripts");
741
}
742
743
getAriaLabel(element: LoadedScriptsItem): string {
744
745
if (element instanceof RootFolderTreeItem) {
746
return nls.localize('loadedScriptsRootFolderAriaLabel', "Workspace folder {0}, loaded script, debug", element.getLabel());
747
}
748
749
if (element instanceof SessionTreeItem) {
750
return nls.localize('loadedScriptsSessionAriaLabel', "Session {0}, loaded script, debug", element.getLabel());
751
}
752
753
if (element.hasChildren()) {
754
return nls.localize('loadedScriptsFolderAriaLabel', "Folder {0}, loaded script, debug", element.getLabel());
755
} else {
756
return nls.localize('loadedScriptsSourceAriaLabel', "{0}, loaded script, debug", element.getLabel());
757
}
758
}
759
}
760
761
class LoadedScriptsFilter implements ITreeFilter<BaseTreeItem, FuzzyScore> {
762
763
private filterText: string | undefined;
764
765
setFilter(filterText: string) {
766
this.filterText = filterText;
767
}
768
769
filter(element: BaseTreeItem, parentVisibility: TreeVisibility): TreeFilterResult<FuzzyScore> {
770
771
if (!this.filterText) {
772
return TreeVisibility.Visible;
773
}
774
775
if (element.isLeaf()) {
776
const name = element.getLabel();
777
if (name.indexOf(this.filterText) >= 0) {
778
return TreeVisibility.Visible;
779
}
780
return TreeVisibility.Hidden;
781
}
782
return TreeVisibility.Recurse;
783
}
784
}
785
registerAction2(class Collapse extends ViewAction<LoadedScriptsView> {
786
constructor() {
787
super({
788
id: 'loadedScripts.collapse',
789
viewId: LOADED_SCRIPTS_VIEW_ID,
790
title: nls.localize('collapse', "Collapse All"),
791
f1: false,
792
icon: Codicon.collapseAll,
793
menu: {
794
id: MenuId.ViewTitle,
795
order: 30,
796
group: 'navigation',
797
when: ContextKeyExpr.equals('view', LOADED_SCRIPTS_VIEW_ID)
798
}
799
});
800
}
801
802
runInView(_accessor: ServicesAccessor, view: LoadedScriptsView) {
803
view.collapseAll();
804
}
805
});
806
807