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