Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/browser/actions/listCommands.ts
5251 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<unknown, unknown> | DataTree<unknown, unknown> | AsyncDataTree<unknown, unknown>, postRevealAction?: (focus: unknown) => 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) => {
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
// eslint-disable-next-line no-restricted-syntax
720
const scrollableElement = treeDOM.querySelector('.monaco-scrollable-element');
721
// eslint-disable-next-line no-restricted-syntax
722
const listRows = scrollableElement?.querySelector('.monaco-list-rows');
723
// eslint-disable-next-line no-restricted-syntax
724
const focusedElement = listRows?.querySelector('.focused');
725
if (!focusedElement) {
726
return;
727
}
728
729
const elementWithHover = getCustomHoverForElement(focusedElement as HTMLElement);
730
if (elementWithHover) {
731
accessor.get(IHoverService).showManagedHover(elementWithHover);
732
}
733
},
734
});
735
736
function getCustomHoverForElement(element: HTMLElement): HTMLElement | undefined {
737
// Check if the element itself has a hover
738
if (element.matches('[custom-hover="true"]')) {
739
return element;
740
}
741
742
// Only consider children that are not action items or have a tabindex
743
// as these element are focusable and the user is able to trigger them already
744
// eslint-disable-next-line no-restricted-syntax
745
const noneFocusableElementWithHover = element.querySelector('[custom-hover="true"]:not([tabindex]):not(.action-item)');
746
if (noneFocusableElementWithHover) {
747
return noneFocusableElementWithHover as HTMLElement;
748
}
749
750
return undefined;
751
}
752
753
KeybindingsRegistry.registerCommandAndKeybindingRule({
754
id: 'list.toggleExpand',
755
weight: KeybindingWeight.WorkbenchContrib,
756
when: WorkbenchListFocusContextKey,
757
primary: KeyCode.Space,
758
handler: (accessor) => {
759
const focused = accessor.get(IListService).lastFocusedList;
760
761
// Tree only
762
if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
763
const tree = focused;
764
const focus = tree.getFocus();
765
766
if (!tree.options.disableExpandOnSpacebar && focus.length > 0 && tree.isCollapsible(focus[0])) {
767
tree.toggleCollapsed(focus[0]);
768
return;
769
}
770
}
771
772
selectElement(accessor, true);
773
}
774
});
775
776
KeybindingsRegistry.registerCommandAndKeybindingRule({
777
id: 'list.stickyScrolltoggleExpand',
778
weight: KeybindingWeight.WorkbenchContrib + 50, // priorities over file explorer
779
when: WorkbenchTreeStickyScrollFocused,
780
primary: KeyCode.Space,
781
handler: (accessor) => {
782
const widget = accessor.get(IListService).lastFocusedList;
783
784
if (!widget || !(widget instanceof ObjectTree || widget instanceof DataTree || widget instanceof AsyncDataTree)) {
785
return;
786
}
787
788
revealFocusedStickyScroll(widget);
789
}
790
});
791
792
KeybindingsRegistry.registerCommandAndKeybindingRule({
793
id: 'list.clear',
794
weight: KeybindingWeight.WorkbenchContrib,
795
when: ContextKeyExpr.and(WorkbenchListFocusContextKey, WorkbenchListHasSelectionOrFocus),
796
primary: KeyCode.Escape,
797
handler: (accessor) => {
798
const widget = accessor.get(IListService).lastFocusedList;
799
800
if (!widget) {
801
return;
802
}
803
804
const selection = widget.getSelection();
805
const fakeKeyboardEvent = new KeyboardEvent('keydown');
806
807
if (selection.length > 1) {
808
const useSelectionNavigation = WorkbenchListSelectionNavigation.getValue(widget.contextKeyService);
809
if (useSelectionNavigation) {
810
const focus = widget.getFocus();
811
widget.setSelection([focus[0]], fakeKeyboardEvent);
812
} else {
813
widget.setSelection([], fakeKeyboardEvent);
814
}
815
} else {
816
widget.setSelection([], fakeKeyboardEvent);
817
widget.setFocus([], fakeKeyboardEvent);
818
}
819
820
widget.setAnchor(undefined);
821
}
822
});
823
824
CommandsRegistry.registerCommand({
825
id: 'list.triggerTypeNavigation',
826
handler: (accessor) => {
827
const widget = accessor.get(IListService).lastFocusedList;
828
widget?.triggerTypeNavigation();
829
}
830
});
831
832
CommandsRegistry.registerCommand({
833
id: 'list.toggleFindMode',
834
handler: (accessor) => {
835
const widget = accessor.get(IListService).lastFocusedList;
836
837
if (widget instanceof AbstractTree || widget instanceof AsyncDataTree) {
838
const tree = widget;
839
tree.findMode = tree.findMode === TreeFindMode.Filter ? TreeFindMode.Highlight : TreeFindMode.Filter;
840
}
841
}
842
});
843
844
CommandsRegistry.registerCommand({
845
id: 'list.toggleFindMatchType',
846
handler: (accessor) => {
847
const widget = accessor.get(IListService).lastFocusedList;
848
849
if (widget instanceof AbstractTree || widget instanceof AsyncDataTree) {
850
const tree = widget;
851
tree.findMatchType = tree.findMatchType === TreeFindMatchType.Contiguous ? TreeFindMatchType.Fuzzy : TreeFindMatchType.Contiguous;
852
}
853
}
854
});
855
856
// Deprecated commands
857
CommandsRegistry.registerCommandAlias('list.toggleKeyboardNavigation', 'list.triggerTypeNavigation');
858
CommandsRegistry.registerCommandAlias('list.toggleFilterOnType', 'list.toggleFindMode');
859
860
KeybindingsRegistry.registerCommandAndKeybindingRule({
861
id: 'list.find',
862
weight: KeybindingWeight.WorkbenchContrib,
863
when: ContextKeyExpr.and(RawWorkbenchListFocusContextKey, WorkbenchListSupportsFind),
864
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyF,
865
secondary: [KeyCode.F3],
866
handler: (accessor) => {
867
const widget = accessor.get(IListService).lastFocusedList;
868
869
// List
870
if (widget instanceof List || widget instanceof PagedList || widget instanceof Table) {
871
// TODO@joao
872
}
873
874
// Tree
875
else if (widget instanceof AbstractTree || widget instanceof AsyncDataTree) {
876
const tree = widget;
877
tree.openFind();
878
}
879
}
880
});
881
882
KeybindingsRegistry.registerCommandAndKeybindingRule({
883
id: 'list.closeFind',
884
weight: KeybindingWeight.WorkbenchContrib,
885
when: ContextKeyExpr.and(RawWorkbenchListFocusContextKey, WorkbenchTreeFindOpen),
886
primary: KeyCode.Escape,
887
handler: (accessor) => {
888
const widget = accessor.get(IListService).lastFocusedList;
889
890
if (widget instanceof AbstractTree || widget instanceof AsyncDataTree) {
891
const tree = widget;
892
tree.closeFind();
893
}
894
}
895
});
896
897
KeybindingsRegistry.registerCommandAndKeybindingRule({
898
id: 'list.scrollUp',
899
weight: KeybindingWeight.WorkbenchContrib,
900
// Since the default keybindings for list.scrollUp and widgetNavigation.focusPrevious
901
// are both Ctrl+UpArrow, we disable this command when the scrollbar is at
902
// top-most position. This will give chance for widgetNavigation.focusPrevious to execute
903
when: ContextKeyExpr.and(
904
WorkbenchListFocusContextKey,
905
WorkbenchListScrollAtTopContextKey?.negate()),
906
primary: KeyMod.CtrlCmd | KeyCode.UpArrow,
907
handler: accessor => {
908
const focused = accessor.get(IListService).lastFocusedList;
909
910
if (!focused) {
911
return;
912
}
913
914
focused.scrollTop -= 10;
915
}
916
});
917
918
KeybindingsRegistry.registerCommandAndKeybindingRule({
919
id: 'list.scrollDown',
920
weight: KeybindingWeight.WorkbenchContrib,
921
// same as above
922
when: ContextKeyExpr.and(
923
WorkbenchListFocusContextKey,
924
WorkbenchListScrollAtBottomContextKey?.negate()),
925
primary: KeyMod.CtrlCmd | KeyCode.DownArrow,
926
handler: accessor => {
927
const focused = accessor.get(IListService).lastFocusedList;
928
929
if (!focused) {
930
return;
931
}
932
933
focused.scrollTop += 10;
934
}
935
});
936
937
KeybindingsRegistry.registerCommandAndKeybindingRule({
938
id: 'list.scrollLeft',
939
weight: KeybindingWeight.WorkbenchContrib,
940
when: WorkbenchListFocusContextKey,
941
handler: accessor => {
942
const focused = accessor.get(IListService).lastFocusedList;
943
944
if (!focused) {
945
return;
946
}
947
948
focused.scrollLeft -= 10;
949
}
950
});
951
952
KeybindingsRegistry.registerCommandAndKeybindingRule({
953
id: 'list.scrollRight',
954
weight: KeybindingWeight.WorkbenchContrib,
955
when: WorkbenchListFocusContextKey,
956
handler: accessor => {
957
const focused = accessor.get(IListService).lastFocusedList;
958
959
if (!focused) {
960
return;
961
}
962
963
focused.scrollLeft += 10;
964
}
965
});
966
967
registerAction2(class ToggleStickyScroll extends Action2 {
968
constructor() {
969
super({
970
id: 'tree.toggleStickyScroll',
971
title: {
972
...localize2('toggleTreeStickyScroll', "Toggle Tree Sticky Scroll"),
973
mnemonicTitle: localize({ key: 'mitoggleTreeStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Toggle Tree Sticky Scroll"),
974
},
975
category: 'View',
976
metadata: { description: localize('toggleTreeStickyScrollDescription', "Toggles Sticky Scroll widget at the top of tree structures such as the File Explorer and Debug variables View.") },
977
f1: true
978
});
979
}
980
981
run(accessor: ServicesAccessor) {
982
const configurationService = accessor.get(IConfigurationService);
983
const newValue = !configurationService.getValue<boolean>('workbench.tree.enableStickyScroll');
984
configurationService.updateValue('workbench.tree.enableStickyScroll', newValue);
985
}
986
});
987
988