Skip to content

Commit e632bde

Browse files
refactor(Mountain): Centralize UriComponents construction with $mid marker
Add a shared UriComponents helper module that ensures every URI payload sent to the VS Code renderer carries the required `$mid: 1` marshalling marker. Without it, VS Code's IPC reviver leaves URIs as plain objects where `.fsPath` and `.with` are undefined, causing the sidebar and icon loader to silently fail. The recent fix for extension location URI marshalling addressed one call site, but inline `json!` builders elsewhere risked regressing the same issue. This change introduces: - `FromFilePath`: constructs a `file://` UriComponents with `$mid` from a filesystem path - `FromUrl`: parses arbitrary URL strings (file://, other schemes) into UriComponents - `StampMidUri`: injects `$mid: 1` into any UriComponents lacking it - `Normalize`: handles mixed input formats (string, object, null) uniformly Now used consistently in: - `handle_extensions_install` (Extension.rs) - fixes location URI - `handle_file_realpath` (both WindServiceHandler and WindServiceHandlers FileSystem.rs) The Extensions.rs handler continues using the existing `Normalize` import. All IPC handlers that return URI-shaped payloads now emit through the centralized helpers, preventing silent breaks in the renderer's URI revival chain.
1 parent 040bfba commit e632bde

5 files changed

Lines changed: 137 additions & 19 deletions

File tree

Source/IPC/UriComponents.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#![allow(non_snake_case, dead_code)]
2+
3+
//! Shared helpers for emitting VS Code `UriComponents` payloads.
4+
//!
5+
//! VS Code's IPC reviver (`src/vs/base/common/uriIpc.ts`
6+
//! `_transformIncomingURIs`) walks every response object and only calls
7+
//! `URI.revive()` on nested objects tagged with the marshalling marker
8+
//! `$mid === MarshalledId.Uri` (= 1). An untagged `UriComponents` reaches
9+
//! callers as a plain bag - `uri.with is not a function`, `uri.fsPath`
10+
//! undefined - and the sidebar / icon loader / joinPath chain silently
11+
//! breaks.
12+
//!
13+
//! Every handler that returns a URI-shaped payload to the renderer has to
14+
//! stamp the marker. Keep construction centralized here so we can't lose a
15+
//! call site to inline `json!` builders.
16+
17+
use serde_json::{Value, json};
18+
19+
/// `MarshalledId.Uri` from VS Code's `src/vs/base/common/marshallingIds.ts`.
20+
/// The reviver keys off this exact value; changing VS Code's enum would
21+
/// require updating here in lockstep (search for `MID_URI` callers).
22+
pub const MID_URI:u64 = 1;
23+
24+
/// Insert `$mid: 1` into a `UriComponents` object if it isn't already tagged.
25+
/// Non-object values pass through unchanged - that lets call sites pipe any
26+
/// `serde_json::Value` they already have through the helper without branching
27+
/// on the variant first.
28+
pub fn StampMidUri(Input:Value) -> Value {
29+
match Input {
30+
Value::Object(mut Map) => {
31+
Map.entry("$mid".to_string()).or_insert(json!(MID_URI));
32+
Value::Object(Map)
33+
}
34+
Other => Other,
35+
}
36+
}
37+
38+
/// Build a `file://` `UriComponents` from an absolute filesystem path
39+
/// string. Path is emitted verbatim - no percent-encoding, no normalisation -
40+
/// which mirrors what VS Code's own `URI.file(…)` / `URI.parse(…)` path
41+
/// readers expect (they undo any encoding themselves when reading `.fsPath`).
42+
pub fn FromFilePath<S:AsRef<str>>(Path:S) -> Value {
43+
StampMidUri(json!({
44+
"scheme": "file",
45+
"authority": "",
46+
"path": Path.as_ref(),
47+
"query": "",
48+
"fragment": "",
49+
}))
50+
}
51+
52+
/// Build a `UriComponents` from a fully-formed URL string. Handles `file://`
53+
/// (authority-optional) and any other scheme generically (`scheme:path` +
54+
/// optional `//authority`). Fragment / query are split off verbatim so
55+
/// downstream `URI.revive()` reconstructs the same URL. Strings that don't
56+
/// parse as URLs fall back to `{ scheme:"file", path:<input> }` - a defensive
57+
/// shape the workbench still tolerates for "unknown location" placeholders.
58+
pub fn FromUrl(Url:&str) -> Value {
59+
if let Some(Rest) = Url.strip_prefix("file://") {
60+
let (Authority, Path) = match Rest.find('/') {
61+
Some(0) => ("", Rest),
62+
Some(Index) => (&Rest[..Index], &Rest[Index..]),
63+
None => ("", ""),
64+
};
65+
return StampMidUri(json!({
66+
"scheme": "file",
67+
"authority": Authority,
68+
"path": Path,
69+
"query": "",
70+
"fragment": "",
71+
}));
72+
}
73+
if let Some((Scheme, PathPart)) = Url.split_once(':') {
74+
let Trimmed = PathPart.trim_start_matches("//");
75+
let (Authority, Path) = match Trimmed.find('/') {
76+
Some(0) => ("", Trimmed),
77+
Some(Index) => (&Trimmed[..Index], &Trimmed[Index..]),
78+
None => ("", Trimmed),
79+
};
80+
return StampMidUri(json!({
81+
"scheme": Scheme,
82+
"authority": Authority,
83+
"path": Path,
84+
"query": "",
85+
"fragment": "",
86+
}));
87+
}
88+
StampMidUri(json!({
89+
"scheme": "file",
90+
"authority": "",
91+
"path": Url,
92+
"query": "",
93+
"fragment": "",
94+
}))
95+
}
96+
97+
/// Normalise an `extensionLocation` (or any similar) field that arrives as
98+
/// either a URL string, a pre-built UriComponents object (possibly
99+
/// already tagged), or is missing / null. The output is always an object
100+
/// with `$mid: 1` and the five URI fields.
101+
pub fn Normalize(Raw:Option<&Value>) -> Value {
102+
match Raw {
103+
Some(Value::Object(Map)) if Map.contains_key("scheme") => {
104+
StampMidUri(Value::Object(Map.clone()))
105+
}
106+
Some(Value::String(Url)) => FromUrl(Url),
107+
_ => FromFilePath("/extensions/unknown"),
108+
}
109+
}

Source/IPC/WindServiceHandler/Extension.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ use serde_json::{Value, json};
88
use tauri::AppHandle;
99
use CommonLibrary::ExtensionManagement::ExtensionManagementService::ExtensionManagementService;
1010

11-
use crate::{ExtensionManagement::VsixInstaller, RunTime::ApplicationRunTime::ApplicationRunTime, Vine, dev_log};
11+
use crate::{
12+
ExtensionManagement::VsixInstaller,
13+
IPC::UriComponents::FromFilePath as UriFromFilePath,
14+
RunTime::ApplicationRunTime::ApplicationRunTime,
15+
Vine,
16+
dev_log,
17+
};
1218

1319
/// Cocoon sidecar id (matches `CocoonManagement::COCOON_SIDE_CAR_IDENTIFIER`).
1420
const COCOON_SIDE_CAR_IDENTIFIER:&str = "cocoon-main";
@@ -279,16 +285,16 @@ pub async fn handle_extensions_install(
279285

280286
// ILocalExtension envelope - matches `handle_extensions_get_installed`
281287
// so VS Code's ExtensionEnablementService merges it into the sidebar.
288+
// `location` must carry `$mid: 1` so the renderer's `URI.revive()` runs;
289+
// otherwise the sidebar's `resources.joinPath(local.location, …)` hits
290+
// `uri.with is not a function`. Routed through the shared helper so the
291+
// marker never drops off.
282292
Ok(json!({
283293
"type": 1,
284294
"isBuiltin": false,
285295
"identifier": { "id": Outcome.Identifier },
286296
"manifest": Descriptor,
287-
"location": {
288-
"scheme": "file",
289-
"path": Outcome.InstalledAt.to_string_lossy(),
290-
"authority": "",
291-
},
297+
"location": UriFromFilePath(Outcome.InstalledAt.to_string_lossy()),
292298
"targetPlatform": "undefined",
293299
"isValid": true,
294300
"validations": [],

Source/IPC/WindServiceHandler/FileSystem.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ use CommonLibrary::{
1414
FileSystem::{FileSystemReader::FileSystemReader, FileSystemWriter::FileSystemWriter},
1515
};
1616

17-
use crate::{RunTime::ApplicationRunTime::ApplicationRunTime, dev_log};
17+
use crate::{
18+
IPC::UriComponents::FromFilePath as UriFromFilePath,
19+
RunTime::ApplicationRunTime::ApplicationRunTime,
20+
dev_log,
21+
};
1822
use super::{extract_path_from_arg, metadata_to_istat};
1923

2024
// ============================================================================
@@ -437,11 +441,8 @@ pub async fn handle_file_realpath(Args:Vec<Value>) -> Result<Value, String> {
437441
.await
438442
.map_err(|E| format!("Failed to realpath: {} ({})", Path, E))?;
439443

440-
Ok(json!({
441-
"scheme": "file",
442-
"path": Canonical.to_string_lossy(),
443-
"authority": ""
444-
}))
444+
// VS Code-marked UriComponents (`$mid: 1`) for the renderer reviver.
445+
Ok(UriFromFilePath(Canonical.to_string_lossy()))
445446
}
446447

447448
/// Clone file (copy with metadata)

Source/IPC/WindServiceHandlers/Extensions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use CommonLibrary::ExtensionManagement::ExtensionManagementService::ExtensionMan
88
use serde_json::{Value, json};
99

1010
use crate::{
11-
IPC::UriComponents::{Normalize as NormalizeUri, StampMidUri},
11+
IPC::UriComponents::Normalize as NormalizeUri,
1212
RunTime::ApplicationRunTime::ApplicationRunTime,
1313
dev_log,
1414
};

Source/IPC/WindServiceHandlers/FileSystem.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ use CommonLibrary::{
1212
FileSystem::{FileSystemReader::FileSystemReader, FileSystemWriter::FileSystemWriter},
1313
};
1414

15-
use crate::{dev_log, RunTime::ApplicationRunTime::ApplicationRunTime};
15+
use crate::{
16+
IPC::UriComponents::FromFilePath as UriFromFilePath,
17+
RunTime::ApplicationRunTime::ApplicationRunTime,
18+
dev_log,
19+
};
1620

1721
use super::Utilities::{extract_path_from_arg, metadata_to_istat};
1822

@@ -342,11 +346,9 @@ pub async fn handle_file_realpath(args:Vec<Value>) -> Result<Value, String> {
342346
.await
343347
.map_err(|E| format!("Failed to realpath: {} ({})", Path, E))?;
344348

345-
Ok(json!({
346-
"scheme": "file",
347-
"path": Canonical.to_string_lossy(),
348-
"authority": ""
349-
}))
349+
// Emit a VS Code-marked UriComponents (`$mid: 1`) so the renderer
350+
// reviver promotes it to a real `URI` with `.fsPath` / `.with`.
351+
Ok(UriFromFilePath(Canonical.to_string_lossy()))
350352
}
351353

352354
/// Clone file (copy with metadata)

0 commit comments

Comments
 (0)