Skip to content

Commit 2bda9da

Browse files
fix(Sky): Support named-key payloads and register resolver for custom editors
The previous implementation in InstallWebview incorrectly assumed positional arguments for custom editor payloads. After the Cocoon extension host switched to named-key payloads (containing `viewType`, `options`, and `selector`), the editor failed to register custom editor capabilities because `Args[1]` was empty. Update the payload parsing logic to defensively handle both the new named-key format and the legacy positional format. Extract `viewType`, `options`, and `selector` from the primary payload fields, falling back to the `args` array only if necessary. Additionally, register the custom editor with `IEditorResolverService` using a `priority.option` fallback. This ensures the VS Code activation event (`onCustomEditor:*`) fires correctly when manifest-based registration is delayed due to lazy extension activation. The resolver includes a safe `createEditorInput` implementation that defers to the builtin factory to prevent crashes if the resolver is invoked before the extension is fully ready. These changes restore full functionality for custom editors (e.g., Markdown preview, image viewers) and ensure proper lifecycle management during extension activation.
1 parent f8b6900 commit 2bda9da

1 file changed

Lines changed: 92 additions & 7 deletions

File tree

Source/Function/Sky/Bridge/InstallWebview.ts

Lines changed: 92 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -754,14 +754,42 @@ export default async (Dependencies: {
754754
const Services: any = (globalThis as any).__CEL_SERVICES__;
755755
if (!Services?.CustomEditor?.registerCustomEditorCapabilities)
756756
return;
757-
const Args = Array.isArray(Payload?.args) ? Payload.args : [];
758-
const ViewType: string = String(Args[1] ?? "");
759-
const Options =
760-
typeof Args[2] === "object" && Args[2] !== null
761-
? (Args[2] as Record<string, unknown>)
762-
: {};
757+
758+
// Resolve viewType from either payload format defensively.
759+
// New (named-key): { viewType, options, selector, handle }
760+
// Old (positional): { args: [handle, viewType, options, ...] }
761+
// The old code read ONLY Args[1], which was always "" after Cocoon
762+
// switched to named-key payloads, silently skipping registration.
763+
const Args: unknown[] = Array.isArray(Payload?.args)
764+
? Payload.args
765+
: [];
766+
const ViewType: string = String(
767+
Payload?.viewType ??
768+
(typeof Args[1] === "string" && Args[1].length > 0
769+
? Args[1]
770+
: undefined) ??
771+
"",
772+
);
773+
774+
// Options: prefer named-key payload.options, fall back to Args[2].
775+
const Options: Record<string, unknown> =
776+
Payload?.options !== null &&
777+
typeof Payload?.options === "object"
778+
? (Payload.options as Record<string, unknown>)
779+
: Args[2] !== null && typeof Args[2] === "object"
780+
? (Args[2] as Record<string, unknown>)
781+
: {};
782+
783+
// Selector: glob patterns like [{ filenamePattern: "*.{png,...}" }]
784+
const Selector: unknown[] = Array.isArray(Payload?.selector)
785+
? Payload.selector
786+
: [];
787+
763788
if (!ViewType || CustomEditorCapabilityHandles.has(ViewType))
764789
return;
790+
791+
// Register capabilities (metadata used by VS Code's
792+
// CustomEditorContribution for multi-editor and lifecycle).
765793
const Disposable =
766794
Services.CustomEditor.registerCustomEditorCapabilities(
767795
ViewType,
@@ -771,7 +799,64 @@ export default async (Dependencies: {
771799
),
772800
},
773801
);
774-
CustomEditorCapabilityHandles.set(ViewType, Disposable);
802+
if (Disposable != null) {
803+
CustomEditorCapabilityHandles.set(ViewType, Disposable);
804+
}
805+
806+
// Also register with IEditorResolverService so VS Code routes
807+
// matching file opens to this custom editor. This is a fallback
808+
// for when CustomEditorContribution's manifest-based registration
809+
// hasn't fired yet (e.g. extension activates lazily on first open).
810+
// Priority is `option` so VS Code's builtin factory (from the
811+
// manifest contribution) takes precedence when both are registered.
812+
const EditorResolver = Services?.EditorResolver;
813+
const Priority = Services?.RegisteredEditorPriority;
814+
if (
815+
typeof EditorResolver?.registerEditor === "function" &&
816+
Selector.length > 0 &&
817+
Priority
818+
) {
819+
for (const S of Selector) {
820+
const GlobPattern =
821+
typeof S === "string"
822+
? S
823+
: typeof (S as any)?.filenamePattern === "string"
824+
? (S as any).filenamePattern
825+
: null;
826+
if (!GlobPattern) continue;
827+
try {
828+
EditorResolver.registerEditor(
829+
GlobPattern,
830+
{
831+
id: ViewType,
832+
label: String(
833+
(Options as any)["displayName"] ?? ViewType,
834+
),
835+
priority: Priority.option,
836+
},
837+
{},
838+
{
839+
// createEditorInput MUST NOT return undefined/null -
840+
// VS Code would crash trying to read .editor on the
841+
// result. We throw so the resolver falls through to
842+
// CustomEditorContribution's builtin factory, which
843+
// is tried first (higher priority) and is the
844+
// canonical path for custom editors. Our registration
845+
// here serves primarily to trigger the
846+
// onCustomEditor:* activation event so the extension
847+
// activates and registers its provider.
848+
createEditorInput: () => {
849+
throw new Error(
850+
`[Sky:CEL] defer-to-builtin:${ViewType}`,
851+
);
852+
},
853+
},
854+
);
855+
} catch {
856+
// Non-fatal: manifest-based registration still works.
857+
}
858+
}
859+
}
775860
} catch (Error) {
776861
try {
777862
const W = globalThis as any;

0 commit comments

Comments
 (0)