Skip to main content

Mountain/RPC/CocoonService/Provider/
ProvideInlineCompletionItems.rs

1//! `ProvideInlineCompletionItems` gRPC RPC handler.
2//!
3//! Called by Mountain's `IInlineCompletionsProvider` (wired into Monaco via
4//! the language feature registry) when the cursor pauses or the user presses
5//! a completion trigger key. Forwards the request to Cocoon which dispatches
6//! `$provideInlineCompletionItems` to the registered extension provider
7//! (GitHub Copilot, Roo Code, Continue, etc.).
8//!
9//! The response is a list of `InlineCompletionItem` messages that Mountain
10//! maps back into Monaco's `InlineCompletionList`.
11
12use tonic::{Response, Status};
13use url::Url;
14use CommonLibrary::LanguageFeature::{
15	DTO::PositionDTO::PositionDTO,
16	LanguageFeatureProviderRegistry::LanguageFeatureProviderRegistry,
17};
18use serde_json::json;
19use ::Vine::Generated::{InlineCompletionItem, ProvideInlineCompletionRequest, ProvideInlineCompletionResponse};
20
21use crate::{RPC::CocoonService::CocoonServiceImpl, dev_log};
22
23pub async fn Fn(
24	Service:&CocoonServiceImpl,
25
26	Request:ProvideInlineCompletionRequest,
27) -> Result<Response<ProvideInlineCompletionResponse>, Status> {
28	let URI = Request.uri.as_ref().map(|U| U.value.as_str()).unwrap_or("");
29
30	let Position_ = Request.position.as_ref();
31
32	let Line = Position_.map(|P| P.line).unwrap_or(0);
33
34	let Character = Position_.map(|P| P.character).unwrap_or(0);
35
36	dev_log!(
37		"provider",
38		"ProvideInlineCompletionItems handle={} uri={} line={} char={}",
39		Request.provider_handle,
40		URI,
41		Line,
42		Character
43	);
44
45	let DocumentURI = Url::parse(URI).map_err(|E| Status::invalid_argument(format!("Invalid URI: {}", E)))?;
46
47	let PositionDTO_ = PositionDTO { LineNumber:Line, Column:Character };
48
49	let Context = json!({
50		"triggerKind": Request.context.as_ref().map(|C| C.trigger_kind).unwrap_or(0),
51		"selectedCompletionInfo": Request.context.as_ref()
52			.map(|C| C.selected_completion_info.as_str())
53			.unwrap_or(""),
54	});
55
56	match Service
57		.environment
58		.ProvideInlineCompletionItems(DocumentURI, PositionDTO_, Context)
59		.await
60	{
61		Ok(Some(Raw)) => {
62			// Raw is a JSON Value returned by Cocoon's provider.
63			// Shape: { items: [{ insertText, range?, isSnippet?, command? }] }
64			// or an array directly.
65			let ItemsArr = Raw
66				.get("items")
67				.and_then(|V| V.as_array())
68				.cloned()
69				.or_else(|| Raw.as_array().cloned())
70				.unwrap_or_default();
71
72			let Items:Vec<InlineCompletionItem> = ItemsArr
73				.iter()
74				.filter_map(|Item| {
75					let InsertText = Item
76						.get("insertText")
77						.and_then(|V| V.as_str())
78						.or_else(|| Item.get("text").and_then(|V| V.as_str()))
79						.unwrap_or("");
80
81					if InsertText.is_empty() {
82						return None;
83					}
84
85					let IsSnippet = Item.get("isSnippet").and_then(|V| V.as_bool()).unwrap_or(false);
86
87					let Command = Item
88						.get("command")
89						.and_then(|V| V.get("command"))
90						.and_then(|V| V.as_str())
91						.or_else(|| Item.get("command").and_then(|V| V.as_str()))
92						.unwrap_or("")
93						.to_string();
94
95					// prost generates snake_case field names from proto PascalCase.
96					Some(InlineCompletionItem {
97						insert_text:InsertText.to_string(),
98						range:None, // Range hydration deferred to Mountain's provider impl
99						command:Command,
100						is_snippet:IsSnippet,
101					})
102				})
103				.collect();
104
105			Ok(Response::new(ProvideInlineCompletionResponse { items:Items }))
106		},
107
108		Ok(None) => Ok(Response::new(ProvideInlineCompletionResponse::default())),
109
110		Err(Error) => {
111			dev_log!("provider", "warn: [ProvideInlineCompletionItems] provider error: {}", Error);
112
113			Ok(Response::new(ProvideInlineCompletionResponse::default()))
114		},
115	}
116}