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