1use crate::config::{DepsConfig, InlayHintsConfig};
7use crate::document::{ServerState, ensure_document_loaded};
8use deps_core::EcosystemConfig;
9use std::sync::Arc;
10use tokio::sync::RwLock;
11use tower_lsp_server::Client;
12use tower_lsp_server::ls_types::{InlayHint, InlayHintParams};
13
14pub async fn handle_inlay_hints(
19 state: Arc<ServerState>,
20 params: InlayHintParams,
21 config: &InlayHintsConfig,
22 client: Client,
23 full_config: Arc<RwLock<DepsConfig>>,
24) -> Vec<InlayHint> {
25 if !config.enabled {
26 return vec![];
27 }
28
29 let uri = ¶ms.text_document.uri;
30
31 if !ensure_document_loaded(uri, Arc::clone(&state), client, Arc::clone(&full_config)).await {
33 tracing::warn!("Could not load document for inlay hints: {:?}", uri);
34 return vec![];
35 }
36
37 let doc = match state.get_document(uri) {
39 Some(d) => d,
40 None => {
41 tracing::warn!("Document not found: {:?}", uri);
42 return vec![];
43 }
44 };
45
46 let ecosystem = match state.ecosystem_registry.get(doc.ecosystem_id) {
47 Some(e) => e,
48 None => {
49 tracing::warn!("Ecosystem not found: {}", doc.ecosystem_id);
50 return vec![];
51 }
52 };
53
54 let parse_result = match doc.parse_result() {
55 Some(p) => p,
56 None => return vec![],
57 };
58
59 let loading_config = { full_config.read().await.loading_indicator.clone() };
61
62 let ecosystem_config = EcosystemConfig {
63 show_up_to_date_hints: true,
64 up_to_date_text: config.up_to_date_text.clone(),
65 needs_update_text: config.needs_update_text.clone(),
66 loading_text: loading_config.loading_text,
67 show_loading_hints: loading_config.enabled && loading_config.fallback_to_hints,
68 };
69
70 ecosystem
72 .generate_inlay_hints(
73 parse_result,
74 &doc.cached_versions,
75 &doc.resolved_versions,
76 doc.loading_state,
77 &ecosystem_config,
78 )
79 .await
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use crate::document::ServerState;
86 use crate::test_utils::test_helpers::create_test_client_and_config;
87 use tower_lsp_server::ls_types::{TextDocumentIdentifier, Uri};
88
89 #[test]
92 fn test_handle_inlay_hints_disabled() {
93 let config = InlayHintsConfig {
94 enabled: false,
95 up_to_date_text: "✅".to_string(),
96 needs_update_text: "❌ {}".to_string(),
97 };
98
99 assert!(!config.enabled);
100 }
101
102 #[tokio::test]
103 async fn test_handle_inlay_hints_disabled_returns_empty() {
104 let state = Arc::new(ServerState::new());
105 let uri = Uri::from_file_path("/test/Cargo.toml").unwrap();
106 let config = InlayHintsConfig {
107 enabled: false,
108 up_to_date_text: "✅".to_string(),
109 needs_update_text: "❌ {}".to_string(),
110 };
111
112 let params = InlayHintParams {
113 text_document: TextDocumentIdentifier { uri },
114 work_done_progress_params: Default::default(),
115 range: tower_lsp_server::ls_types::Range::new(
116 tower_lsp_server::ls_types::Position::new(0, 0),
117 tower_lsp_server::ls_types::Position::new(100, 0),
118 ),
119 };
120
121 let (client, full_config) = create_test_client_and_config();
122 let result = handle_inlay_hints(state, params, &config, client, full_config).await;
123 assert!(result.is_empty());
124 }
125
126 #[tokio::test]
127 async fn test_handle_inlay_hints_missing_document() {
128 let state = Arc::new(ServerState::new());
129 let uri = Uri::from_file_path("/test/Cargo.toml").unwrap();
130 let config = InlayHintsConfig {
131 enabled: true,
132 up_to_date_text: "✅".to_string(),
133 needs_update_text: "❌ {}".to_string(),
134 };
135
136 let params = InlayHintParams {
137 text_document: TextDocumentIdentifier { uri },
138 work_done_progress_params: Default::default(),
139 range: tower_lsp_server::ls_types::Range::new(
140 tower_lsp_server::ls_types::Position::new(0, 0),
141 tower_lsp_server::ls_types::Position::new(100, 0),
142 ),
143 };
144
145 let (client, full_config) = create_test_client_and_config();
146 let result = handle_inlay_hints(state, params, &config, client, full_config).await;
147 assert!(result.is_empty());
148 }
149
150 #[cfg(feature = "cargo")]
152 mod cargo_tests {
153 use super::*;
154 use crate::document::{DocumentState, Ecosystem};
155
156 #[tokio::test]
157 async fn test_handle_inlay_hints() {
158 let state = Arc::new(ServerState::new());
159 let uri = Uri::from_file_path("/test/Cargo.toml").unwrap();
160 let config = InlayHintsConfig {
161 enabled: true,
162 up_to_date_text: "✅".to_string(),
163 needs_update_text: "❌ {}".to_string(),
164 };
165
166 let ecosystem = state.ecosystem_registry.get("cargo").unwrap();
167 let content = r#"[dependencies]
168serde = "1.0.0"
169"#
170 .to_string();
171
172 let parse_result = ecosystem
173 .parse_manifest(&content, &uri)
174 .await
175 .expect("Failed to parse manifest");
176
177 let doc_state = DocumentState::new_from_parse_result("cargo", content, parse_result);
178 state.update_document(uri.clone(), doc_state);
179
180 let params = InlayHintParams {
181 text_document: TextDocumentIdentifier { uri },
182 work_done_progress_params: Default::default(),
183 range: tower_lsp_server::ls_types::Range::new(
184 tower_lsp_server::ls_types::Position::new(0, 0),
185 tower_lsp_server::ls_types::Position::new(100, 0),
186 ),
187 };
188
189 let (client, full_config) = create_test_client_and_config();
190 let _result = handle_inlay_hints(state, params, &config, client, full_config).await;
191 }
193
194 #[tokio::test]
195 async fn test_handle_inlay_hints_no_parse_result() {
196 let state = Arc::new(ServerState::new());
197 let uri = Uri::from_file_path("/test/Cargo.toml").unwrap();
198 let config = InlayHintsConfig {
199 enabled: true,
200 up_to_date_text: "✅".to_string(),
201 needs_update_text: "❌ {}".to_string(),
202 };
203
204 let doc_state = DocumentState::new(Ecosystem::Cargo, String::new(), vec![]);
205 state.update_document(uri.clone(), doc_state);
206
207 let params = InlayHintParams {
208 text_document: TextDocumentIdentifier { uri },
209 work_done_progress_params: Default::default(),
210 range: tower_lsp_server::ls_types::Range::new(
211 tower_lsp_server::ls_types::Position::new(0, 0),
212 tower_lsp_server::ls_types::Position::new(100, 0),
213 ),
214 };
215
216 let (client, full_config) = create_test_client_and_config();
217 let result = handle_inlay_hints(state, params, &config, client, full_config).await;
218 assert!(result.is_empty());
219 }
220
221 #[tokio::test]
222 async fn test_handle_inlay_hints_custom_config() {
223 let state = Arc::new(ServerState::new());
224 let uri = Uri::from_file_path("/test/Cargo.toml").unwrap();
225 let config = InlayHintsConfig {
226 enabled: true,
227 up_to_date_text: "OK".to_string(),
228 needs_update_text: "UPDATE: {}".to_string(),
229 };
230
231 let ecosystem = state.ecosystem_registry.get("cargo").unwrap();
232 let content = r#"[dependencies]
233serde = "1.0.0"
234"#
235 .to_string();
236
237 let parse_result = ecosystem
238 .parse_manifest(&content, &uri)
239 .await
240 .expect("Failed to parse manifest");
241
242 let doc_state = DocumentState::new_from_parse_result("cargo", content, parse_result);
243 state.update_document(uri.clone(), doc_state);
244
245 let params = InlayHintParams {
246 text_document: TextDocumentIdentifier { uri },
247 work_done_progress_params: Default::default(),
248 range: tower_lsp_server::ls_types::Range::new(
249 tower_lsp_server::ls_types::Position::new(0, 0),
250 tower_lsp_server::ls_types::Position::new(100, 0),
251 ),
252 };
253
254 let (client, full_config) = create_test_client_and_config();
255 let _result = handle_inlay_hints(state, params, &config, client, full_config).await;
256 }
258 }
259
260 #[cfg(feature = "npm")]
262 mod npm_tests {
263 use super::*;
264 use crate::document::DocumentState;
265
266 #[tokio::test]
267 async fn test_handle_inlay_hints() {
268 let state = Arc::new(ServerState::new());
269 let uri = Uri::from_file_path("/test/package.json").unwrap();
270 let config = InlayHintsConfig {
271 enabled: true,
272 up_to_date_text: "✅".to_string(),
273 needs_update_text: "❌ {}".to_string(),
274 };
275
276 let ecosystem = state.ecosystem_registry.get("npm").unwrap();
277 let content = r#"{"dependencies": {"express": "4.0.0"}}"#.to_string();
278
279 let parse_result = ecosystem
280 .parse_manifest(&content, &uri)
281 .await
282 .expect("Failed to parse manifest");
283
284 let doc_state = DocumentState::new_from_parse_result("npm", content, parse_result);
285 state.update_document(uri.clone(), doc_state);
286
287 let params = InlayHintParams {
288 text_document: TextDocumentIdentifier { uri },
289 work_done_progress_params: Default::default(),
290 range: tower_lsp_server::ls_types::Range::new(
291 tower_lsp_server::ls_types::Position::new(0, 0),
292 tower_lsp_server::ls_types::Position::new(100, 0),
293 ),
294 };
295
296 let (client, full_config) = create_test_client_and_config();
297 let _result = handle_inlay_hints(state, params, &config, client, full_config).await;
298 }
300 }
301
302 #[cfg(feature = "pypi")]
304 mod pypi_tests {
305 use super::*;
306 use crate::document::DocumentState;
307
308 #[tokio::test]
309 async fn test_handle_inlay_hints() {
310 let state = Arc::new(ServerState::new());
311 let uri = Uri::from_file_path("/test/pyproject.toml").unwrap();
312 let config = InlayHintsConfig {
313 enabled: true,
314 up_to_date_text: "✅".to_string(),
315 needs_update_text: "❌ {}".to_string(),
316 };
317
318 let ecosystem = state.ecosystem_registry.get("pypi").unwrap();
319 let content = r#"[project]
320dependencies = ["requests>=2.0.0"]
321"#
322 .to_string();
323
324 let parse_result = ecosystem
325 .parse_manifest(&content, &uri)
326 .await
327 .expect("Failed to parse manifest");
328
329 let doc_state = DocumentState::new_from_parse_result("pypi", content, parse_result);
330 state.update_document(uri.clone(), doc_state);
331
332 let params = InlayHintParams {
333 text_document: TextDocumentIdentifier { uri },
334 work_done_progress_params: Default::default(),
335 range: tower_lsp_server::ls_types::Range::new(
336 tower_lsp_server::ls_types::Position::new(0, 0),
337 tower_lsp_server::ls_types::Position::new(100, 0),
338 ),
339 };
340
341 let (client, full_config) = create_test_client_and_config();
342 let _result = handle_inlay_hints(state, params, &config, client, full_config).await;
343 }
345 }
346}