Skip to main content

DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/IPC/WindServiceHandlers/Extensions/
ExtensionsGetInstalled.rs

1#![allow(non_snake_case)]
2
3//! `extensions:getInstalled(type?)` - return scanned extensions reshaped as
4//! VS Code's `ILocalExtension[]` so `ExtensionManagementChannelClient
5//! .getInstalled` can destructure `extension.identifier.id`,
6//! `extension.manifest.*`, and `extension.location` without blowing up.
7//!
8//! ## Argument contract
9//!
10//! `Arguments[0]` is the optional `ExtensionType` filter VS Code passes:
11//! - `0` (System) → only built-ins.
12//! - `1` (User) → only VSIX-installed.
13//! - `null` / missing → every known extension.
14//!
15//! Without the filter the trusted-publishers boot migration iterates
16//! User-typed extensions over System manifests and crashes on
17//! `manifest.publisher.toLowerCase()`.
18//!
19//! ## Boot-time race
20//!
21//! The workbench fires `getInstalled` ~13 times within the first second.
22//! `ExtensionPopulate` runs in parallel and writes to `ScannedExtensions`
23//! 250-500 ms in. We await `ExtensionState.ScanReady` (a `tokio::sync::Notify`
24//! fired once the scan commits its results) with a 5 s hard cap, then return
25//! whatever is available. No 50 ms polling loop - we wake exactly when data
26//! arrives.
27//!
28//! ## Manifest skeleton
29//!
30//! VS Code unconditionally calls `manifest.publisher.toLowerCase()`. A `null`
31//! or non-object manifest crashes the webview before its first paint. We
32//! coerce to `{}` and inject `publisher`/`name`/`version` defaults.
33
34use std::{sync::Arc, time::Duration};
35
36use CommonLibrary::ExtensionManagement::ExtensionManagementService::ExtensionManagementService;
37use serde_json::{Value, json};
38
39use crate::{
40	IPC::UriComponents::Normalize::Fn as NormalizeUri,
41	RunTime::ApplicationRunTime::ApplicationRunTime,
42	dev_log,
43};
44
45const EXTENSION_TYPE_SYSTEM:u8 = 0;
46
47const EXTENSION_TYPE_USER:u8 = 1;
48
49const SCAN_WAIT_CAP_MS:u64 = 5000;
50
51pub async fn ExtensionsGetInstalled(RunTime:Arc<ApplicationRunTime>, Arguments:Vec<Value>) -> Result<Value, String> {
52	let TypeFilter:Option<u8> = Arguments.first().and_then(|V| V.as_u64()).map(|N| N as u8);
53
54	let mut Extensions = RunTime
55		.Environment
56		.GetExtensions()
57		.await
58		.map_err(|Error| format!("extensions:getInstalled failed: {}", Error))?;
59
60	if Extensions.is_empty() {
61		let ScanReady = RunTime.Environment.ApplicationState.Extension.ScanReady.clone();
62
63		let Notified = tokio::time::timeout(Duration::from_millis(SCAN_WAIT_CAP_MS), ScanReady.notified()).await;
64
65		Extensions = RunTime
66			.Environment
67			.GetExtensions()
68			.await
69			.map_err(|Error| format!("extensions:getInstalled failed: {}", Error))?;
70
71		match Notified {
72			Ok(()) => {
73				dev_log!(
74					"extensions",
75					"extensions:getInstalled: scan-ready signal received, {} entries available",
76					Extensions.len()
77				);
78			},
79			Err(_) => {
80				dev_log!(
81					"extensions",
82					"warn: extensions:getInstalled: scan-ready timed out after {}ms; {} entries available",
83					SCAN_WAIT_CAP_MS,
84					Extensions.len()
85				);
86			},
87		}
88	}
89
90	let Wrapped:Vec<Value> = Extensions
91		.into_iter()
92		.filter_map(|Manifest| {
93			let IsBuiltin = Manifest.get("isBuiltin").and_then(Value::as_bool).unwrap_or(true);
94			let ExtensionType = if IsBuiltin { EXTENSION_TYPE_SYSTEM } else { EXTENSION_TYPE_USER };
95
96			if let Some(Wanted) = TypeFilter
97				&& Wanted != ExtensionType
98			{
99				return None;
100			}
101
102			let Publisher = Manifest
103				.get("publisher")
104				.and_then(Value::as_str)
105				.filter(|S| !S.is_empty())
106				.unwrap_or("unknown")
107				.to_string();
108			let Name = Manifest
109				.get("name")
110				.and_then(Value::as_str)
111				.filter(|S| !S.is_empty())
112				.unwrap_or("unknown")
113				.to_string();
114			let Id = format!("{}.{}", Publisher, Name);
115
116			let Location = NormalizeUri(Manifest.get("extensionLocation"));
117
118			let mut Manifest = match Manifest {
119				Value::Object(_) => Manifest,
120				_ => json!({}),
121			};
122			if let Value::Object(ref mut Map) = Manifest {
123				Map.insert("extensionLocation".to_string(), Location.clone());
124				Map.entry("publisher".to_string()).or_insert_with(|| json!(Publisher.clone()));
125				Map.entry("name".to_string()).or_insert_with(|| json!(Name.clone()));
126				Map.entry("version".to_string()).or_insert_with(|| json!("0.0.0"));
127			}
128
129			Some(json!({
130				"type": ExtensionType,
131				"isBuiltin": IsBuiltin,
132				"identifier": { "id": Id },
133				"manifest": Manifest,
134				"location": Location,
135				"targetPlatform": "undefined",
136				"isValid": true,
137				"validations": [],
138				"preRelease": false,
139				"isWorkspaceScoped": false,
140				"isMachineScoped": false,
141				"isApplicationScoped": false,
142				"publisherId": null,
143				"isPreReleaseVersion": false,
144				"hasPreReleaseVersion": false,
145				"private": false,
146				"updated": false,
147				"pinned": false,
148				"forceAutoUpdate": false,
149				"source": if IsBuiltin { "system" } else { "vsix" },
150				"size": 0,
151			}))
152		})
153		.collect();
154
155	dev_log!(
156		"extensions",
157		"extensions:getInstalled type={:?} returning {} ILocalExtension-shaped entries",
158		TypeFilter,
159		Wrapped.len()
160	);
161
162	Ok(json!(Wrapped))
163}