Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/browser/actions/listCommands.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 { KeyMod, KeyCode, KeyChord } from '../../../base/common/keyCodes.js';
7
import { ServicesAccessor } from '../../../platform/instantiation/common/instantiation.js';
8
import { KeybindingsRegistry, KeybindingWeight } from '../../../platform/keybinding/common/keybindingsRegistry.js';
9
import { List } from '../../../base/browser/ui/list/listWidget.js';
10
import { WorkbenchListFocusContextKey, IListService, WorkbenchListSupportsMultiSelectContextKey, ListWidget, WorkbenchListHasSelectionOrFocus, getSelectionKeyboardEvent, WorkbenchListWidget, WorkbenchListSelectionNavigation, WorkbenchTreeElementCanCollapse, WorkbenchTreeElementHasParent, WorkbenchTreeElementHasChild, WorkbenchTreeElementCanExpand, RawWorkbenchListFocusContextKey, WorkbenchTreeFindOpen, WorkbenchListSupportsFind, WorkbenchListScrollAtBottomContextKey, WorkbenchListScrollAtTopContextKey, WorkbenchTreeStickyScrollFocused } from '../../../platform/list/browser/listService.js';
11
import { PagedList } from '../../../base/browser/ui/list/listPaging.js';
12
import { equals, range } from '../../../base/common/arrays.js';
13
import { ContextKeyExpr } from '../../../platform/contextkey/common/contextkey.js';
14
import { ObjectTree } from '../../../base/browser/ui/tree/objectTree.js';
15
import { AsyncDataTree } from '../../../base/browser/ui/tree/asyncDataTree.js';
16
import { DataTree } from '../../../base/browser/ui/tree/dataTree.js';
17
import { ITreeNode } from '../../../base/browser/ui/tree/tree.js';
18
import { CommandsRegistry } from '../../../platform/commands/common/commands.js';
19
import { Table } from '../../../base/browser/ui/table/tableWidget.js';
20
import { AbstractTree, TreeFindMatchType, TreeFindMode } from '../../../base/browser/ui/tree/abstractTree.js';
21
import { isActiveElement } from '../../../base/browser/dom.js';
22
import { Action2, registerAction2 } from '../../../platform/actions/common/actions.js';
23
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
24
import { localize, localize2 } from '../../../nls.js';
25
import { IHoverService } from '../../../platform/hover/browser/hover.js';
26
27
function ensureDOMFocus(widget: ListWidget | undefined): void {
28
// it can happen that one of the commands is executed while
29
// DOM focus is within another focusable control within the
30
// list/tree item. therefor we should ensure that the
31
// list/tree has DOM focus again after the command ran.
32
const element = widget?.getHTMLElement();
33
if (element && !isActiveElement(element)) {
34
widget?.domFocus();
35
}
36
}
37
38
async function updateFocus(widget: WorkbenchListWidget, updateFocusFn: (widget: WorkbenchListWidget) => void | Promise<void>): Promise<void> {
39
if (!WorkbenchListSelectionNavigation.getValue(widget.contextKeyService)) {
40
return updateFocusFn(widget);
41
}
42
43
const focus = widget.getFocus();
44
const selection = widget.getSelection();
45
46
await updateFocusFn(widget);
47
48
const newFocus = widget.getFocus();
49
50
if (selection.length > 1 || !equals(focus, selection) || equals(focus, newFocus)) {
51
return;
52
}
53
54
const fakeKeyboardEvent = new KeyboardEvent('keydown');
55
widget.setSelection(newFocus, fakeKeyboardEvent);
56
}
57
58
async function navigate(widget: WorkbenchListWidget | undefined, updateFocusFn: (widget: WorkbenchListWidget) => void | Promise<void>): Promise<void> {
59
if (!widget) {
60
return;
61
}
62
63
await updateFocus(widget, updateFocusFn);
64
65
const listFocus = widget.getFocus();
66
67
if (listFocus.length) {
68
widget.reveal(listFocus[0]);
69
}
70
71
widget.setAnchor(listFocus[0]);
72
ensureDOMFocus(widget);
73
}
74
75
KeybindingsRegistry.registerCommandAndKeybindingRule({
76
id: 'list.focusDown',
77
weight: KeybindingWeight.WorkbenchContrib,
78
when: WorkbenchListFocusContextKey,
79
primary: KeyCode.DownArrow,
80
mac: {
81
primary: KeyCode.DownArrow,
82
secondary: [KeyMod.WinCtrl | KeyCode.KeyN]
83
},
84
handler: (accessor, arg2) => {
85
navigate(accessor.get(IListService).lastFocusedList, async widget => {
86
const fakeKeyboardEvent = new KeyboardEvent('keydown');
87
await widget.focusNext(typeof arg2 === 'number' ? arg2 : 1, false, fakeKeyboardEvent);
88
});
89
}
90
});
91
92
KeybindingsRegistry.registerCommandAndKeybindingRule({
93
id: 'list.focusUp',
94
weight: KeybindingWeight.WorkbenchContrib,
95
when: WorkbenchListFocusContextKey,
96
primary: KeyCode.UpArrow,
97
mac: {
98
primary: KeyCode.UpArrow,
99
secondary: [KeyMod.WinCtrl | KeyCode.KeyP]
100
},
101
handler: (accessor, arg2) => {
102
navigate(accessor.get(IListService).lastFocusedList, async widget => {
103
const fakeKeyboardEvent = new KeyboardEvent('keydown');
104
await widget.focusPrevious(typeof arg2 === 'number' ? arg2 : 1, false, fakeKeyboardEvent);
105
});
106
}
107
});
108
109
KeybindingsRegistry.registerCommandAndKeybindingRule({
110
id: 'list.focusAnyDown',
111
weight: KeybindingWeight.WorkbenchContrib,
112
when: WorkbenchListFocusContextKey,
113
primary: KeyMod.Alt | KeyCode.DownArrow,
114
mac: {
115
primary: KeyMod.Alt | KeyCode.DownArrow,
116
secondary: [KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KeyN]
117
},
118
handler: (accessor, arg2) => {
119
navigate(accessor.get(IListService).lastFocusedList, async widget => {
120
const fakeKeyboardEvent = new KeyboardEvent('keydown', { altKey: true });
121
await widget.focusNext(typeof arg2 === 'number' ? arg2 : 1, false, fakeKeyboardEvent);
122
});
123
}
124
});
125
126
KeybindingsRegistry.registerCommandAndKeybindingRule({
127
id: 'list.focusAnyUp',
128
weight: KeybindingWeight.WorkbenchContrib,
129
when: WorkbenchListFocusContextKey,
130
primary: KeyMod.Alt | KeyCode.UpArrow,
131
mac: {
132
primary: KeyMod.Alt | KeyCode.UpArrow,
133
secondary: [KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KeyP]
134
},
135
handler: (accessor, arg2) => {
136
navigate(accessor.get(IListService).lastFocusedList, async widget => {
137
const fakeKeyboardEvent = new KeyboardEvent('keydown', { altKey: true });
138
await widget.focusPrevious(typeof arg2 === 'number' ? arg2 : 1, false, fakeKeyboardEvent);
139
});
140
}
141
});
142
143
KeybindingsRegistry.registerCommandAndKeybindingRule({
144
id: 'list.focusPageDown',
145
weight: KeybindingWeight.WorkbenchContrib,
146
when: WorkbenchListFocusContextKey,
147
primary: KeyCode.PageDown,
148
handler: (accessor) => {
149
navigate(accessor.get(IListService).lastFocusedList, async widget => {
150
const fakeKeyboardEvent = new KeyboardEvent('keydown');
151
await widget.focusNextPage(fakeKeyboardEvent);
152
});
153
}
154
});
155
156
KeybindingsRegistry.registerCommandAndKeybindingRule({
157
id: 'list.focusPageUp',
158
weight: KeybindingWeight.WorkbenchContrib,
159
when: WorkbenchListFocusContextKey,
160
primary: KeyCode.PageUp,
161
handler: (accessor) => {
162
navigate(accessor.get(IListService).lastFocusedList, async widget => {
163
const fakeKeyboardEvent = new KeyboardEvent('keydown');
164
await widget.focusPreviousPage(fakeKeyboardEvent);
165
});
166
}
167
});
168
169
KeybindingsRegistry.registerCommandAndKeybindingRule({
170
id: 'list.focusFirst',
171
weight: KeybindingWeight.WorkbenchContrib,
172
when: WorkbenchListFocusContextKey,
173
primary: KeyCode.Home,
174
handler: (accessor) => {
175
navigate(accessor.get(IListService).lastFocusedList, async widget => {
176
const fakeKeyboardEvent = new KeyboardEvent('keydown');
177
await widget.focusFirst(fakeKeyboardEvent);
178
});
179
}
180
});
181
182
KeybindingsRegistry.registerCommandAndKeybindingRule({
183
id: 'list.focusLast',
184
weight: KeybindingWeight.WorkbenchContrib,
185
when: WorkbenchListFocusContextKey,
186
primary: KeyCode.End,
187
handler: (accessor) => {
188
navigate(accessor.get(IListService).lastFocusedList, async widget => {
189
const fakeKeyboardEvent = new KeyboardEvent('keydown');
190
await widget.focusLast(fakeKeyboardEvent);
191
});
192
}
193
});
194
195
KeybindingsRegistry.registerCommandAndKeybindingRule({
196
id: 'list.focusAnyFirst',
197
weight: KeybindingWeight.WorkbenchContrib,
198
when: WorkbenchListFocusContextKey,
199
primary: KeyMod.Alt | KeyCode.Home,
200
handler: (accessor) => {
201
navigate(accessor.get(IListService).lastFocusedList, async widget => {
202
const fakeKeyboardEvent = new KeyboardEvent('keydown', { altKey: true });
203
await widget.focusFirst(fakeKeyboardEvent);
204
});
205
}
206
});
207
208
KeybindingsRegistry.registerCommandAndKeybindingRule({
209
id: 'list.focusAnyLast',
210
weight: KeybindingWeight.WorkbenchContrib,
211
when: WorkbenchListFocusContextKey,
212
primary: KeyMod.Alt | KeyCode.End,
213
handler: (accessor) => {
214
navigate(accessor.get(IListService).lastFocusedList, async widget => {
215
const fakeKeyboardEvent = new KeyboardEvent('keydown', { altKey: true });
216
await widget.focusLast(fakeKeyboardEvent);
217
});
218
}
219
});
220
221
function expandMultiSelection(focused: WorkbenchListWidget, previousFocus: unknown): void {
222
223
// List
224
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
225
const list = focused;
226
227
const focus = list.getFocus() ? list.getFocus()[0] : undefined;
228
const selection = list.getSelection();
229
if (selection && typeof focus === 'number' && selection.indexOf(focus) >= 0) {
230
list.setSelection(selection.filter(s => s !== previousFocus));
231
} else {
232
if (typeof focus === 'number') {
233
list.setSelection(selection.concat(focus));
234
}
235
}
236
}
237
238
// Tree
239
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
240
const list = focused;
241
242
const focus = list.getFocus() ? list.getFocus()[0] : undefined;
243
244
if (previousFocus === focus) {
245
return;
246
}
247
248
const selection = list.getSelection();
249
const fakeKeyboardEvent = new KeyboardEvent('keydown', { shiftKey: true });
250
251
if (selection && selection.indexOf(focus) >= 0) {
252
list.setSelection(selection.filter(s => s !== previousFocus), fakeKeyboardEvent);
253
} else {
254
list.setSelection(selection.concat(focus), fakeKeyboardEvent);
255
}
256
}
257
}
258
259
function revealFocusedStickyScroll(tree: ObjectTree<any, any> | DataTree<any, any> | AsyncDataTree<any, any>, postRevealAction?: (focus: any) => void): void {
260
const focus = tree.getStickyScrollFocus();
261
262
if (focus.length === 0) {
263
throw new Error(`StickyScroll has no focus`);
264
}
265
if (focus.length > 1) {
266
throw new Error(`StickyScroll can only have a single focused item`);
267
}
268
269
tree.reveal(focus[0]);
270
tree.getHTMLElement().focus(); // domfocus() would focus stiky scroll dom and not the tree todo@benibenj
271
tree.setFocus(focus);
272
postRevealAction?.(focus[0]);
273
}
274
275
KeybindingsRegistry.registerCommandAndKeybindingRule({
276
id: 'list.expandSelectionDown',
277
weight: KeybindingWeight.WorkbenchContrib,
278
when: ContextKeyExpr.and(WorkbenchListFocusContextKey, WorkbenchListSupportsMultiSelectContextKey),
279
primary: KeyMod.Shift | KeyCode.DownArrow,
280
handler: (accessor, arg2) => {
281
const widget = accessor.get(IListService).lastFocusedList;
282
283
if (!widget) {
284
return;
285
}
286
287
// Focus down first
288
const previousFocus = widget.getFocus() ? widget.getFocus()[0] : undefined;
289
const fakeKeyboardEvent = new KeyboardEvent('keydown');
290
widget.focusNext(typeof arg2 === 'number' ? arg2 : 1, false, fakeKeyboardEvent);
291
292
// Then adjust selection
293
expandMultiSelection(widget, previousFocus);
294
295
const focus = widget.getFocus();
296
297
if (focus.length) {
298
widget.reveal(focus[0]);
299
}
300
301
ensureDOMFocus(widget);
302
}
303
});
304
305
KeybindingsRegistry.registerCommandAndKeybindingRule({
306
id: 'list.expandSelectionUp',
307
weight: KeybindingWeight.WorkbenchContrib,
308
when: ContextKeyExpr.and(WorkbenchListFocusContextKey, WorkbenchListSupportsMultiSelectContextKey),
309
primary: KeyMod.Shift | KeyCode.UpArrow,
310
handler: (accessor, arg2) => {
311
const widget = accessor.get(IListService).lastFocusedList;
312
313
if (!widget) {
314
return;
315
}
316
317
// Focus up first
318
const previousFocus = widget.getFocus() ? widget.getFocus()[0] : undefined;
319
const fakeKeyboardEvent = new KeyboardEvent('keydown');
320
widget.focusPrevious(typeof arg2 === 'number' ? arg2 : 1, false, fakeKeyboardEvent);
321
322
// Then adjust selection
323
expandMultiSelection(widget, previousFocus);
324
325
const focus = widget.getFocus();
326
327
if (focus.length) {
328
widget.reveal(focus[0]);
329
}
330
331
ensureDOMFocus(widget);
332
}
333
});
334
335
KeybindingsRegistry.registerCommandAndKeybindingRule({
336
id: 'list.collapse',
337
weight: KeybindingWeight.WorkbenchContrib,
338
when: ContextKeyExpr.and(WorkbenchListFocusContextKey, ContextKeyExpr.or(WorkbenchTreeElementCanCollapse, WorkbenchTreeElementHasParent)),
339
primary: KeyCode.LeftArrow,
340
mac: {
341
primary: KeyCode.LeftArrow,
342
secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow]
343
},
344
handler: (accessor) => {
345
const widget = accessor.get(IListService).lastFocusedList;
346
347
if (!widget || !(widget instanceof ObjectTree || widget instanceof DataTree || widget instanceof AsyncDataTree)) {
348
return;
349
}
350
351
const tree = widget;
352
const focusedElements = tree.getFocus();
353
354
if (focusedElements.length === 0) {
355
return;
356
}
357
358
const focus = focusedElements[0];
359
360
if (!tree.collapse(focus)) {
361
const parent = tree.getParentElement(focus);
362
363
if (parent) {
364
navigate(widget, widget => {
365
const fakeKeyboardEvent = new KeyboardEvent('keydown');
366
widget.setFocus([parent], fakeKeyboardEvent);
367
});
368
}
369
}
370
}
371
});
372
373
KeybindingsRegistry.registerCommandAndKeybindingRule({
374
id: 'list.stickyScroll.collapse',
375
weight: KeybindingWeight.WorkbenchContrib + 50,
376
when: WorkbenchTreeStickyScrollFocused,
377
primary: KeyCode.LeftArrow,
378
mac: {
379
primary: KeyCode.LeftArrow,
380
secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow]
381
},
382
handler: (accessor) => {
383
const widget = accessor.get(IListService).lastFocusedList;
384
385
if (!widget || !(widget instanceof ObjectTree || widget instanceof DataTree || widget instanceof AsyncDataTree)) {
386
return;
387
}
388
389
revealFocusedStickyScroll(widget, focus => widget.collapse(focus));
390
}
391
});
392
393
KeybindingsRegistry.registerCommandAndKeybindingRule({
394
id: 'list.collapseAll',
395
weight: KeybindingWeight.WorkbenchContrib,
396
when: WorkbenchListFocusContextKey,
397
primary: KeyMod.CtrlCmd | KeyCode.LeftArrow,
398
mac: {
399
primary: KeyMod.CtrlCmd | KeyCode.LeftArrow,
400
secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow]
401
},
402
handler: (accessor) => {
403
const focused = accessor.get(IListService).lastFocusedList;
404
405
if (focused && !(focused instanceof List || focused instanceof PagedList || focused instanceof Table)) {
406
focused.collapseAll();
407
}
408
}
409
});
410
411
KeybindingsRegistry.registerCommandAndKeybindingRule({
412
id: 'list.collapseAllToFocus',
413
weight: KeybindingWeight.WorkbenchContrib,
414
when: WorkbenchListFocusContextKey,
415
handler: accessor => {
416
const focused = accessor.get(IListService).lastFocusedList;
417
const fakeKeyboardEvent = getSelectionKeyboardEvent('keydown', true);
418
// Trees
419
if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
420
const tree = focused;
421
const focus = tree.getFocus();
422
423
if (focus.length > 0) {
424
tree.collapse(focus[0], true);
425
}
426
tree.setSelection(focus, fakeKeyboardEvent);
427
tree.setAnchor(focus[0]);
428
}
429
}
430
});
431
432
433
KeybindingsRegistry.registerCommandAndKeybindingRule({
434
id: 'list.focusParent',
435
weight: KeybindingWeight.WorkbenchContrib,
436
when: WorkbenchListFocusContextKey,
437
handler: (accessor) => {
438
const widget = accessor.get(IListService).lastFocusedList;
439
440
if (!widget || !(widget instanceof ObjectTree || widget instanceof DataTree || widget instanceof AsyncDataTree)) {
441
return;
442
}
443
444
const tree = widget;
445
const focusedElements = tree.getFocus();
446
if (focusedElements.length === 0) {
447
return;
448
}
449
const focus = focusedElements[0];
450
const parent = tree.getParentElement(focus);
451
if (parent) {
452
navigate(widget, widget => {
453
const fakeKeyboardEvent = new KeyboardEvent('keydown');
454
widget.setFocus([parent], fakeKeyboardEvent);
455
});
456
}
457
}
458
});
459
460
KeybindingsRegistry.registerCommandAndKeybindingRule({
461
id: 'list.expand',
462
weight: KeybindingWeight.WorkbenchContrib,
463
when: ContextKeyExpr.and(WorkbenchListFocusContextKey, ContextKeyExpr.or(WorkbenchTreeElementCanExpand, WorkbenchTreeElementHasChild)),
464
primary: KeyCode.RightArrow,
465
handler: (accessor) => {
466
const widget = accessor.get(IListService).lastFocusedList;
467
468
if (!widget) {
469
return;
470
}
471
472
if (widget instanceof ObjectTree || widget instanceof DataTree) {
473
// TODO@Joao: instead of doing this here, just delegate to a tree method
474
const focusedElements = widget.getFocus();
475
476
if (focusedElements.length === 0) {
477
return;
478
}
479
480
const focus = focusedElements[0];
481
482
if (!widget.expand(focus)) {
483
const child = widget.getFirstElementChild(focus);
484
485
if (child) {
486
const node = widget.getNode(child);
487
488
if (node.visible) {
489
navigate(widget, widget => {
490
const fakeKeyboardEvent = new KeyboardEvent('keydown');
491
widget.setFocus([child], fakeKeyboardEvent);
492
});
493
}
494
}
495
}
496
} else if (widget instanceof AsyncDataTree) {
497
// TODO@Joao: instead of doing this here, just delegate to a tree method
498
const focusedElements = widget.getFocus();
499
500
if (focusedElements.length === 0) {
501
return;
502
}
503
504
const focus = focusedElements[0];
505
widget.expand(focus).then(didExpand => {
506
if (focus && !didExpand) {
507
const child = widget.getFirstElementChild(focus);
508
509
if (child) {
510
const node = widget.getNode(child);
511
512
if (node.visible) {
513
navigate(widget, widget => {
514
const fakeKeyboardEvent = new KeyboardEvent('keydown');
515
widget.setFocus([child], fakeKeyboardEvent);
516
});
517
}
518
}
519
}
520
});
521
}
522
}
523
});
524
525
function selectElement(accessor: ServicesAccessor, retainCurrentFocus: boolean): void {
526
const focused = accessor.get(IListService).lastFocusedList;
527
const fakeKeyboardEvent = getSelectionKeyboardEvent('keydown', retainCurrentFocus);
528
// List
529
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
530
const list = focused;
531
list.setAnchor(list.getFocus()[0]);
532
list.setSelection(list.getFocus(), fakeKeyboardEvent);
533
}
534
535
// Trees
536
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
537
const tree = focused;
538
const focus = tree.getFocus();
539
540
if (focus.length > 0) {
541
let toggleCollapsed = true;
542
543
if (tree.expandOnlyOnTwistieClick === true) {
544
toggleCollapsed = false;
545
} else if (typeof tree.expandOnlyOnTwistieClick !== 'boolean' && tree.expandOnlyOnTwistieClick(focus[0])) {
546
toggleCollapsed = false;
547
}
548
549
if (toggleCollapsed) {
550
tree.toggleCollapsed(focus[0]);
551
}
552
}
553
tree.setAnchor(focus[0]);
554
tree.setSelection(focus, fakeKeyboardEvent);
555
}
556
}
557
558
KeybindingsRegistry.registerCommandAndKeybindingRule({
559
id: 'list.select',
560
weight: KeybindingWeight.WorkbenchContrib,
561
when: WorkbenchListFocusContextKey,
562
primary: KeyCode.Enter,
563
mac: {
564
primary: KeyCode.Enter,
565
secondary: [KeyMod.CtrlCmd | KeyCode.DownArrow]
566
},
567
handler: (accessor) => {
568
selectElement(accessor, false);
569
}
570
});
571
572
KeybindingsRegistry.registerCommandAndKeybindingRule({
573
id: 'list.stickyScrollselect',
574
weight: KeybindingWeight.WorkbenchContrib + 50, // priorities over file explorer
575
when: WorkbenchTreeStickyScrollFocused,
576
primary: KeyCode.Enter,
577
mac: {
578
primary: KeyCode.Enter,
579
secondary: [KeyMod.CtrlCmd | KeyCode.DownArrow]
580
},
581
handler: (accessor) => {
582
const widget = accessor.get(IListService).lastFocusedList;
583
584
if (!widget || !(widget instanceof ObjectTree || widget instanceof DataTree || widget instanceof AsyncDataTree)) {
585
return;
586
}
587
588
revealFocusedStickyScroll(widget, focus => widget.setSelection([focus]));
589
}
590
});
591
592
KeybindingsRegistry.registerCommandAndKeybindingRule({
593
id: 'list.selectAndPreserveFocus',
594
weight: KeybindingWeight.WorkbenchContrib,
595
when: WorkbenchListFocusContextKey,
596
handler: accessor => {
597
selectElement(accessor, true);
598
}
599
});
600
601
KeybindingsRegistry.registerCommandAndKeybindingRule({
602
id: 'list.selectAll',
603
weight: KeybindingWeight.WorkbenchContrib,
604
when: ContextKeyExpr.and(WorkbenchListFocusContextKey, WorkbenchListSupportsMultiSelectContextKey),
605
primary: KeyMod.CtrlCmd | KeyCode.KeyA,
606
handler: (accessor) => {
607
const focused = accessor.get(IListService).lastFocusedList;
608
609
// List
610
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
611
const list = focused;
612
const fakeKeyboardEvent = new KeyboardEvent('keydown');
613
list.setSelection(range(list.length), fakeKeyboardEvent);
614
}
615
616
// Trees
617
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
618
const tree = focused;
619
const focus = tree.getFocus();
620
const selection = tree.getSelection();
621
622
// Which element should be considered to start selecting all?
623
let start: unknown | undefined = undefined;
624
625
if (focus.length > 0 && (selection.length === 0 || !selection.includes(focus[0]))) {
626
start = focus[0];
627
}
628
629
if (!start && selection.length > 0) {
630
start = selection[0];
631
}
632
633
// What is the scope of select all?
634
let scope: unknown | undefined = undefined;
635
636
if (!start) {
637
scope = undefined;
638
} else {
639
scope = tree.getParentElement(start);
640
}
641
642
const newSelection: unknown[] = [];
643
const visit = (node: ITreeNode<unknown, unknown>) => {
644
for (const child of node.children) {
645
if (child.visible) {
646
newSelection.push(child.element);
647
648
if (!child.collapsed) {
649
visit(child);
650
}
651
}
652
}
653
};
654
655
// Add the whole scope subtree to the new selection
656
visit(tree.getNode(scope));
657
658
// If the scope isn't the tree root, it should be part of the new selection
659
if (scope && selection.length === newSelection.length) {
660
newSelection.unshift(scope);
661
}
662
663
const fakeKeyboardEvent = new KeyboardEvent('keydown');
664
tree.setSelection(newSelection, fakeKeyboardEvent);
665
}
666
}
667
});
668
669
KeybindingsRegistry.registerCommandAndKeybindingRule({
670
id: 'list.toggleSelection',
671
weight: KeybindingWeight.WorkbenchContrib,
672
when: WorkbenchListFocusContextKey,
673
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter,
674
handler: (accessor) => {
675
const widget = accessor.get(IListService).lastFocusedList;
676
677
if (!widget) {
678
return;
679
}
680
681
const focus = widget.getFocus();
682
683
if (focus.length === 0) {
684
return;
685
}
686
687
const selection = widget.getSelection();
688
const index = selection.indexOf(focus[0]);
689
690
if (index > -1) {
691
widget.setSelection([...selection.slice(0, index), ...selection.slice(index + 1)]);
692
} else {
693
widget.setSelection([...selection, focus[0]]);
694
}
695
}
696
});
697
698
KeybindingsRegistry.registerCommandAndKeybindingRule({
699
id: 'list.showHover',
700
weight: KeybindingWeight.WorkbenchContrib,
701
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyI),
702
when: WorkbenchListFocusContextKey,
703
handler: async (accessor: ServicesAccessor, ...args: any[]) => {
704
const listService = accessor.get(IListService);
705
const lastFocusedList = listService.lastFocusedList;
706
if (!lastFocusedList) {
707
return;
708
}
709
710
// Check if a tree element is focused
711
const focus = lastFocusedList.getFocus();
712
if (!focus || (focus.length === 0)) {
713
return;
714
}
715
716
// As the tree does not know anything about the rendered DOM elements
717
// we have to traverse the dom to find the HTMLElements
718
const treeDOM = lastFocusedList.getHTMLElement();
719
const scrollableElement = treeDOM.querySelector('.monaco-scrollable-element');
720
const listRows = scrollableElement?.querySelector('.monaco-list-rows');
721
const focusedElement = listRows?.querySelector('.focused');
722
if (!focusedElement) {
723
return;
724
}
725
726
const elementWithHover = getCustomHoverForElement(focusedElement as HTMLElement);
727
if (elementWithHover) {
728
accessor.get(IHoverService).showManagedHover(elementWithHover as HTMLElement);
729
}
730
},
731
});
732
733
function getCustomHoverForElement(element: HTMLElement): HTMLElement | undefined {
734
// Check if the element itself has a hover
735
if (element.matches('[custom-hover="true"]')) {
736
return element;
737
}
738
739
// Only consider children that are not action items or have a tabindex
740
// as these element are focusable and the user is able to trigger them already
741
const noneFocusableElementWithHover = element.querySelector('[custom-hover="true"]:not([tabindex]):not(.action-item)');
742
if (noneFocusableElementWithHover) {
743
return noneFocusableElementWithHover as HTMLElement;
744
}
745
746
return undefined;
747
}
748
749
KeybindingsRegistry.registerCommandAndKeybindingRule({
750
id: 'list.toggleExpand',
751
weight: KeybindingWeight.WorkbenchContrib,
752
when: WorkbenchListFocusContextKey,
753
primary: KeyCode.Space,
754
handler: (accessor) => {
755
const focused = accessor.get(IListService).lastFocusedList;
756
757
// Tree only
758
if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
759
const tree = focused;
760
const focus = tree.getFocus();
761
762
if (!tree.options.disableExpandOnSpacebar && focus.length > 0 && tree.isCollapsible(focus[0])) {
763
tree.toggleCollapsed(focus[0]);
764
return;
765
}
766
}
767
768
selectElement(accessor, true);
769
}
770
});
771
772
KeybindingsRegistry.registerCommandAndKeybindingRule({
773
id: 'list.stickyScrolltoggleExpand',
774
weight: KeybindingWeight.WorkbenchContrib + 50, // priorities over file explorer
775
when: WorkbenchTreeStickyScrollFocused,
776
primary: KeyCode.Space,
777
handler: (accessor) => {
778
const widget = accessor.get(IListService).lastFocusedList;
779
780
if (!widget || !(widget instanceof ObjectTree || widget instanceof DataTree || widget instanceof AsyncDataTree)) {
781
return;
782
}
783
784
revealFocusedStickyScroll(widget);
785
}
786
});
787
788
KeybindingsRegistry.registerCommandAndKeybindingRule({
789
id: 'list.clear',
790
weight: KeybindingWeight.WorkbenchContrib,
791
when: ContextKeyExpr.and(WorkbenchListFocusContextKey, WorkbenchListHasSelectionOrFocus),
792
primary: KeyCode.Escape,
793
handler: (accessor) => {
794
const widget = accessor.get(IListService).lastFocusedList;
795
796
if (!widget) {
797
return;
798
}
799
800
const selection = widget.getSelection();
801
const fakeKeyboardEvent = new KeyboardEvent('keydown');
802
803
if (selection.length > 1) {
804
const useSelectionNavigation = WorkbenchListSelectionNavigation.getValue(widget.contextKeyService);
805
if (useSelectionNavigation) {
806
const focus = widget.getFocus();
807
widget.setSelection([focus[0]], fakeKeyboardEvent);
808
} else {
809
widget.setSelection([], fakeKeyboardEvent);
810
}
811
} else {
812
widget.setSelection([], fakeKeyboardEvent);
813
widget.setFocus([], fakeKeyboardEvent);
814
}
815
816
widget.setAnchor(undefined);
817
}
818
});
819
820
CommandsRegistry.registerCommand({
821
id: 'list.triggerTypeNavigation',
822
handler: (accessor) => {
823
const widget = accessor.get(IListService).lastFocusedList;
824
widget?.triggerTypeNavigation();
825
}
826
});
827
828
CommandsRegistry.registerCommand({
829
id: 'list.toggleFindMode',
830
handler: (accessor) => {
831
const widget = accessor.get(IListService).lastFocusedList;
832
833
if (widget instanceof AbstractTree || widget instanceof AsyncDataTree) {
834
const tree = widget;
835
tree.findMode = tree.findMode === TreeFindMode.Filter ? TreeFindMode.Highlight : TreeFindMode.Filter;
836
}
837
}
838
});
839
840
CommandsRegistry.registerCommand({
841
id: 'list.toggleFindMatchType',
842
handler: (accessor) => {
843
const widget = accessor.get(IListService).lastFocusedList;
844
845
if (widget instanceof AbstractTree || widget instanceof AsyncDataTree) {
846
const tree = widget;
847
tree.findMatchType = tree.findMatchType === TreeFindMatchType.Contiguous ? TreeFindMatchType.Fuzzy : TreeFindMatchType.Contiguous;
848
}
849
}
850
});
851
852
// Deprecated commands
853
CommandsRegistry.registerCommandAlias('list.toggleKeyboardNavigation', 'list.triggerTypeNavigation');
854
CommandsRegistry.registerCommandAlias('list.toggleFilterOnType', 'list.toggleFindMode');
855
856
KeybindingsRegistry.registerCommandAndKeybindingRule({
857
id: 'list.find',
858
weight: KeybindingWeight.WorkbenchContrib,
859
when: ContextKeyExpr.and(RawWorkbenchListFocusContextKey, WorkbenchListSupportsFind),
860
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyF,
861
secondary: [KeyCode.F3],
862
handler: (accessor) => {
863
const widget = accessor.get(IListService).lastFocusedList;
864
865
// List
866
if (widget instanceof List || widget instanceof PagedList || widget instanceof Table) {
867
// TODO@joao
868
}
869
870
// Tree
871
else if (widget instanceof AbstractTree || widget instanceof AsyncDataTree) {
872
const tree = widget;
873
tree.openFind();
874
}
875
}
876
});
877
878
KeybindingsRegistry.registerCommandAndKeybindingRule({
879
id: 'list.closeFind',
880
weight: KeybindingWeight.WorkbenchContrib,
881
when: ContextKeyExpr.and(RawWorkbenchListFocusContextKey, WorkbenchTreeFindOpen),
882
primary: KeyCode.Escape,
883
handler: (accessor) => {
884
const widget = accessor.get(IListService).lastFocusedList;
885
886
if (widget instanceof AbstractTree || widget instanceof AsyncDataTree) {
887
const tree = widget;
888
tree.closeFind();
889
}
890
}
891
});
892
893
KeybindingsRegistry.registerCommandAndKeybindingRule({
894
id: 'list.scrollUp',
895
weight: KeybindingWeight.WorkbenchContrib,
896
// Since the default keybindings for list.scrollUp and widgetNavigation.focusPrevious
897
// are both Ctrl+UpArrow, we disable this command when the scrollbar is at
898
// top-most position. This will give chance for widgetNavigation.focusPrevious to execute
899
when: ContextKeyExpr.and(
900
WorkbenchListFocusContextKey,
901
WorkbenchListScrollAtTopContextKey?.negate()),
902
primary: KeyMod.CtrlCmd | KeyCode.UpArrow,
903
handler: accessor => {
904
const focused = accessor.get(IListService).lastFocusedList;
905
906
if (!focused) {
907
return;
908
}
909
910
focused.scrollTop -= 10;
911
}
912
});
913
914
KeybindingsRegistry.registerCommandAndKeybindingRule({
915
id: 'list.scrollDown',
916
weight: KeybindingWeight.WorkbenchContrib,
917
// same as above
918
when: ContextKeyExpr.and(
919
WorkbenchListFocusContextKey,
920
WorkbenchListScrollAtBottomContextKey?.negate()),
921
primary: KeyMod.CtrlCmd | KeyCode.DownArrow,
922
handler: accessor => {
923
const focused = accessor.get(IListService).lastFocusedList;
924
925
if (!focused) {
926
return;
927
}
928
929
focused.scrollTop += 10;
930
}
931
});
932
933
KeybindingsRegistry.registerCommandAndKeybindingRule({
934
id: 'list.scrollLeft',
935
weight: KeybindingWeight.WorkbenchContrib,
936
when: WorkbenchListFocusContextKey,
937
handler: accessor => {
938
const focused = accessor.get(IListService).lastFocusedList;
939
940
if (!focused) {
941
return;
942
}
943
944
focused.scrollLeft -= 10;
945
}
946
});
947
948
KeybindingsRegistry.registerCommandAndKeybindingRule({
949
id: 'list.scrollRight',
950
weight: KeybindingWeight.WorkbenchContrib,
951
when: WorkbenchListFocusContextKey,
952
handler: accessor => {
953
const focused = accessor.get(IListService).lastFocusedList;
954
955
if (!focused) {
956
return;
957
}
958
959
focused.scrollLeft += 10;
960
}
961
});
962
963
registerAction2(class ToggleStickyScroll extends Action2 {
964
constructor() {
965
super({
966
id: 'tree.toggleStickyScroll',
967
title: {
968
...localize2('toggleTreeStickyScroll', "Toggle Tree Sticky Scroll"),
969
mnemonicTitle: localize({ key: 'mitoggleTreeStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Toggle Tree Sticky Scroll"),
970
},
971
category: 'View',
972
metadata: { description: localize('toggleTreeStickyScrollDescription', "Toggles Sticky Scroll widget at the top of tree structures such as the File Explorer and Debug variables View.") },
973
f1: true
974
});
975
}
976
977
run(accessor: ServicesAccessor) {
978
const configurationService = accessor.get(IConfigurationService);
979
const newValue = !configurationService.getValue<boolean>('workbench.tree.enableStickyScroll');
980
configurationService.updateValue('workbench.tree.enableStickyScroll', newValue);
981
}
982
});
983
984