diff options
Diffstat (limited to 'crates/ra_lsp_server/src/conv.rs')
-rw-r--r-- | crates/ra_lsp_server/src/conv.rs | 629 |
1 files changed, 0 insertions, 629 deletions
diff --git a/crates/ra_lsp_server/src/conv.rs b/crates/ra_lsp_server/src/conv.rs deleted file mode 100644 index 8af74b211..000000000 --- a/crates/ra_lsp_server/src/conv.rs +++ /dev/null | |||
@@ -1,629 +0,0 @@ | |||
1 | //! Convenience module responsible for translating between rust-analyzer's types and LSP types. | ||
2 | |||
3 | use lsp_types::{ | ||
4 | self, CreateFile, DiagnosticSeverity, DocumentChangeOperation, DocumentChanges, Documentation, | ||
5 | Location, LocationLink, MarkupContent, MarkupKind, Position, Range, RenameFile, ResourceOp, | ||
6 | SymbolKind, TextDocumentEdit, TextDocumentIdentifier, TextDocumentItem, | ||
7 | TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier, WorkspaceEdit, | ||
8 | }; | ||
9 | use ra_ide::{ | ||
10 | translate_offset_with_edit, CompletionItem, CompletionItemKind, FileId, FilePosition, | ||
11 | FileRange, FileSystemEdit, Fold, FoldKind, InsertTextFormat, LineCol, LineIndex, | ||
12 | NavigationTarget, RangeInfo, ReferenceAccess, Severity, SourceChange, SourceFileEdit, | ||
13 | }; | ||
14 | use ra_syntax::{SyntaxKind, TextRange, TextUnit}; | ||
15 | use ra_text_edit::{AtomTextEdit, TextEdit}; | ||
16 | use ra_vfs::LineEndings; | ||
17 | |||
18 | use crate::{req, world::WorldSnapshot, Result}; | ||
19 | |||
20 | pub trait Conv { | ||
21 | type Output; | ||
22 | fn conv(self) -> Self::Output; | ||
23 | } | ||
24 | |||
25 | pub trait ConvWith<CTX> { | ||
26 | type Output; | ||
27 | fn conv_with(self, ctx: CTX) -> Self::Output; | ||
28 | } | ||
29 | |||
30 | pub trait TryConvWith<CTX> { | ||
31 | type Output; | ||
32 | fn try_conv_with(self, ctx: CTX) -> Result<Self::Output>; | ||
33 | } | ||
34 | |||
35 | impl Conv for SyntaxKind { | ||
36 | type Output = SymbolKind; | ||
37 | |||
38 | fn conv(self) -> <Self as Conv>::Output { | ||
39 | match self { | ||
40 | SyntaxKind::FN_DEF => SymbolKind::Function, | ||
41 | SyntaxKind::STRUCT_DEF => SymbolKind::Struct, | ||
42 | SyntaxKind::ENUM_DEF => SymbolKind::Enum, | ||
43 | SyntaxKind::ENUM_VARIANT => SymbolKind::EnumMember, | ||
44 | SyntaxKind::TRAIT_DEF => SymbolKind::Interface, | ||
45 | SyntaxKind::MACRO_CALL => SymbolKind::Function, | ||
46 | SyntaxKind::MODULE => SymbolKind::Module, | ||
47 | SyntaxKind::TYPE_ALIAS_DEF => SymbolKind::TypeParameter, | ||
48 | SyntaxKind::RECORD_FIELD_DEF => SymbolKind::Field, | ||
49 | SyntaxKind::STATIC_DEF => SymbolKind::Constant, | ||
50 | SyntaxKind::CONST_DEF => SymbolKind::Constant, | ||
51 | SyntaxKind::IMPL_BLOCK => SymbolKind::Object, | ||
52 | _ => SymbolKind::Variable, | ||
53 | } | ||
54 | } | ||
55 | } | ||
56 | |||
57 | impl Conv for ReferenceAccess { | ||
58 | type Output = ::lsp_types::DocumentHighlightKind; | ||
59 | |||
60 | fn conv(self) -> Self::Output { | ||
61 | use lsp_types::DocumentHighlightKind; | ||
62 | match self { | ||
63 | ReferenceAccess::Read => DocumentHighlightKind::Read, | ||
64 | ReferenceAccess::Write => DocumentHighlightKind::Write, | ||
65 | } | ||
66 | } | ||
67 | } | ||
68 | |||
69 | impl Conv for CompletionItemKind { | ||
70 | type Output = ::lsp_types::CompletionItemKind; | ||
71 | |||
72 | fn conv(self) -> <Self as Conv>::Output { | ||
73 | use lsp_types::CompletionItemKind::*; | ||
74 | match self { | ||
75 | CompletionItemKind::Keyword => Keyword, | ||
76 | CompletionItemKind::Snippet => Snippet, | ||
77 | CompletionItemKind::Module => Module, | ||
78 | CompletionItemKind::Function => Function, | ||
79 | CompletionItemKind::Struct => Struct, | ||
80 | CompletionItemKind::Enum => Enum, | ||
81 | CompletionItemKind::EnumVariant => EnumMember, | ||
82 | CompletionItemKind::BuiltinType => Struct, | ||
83 | CompletionItemKind::Binding => Variable, | ||
84 | CompletionItemKind::Field => Field, | ||
85 | CompletionItemKind::Trait => Interface, | ||
86 | CompletionItemKind::TypeAlias => Struct, | ||
87 | CompletionItemKind::Const => Constant, | ||
88 | CompletionItemKind::Static => Value, | ||
89 | CompletionItemKind::Method => Method, | ||
90 | CompletionItemKind::TypeParam => TypeParameter, | ||
91 | CompletionItemKind::Macro => Method, | ||
92 | } | ||
93 | } | ||
94 | } | ||
95 | |||
96 | impl Conv for Severity { | ||
97 | type Output = DiagnosticSeverity; | ||
98 | fn conv(self) -> DiagnosticSeverity { | ||
99 | match self { | ||
100 | Severity::Error => DiagnosticSeverity::Error, | ||
101 | Severity::WeakWarning => DiagnosticSeverity::Hint, | ||
102 | } | ||
103 | } | ||
104 | } | ||
105 | |||
106 | impl ConvWith<(&LineIndex, LineEndings)> for CompletionItem { | ||
107 | type Output = ::lsp_types::CompletionItem; | ||
108 | |||
109 | fn conv_with(self, ctx: (&LineIndex, LineEndings)) -> ::lsp_types::CompletionItem { | ||
110 | let mut additional_text_edits = Vec::new(); | ||
111 | let mut text_edit = None; | ||
112 | // LSP does not allow arbitrary edits in completion, so we have to do a | ||
113 | // non-trivial mapping here. | ||
114 | for atom_edit in self.text_edit().as_atoms() { | ||
115 | if self.source_range().is_subrange(&atom_edit.delete) { | ||
116 | text_edit = Some(if atom_edit.delete == self.source_range() { | ||
117 | atom_edit.conv_with(ctx) | ||
118 | } else { | ||
119 | assert!(self.source_range().end() == atom_edit.delete.end()); | ||
120 | let range1 = | ||
121 | TextRange::from_to(atom_edit.delete.start(), self.source_range().start()); | ||
122 | let range2 = self.source_range(); | ||
123 | let edit1 = AtomTextEdit::replace(range1, String::new()); | ||
124 | let edit2 = AtomTextEdit::replace(range2, atom_edit.insert.clone()); | ||
125 | additional_text_edits.push(edit1.conv_with(ctx)); | ||
126 | edit2.conv_with(ctx) | ||
127 | }) | ||
128 | } else { | ||
129 | assert!(self.source_range().intersection(&atom_edit.delete).is_none()); | ||
130 | additional_text_edits.push(atom_edit.conv_with(ctx)); | ||
131 | } | ||
132 | } | ||
133 | let text_edit = text_edit.unwrap(); | ||
134 | |||
135 | let mut res = lsp_types::CompletionItem { | ||
136 | label: self.label().to_string(), | ||
137 | detail: self.detail().map(|it| it.to_string()), | ||
138 | filter_text: Some(self.lookup().to_string()), | ||
139 | kind: self.kind().map(|it| it.conv()), | ||
140 | text_edit: Some(text_edit), | ||
141 | additional_text_edits: Some(additional_text_edits), | ||
142 | documentation: self.documentation().map(|it| it.conv()), | ||
143 | deprecated: Some(self.deprecated()), | ||
144 | ..Default::default() | ||
145 | }; | ||
146 | |||
147 | if self.deprecated() { | ||
148 | res.tags = Some(vec![lsp_types::CompletionItemTag::Deprecated]) | ||
149 | } | ||
150 | |||
151 | res.insert_text_format = Some(match self.insert_text_format() { | ||
152 | InsertTextFormat::Snippet => lsp_types::InsertTextFormat::Snippet, | ||
153 | InsertTextFormat::PlainText => lsp_types::InsertTextFormat::PlainText, | ||
154 | }); | ||
155 | |||
156 | res | ||
157 | } | ||
158 | } | ||
159 | |||
160 | impl ConvWith<&LineIndex> for Position { | ||
161 | type Output = TextUnit; | ||
162 | |||
163 | fn conv_with(self, line_index: &LineIndex) -> TextUnit { | ||
164 | let line_col = LineCol { line: self.line as u32, col_utf16: self.character as u32 }; | ||
165 | line_index.offset(line_col) | ||
166 | } | ||
167 | } | ||
168 | |||
169 | impl ConvWith<&LineIndex> for TextUnit { | ||
170 | type Output = Position; | ||
171 | |||
172 | fn conv_with(self, line_index: &LineIndex) -> Position { | ||
173 | let line_col = line_index.line_col(self); | ||
174 | Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16)) | ||
175 | } | ||
176 | } | ||
177 | |||
178 | impl ConvWith<&LineIndex> for TextRange { | ||
179 | type Output = Range; | ||
180 | |||
181 | fn conv_with(self, line_index: &LineIndex) -> Range { | ||
182 | Range::new(self.start().conv_with(line_index), self.end().conv_with(line_index)) | ||
183 | } | ||
184 | } | ||
185 | |||
186 | impl ConvWith<&LineIndex> for Range { | ||
187 | type Output = TextRange; | ||
188 | |||
189 | fn conv_with(self, line_index: &LineIndex) -> TextRange { | ||
190 | TextRange::from_to(self.start.conv_with(line_index), self.end.conv_with(line_index)) | ||
191 | } | ||
192 | } | ||
193 | |||
194 | impl Conv for ra_ide::Documentation { | ||
195 | type Output = lsp_types::Documentation; | ||
196 | fn conv(self) -> Documentation { | ||
197 | Documentation::MarkupContent(MarkupContent { | ||
198 | kind: MarkupKind::Markdown, | ||
199 | value: crate::markdown::format_docs(self.as_str()), | ||
200 | }) | ||
201 | } | ||
202 | } | ||
203 | |||
204 | impl Conv for ra_ide::FunctionSignature { | ||
205 | type Output = lsp_types::SignatureInformation; | ||
206 | fn conv(self) -> Self::Output { | ||
207 | use lsp_types::{ParameterInformation, ParameterLabel, SignatureInformation}; | ||
208 | |||
209 | let label = self.to_string(); | ||
210 | |||
211 | let documentation = self.doc.map(|it| it.conv()); | ||
212 | |||
213 | let parameters: Vec<ParameterInformation> = self | ||
214 | .parameters | ||
215 | .into_iter() | ||
216 | .map(|param| ParameterInformation { | ||
217 | label: ParameterLabel::Simple(param), | ||
218 | documentation: None, | ||
219 | }) | ||
220 | .collect(); | ||
221 | |||
222 | SignatureInformation { label, documentation, parameters: Some(parameters) } | ||
223 | } | ||
224 | } | ||
225 | |||
226 | impl ConvWith<(&LineIndex, LineEndings)> for TextEdit { | ||
227 | type Output = Vec<lsp_types::TextEdit>; | ||
228 | |||
229 | fn conv_with(self, ctx: (&LineIndex, LineEndings)) -> Vec<lsp_types::TextEdit> { | ||
230 | self.as_atoms().iter().map_conv_with(ctx).collect() | ||
231 | } | ||
232 | } | ||
233 | |||
234 | impl ConvWith<(&LineIndex, LineEndings)> for &AtomTextEdit { | ||
235 | type Output = lsp_types::TextEdit; | ||
236 | |||
237 | fn conv_with( | ||
238 | self, | ||
239 | (line_index, line_endings): (&LineIndex, LineEndings), | ||
240 | ) -> lsp_types::TextEdit { | ||
241 | let mut new_text = self.insert.clone(); | ||
242 | if line_endings == LineEndings::Dos { | ||
243 | new_text = new_text.replace('\n', "\r\n"); | ||
244 | } | ||
245 | lsp_types::TextEdit { range: self.delete.conv_with(line_index), new_text } | ||
246 | } | ||
247 | } | ||
248 | |||
249 | pub(crate) struct FoldConvCtx<'a> { | ||
250 | pub(crate) text: &'a str, | ||
251 | pub(crate) line_index: &'a LineIndex, | ||
252 | pub(crate) line_folding_only: bool, | ||
253 | } | ||
254 | |||
255 | impl ConvWith<&FoldConvCtx<'_>> for Fold { | ||
256 | type Output = lsp_types::FoldingRange; | ||
257 | |||
258 | fn conv_with(self, ctx: &FoldConvCtx) -> lsp_types::FoldingRange { | ||
259 | let kind = match self.kind { | ||
260 | FoldKind::Comment => Some(lsp_types::FoldingRangeKind::Comment), | ||
261 | FoldKind::Imports => Some(lsp_types::FoldingRangeKind::Imports), | ||
262 | FoldKind::Mods => None, | ||
263 | FoldKind::Block => None, | ||
264 | }; | ||
265 | |||
266 | let range = self.range.conv_with(&ctx.line_index); | ||
267 | |||
268 | if ctx.line_folding_only { | ||
269 | // Clients with line_folding_only == true (such as VSCode) will fold the whole end line | ||
270 | // even if it contains text not in the folding range. To prevent that we exclude | ||
271 | // range.end.line from the folding region if there is more text after range.end | ||
272 | // on the same line. | ||
273 | let has_more_text_on_end_line = ctx.text | ||
274 | [TextRange::from_to(self.range.end(), TextUnit::of_str(ctx.text))] | ||
275 | .chars() | ||
276 | .take_while(|it| *it != '\n') | ||
277 | .any(|it| !it.is_whitespace()); | ||
278 | |||
279 | let end_line = if has_more_text_on_end_line { | ||
280 | range.end.line.saturating_sub(1) | ||
281 | } else { | ||
282 | range.end.line | ||
283 | }; | ||
284 | |||
285 | lsp_types::FoldingRange { | ||
286 | start_line: range.start.line, | ||
287 | start_character: None, | ||
288 | end_line, | ||
289 | end_character: None, | ||
290 | kind, | ||
291 | } | ||
292 | } else { | ||
293 | lsp_types::FoldingRange { | ||
294 | start_line: range.start.line, | ||
295 | start_character: Some(range.start.character), | ||
296 | end_line: range.end.line, | ||
297 | end_character: Some(range.end.character), | ||
298 | kind, | ||
299 | } | ||
300 | } | ||
301 | } | ||
302 | } | ||
303 | |||
304 | impl<T: ConvWith<CTX>, CTX> ConvWith<CTX> for Option<T> { | ||
305 | type Output = Option<T::Output>; | ||
306 | |||
307 | fn conv_with(self, ctx: CTX) -> Self::Output { | ||
308 | self.map(|x| ConvWith::conv_with(x, ctx)) | ||
309 | } | ||
310 | } | ||
311 | |||
312 | impl TryConvWith<&WorldSnapshot> for &Url { | ||
313 | type Output = FileId; | ||
314 | fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> { | ||
315 | world.uri_to_file_id(self) | ||
316 | } | ||
317 | } | ||
318 | |||
319 | impl TryConvWith<&WorldSnapshot> for FileId { | ||
320 | type Output = Url; | ||
321 | fn try_conv_with(self, world: &WorldSnapshot) -> Result<Url> { | ||
322 | world.file_id_to_uri(self) | ||
323 | } | ||
324 | } | ||
325 | |||
326 | impl TryConvWith<&WorldSnapshot> for &TextDocumentItem { | ||
327 | type Output = FileId; | ||
328 | fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> { | ||
329 | self.uri.try_conv_with(world) | ||
330 | } | ||
331 | } | ||
332 | |||
333 | impl TryConvWith<&WorldSnapshot> for &VersionedTextDocumentIdentifier { | ||
334 | type Output = FileId; | ||
335 | fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> { | ||
336 | self.uri.try_conv_with(world) | ||
337 | } | ||
338 | } | ||
339 | |||
340 | impl TryConvWith<&WorldSnapshot> for &TextDocumentIdentifier { | ||
341 | type Output = FileId; | ||
342 | fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> { | ||
343 | world.uri_to_file_id(&self.uri) | ||
344 | } | ||
345 | } | ||
346 | |||
347 | impl TryConvWith<&WorldSnapshot> for &TextDocumentPositionParams { | ||
348 | type Output = FilePosition; | ||
349 | fn try_conv_with(self, world: &WorldSnapshot) -> Result<FilePosition> { | ||
350 | let file_id = self.text_document.try_conv_with(world)?; | ||
351 | let line_index = world.analysis().file_line_index(file_id)?; | ||
352 | let offset = self.position.conv_with(&line_index); | ||
353 | Ok(FilePosition { file_id, offset }) | ||
354 | } | ||
355 | } | ||
356 | |||
357 | impl TryConvWith<&WorldSnapshot> for (&TextDocumentIdentifier, Range) { | ||
358 | type Output = FileRange; | ||
359 | fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileRange> { | ||
360 | let file_id = self.0.try_conv_with(world)?; | ||
361 | let line_index = world.analysis().file_line_index(file_id)?; | ||
362 | let range = self.1.conv_with(&line_index); | ||
363 | Ok(FileRange { file_id, range }) | ||
364 | } | ||
365 | } | ||
366 | |||
367 | impl<T: TryConvWith<CTX>, CTX: Copy> TryConvWith<CTX> for Vec<T> { | ||
368 | type Output = Vec<<T as TryConvWith<CTX>>::Output>; | ||
369 | fn try_conv_with(self, ctx: CTX) -> Result<Self::Output> { | ||
370 | let mut res = Vec::with_capacity(self.len()); | ||
371 | for item in self { | ||
372 | res.push(item.try_conv_with(ctx)?); | ||
373 | } | ||
374 | Ok(res) | ||
375 | } | ||
376 | } | ||
377 | |||
378 | impl TryConvWith<&WorldSnapshot> for SourceChange { | ||
379 | type Output = req::SourceChange; | ||
380 | fn try_conv_with(self, world: &WorldSnapshot) -> Result<req::SourceChange> { | ||
381 | let cursor_position = match self.cursor_position { | ||
382 | None => None, | ||
383 | Some(pos) => { | ||
384 | let line_index = world.analysis().file_line_index(pos.file_id)?; | ||
385 | let edit = self | ||
386 | .source_file_edits | ||
387 | .iter() | ||
388 | .find(|it| it.file_id == pos.file_id) | ||
389 | .map(|it| &it.edit); | ||
390 | let line_col = match edit { | ||
391 | Some(edit) => translate_offset_with_edit(&*line_index, pos.offset, edit), | ||
392 | None => line_index.line_col(pos.offset), | ||
393 | }; | ||
394 | let position = | ||
395 | Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16)); | ||
396 | Some(TextDocumentPositionParams { | ||
397 | text_document: TextDocumentIdentifier::new(pos.file_id.try_conv_with(world)?), | ||
398 | position, | ||
399 | }) | ||
400 | } | ||
401 | }; | ||
402 | let mut document_changes: Vec<DocumentChangeOperation> = Vec::new(); | ||
403 | for resource_op in self.file_system_edits.try_conv_with(world)? { | ||
404 | document_changes.push(DocumentChangeOperation::Op(resource_op)); | ||
405 | } | ||
406 | for text_document_edit in self.source_file_edits.try_conv_with(world)? { | ||
407 | document_changes.push(DocumentChangeOperation::Edit(text_document_edit)); | ||
408 | } | ||
409 | let workspace_edit = WorkspaceEdit { | ||
410 | changes: None, | ||
411 | document_changes: Some(DocumentChanges::Operations(document_changes)), | ||
412 | }; | ||
413 | Ok(req::SourceChange { label: self.label, workspace_edit, cursor_position }) | ||
414 | } | ||
415 | } | ||
416 | |||
417 | impl TryConvWith<&WorldSnapshot> for SourceFileEdit { | ||
418 | type Output = TextDocumentEdit; | ||
419 | fn try_conv_with(self, world: &WorldSnapshot) -> Result<TextDocumentEdit> { | ||
420 | let text_document = VersionedTextDocumentIdentifier { | ||
421 | uri: self.file_id.try_conv_with(world)?, | ||
422 | version: None, | ||
423 | }; | ||
424 | let line_index = world.analysis().file_line_index(self.file_id)?; | ||
425 | let line_endings = world.file_line_endings(self.file_id); | ||
426 | let edits = | ||
427 | self.edit.as_atoms().iter().map_conv_with((&line_index, line_endings)).collect(); | ||
428 | Ok(TextDocumentEdit { text_document, edits }) | ||
429 | } | ||
430 | } | ||
431 | |||
432 | impl TryConvWith<&WorldSnapshot> for FileSystemEdit { | ||
433 | type Output = ResourceOp; | ||
434 | fn try_conv_with(self, world: &WorldSnapshot) -> Result<ResourceOp> { | ||
435 | let res = match self { | ||
436 | FileSystemEdit::CreateFile { source_root, path } => { | ||
437 | let uri = world.path_to_uri(source_root, &path)?; | ||
438 | ResourceOp::Create(CreateFile { uri, options: None }) | ||
439 | } | ||
440 | FileSystemEdit::MoveFile { src, dst_source_root, dst_path } => { | ||
441 | let old_uri = world.file_id_to_uri(src)?; | ||
442 | let new_uri = world.path_to_uri(dst_source_root, &dst_path)?; | ||
443 | ResourceOp::Rename(RenameFile { old_uri, new_uri, options: None }) | ||
444 | } | ||
445 | }; | ||
446 | Ok(res) | ||
447 | } | ||
448 | } | ||
449 | |||
450 | impl TryConvWith<&WorldSnapshot> for &NavigationTarget { | ||
451 | type Output = Location; | ||
452 | fn try_conv_with(self, world: &WorldSnapshot) -> Result<Location> { | ||
453 | let line_index = world.analysis().file_line_index(self.file_id())?; | ||
454 | let range = self.range(); | ||
455 | to_location(self.file_id(), range, &world, &line_index) | ||
456 | } | ||
457 | } | ||
458 | |||
459 | impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<NavigationTarget>) { | ||
460 | type Output = LocationLink; | ||
461 | fn try_conv_with(self, world: &WorldSnapshot) -> Result<LocationLink> { | ||
462 | let (src_file_id, target) = self; | ||
463 | |||
464 | let target_uri = target.info.file_id().try_conv_with(world)?; | ||
465 | let src_line_index = world.analysis().file_line_index(src_file_id)?; | ||
466 | let tgt_line_index = world.analysis().file_line_index(target.info.file_id())?; | ||
467 | |||
468 | let target_range = target.info.full_range().conv_with(&tgt_line_index); | ||
469 | |||
470 | let target_selection_range = target | ||
471 | .info | ||
472 | .focus_range() | ||
473 | .map(|it| it.conv_with(&tgt_line_index)) | ||
474 | .unwrap_or(target_range); | ||
475 | |||
476 | let res = LocationLink { | ||
477 | origin_selection_range: Some(target.range.conv_with(&src_line_index)), | ||
478 | target_uri, | ||
479 | target_range, | ||
480 | target_selection_range, | ||
481 | }; | ||
482 | Ok(res) | ||
483 | } | ||
484 | } | ||
485 | |||
486 | impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<Vec<NavigationTarget>>) { | ||
487 | type Output = req::GotoDefinitionResponse; | ||
488 | fn try_conv_with(self, world: &WorldSnapshot) -> Result<req::GotoTypeDefinitionResponse> { | ||
489 | let (file_id, RangeInfo { range, info: navs }) = self; | ||
490 | let links = navs | ||
491 | .into_iter() | ||
492 | .map(|nav| (file_id, RangeInfo::new(range, nav))) | ||
493 | .try_conv_with_to_vec(world)?; | ||
494 | if world.options.supports_location_link { | ||
495 | Ok(links.into()) | ||
496 | } else { | ||
497 | let locations: Vec<Location> = links | ||
498 | .into_iter() | ||
499 | .map(|link| Location { uri: link.target_uri, range: link.target_selection_range }) | ||
500 | .collect(); | ||
501 | Ok(locations.into()) | ||
502 | } | ||
503 | } | ||
504 | } | ||
505 | |||
506 | pub fn to_call_hierarchy_item( | ||
507 | file_id: FileId, | ||
508 | range: TextRange, | ||
509 | world: &WorldSnapshot, | ||
510 | line_index: &LineIndex, | ||
511 | nav: NavigationTarget, | ||
512 | ) -> Result<lsp_types::CallHierarchyItem> { | ||
513 | Ok(lsp_types::CallHierarchyItem { | ||
514 | name: nav.name().to_string(), | ||
515 | kind: nav.kind().conv(), | ||
516 | tags: None, | ||
517 | detail: nav.description().map(|it| it.to_string()), | ||
518 | uri: file_id.try_conv_with(&world)?, | ||
519 | range: nav.range().conv_with(&line_index), | ||
520 | selection_range: range.conv_with(&line_index), | ||
521 | }) | ||
522 | } | ||
523 | |||
524 | pub fn to_location( | ||
525 | file_id: FileId, | ||
526 | range: TextRange, | ||
527 | world: &WorldSnapshot, | ||
528 | line_index: &LineIndex, | ||
529 | ) -> Result<Location> { | ||
530 | let url = file_id.try_conv_with(world)?; | ||
531 | let loc = Location::new(url, range.conv_with(line_index)); | ||
532 | Ok(loc) | ||
533 | } | ||
534 | |||
535 | pub trait MapConvWith<CTX>: Sized { | ||
536 | type Output; | ||
537 | |||
538 | fn map_conv_with(self, ctx: CTX) -> ConvWithIter<Self, CTX> { | ||
539 | ConvWithIter { iter: self, ctx } | ||
540 | } | ||
541 | } | ||
542 | |||
543 | impl<CTX, I> MapConvWith<CTX> for I | ||
544 | where | ||
545 | I: Iterator, | ||
546 | I::Item: ConvWith<CTX>, | ||
547 | { | ||
548 | type Output = <I::Item as ConvWith<CTX>>::Output; | ||
549 | } | ||
550 | |||
551 | pub struct ConvWithIter<I, CTX> { | ||
552 | iter: I, | ||
553 | ctx: CTX, | ||
554 | } | ||
555 | |||
556 | impl<I, CTX> Iterator for ConvWithIter<I, CTX> | ||
557 | where | ||
558 | I: Iterator, | ||
559 | I::Item: ConvWith<CTX>, | ||
560 | CTX: Copy, | ||
561 | { | ||
562 | type Item = <I::Item as ConvWith<CTX>>::Output; | ||
563 | |||
564 | fn next(&mut self) -> Option<Self::Item> { | ||
565 | self.iter.next().map(|item| item.conv_with(self.ctx)) | ||
566 | } | ||
567 | } | ||
568 | |||
569 | pub trait TryConvWithToVec<CTX>: Sized { | ||
570 | type Output; | ||
571 | |||
572 | fn try_conv_with_to_vec(self, ctx: CTX) -> Result<Vec<Self::Output>>; | ||
573 | } | ||
574 | |||
575 | impl<I, CTX> TryConvWithToVec<CTX> for I | ||
576 | where | ||
577 | I: Iterator, | ||
578 | I::Item: TryConvWith<CTX>, | ||
579 | CTX: Copy, | ||
580 | { | ||
581 | type Output = <I::Item as TryConvWith<CTX>>::Output; | ||
582 | |||
583 | fn try_conv_with_to_vec(self, ctx: CTX) -> Result<Vec<Self::Output>> { | ||
584 | self.map(|it| it.try_conv_with(ctx)).collect() | ||
585 | } | ||
586 | } | ||
587 | |||
588 | #[cfg(test)] | ||
589 | mod tests { | ||
590 | use super::*; | ||
591 | use test_utils::extract_ranges; | ||
592 | |||
593 | #[test] | ||
594 | fn conv_fold_line_folding_only_fixup() { | ||
595 | let text = r#"<fold>mod a; | ||
596 | mod b; | ||
597 | mod c;</fold> | ||
598 | |||
599 | fn main() <fold>{ | ||
600 | if cond <fold>{ | ||
601 | a::do_a(); | ||
602 | }</fold> else <fold>{ | ||
603 | b::do_b(); | ||
604 | }</fold> | ||
605 | }</fold>"#; | ||
606 | |||
607 | let (ranges, text) = extract_ranges(text, "fold"); | ||
608 | assert_eq!(ranges.len(), 4); | ||
609 | let folds = vec![ | ||
610 | Fold { range: ranges[0], kind: FoldKind::Mods }, | ||
611 | Fold { range: ranges[1], kind: FoldKind::Block }, | ||
612 | Fold { range: ranges[2], kind: FoldKind::Block }, | ||
613 | Fold { range: ranges[3], kind: FoldKind::Block }, | ||
614 | ]; | ||
615 | |||
616 | let line_index = LineIndex::new(&text); | ||
617 | let ctx = FoldConvCtx { text: &text, line_index: &line_index, line_folding_only: true }; | ||
618 | let converted: Vec<_> = folds.into_iter().map_conv_with(&ctx).collect(); | ||
619 | |||
620 | let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)]; | ||
621 | assert_eq!(converted.len(), expected_lines.len()); | ||
622 | for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) { | ||
623 | assert_eq!(folding_range.start_line, *start_line); | ||
624 | assert_eq!(folding_range.start_character, None); | ||
625 | assert_eq!(folding_range.end_line, *end_line); | ||
626 | assert_eq!(folding_range.end_character, None); | ||
627 | } | ||
628 | } | ||
629 | } | ||