Skip to content

Commit 6de3a52

Browse files
fix(Sky): Improve tree view data provider attachment for late-registering extensions
The original 5-retry × 150ms attachment logic (750ms total budget) times out for extensions like gitlens, clangd, and dependencies that register their views 3-5 seconds into boot. Changes: - Replace fixed retry schedule with exponential-ish backoff (~10s across 12 retries) - Add PendingAttaches set to queue views that exhaust their retry budget - Add RetryPendingAttaches() to sweep pending views when any successful attach occurs (indicating the workbench bridge is now wired) - Extract AttachToDescriptor for reuse across both immediate and pending paths This handles the full async boot sequence where view contributions register after the initial tree-view:create event fires.
1 parent 854d124 commit 6de3a52

1 file changed

Lines changed: 68 additions & 16 deletions

File tree

Source/Function/SkyBridge.ts

Lines changed: 68 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,24 +1283,34 @@ export async function InstallSkyBridge(): Promise<void> {
12831283
return [];
12841284
}
12851285
};
1286-
const AttachDataProvider = (ViewId: string, Retries: number): void => {
1286+
// Pending attaches: views whose extension contributes the
1287+
// `viewsRegistry` registration AFTER our `cel:tree-view:create`
1288+
// event fires (gitlens, clangd, dependencies all hit this -
1289+
// their views activate ~3-5 s into boot, well after the original
1290+
// 750 ms retry window expired). Each attempt-attach call adds to
1291+
// this set on miss and removes on success; whenever ANY view
1292+
// successfully attaches, we replay the pending set (the
1293+
// workbench tree-views service is now wired - other pending
1294+
// views likely just have to be looked up).
1295+
const PendingAttaches = new Set<string>();
1296+
1297+
const RetryPendingAttaches = (): void => {
1298+
if (PendingAttaches.size === 0) return;
12871299
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+
if (!Services?.TreeViewByViewId) return;
1301+
for (const ViewId of [...PendingAttaches]) {
1302+
const TreeView = Services.TreeViewByViewId(ViewId);
1303+
if (TreeView) {
1304+
PendingAttaches.delete(ViewId);
1305+
AttachToDescriptor(ViewId, TreeView);
13001306
}
1301-
setTimeout(() => AttachDataProvider(ViewId, Retries - 1), 150);
1302-
return;
13031307
}
1308+
};
1309+
1310+
const AttachToDescriptor = (
1311+
ViewId: string,
1312+
TreeView: NonNullable<ReturnType<NonNullable<CelServices["TreeViewByViewId"]>>>,
1313+
): void => {
13041314
if (TreeView.dataProvider) {
13051315
// Already wired (e.g. by a prior register for the same id
13061316
// during a reload). Keep the existing provider to respect
@@ -1319,14 +1329,56 @@ export async function InstallSkyBridge(): Promise<void> {
13191329
tag: "tree-view",
13201330
message: `[TreeView] attach-ok view=${ViewId}`,
13211331
}).catch(() => {});
1332+
// First successful attach can mean the workbench bridge has
1333+
// finally wired up its `TreeViewByViewId` map. Sweep any
1334+
// pending attachers - cheap (~one HashMap lookup each) and
1335+
// rescues the views whose retry budget hadn't quite expired.
1336+
RetryPendingAttaches();
1337+
};
1338+
1339+
// Exponential-ish backoff with a generous total budget. Stock
1340+
// VS Code's view contributions register within ~3 s of extension
1341+
// activation; gitlens / clangd / heavy extensions stretch that
1342+
// to ~5 s. Total budget here is ~10 s across 12 retries; the
1343+
// last ~half are 1 s apart so we don't keep firing setTimeouts
1344+
// indefinitely. After budget exhaustion we register in
1345+
// `PendingAttaches` so any later successful attach can sweep
1346+
// the still-missing entries.
1347+
const AttachBackoffSchedule:number[] = [
1348+
100, 200, 400, 600, 800, 1000, 1000, 1000, 1500, 1500, 1500, 1500,
1349+
];
1350+
1351+
const AttachDataProvider = (ViewId: string, Step: number): void => {
1352+
const Services = GetServices();
1353+
const GetTreeView = Services?.TreeViewByViewId;
1354+
const TreeView =
1355+
typeof GetTreeView === "function" ? GetTreeView(ViewId) : null;
1356+
if (!TreeView) {
1357+
if (Step >= AttachBackoffSchedule.length) {
1358+
PendingAttaches.add(ViewId);
1359+
invoke("RenderDevLog", {
1360+
Tag: "tree-view",
1361+
Message: `[TreeView] attach-pending view=${ViewId} (queued for late workbench wiring)`,
1362+
tag: "tree-view",
1363+
message: `[TreeView] attach-pending view=${ViewId} (queued for late workbench wiring)`,
1364+
}).catch(() => {});
1365+
return;
1366+
}
1367+
setTimeout(
1368+
() => AttachDataProvider(ViewId, Step + 1),
1369+
AttachBackoffSchedule[Step] ?? 1500,
1370+
);
1371+
return;
1372+
}
1373+
AttachToDescriptor(ViewId, TreeView);
13221374
};
13231375
document.addEventListener("cel:tree-view:create", (Event: Event) => {
13241376
const Detail = (Event as CustomEvent).detail as
13251377
| { viewId?: string; extensionId?: string }
13261378
| undefined;
13271379
const ViewId = Detail?.viewId ?? "";
13281380
if (!ViewId) return;
1329-
AttachDataProvider(ViewId, 5);
1381+
AttachDataProvider(ViewId, 0);
13301382
// Prime the DOM fan-out with the initial children too so
13311383
// side-panel shims that mirror tree state don't need to wait
13321384
// for a user-triggered expand.

0 commit comments

Comments
 (0)