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