mcpls_core/bridge/translator/
navigation.rs1use lsp_types::{
5 GotoDefinitionParams, Hover, HoverContents, HoverParams as LspHoverParams, MarkedString,
6 PartialResultParams, ReferenceContext, ReferenceParams, TextDocumentIdentifier,
7 TextDocumentPositionParams, WorkDoneProgressParams,
8};
9
10use super::Translator;
11use super::dto::{DefinitionResult, HoverResult, Location, LocationsResult, ReferencesResult};
12use super::encoding_ctx::EncodingCtx;
13use crate::config::ToolKind;
14use crate::error::Result;
15
16async fn goto_response_to_locations(
18 response: Option<lsp_types::GotoDefinitionResponse>,
19 ctx: &EncodingCtx,
20) -> Vec<Location> {
21 let lsp_locs: Vec<lsp_types::Location> = match response {
22 Some(lsp_types::GotoDefinitionResponse::Scalar(loc)) => vec![loc],
23 Some(lsp_types::GotoDefinitionResponse::Array(locs)) => locs,
24 Some(lsp_types::GotoDefinitionResponse::Link(links)) => links
25 .into_iter()
26 .map(|link| lsp_types::Location {
27 uri: link.target_uri,
28 range: link.target_selection_range,
29 })
30 .collect(),
31 None => vec![],
32 };
33
34 let mut locations = Vec::with_capacity(lsp_locs.len());
35 for loc in lsp_locs {
36 locations.push(Location {
37 uri: loc.uri.to_string(),
38 range: ctx.normalize_range(&loc.uri, loc.range).await,
39 });
40 }
41 locations
42}
43
44fn extract_hover_contents(contents: HoverContents) -> String {
45 match contents {
46 HoverContents::Scalar(marked_string) => marked_string_to_string(marked_string),
47 HoverContents::Array(marked_strings) => marked_strings
48 .into_iter()
49 .map(marked_string_to_string)
50 .collect::<Vec<_>>()
51 .join("\n\n"),
52 HoverContents::Markup(markup) => markup.value,
53 }
54}
55
56fn marked_string_to_string(marked: MarkedString) -> String {
58 match marked {
59 MarkedString::String(s) => s,
60 MarkedString::LanguageString(ls) => format!("```{}\n{}\n```", ls.language, ls.value),
61 }
62}
63
64impl Translator {
65 pub async fn handle_hover(
72 &self,
73 file_path: String,
74 line: u32,
75 character: u32,
76 ) -> Result<HoverResult> {
77 let (server_id, client, uri) = self
78 .prepare_gated_document(&file_path, ToolKind::Hover, "hoverProvider", |caps| {
79 matches!(
80 caps.hover_provider,
81 Some(
82 lsp_types::HoverProviderCapability::Simple(true)
83 | lsp_types::HoverProviderCapability::Options(_)
84 )
85 )
86 })
87 .await?;
88 let ctx = self.encoding_ctx(&server_id);
89 let lsp_position = ctx.to_lsp(&uri, line, character).await;
90 let response_uri = uri.clone();
91
92 let params = LspHoverParams {
93 text_document_position_params: TextDocumentPositionParams {
94 text_document: TextDocumentIdentifier { uri },
95 position: lsp_position,
96 },
97 work_done_progress_params: WorkDoneProgressParams::default(),
98 };
99
100 let response: Option<Hover> = client
101 .request("textDocument/hover", params, client.request_timeout())
102 .await?;
103
104 let result = match response {
105 Some(hover) => {
106 let contents = extract_hover_contents(hover.contents);
107 let range = match hover.range {
108 Some(r) => Some(ctx.normalize_range(&response_uri, r).await),
109 None => None,
110 };
111 HoverResult { contents, range }
112 }
113 None => HoverResult {
114 contents: "No hover information available".to_string(),
115 range: None,
116 },
117 };
118
119 Ok(result)
120 }
121
122 pub async fn handle_definition(
129 &self,
130 file_path: String,
131 line: u32,
132 character: u32,
133 ) -> Result<DefinitionResult> {
134 let (server_id, client, uri) = self
135 .prepare_gated_document(
136 &file_path,
137 ToolKind::Definition,
138 "definitionProvider",
139 |caps| {
140 matches!(
141 caps.definition_provider,
142 Some(lsp_types::OneOf::Left(true) | lsp_types::OneOf::Right(_))
143 )
144 },
145 )
146 .await?;
147 let ctx = self.encoding_ctx(&server_id);
148 let lsp_position = ctx.to_lsp(&uri, line, character).await;
149
150 let params = GotoDefinitionParams {
151 text_document_position_params: TextDocumentPositionParams {
152 text_document: TextDocumentIdentifier { uri },
153 position: lsp_position,
154 },
155 work_done_progress_params: WorkDoneProgressParams::default(),
156 partial_result_params: PartialResultParams::default(),
157 };
158
159 let response: Option<lsp_types::GotoDefinitionResponse> = client
160 .request("textDocument/definition", params, client.request_timeout())
161 .await?;
162
163 let result = DefinitionResult {
164 locations: goto_response_to_locations(response, &ctx).await,
165 };
166
167 Ok(result)
168 }
169
170 pub async fn handle_references(
177 &self,
178 file_path: String,
179 line: u32,
180 character: u32,
181 include_declaration: bool,
182 ) -> Result<ReferencesResult> {
183 let (server_id, client, uri) = self
184 .prepare_gated_document(
185 &file_path,
186 ToolKind::References,
187 "referencesProvider",
188 |caps| {
189 matches!(
190 caps.references_provider,
191 Some(lsp_types::OneOf::Left(true) | lsp_types::OneOf::Right(_))
192 )
193 },
194 )
195 .await?;
196 let ctx = self.encoding_ctx(&server_id);
197 let lsp_position = ctx.to_lsp(&uri, line, character).await;
198
199 let params = ReferenceParams {
200 text_document_position: TextDocumentPositionParams {
201 text_document: TextDocumentIdentifier { uri },
202 position: lsp_position,
203 },
204 work_done_progress_params: WorkDoneProgressParams::default(),
205 partial_result_params: PartialResultParams::default(),
206 context: ReferenceContext {
207 include_declaration,
208 },
209 };
210
211 let response: Option<Vec<lsp_types::Location>> = client
212 .request("textDocument/references", params, client.request_timeout())
213 .await?;
214
215 let locations = response.unwrap_or_default();
216
217 let mut result_locations = Vec::with_capacity(locations.len());
218 for loc in locations {
219 result_locations.push(Location {
220 uri: loc.uri.to_string(),
221 range: ctx.normalize_range(&loc.uri, loc.range).await,
222 });
223 }
224 let result = ReferencesResult {
225 locations: result_locations,
226 };
227
228 Ok(result)
229 }
230
231 pub async fn handle_implementation(
240 &self,
241 file_path: String,
242 line: u32,
243 character: u32,
244 ) -> Result<LocationsResult> {
245 let (server_id, client, uri) = self
246 .prepare_gated_document(
247 &file_path,
248 ToolKind::Implementation,
249 "implementationProvider",
250 |caps| {
251 matches!(
252 caps.implementation_provider,
253 Some(
254 lsp_types::ImplementationProviderCapability::Simple(true)
255 | lsp_types::ImplementationProviderCapability::Options(_)
256 )
257 )
258 },
259 )
260 .await?;
261 let ctx = self.encoding_ctx(&server_id);
262 let lsp_position = ctx.to_lsp(&uri, line, character).await;
263
264 let params = GotoDefinitionParams {
265 text_document_position_params: TextDocumentPositionParams {
266 text_document: TextDocumentIdentifier { uri },
267 position: lsp_position,
268 },
269 work_done_progress_params: WorkDoneProgressParams::default(),
270 partial_result_params: PartialResultParams::default(),
271 };
272
273 let response: Option<lsp_types::GotoDefinitionResponse> = client
274 .request(
275 "textDocument/implementation",
276 params,
277 client.request_timeout(),
278 )
279 .await?;
280
281 Ok(LocationsResult {
282 locations: goto_response_to_locations(response, &ctx).await,
283 })
284 }
285
286 pub async fn handle_type_definition(
296 &self,
297 file_path: String,
298 line: u32,
299 character: u32,
300 ) -> Result<LocationsResult> {
301 let (server_id, client, uri) = self
302 .prepare_gated_document(
303 &file_path,
304 ToolKind::TypeDefinition,
305 "typeDefinitionProvider",
306 |caps| {
307 matches!(
308 caps.type_definition_provider,
309 Some(
310 lsp_types::TypeDefinitionProviderCapability::Simple(true)
311 | lsp_types::TypeDefinitionProviderCapability::Options(_)
312 )
313 )
314 },
315 )
316 .await?;
317 let ctx = self.encoding_ctx(&server_id);
318 let lsp_position = ctx.to_lsp(&uri, line, character).await;
319
320 let params = GotoDefinitionParams {
321 text_document_position_params: TextDocumentPositionParams {
322 text_document: TextDocumentIdentifier { uri },
323 position: lsp_position,
324 },
325 work_done_progress_params: WorkDoneProgressParams::default(),
326 partial_result_params: PartialResultParams::default(),
327 };
328
329 let response: Option<lsp_types::GotoDefinitionResponse> = client
330 .request(
331 "textDocument/typeDefinition",
332 params,
333 client.request_timeout(),
334 )
335 .await?;
336
337 Ok(LocationsResult {
338 locations: goto_response_to_locations(response, &ctx).await,
339 })
340 }
341}
342
343#[cfg(test)]
344#[allow(clippy::unwrap_used, clippy::expect_used)]
345mod tests {
346 use super::*;
347
348 #[test]
349 fn test_extract_hover_contents_string() {
350 let marked_string = lsp_types::MarkedString::String("Test hover".to_string());
351 let contents = lsp_types::HoverContents::Scalar(marked_string);
352 let result = extract_hover_contents(contents);
353 assert_eq!(result, "Test hover");
354 }
355
356 #[test]
357 fn test_extract_hover_contents_language_string() {
358 let marked_string = lsp_types::MarkedString::LanguageString(lsp_types::LanguageString {
359 language: "rust".to_string(),
360 value: "fn main() {}".to_string(),
361 });
362 let contents = lsp_types::HoverContents::Scalar(marked_string);
363 let result = extract_hover_contents(contents);
364 assert_eq!(result, "```rust\nfn main() {}\n```");
365 }
366
367 #[test]
368 fn test_extract_hover_contents_markup() {
369 let markup = lsp_types::MarkupContent {
370 kind: lsp_types::MarkupKind::Markdown,
371 value: "# Documentation".to_string(),
372 };
373 let contents = lsp_types::HoverContents::Markup(markup);
374 let result = extract_hover_contents(contents);
375 assert_eq!(result, "# Documentation");
376 }
377}