1 //! Convenience module responsible for translating between rust-analyzer's 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,
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,
17 use ra_syntax::{SyntaxKind, TextRange, TextUnit};
18 use ra_text_edit::{AtomTextEdit, TextEdit};
19 use ra_vfs::LineEndings;
23 semantic_tokens::{self, ModifierSet, BUILTIN, CONSTANT, CONTROL, MUTABLE, UNSAFE},
27 use semantic_tokens::ATTRIBUTE;
31 fn conv(self) -> Self::Output;
34 pub trait ConvWith<CTX> {
36 fn conv_with(self, ctx: CTX) -> Self::Output;
39 pub trait TryConvWith<CTX> {
41 fn try_conv_with(self, ctx: CTX) -> Result<Self::Output>;
44 impl Conv for SyntaxKind {
45 type Output = SymbolKind;
47 fn conv(self) -> <Self as Conv>::Output {
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,
66 impl Conv for ReferenceAccess {
67 type Output = ::lsp_types::DocumentHighlightKind;
69 fn conv(self) -> Self::Output {
70 use lsp_types::DocumentHighlightKind;
72 ReferenceAccess::Read => DocumentHighlightKind::Read,
73 ReferenceAccess::Write => DocumentHighlightKind::Write,
78 impl Conv for CompletionItemKind {
79 type Output = ::lsp_types::CompletionItemKind;
81 fn conv(self) -> <Self as Conv>::Output {
82 use lsp_types::CompletionItemKind::*;
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,
105 impl Conv for Severity {
106 type Output = DiagnosticSeverity;
107 fn conv(self) -> DiagnosticSeverity {
109 Severity::Error => DiagnosticSeverity::Error,
110 Severity::WeakWarning => DiagnosticSeverity::Hint,
115 impl ConvWith<(&LineIndex, LineEndings)> for CompletionItem {
116 type Output = ::lsp_types::CompletionItem;
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)
128 assert!(self.source_range().end() == atom_edit.delete.end());
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));
138 assert!(self.source_range().intersection(&atom_edit.delete).is_none());
139 additional_text_edits.push(atom_edit.conv_with(ctx));
142 let text_edit = text_edit.unwrap();
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()),
156 if self.deprecated() {
157 res.tags = Some(vec![lsp_types::CompletionItemTag::Deprecated])
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,
169 impl ConvWith<&LineIndex> for Position {
170 type Output = TextUnit;
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)
178 impl ConvWith<&LineIndex> for TextUnit {
179 type Output = Position;
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))
187 impl ConvWith<&LineIndex> for TextRange {
190 fn conv_with(self, line_index: &LineIndex) -> Range {
191 Range::new(self.start().conv_with(line_index), self.end().conv_with(line_index))
195 impl ConvWith<&LineIndex> for Range {
196 type Output = TextRange;
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))
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()),
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};
218 let label = self.to_string();
220 let documentation = self.doc.map(|it| it.conv());
222 let parameters: Vec<ParameterInformation> = self
225 .map(|param| ParameterInformation {
226 label: ParameterLabel::Simple(param),
231 SignatureInformation { label, documentation, parameters: Some(parameters) }
235 impl ConvWith<(&LineIndex, LineEndings)> for TextEdit {
236 type Output = Vec<lsp_types::TextEdit>;
238 fn conv_with(self, ctx: (&LineIndex, LineEndings)) -> Vec<lsp_types::TextEdit> {
239 self.as_atoms().iter().map_conv_with(ctx).collect()
243 impl ConvWith<(&LineIndex, LineEndings)> for &AtomTextEdit {
244 type Output = lsp_types::TextEdit;
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");
254 lsp_types::TextEdit { range: self.delete.conv_with(line_index), new_text }
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,
264 impl ConvWith<&FoldConvCtx<'_>> for Fold {
265 type Output = lsp_types::FoldingRange;
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,
275 let range = self.range.conv_with(&ctx.line_index);
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
282 let has_more_text_on_end_line = ctx.text
283 [TextRange::from_to(self.range.end(), TextUnit::of_str(ctx.text))]
285 .take_while(|it| *it != '\n')
286 .any(|it| !it.is_whitespace());
288 let end_line = if has_more_text_on_end_line {
289 range.end.line.saturating_sub(1)
294 lsp_types::FoldingRange {
295 start_line: range.start.line,
296 start_character: None,
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),
313 impl Conv for Highlight {
314 type Output = (u32, u32);
316 fn conv(self) -> Self::Output {
317 let mut mods = ModifierSet::default();
318 let type_ = match self.tag {
321 | HighlightTag::Union
322 | HighlightTag::TypeAlias
323 | HighlightTag::Trait
324 | HighlightTag::BuiltinType => SemanticTokenType::TYPE,
325 HighlightTag::Field => SemanticTokenType::MEMBER,
326 HighlightTag::Function => SemanticTokenType::FUNCTION,
327 HighlightTag::Module => SemanticTokenType::NAMESPACE,
328 HighlightTag::Constant => {
329 mods |= SemanticTokenModifier::STATIC;
330 mods |= SemanticTokenModifier::READONLY;
333 HighlightTag::Macro => SemanticTokenType::MACRO,
334 HighlightTag::Variable => SemanticTokenType::VARIABLE,
335 HighlightTag::TypeSelf => {
336 mods |= SemanticTokenModifier::REFERENCE;
337 SemanticTokenType::TYPE
339 HighlightTag::TypeParam => SemanticTokenType::TYPE_PARAMETER,
340 HighlightTag::TypeLifetime => {
341 mods |= SemanticTokenModifier::REFERENCE;
342 SemanticTokenType::LABEL
344 HighlightTag::LiteralByte => SemanticTokenType::NUMBER,
345 HighlightTag::LiteralNumeric => SemanticTokenType::NUMBER,
346 HighlightTag::LiteralChar => SemanticTokenType::NUMBER,
347 HighlightTag::Comment => SemanticTokenType::COMMENT,
348 HighlightTag::LiteralString => SemanticTokenType::STRING,
349 HighlightTag::Attribute => ATTRIBUTE,
350 HighlightTag::Keyword => SemanticTokenType::KEYWORD,
353 for modifier in self.modifiers.iter() {
354 let modifier = match modifier {
355 HighlightModifier::Mutable => MUTABLE,
356 HighlightModifier::Unsafe => UNSAFE,
357 HighlightModifier::Control => CONTROL,
362 (semantic_tokens::type_index(type_), mods.0)
366 impl<T: ConvWith<CTX>, CTX> ConvWith<CTX> for Option<T> {
367 type Output = Option<T::Output>;
369 fn conv_with(self, ctx: CTX) -> Self::Output {
370 self.map(|x| ConvWith::conv_with(x, ctx))
374 impl TryConvWith<&WorldSnapshot> for &Url {
375 type Output = FileId;
376 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
377 world.uri_to_file_id(self)
381 impl TryConvWith<&WorldSnapshot> for FileId {
383 fn try_conv_with(self, world: &WorldSnapshot) -> Result<Url> {
384 world.file_id_to_uri(self)
388 impl TryConvWith<&WorldSnapshot> for &TextDocumentItem {
389 type Output = FileId;
390 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
391 self.uri.try_conv_with(world)
395 impl TryConvWith<&WorldSnapshot> for &VersionedTextDocumentIdentifier {
396 type Output = FileId;
397 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
398 self.uri.try_conv_with(world)
402 impl TryConvWith<&WorldSnapshot> for &TextDocumentIdentifier {
403 type Output = FileId;
404 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
405 world.uri_to_file_id(&self.uri)
409 impl TryConvWith<&WorldSnapshot> for &TextDocumentPositionParams {
410 type Output = FilePosition;
411 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FilePosition> {
412 let file_id = self.text_document.try_conv_with(world)?;
413 let line_index = world.analysis().file_line_index(file_id)?;
414 let offset = self.position.conv_with(&line_index);
415 Ok(FilePosition { file_id, offset })
419 impl TryConvWith<&WorldSnapshot> for (&TextDocumentIdentifier, Range) {
420 type Output = FileRange;
421 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileRange> {
422 let file_id = self.0.try_conv_with(world)?;
423 let line_index = world.analysis().file_line_index(file_id)?;
424 let range = self.1.conv_with(&line_index);
425 Ok(FileRange { file_id, range })
429 impl<T: TryConvWith<CTX>, CTX: Copy> TryConvWith<CTX> for Vec<T> {
430 type Output = Vec<<T as TryConvWith<CTX>>::Output>;
431 fn try_conv_with(self, ctx: CTX) -> Result<Self::Output> {
432 let mut res = Vec::with_capacity(self.len());
434 res.push(item.try_conv_with(ctx)?);
440 impl TryConvWith<&WorldSnapshot> for SourceChange {
441 type Output = req::SourceChange;
442 fn try_conv_with(self, world: &WorldSnapshot) -> Result<req::SourceChange> {
443 let cursor_position = match self.cursor_position {
446 let line_index = world.analysis().file_line_index(pos.file_id)?;
450 .find(|it| it.file_id == pos.file_id)
452 let line_col = match edit {
453 Some(edit) => translate_offset_with_edit(&*line_index, pos.offset, edit),
454 None => line_index.line_col(pos.offset),
457 Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16));
458 Some(TextDocumentPositionParams {
459 text_document: TextDocumentIdentifier::new(pos.file_id.try_conv_with(world)?),
464 let mut document_changes: Vec<DocumentChangeOperation> = Vec::new();
465 for resource_op in self.file_system_edits.try_conv_with(world)? {
466 document_changes.push(DocumentChangeOperation::Op(resource_op));
468 for text_document_edit in self.source_file_edits.try_conv_with(world)? {
469 document_changes.push(DocumentChangeOperation::Edit(text_document_edit));
471 let workspace_edit = WorkspaceEdit {
473 document_changes: Some(DocumentChanges::Operations(document_changes)),
475 Ok(req::SourceChange { label: self.label, workspace_edit, cursor_position })
479 impl TryConvWith<&WorldSnapshot> for SourceFileEdit {
480 type Output = TextDocumentEdit;
481 fn try_conv_with(self, world: &WorldSnapshot) -> Result<TextDocumentEdit> {
482 let text_document = VersionedTextDocumentIdentifier {
483 uri: self.file_id.try_conv_with(world)?,
486 let line_index = world.analysis().file_line_index(self.file_id)?;
487 let line_endings = world.file_line_endings(self.file_id);
489 self.edit.as_atoms().iter().map_conv_with((&line_index, line_endings)).collect();
490 Ok(TextDocumentEdit { text_document, edits })
494 impl TryConvWith<&WorldSnapshot> for FileSystemEdit {
495 type Output = ResourceOp;
496 fn try_conv_with(self, world: &WorldSnapshot) -> Result<ResourceOp> {
497 let res = match self {
498 FileSystemEdit::CreateFile { source_root, path } => {
499 let uri = world.path_to_uri(source_root, &path)?;
500 ResourceOp::Create(CreateFile { uri, options: None })
502 FileSystemEdit::MoveFile { src, dst_source_root, dst_path } => {
503 let old_uri = world.file_id_to_uri(src)?;
504 let new_uri = world.path_to_uri(dst_source_root, &dst_path)?;
505 ResourceOp::Rename(RenameFile { old_uri, new_uri, options: None })
512 impl TryConvWith<&WorldSnapshot> for &NavigationTarget {
513 type Output = Location;
514 fn try_conv_with(self, world: &WorldSnapshot) -> Result<Location> {
515 let line_index = world.analysis().file_line_index(self.file_id())?;
516 let range = self.range();
517 to_location(self.file_id(), range, &world, &line_index)
521 impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<NavigationTarget>) {
522 type Output = LocationLink;
523 fn try_conv_with(self, world: &WorldSnapshot) -> Result<LocationLink> {
524 let (src_file_id, target) = self;
526 let target_uri = target.info.file_id().try_conv_with(world)?;
527 let src_line_index = world.analysis().file_line_index(src_file_id)?;
528 let tgt_line_index = world.analysis().file_line_index(target.info.file_id())?;
530 let target_range = target.info.full_range().conv_with(&tgt_line_index);
532 let target_selection_range = target
535 .map(|it| it.conv_with(&tgt_line_index))
536 .unwrap_or(target_range);
538 let res = LocationLink {
539 origin_selection_range: Some(target.range.conv_with(&src_line_index)),
542 target_selection_range,
548 impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<Vec<NavigationTarget>>) {
549 type Output = req::GotoDefinitionResponse;
550 fn try_conv_with(self, world: &WorldSnapshot) -> Result<req::GotoTypeDefinitionResponse> {
551 let (file_id, RangeInfo { range, info: navs }) = self;
554 .map(|nav| (file_id, RangeInfo::new(range, nav)))
555 .try_conv_with_to_vec(world)?;
556 if world.options.supports_location_link {
559 let locations: Vec<Location> = links
561 .map(|link| Location { uri: link.target_uri, range: link.target_selection_range })
568 pub fn to_call_hierarchy_item(
571 world: &WorldSnapshot,
572 line_index: &LineIndex,
573 nav: NavigationTarget,
574 ) -> Result<lsp_types::CallHierarchyItem> {
575 Ok(lsp_types::CallHierarchyItem {
576 name: nav.name().to_string(),
577 kind: nav.kind().conv(),
579 detail: nav.description().map(|it| it.to_string()),
580 uri: file_id.try_conv_with(&world)?,
581 range: nav.range().conv_with(&line_index),
582 selection_range: range.conv_with(&line_index),
589 world: &WorldSnapshot,
590 line_index: &LineIndex,
591 ) -> Result<Location> {
592 let url = file_id.try_conv_with(world)?;
593 let loc = Location::new(url, range.conv_with(line_index));
597 pub trait MapConvWith<CTX>: Sized {
600 fn map_conv_with(self, ctx: CTX) -> ConvWithIter<Self, CTX> {
601 ConvWithIter { iter: self, ctx }
605 impl<CTX, I> MapConvWith<CTX> for I
608 I::Item: ConvWith<CTX>,
610 type Output = <I::Item as ConvWith<CTX>>::Output;
613 pub struct ConvWithIter<I, CTX> {
618 impl<I, CTX> Iterator for ConvWithIter<I, CTX>
621 I::Item: ConvWith<CTX>,
624 type Item = <I::Item as ConvWith<CTX>>::Output;
626 fn next(&mut self) -> Option<Self::Item> {
627 self.iter.next().map(|item| item.conv_with(self.ctx))
631 pub trait TryConvWithToVec<CTX>: Sized {
634 fn try_conv_with_to_vec(self, ctx: CTX) -> Result<Vec<Self::Output>>;
637 impl<I, CTX> TryConvWithToVec<CTX> for I
640 I::Item: TryConvWith<CTX>,
643 type Output = <I::Item as TryConvWith<CTX>>::Output;
645 fn try_conv_with_to_vec(self, ctx: CTX) -> Result<Vec<Self::Output>> {
646 self.map(|it| it.try_conv_with(ctx)).collect()
653 use test_utils::extract_ranges;
656 fn conv_fold_line_folding_only_fixup() {
657 let text = r#"<fold>mod a;
664 }</fold> else <fold>{
669 let (ranges, text) = extract_ranges(text, "fold");
670 assert_eq!(ranges.len(), 4);
672 Fold { range: ranges[0], kind: FoldKind::Mods },
673 Fold { range: ranges[1], kind: FoldKind::Block },
674 Fold { range: ranges[2], kind: FoldKind::Block },
675 Fold { range: ranges[3], kind: FoldKind::Block },
678 let line_index = LineIndex::new(&text);
679 let ctx = FoldConvCtx { text: &text, line_index: &line_index, line_folding_only: true };
680 let converted: Vec<_> = folds.into_iter().map_conv_with(&ctx).collect();
682 let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)];
683 assert_eq!(converted.len(), expected_lines.len());
684 for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) {
685 assert_eq!(folding_range.start_line, *start_line);
686 assert_eq!(folding_range.start_character, None);
687 assert_eq!(folding_range.end_line, *end_line);
688 assert_eq!(folding_range.end_character, None);