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, HighlightTag, InsertTextFormat, LineCol, LineIndex,
14 NavigationTarget, RangeInfo, ReferenceAccess, Severity, SourceChange, SourceFileEdit,
16 use ra_syntax::{SyntaxKind, TextRange, TextUnit};
17 use ra_text_edit::{AtomTextEdit, TextEdit};
18 use ra_vfs::LineEndings;
20 use crate::{req, semantic_tokens, world::WorldSnapshot, Result};
24 fn conv(self) -> Self::Output;
27 pub trait ConvWith<CTX> {
29 fn conv_with(self, ctx: CTX) -> Self::Output;
32 pub trait TryConvWith<CTX> {
34 fn try_conv_with(self, ctx: CTX) -> Result<Self::Output>;
37 impl Conv for SyntaxKind {
38 type Output = SymbolKind;
40 fn conv(self) -> <Self as Conv>::Output {
42 SyntaxKind::FN_DEF => SymbolKind::Function,
43 SyntaxKind::STRUCT_DEF => SymbolKind::Struct,
44 SyntaxKind::ENUM_DEF => SymbolKind::Enum,
45 SyntaxKind::ENUM_VARIANT => SymbolKind::EnumMember,
46 SyntaxKind::TRAIT_DEF => SymbolKind::Interface,
47 SyntaxKind::MACRO_CALL => SymbolKind::Function,
48 SyntaxKind::MODULE => SymbolKind::Module,
49 SyntaxKind::TYPE_ALIAS_DEF => SymbolKind::TypeParameter,
50 SyntaxKind::RECORD_FIELD_DEF => SymbolKind::Field,
51 SyntaxKind::STATIC_DEF => SymbolKind::Constant,
52 SyntaxKind::CONST_DEF => SymbolKind::Constant,
53 SyntaxKind::IMPL_BLOCK => SymbolKind::Object,
54 _ => SymbolKind::Variable,
59 impl Conv for ReferenceAccess {
60 type Output = ::lsp_types::DocumentHighlightKind;
62 fn conv(self) -> Self::Output {
63 use lsp_types::DocumentHighlightKind;
65 ReferenceAccess::Read => DocumentHighlightKind::Read,
66 ReferenceAccess::Write => DocumentHighlightKind::Write,
71 impl Conv for CompletionItemKind {
72 type Output = ::lsp_types::CompletionItemKind;
74 fn conv(self) -> <Self as Conv>::Output {
75 use lsp_types::CompletionItemKind::*;
77 CompletionItemKind::Keyword => Keyword,
78 CompletionItemKind::Snippet => Snippet,
79 CompletionItemKind::Module => Module,
80 CompletionItemKind::Function => Function,
81 CompletionItemKind::Struct => Struct,
82 CompletionItemKind::Enum => Enum,
83 CompletionItemKind::EnumVariant => EnumMember,
84 CompletionItemKind::BuiltinType => Struct,
85 CompletionItemKind::Binding => Variable,
86 CompletionItemKind::Field => Field,
87 CompletionItemKind::Trait => Interface,
88 CompletionItemKind::TypeAlias => Struct,
89 CompletionItemKind::Const => Constant,
90 CompletionItemKind::Static => Value,
91 CompletionItemKind::Method => Method,
92 CompletionItemKind::TypeParam => TypeParameter,
93 CompletionItemKind::Macro => Method,
98 impl Conv for Severity {
99 type Output = DiagnosticSeverity;
100 fn conv(self) -> DiagnosticSeverity {
102 Severity::Error => DiagnosticSeverity::Error,
103 Severity::WeakWarning => DiagnosticSeverity::Hint,
108 impl ConvWith<(&LineIndex, LineEndings)> for CompletionItem {
109 type Output = ::lsp_types::CompletionItem;
111 fn conv_with(self, ctx: (&LineIndex, LineEndings)) -> ::lsp_types::CompletionItem {
112 let mut additional_text_edits = Vec::new();
113 let mut text_edit = None;
114 // LSP does not allow arbitrary edits in completion, so we have to do a
115 // non-trivial mapping here.
116 for atom_edit in self.text_edit().as_atoms() {
117 if self.source_range().is_subrange(&atom_edit.delete) {
118 text_edit = Some(if atom_edit.delete == self.source_range() {
119 atom_edit.conv_with(ctx)
121 assert!(self.source_range().end() == atom_edit.delete.end());
123 TextRange::from_to(atom_edit.delete.start(), self.source_range().start());
124 let range2 = self.source_range();
125 let edit1 = AtomTextEdit::replace(range1, String::new());
126 let edit2 = AtomTextEdit::replace(range2, atom_edit.insert.clone());
127 additional_text_edits.push(edit1.conv_with(ctx));
131 assert!(self.source_range().intersection(&atom_edit.delete).is_none());
132 additional_text_edits.push(atom_edit.conv_with(ctx));
135 let text_edit = text_edit.unwrap();
137 let mut res = lsp_types::CompletionItem {
138 label: self.label().to_string(),
139 detail: self.detail().map(|it| it.to_string()),
140 filter_text: Some(self.lookup().to_string()),
141 kind: self.kind().map(|it| it.conv()),
142 text_edit: Some(text_edit),
143 additional_text_edits: Some(additional_text_edits),
144 documentation: self.documentation().map(|it| it.conv()),
145 deprecated: Some(self.deprecated()),
149 if self.deprecated() {
150 res.tags = Some(vec![lsp_types::CompletionItemTag::Deprecated])
153 res.insert_text_format = Some(match self.insert_text_format() {
154 InsertTextFormat::Snippet => lsp_types::InsertTextFormat::Snippet,
155 InsertTextFormat::PlainText => lsp_types::InsertTextFormat::PlainText,
162 impl ConvWith<&LineIndex> for Position {
163 type Output = TextUnit;
165 fn conv_with(self, line_index: &LineIndex) -> TextUnit {
166 let line_col = LineCol { line: self.line as u32, col_utf16: self.character as u32 };
167 line_index.offset(line_col)
171 impl ConvWith<&LineIndex> for TextUnit {
172 type Output = Position;
174 fn conv_with(self, line_index: &LineIndex) -> Position {
175 let line_col = line_index.line_col(self);
176 Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16))
180 impl ConvWith<&LineIndex> for TextRange {
183 fn conv_with(self, line_index: &LineIndex) -> Range {
184 Range::new(self.start().conv_with(line_index), self.end().conv_with(line_index))
188 impl ConvWith<&LineIndex> for Range {
189 type Output = TextRange;
191 fn conv_with(self, line_index: &LineIndex) -> TextRange {
192 TextRange::from_to(self.start.conv_with(line_index), self.end.conv_with(line_index))
196 impl Conv for ra_ide::Documentation {
197 type Output = lsp_types::Documentation;
198 fn conv(self) -> Documentation {
199 Documentation::MarkupContent(MarkupContent {
200 kind: MarkupKind::Markdown,
201 value: crate::markdown::format_docs(self.as_str()),
206 impl Conv for ra_ide::FunctionSignature {
207 type Output = lsp_types::SignatureInformation;
208 fn conv(self) -> Self::Output {
209 use lsp_types::{ParameterInformation, ParameterLabel, SignatureInformation};
211 let label = self.to_string();
213 let documentation = self.doc.map(|it| it.conv());
215 let parameters: Vec<ParameterInformation> = self
218 .map(|param| ParameterInformation {
219 label: ParameterLabel::Simple(param),
224 SignatureInformation { label, documentation, parameters: Some(parameters) }
228 impl ConvWith<(&LineIndex, LineEndings)> for TextEdit {
229 type Output = Vec<lsp_types::TextEdit>;
231 fn conv_with(self, ctx: (&LineIndex, LineEndings)) -> Vec<lsp_types::TextEdit> {
232 self.as_atoms().iter().map_conv_with(ctx).collect()
236 impl ConvWith<(&LineIndex, LineEndings)> for &AtomTextEdit {
237 type Output = lsp_types::TextEdit;
241 (line_index, line_endings): (&LineIndex, LineEndings),
242 ) -> lsp_types::TextEdit {
243 let mut new_text = self.insert.clone();
244 if line_endings == LineEndings::Dos {
245 new_text = new_text.replace('\n', "\r\n");
247 lsp_types::TextEdit { range: self.delete.conv_with(line_index), new_text }
251 pub(crate) struct FoldConvCtx<'a> {
252 pub(crate) text: &'a str,
253 pub(crate) line_index: &'a LineIndex,
254 pub(crate) line_folding_only: bool,
257 impl ConvWith<&FoldConvCtx<'_>> for Fold {
258 type Output = lsp_types::FoldingRange;
260 fn conv_with(self, ctx: &FoldConvCtx) -> lsp_types::FoldingRange {
261 let kind = match self.kind {
262 FoldKind::Comment => Some(lsp_types::FoldingRangeKind::Comment),
263 FoldKind::Imports => Some(lsp_types::FoldingRangeKind::Imports),
264 FoldKind::Mods => None,
265 FoldKind::Block => None,
268 let range = self.range.conv_with(&ctx.line_index);
270 if ctx.line_folding_only {
271 // Clients with line_folding_only == true (such as VSCode) will fold the whole end line
272 // even if it contains text not in the folding range. To prevent that we exclude
273 // range.end.line from the folding region if there is more text after range.end
275 let has_more_text_on_end_line = ctx.text
276 [TextRange::from_to(self.range.end(), TextUnit::of_str(ctx.text))]
278 .take_while(|it| *it != '\n')
279 .any(|it| !it.is_whitespace());
281 let end_line = if has_more_text_on_end_line {
282 range.end.line.saturating_sub(1)
287 lsp_types::FoldingRange {
288 start_line: range.start.line,
289 start_character: None,
295 lsp_types::FoldingRange {
296 start_line: range.start.line,
297 start_character: Some(range.start.character),
298 end_line: range.end.line,
299 end_character: Some(range.end.character),
306 impl Conv for HighlightTag {
307 type Output = (SemanticTokenType, Vec<SemanticTokenModifier>);
309 fn conv(self) -> (SemanticTokenType, Vec<SemanticTokenModifier>) {
310 let token_type: SemanticTokenType = match self {
311 HighlightTag::FIELD => SemanticTokenType::MEMBER,
312 HighlightTag::FUNCTION => SemanticTokenType::FUNCTION,
313 HighlightTag::MODULE => SemanticTokenType::NAMESPACE,
314 HighlightTag::CONSTANT => {
316 SemanticTokenType::VARIABLE,
317 vec![SemanticTokenModifier::STATIC, SemanticTokenModifier::READONLY],
320 HighlightTag::MACRO => SemanticTokenType::MACRO,
322 HighlightTag::VARIABLE => {
323 return (SemanticTokenType::VARIABLE, vec![SemanticTokenModifier::READONLY])
325 HighlightTag::VARIABLE_MUT => SemanticTokenType::VARIABLE,
327 HighlightTag::TYPE => SemanticTokenType::TYPE,
328 HighlightTag::TYPE_BUILTIN => SemanticTokenType::TYPE,
329 HighlightTag::TYPE_SELF => {
330 return (SemanticTokenType::TYPE, vec![SemanticTokenModifier::REFERENCE])
332 HighlightTag::TYPE_PARAM => SemanticTokenType::TYPE_PARAMETER,
333 HighlightTag::TYPE_LIFETIME => {
334 return (SemanticTokenType::LABEL, vec![SemanticTokenModifier::REFERENCE])
337 HighlightTag::LITERAL_BYTE => SemanticTokenType::NUMBER,
338 HighlightTag::LITERAL_NUMERIC => SemanticTokenType::NUMBER,
339 HighlightTag::LITERAL_CHAR => SemanticTokenType::NUMBER,
341 HighlightTag::LITERAL_COMMENT => {
342 return (SemanticTokenType::COMMENT, vec![SemanticTokenModifier::DOCUMENTATION])
345 HighlightTag::LITERAL_STRING => SemanticTokenType::STRING,
346 HighlightTag::LITERAL_ATTRIBUTE => "attribute".into(),
348 HighlightTag::KEYWORD => SemanticTokenType::KEYWORD,
349 HighlightTag::KEYWORD_UNSAFE => SemanticTokenType::KEYWORD,
350 HighlightTag::KEYWORD_CONTROL => SemanticTokenType::KEYWORD,
351 unknown => panic!("Unknown semantic token: {}", unknown),
358 impl Conv for (SemanticTokenType, Vec<SemanticTokenModifier>) {
359 type Output = (u32, u32);
361 fn conv(self) -> Self::Output {
363 semantic_tokens::supported_token_types().iter().position(|it| *it == self.0).unwrap();
364 let mut token_modifier_bitset = 0;
365 for modifier in self.1.iter() {
366 let modifier_index = semantic_tokens::supported_token_modifiers()
368 .position(|it| it == modifier)
370 token_modifier_bitset |= 1 << modifier_index;
373 (token_index as u32, token_modifier_bitset as u32)
377 impl<T: ConvWith<CTX>, CTX> ConvWith<CTX> for Option<T> {
378 type Output = Option<T::Output>;
380 fn conv_with(self, ctx: CTX) -> Self::Output {
381 self.map(|x| ConvWith::conv_with(x, ctx))
385 impl TryConvWith<&WorldSnapshot> for &Url {
386 type Output = FileId;
387 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
388 world.uri_to_file_id(self)
392 impl TryConvWith<&WorldSnapshot> for FileId {
394 fn try_conv_with(self, world: &WorldSnapshot) -> Result<Url> {
395 world.file_id_to_uri(self)
399 impl TryConvWith<&WorldSnapshot> for &TextDocumentItem {
400 type Output = FileId;
401 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
402 self.uri.try_conv_with(world)
406 impl TryConvWith<&WorldSnapshot> for &VersionedTextDocumentIdentifier {
407 type Output = FileId;
408 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
409 self.uri.try_conv_with(world)
413 impl TryConvWith<&WorldSnapshot> for &TextDocumentIdentifier {
414 type Output = FileId;
415 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
416 world.uri_to_file_id(&self.uri)
420 impl TryConvWith<&WorldSnapshot> for &TextDocumentPositionParams {
421 type Output = FilePosition;
422 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FilePosition> {
423 let file_id = self.text_document.try_conv_with(world)?;
424 let line_index = world.analysis().file_line_index(file_id)?;
425 let offset = self.position.conv_with(&line_index);
426 Ok(FilePosition { file_id, offset })
430 impl TryConvWith<&WorldSnapshot> for (&TextDocumentIdentifier, Range) {
431 type Output = FileRange;
432 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileRange> {
433 let file_id = self.0.try_conv_with(world)?;
434 let line_index = world.analysis().file_line_index(file_id)?;
435 let range = self.1.conv_with(&line_index);
436 Ok(FileRange { file_id, range })
440 impl<T: TryConvWith<CTX>, CTX: Copy> TryConvWith<CTX> for Vec<T> {
441 type Output = Vec<<T as TryConvWith<CTX>>::Output>;
442 fn try_conv_with(self, ctx: CTX) -> Result<Self::Output> {
443 let mut res = Vec::with_capacity(self.len());
445 res.push(item.try_conv_with(ctx)?);
451 impl TryConvWith<&WorldSnapshot> for SourceChange {
452 type Output = req::SourceChange;
453 fn try_conv_with(self, world: &WorldSnapshot) -> Result<req::SourceChange> {
454 let cursor_position = match self.cursor_position {
457 let line_index = world.analysis().file_line_index(pos.file_id)?;
461 .find(|it| it.file_id == pos.file_id)
463 let line_col = match edit {
464 Some(edit) => translate_offset_with_edit(&*line_index, pos.offset, edit),
465 None => line_index.line_col(pos.offset),
468 Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16));
469 Some(TextDocumentPositionParams {
470 text_document: TextDocumentIdentifier::new(pos.file_id.try_conv_with(world)?),
475 let mut document_changes: Vec<DocumentChangeOperation> = Vec::new();
476 for resource_op in self.file_system_edits.try_conv_with(world)? {
477 document_changes.push(DocumentChangeOperation::Op(resource_op));
479 for text_document_edit in self.source_file_edits.try_conv_with(world)? {
480 document_changes.push(DocumentChangeOperation::Edit(text_document_edit));
482 let workspace_edit = WorkspaceEdit {
484 document_changes: Some(DocumentChanges::Operations(document_changes)),
486 Ok(req::SourceChange { label: self.label, workspace_edit, cursor_position })
490 impl TryConvWith<&WorldSnapshot> for SourceFileEdit {
491 type Output = TextDocumentEdit;
492 fn try_conv_with(self, world: &WorldSnapshot) -> Result<TextDocumentEdit> {
493 let text_document = VersionedTextDocumentIdentifier {
494 uri: self.file_id.try_conv_with(world)?,
497 let line_index = world.analysis().file_line_index(self.file_id)?;
498 let line_endings = world.file_line_endings(self.file_id);
500 self.edit.as_atoms().iter().map_conv_with((&line_index, line_endings)).collect();
501 Ok(TextDocumentEdit { text_document, edits })
505 impl TryConvWith<&WorldSnapshot> for FileSystemEdit {
506 type Output = ResourceOp;
507 fn try_conv_with(self, world: &WorldSnapshot) -> Result<ResourceOp> {
508 let res = match self {
509 FileSystemEdit::CreateFile { source_root, path } => {
510 let uri = world.path_to_uri(source_root, &path)?;
511 ResourceOp::Create(CreateFile { uri, options: None })
513 FileSystemEdit::MoveFile { src, dst_source_root, dst_path } => {
514 let old_uri = world.file_id_to_uri(src)?;
515 let new_uri = world.path_to_uri(dst_source_root, &dst_path)?;
516 ResourceOp::Rename(RenameFile { old_uri, new_uri, options: None })
523 impl TryConvWith<&WorldSnapshot> for &NavigationTarget {
524 type Output = Location;
525 fn try_conv_with(self, world: &WorldSnapshot) -> Result<Location> {
526 let line_index = world.analysis().file_line_index(self.file_id())?;
527 let range = self.range();
528 to_location(self.file_id(), range, &world, &line_index)
532 impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<NavigationTarget>) {
533 type Output = LocationLink;
534 fn try_conv_with(self, world: &WorldSnapshot) -> Result<LocationLink> {
535 let (src_file_id, target) = self;
537 let target_uri = target.info.file_id().try_conv_with(world)?;
538 let src_line_index = world.analysis().file_line_index(src_file_id)?;
539 let tgt_line_index = world.analysis().file_line_index(target.info.file_id())?;
541 let target_range = target.info.full_range().conv_with(&tgt_line_index);
543 let target_selection_range = target
546 .map(|it| it.conv_with(&tgt_line_index))
547 .unwrap_or(target_range);
549 let res = LocationLink {
550 origin_selection_range: Some(target.range.conv_with(&src_line_index)),
553 target_selection_range,
559 impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<Vec<NavigationTarget>>) {
560 type Output = req::GotoDefinitionResponse;
561 fn try_conv_with(self, world: &WorldSnapshot) -> Result<req::GotoTypeDefinitionResponse> {
562 let (file_id, RangeInfo { range, info: navs }) = self;
565 .map(|nav| (file_id, RangeInfo::new(range, nav)))
566 .try_conv_with_to_vec(world)?;
567 if world.options.supports_location_link {
570 let locations: Vec<Location> = links
572 .map(|link| Location { uri: link.target_uri, range: link.target_selection_range })
579 pub fn to_call_hierarchy_item(
582 world: &WorldSnapshot,
583 line_index: &LineIndex,
584 nav: NavigationTarget,
585 ) -> Result<lsp_types::CallHierarchyItem> {
586 Ok(lsp_types::CallHierarchyItem {
587 name: nav.name().to_string(),
588 kind: nav.kind().conv(),
590 detail: nav.description().map(|it| it.to_string()),
591 uri: file_id.try_conv_with(&world)?,
592 range: nav.range().conv_with(&line_index),
593 selection_range: range.conv_with(&line_index),
600 world: &WorldSnapshot,
601 line_index: &LineIndex,
602 ) -> Result<Location> {
603 let url = file_id.try_conv_with(world)?;
604 let loc = Location::new(url, range.conv_with(line_index));
608 pub trait MapConvWith<CTX>: Sized {
611 fn map_conv_with(self, ctx: CTX) -> ConvWithIter<Self, CTX> {
612 ConvWithIter { iter: self, ctx }
616 impl<CTX, I> MapConvWith<CTX> for I
619 I::Item: ConvWith<CTX>,
621 type Output = <I::Item as ConvWith<CTX>>::Output;
624 pub struct ConvWithIter<I, CTX> {
629 impl<I, CTX> Iterator for ConvWithIter<I, CTX>
632 I::Item: ConvWith<CTX>,
635 type Item = <I::Item as ConvWith<CTX>>::Output;
637 fn next(&mut self) -> Option<Self::Item> {
638 self.iter.next().map(|item| item.conv_with(self.ctx))
642 pub trait TryConvWithToVec<CTX>: Sized {
645 fn try_conv_with_to_vec(self, ctx: CTX) -> Result<Vec<Self::Output>>;
648 impl<I, CTX> TryConvWithToVec<CTX> for I
651 I::Item: TryConvWith<CTX>,
654 type Output = <I::Item as TryConvWith<CTX>>::Output;
656 fn try_conv_with_to_vec(self, ctx: CTX) -> Result<Vec<Self::Output>> {
657 self.map(|it| it.try_conv_with(ctx)).collect()
664 use test_utils::extract_ranges;
667 fn conv_fold_line_folding_only_fixup() {
668 let text = r#"<fold>mod a;
675 }</fold> else <fold>{
680 let (ranges, text) = extract_ranges(text, "fold");
681 assert_eq!(ranges.len(), 4);
683 Fold { range: ranges[0], kind: FoldKind::Mods },
684 Fold { range: ranges[1], kind: FoldKind::Block },
685 Fold { range: ranges[2], kind: FoldKind::Block },
686 Fold { range: ranges[3], kind: FoldKind::Block },
689 let line_index = LineIndex::new(&text);
690 let ctx = FoldConvCtx { text: &text, line_index: &line_index, line_folding_only: true };
691 let converted: Vec<_> = folds.into_iter().map_conv_with(&ctx).collect();
693 let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)];
694 assert_eq!(converted.len(), expected_lines.len());
695 for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) {
696 assert_eq!(folding_range.start_line, *start_line);
697 assert_eq!(folding_range.start_character, None);
698 assert_eq!(folding_range.end_line, *end_line);
699 assert_eq!(folding_range.end_character, None);