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