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