Skip to main content

Grove/API/
VSCode.rs

1//! VS Code API Facade Module
2//!
3//! Provides the VS Code API facade for Grove extensions.
4//! This implements the interface described in vscode.d.ts for extension
5//! compatibility.
6
7use std::sync::{
8	Arc,
9	Mutex,
10	atomic::{AtomicU32, Ordering},
11};
12
13use serde::{Deserialize, Serialize};
14
15use crate::{API::Types::*, Transport::Strategy::Transport, dev_log};
16
17// ============================================================================
18// Provider Registration Store
19// ============================================================================
20
21/// Tracks all active language provider registrations with their handles.
22///
23/// A registration is added when an extension calls `register_*_provider` and
24/// removed when `Disposable::dispose()` is called on the returned handle.
25#[derive(Debug, Default)]
26struct ProviderStore {
27	/// Map from handle → (provider_type, selector) for diagnostics.
28	entries:Mutex<std::collections::HashMap<u32, (String, String)>>,
29
30	/// Monotonically increasing handle counter.
31	next_handle:AtomicU32,
32}
33
34impl ProviderStore {
35	/// Returns the next unique handle and inserts a registration record.
36	fn insert(&self, provider_type:&str, selector:&str) -> u32 {
37		let Handle = self.next_handle.fetch_add(1, Ordering::Relaxed);
38
39		if let Ok(mut Guard) = self.entries.lock() {
40			Guard.insert(Handle, (provider_type.to_string(), selector.to_string()));
41		}
42
43		Handle
44	}
45
46	/// Removes a registration by handle (called from Disposable::dispose).
47	fn remove(&self, handle:u32) {
48		if let Ok(mut Guard) = self.entries.lock() {
49			Guard.remove(&handle);
50		}
51	}
52
53	/// Returns the number of active registrations.
54	#[allow(dead_code)]
55	fn len(&self) -> usize { self.entries.lock().map(|G| G.len()).unwrap_or(0) }
56}
57
58/// VS Code API facade - the main entry point for extensions
59#[derive(Debug, Clone)]
60pub struct VSCodeAPI {
61	/// Commands namespace
62	pub commands:Arc<CommandNamespace>,
63
64	/// Window namespace
65	pub window:Arc<Window>,
66
67	/// Workspace namespace
68	pub workspace:Arc<Workspace>,
69
70	/// Languages namespace
71	pub languages:Arc<LanguageNamespace>,
72
73	/// Extensions namespace
74	pub extensions:Arc<ExtensionNamespace>,
75
76	/// Environment namespace
77	pub env:Arc<Env>,
78}
79
80impl VSCodeAPI {
81	/// Create a new VS Code API facade (no transport - registrations stored
82	/// locally only)
83	pub fn new() -> Self {
84		Self {
85			commands:Arc::new(CommandNamespace::new()),
86
87			window:Arc::new(Window::new()),
88
89			workspace:Arc::new(Workspace::new()),
90
91			languages:Arc::new(LanguageNamespace::new()),
92
93			extensions:Arc::new(ExtensionNamespace::new()),
94
95			env:Arc::new(Env::new()),
96		}
97	}
98
99	/// Create a VS Code API facade wired to a Mountain transport.
100	/// Provider registrations will be forwarded to Mountain via
101	/// `send_no_response`.
102	pub fn new_with_transport(transport:Arc<Transport>) -> Self {
103		Self {
104			commands:Arc::new(CommandNamespace::new()),
105
106			window:Arc::new(Window::new()),
107
108			workspace:Arc::new(Workspace::new()),
109
110			languages:Arc::new(LanguageNamespace::new_with_transport(Arc::clone(&transport))),
111
112			extensions:Arc::new(ExtensionNamespace::new()),
113
114			env:Arc::new(Env::new()),
115		}
116	}
117}
118
119impl Default for VSCodeAPI {
120	fn default() -> Self { Self::new() }
121}
122
123/// Commands namespace
124#[derive(Debug, Clone)]
125pub struct CommandNamespace;
126
127impl CommandNamespace {
128	/// Create a new CommandNamespace instance
129	pub fn new() -> Self { Self }
130
131	/// Register a command
132	pub fn register_command(&self, command_id:String, _callback:CommandCallback) -> Result<Command, String> {
133		Ok(Command { id:command_id.clone() })
134	}
135
136	/// Execute a command
137	pub async fn execute_command<T:serde::de::DeserializeOwned>(
138		&self,
139
140		command_id:String,
141
142		_args:Vec<serde_json::Value>,
143	) -> Result<T, String> {
144		// Placeholder implementation
145		Err(format!("Command not implemented: {}", command_id))
146	}
147}
148
149/// Command callback type
150pub type CommandCallback = Box<dyn Fn(Vec<serde_json::Value>) -> Result<serde_json::Value, String> + Send + Sync>;
151
152/// Command representation
153#[derive(Debug, Clone)]
154pub struct Command {
155	/// The unique identifier of the command
156	pub id:String,
157}
158
159/// Window namespace
160#[derive(Debug, Clone)]
161pub struct Window;
162
163impl Window {
164	/// Create a new Window instance
165	pub fn new() -> Self { Self }
166
167	/// Show an information message
168	pub async fn show_information_message(&self, _message:String) -> Result<String, String> {
169		// Placeholder implementation
170		Ok("OK".to_string())
171	}
172
173	/// Show a warning message
174	pub async fn show_warning_message(&self, _message:String) -> Result<String, String> {
175		// Placeholder implementation
176		Ok("OK".to_string())
177	}
178
179	/// Show an error message
180	pub async fn show_error_message(&self, _message:String) -> Result<String, String> {
181		// Placeholder implementation
182		Ok("OK".to_string())
183	}
184
185	/// Create and show a new output channel
186	pub fn create_output_channel(&self, name:String) -> OutputChannel { OutputChannel::new(name) }
187}
188
189/// Output channel for logging
190#[derive(Debug, Clone)]
191pub struct OutputChannel {
192	/// The name of the output channel
193	name:String,
194}
195
196impl OutputChannel {
197	/// Create a new output channel
198	///
199	/// # Arguments
200	///
201	/// * `name` - The name of the output channel
202	pub fn new(name:String) -> Self { Self { name } }
203
204	/// Append a line to the channel
205	pub fn append_line(&self, line:&str) {
206		dev_log!("output", "[{}] {}", self.name, line);
207	}
208
209	/// Append to the channel
210	pub fn append(&self, value:&str) {
211		dev_log!("output", "[{}] {}", self.name, value);
212	}
213
214	/// Show the output channel
215	pub fn show(&self) {
216
217		// Placeholder - in real implementation, would show the channel
218	}
219
220	/// Hide the output channel
221	pub fn hide(&self) {
222
223		// Placeholder - in real implementation, would hide the channel
224	}
225
226	/// Dispose the output channel
227	pub fn dispose(&self) {
228
229		// Placeholder - in real implementation, would dispose resources
230	}
231}
232
233/// Workspace namespace
234#[derive(Debug, Clone)]
235pub struct Workspace;
236
237impl Workspace {
238	/// Create a new Workspace instance
239	pub fn new() -> Self { Self }
240
241	/// Get workspace folders
242	pub fn workspace_folders(&self) -> Vec<WorkspaceFolder> {
243		// Placeholder implementation
244		Vec::new()
245	}
246
247	/// Get workspace configuration
248	pub fn get_configuration(&self, section:Option<String>) -> WorkspaceConfiguration {
249		WorkspaceConfiguration::new(section)
250	}
251}
252
253/// Workspace folder
254#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct WorkspaceFolder {
256	/// The uri of the workspace folder
257	pub uri:String,
258
259	/// The name of the workspace folder
260	pub name:String,
261
262	/// The ordinal number of the workspace folder
263	pub index:u32,
264}
265
266/// Workspace configuration
267#[derive(Debug, Clone)]
268pub struct WorkspaceConfiguration {
269	/// The configuration section name
270	#[allow(dead_code)]
271	section:Option<String>,
272}
273
274impl WorkspaceConfiguration {
275	/// Create a new workspace configuration
276	///
277	/// # Arguments
278	///
279	/// * `section` - Optional section name to retrieve
280	pub fn new(section:Option<String>) -> Self { Self { section } }
281
282	/// Get a configuration value
283	pub fn get<T:serde::de::DeserializeOwned>(&self, _key:String) -> Result<T, String> {
284		// Placeholder implementation
285		Err("Configuration not implemented".to_string())
286	}
287
288	/// Check if a key exists in the configuration
289	pub fn has(&self, _key:String) -> bool { false }
290
291	/// Update a configuration value
292	pub async fn update(&self, _key:String, _value:serde_json::Value) -> Result<(), String> {
293		// Placeholder implementation
294		Err("Update configuration not implemented".to_string())
295	}
296}
297
298/// Languages namespace - mirrors the full vscode.languages API surface.
299///
300/// Each `register_*_provider` method:
301/// 1. Assigns a unique handle from the atomic counter
302/// 2. Stores the registration in `ProviderStore` for lifecycle tracking
303/// 3. If a Mountain `Transport` is wired, forwards a `send_no_response` JSON
304///    notification matching Mountain's `GenericNotification` format so that
305///    Mountain can store the provider in its `ProviderRegistry`
306/// 4. Returns a `Disposable` that removes the registration on dispose
307#[derive(Debug)]
308pub struct LanguageNamespace {
309	/// Active provider registration store.
310	store:Arc<ProviderStore>,
311
312	/// Optional transport to Mountain for forwarding registrations.
313	transport:Option<Arc<Transport>>,
314}
315
316impl Clone for LanguageNamespace {
317	fn clone(&self) -> Self { Self { store:Arc::clone(&self.store), transport:self.transport.clone() } }
318}
319
320impl LanguageNamespace {
321	/// Create a new LanguageNamespace instance (local storage only).
322	pub fn new() -> Self { Self { store:Arc::new(ProviderStore::default()), transport:None } }
323
324	/// Create a new LanguageNamespace wired to a Mountain transport.
325	/// Registrations are forwarded via `send_no_response` as JSON
326	/// notifications.
327	pub fn new_with_transport(transport:Arc<Transport>) -> Self {
328		Self { store:Arc::new(ProviderStore::default()), transport:Some(transport) }
329	}
330
331	/// Returns the number of active provider registrations.
332	pub fn active_registration_count(&self) -> usize { self.store.len() }
333
334	/// Internal helper: register a provider, return a disposable handle.
335	fn register(&self, provider_type:&str, selector:&DocumentSelector) -> Disposable {
336		let ProviderTypeOwned = provider_type.to_string();
337
338		let SelectorStr = selector
339			.iter()
340			.filter_map(|F| F.language.as_deref())
341			.collect::<Vec<_>>()
342			.join(",");
343
344		let Handle = self.store.insert(&ProviderTypeOwned, &SelectorStr);
345
346		let Store = Arc::clone(&self.store);
347
348		dev_log!(
349			"extensions",
350			"[LanguageNamespace] registered {} handle={} selector={}",
351			ProviderTypeOwned,
352			Handle,
353			SelectorStr
354		);
355
356		// Forward registration to Mountain if transport is wired
357		if let Some(Transport) = &self.transport {
358			let Notification = serde_json::json!({
359				"method": format!("register_{}", ProviderTypeOwned),
360				"parameters": {
361					"handle": Handle,
362					"language_selector": SelectorStr,
363					"extension_id": "grove-extension",
364				}
365			});
366
367			if let Ok(Bytes) = serde_json::to_vec(&Notification) {
368				let TransportClone = Arc::clone(Transport);
369
370				tokio::spawn(async move {
371					let _ = TransportClone.send_no_response(&Bytes).await;
372				});
373			}
374		}
375
376		Disposable::with_callback(Box::new(move || {
377			Store.remove(Handle);
378			dev_log!(
379				"extensions",
380				"[LanguageNamespace] disposed {} handle={}",
381				ProviderTypeOwned,
382				Handle
383			);
384		}))
385	}
386
387	/// Register completion item provider
388	pub async fn register_completion_item_provider<T:CompletionItemProvider>(
389		&self,
390
391		selector:DocumentSelector,
392
393		_provider:T,
394
395		_trigger_characters:Option<Vec<String>>,
396	) -> Result<Disposable, String> {
397		Ok(self.register("completion", &selector))
398	}
399
400	/// Register hover provider
401	pub fn register_hover_provider(&self, selector:DocumentSelector) -> Disposable { self.register("hover", &selector) }
402
403	/// Register definition provider
404	pub fn register_definition_provider(&self, selector:DocumentSelector) -> Disposable {
405		self.register("definition", &selector)
406	}
407
408	/// Register reference provider
409	pub fn register_reference_provider(&self, selector:DocumentSelector) -> Disposable {
410		self.register("references", &selector)
411	}
412
413	/// Register code actions provider
414	pub fn register_code_actions_provider(&self, selector:DocumentSelector) -> Disposable {
415		self.register("codeAction", &selector)
416	}
417
418	/// Register document highlight provider
419	pub fn register_document_highlight_provider(&self, selector:DocumentSelector) -> Disposable {
420		self.register("documentHighlight", &selector)
421	}
422
423	/// Register document symbol provider
424	pub fn register_document_symbol_provider(&self, selector:DocumentSelector) -> Disposable {
425		self.register("documentSymbol", &selector)
426	}
427
428	/// Register workspace symbol provider
429	pub fn register_workspace_symbol_provider(&self) -> Disposable { self.register("workspaceSymbol", &Vec::new()) }
430
431	/// Register rename provider
432	pub fn register_rename_provider(&self, selector:DocumentSelector) -> Disposable {
433		self.register("rename", &selector)
434	}
435
436	/// Register document formatting provider
437	pub fn register_document_formatting_edit_provider(&self, selector:DocumentSelector) -> Disposable {
438		self.register("documentFormatting", &selector)
439	}
440
441	/// Register document range formatting provider
442	pub fn register_document_range_formatting_edit_provider(&self, selector:DocumentSelector) -> Disposable {
443		self.register("documentRangeFormatting", &selector)
444	}
445
446	/// Register on-type formatting provider
447	pub fn register_on_type_formatting_edit_provider(
448		&self,
449
450		selector:DocumentSelector,
451
452		_trigger_characters:Vec<String>,
453	) -> Disposable {
454		self.register("onTypeFormatting", &selector)
455	}
456
457	/// Register signature help provider
458	pub fn register_signature_help_provider(&self, selector:DocumentSelector) -> Disposable {
459		self.register("signatureHelp", &selector)
460	}
461
462	/// Register code lens provider
463	pub fn register_code_lens_provider(&self, selector:DocumentSelector) -> Disposable {
464		self.register("codeLens", &selector)
465	}
466
467	/// Register folding range provider
468	pub fn register_folding_range_provider(&self, selector:DocumentSelector) -> Disposable {
469		self.register("foldingRange", &selector)
470	}
471
472	/// Register selection range provider
473	pub fn register_selection_range_provider(&self, selector:DocumentSelector) -> Disposable {
474		self.register("selectionRange", &selector)
475	}
476
477	/// Register semantic tokens provider
478	pub fn register_document_semantic_tokens_provider(&self, selector:DocumentSelector) -> Disposable {
479		self.register("semanticTokens", &selector)
480	}
481
482	/// Register inlay hints provider
483	pub fn register_inlay_hints_provider(&self, selector:DocumentSelector) -> Disposable {
484		self.register("inlayHints", &selector)
485	}
486
487	/// Register type hierarchy provider
488	pub fn register_type_hierarchy_provider(&self, selector:DocumentSelector) -> Disposable {
489		self.register("typeHierarchy", &selector)
490	}
491
492	/// Register call hierarchy provider
493	pub fn register_call_hierarchy_provider(&self, selector:DocumentSelector) -> Disposable {
494		self.register("callHierarchy", &selector)
495	}
496
497	/// Register linked editing range provider
498	pub fn register_linked_editing_range_provider(&self, selector:DocumentSelector) -> Disposable {
499		self.register("linkedEditingRange", &selector)
500	}
501
502	/// Register declaration provider
503	pub fn register_declaration_provider(&self, selector:DocumentSelector) -> Disposable {
504		self.register("declaration", &selector)
505	}
506
507	/// Register implementation provider
508	pub fn register_implementation_provider(&self, selector:DocumentSelector) -> Disposable {
509		self.register("implementation", &selector)
510	}
511
512	/// Register type definition provider
513	pub fn register_type_definition_provider(&self, selector:DocumentSelector) -> Disposable {
514		self.register("typeDefinition", &selector)
515	}
516
517	/// Register diagnostic collection
518	pub fn create_diagnostic_collection(&self, name:Option<String>) -> DiagnosticCollection {
519		DiagnosticCollection::new(name)
520	}
521
522	/// Set language configuration
523	pub fn set_language_configuration(&self, language:String) -> Disposable {
524		self.register(
525			"languageConfiguration",
526			&vec![DocumentFilter { language:Some(language), scheme:None, pattern:None }],
527		)
528	}
529}
530
531/// Document selector
532#[derive(Debug, Clone, Serialize, Deserialize)]
533pub struct DocumentFilter {
534	/// A language id, like `typescript`
535	pub language:Option<String>,
536
537	/// A Uri scheme, like `file` or `untitled`
538	pub scheme:Option<String>,
539
540	/// A glob pattern, like `*.{ts,js}`
541	pub pattern:Option<String>,
542}
543
544/// Document selector type
545pub type DocumentSelector = Vec<DocumentFilter>;
546
547/// Completion item provider
548pub trait CompletionItemProvider: Send + Sync {
549	/// Provide completion items at the given position
550	///
551	/// # Arguments
552	///
553	/// * `document` - The text document identifier
554	/// * `position` - The position in the document
555	/// * `context` - The completion context
556	/// * `token` - Optional cancellation token
557	///
558	/// # Returns
559	///
560	/// A vector of completion items
561	fn provide_completion_items(
562		&self,
563
564		document:TextDocumentIdentifier,
565
566		position:Position,
567
568		context:CompletionContext,
569
570		token:Option<String>,
571	) -> Vec<CompletionItem>;
572}
573
574/// Completion context
575#[derive(Debug, Clone, Serialize, Deserialize)]
576pub struct CompletionContext {
577	/// How the completion was triggered
578	#[serde(rename = "triggerKind")]
579	pub trigger_kind:CompletionTriggerKind,
580
581	/// The character that triggered the completion
582	#[serde(rename = "triggerCharacter")]
583	pub trigger_character:Option<String>,
584}
585
586/// Completion trigger kind
587#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
588pub enum CompletionTriggerKind {
589	/// Completion was triggered by typing an identifier
590	#[serde(rename = "Invoke")]
591	Invoke = 0,
592
593	/// Completion was triggered by a trigger character
594	#[serde(rename = "TriggerCharacter")]
595	TriggerCharacter = 1,
596
597	/// Completion was re-triggered
598	#[serde(rename = "TriggerForIncompleteCompletions")]
599	TriggerForIncompleteCompletions = 2,
600}
601
602/// Diagnostic collection
603#[derive(Debug, Clone)]
604pub struct DiagnosticCollection {
605	/// The name of the diagnostic collection
606	#[allow(dead_code)]
607	name:Option<String>,
608}
609
610impl DiagnosticCollection {
611	/// Create a new diagnostic collection
612	///
613	/// # Arguments
614	///
615	/// * `name` - Optional name for the collection
616	pub fn new(name:Option<String>) -> Self { Self { name } }
617
618	/// Set diagnostics for a resource
619	pub fn set(&self, _uri:String, _diagnostics:Vec<Diagnostic>) {
620
621		// Placeholder implementation
622	}
623
624	/// Delete diagnostics for a resource
625	pub fn delete(&self, _uri:String) {
626
627		// Placeholder implementation
628	}
629
630	/// Clear all diagnostics
631	pub fn clear(&self) {
632
633		// Placeholder implementation
634	}
635
636	/// Dispose the collection
637	pub fn dispose(&self) {
638
639		// Placeholder implementation
640	}
641}
642
643/// Disposable resource handle.
644///
645/// Returned by all `register_*_provider` methods. Calling `dispose()` removes
646/// the provider registration from the `LanguageNamespace` store.
647pub struct Disposable {
648	callback:Option<Box<dyn FnOnce() + Send + Sync>>,
649}
650
651impl std::fmt::Debug for Disposable {
652	fn fmt(&self, f:&mut std::fmt::Formatter<'_>) -> std::fmt::Result {
653		f.debug_struct("Disposable")
654			.field("has_callback", &self.callback.is_some())
655			.finish()
656	}
657}
658
659impl Clone for Disposable {
660	/// Cloning a Disposable produces a no-op copy.
661	/// The original disposable retains the callback.
662	fn clone(&self) -> Self { Self { callback:None } }
663}
664
665impl Disposable {
666	/// Create a no-op disposable.
667	pub fn new() -> Self { Self { callback:None } }
668
669	/// Create a disposable with a callback invoked on `dispose()`.
670	pub fn with_callback(callback:Box<dyn FnOnce() + Send + Sync>) -> Self { Self { callback:Some(callback) } }
671
672	/// Dispose the resource, invoking the registered callback if present.
673	pub fn dispose(mut self) {
674		if let Some(Callback) = self.callback.take() {
675			Callback();
676		}
677	}
678}
679
680impl Default for Disposable {
681	fn default() -> Self { Self::new() }
682}
683
684/// Extensions namespace
685#[derive(Debug, Clone)]
686pub struct ExtensionNamespace;
687
688impl ExtensionNamespace {
689	/// Create a new ExtensionNamespace instance
690	pub fn new() -> Self { Self }
691
692	/// Get all extensions
693	pub fn all(&self) -> Vec<Extension> { Vec::new() }
694
695	/// Get an extension by id
696	pub fn get_extension(&self, _extension_id:String) -> Option<Extension> { None }
697}
698
699/// Extension representation
700#[derive(Debug, Clone, Serialize, Deserialize)]
701pub struct Extension {
702	/// The canonical extension identifier in the form of `publisher.name`
703	pub id:String,
704
705	/// The absolute file path of the directory containing the extension
706	#[serde(rename = "extensionPath")]
707	pub extension_path:String,
708
709	/// `true` if the extension is enabled
710	pub is_active:bool,
711
712	/// The package.json object of the extension
713	#[serde(rename = "packageJSON")]
714	pub package_json:serde_json::Value,
715}
716
717/// Environment namespace
718#[derive(Debug, Clone)]
719pub struct Env;
720
721impl Env {
722	/// Create a new Env instance
723	pub fn new() -> Self { Self }
724
725	/// Get environment variable
726	pub fn get_env_var(&self, name:String) -> Option<String> { std::env::var(name).ok() }
727
728	/// Check if running on a specific platform
729	pub fn is_windows(&self) -> bool { cfg!(windows) }
730
731	/// Check if running on macOS
732	pub fn is_mac(&self) -> bool { cfg!(target_os = "macos") }
733
734	/// Check if running on Linux
735	pub fn is_linux(&self) -> bool { cfg!(target_os = "linux") }
736
737	/// Get the app name
738	pub fn app_name(&self) -> String { "VS Code".to_string() }
739
740	/// Get the app root
741	pub fn app_root(&self) -> Option<String> { std::env::var("VSCODE_APP_ROOT").ok() }
742}
743
744#[cfg(test)]
745mod tests {
746
747	use super::*;
748
749	#[test]
750	fn test_vscode_api_creation() {
751		let _api = VSCodeAPI::new();
752
753		// Arc fields are always initialized, so just verify creation works
754	}
755
756	#[test]
757	fn test_position_operations() {
758		let pos = Position::new(5, 10);
759
760		assert_eq!(pos.line, 5);
761
762		assert_eq!(pos.character, 10);
763	}
764
765	#[test]
766	fn test_output_channel() {
767		let channel = OutputChannel::new("test".to_string());
768
769		channel.append_line("test message");
770	}
771
772	#[test]
773	fn test_disposable() {
774		let disposable = Disposable::new();
775
776		disposable.dispose();
777	}
778}