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