Skip to main content

Mountain/RPC/CocoonService/Provider/
ProvideInlineCompletionItems.rs

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