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