@workbench-kit/shell-react 0.0.2-prototype.0.2.32 → 0.0.2-prototype.0.2.34

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 (52) hide show
  1. package/README.md +32 -0
  2. package/package.json +9 -9
  3. package/src/commands/use-command-descriptors.ts +7 -3
  4. package/src/commands/use-extension-registry-command-descriptors.ts +23 -6
  5. package/src/devtools/use-workbench-devtools-snapshot.ts +39 -14
  6. package/src/devtools/workbench-devtools-snapshot.ts +32 -13
  7. package/src/editor/area.tsx +3 -2
  8. package/src/editor/state-storage.ts +31 -0
  9. package/src/editor/tab-context-menu.ts +8 -8
  10. package/src/editor/use-editor.ts +6 -6
  11. package/src/explorer/context-menu.ts +8 -4
  12. package/src/explorer/view.tsx +4 -3
  13. package/src/extensions/canonical-extension-descriptions.ts +58 -0
  14. package/src/extensions/context-menu.ts +9 -6
  15. package/src/extensions/extension-enablement-context.ts +15 -0
  16. package/src/extensions/extension-enablement-controller.ts +453 -0
  17. package/src/extensions/management-model.ts +111 -47
  18. package/src/extensions/theme-selection-protection.ts +98 -0
  19. package/src/extensions/uninstall-eligibility.ts +103 -0
  20. package/src/extensions/use-extension-management.ts +151 -67
  21. package/src/extensions/view.tsx +4 -0
  22. package/src/field-remap/chrome-labels.ts +87 -2
  23. package/src/field-remap/convert-palette.tsx +164 -18
  24. package/src/field-remap/demo.tsx +48 -3
  25. package/src/field-remap/detail-panel.tsx +6 -5
  26. package/src/field-remap/flow.tsx +341 -155
  27. package/src/field-remap/history.ts +99 -0
  28. package/src/field-remap/index.ts +9 -1
  29. package/src/field-remap/panel.tsx +388 -97
  30. package/src/field-remap/preview-controller.ts +122 -0
  31. package/src/field-remap/preview.tsx +171 -0
  32. package/src/field-remap/view.css +154 -2
  33. package/src/index.ts +16 -1
  34. package/src/management/keybinding-overrides-storage.ts +28 -0
  35. package/src/management/preference-settings-storage.ts +28 -0
  36. package/src/management/settings.tsx +2 -0
  37. package/src/management/use-command-management.ts +31 -26
  38. package/src/management/use-keybinding-management.ts +25 -12
  39. package/src/shell/focused-extension-services.ts +138 -0
  40. package/src/shell/host-shell.tsx +13 -10
  41. package/src/shell/persistence-diagnostic-context.ts +11 -0
  42. package/src/shell/provider.tsx +373 -69
  43. package/src/shell/settings.tsx +25 -16
  44. package/src/shell/shell.tsx +119 -78
  45. package/src/shell/view-host.tsx +23 -22
  46. package/src/storage/local-json-storage.ts +43 -29
  47. package/src/storage/persistence-diagnostics.ts +72 -0
  48. package/src/workbench/appearance-storage.ts +28 -0
  49. package/src/workbench/command-host.tsx +19 -22
  50. package/src/workbench/command-palette.ts +14 -13
  51. package/src/workbench/layout-storage.ts +52 -5
  52. package/src/workbench/use-persisted-appearance.ts +38 -12
@@ -0,0 +1,122 @@
1
+ import {
2
+ convertMappedInputs,
3
+ isAbortError,
4
+ type ConvertMappedInputsInput,
5
+ type ConvertToShapeResult,
6
+ } from '@workbench-kit/field-remap';
7
+
8
+ import type { FieldRemapPreviewState } from './preview.js';
9
+
10
+ export type FieldRemapPreviewCommand =
11
+ | { readonly kind: 'hidden' }
12
+ | { readonly kind: 'no-sample' }
13
+ | {
14
+ readonly kind: 'evaluate';
15
+ /** Stable signature of every durable input, including transform implementations. */
16
+ readonly revision: string;
17
+ readonly input: Omit<ConvertMappedInputsInput, 'signal'>;
18
+ };
19
+
20
+ type FieldRemapPreviewEvaluator = (
21
+ input: ConvertMappedInputsInput,
22
+ ) => Promise<ConvertToShapeResult>;
23
+
24
+ export interface FieldRemapPreviewController {
25
+ readonly getSnapshot: () => FieldRemapPreviewState;
26
+ readonly subscribe: (listener: () => void) => () => void;
27
+ readonly update: (command: FieldRemapPreviewCommand) => void;
28
+ readonly dispose: () => void;
29
+ }
30
+
31
+ /** Package-private execution owner. Public Flow surfaces consume only its snapshot. */
32
+ export function createFieldRemapPreviewController(
33
+ evaluate: FieldRemapPreviewEvaluator = convertMappedInputs,
34
+ ): FieldRemapPreviewController {
35
+ let snapshot: FieldRemapPreviewState = {
36
+ status: 'unavailable',
37
+ reason: 'no-sample',
38
+ };
39
+ let generation = 0;
40
+ let disposed = false;
41
+ let abortController: AbortController | undefined;
42
+ let activeRevision: string | undefined;
43
+ const listeners = new Set<() => void>();
44
+
45
+ const publish = (next: FieldRemapPreviewState): void => {
46
+ if (disposed) {
47
+ return;
48
+ }
49
+ snapshot = next;
50
+ for (const listener of listeners) {
51
+ listener();
52
+ }
53
+ };
54
+
55
+ return {
56
+ getSnapshot: () => snapshot,
57
+ subscribe: (listener) => {
58
+ if (disposed) {
59
+ return () => {};
60
+ }
61
+ listeners.add(listener);
62
+ return () => listeners.delete(listener);
63
+ },
64
+ update: (command) => {
65
+ if (disposed) {
66
+ return;
67
+ }
68
+ if (command.kind === 'evaluate' && command.revision === activeRevision) {
69
+ return;
70
+ }
71
+
72
+ abortController?.abort();
73
+ generation += 1;
74
+ const currentGeneration = generation;
75
+ abortController = undefined;
76
+
77
+ if (command.kind !== 'evaluate') {
78
+ activeRevision = undefined;
79
+ publish({ status: 'unavailable', reason: command.kind });
80
+ return;
81
+ }
82
+
83
+ activeRevision = command.revision;
84
+ const controller = new AbortController();
85
+ abortController = controller;
86
+ publish({ status: 'loading' });
87
+
88
+ void evaluate({ ...command.input, signal: controller.signal })
89
+ .then((result) => {
90
+ if (disposed || controller.signal.aborted || currentGeneration !== generation) {
91
+ return;
92
+ }
93
+ publish({ status: 'ready', result });
94
+ })
95
+ .catch((error: unknown) => {
96
+ if (
97
+ disposed ||
98
+ controller.signal.aborted ||
99
+ currentGeneration !== generation ||
100
+ isAbortError(error)
101
+ ) {
102
+ return;
103
+ }
104
+ publish({
105
+ status: 'error',
106
+ message: error instanceof Error ? error.message : String(error),
107
+ });
108
+ });
109
+ },
110
+ dispose: () => {
111
+ if (disposed) {
112
+ return;
113
+ }
114
+ disposed = true;
115
+ generation += 1;
116
+ abortController?.abort();
117
+ abortController = undefined;
118
+ activeRevision = undefined;
119
+ listeners.clear();
120
+ },
121
+ };
122
+ }
@@ -0,0 +1,171 @@
1
+ import { useMemo, type JSX } from 'react';
2
+ import type { ConvertToShapeResult, MappingEdge } from '@workbench-kit/field-remap';
3
+
4
+ import { defaultFieldRemapChromeLabels, type FieldRemapChromeLabels } from './chrome-labels.js';
5
+ import type { FieldRemapSelection } from './flow-ops.js';
6
+
7
+ export type FieldRemapPreviewState =
8
+ | {
9
+ readonly status: 'unavailable';
10
+ readonly reason: 'hidden' | 'no-sample';
11
+ }
12
+ | { readonly status: 'loading' }
13
+ | {
14
+ readonly status: 'ready';
15
+ readonly result: ConvertToShapeResult;
16
+ }
17
+ | {
18
+ readonly status: 'error';
19
+ readonly message: string;
20
+ };
21
+
22
+ export type FieldRemapPreviewProjection =
23
+ | FieldRemapPreviewState
24
+ | {
25
+ readonly status: 'unsupported';
26
+ readonly reason: 'draft' | 'stale-selection';
27
+ }
28
+ | {
29
+ readonly status: 'value';
30
+ readonly scope: 'binding' | 'document';
31
+ readonly value: unknown;
32
+ readonly notice?: 'operator-intermediate' | 'transform-step-intermediate';
33
+ };
34
+
35
+ export function resolveFieldRemapPreviewProjection(
36
+ preview: FieldRemapPreviewState,
37
+ selection: FieldRemapSelection,
38
+ edges: readonly MappingEdge[],
39
+ operatorExists: boolean,
40
+ ): FieldRemapPreviewProjection {
41
+ if (preview.status !== 'ready') {
42
+ return preview;
43
+ }
44
+ if (!selection) {
45
+ return { status: 'value', scope: 'document', value: preview.result.output };
46
+ }
47
+ if (selection.kind === 'draft') {
48
+ return { status: 'unsupported', reason: 'draft' };
49
+ }
50
+ if (selection.kind === 'operator') {
51
+ return operatorExists
52
+ ? {
53
+ status: 'value',
54
+ scope: 'document',
55
+ value: preview.result.output,
56
+ notice: 'operator-intermediate',
57
+ }
58
+ : { status: 'unsupported', reason: 'stale-selection' };
59
+ }
60
+
61
+ const edge = edges.find((item) => item.id === selection.edgeId);
62
+ if (!edge) {
63
+ return { status: 'unsupported', reason: 'stale-selection' };
64
+ }
65
+ if (
66
+ selection.kind === 'transformStep' &&
67
+ (selection.stepIndex < 0 || selection.stepIndex >= (edge.transformIds?.length ?? 0))
68
+ ) {
69
+ return { status: 'unsupported', reason: 'stale-selection' };
70
+ }
71
+
72
+ const slot = preview.result.slots.find((item) => item.edgeId === edge.id);
73
+ if (!slot) {
74
+ return { status: 'unsupported', reason: 'stale-selection' };
75
+ }
76
+ return {
77
+ status: 'value',
78
+ scope: 'binding',
79
+ value: slot.value,
80
+ ...(selection.kind === 'transformStep'
81
+ ? { notice: 'transform-step-intermediate' as const }
82
+ : {}),
83
+ };
84
+ }
85
+
86
+ function previewLabel(labels: FieldRemapChromeLabels, key: keyof FieldRemapChromeLabels): string {
87
+ return labels[key] ?? defaultFieldRemapChromeLabels[key];
88
+ }
89
+
90
+ function formatPreviewValue(value: unknown): string {
91
+ try {
92
+ return JSON.stringify(value, null, 2) ?? String(value);
93
+ } catch {
94
+ return String(value);
95
+ }
96
+ }
97
+
98
+ export interface FieldRemapPreviewRailProps {
99
+ readonly preview: FieldRemapPreviewState;
100
+ readonly selection: FieldRemapSelection;
101
+ readonly edges: readonly MappingEdge[];
102
+ readonly operatorExists: boolean;
103
+ readonly labels: FieldRemapChromeLabels;
104
+ }
105
+
106
+ export function FieldRemapPreviewRail({
107
+ preview,
108
+ selection,
109
+ edges,
110
+ operatorExists,
111
+ labels,
112
+ }: FieldRemapPreviewRailProps): JSX.Element {
113
+ const projection = useMemo(
114
+ () => resolveFieldRemapPreviewProjection(preview, selection, edges, operatorExists),
115
+ [edges, operatorExists, preview, selection],
116
+ );
117
+
118
+ return (
119
+ <aside
120
+ className="workbench-field-remap-preview"
121
+ data-testid="field-remap-preview"
122
+ aria-label={previewLabel(labels, 'previewTitle')}
123
+ >
124
+ <h4>{previewLabel(labels, 'previewTitle')}</h4>
125
+ {projection.status === 'loading' ? (
126
+ <p role="status" data-testid="field-remap-preview-loading">
127
+ {previewLabel(labels, 'previewLoading')}
128
+ </p>
129
+ ) : null}
130
+ {projection.status === 'error' ? (
131
+ <p role="alert" data-testid="field-remap-preview-error">
132
+ {previewLabel(labels, 'previewError')}: {projection.message}
133
+ </p>
134
+ ) : null}
135
+ {projection.status === 'unsupported' ? (
136
+ <p role="status" data-testid="field-remap-preview-unavailable">
137
+ {previewLabel(
138
+ labels,
139
+ projection.reason === 'draft'
140
+ ? 'previewDraftUnavailable'
141
+ : 'previewSelectionUnavailable',
142
+ )}
143
+ </p>
144
+ ) : null}
145
+ {projection.status === 'value' ? (
146
+ <>
147
+ <p className="workbench-field-remap-preview__scope">
148
+ {previewLabel(
149
+ labels,
150
+ projection.scope === 'binding' ? 'previewBindingResult' : 'previewDocumentResult',
151
+ )}
152
+ </p>
153
+ {projection.notice ? (
154
+ <p
155
+ className="workbench-field-remap-preview__notice"
156
+ data-testid="field-remap-preview-notice"
157
+ >
158
+ {previewLabel(
159
+ labels,
160
+ projection.notice === 'transform-step-intermediate'
161
+ ? 'previewStepIntermediateUnavailable'
162
+ : 'previewOperatorIntermediateUnavailable',
163
+ )}
164
+ </p>
165
+ ) : null}
166
+ <pre data-testid="field-remap-preview-value">{formatPreviewValue(projection.value)}</pre>
167
+ </>
168
+ ) : null}
169
+ </aside>
170
+ );
171
+ }
@@ -301,6 +301,8 @@
301
301
  display: flex;
302
302
  flex-direction: column;
303
303
  gap: 0.75rem;
304
+ container-name: field-remap-flow;
305
+ container-type: inline-size;
304
306
  }
305
307
 
306
308
  .workbench-field-remap-flow__workspace {
@@ -311,7 +313,79 @@
311
313
  align-items: stretch;
312
314
  }
313
315
 
314
- @media (max-width: 1100px) {
316
+ .workbench-field-remap-flow__workspace--without-palette {
317
+ grid-template-columns: minmax(0, 1fr) minmax(16rem, 20rem);
318
+ }
319
+
320
+ .workbench-field-remap-flow__workspace--split {
321
+ display: flex;
322
+ height: min(40rem, 70vh);
323
+ min-height: 26rem;
324
+ gap: 0;
325
+ }
326
+
327
+ .workbench-field-remap-flow__workspace--split > .ui-workbench-split-view,
328
+ .workbench-field-remap-flow__workspace--split
329
+ .ui-workbench-split-view__secondary
330
+ > .ui-workbench-split-view {
331
+ width: 100%;
332
+ height: 100%;
333
+ }
334
+
335
+ .workbench-field-remap-flow__workspace--split .workbench-field-remap-flow__canvas {
336
+ height: auto;
337
+ min-height: 0;
338
+ }
339
+
340
+ /* Embed hosts own the outer pane. Keep Flow rails flat and separate them with dividers only. */
341
+ .workbench-field-remap-flow[data-chrome='embed'] .workbench-field-remap-flow__workspace {
342
+ gap: 0;
343
+ }
344
+
345
+ .workbench-field-remap-flow[data-chrome='embed'] .workbench-field-remap-convert-palette,
346
+ .workbench-field-remap-flow[data-chrome='embed'] .workbench-field-remap-flow__canvas,
347
+ .workbench-field-remap-flow[data-chrome='embed'] .workbench-field-remap-detail,
348
+ .workbench-field-remap-flow[data-chrome='embed'] .workbench-field-remap-preview,
349
+ .workbench-field-remap-flow[data-chrome='embed'] .workbench-field-remap-convert-note {
350
+ border-radius: 0;
351
+ }
352
+
353
+ .workbench-field-remap-flow[data-chrome='embed'] .workbench-field-remap-convert-palette {
354
+ border: 0;
355
+ border-inline-end: 1px solid var(--vscode-panel-border, var(--color-border));
356
+ }
357
+
358
+ .workbench-field-remap-flow[data-chrome='embed'] .workbench-field-remap-flow__canvas {
359
+ border: 0;
360
+ }
361
+
362
+ .workbench-field-remap-flow[data-chrome='embed'] .workbench-field-remap-detail,
363
+ .workbench-field-remap-flow[data-chrome='embed'] .workbench-field-remap-preview,
364
+ .workbench-field-remap-flow[data-chrome='embed'] .workbench-field-remap-convert-note {
365
+ border: 0;
366
+ border-inline-start: 1px solid var(--vscode-panel-border, var(--color-border));
367
+ }
368
+
369
+ /* SplitView supplies the shared rail divider in embed mode, avoiding a doubled border. */
370
+ .workbench-field-remap-flow[data-chrome='embed']
371
+ .workbench-field-remap-flow__workspace--split
372
+ .workbench-field-remap-convert-palette,
373
+ .workbench-field-remap-flow[data-chrome='embed']
374
+ .workbench-field-remap-flow__workspace--split
375
+ .workbench-field-remap-flow__canvas,
376
+ .workbench-field-remap-flow[data-chrome='embed']
377
+ .workbench-field-remap-flow__workspace--split
378
+ .workbench-field-remap-detail,
379
+ .workbench-field-remap-flow[data-chrome='embed']
380
+ .workbench-field-remap-flow__workspace--split
381
+ .workbench-field-remap-preview,
382
+ .workbench-field-remap-flow[data-chrome='embed']
383
+ .workbench-field-remap-flow__workspace--split
384
+ .workbench-field-remap-convert-note {
385
+ border: 0;
386
+ }
387
+
388
+ @container field-remap-flow (max-width: 68.75rem) {
315
389
  .workbench-field-remap-flow__workspace {
316
390
  grid-template-columns: minmax(0, 1fr) minmax(16rem, 20rem);
317
391
  }
@@ -319,12 +393,24 @@
319
393
  .workbench-field-remap-convert-palette {
320
394
  grid-column: 1 / -1;
321
395
  }
396
+
397
+ .workbench-field-remap-flow[data-chrome='embed'] .workbench-field-remap-convert-palette {
398
+ border-inline-end: 0;
399
+ border-block-end: 1px solid var(--vscode-panel-border, var(--color-border));
400
+ }
322
401
  }
323
402
 
324
- @media (max-width: 960px) {
403
+ @container field-remap-flow (max-width: 60rem) {
325
404
  .workbench-field-remap-flow__workspace {
326
405
  grid-template-columns: 1fr;
327
406
  }
407
+
408
+ .workbench-field-remap-flow[data-chrome='embed'] .workbench-field-remap-detail,
409
+ .workbench-field-remap-flow[data-chrome='embed'] .workbench-field-remap-preview,
410
+ .workbench-field-remap-flow[data-chrome='embed'] .workbench-field-remap-convert-note {
411
+ border-inline-start: 0;
412
+ border-block-start: 1px solid var(--vscode-panel-border, var(--color-border));
413
+ }
328
414
  }
329
415
 
330
416
  .workbench-field-remap-convert-palette {
@@ -359,6 +445,7 @@
359
445
  }
360
446
 
361
447
  .workbench-field-remap-convert-palette__header,
448
+ .workbench-field-remap-convert-palette__filter,
362
449
  .workbench-field-remap-convert-palette__place,
363
450
  .workbench-field-remap-convert-palette__operators {
364
451
  flex: 0 0 auto;
@@ -377,6 +464,14 @@
377
464
  font-size: 0.7rem;
378
465
  }
379
466
 
467
+ .workbench-field-remap-convert-palette__empty {
468
+ flex: 1 1 auto;
469
+ margin: 0;
470
+ padding: 0.35rem;
471
+ font-size: 0.75rem;
472
+ color: var(--vscode-descriptionForeground, var(--color-text-muted));
473
+ }
474
+
380
475
  .workbench-field-remap-convert-palette__operators {
381
476
  display: flex;
382
477
  flex-direction: column;
@@ -693,6 +788,63 @@
693
788
  font-size: 0.8rem;
694
789
  }
695
790
 
791
+ .workbench-field-remap-flow__side-rail {
792
+ display: flex;
793
+ flex-direction: column;
794
+ min-width: 0;
795
+ min-height: 0;
796
+ height: 100%;
797
+ overflow: auto;
798
+ }
799
+
800
+ .workbench-field-remap-preview {
801
+ display: flex;
802
+ flex-direction: column;
803
+ gap: 0.6rem;
804
+ min-width: 0;
805
+ padding: 0.75rem;
806
+ border: 1px solid var(--vscode-panel-border, var(--color-border));
807
+ border-radius: 0.375rem;
808
+ background: var(--vscode-editor-background, var(--color-bg));
809
+ color: var(--vscode-foreground, var(--color-text));
810
+ font-size: 0.8rem;
811
+ }
812
+
813
+ .workbench-field-remap-flow__side-rail > .workbench-field-remap-preview:not(:first-child) {
814
+ border-block-start: 0;
815
+ border-start-start-radius: 0;
816
+ border-start-end-radius: 0;
817
+ }
818
+
819
+ .workbench-field-remap-preview h4,
820
+ .workbench-field-remap-preview p {
821
+ margin: 0;
822
+ }
823
+
824
+ .workbench-field-remap-preview h4 {
825
+ font-size: 0.85rem;
826
+ }
827
+
828
+ .workbench-field-remap-preview__scope,
829
+ .workbench-field-remap-preview__notice {
830
+ color: var(--vscode-descriptionForeground, var(--color-text-muted));
831
+ }
832
+
833
+ .workbench-field-remap-preview pre {
834
+ box-sizing: border-box;
835
+ max-height: 16rem;
836
+ margin: 0;
837
+ padding: 0.6rem;
838
+ overflow: auto;
839
+ border: 1px solid var(--vscode-panel-border, var(--color-border));
840
+ border-radius: 0.25rem;
841
+ background: var(--vscode-textCodeBlock-background, var(--color-surface));
842
+ color: inherit;
843
+ font: 0.75rem/1.45 var(--vscode-editor-font-family, ui-monospace, monospace);
844
+ white-space: pre-wrap;
845
+ word-break: break-word;
846
+ }
847
+
696
848
  .workbench-field-remap-convert-note {
697
849
  border-color: var(--vscode-focusBorder, var(--color-accent, #3794ff));
698
850
  background: var(--vscode-editor-background, var(--color-bg));
package/src/index.ts CHANGED
@@ -4,7 +4,12 @@ export {
4
4
  WorkbenchProvider,
5
5
  useWorkbench,
6
6
  type WorkbenchContextValue,
7
+ type WorkbenchExtensionActivationAccess,
8
+ type WorkbenchExtensionActivationStateReader,
9
+ type WorkbenchExtensionCatalogReader,
7
10
  type WorkbenchProviderProps,
11
+ type WorkbenchSettingsCapabilityPublication,
12
+ type WorkbenchSettingsCapabilityPublisher,
8
13
  type WorkbenchStorageAdapter,
9
14
  type WorkbenchWorkspaceHostPort,
10
15
  } from './shell/provider.js';
@@ -99,12 +104,20 @@ export {
99
104
  type SampleJdwWidgetPreviewRenderData,
100
105
  } from './jdw/document-view-data.js';
101
106
  export { SampleFieldRemapDemo, type SampleFieldRemapDemoProps } from './field-remap/demo.js';
102
- export { FieldRemapPanel, type FieldRemapPanelProps } from './field-remap/panel.js';
107
+ export {
108
+ FieldRemapPanel,
109
+ type FieldRemapHistoryActions,
110
+ type FieldRemapHistoryAvailability,
111
+ type FieldRemapHistoryOwner,
112
+ type FieldRemapHistorySnapshot,
113
+ type FieldRemapPanelProps,
114
+ } from './field-remap/panel.js';
103
115
  export {
104
116
  FieldRemapFlowMapper,
105
117
  type FieldRemapFlowActions,
106
118
  type FieldRemapFlowMapperProps,
107
119
  } from './field-remap/flow.js';
120
+ export type { FieldRemapPreviewState } from './field-remap/preview.js';
108
121
  export {
109
122
  defaultFieldRemapChromeLabels,
110
123
  fieldRemapChromeLabelKeys,
@@ -206,6 +219,8 @@ export {
206
219
  export {
207
220
  WorkbenchShell,
208
221
  type WorkbenchLocaleOption,
222
+ type WorkbenchShellCommandHostProps,
223
+ type WorkbenchShellCommandRunContext,
209
224
  type WorkbenchShellProps,
210
225
  type WorkbenchThemeOption,
211
226
  } from './shell/shell.js';
@@ -3,14 +3,19 @@ import {
3
3
  type WorkbenchKeybindingDefinition,
4
4
  } from '@workbench-kit/workbench-config';
5
5
  import {
6
+ type WorkbenchPersistenceDiagnosticOptions,
7
+ type WorkbenchPersistenceReadResult,
8
+ type WorkbenchPersistenceWriteResult,
6
9
  type WorkbenchStorageReader,
7
10
  type WorkbenchStorageWriter,
8
11
  } from '@workbench-kit/workbench-core';
9
12
 
10
13
  import {
11
14
  readLocalJsonStorage,
15
+ readLocalJsonStorageResult,
12
16
  resolveLocalWorkbenchStorage,
13
17
  writeLocalJsonStorage,
18
+ writeLocalJsonStorageResult,
14
19
  } from '../storage/local-json-storage.js';
15
20
 
16
21
  export const DEFAULT_WORKBENCH_KEYBINDING_STORAGE_KEY = 'workbench-kit/.workbench/keybindings';
@@ -26,6 +31,20 @@ export function readPersistedKeybindingOverrides(
26
31
  return readLocalJsonStorage(storageKey, parseWorkbenchKeybindingsConfig, () => [], storage);
27
32
  }
28
33
 
34
+ export function readPersistedKeybindingOverridesResult(
35
+ storageKey = DEFAULT_WORKBENCH_KEYBINDING_STORAGE_KEY,
36
+ storage?: WorkbenchStorageReader,
37
+ options: WorkbenchPersistenceDiagnosticOptions = {},
38
+ ): WorkbenchPersistenceReadResult<readonly WorkbenchKeybindingDefinition[]> {
39
+ return readLocalJsonStorageResult(
40
+ storageKey,
41
+ parseWorkbenchKeybindingsConfig,
42
+ () => [],
43
+ storage,
44
+ options,
45
+ );
46
+ }
47
+
29
48
  export function writePersistedKeybindingOverrides(
30
49
  overrides: readonly WorkbenchKeybindingDefinition[],
31
50
  storageKey = DEFAULT_WORKBENCH_KEYBINDING_STORAGE_KEY,
@@ -33,3 +52,12 @@ export function writePersistedKeybindingOverrides(
33
52
  ): void {
34
53
  writeLocalJsonStorage(storageKey, overrides, storage, { errorMode: 'throw' });
35
54
  }
55
+
56
+ export function writePersistedKeybindingOverridesResult(
57
+ overrides: readonly WorkbenchKeybindingDefinition[],
58
+ storageKey = DEFAULT_WORKBENCH_KEYBINDING_STORAGE_KEY,
59
+ storage?: WorkbenchStorageWriter,
60
+ options: WorkbenchPersistenceDiagnosticOptions = {},
61
+ ): WorkbenchPersistenceWriteResult {
62
+ return writeLocalJsonStorageResult(storageKey, overrides, storage, options);
63
+ }
@@ -3,14 +3,19 @@ import {
3
3
  type WorkbenchSettingsConfig,
4
4
  } from '@workbench-kit/workbench-config';
5
5
  import {
6
+ type WorkbenchPersistenceDiagnosticOptions,
7
+ type WorkbenchPersistenceReadResult,
8
+ type WorkbenchPersistenceWriteResult,
6
9
  type WorkbenchStorageReader,
7
10
  type WorkbenchStorageWriter,
8
11
  } from '@workbench-kit/workbench-core';
9
12
 
10
13
  import {
11
14
  readLocalJsonStorage,
15
+ readLocalJsonStorageResult,
12
16
  resolveLocalWorkbenchStorage,
13
17
  writeLocalJsonStorage,
18
+ writeLocalJsonStorageResult,
14
19
  } from '../storage/local-json-storage.js';
15
20
 
16
21
  export const DEFAULT_WORKBENCH_LOCAL_PREFERENCE_STORAGE_KEY =
@@ -27,6 +32,20 @@ export function readPersistedLocalPreferences(
27
32
  return readLocalJsonStorage(storageKey, parseWorkbenchSettingsConfig, () => ({}), storage);
28
33
  }
29
34
 
35
+ export function readPersistedLocalPreferencesResult(
36
+ storageKey = DEFAULT_WORKBENCH_LOCAL_PREFERENCE_STORAGE_KEY,
37
+ storage?: WorkbenchStorageReader,
38
+ options: WorkbenchPersistenceDiagnosticOptions = {},
39
+ ): WorkbenchPersistenceReadResult<WorkbenchSettingsConfig> {
40
+ return readLocalJsonStorageResult(
41
+ storageKey,
42
+ parseWorkbenchSettingsConfig,
43
+ () => ({}),
44
+ storage,
45
+ options,
46
+ );
47
+ }
48
+
30
49
  export function writePersistedLocalPreferences(
31
50
  values: WorkbenchSettingsConfig,
32
51
  storageKey = DEFAULT_WORKBENCH_LOCAL_PREFERENCE_STORAGE_KEY,
@@ -34,3 +53,12 @@ export function writePersistedLocalPreferences(
34
53
  ): void {
35
54
  writeLocalJsonStorage(storageKey, values, storage, { errorMode: 'throw' });
36
55
  }
56
+
57
+ export function writePersistedLocalPreferencesResult(
58
+ values: WorkbenchSettingsConfig,
59
+ storageKey = DEFAULT_WORKBENCH_LOCAL_PREFERENCE_STORAGE_KEY,
60
+ storage?: WorkbenchStorageWriter,
61
+ options: WorkbenchPersistenceDiagnosticOptions = {},
62
+ ): WorkbenchPersistenceWriteResult {
63
+ return writeLocalJsonStorageResult(storageKey, values, storage, options);
64
+ }
@@ -55,6 +55,7 @@ export function WorkbenchExtensionManagementSettings({
55
55
  installTrustRecords,
56
56
  rememberInstallTrust,
57
57
  toggleInstalledEntry,
58
+ uninstallInstalledEntry,
58
59
  } = useExtensionManagementModel({ catalogTrustPolicy, catalogUrl });
59
60
 
60
61
  const isInstallTrusted = useCallback(
@@ -77,6 +78,7 @@ export function WorkbenchExtensionManagementSettings({
77
78
  onInstall={installCatalogEntry}
78
79
  onRememberInstallTrust={rememberInstallTrust}
79
80
  onToggleEnabled={toggleInstalledEntry}
81
+ onUninstall={uninstallInstalledEntry}
80
82
  />
81
83
  );
82
84
  }