Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/browser/actions/widgetNavigationCommands.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 } from '../../../base/common/keyCodes.js';
7
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from '../../../platform/contextkey/common/contextkey.js';
8
import { KeybindingWeight, KeybindingsRegistry } from '../../../platform/keybinding/common/keybindingsRegistry.js';
9
import { WorkbenchListFocusContextKey, WorkbenchListScrollAtBottomContextKey, WorkbenchListScrollAtTopContextKey } from '../../../platform/list/browser/listService.js';
10
import { Event } from '../../../base/common/event.js';
11
import { combinedDisposable, toDisposable, IDisposable, Disposable } from '../../../base/common/lifecycle.js';
12
import { WorkbenchPhase, registerWorkbenchContribution2 } from '../../common/contributions.js';
13
import { ILogService } from '../../../platform/log/common/log.js';
14
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
15
16
/** INavigableContainer represents a logical container composed of widgets that can
17
be navigated back and forth with key shortcuts */
18
interface INavigableContainer {
19
/**
20
* The container may coomposed of multiple parts that share no DOM ancestor
21
* (e.g., the main body and filter box of MarkersView may be separated).
22
* To track the focus of container we must pass in focus/blur events of all parts
23
* as `focusNotifiers`.
24
*
25
* Each element of `focusNotifiers` notifies the focus/blur event for a part of
26
* the container. The container is considered focused if at least one part being
27
* focused, and blurred if all parts being blurred.
28
*/
29
readonly focusNotifiers: readonly IFocusNotifier[];
30
readonly name?: string; // for debugging
31
focusPreviousWidget(): void;
32
focusNextWidget(): void;
33
}
34
35
interface IFocusNotifier {
36
readonly onDidFocus: Event<any>;
37
readonly onDidBlur: Event<any>;
38
}
39
40
function handleFocusEventsGroup(group: readonly IFocusNotifier[], handler: (isFocus: boolean) => void, onPartFocusChange?: (index: number, state: string) => void): IDisposable {
41
const focusedIndices = new Set<number>();
42
return combinedDisposable(...group.map((events, index) => combinedDisposable(
43
events.onDidFocus(() => {
44
onPartFocusChange?.(index, 'focus');
45
if (!focusedIndices.size) {
46
handler(true);
47
}
48
focusedIndices.add(index);
49
}),
50
events.onDidBlur(() => {
51
onPartFocusChange?.(index, 'blur');
52
focusedIndices.delete(index);
53
if (!focusedIndices.size) {
54
handler(false);
55
}
56
}),
57
)));
58
}
59
60
const NavigableContainerFocusedContextKey = new RawContextKey<boolean>('navigableContainerFocused', false);
61
62
class NavigableContainerManager implements IDisposable {
63
64
static readonly ID = 'workbench.contrib.navigableContainerManager';
65
66
private static INSTANCE: NavigableContainerManager | undefined;
67
68
private readonly containers = new Set<INavigableContainer>();
69
private lastContainer: INavigableContainer | undefined;
70
private focused: IContextKey<boolean>;
71
72
73
constructor(
74
@IContextKeyService contextKeyService: IContextKeyService,
75
@ILogService private logService: ILogService,
76
@IConfigurationService private configurationService: IConfigurationService) {
77
this.focused = NavigableContainerFocusedContextKey.bindTo(contextKeyService);
78
NavigableContainerManager.INSTANCE = this;
79
}
80
81
dispose(): void {
82
this.containers.clear();
83
this.focused.reset();
84
NavigableContainerManager.INSTANCE = undefined;
85
}
86
87
private get debugEnabled(): boolean {
88
return this.configurationService.getValue('workbench.navigibleContainer.enableDebug');
89
}
90
91
private log(msg: string, ...args: any[]): void {
92
if (this.debugEnabled) {
93
this.logService.debug(msg, ...args);
94
}
95
}
96
97
static register(container: INavigableContainer): IDisposable {
98
const instance = this.INSTANCE;
99
if (!instance) {
100
return Disposable.None;
101
}
102
instance.containers.add(container);
103
instance.log('NavigableContainerManager.register', container.name);
104
105
return combinedDisposable(
106
handleFocusEventsGroup(container.focusNotifiers, (isFocus) => {
107
if (isFocus) {
108
instance.log('NavigableContainerManager.focus', container.name);
109
instance.focused.set(true);
110
instance.lastContainer = container;
111
} else {
112
instance.log('NavigableContainerManager.blur', container.name, instance.lastContainer?.name);
113
if (instance.lastContainer === container) {
114
instance.focused.set(false);
115
instance.lastContainer = undefined;
116
}
117
}
118
}, (index: number, event: string) => {
119
instance.log('NavigableContainerManager.partFocusChange', container.name, index, event);
120
}),
121
toDisposable(() => {
122
instance.containers.delete(container);
123
instance.log('NavigableContainerManager.unregister', container.name, instance.lastContainer?.name);
124
if (instance.lastContainer === container) {
125
instance.focused.set(false);
126
instance.lastContainer = undefined;
127
}
128
})
129
);
130
}
131
132
static getActive(): INavigableContainer | undefined {
133
return this.INSTANCE?.lastContainer;
134
}
135
}
136
137
export function registerNavigableContainer(container: INavigableContainer): IDisposable {
138
return NavigableContainerManager.register(container);
139
}
140
141
registerWorkbenchContribution2(NavigableContainerManager.ID, NavigableContainerManager, WorkbenchPhase.BlockStartup);
142
143
KeybindingsRegistry.registerCommandAndKeybindingRule({
144
id: 'widgetNavigation.focusPrevious',
145
weight: KeybindingWeight.WorkbenchContrib,
146
when: ContextKeyExpr.and(
147
NavigableContainerFocusedContextKey,
148
ContextKeyExpr.or(
149
WorkbenchListFocusContextKey?.negate(),
150
WorkbenchListScrollAtTopContextKey,
151
)
152
),
153
primary: KeyMod.CtrlCmd | KeyCode.UpArrow,
154
handler: () => {
155
const activeContainer = NavigableContainerManager.getActive();
156
activeContainer?.focusPreviousWidget();
157
}
158
});
159
160
KeybindingsRegistry.registerCommandAndKeybindingRule({
161
id: 'widgetNavigation.focusNext',
162
weight: KeybindingWeight.WorkbenchContrib,
163
when: ContextKeyExpr.and(
164
NavigableContainerFocusedContextKey,
165
ContextKeyExpr.or(
166
WorkbenchListFocusContextKey?.negate(),
167
WorkbenchListScrollAtBottomContextKey,
168
)
169
),
170
primary: KeyMod.CtrlCmd | KeyCode.DownArrow,
171
handler: () => {
172
const activeContainer = NavigableContainerManager.getActive();
173
activeContainer?.focusNextWidget();
174
}
175
});
176
177