@lvce-editor/api 9.28.3 → 9.29.1

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.
package/dist/index.d.ts CHANGED
@@ -27,10 +27,11 @@ export { getLanguageServerRegistrySnapshot, registerLanguageServer, resetLanguag
27
27
  export { registerBraceCompletionProvider, registerClosingTagProvider, registerCodeActionsProvider, registerCommentProvider, registerDefinitionProvider, registerDocumentSymbolProvider, registerImplementationProvider, registerReferenceProvider, registerRenameProvider, registerSelectionProvider, registerTabCompletionProvider, registerTypeDefinitionProvider, resetLanguageProviderRegistry, } from './parts/LanguageProvider/LanguageProvider.ts';
28
28
  export type { DocumentSymbol, DocumentSymbolProvider, DocumentSymbolTextDocument, LanguageProvider, } from './parts/LanguageProvider/LanguageProvider.ts';
29
29
  export { executeSourceControlAcceptInput, executeSourceControlAdd, executeSourceControlDiscard, executeSourceControlGenerateCommitMessage, executeSourceControlGetBadgeCount, executeSourceControlGetChangedFiles, executeSourceControlGetFeatures, executeSourceControlGetFileBefore, executeSourceControlGetFileBeforeUri, executeSourceControlGetFileDecorations, executeSourceControlGetGroups, executeSourceControlIsActive, registerSourceControlProvider, resetSourceControlProviderRegistry, } from './parts/SourceControl/SourceControl.ts';
30
- export { createViewInstance, dispatchViewEvent, disposeViewInstance, executeViewProvider, getViewRegistrySnapshot, renderViewInstance, registerView, resetViewRegistry, saveViewInstanceState, setViewInstanceActive, } from './parts/ViewRegistry/ViewRegistry.ts';
30
+ export { createViewInstance, dispatchViewEvent, disposeViewInstance, executeViewProvider, getViewInstanceState, getViewRegistrySnapshot, renderViewInstance, registerView, resetViewRegistry, saveViewInstanceState, setViewInstanceState, setViewInstanceActive, } from './parts/ViewRegistry/ViewRegistry.ts';
31
+ export * as ViewletStates from './parts/PublicViewletStates/PublicViewletStates.ts';
31
32
  export { createOutputChannel, getOutputChannelLogs, getOutputChannelRegistrySnapshot, resetOutputChannelRegistry, } from './parts/OutputChannel/OutputChannel.ts';
32
33
  export { getStatusBarItemProviderRegistrySnapshot, registerStatusBarItemProvider, resetStatusBarItemProviderRegistry, } from './parts/StatusBar/StatusBar.ts';
33
- export type { RegisteredView, View, ViewAction, ViewCommand, ViewContext, ViewEvent, ViewKind, ViewPreferredLocation, MenuEntry, ViewRegistrySnapshot, ViewRenderResult, ViewScrollPosition, ViewSelection, VirtualDomViewInstance, } from './parts/View/View.ts';
34
+ export type { RegisteredView, InstanceView, StatefulView, View, ViewAction, ViewCommand, ViewContext, ViewEvent, ViewKind, ViewPreferredLocation, MenuEntry, ViewRegistrySnapshot, ViewRenderResult, ViewScrollPosition, ViewSelection, VirtualDomViewInstance, } from './parts/View/View.ts';
34
35
  export { handleExtensionManagementMessagePort } from './parts/HandleExtensionManagementMessagePort/HandleExtensionManagementMessagePort.ts';
35
36
  export { createNodeRpc, createRpc } from './parts/Rpc/Rpc.ts';
36
37
  export type { Command } from './parts/Command/Command.ts';
package/dist/index.js CHANGED
@@ -26,7 +26,8 @@ export { openDebugConsole, openOutputView, openProblemsView, } from "./parts/Pan
26
26
  export { getLanguageServerRegistrySnapshot, registerLanguageServer, resetLanguageServerRegistry } from "./parts/LanguageServer/LanguageServer.js";
27
27
  export { registerBraceCompletionProvider, registerClosingTagProvider, registerCodeActionsProvider, registerCommentProvider, registerDefinitionProvider, registerDocumentSymbolProvider, registerImplementationProvider, registerReferenceProvider, registerRenameProvider, registerSelectionProvider, registerTabCompletionProvider, registerTypeDefinitionProvider, resetLanguageProviderRegistry, } from "./parts/LanguageProvider/LanguageProvider.js";
28
28
  export { executeSourceControlAcceptInput, executeSourceControlAdd, executeSourceControlDiscard, executeSourceControlGenerateCommitMessage, executeSourceControlGetBadgeCount, executeSourceControlGetChangedFiles, executeSourceControlGetFeatures, executeSourceControlGetFileBefore, executeSourceControlGetFileBeforeUri, executeSourceControlGetFileDecorations, executeSourceControlGetGroups, executeSourceControlIsActive, registerSourceControlProvider, resetSourceControlProviderRegistry, } from "./parts/SourceControl/SourceControl.js";
29
- export { createViewInstance, dispatchViewEvent, disposeViewInstance, executeViewProvider, getViewRegistrySnapshot, renderViewInstance, registerView, resetViewRegistry, saveViewInstanceState, setViewInstanceActive, } from "./parts/ViewRegistry/ViewRegistry.js";
29
+ export { createViewInstance, dispatchViewEvent, disposeViewInstance, executeViewProvider, getViewInstanceState, getViewRegistrySnapshot, renderViewInstance, registerView, resetViewRegistry, saveViewInstanceState, setViewInstanceState, setViewInstanceActive, } from "./parts/ViewRegistry/ViewRegistry.js";
30
+ export * as ViewletStates from "./parts/PublicViewletStates/PublicViewletStates.js";
30
31
  export { createOutputChannel, getOutputChannelLogs, getOutputChannelRegistrySnapshot, resetOutputChannelRegistry, } from "./parts/OutputChannel/OutputChannel.js";
31
32
  export { getStatusBarItemProviderRegistrySnapshot, registerStatusBarItemProvider, resetStatusBarItemProviderRegistry, } from "./parts/StatusBar/StatusBar.js";
32
33
  export { handleExtensionManagementMessagePort } from "./parts/HandleExtensionManagementMessagePort/HandleExtensionManagementMessagePort.js";
@@ -46,7 +46,7 @@ export const getFileHashes = async (uris) => {
46
46
  return FileSystemWorker.invoke('FileSystem.getFileHashes', uris);
47
47
  };
48
48
  export const readFile = async (uri) => {
49
- if (isMemory(uri)) {
49
+ if (isCustomFileSystemUri(uri)) {
50
50
  return ExtensionManagementWorker.invoke('ExtensionApi.readFile', uri);
51
51
  }
52
52
  return FileSystemWorker.readFile(uri);
@@ -0,0 +1 @@
1
+ export { get, set } from '../ViewletStates/ViewletStates.ts';
@@ -0,0 +1 @@
1
+ export { get, set } from "../ViewletStates/ViewletStates.js";
@@ -62,9 +62,8 @@ export interface VirtualDomViewInstance {
62
62
  readonly saveState?: () => unknown;
63
63
  }
64
64
  export type ViewCommand<State = VirtualDomViewInstance, Args extends readonly any[] = readonly any[]> = (state: State, ...args: Args) => State | Promise<State>;
65
- export interface View<State = unknown> {
65
+ interface ViewBase<State> {
66
66
  readonly commands?: Readonly<Record<string, ViewCommand<State>>>;
67
- readonly create: (context?: ViewContext) => State | Promise<State>;
68
67
  readonly displayName?: string;
69
68
  readonly eventListeners?: readonly DomEventListener[];
70
69
  readonly icon?: string;
@@ -74,6 +73,29 @@ export interface View<State = unknown> {
74
73
  readonly preferredLocation?: ViewPreferredLocation;
75
74
  readonly title?: string;
76
75
  }
76
+ export interface InstanceView<State = unknown> extends ViewBase<State> {
77
+ readonly create: (context?: ViewContext) => State | Promise<State>;
78
+ readonly createInitialState?: never;
79
+ }
80
+ export interface StatefulView<State = unknown> extends ViewBase<State> {
81
+ readonly create?: never;
82
+ readonly createInitialState: (context?: ViewContext) => State | Promise<State>;
83
+ readonly dispose?: (state: State) => unknown;
84
+ readonly getContext?: (state: State) => Readonly<Record<string, boolean>>;
85
+ readonly getCss?: (state: State) => string | Promise<string>;
86
+ readonly getMenuEntries?: (state: State, menuId: string) => readonly MenuEntry[] | Promise<readonly MenuEntry[]>;
87
+ readonly handleEvent?: (state: State, event: ViewEvent) => State | Promise<State>;
88
+ readonly render: (state: State) => readonly VirtualDomNode[] | Promise<readonly VirtualDomNode[]>;
89
+ readonly renderActions?: (state: State) => readonly ViewAction[] | Promise<readonly ViewAction[]>;
90
+ readonly renderActionsDom?: (state: State) => readonly VirtualDomNode[];
91
+ readonly renderFocus?: (state: State, oldContext: Readonly<Record<string, boolean>>, newContext: Readonly<Record<string, boolean>>) => string | Promise<string>;
92
+ readonly renderScrollPosition?: (state: State) => readonly [] | ViewScrollPosition | Promise<readonly [] | ViewScrollPosition>;
93
+ readonly renderSelections?: (state: State) => readonly ViewSelection[] | Promise<readonly ViewSelection[]>;
94
+ readonly renderStatusBarItems?: (state: State) => readonly StatusBarItem[] | Promise<readonly StatusBarItem[]>;
95
+ readonly renderTitle?: (state: State) => string | Promise<string>;
96
+ readonly saveState?: (state: State) => unknown;
97
+ }
98
+ export type View<State = unknown> = InstanceView<State> | StatefulView<State>;
77
99
  export interface RegisteredView {
78
100
  readonly displayName?: string;
79
101
  readonly eventListeners?: readonly DomEventListener[];
@@ -82,6 +104,7 @@ export interface RegisteredView {
82
104
  readonly kind?: ViewKind;
83
105
  readonly name?: string;
84
106
  readonly preferredLocation: ViewPreferredLocation;
107
+ readonly stateful?: boolean;
85
108
  readonly title?: string;
86
109
  }
87
110
  export interface ViewRegistrySnapshot {
@@ -106,3 +129,4 @@ export interface ViewRenderResultPatches {
106
129
  readonly type: 'setPatches';
107
130
  }
108
131
  export type ViewRenderResult = ViewRenderResultDom | ViewRenderResultPatches;
132
+ export {};
@@ -9,6 +9,8 @@ export declare const renderViewInstance: (uid: number) => Promise<ViewRenderResu
9
9
  export declare const setViewInstanceActive: (uid: number, active: boolean) => Promise<void>;
10
10
  export declare const disposeViewInstance: (uid: number) => Promise<void>;
11
11
  export declare const saveViewInstanceState: (uid: number) => Promise<unknown>;
12
+ export declare const getViewInstanceState: (uid: number) => unknown;
13
+ export declare const setViewInstanceState: (uid: number, newState: unknown) => Promise<ViewRenderResult>;
12
14
  export declare const getViewMenuEntries: (uid: number, menuId: string) => Promise<readonly MenuEntry[]>;
13
15
  export declare const getViewActions: (uid: number) => Promise<readonly ViewAction[]>;
14
16
  export declare const getViewActionsDom: (uid: number) => readonly VirtualDomNode[] | undefined;
@@ -3,6 +3,7 @@ import { diffTree, validate as validateVirtualDom } from '@lvce-editor/virtual-d
3
3
  import { registerCommand } from "../CommandRegistry/CommandRegistry.js";
4
4
  import * as ExtensionApiCommandRegistry from "../ExtensionApiCommandRegistry/ExtensionApiCommandRegistry.js";
5
5
  import { ExtensionApiError } from "../ExtensionApiError/ExtensionApiError.js";
6
+ import * as ViewletStates from "../ViewletStates/ViewletStates.js";
6
7
  import * as ViewStatusBarItems from "../ViewStatusBarItems/ViewStatusBarItems.js";
7
8
  const views = Object.create(null);
8
9
  const instances = Object.create(null);
@@ -11,6 +12,7 @@ const renderedDoms = Object.create(null);
11
12
  const contexts = Object.create(null);
12
13
  const contextViewIds = Object.create(null);
13
14
  const viewCommandDisposables = Object.create(null);
15
+ const isStatefulView = (view) => typeof view.createInitialState === 'function';
14
16
  const assertBoolean = (value, message) => {
15
17
  if (value !== undefined && typeof value !== 'boolean') {
16
18
  throw new ExtensionApiError(message);
@@ -82,9 +84,13 @@ const assertView = (view) => {
82
84
  if (typeof view.id !== 'string' || view.id.length === 0) {
83
85
  throw new ExtensionApiError('view is missing id');
84
86
  }
85
- if (typeof view.create !== 'function') {
87
+ const candidate = view;
88
+ if (typeof candidate.create !== 'function' && typeof candidate.createInitialState !== 'function') {
86
89
  throw new ExtensionApiError(`view ${view.id} is missing create function`);
87
90
  }
91
+ if (typeof candidate.createInitialState === 'function' && typeof candidate.render !== 'function') {
92
+ throw new ExtensionApiError(`view ${view.id} is missing render function`);
93
+ }
88
94
  if (view.id in views) {
89
95
  throw new ExtensionApiError(`view ${view.id} is already registered`);
90
96
  }
@@ -103,6 +109,7 @@ const toRegisteredView = (view) => {
103
109
  id: view.id,
104
110
  name: view.name,
105
111
  preferredLocation: view.preferredLocation || 'sideBar',
112
+ ...(isStatefulView(view) && { stateful: true }),
106
113
  title: displayName,
107
114
  };
108
115
  if (view.kind) {
@@ -131,9 +138,16 @@ const executeViewCommand = async (view, commandId, args) => {
131
138
  }
132
139
  const [uid, instance] = activeInstance;
133
140
  const command = view.commands[commandId];
134
- const newInstance = await command(instance, ...args);
135
- assertVirtualDomViewInstance(view.id, newInstance);
136
- instances[uid] = newInstance;
141
+ if (isStatefulView(view)) {
142
+ const newState = await command(ViewletStates.get(uid), ...args);
143
+ assertViewState(view.id, newState);
144
+ ViewletStates.replace(uid, newState);
145
+ }
146
+ else {
147
+ const newInstance = await command(instance, ...args);
148
+ assertVirtualDomViewInstance(view.id, newInstance);
149
+ instances[uid] = newInstance;
150
+ }
137
151
  await ExtensionManagementWorker.invoke('Extensions.requestViewRerender', uid);
138
152
  };
139
153
  const registerViewCommands = (view) => {
@@ -177,7 +191,7 @@ export const executeViewProvider = (id) => {
177
191
  if (!view) {
178
192
  throw new ExtensionApiError(`view ${id} not found`);
179
193
  }
180
- return view.create();
194
+ return isStatefulView(view) ? view.createInitialState() : view.create();
181
195
  };
182
196
  const assertVirtualDomViewInstance = (id, instance) => {
183
197
  if (!instance || typeof instance !== 'object') {
@@ -187,6 +201,62 @@ const assertVirtualDomViewInstance = (id, instance) => {
187
201
  throw new ExtensionApiError(`view ${id} instance is missing render function`);
188
202
  }
189
203
  };
204
+ const assertViewState = (viewId, state) => {
205
+ if (!state || typeof state !== 'object' || Array.isArray(state)) {
206
+ throw new ExtensionApiError(`view ${viewId} state must be an object`);
207
+ }
208
+ };
209
+ const createStatefulViewInstance = (view, uid) => {
210
+ const instance = {
211
+ render() {
212
+ return view.render(ViewletStates.get(uid));
213
+ },
214
+ };
215
+ if (view.dispose) {
216
+ instance.dispose = () => view.dispose(ViewletStates.get(uid));
217
+ }
218
+ if (view.getContext) {
219
+ instance.getContext = () => view.getContext(ViewletStates.get(uid));
220
+ }
221
+ if (view.getCss) {
222
+ instance.getCss = () => view.getCss(ViewletStates.get(uid));
223
+ }
224
+ if (view.getMenuEntries) {
225
+ instance.getMenuEntries = (menuId) => view.getMenuEntries(ViewletStates.get(uid), menuId);
226
+ }
227
+ if (view.handleEvent) {
228
+ instance.handleEvent = async (event) => {
229
+ const newState = await view.handleEvent(ViewletStates.get(uid), event);
230
+ assertViewState(view.id, newState);
231
+ ViewletStates.replace(uid, newState);
232
+ };
233
+ }
234
+ if (view.renderActions) {
235
+ instance.renderActions = () => view.renderActions(ViewletStates.get(uid));
236
+ }
237
+ if (view.renderActionsDom) {
238
+ instance.renderActionsDom = () => view.renderActionsDom(ViewletStates.get(uid));
239
+ }
240
+ if (view.renderFocus) {
241
+ instance.renderFocus = (oldContext, newContext) => view.renderFocus(ViewletStates.get(uid), oldContext, newContext);
242
+ }
243
+ if (view.renderScrollPosition) {
244
+ instance.renderScrollPosition = () => view.renderScrollPosition(ViewletStates.get(uid));
245
+ }
246
+ if (view.renderSelections) {
247
+ instance.renderSelections = () => view.renderSelections(ViewletStates.get(uid));
248
+ }
249
+ if (view.renderStatusBarItems) {
250
+ instance.renderStatusBarItems = () => view.renderStatusBarItems(ViewletStates.get(uid));
251
+ }
252
+ if (view.renderTitle) {
253
+ instance.renderTitle = () => view.renderTitle(ViewletStates.get(uid));
254
+ }
255
+ if (view.saveState) {
256
+ instance.saveState = () => view.saveState(ViewletStates.get(uid));
257
+ }
258
+ return instance;
259
+ };
190
260
  const getVirtualDomInstance = (uid) => {
191
261
  const instance = instances[uid];
192
262
  if (!instance) {
@@ -448,7 +518,7 @@ export const createViewInstance = async (viewId, uid, context) => {
448
518
  if (view.kind !== 'virtualDom') {
449
519
  throw new ExtensionApiError(`view ${viewId} is not a virtual dom view`);
450
520
  }
451
- const instance = await view.create({
521
+ const viewContext = {
452
522
  ...context,
453
523
  requestRerender() {
454
524
  return ExtensionManagementWorker.invoke('Extensions.requestViewRerender', uid);
@@ -461,7 +531,19 @@ export const createViewInstance = async (viewId, uid, context) => {
461
531
  },
462
532
  uid,
463
533
  viewId,
464
- });
534
+ };
535
+ let instance;
536
+ if (isStatefulView(view)) {
537
+ const initialState = await view.createInitialState(viewContext);
538
+ assertViewState(viewId, initialState);
539
+ ViewletStates.initialize(uid, initialState);
540
+ instance = createStatefulViewInstance(view, uid);
541
+ }
542
+ else {
543
+ const createdInstance = await view.create(viewContext);
544
+ assertVirtualDomViewInstance(viewId, createdInstance);
545
+ instance = createdInstance;
546
+ }
465
547
  assertVirtualDomViewInstance(viewId, instance);
466
548
  instances[uid] = instance;
467
549
  const instanceUids = instanceUidsByView[viewId] || new Set();
@@ -531,6 +613,7 @@ export const disposeViewInstance = async (uid) => {
531
613
  delete instances[uid];
532
614
  delete renderedDoms[uid];
533
615
  delete contextViewIds[uid];
616
+ ViewletStates.remove(uid);
534
617
  }
535
618
  }
536
619
  };
@@ -541,6 +624,24 @@ export const saveViewInstanceState = async (uid) => {
541
624
  }
542
625
  return instance.saveState();
543
626
  };
627
+ export const getViewInstanceState = (uid) => {
628
+ getVirtualDomInstance(uid);
629
+ return ViewletStates.get(uid);
630
+ };
631
+ export const setViewInstanceState = async (uid, newState) => {
632
+ getVirtualDomInstance(uid);
633
+ const viewId = contextViewIds[uid];
634
+ assertViewState(viewId, newState);
635
+ const oldState = ViewletStates.get(uid);
636
+ ViewletStates.replace(uid, newState);
637
+ try {
638
+ return await renderViewInstance(uid);
639
+ }
640
+ catch (error) {
641
+ ViewletStates.replace(uid, oldState);
642
+ throw error;
643
+ }
644
+ };
544
645
  export const getViewMenuEntries = async (uid, menuId) => {
545
646
  assertString(menuId, 'menuId must be a string');
546
647
  const instance = getVirtualDomInstance(uid);
@@ -579,14 +680,17 @@ const commandMap = {
579
680
  'ExtensionApi.executeViewProvider': executeViewProvider,
580
681
  'ExtensionApi.getViewActions': getViewActions,
581
682
  'ExtensionApi.getViewActionsDom': getViewActionsDom,
683
+ 'ExtensionApi.getViewInstanceState': getViewInstanceState,
582
684
  'ExtensionApi.getViewMenuEntries': getViewMenuEntries,
583
685
  'ExtensionApi.getViewRegistrySnapshot': getViewRegistrySnapshot,
584
686
  'ExtensionApi.renderViewInstance': renderViewInstance,
585
687
  'ExtensionApi.saveViewInstanceState': saveViewInstanceState,
586
688
  'ExtensionApi.setViewInstanceActive': setViewInstanceActive,
689
+ 'ExtensionApi.setViewInstanceState': setViewInstanceState,
587
690
  };
588
691
  export const resetViewRegistry = () => {
589
692
  ViewStatusBarItems.resetViewStatusBarItems();
693
+ ViewletStates.reset();
590
694
  for (const id of Object.keys(views)) {
591
695
  for (const disposable of viewCommandDisposables[id] || []) {
592
696
  disposable.dispose();
@@ -0,0 +1,7 @@
1
+ export declare const has: (uid: number) => boolean;
2
+ export declare const get: <State = unknown>(uid: number) => State;
3
+ export declare const replace: (uid: number, newState: unknown) => void;
4
+ export declare const set: (uid: number, newState: unknown) => Promise<void>;
5
+ export declare const initialize: (uid: number, initialState: unknown) => void;
6
+ export declare const remove: (uid: number) => void;
7
+ export declare const reset: () => void;
@@ -0,0 +1,38 @@
1
+ import { ExtensionManagementWorker } from '@lvce-editor/rpc-registry';
2
+ import { ExtensionApiError } from "../ExtensionApiError/ExtensionApiError.js";
3
+ const states = Object.create(null);
4
+ const assertState = (state) => {
5
+ if (!state || typeof state !== 'object' || Array.isArray(state)) {
6
+ throw new ExtensionApiError('view state must be an object');
7
+ }
8
+ };
9
+ export const has = (uid) => Object.hasOwn(states, uid);
10
+ export const get = (uid) => {
11
+ if (!has(uid)) {
12
+ throw new ExtensionApiError(`view state ${uid} not found`);
13
+ }
14
+ return states[uid];
15
+ };
16
+ export const replace = (uid, newState) => {
17
+ if (!has(uid)) {
18
+ throw new ExtensionApiError(`view state ${uid} not found`);
19
+ }
20
+ assertState(newState);
21
+ states[uid] = newState;
22
+ };
23
+ export const set = async (uid, newState) => {
24
+ replace(uid, newState);
25
+ await ExtensionManagementWorker.invoke('Extensions.requestViewRerender', uid);
26
+ };
27
+ export const initialize = (uid, initialState) => {
28
+ assertState(initialState);
29
+ states[uid] = initialState;
30
+ };
31
+ export const remove = (uid) => {
32
+ delete states[uid];
33
+ };
34
+ export const reset = () => {
35
+ for (const uid of Object.keys(states)) {
36
+ delete states[Number(uid)];
37
+ }
38
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/api",
3
- "version": "9.28.3",
3
+ "version": "9.29.1",
4
4
  "description": "Tree-shakeable extension API for Lvce Editor extensions.",
5
5
  "keywords": [
6
6
  "lvce-editor",
@@ -28,8 +28,8 @@
28
28
  "sideEffects": false,
29
29
  "dependencies": {
30
30
  "@lvce-editor/command": "^2.0.0",
31
- "@lvce-editor/rpc": "^6.4.0",
32
- "@lvce-editor/rpc-registry": "^9.24.0",
33
- "@lvce-editor/virtual-dom-worker": "^11.2.0"
31
+ "@lvce-editor/rpc": "^6.16.0",
32
+ "@lvce-editor/rpc-registry": "^9.48.0",
33
+ "@lvce-editor/virtual-dom-worker": "^11.12.1"
34
34
  }
35
35
  }