@workbench-kit/shell-react 0.0.2-prototype.0.2.41 → 0.0.2-prototype.0.2.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +42 -2
  2. package/package.json +13 -10
  3. package/src/extensions/extension-enablement-controller.ts +40 -9
  4. package/src/extensions/theme-selection-protection.ts +80 -55
  5. package/src/field-remap/chrome-labels.ts +18 -0
  6. package/src/field-remap/convert-note-editor.tsx +30 -24
  7. package/src/field-remap/convert-palette.tsx +6 -0
  8. package/src/field-remap/demo.tsx +8 -2
  9. package/src/field-remap/detail-panel.tsx +225 -192
  10. package/src/field-remap/drag-payload.ts +48 -0
  11. package/src/field-remap/flow-adapter.ts +216 -88
  12. package/src/field-remap/flow-ops.ts +150 -0
  13. package/src/field-remap/flow.tsx +1055 -162
  14. package/src/field-remap/index.ts +2 -0
  15. package/src/field-remap/io-class-browse.tsx +34 -22
  16. package/src/field-remap/keyboard.ts +16 -0
  17. package/src/field-remap/modal-detail.tsx +34 -0
  18. package/src/field-remap/panel.tsx +50 -14
  19. package/src/field-remap/transform-options-editor.tsx +5 -1
  20. package/src/field-remap/view.css +61 -42
  21. package/src/index.ts +7 -0
  22. package/src/management/keybinding-overrides-storage.ts +48 -23
  23. package/src/management/keybinding-settings.tsx +10 -1
  24. package/src/management/use-keybinding-management.ts +69 -19
  25. package/src/shell/appearance-catalog.ts +453 -0
  26. package/src/shell/appearance-controller.ts +331 -0
  27. package/src/shell/appearance-presentation.ts +269 -0
  28. package/src/shell/provider.tsx +147 -18
  29. package/src/shell/settings.tsx +282 -153
  30. package/src/shell/shell.tsx +88 -18
  31. package/src/workbench/appearance-storage.ts +2 -2
  32. package/src/workbench/command-host-controller.tsx +223 -0
  33. package/src/workbench/command-host.tsx +100 -150
  34. package/src/workbench/keybinding-bridge.ts +84 -38
  35. package/src/workbench/shell-command-registration.ts +27 -2
@@ -1,14 +1,6 @@
1
1
  import {
2
- createCommandRegistryFromContributions,
3
- type CommandRegistry,
4
- } from '@workbench-kit/platform';
5
- import {
6
- WorkbenchCommandPalette,
7
- WorkbenchQuickOpen,
8
- WorkbenchShortcutCommandBridge,
9
2
  createWorkbenchShellCommands,
10
3
  createWorkspaceFilesQuickOpenProvider,
11
- resolveQuickOpenItemPath,
12
4
  type QuickOpenItem,
13
5
  type QuickOpenProvider,
14
6
  type QuickOpenSelectContext,
@@ -20,21 +12,23 @@ import {
20
12
  matchesWorkbenchCommandPaletteShortcut,
21
13
  matchesWorkbenchQuickAccessShortcut,
22
14
  } from '@workbench-kit/react/workbench/command-ui';
23
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
15
+ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
24
16
 
25
17
  import { useContextKeyRevision } from '../commands/use-context-key-revision.js';
26
18
  import { useWorkbench } from '../shell/provider.js';
19
+ import { WorkbenchCommandHostController } from './command-host-controller.js';
27
20
  import { registerWorkbenchShellCommandHandlers } from './shell-command-registration.js';
28
21
  import {
29
22
  buildWorkbenchPaletteCommands,
30
23
  collectExtensionCommandFeaturesById,
31
24
  resolveShellCommandActivities,
32
25
  } from './command-palette.js';
33
- import { resolveExtensionKeybindingCommand } from './keybinding-bridge.js';
26
+ import {
27
+ normalizeExtensionKeybindingCandidates,
28
+ resolveExtensionKeybindingCommand,
29
+ } from './keybinding-bridge.js';
34
30
  import { isWorkspaceResourceService, useWorkspaceResourceState } from './workspace-view-state.js';
35
31
 
36
- const WORKSPACE_OPEN_COMMAND_ID = 'workspace.open' as const;
37
-
38
32
  export interface WorkbenchCommandHostProps {
39
33
  additionalCommands?: readonly WorkbenchCommandDescriptor[];
40
34
  /** Override command palette close control label (default English). */
@@ -108,21 +102,55 @@ export function WorkbenchCommandHost({
108
102
  extensionCatalog,
109
103
  keybindings,
110
104
  keybindingOverrides,
105
+ keybindingPlatform,
106
+ keybindingProjection,
111
107
  layoutService,
112
108
  views,
113
109
  workspaceHostPort,
114
110
  } = useWorkbench();
115
- const [paletteOpen, setPaletteOpen] = useState(false);
116
- const [paletteQuery, setPaletteQuery] = useState('');
117
- const [quickOpenOpen, setQuickOpenOpen] = useState(false);
118
- const [quickOpenQuery, setQuickOpenQuery] = useState('');
119
111
  const [layout, setLayout] = useState(() => layoutService.getState());
112
+ const [extensionKeybindingRevision, setExtensionKeybindingRevision] = useState(
113
+ () => keybindings.revision,
114
+ );
120
115
  const shellContextRef = useRef<WorkbenchShellCommandContext | undefined>(undefined);
121
116
  const contextKeyRevision = useContextKeyRevision(contextKeyService);
122
117
  const contextKeySnapshot = useMemo(
123
118
  () => contextKeyService.createSnapshot(),
124
119
  [contextKeyRevision, contextKeyService],
125
120
  );
121
+ const genericKeybindingCommandIds = useMemo(
122
+ () => new Set(keybindingProjection.defaults.map((binding) => binding.command)),
123
+ [keybindingProjection.defaults],
124
+ );
125
+ useLayoutEffect(() => {
126
+ const disposable = keybindings.onDidChangeKeybindings(() => {
127
+ setExtensionKeybindingRevision(keybindings.revision);
128
+ });
129
+ setExtensionKeybindingRevision(keybindings.revision);
130
+ return () => disposable.dispose();
131
+ }, [keybindings]);
132
+ const extensionOnlyCommandIds = useMemo(() => {
133
+ const commandIds = new Set<string>();
134
+ for (const binding of keybindings.getKeybindings()) {
135
+ if (
136
+ !genericKeybindingCommandIds.has(binding.command) &&
137
+ normalizeExtensionKeybindingCandidates(binding.key, keybindingPlatform, true).length > 0
138
+ ) {
139
+ commandIds.add(binding.command);
140
+ }
141
+ }
142
+ return commandIds;
143
+ }, [extensionKeybindingRevision, genericKeybindingCommandIds, keybindingPlatform, keybindings]);
144
+ const runtimeKeybindingProjection = useMemo(() => {
145
+ if (!enableExtensionKeybindings) return keybindingProjection;
146
+
147
+ return Object.freeze({
148
+ commands: Object.freeze(
149
+ keybindingProjection.commands.filter((command) => !extensionOnlyCommandIds.has(command.id)),
150
+ ),
151
+ defaults: keybindingProjection.defaults,
152
+ });
153
+ }, [enableExtensionKeybindings, extensionOnlyCommandIds, keybindingProjection]);
126
154
 
127
155
  const workspaceService = isWorkspaceResourceService(workspaceHostPort?.service)
128
156
  ? workspaceHostPort.service
@@ -176,14 +204,6 @@ export function WorkbenchCommandHost({
176
204
  [managedShellActivities],
177
205
  );
178
206
 
179
- const shellCommandRegistry = useMemo(
180
- () =>
181
- createCommandRegistryFromContributions<WorkbenchShellCommandContext>([
182
- { commands: shellCommandDefinitions },
183
- ]),
184
- [shellCommandDefinitions],
185
- );
186
-
187
207
  const shellContext = useMemo<WorkbenchShellCommandContext>(
188
208
  () => ({
189
209
  isFocusModeActive: layoutService.isFocusModeActive(),
@@ -261,101 +281,16 @@ export function WorkbenchCommandHost({
261
281
  ];
262
282
  }, [quickOpenProviders, quickOpenRecentPaths, workspaceService, workspaceState?.files]);
263
283
 
264
- const closePalette = useCallback(() => {
265
- setPaletteOpen(false);
266
- }, []);
267
-
268
- const openPalette = useCallback((query = '') => {
269
- setQuickOpenOpen(false);
270
- setPaletteQuery(query);
271
- setPaletteOpen(true);
272
- }, []);
273
-
274
- const closeQuickOpen = useCallback(() => {
275
- setQuickOpenOpen(false);
276
- }, []);
277
-
278
- const openQuickOpen = useCallback((query = '') => {
279
- setPaletteOpen(false);
280
- setQuickOpenQuery(query);
281
- setQuickOpenOpen(true);
282
- }, []);
283
-
284
- const runPaletteCommand = useCallback(
285
- (command: WorkbenchCommandDescriptor, context: WorkbenchCommandRunContext) => {
286
- const finish = () => {
287
- closePalette();
288
- };
289
-
290
- if (onRunCommand?.(command, context)) {
291
- finish();
292
- return;
293
- }
294
-
295
- void executeCommand(command.id).finally(finish);
296
- },
297
- [closePalette, executeCommand, onRunCommand],
298
- );
299
-
300
- const runQuickOpenItem = useCallback(
301
- (item: QuickOpenItem, context: QuickOpenSelectContext) => {
302
- const finish = () => {
303
- closeQuickOpen();
304
- };
305
-
306
- if (onOpenQuickOpenItem?.(item, context)) {
307
- finish();
308
- return;
309
- }
310
-
311
- const path = resolveQuickOpenItemPath(item);
312
- if (!path) {
313
- finish();
314
- return;
315
- }
316
-
317
- void executeCommand(WORKSPACE_OPEN_COMMAND_ID, { path }).finally(finish);
318
- },
319
- [closeQuickOpen, executeCommand, onOpenQuickOpenItem],
320
- );
321
-
322
284
  useEffect(() => {
323
- if (!enableCommandPalette && !enableQuickOpen) {
285
+ if (!enableExtensionKeybindings) {
324
286
  return undefined;
325
287
  }
326
288
 
327
289
  const onKeyDown = (event: KeyboardEvent) => {
328
- if (enableCommandPalette && matchesWorkbenchCommandPaletteShortcut(event)) {
329
- event.preventDefault();
330
- openPalette('>');
290
+ if (isActiveShortcutCaptureTarget(event)) {
331
291
  return;
332
292
  }
333
293
 
334
- if (matchesWorkbenchQuickAccessShortcut(event)) {
335
- event.preventDefault();
336
- if (enableQuickOpen) {
337
- openQuickOpen();
338
- return;
339
- }
340
-
341
- if (enableCommandPalette) {
342
- openPalette();
343
- }
344
- }
345
- };
346
-
347
- window.addEventListener('keydown', onKeyDown);
348
- return () => {
349
- window.removeEventListener('keydown', onKeyDown);
350
- };
351
- }, [enableCommandPalette, enableQuickOpen, openPalette, openQuickOpen]);
352
-
353
- useEffect(() => {
354
- if (!enableExtensionKeybindings) {
355
- return undefined;
356
- }
357
-
358
- const onKeyDown = (event: KeyboardEvent) => {
359
294
  if (
360
295
  matchesWorkbenchCommandPaletteShortcut(event) ||
361
296
  matchesWorkbenchQuickAccessShortcut(event)
@@ -363,7 +298,14 @@ export function WorkbenchCommandHost({
363
298
  return;
364
299
  }
365
300
 
366
- const match = resolveExtensionKeybindingCommand(keybindings, event, {}, keybindingOverrides);
301
+ const match = resolveExtensionKeybindingCommand(
302
+ keybindings,
303
+ event,
304
+ {},
305
+ keybindingOverrides,
306
+ keybindingPlatform,
307
+ genericKeybindingCommandIds,
308
+ );
367
309
  if (!match) {
368
310
  return;
369
311
  }
@@ -376,45 +318,53 @@ export function WorkbenchCommandHost({
376
318
  return () => {
377
319
  window.removeEventListener('keydown', onKeyDown);
378
320
  };
379
- }, [enableExtensionKeybindings, executeCommand, keybindings, keybindingOverrides]);
321
+ }, [
322
+ enableExtensionKeybindings,
323
+ executeCommand,
324
+ genericKeybindingCommandIds,
325
+ keybindings,
326
+ keybindingOverrides,
327
+ keybindingPlatform,
328
+ ]);
329
+
330
+ return (
331
+ <WorkbenchCommandHostController
332
+ commandPaletteCloseLabel={commandPaletteCloseLabel}
333
+ commandPaletteEmptyLabel={commandPaletteEmptyLabel}
334
+ commandPalettePlaceholder={commandPalettePlaceholder}
335
+ commandPaletteTitle={commandPaletteTitle}
336
+ commands={paletteCommands}
337
+ enableCommandPalette={enableCommandPalette}
338
+ enableQuickOpen={enableQuickOpen}
339
+ executeCommand={executeCommand}
340
+ quickOpenCloseLabel={quickOpenCloseLabel}
341
+ quickOpenEmptyLabel={quickOpenEmptyLabel}
342
+ quickOpenPlaceholder={quickOpenPlaceholder}
343
+ quickOpenProviders={resolvedQuickOpenProviders}
344
+ quickOpenTitle={quickOpenTitle}
345
+ shortcutBridge={
346
+ enableShortcutBridge
347
+ ? {
348
+ context: undefined,
349
+ keybindingOverrides,
350
+ keybindingProjection: runtimeKeybindingProjection,
351
+ platform: keybindingPlatform,
352
+ preventDefault: true,
353
+ registry: commands,
354
+ }
355
+ : false
356
+ }
357
+ {...(onOpenQuickOpenItem === undefined ? {} : { onOpenQuickOpenItem })}
358
+ {...(onRunCommand === undefined ? {} : { onRunCommand })}
359
+ />
360
+ );
361
+ }
380
362
 
363
+ function isActiveShortcutCaptureTarget(event: Event): boolean {
364
+ const target = event.target;
381
365
  return (
382
- <>
383
- {enableShortcutBridge ? (
384
- <WorkbenchShortcutCommandBridge
385
- context={shellContext}
386
- preventDefault
387
- registry={shellCommandRegistry as CommandRegistry<WorkbenchShellCommandContext>}
388
- />
389
- ) : null}
390
- {enableCommandPalette ? (
391
- <WorkbenchCommandPalette
392
- closeLabel={commandPaletteCloseLabel}
393
- commands={paletteCommands}
394
- emptyLabel={commandPaletteEmptyLabel}
395
- open={paletteOpen}
396
- placeholder={commandPalettePlaceholder}
397
- query={paletteQuery}
398
- title={commandPaletteTitle}
399
- onClose={closePalette}
400
- onQueryChange={setPaletteQuery}
401
- onRunCommand={runPaletteCommand}
402
- />
403
- ) : null}
404
- {enableQuickOpen ? (
405
- <WorkbenchQuickOpen
406
- closeLabel={quickOpenCloseLabel}
407
- emptyLabel={quickOpenEmptyLabel}
408
- open={quickOpenOpen}
409
- placeholder={quickOpenPlaceholder}
410
- providers={resolvedQuickOpenProviders}
411
- query={quickOpenQuery}
412
- title={quickOpenTitle}
413
- onClose={closeQuickOpen}
414
- onQueryChange={setQuickOpenQuery}
415
- onSelectItem={runQuickOpenItem}
416
- />
417
- ) : null}
418
- </>
366
+ typeof Element !== 'undefined' &&
367
+ target instanceof Element &&
368
+ target.closest('[data-workbench-shortcut-capture-recording="true"]') !== null
419
369
  );
420
370
  }
@@ -1,32 +1,21 @@
1
- import type { ContextKeyValue, KeybindingRegistry } from '@workbench-kit/platform';
2
- import { resolveKeybindingWithOverrides, type KeybindingDefinition } from '@workbench-kit/platform';
3
-
4
- function normalizeKeyToken(token: string): string {
5
- const key = token.trim().toLowerCase();
6
- if (key === 'del') return 'delete';
7
- if (key === 'esc') return 'escape';
8
- if (key === 'return') return 'enter';
9
- if (key === 'spacebar' || key === 'space') return 'space';
10
- return key.length === 1 ? key : key;
11
- }
1
+ import {
2
+ evaluateWhenClause,
3
+ normalizeWorkbenchShortcutCandidates,
4
+ normalizeWorkbenchShortcutFromEvent,
5
+ resolveWorkbenchShortcutPlatform,
6
+ workbenchShortcutsOverlap,
7
+ type ContextKeyValue,
8
+ type KeybindingDefinition,
9
+ type KeybindingMatch,
10
+ type KeybindingRegistry,
11
+ type WorkbenchShortcutPlatform,
12
+ } from '@workbench-kit/platform';
12
13
 
13
14
  export function normalizeKeybindingKeyFromEvent(
14
15
  event: Pick<KeyboardEvent, 'altKey' | 'ctrlKey' | 'key' | 'metaKey' | 'shiftKey'>,
16
+ platform: WorkbenchShortcutPlatform = resolveWorkbenchShortcutPlatform(),
15
17
  ): string {
16
- const parts: string[] = [];
17
-
18
- if (event.ctrlKey || event.metaKey) {
19
- parts.push('ctrl');
20
- }
21
- if (event.altKey) {
22
- parts.push('alt');
23
- }
24
- if (event.shiftKey) {
25
- parts.push('shift');
26
- }
27
-
28
- parts.push(normalizeKeyToken(event.key));
29
- return parts.join('+');
18
+ return normalizeWorkbenchShortcutFromEvent(event, platform) ?? '';
30
19
  }
31
20
 
32
21
  export function resolveExtensionKeybindingCommand(
@@ -34,21 +23,78 @@ export function resolveExtensionKeybindingCommand(
34
23
  event: Pick<KeyboardEvent, 'altKey' | 'ctrlKey' | 'key' | 'metaKey' | 'shiftKey'>,
35
24
  contextKeys: Readonly<Record<string, ContextKeyValue>> = {},
36
25
  overrides: readonly KeybindingDefinition[] = [],
37
- ) {
38
- const key = normalizeKeybindingKeyFromEvent(event);
26
+ platform: WorkbenchShortcutPlatform = resolveWorkbenchShortcutPlatform(),
27
+ excludedCommandIds: ReadonlySet<string> = new Set(),
28
+ ): KeybindingMatch | undefined {
29
+ const key = normalizeKeybindingKeyFromEvent(event, platform);
30
+ if (!key) return undefined;
39
31
 
40
- if (overrides.length > 0) {
41
- const match = resolveKeybindingWithOverrides(
42
- registry.getKeybindings(),
43
- overrides,
32
+ const defaults = registry
33
+ .getKeybindings()
34
+ .filter(
35
+ (binding) =>
36
+ !excludedCommandIds.has(binding.command) &&
37
+ normalizeExtensionKeybindingCandidates(binding.key, platform, true).length > 0,
38
+ );
39
+ const ownedCommandIds = new Set(defaults.map((binding) => binding.command));
40
+ const ownedOverrides = overrides.filter((binding) => ownedCommandIds.has(binding.command));
41
+ const overriddenCommands = new Set(ownedOverrides.map((binding) => binding.command));
42
+ const userMatches = ownedOverrides.filter((binding) =>
43
+ bindingMatchesEvent(
44
+ binding,
44
45
  key,
45
46
  contextKeys,
46
- );
47
- if (match) {
48
- return match;
49
- }
50
- }
47
+ platform,
48
+ binding.when !== undefined || (binding.args?.length ?? 0) > 0,
49
+ ),
50
+ );
51
+ const defaultMatches = defaults.filter(
52
+ (binding) =>
53
+ !overriddenCommands.has(binding.command) &&
54
+ bindingMatchesEvent(binding, key, contextKeys, platform, true),
55
+ );
56
+
57
+ return [...userMatches, ...defaultMatches]
58
+ .map((binding) => ({
59
+ ...binding,
60
+ specificity: binding.when ? binding.when.length : 0,
61
+ }))
62
+ .sort((left, right) => right.specificity - left.specificity)[0];
63
+ }
51
64
 
52
- const matches = registry.resolveKeybindings(key, contextKeys);
53
- return matches[0];
65
+ function bindingMatchesEvent(
66
+ binding: KeybindingDefinition,
67
+ eventKey: string,
68
+ contextKeys: Readonly<Record<string, ContextKeyValue>>,
69
+ platform: WorkbenchShortcutPlatform,
70
+ preserveLegacyMacPrimaryCompatibility: boolean,
71
+ ): boolean {
72
+ return (
73
+ (workbenchShortcutsOverlap(binding.key, eventKey, platform) ||
74
+ normalizeExtensionKeybindingCandidates(
75
+ binding.key,
76
+ platform,
77
+ preserveLegacyMacPrimaryCompatibility,
78
+ ).some((candidate) => workbenchShortcutsOverlap(candidate, eventKey, platform))) &&
79
+ evaluateWhenClause(binding.when, contextKeys)
80
+ );
81
+ }
82
+
83
+ export function normalizeExtensionKeybindingCandidates(
84
+ shortcut: string,
85
+ platform: WorkbenchShortcutPlatform,
86
+ preserveLegacyMacPrimaryCompatibility = false,
87
+ ): readonly string[] {
88
+ return normalizeWorkbenchShortcutCandidates(shortcut, platform).flatMap((candidate) => {
89
+ const tokens = candidate.split('+');
90
+ if (
91
+ !preserveLegacyMacPrimaryCompatibility ||
92
+ platform !== 'mac' ||
93
+ !tokens.includes('ctrl') ||
94
+ tokens.includes('meta')
95
+ ) {
96
+ return [candidate];
97
+ }
98
+ return [candidate, tokens.map((token) => (token === 'ctrl' ? 'meta' : token)).join('+')];
99
+ });
54
100
  }
@@ -1,4 +1,8 @@
1
- import type { CommandDefinition, CommandRegistry } from '@workbench-kit/platform';
1
+ import {
2
+ resolveCommandValue,
3
+ type CommandDefinition,
4
+ type CommandRegistry,
5
+ } from '@workbench-kit/platform';
2
6
  import type { WorkbenchShellCommandContext } from '@workbench-kit/react/workbench';
3
7
 
4
8
  export interface WorkbenchShellCommandRegistration {
@@ -14,16 +18,34 @@ export function registerWorkbenchShellCommandHandlers(
14
18
  const handler = () => {
15
19
  command.run?.(getContext());
16
20
  };
21
+ const isEnabled = command.isEnabled
22
+ ? () => command.isEnabled?.(getContext()) ?? true
23
+ : undefined;
24
+ const run = () => {
25
+ command.run?.(getContext());
26
+ };
27
+ const shortcut = resolveCommandValue(command.shortcut, getContext());
17
28
  const existing = registry.getCommand(command.id);
18
29
 
19
30
  if (existing) {
20
31
  const previousHandler = existing.handler;
32
+ const previousIsEnabled = existing.isEnabled;
33
+ const previousRun = existing.run;
34
+ const previousShortcut = existing.shortcut;
21
35
  existing.handler = handler;
36
+ existing.isEnabled = isEnabled;
37
+ existing.run = run;
38
+ existing.shortcut = shortcut;
39
+ registry.notifyCommandChanged(command.id);
22
40
 
23
41
  return {
24
42
  dispose() {
25
- if (existing.handler === handler) {
43
+ if (registry.getCommand(command.id) === existing && existing.handler === handler) {
26
44
  existing.handler = previousHandler;
45
+ existing.isEnabled = previousIsEnabled;
46
+ existing.run = previousRun;
47
+ existing.shortcut = previousShortcut;
48
+ registry.notifyCommandChanged(command.id);
27
49
  }
28
50
  },
29
51
  };
@@ -34,6 +56,9 @@ export function registerWorkbenchShellCommandHandlers(
34
56
  handler,
35
57
  icon: resolveStaticCommandIcon(command),
36
58
  id: command.id,
59
+ isEnabled,
60
+ run,
61
+ shortcut,
37
62
  title: resolveStaticCommandTitle(command),
38
63
  });
39
64
  });