Skip to content

Commit e9eff04

Browse files
fix(Sky): Fix URI object construction in search results for workbench deduplication
The previous manual resource object construction (`{ $mid: 1, path, scheme }`) fails when the workbench attempts result deduplication via `uri.with({...})` because it's a POJO without methods. Add `CelUriCtor` and `CelUri` interfaces to type VS Code's URI class, then create `MakeFileUri` that uses the actual `Ctor.file()` method when `__CEL_SERVICES__.URI` is available. The lookup is re-resolved on each result construction to handle the event-rescue path where SkyBridge registers before the URI patch binds. Includes a last-resort POJO fallback for edge cases, though the workbench will gracefully fail on dedup in that scenario. Updates both `MatchFromHit` (text search results) and file search result mapping to use `MakeFileUri` instead of manual object construction.
1 parent 70de1fd commit e9eff04

1 file changed

Lines changed: 69 additions & 8 deletions

File tree

Source/Function/SkyBridge.ts

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -186,13 +186,50 @@ interface CelTreeView {
186186
checkboxesChanged?: readonly unknown[],
187187
): Promise<void>;
188188
}
189+
/**
190+
* Stock VS Code `URI` class shape. Only the methods Sky-side bridges
191+
* actually invoke are typed; everything else flows through the workbench
192+
* as opaque. The full class lives at
193+
* `vs/base/common/uri.js` in the renderer bundle and is exposed on
194+
* `__CEL_SERVICES__.URI` by `ExposeWorkbenchAccessor`.
195+
*/
196+
interface CelUriCtor {
197+
file(path: string): CelUri;
198+
parse(value: string, strict?: boolean): CelUri;
199+
from(components: {
200+
scheme: string;
201+
authority?: string;
202+
path?: string;
203+
query?: string;
204+
fragment?: string;
205+
}): CelUri;
206+
revive(value: unknown): CelUri;
207+
}
208+
interface CelUri {
209+
readonly scheme: string;
210+
readonly authority: string;
211+
readonly path: string;
212+
readonly query: string;
213+
readonly fragment: string;
214+
readonly fsPath: string;
215+
with(change: {
216+
scheme?: string;
217+
authority?: string;
218+
path?: string;
219+
query?: string;
220+
fragment?: string;
221+
}): CelUri;
222+
toString(skipEncoding?: boolean): string;
223+
toJSON(): unknown;
224+
}
189225
interface CelServices {
190226
Statusbar: CelStatusbarService;
191227
Commands: CelCommandService;
192228
CommandRegistry: CelCommandRegistry;
193229
Search: CelSearchService;
194230
Views?: unknown;
195231
TreeViewByViewId?: (viewId: string) => CelTreeView | null;
232+
URI?: CelUriCtor;
196233
}
197234

198235
function GetServices(): CelServices | null {
@@ -729,16 +766,42 @@ export async function InstallSkyBridge(): Promise<void> {
729766
return Path || null;
730767
};
731768

769+
// `URI` lookup is re-resolved at every result construction so a
770+
// SkyBridge that registered before `__CEL_SERVICES__.URI` was
771+
// bound (event-rescue path runs before the URI patch lands) can
772+
// pick it up on the first actual search call rather than being
773+
// stuck with the boot-time snapshot. Cheap - single property
774+
// read per result row.
775+
//
776+
// The provider is registered IN-PROCESS in the workbench, NOT
777+
// through the extension-host RPC bridge - so the workbench
778+
// never calls `URI.revive(...)` on what we return. It dedups
779+
// results via `getComparisonKey(uri)` which is `uri.with({...})`
780+
// plus `.toString()`. Returning a raw `URIComponents` POJO
781+
// (`{ $mid:1, path, scheme }`) throws
782+
// `uri.with is not a function` at the first dedup check.
783+
const MakeFileUri = (
784+
FsPath: string,
785+
): CelUri | { scheme: string; path: string; fsPath: string } => {
786+
const Ctor = GetServices()?.URI;
787+
if (Ctor) return Ctor.file(FsPath);
788+
// Last-resort fallback when `__CEL_SERVICES__.URI` somehow
789+
// missed the patch. The result is a POJO with the right
790+
// shape but no `.with()` - the workbench will still throw
791+
// on dedup, just gracefully now (no `$mid:1` because that
792+
// implies revive should be called and isn't here).
793+
return { scheme: "file", path: FsPath, fsPath: FsPath };
794+
};
795+
732796
// Translate a raw Mountain hit into the `IFileMatch` shape the
733-
// workbench renderer expects. `resource` must carry `$mid:1`
734-
// so VS Code's `URI.revive()` path restores it.
797+
// workbench renderer expects.
735798
const MatchFromHit = (Hit: any) => {
736799
const Raw = String(Hit?.uri ?? "");
737800
const OsPath = Raw.replace(/^file:\/\//, "");
738801
const Line = Number(Hit?.lineNumber ?? 1);
739802
const Preview = String(Hit?.preview ?? "");
740803
return {
741-
resource: { $mid: 1, path: OsPath, scheme: "file" },
804+
resource: MakeFileUri(OsPath),
742805
results: [
743806
{
744807
preview: { text: Preview, matches: [] },
@@ -834,11 +897,9 @@ export async function InstallSkyBridge(): Promise<void> {
834897
params: [Glob, MaxResults],
835898
})) as string[];
836899
const Results = (Files ?? []).map((Uri) => ({
837-
resource: {
838-
$mid: 1,
839-
path: String(Uri).replace(/^file:\/\//, ""),
840-
scheme: "file",
841-
},
900+
resource: MakeFileUri(
901+
String(Uri).replace(/^file:\/\//, ""),
902+
),
842903
}));
843904
// Suppress unused warning - FolderRoot would be used
844905
// by a multi-folder fan-out that we don't need yet.

0 commit comments

Comments
 (0)