Skip to content

Commit 5e9ea7e

Browse files
committed
Add commands and registry for widgetNavigation
1 parent f215fbe commit 5e9ea7e

File tree

2 files changed

+150
-0
lines changed

2 files changed

+150
-0
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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 'vs/base/common/keyCodes';
7+
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
8+
import { KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
9+
import { WorkbenchListFocusContextKey, WorkbenchListScrollAtBottomContextKey, WorkbenchListScrollAtTopContextKey } from 'vs/platform/list/browser/listService';
10+
import { Event } from 'vs/base/common/event';
11+
import { combinedDisposable, toDisposable, IDisposable, Disposable } from 'vs/base/common/lifecycle';
12+
import { Registry } from 'vs/platform/registry/common/platform';
13+
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
14+
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
15+
16+
/** INavigatableContainer represents a logical container composed of widgets that can
17+
be navigated back and forth with key shortcuts */
18+
interface INavigatableContainer {
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+
focusPreviousWidget(): void;
31+
focusNextWidget(): void;
32+
}
33+
34+
interface IFocusNotifier {
35+
readonly onDidFocus: Event<any>;
36+
readonly onDidBlur: Event<any>;
37+
}
38+
39+
function handleFocusEventsGroup(group: readonly IFocusNotifier[], handler: (isFocus: boolean) => void): IDisposable {
40+
const focusedIndices = new Set<number>();
41+
return combinedDisposable(...group.map((events, index) => combinedDisposable(
42+
events.onDidFocus(() => {
43+
if (!focusedIndices.size) {
44+
handler(true);
45+
}
46+
focusedIndices.add(index);
47+
}),
48+
events.onDidBlur(() => {
49+
focusedIndices.delete(index);
50+
if (!focusedIndices.size) {
51+
handler(false);
52+
}
53+
}),
54+
)));
55+
}
56+
57+
const NavigatableContainerFocusedContextKey = new RawContextKey<boolean>('navigatableContainerFocused', false);
58+
59+
class NavigatableContainerManager implements IDisposable {
60+
private static INSTANCE: NavigatableContainerManager | undefined;
61+
62+
private readonly containers = new Set<INavigatableContainer>();
63+
private lastContainer: INavigatableContainer | undefined;
64+
private focused: IContextKey<boolean>;
65+
66+
67+
constructor(@IContextKeyService contextKeyService: IContextKeyService) {
68+
this.focused = NavigatableContainerFocusedContextKey.bindTo(contextKeyService);
69+
NavigatableContainerManager.INSTANCE = this;
70+
}
71+
72+
dispose(): void {
73+
this.containers.clear();
74+
this.focused.reset();
75+
NavigatableContainerManager.INSTANCE = undefined;
76+
}
77+
78+
static register(container: INavigatableContainer): IDisposable {
79+
const instance = this.INSTANCE;
80+
if (!instance) {
81+
return Disposable.None;
82+
}
83+
instance.containers.add(container);
84+
85+
return combinedDisposable(
86+
handleFocusEventsGroup(container.focusNotifiers, (isFocus) => {
87+
if (isFocus) {
88+
instance.focused.set(true);
89+
instance.lastContainer = container;
90+
} else if (instance.lastContainer === container) {
91+
instance.focused.set(false);
92+
instance.lastContainer = undefined;
93+
}
94+
}),
95+
toDisposable(() => {
96+
instance.containers.delete(container);
97+
if (instance.lastContainer === container) {
98+
instance.focused.set(false);
99+
instance.lastContainer = undefined;
100+
}
101+
})
102+
);
103+
}
104+
105+
static getActive(): INavigatableContainer | undefined {
106+
return this.INSTANCE?.lastContainer;
107+
}
108+
}
109+
110+
export function registerNavigatableContainer(container: INavigatableContainer): IDisposable {
111+
return NavigatableContainerManager.register(container);
112+
}
113+
114+
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
115+
.registerWorkbenchContribution(NavigatableContainerManager, LifecyclePhase.Starting);
116+
117+
KeybindingsRegistry.registerCommandAndKeybindingRule({
118+
id: 'widgetNavigation.focusPrevious',
119+
weight: KeybindingWeight.WorkbenchContrib,
120+
when: ContextKeyExpr.and(
121+
NavigatableContainerFocusedContextKey,
122+
ContextKeyExpr.or(
123+
WorkbenchListFocusContextKey?.negate(),
124+
WorkbenchListScrollAtTopContextKey,
125+
)
126+
),
127+
primary: KeyMod.CtrlCmd | KeyCode.UpArrow,
128+
handler: () => {
129+
const activeContainer = NavigatableContainerManager.getActive();
130+
activeContainer?.focusPreviousWidget();
131+
}
132+
});
133+
134+
KeybindingsRegistry.registerCommandAndKeybindingRule({
135+
id: 'widgetNavigation.focusNext',
136+
weight: KeybindingWeight.WorkbenchContrib,
137+
when: ContextKeyExpr.and(
138+
NavigatableContainerFocusedContextKey,
139+
ContextKeyExpr.or(
140+
WorkbenchListFocusContextKey?.negate(),
141+
WorkbenchListScrollAtBottomContextKey,
142+
)
143+
),
144+
primary: KeyMod.CtrlCmd | KeyCode.DownArrow,
145+
handler: () => {
146+
const activeContainer = NavigatableContainerManager.getActive();
147+
activeContainer?.focusNextWidget();
148+
}
149+
});

src/vs/workbench/workbench.common.main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import 'vs/workbench/browser/actions/windowActions';
2525
import 'vs/workbench/browser/actions/workspaceActions';
2626
import 'vs/workbench/browser/actions/workspaceCommands';
2727
import 'vs/workbench/browser/actions/quickAccessActions';
28+
import 'vs/workbench/browser/actions/widgetNavigationCommands';
2829

2930
//#endregion
3031

0 commit comments

Comments
 (0)