@@ -166,11 +166,33 @@ interface CelSearchService {
166166 provider : unknown ,
167167 ) : { dispose ( ) : void } ;
168168}
169+ // `ITreeView` from `vs/workbench/common/views`. Only the shape Sky
170+ // actually writes to (`dataProvider`) is typed - the rest is optional
171+ // read-only metadata the stock pane handles.
172+ interface CelTreeView {
173+ dataProvider :
174+ | undefined
175+ | {
176+ getChildren ( element ?: {
177+ handle ?: string ;
178+ } ) : Promise < unknown [ ] | undefined > ;
179+ isTreeEmpty ?: boolean ;
180+ } ;
181+ title ?: string ;
182+ description ?: string | undefined ;
183+ message ?: string | undefined ;
184+ refresh ?(
185+ treeItems ?: readonly unknown [ ] ,
186+ checkboxesChanged ?: readonly unknown [ ] ,
187+ ) : Promise < void > ;
188+ }
169189interface CelServices {
170190 Statusbar : CelStatusbarService ;
171191 Commands : CelCommandService ;
172192 CommandRegistry : CelCommandRegistry ;
173193 Search : CelSearchService ;
194+ Views ?: unknown ;
195+ TreeViewByViewId ?: ( viewId : string ) => CelTreeView | null ;
174196}
175197
176198function GetServices ( ) : CelServices | null {
@@ -1095,6 +1117,7 @@ export async function InstallSkyBridge(): Promise<void> {
10951117 SkyEvent . ThemeChange ,
10961118 SkyEvent . TreeViewDispose ,
10971119 SkyEvent . TreeViewCreate ,
1120+ SkyEvent . TreeViewRefresh ,
10981121 SkyEvent . TestRegistered ,
10991122 SkyEvent . SCMProviderAdded ,
11001123 SkyEvent . SCMProviderRemoved ,
@@ -1132,57 +1155,217 @@ export async function InstallSkyBridge(): Promise<void> {
11321155 }
11331156
11341157 // ---- Tree-view data bridge ----
1135- // Consumer for `cel:tree-view:create` - without this listener the fan-out
1136- // above re-dispatches the event but nothing downstream listens, so every
1137- // registered view logs `consumer-present=false` (F1.1 in HANDOFF §-10).
1138- // The listener primes the viewer by requesting the root children through
1139- // Mountain's `tree:getChildren` invoke - Mountain forwards to Cocoon's
1140- // `$provideTreeChildren` and returns `{ items: [...] }`. We re-dispatch
1141- // the children on `cel:tree-view:items` so any renderer shim can pick
1142- // them up without an extra round-trip.
1158+ // Two-way wire so extension-registered tree views actually render:
1159+ //
1160+ // 1. **Native data provider attach**: workbench renders a tree view
1161+ // only when `treeView.dataProvider` is non-undefined. Stock VS
1162+ // Code sets this in `MainThreadTreeViews.$registerTreeViewDataProvider`
1163+ // via the ExtHostContext RPC - we don't have that channel yet
1164+ // (Track A bring-up from the coverage matrix), so we attach a
1165+ // data provider here that calls `tree:getChildren` via
1166+ // `MountainIPCInvoke`. `__CEL_SERVICES__.TreeViewByViewId(id)` is
1167+ // exposed by the Output transform plugin - it returns the same
1168+ // `ITreeView` the stock mainThread accesses via
1169+ // `Registry.as(ViewsRegistry).getView(id).treeView`.
1170+ //
1171+ // 2. **CustomEvent fan-out** (existing): the `cel:tree-view:items`
1172+ // DOM event stays so any Sky/Astro observer (side-panel mirror,
1173+ // diagnostic inspector) can react without going through the
1174+ // workbench tree rendering pipeline.
1175+ //
1176+ // If the view is registered BEFORE the tree descriptor is mounted,
1177+ // `TreeViewByViewId` returns null - retry on microtask + rAF (covers
1178+ // both async workbench init and the pane-is-collapsed-so-not-yet-mounted
1179+ // case). After 5 retries spaced 150 ms apart we give up and rely on
1180+ // whatever `$refresh` the extension issues next to re-trigger us.
11431181 if ( typeof document !== "undefined" ) {
1182+ // Map Cocoon's `{handle, label: string, isCollapsed, icon: string}`
1183+ // wire shape (from `RequestRoutingHandler.$provideTreeChildren`)
1184+ // into the workbench's `ITreeItem` shape. The fields the tree
1185+ // renderer actually reads are `handle`, `collapsibleState`, and
1186+ // `label: { label: string }`. Icons can be promoted to `iconPath`
1187+ // once Mountain starts returning URI components - keep the
1188+ // field name `icon` exposed on the extended shape so side-panel
1189+ // observers can still use it.
1190+ const ToTreeItem = (
1191+ Raw : unknown ,
1192+ Fallback : { ViewId : string ; ParentHandle : string ; Index : number } ,
1193+ ) => {
1194+ const Wire = ( Raw ?? { } ) as Record < string , unknown > ;
1195+ const Handle =
1196+ typeof Wire . handle === "string" && Wire . handle . length > 0
1197+ ? Wire . handle
1198+ : `${ Fallback . ViewId } /${ Fallback . ParentHandle || "root" } /${ Fallback . Index } ` ;
1199+ const Label =
1200+ typeof Wire . label === "string"
1201+ ? { label : Wire . label }
1202+ : ( Wire . label as { label ?: string } | undefined ) ?. label
1203+ ? ( Wire . label as { label : string } )
1204+ : { label : "" } ;
1205+ const CollapsibleState =
1206+ Wire . isCollapsed === true
1207+ ? 1
1208+ : typeof Wire . collapsibleState === "number"
1209+ ? Wire . collapsibleState
1210+ : 0 ;
1211+ // Pass through the full set of fields Cocoon's wire DTO
1212+ // carries. Any field the workbench tree renderer doesn't
1213+ // read is ignored silently; keeping them lets side-panel
1214+ // mirrors (diagnostic inspectors, test harnesses) see the
1215+ // same content the built-in tree does.
1216+ const Description =
1217+ typeof Wire . description === "string" ? Wire . description : undefined ;
1218+ const Tooltip =
1219+ typeof Wire . tooltip === "string" ? Wire . tooltip : undefined ;
1220+ const ContextValue =
1221+ typeof Wire . contextValue === "string" ? Wire . contextValue : undefined ;
1222+ return {
1223+ handle : Handle ,
1224+ collapsibleState : CollapsibleState ,
1225+ label : Label ,
1226+ icon :
1227+ typeof Wire . icon === "string" && Wire . icon . length > 0
1228+ ? Wire . icon
1229+ : undefined ,
1230+ description : Description ,
1231+ tooltip : Tooltip ,
1232+ resourceUri : Wire . resourceUri ,
1233+ contextValue : ContextValue ,
1234+ command : Wire . command ,
1235+ accessibilityInformation : Wire . accessibilityInformation ,
1236+ } ;
1237+ } ;
1238+ const ProvideChildren = async (
1239+ ViewId : string ,
1240+ Element ?: { handle ?: string } ,
1241+ ) : Promise < unknown [ ] > => {
1242+ try {
1243+ const Response = ( await invoke ( "MountainIPCInvoke" , {
1244+ method : "tree:getChildren" ,
1245+ params : [
1246+ {
1247+ viewId : ViewId ,
1248+ treeItemHandle : Element ?. handle ?? "" ,
1249+ } ,
1250+ ] ,
1251+ } ) ) as { items ?: unknown [ ] } ;
1252+ const RawItems = Array . isArray ( Response ?. items )
1253+ ? Response . items
1254+ : [ ] ;
1255+ const ParentHandle = Element ?. handle ?? "" ;
1256+ const Items = RawItems . map ( ( Raw , Index ) =>
1257+ ToTreeItem ( Raw , {
1258+ ViewId,
1259+ ParentHandle,
1260+ Index,
1261+ } ) ,
1262+ ) ;
1263+ // Dual-emit: DOM CustomEvent for Sky-side observers
1264+ // (same shape as the workbench tree renderer sees so
1265+ // mirror panels don't need a second conversion).
1266+ document . dispatchEvent (
1267+ new CustomEvent ( "cel:tree-view:items" , {
1268+ detail : {
1269+ viewId : ViewId ,
1270+ parent : ParentHandle ,
1271+ items : Items ,
1272+ } ,
1273+ } ) ,
1274+ ) ;
1275+ return Items ;
1276+ } catch ( Error ) {
1277+ invoke ( "RenderDevLog" , {
1278+ Tag : "tree-view" ,
1279+ Message : `[TreeView] bridge-error view=${ ViewId } err=${ String ( Error ) } ` ,
1280+ tag : "tree-view" ,
1281+ message : `[TreeView] bridge-error view=${ ViewId } err=${ String ( Error ) } ` ,
1282+ } ) . catch ( ( ) => { } ) ;
1283+ return [ ] ;
1284+ }
1285+ } ;
1286+ const AttachDataProvider = ( ViewId : string , Retries : number ) : void => {
1287+ const Services = GetServices ( ) ;
1288+ const GetTreeView = Services ?. TreeViewByViewId ;
1289+ const TreeView =
1290+ typeof GetTreeView === "function" ? GetTreeView ( ViewId ) : null ;
1291+ if ( ! TreeView ) {
1292+ if ( Retries <= 0 ) {
1293+ invoke ( "RenderDevLog" , {
1294+ Tag : "tree-view" ,
1295+ Message : `[TreeView] attach-give-up view=${ ViewId } (no workbench tree descriptor)` ,
1296+ tag : "tree-view" ,
1297+ message : `[TreeView] attach-give-up view=${ ViewId } (no workbench tree descriptor)` ,
1298+ } ) . catch ( ( ) => { } ) ;
1299+ return ;
1300+ }
1301+ setTimeout ( ( ) => AttachDataProvider ( ViewId , Retries - 1 ) , 150 ) ;
1302+ return ;
1303+ }
1304+ if ( TreeView . dataProvider ) {
1305+ // Already wired (e.g. by a prior register for the same id
1306+ // during a reload). Keep the existing provider to respect
1307+ // any extension that registered their own.
1308+ return ;
1309+ }
1310+ TreeView . dataProvider = {
1311+ async getChildren ( Element ?: { handle ?: string } ) {
1312+ const Items = await ProvideChildren ( ViewId , Element ) ;
1313+ return Items as any [ ] ;
1314+ } ,
1315+ } ;
1316+ invoke ( "RenderDevLog" , {
1317+ Tag : "tree-view" ,
1318+ Message : `[TreeView] attach-ok view=${ ViewId } ` ,
1319+ tag : "tree-view" ,
1320+ message : `[TreeView] attach-ok view=${ ViewId } ` ,
1321+ } ) . catch ( ( ) => { } ) ;
1322+ } ;
11441323 document . addEventListener ( "cel:tree-view:create" , ( Event : Event ) => {
11451324 const Detail = ( Event as CustomEvent ) . detail as
11461325 | { viewId ?: string ; extensionId ?: string }
11471326 | undefined ;
11481327 const ViewId = Detail ?. viewId ?? "" ;
11491328 if ( ! ViewId ) return ;
1150- invoke < { items ?: unknown [ ] } > ( "MountainIPCInvoke" , {
1151- method : "tree:getChildren" ,
1152- params : [ { viewId : ViewId , treeItemHandle : "" } ] ,
1153- } )
1154- . then ( ( Response ) => {
1155- const Items = Array . isArray ( Response ?. items )
1156- ? Response . items
1157- : [ ] ;
1158- document . dispatchEvent (
1159- new CustomEvent ( "cel:tree-view:items" , {
1160- detail : {
1161- viewId : ViewId ,
1162- parent : "" ,
1163- items : Items ,
1164- } ,
1165- } ) ,
1166- ) ;
1167- try {
1168- invoke < void > ( "RenderDevLog" , {
1169- Tag : "tree-view" ,
1170- Message : `[TreeView] bridge-items view=${ ViewId } count=${ Items . length } ` ,
1171- tag : "tree-view" ,
1172- message : `[TreeView] bridge-items view=${ ViewId } count=${ Items . length } ` ,
1173- } ) . catch ( ( ) => { } ) ;
1174- } catch { }
1175- } )
1176- . catch ( ( Error ) => {
1177- try {
1178- invoke < void > ( "RenderDevLog" , {
1179- Tag : "tree-view" ,
1180- Message : `[TreeView] bridge-error view=${ ViewId } err=${ String ( Error ) } ` ,
1181- tag : "tree-view" ,
1182- message : `[TreeView] bridge-error view=${ ViewId } err=${ String ( Error ) } ` ,
1183- } ) . catch ( ( ) => { } ) ;
1184- } catch { }
1185- } ) ;
1329+ AttachDataProvider ( ViewId , 5 ) ;
1330+ // Prime the DOM fan-out with the initial children too so
1331+ // side-panel shims that mirror tree state don't need to wait
1332+ // for a user-triggered expand.
1333+ void ProvideChildren ( ViewId , undefined ) ;
1334+ } ) ;
1335+
1336+ // `cel:tree-view:refresh` - extension called `treeView.refresh()` or
1337+ // fired `onDidChangeTreeData`. Workbench re-queries `getChildren`
1338+ // via the provider we attached above when we call `treeView.refresh()`.
1339+ document . addEventListener ( "cel:tree-view:refresh" , ( Event : Event ) => {
1340+ const Detail = ( Event as CustomEvent ) . detail as
1341+ | { viewId ?: string }
1342+ | undefined ;
1343+ const ViewId = Detail ?. viewId ?? "" ;
1344+ if ( ! ViewId ) return ;
1345+ const Services = GetServices ( ) ;
1346+ const TreeView = Services ?. TreeViewByViewId ?.( ViewId ) ;
1347+ if ( TreeView ?. refresh ) {
1348+ TreeView . refresh ( ) . catch ( ( ) => { } ) ;
1349+ }
1350+ // Also re-prime the Sky observers.
1351+ void ProvideChildren ( ViewId , undefined ) ;
1352+ } ) ;
1353+
1354+ // `cel:tree-view:dispose` - extension disposed its tree data
1355+ // provider. Clear the native pane's dataProvider so the workbench
1356+ // falls back to the empty-state message. The pane stays registered
1357+ // (ViewsRegistry keeps it) - dispose only detaches the provider.
1358+ document . addEventListener ( "cel:tree-view:dispose" , ( Event : Event ) => {
1359+ const Detail = ( Event as CustomEvent ) . detail as
1360+ | { viewId ?: string ; handle ?: string | number }
1361+ | undefined ;
1362+ const ViewId = Detail ?. viewId ?? "" ;
1363+ if ( ! ViewId ) return ;
1364+ const Services = GetServices ( ) ;
1365+ const TreeView = Services ?. TreeViewByViewId ?.( ViewId ) ;
1366+ if ( TreeView && TreeView . dataProvider !== undefined ) {
1367+ TreeView . dataProvider = undefined ;
1368+ }
11861369 } ) ;
11871370 }
11881371
0 commit comments