|
| 1 | +/** |
| 2 | + * @module Bridge/InstallDiagnostics |
| 3 | + * |
| 4 | + * ---- Diagnostics → IMarkerService bridge ---- |
| 5 | + * |
| 6 | + * Mountain emits `sky://diagnostics/changed` after each `Diagnostic.Set` |
| 7 | + * from Cocoon's `vscode.languages.createDiagnosticCollection().set(...)`. |
| 8 | + * Without a renderer-side consumer that pushes into the workbench's |
| 9 | + * `IMarkerService`, the data lands in Mountain's `DiagnosticsMap` but |
| 10 | + * the editor never paints red squiggles and the Problems panel stays |
| 11 | + * empty - every language extension's compile errors / lint warnings / |
| 12 | + * type errors are invisible. |
| 13 | + * |
| 14 | + * Payload shape (from `DiagnosticProvider.SetDiagnostics`): `{ owner, |
| 15 | + * changedURIs: [{ uri, markers }] }`. We translate per-URI marker |
| 16 | + * arrays into `IMarkerService.changeOne(owner, URI, markers)` calls. |
| 17 | + * `Markers.changeOne` REPLACES the marker set for that URI under the |
| 18 | + * given owner - matching VS Code's `MainThreadDiagnostics` behaviour |
| 19 | + * where each `set()` call overwrites the previous diagnostic state. |
| 20 | + */ |
| 21 | + |
| 22 | +export default async (Dependencies: { |
| 23 | + Register: ( |
| 24 | + Channel: string, |
| 25 | + Handler: (Payload: any) => void, |
| 26 | + ) => Promise<void>; |
| 27 | + GetServices: () => { |
| 28 | + Markers?: { |
| 29 | + changeOne( |
| 30 | + owner: string, |
| 31 | + uri: unknown, |
| 32 | + markers: unknown[], |
| 33 | + ): void; |
| 34 | + read(...args: unknown[]): unknown[]; |
| 35 | + }; |
| 36 | + URI?: { |
| 37 | + parse(value: string): unknown; |
| 38 | + from(components: object): unknown; |
| 39 | + }; |
| 40 | + Views?: { |
| 41 | + getViewWithId?(id: string): unknown; |
| 42 | + }; |
| 43 | + [key: string]: unknown; |
| 44 | + } | null; |
| 45 | + Invoke: ( |
| 46 | + cmd: string, |
| 47 | + args?: Record<string, unknown>, |
| 48 | + ) => Promise<unknown>; |
| 49 | +}): Promise<void> => { |
| 50 | + const { Register, GetServices, Invoke } = Dependencies; |
| 51 | + |
| 52 | + let MarkersBridgeFirstSuccessLogged = false; |
| 53 | + |
| 54 | + await Register("sky://diagnostics/changed", (Payload: any) => { |
| 55 | + const Services = GetServices(); |
| 56 | + const Markers = (Services as any)?.Markers; |
| 57 | + const URICtor = (Services as any)?.URI; |
| 58 | + const Owner = String(Payload?.owner ?? ""); |
| 59 | + const Changed = Array.isArray(Payload?.changedURIs) |
| 60 | + ? Payload.changedURIs |
| 61 | + : []; |
| 62 | + if (!Markers?.changeOne || !URICtor) { |
| 63 | + Invoke("MountainIPCInvoke", { |
| 64 | + method: "diagnostic:log", |
| 65 | + params: [ |
| 66 | + "markers-bridge", |
| 67 | + `owner=${Owner} uris=${Changed.length} pushable=false markers=${typeof Markers?.changeOne} uri=${!!URICtor}`, |
| 68 | + ], |
| 69 | + }).catch(() => {}); |
| 70 | + return; |
| 71 | + } |
| 72 | + let PushedTotal = 0; |
| 73 | + let FirstUri = ""; |
| 74 | + let FirstSeverity: number | undefined; |
| 75 | + let FirstMessageLength = 0; |
| 76 | + for (const Entry of Changed) { |
| 77 | + try { |
| 78 | + const Uri = Entry?.uri; |
| 79 | + const Markers_ = Array.isArray(Entry?.markers) |
| 80 | + ? Entry.markers |
| 81 | + : []; |
| 82 | + if (!Uri) continue; |
| 83 | + const RealUri = |
| 84 | + typeof Uri === "string" |
| 85 | + ? URICtor.parse(Uri) |
| 86 | + : Uri && typeof (Uri as any).with === "function" |
| 87 | + ? Uri |
| 88 | + : URICtor.from(Uri); |
| 89 | + Markers.changeOne(Owner, RealUri, Markers_); |
| 90 | + PushedTotal += Markers_.length; |
| 91 | + if (!FirstUri) { |
| 92 | + FirstUri = |
| 93 | + typeof Uri === "string" |
| 94 | + ? Uri |
| 95 | + : typeof (RealUri as any)?.toString === "function" |
| 96 | + ? (RealUri as any).toString() |
| 97 | + : ""; |
| 98 | + if (Markers_[0]) { |
| 99 | + FirstSeverity = (Markers_[0] as any)?.severity; |
| 100 | + FirstMessageLength = String( |
| 101 | + (Markers_[0] as any)?.message ?? "", |
| 102 | + ).length; |
| 103 | + } |
| 104 | + } |
| 105 | + } catch (Error) { |
| 106 | + // Swallow - one bad entry must not stop the rest. |
| 107 | + void Error; |
| 108 | + } |
| 109 | + } |
| 110 | + // One-time success-path confirmation. Also checks the Problems panel |
| 111 | + // view filter state and clears `activeFile` if it was left on, which |
| 112 | + // causes "count shows but panel empty" - the status bar reads from |
| 113 | + // IMarkerService (unfiltered) while the panel reads from |
| 114 | + // IMarkersView.filteredGroups which respects the activeFile toggle. |
| 115 | + if (!MarkersBridgeFirstSuccessLogged && PushedTotal > 0) { |
| 116 | + MarkersBridgeFirstSuccessLogged = true; |
| 117 | + |
| 118 | + // Read back from IMarkerService to confirm markers are stored. |
| 119 | + const AllStored = |
| 120 | + (Markers.read as (...args: unknown[]) => unknown[])?.() ?? []; |
| 121 | + |
| 122 | + // Check the Problems panel view filter state. `getViewWithId` |
| 123 | + // returns the view regardless of whether it's currently focused; |
| 124 | + // if the panel is open, the view instance exists. Clear |
| 125 | + // `activeFile` if it was on so all markers are visible, not just |
| 126 | + // the active file's. |
| 127 | + try { |
| 128 | + const ViewsSvc = (Services as any)?.Views; |
| 129 | + const MarkersView = ViewsSvc?.getViewWithId?.( |
| 130 | + "workbench.panel.markers.view", |
| 131 | + ) as any; |
| 132 | + const FilterStats = MarkersView?.getFilterStats?.() as |
| 133 | + | { total: number; filtered: number } |
| 134 | + | undefined; |
| 135 | + const ActiveFileWasOn = |
| 136 | + MarkersView?.filters?.activeFile === true; |
| 137 | + if (ActiveFileWasOn) { |
| 138 | + MarkersView.filters.activeFile = false; |
| 139 | + } |
| 140 | + Invoke("MountainIPCInvoke", { |
| 141 | + method: "diagnostic:log", |
| 142 | + params: [ |
| 143 | + "markers-bridge", |
| 144 | + `first-push owner=${Owner} uris=${Changed.length} markers=${PushedTotal} stored=${AllStored.length} firstUri=${FirstUri.slice(0, 200)} firstSeverity=${FirstSeverity ?? "?"} firstMsgLen=${FirstMessageLength} filterTotal=${FilterStats?.total ?? "?"} filterFiltered=${FilterStats?.filtered ?? "?"} activeFileCleared=${ActiveFileWasOn}`, |
| 145 | + ], |
| 146 | + }).catch(() => {}); |
| 147 | + } catch { |
| 148 | + Invoke("MountainIPCInvoke", { |
| 149 | + method: "diagnostic:log", |
| 150 | + params: [ |
| 151 | + "markers-bridge", |
| 152 | + `first-push owner=${Owner} uris=${Changed.length} markers=${PushedTotal} stored=${AllStored.length} firstUri=${FirstUri.slice(0, 200)} firstSeverity=${FirstSeverity ?? "?"} firstMsgLen=${FirstMessageLength}`, |
| 153 | + ], |
| 154 | + }).catch(() => {}); |
| 155 | + } |
| 156 | + } |
| 157 | + }); |
| 158 | +}; |
0 commit comments