3030 * sky://ui/show-message-request → shows a dialog/notification
3131 */
3232
33+ import { invoke } from "@tauri-apps/api/core" ;
3334import { listen } from "@tauri-apps/api/event" ;
3435
3536// ============================================================================
@@ -244,6 +245,28 @@ export async function InstallSkyBridge(): Promise<void> {
244245 Cleanups . push ( Unlisten ) ;
245246 } ;
246247
248+ // Atom Q1: resolve UI requests via Mountain's `ResolveUIRequest` Tauri
249+ // command (registered in CommandRegister). Mountain emits
250+ // `sky://ui/show-*-request` with shape `{ RequestIdentifier, Payload }`
251+ // and waits on a oneshot keyed by RequestIdentifier. We MUST send back a
252+ // ResolveUIRequest invocation with the exact same identifier or the
253+ // 300s timeout in UserInterfaceProvider fires. Declared here so every
254+ // listener below can reference it.
255+ const ResolveUiRequest = (
256+ RequestIdentifier : string ,
257+ Result : unknown ,
258+ ) : Promise < void > =>
259+ invoke < void > ( "ResolveUIRequest" , {
260+ RequestID : RequestIdentifier ,
261+ Result,
262+ } ) . catch ( ( Error ) => {
263+ console . warn (
264+ "[SkyBridge] ResolveUIRequest failed" ,
265+ RequestIdentifier ,
266+ Error ,
267+ ) ;
268+ } ) ;
269+
247270 // ---- Editor ----
248271 await Register ( "sky://editor/openDocument" , ( { uri, viewColumn } : any ) => {
249272 const Wb = GetWorkbench ( ) ;
@@ -270,6 +293,85 @@ export async function InstallSkyBridge(): Promise<void> {
270293 . catch ( ( ) => { } ) ;
271294 } ) ;
272295
296+ // Atom T1: workspace.applyEdit - round-trip reply. Mountain's request
297+ // carries `{ RequestIdentifier, Payload }` and blocks the extension's
298+ // awaited promise until we resolve.
299+ await Register (
300+ "sky://workspace/applyEdit" ,
301+ async ( { RequestIdentifier, Payload } : any ) => {
302+ if ( ! RequestIdentifier ) return ;
303+ try {
304+ const Wb = GetWorkbench ( ) ;
305+ const Edits = Payload ?. edits ?? Payload ?? [ ] ;
306+ if ( Wb && Edits ) {
307+ await Wb . commands . executeCommand (
308+ "workbench.action.applyThemeFromFile" ,
309+ Edits ,
310+ ) ;
311+ }
312+ void ResolveUiRequest ( RequestIdentifier , true ) ;
313+ } catch ( Error ) {
314+ console . warn ( "[SkyBridge] applyEdit failed" , Error ) ;
315+ void ResolveUiRequest ( RequestIdentifier , false ) ;
316+ }
317+ } ,
318+ ) ;
319+
320+ // Atom T1: window.showTextDocument - round-trip reply with a
321+ // minimal TextEditor-shaped acknowledgement (`{ uri, viewColumn }`).
322+ // Extensions chaining editor-scoped operations will see undefined for
323+ // properties we don't synthesise yet; tracking that enrichment
324+ // separately as T2.
325+ await Register (
326+ "sky://window/showTextDocument" ,
327+ async ( RawPayload : any ) => {
328+ const RequestIdentifier = RawPayload ?. RequestIdentifier ;
329+ const Payload = RawPayload ?. Payload ?? RawPayload ;
330+ const UriValue =
331+ Payload ?. [ 0 ] ?. uri ??
332+ Payload ?. uri ??
333+ Payload ?. [ 0 ] ??
334+ null ;
335+ const ViewColumn =
336+ Payload ?. [ 1 ] ?. viewColumn ??
337+ Payload ?. viewColumn ??
338+ Payload ?. [ 1 ] ??
339+ null ;
340+ try {
341+ const Wb = GetWorkbench ( ) ;
342+ if ( Wb && UriValue ) {
343+ await Wb . commands . executeCommand (
344+ "vscode.open" ,
345+ {
346+ $mid : 1 ,
347+ path : typeof UriValue === "string" ? UriValue : UriValue ?. path ,
348+ scheme :
349+ ( typeof UriValue === "string"
350+ ? UriValue
351+ : ( UriValue ?. scheme ?? "" )
352+ ) . startsWith ?.( "file://" ) ||
353+ UriValue ?. scheme === "file"
354+ ? "file"
355+ : "untitled" ,
356+ } ,
357+ ViewColumn ,
358+ ) ;
359+ }
360+ if ( RequestIdentifier ) {
361+ void ResolveUiRequest ( RequestIdentifier , {
362+ uri : UriValue ,
363+ viewColumn : ViewColumn ,
364+ } ) ;
365+ }
366+ } catch ( Error ) {
367+ console . warn ( "[SkyBridge] showTextDocument failed" , Error ) ;
368+ if ( RequestIdentifier ) {
369+ void ResolveUiRequest ( RequestIdentifier , null ) ;
370+ }
371+ }
372+ } ,
373+ ) ;
374+
273375 await Register ( "sky://editor/applyEdits" , ( { edits } : any ) => {
274376 if ( ! Array . isArray ( edits ) || ! edits . length ) return ;
275377 GetWorkbench ( )
@@ -696,43 +798,163 @@ export async function InstallSkyBridge(): Promise<void> {
696798 } ) ;
697799
698800 // ---- UI dialogs / notifications ----
801+ // Atom Q1: Mountain emits this for *every* showMessage call regardless
802+ // of whether actions are provided. Two shapes land here:
803+ // Legacy/passive : { severity, message, actions }
804+ // Promise/pending: { RequestIdentifier, Payload: { Severity, Message, Options } }
805+ // The Promise shape carries a RequestIdentifier; the resolve path mirrors
806+ // the quick-pick / input-box flow.
699807 await Register (
700808 "sky://ui/show-message-request" ,
701- ( { severity, message, actions } : any ) => {
702- ShowNotification ( severity ?? "info" , message ?? "" , actions ) ;
809+ ( RawPayload : any ) => {
810+ if ( RawPayload ?. RequestIdentifier ) {
811+ const Inner = RawPayload . Payload ?? { } ;
812+ const Severity =
813+ Inner ?. Severity ?? Inner ?. severity ?? "info" ;
814+ const Message = Inner ?. Message ?? Inner ?. message ?? "" ;
815+ const Options = Inner ?. Options ?? Inner ?. options ?? { } ;
816+ const Actions : Array < { title : string } > = Array . isArray (
817+ Options ?. Actions ?? Options ?. actions ,
818+ )
819+ ? ( Options ?. Actions ?? Options ?. actions )
820+ : [ ] ;
821+ if ( Actions . length === 0 ) {
822+ ShowNotification ( Severity , Message , [ ] ) ;
823+ void ResolveUiRequest (
824+ RawPayload . RequestIdentifier ,
825+ null ,
826+ ) ;
827+ return ;
828+ }
829+ let Picked : string | null = null ;
830+ if ( Actions . length === 1 ) {
831+ if ( window . confirm ( `${ Message } \n\n(${ Actions [ 0 ] . title } )` ) ) {
832+ Picked = Actions [ 0 ] . title ;
833+ }
834+ } else {
835+ const Choice = window . prompt (
836+ `${ Message } \n\nChoose: ${ Actions . map (
837+ ( A ) => A . title ,
838+ ) . join ( " / " ) } `,
839+ Actions [ 0 ] . title ,
840+ ) ;
841+ if (
842+ Choice &&
843+ Actions . some ( ( A ) => A . title === Choice )
844+ ) {
845+ Picked = Choice ;
846+ }
847+ }
848+ void ResolveUiRequest ( RawPayload . RequestIdentifier , Picked ) ;
849+ return ;
850+ }
851+ // Legacy passive shape - still used by telemetry / toast channels.
852+ ShowNotification (
853+ RawPayload ?. severity ?? "info" ,
854+ RawPayload ?. message ?? "" ,
855+ RawPayload ?. actions ,
856+ ) ;
703857 } ,
704858 ) ;
705859
706860 await Register (
707861 "sky://ui/show-input-box-request" ,
708- ( { id, options } : any ) => {
709- // VS Code resolves QuickInput via ResolveUIRequest command
862+ ( { RequestIdentifier, Payload } : any ) => {
863+ if ( ! RequestIdentifier ) return ;
864+ // Minimal fallback - a DOM prompt is serviceable until Sky ships
865+ // a native input box component. Extensions receive the literal
866+ // string the user typed, or `null` when the user dismissed.
867+ const Options = Payload ?? { } ;
710868 const Answer = window . prompt (
711- options ?. prompt ?? options ?. placeHolder ?? "" ,
869+ Options ?. Prompt ??
870+ Options ?. PlaceHolder ??
871+ Options ?. prompt ??
872+ Options ?. placeHolder ??
873+ "" ,
874+ Options ?. Value ?? Options ?. value ?? "" ,
712875 ) ;
713- GetWorkbench ( )
714- ?. commands . executeCommand ( "workbench.resolveUIRequest" , {
715- id,
716- value : Answer ,
717- } )
718- . catch ( ( ) => { } ) ;
876+ void ResolveUiRequest ( RequestIdentifier , Answer ) ;
719877 } ,
720878 ) ;
721879
722880 await Register (
723881 "sky://ui/show-quick-pick-request" ,
724- ( { id , items , options } : any ) => {
725- // Minimal fallback: show VS Code quick-pick command
726- GetWorkbench ( )
727- ?. commands . executeCommand (
728- "workbench.action.quickOpenSelectNext" ,
729- )
730- . catch ( ( ) => { } ) ;
882+ ( { RequestIdentifier , Payload } : any ) => {
883+ if ( ! RequestIdentifier ) return ;
884+ const Items = Payload ?. Items ?? Payload ?. items ?? [ ] ;
885+ const Options = Payload ?. Options ?? Payload ?. options ?? { } ;
886+ // Broadcast a DOM event so Sky components can render a real
887+ // quick-pick UI. Components call `ResolveUiRequest` themselves
888+ // by listening for `cel:quickpick:resolve` CustomEvents.
731889 document . dispatchEvent (
732890 new CustomEvent ( "cel:quickpick:show" , {
733- detail : { id , items , options } ,
891+ detail : { RequestIdentifier , Items , Options } ,
734892 } ) ,
735893 ) ;
894+ // Safety-net fallback: if no component consumes the event
895+ // within 30 s, resolve with the first picked label (or null
896+ // when no item is pre-selected). Prevents Mountain from
897+ // timing out on a missing UI.
898+ const FallbackTimer = window . setTimeout ( ( ) => {
899+ const PickedLabels = Array . isArray ( Items )
900+ ? Items . filter ( ( Item : any ) => Item ?. picked ) . map (
901+ ( Item : any ) => Item ?. label ?? null ,
902+ )
903+ : [ ] ;
904+ const Fallback = Options ?. canPickMany
905+ ? PickedLabels
906+ : ( PickedLabels [ 0 ] ?? null ) ;
907+ void ResolveUiRequest ( RequestIdentifier , Fallback ) ;
908+ } , 30_000 ) ;
909+ document . addEventListener (
910+ "cel:quickpick:resolve" ,
911+ ( Event : any ) => {
912+ if ( Event ?. detail ?. RequestIdentifier !== RequestIdentifier )
913+ return ;
914+ window . clearTimeout ( FallbackTimer ) ;
915+ void ResolveUiRequest (
916+ RequestIdentifier ,
917+ Event ?. detail ?. Result ?? null ,
918+ ) ;
919+ } ,
920+ { once : true } ,
921+ ) ;
922+ } ,
923+ ) ;
924+
925+ // Atom Q1: message box with actions. Mountain already uses this shape
926+ // (see `sky://ui/show-message-request` above for the notification fn);
927+ // when extensions pass `actions`, we must return the picked index.
928+ await Register (
929+ "sky://ui/show-message-with-actions-request" ,
930+ ( { RequestIdentifier, Payload } : any ) => {
931+ if ( ! RequestIdentifier ) return ;
932+ const Message = Payload ?. Message ?? Payload ?. message ?? "" ;
933+ const Actions : Array < { title : string } > =
934+ Payload ?. Actions ?? Payload ?. actions ?? [ ] ;
935+ // No native chooser yet - use confirm() for a single action, or
936+ // prompt() with the action titles for multiple. Real UI work
937+ // happens downstream when Sky ships a message-box component.
938+ let Picked : string | null = null ;
939+ if ( Actions . length === 0 ) {
940+ window . alert ( Message ) ;
941+ } else if ( Actions . length === 1 ) {
942+ if ( window . confirm ( `${ Message } \n\n(${ Actions [ 0 ] . title } )` ) ) {
943+ Picked = Actions [ 0 ] . title ;
944+ }
945+ } else {
946+ const Choice = window . prompt (
947+ `${ Message } \n\nChoose: ${ Actions . map ( ( A ) => A . title ) . join ( " / " ) } ` ,
948+ Actions [ 0 ] . title ,
949+ ) ;
950+ if (
951+ Choice &&
952+ Actions . some ( ( A ) => A . title === Choice )
953+ ) {
954+ Picked = Choice ;
955+ }
956+ }
957+ void ResolveUiRequest ( RequestIdentifier , Picked ) ;
736958 } ,
737959 ) ;
738960
0 commit comments