1 //! Convenience module responsible for translating between rust-analyzer's 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,
12 translate_offset_with_edit, CompletionItem, CompletionItemKind, CompletionScore, FileId,
13 FilePosition, FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HighlightModifier,
14 HighlightTag, InlayHint, InlayKind, InsertTextFormat, LineCol, LineIndex, NavigationTarget,
15 RangeInfo, ReferenceAccess, Severity, 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, CONSTANT, CONTROL_FLOW, MUTABLE, UNSAFE},
27 use semantic_tokens::{
28 ATTRIBUTE, BUILTIN_TYPE, ENUM_MEMBER, LIFETIME, TYPE_ALIAS, UNION, UNRESOLVED_REFERENCE,
33 fn conv(self) -> Self::Output;
36 pub trait ConvWith<CTX> {
38 fn conv_with(self, ctx: CTX) -> Self::Output;
41 pub trait TryConvWith<CTX> {
43 fn try_conv_with(self, ctx: CTX) -> Result<Self::Output>;
46 impl Conv for SyntaxKind {
47 type Output = SymbolKind;
49 fn conv(self) -> <Self as Conv>::Output {
51 SyntaxKind::FN_DEF => SymbolKind::Function,
52 SyntaxKind::STRUCT_DEF => SymbolKind::Struct,
53 SyntaxKind::ENUM_DEF => SymbolKind::Enum,
54 SyntaxKind::ENUM_VARIANT => SymbolKind::EnumMember,
55 SyntaxKind::TRAIT_DEF => SymbolKind::Interface,
56 SyntaxKind::MACRO_CALL => SymbolKind::Function,
57 SyntaxKind::MODULE => SymbolKind::Module,
58 SyntaxKind::TYPE_ALIAS_DEF => SymbolKind::TypeParameter,
59 SyntaxKind::RECORD_FIELD_DEF => SymbolKind::Field,
60 SyntaxKind::STATIC_DEF => SymbolKind::Constant,
61 SyntaxKind::CONST_DEF => SymbolKind::Constant,
62 SyntaxKind::IMPL_DEF => SymbolKind::Object,
63 _ => SymbolKind::Variable,
68 impl Conv for ReferenceAccess {
69 type Output = ::lsp_types::DocumentHighlightKind;
71 fn conv(self) -> Self::Output {
72 use lsp_types::DocumentHighlightKind;
74 ReferenceAccess::Read => DocumentHighlightKind::Read,
75 ReferenceAccess::Write => DocumentHighlightKind::Write,
80 impl Conv for CompletionItemKind {
81 type Output = ::lsp_types::CompletionItemKind;
83 fn conv(self) -> <Self as Conv>::Output {
84 use lsp_types::CompletionItemKind::*;
86 CompletionItemKind::Keyword => Keyword,
87 CompletionItemKind::Snippet => Snippet,
88 CompletionItemKind::Module => Module,
89 CompletionItemKind::Function => Function,
90 CompletionItemKind::Struct => Struct,
91 CompletionItemKind::Enum => Enum,
92 CompletionItemKind::EnumVariant => EnumMember,
93 CompletionItemKind::BuiltinType => Struct,
94 CompletionItemKind::Binding => Variable,
95 CompletionItemKind::Field => Field,
96 CompletionItemKind::Trait => Interface,
97 CompletionItemKind::TypeAlias => Struct,
98 CompletionItemKind::Const => Constant,
99 CompletionItemKind::Static => Value,
100 CompletionItemKind::Method => Method,
101 CompletionItemKind::TypeParam => TypeParameter,
102 CompletionItemKind::Macro => Method,
107 impl Conv for Severity {
108 type Output = DiagnosticSeverity;
109 fn conv(self) -> DiagnosticSeverity {
111 Severity::Error => DiagnosticSeverity::Error,
112 Severity::WeakWarning => DiagnosticSeverity::Hint,
117 impl ConvWith<(&LineIndex, LineEndings, &mut usize)> for CompletionItem {
118 type Output = ::lsp_types::CompletionItem;
120 fn conv_with(self, ctx: (&LineIndex, LineEndings, &mut usize)) -> ::lsp_types::CompletionItem {
121 let mut additional_text_edits = Vec::new();
122 let mut text_edit = None;
123 // LSP does not allow arbitrary edits in completion, so we have to do a
124 // non-trivial mapping here.
125 for atom_edit in self.text_edit().as_atoms() {
126 if self.source_range().is_subrange(&atom_edit.delete) {
127 text_edit = Some(if atom_edit.delete == self.source_range() {
128 atom_edit.conv_with((ctx.0, ctx.1))
130 assert!(self.source_range().end() == atom_edit.delete.end());
132 TextRange::from_to(atom_edit.delete.start(), self.source_range().start());
133 let range2 = self.source_range();
134 let edit1 = AtomTextEdit::replace(range1, String::new());
135 let edit2 = AtomTextEdit::replace(range2, atom_edit.insert.clone());
136 additional_text_edits.push(edit1.conv_with((ctx.0, ctx.1)));
137 edit2.conv_with((ctx.0, ctx.1))
140 assert!(self.source_range().intersection(&atom_edit.delete).is_none());
141 additional_text_edits.push(atom_edit.conv_with((ctx.0, ctx.1)));
144 let text_edit = text_edit.unwrap();
146 let mut res = lsp_types::CompletionItem {
147 label: self.label().to_string(),
148 detail: self.detail().map(|it| it.to_string()),
149 filter_text: Some(self.lookup().to_string()),
150 kind: self.kind().map(|it| it.conv()),
151 text_edit: Some(text_edit),
152 additional_text_edits: Some(additional_text_edits),
153 documentation: self.documentation().map(|it| it.conv()),
154 deprecated: Some(self.deprecated()),
155 command: if self.trigger_call_info() {
156 let cmd = lsp_types::Command {
157 title: "triggerParameterHints".into(),
158 command: "editor.action.triggerParameterHints".into(),
168 if let Some(score) = self.score() {
170 CompletionScore::TypeAndNameMatch => res.preselect = Some(true),
171 CompletionScore::TypeMatch => {}
173 res.sort_text = Some(format!("{:02}", *ctx.2));
177 if self.deprecated() {
178 res.tags = Some(vec![lsp_types::CompletionItemTag::Deprecated])
181 res.insert_text_format = Some(match self.insert_text_format() {
182 InsertTextFormat::Snippet => lsp_types::InsertTextFormat::Snippet,
183 InsertTextFormat::PlainText => lsp_types::InsertTextFormat::PlainText,
190 impl ConvWith<&LineIndex> for Position {
191 type Output = TextUnit;
193 fn conv_with(self, line_index: &LineIndex) -> TextUnit {
194 let line_col = LineCol { line: self.line as u32, col_utf16: self.character as u32 };
195 line_index.offset(line_col)
199 impl ConvWith<&LineIndex> for TextUnit {
200 type Output = Position;
202 fn conv_with(self, line_index: &LineIndex) -> Position {
203 let line_col = line_index.line_col(self);
204 Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16))
208 impl ConvWith<&LineIndex> for TextRange {
211 fn conv_with(self, line_index: &LineIndex) -> Range {
212 Range::new(self.start().conv_with(line_index), self.end().conv_with(line_index))
216 impl ConvWith<&LineIndex> for Range {
217 type Output = TextRange;
219 fn conv_with(self, line_index: &LineIndex) -> TextRange {
220 TextRange::from_to(self.start.conv_with(line_index), self.end.conv_with(line_index))
224 impl Conv for ra_ide::Documentation {
225 type Output = lsp_types::Documentation;
226 fn conv(self) -> Documentation {
227 Documentation::MarkupContent(MarkupContent {
228 kind: MarkupKind::Markdown,
229 value: crate::markdown::format_docs(self.as_str()),
234 impl ConvWith<bool> for ra_ide::FunctionSignature {
235 type Output = lsp_types::SignatureInformation;
236 fn conv_with(self, concise: bool) -> Self::Output {
237 let (label, documentation, params) = if concise {
238 let mut params = self.parameters;
239 if self.has_self_param {
242 (params.join(", "), None, params)
244 (self.to_string(), self.doc.map(|it| it.conv()), self.parameters)
247 let parameters: Vec<ParameterInformation> = params
249 .map(|param| ParameterInformation {
250 label: ParameterLabel::Simple(param),
255 SignatureInformation { label, documentation, parameters: Some(parameters) }
259 impl ConvWith<(&LineIndex, LineEndings)> for TextEdit {
260 type Output = Vec<lsp_types::TextEdit>;
262 fn conv_with(self, ctx: (&LineIndex, LineEndings)) -> Vec<lsp_types::TextEdit> {
263 self.as_atoms().iter().map_conv_with(ctx).collect()
267 impl ConvWith<(&LineIndex, LineEndings)> for &AtomTextEdit {
268 type Output = lsp_types::TextEdit;
272 (line_index, line_endings): (&LineIndex, LineEndings),
273 ) -> lsp_types::TextEdit {
274 let mut new_text = self.insert.clone();
275 if line_endings == LineEndings::Dos {
276 new_text = new_text.replace('\n', "\r\n");
278 lsp_types::TextEdit { range: self.delete.conv_with(line_index), new_text }
282 pub(crate) struct FoldConvCtx<'a> {
283 pub(crate) text: &'a str,
284 pub(crate) line_index: &'a LineIndex,
285 pub(crate) line_folding_only: bool,
288 impl ConvWith<&FoldConvCtx<'_>> for Fold {
289 type Output = lsp_types::FoldingRange;
291 fn conv_with(self, ctx: &FoldConvCtx) -> lsp_types::FoldingRange {
292 let kind = match self.kind {
293 FoldKind::Comment => Some(lsp_types::FoldingRangeKind::Comment),
294 FoldKind::Imports => Some(lsp_types::FoldingRangeKind::Imports),
295 FoldKind::Mods => None,
296 FoldKind::Block => None,
299 let range = self.range.conv_with(&ctx.line_index);
301 if ctx.line_folding_only {
302 // Clients with line_folding_only == true (such as VSCode) will fold the whole end line
303 // even if it contains text not in the folding range. To prevent that we exclude
304 // range.end.line from the folding region if there is more text after range.end
306 let has_more_text_on_end_line = ctx.text
307 [TextRange::from_to(self.range.end(), TextUnit::of_str(ctx.text))]
309 .take_while(|it| *it != '\n')
310 .any(|it| !it.is_whitespace());
312 let end_line = if has_more_text_on_end_line {
313 range.end.line.saturating_sub(1)
318 lsp_types::FoldingRange {
319 start_line: range.start.line,
320 start_character: None,
326 lsp_types::FoldingRange {
327 start_line: range.start.line,
328 start_character: Some(range.start.character),
329 end_line: range.end.line,
330 end_character: Some(range.end.character),
337 impl ConvWith<&LineIndex> for InlayHint {
338 type Output = req::InlayHint;
339 fn conv_with(self, line_index: &LineIndex) -> Self::Output {
341 label: self.label.to_string(),
342 range: self.range.conv_with(line_index),
343 kind: match self.kind {
344 InlayKind::ParameterHint => req::InlayKind::ParameterHint,
345 InlayKind::TypeHint => req::InlayKind::TypeHint,
346 InlayKind::ChainingHint => req::InlayKind::ChainingHint,
352 impl Conv for Highlight {
353 type Output = (u32, u32);
355 fn conv(self) -> Self::Output {
356 let mut mods = ModifierSet::default();
357 let type_ = match self.tag {
358 HighlightTag::Struct => SemanticTokenType::STRUCT,
359 HighlightTag::Enum => SemanticTokenType::ENUM,
360 HighlightTag::Union => UNION,
361 HighlightTag::TypeAlias => TYPE_ALIAS,
362 HighlightTag::Trait => SemanticTokenType::INTERFACE,
363 HighlightTag::BuiltinType => BUILTIN_TYPE,
364 HighlightTag::SelfType => SemanticTokenType::TYPE,
365 HighlightTag::Field => SemanticTokenType::MEMBER,
366 HighlightTag::Function => SemanticTokenType::FUNCTION,
367 HighlightTag::Module => SemanticTokenType::NAMESPACE,
368 HighlightTag::Constant => {
370 mods |= SemanticTokenModifier::STATIC;
371 SemanticTokenType::VARIABLE
373 HighlightTag::Static => {
374 mods |= SemanticTokenModifier::STATIC;
375 SemanticTokenType::VARIABLE
377 HighlightTag::EnumVariant => ENUM_MEMBER,
378 HighlightTag::Macro => SemanticTokenType::MACRO,
379 HighlightTag::Local => SemanticTokenType::VARIABLE,
380 HighlightTag::TypeParam => SemanticTokenType::TYPE_PARAMETER,
381 HighlightTag::Lifetime => LIFETIME,
382 HighlightTag::ByteLiteral | HighlightTag::NumericLiteral => SemanticTokenType::NUMBER,
383 HighlightTag::CharLiteral | HighlightTag::StringLiteral => SemanticTokenType::STRING,
384 HighlightTag::Comment => SemanticTokenType::COMMENT,
385 HighlightTag::Attribute => ATTRIBUTE,
386 HighlightTag::Keyword => SemanticTokenType::KEYWORD,
387 HighlightTag::UnresolvedReference => UNRESOLVED_REFERENCE,
390 for modifier in self.modifiers.iter() {
391 let modifier = match modifier {
392 HighlightModifier::Definition => SemanticTokenModifier::DECLARATION,
393 HighlightModifier::ControlFlow => CONTROL_FLOW,
394 HighlightModifier::Mutable => MUTABLE,
395 HighlightModifier::Unsafe => UNSAFE,
400 (semantic_tokens::type_index(type_), mods.0)
404 impl<T: ConvWith<CTX>, CTX> ConvWith<CTX> for Option<T> {
405 type Output = Option<T::Output>;
407 fn conv_with(self, ctx: CTX) -> Self::Output {
408 self.map(|x| ConvWith::conv_with(x, ctx))
412 impl TryConvWith<&WorldSnapshot> for &Url {
413 type Output = FileId;
414 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
415 world.uri_to_file_id(self)
419 impl TryConvWith<&WorldSnapshot> for FileId {
421 fn try_conv_with(self, world: &WorldSnapshot) -> Result<Url> {
422 world.file_id_to_uri(self)
426 impl TryConvWith<&WorldSnapshot> for &TextDocumentItem {
427 type Output = FileId;
428 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
429 self.uri.try_conv_with(world)
433 impl TryConvWith<&WorldSnapshot> for &VersionedTextDocumentIdentifier {
434 type Output = FileId;
435 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
436 self.uri.try_conv_with(world)
440 impl TryConvWith<&WorldSnapshot> for &TextDocumentIdentifier {
441 type Output = FileId;
442 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
443 world.uri_to_file_id(&self.uri)
447 impl TryConvWith<&WorldSnapshot> for &TextDocumentPositionParams {
448 type Output = FilePosition;
449 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FilePosition> {
450 let file_id = self.text_document.try_conv_with(world)?;
451 let line_index = world.analysis().file_line_index(file_id)?;
452 let offset = self.position.conv_with(&line_index);
453 Ok(FilePosition { file_id, offset })
457 impl TryConvWith<&WorldSnapshot> for (&TextDocumentIdentifier, Range) {
458 type Output = FileRange;
459 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileRange> {
460 let file_id = self.0.try_conv_with(world)?;
461 let line_index = world.analysis().file_line_index(file_id)?;
462 let range = self.1.conv_with(&line_index);
463 Ok(FileRange { file_id, range })
467 impl<T: TryConvWith<CTX>, CTX: Copy> TryConvWith<CTX> for Vec<T> {
468 type Output = Vec<<T as TryConvWith<CTX>>::Output>;
469 fn try_conv_with(self, ctx: CTX) -> Result<Self::Output> {
470 let mut res = Vec::with_capacity(self.len());
472 res.push(item.try_conv_with(ctx)?);
478 impl TryConvWith<&WorldSnapshot> for SourceChange {
479 type Output = req::SourceChange;
480 fn try_conv_with(self, world: &WorldSnapshot) -> Result<req::SourceChange> {
481 let cursor_position = match self.cursor_position {
484 let line_index = world.analysis().file_line_index(pos.file_id)?;
488 .find(|it| it.file_id == pos.file_id)
490 let line_col = match edit {
491 Some(edit) => translate_offset_with_edit(&*line_index, pos.offset, edit),
492 None => line_index.line_col(pos.offset),
495 Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16));
496 Some(TextDocumentPositionParams {
497 text_document: TextDocumentIdentifier::new(pos.file_id.try_conv_with(world)?),
502 let mut document_changes: Vec<DocumentChangeOperation> = Vec::new();
503 for resource_op in self.file_system_edits.try_conv_with(world)? {
504 document_changes.push(DocumentChangeOperation::Op(resource_op));
506 for text_document_edit in self.source_file_edits.try_conv_with(world)? {
507 document_changes.push(DocumentChangeOperation::Edit(text_document_edit));
509 let workspace_edit = WorkspaceEdit {
511 document_changes: Some(DocumentChanges::Operations(document_changes)),
513 Ok(req::SourceChange { label: self.label, workspace_edit, cursor_position })
517 impl TryConvWith<&WorldSnapshot> for SourceFileEdit {
518 type Output = TextDocumentEdit;
519 fn try_conv_with(self, world: &WorldSnapshot) -> Result<TextDocumentEdit> {
520 let text_document = VersionedTextDocumentIdentifier {
521 uri: self.file_id.try_conv_with(world)?,
524 let line_index = world.analysis().file_line_index(self.file_id)?;
525 let line_endings = world.file_line_endings(self.file_id);
527 self.edit.as_atoms().iter().map_conv_with((&line_index, line_endings)).collect();
528 Ok(TextDocumentEdit { text_document, edits })
532 impl TryConvWith<&WorldSnapshot> for FileSystemEdit {
533 type Output = ResourceOp;
534 fn try_conv_with(self, world: &WorldSnapshot) -> Result<ResourceOp> {
535 let res = match self {
536 FileSystemEdit::CreateFile { source_root, path } => {
537 let uri = world.path_to_uri(source_root, &path)?;
538 ResourceOp::Create(CreateFile { uri, options: None })
540 FileSystemEdit::MoveFile { src, dst_source_root, dst_path } => {
541 let old_uri = world.file_id_to_uri(src)?;
542 let new_uri = world.path_to_uri(dst_source_root, &dst_path)?;
543 ResourceOp::Rename(RenameFile { old_uri, new_uri, options: None })
550 impl TryConvWith<&WorldSnapshot> for &NavigationTarget {
551 type Output = Location;
552 fn try_conv_with(self, world: &WorldSnapshot) -> Result<Location> {
553 let line_index = world.analysis().file_line_index(self.file_id())?;
554 let range = self.range();
555 to_location(self.file_id(), range, &world, &line_index)
559 impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<NavigationTarget>) {
560 type Output = LocationLink;
561 fn try_conv_with(self, world: &WorldSnapshot) -> Result<LocationLink> {
562 let (src_file_id, target) = self;
564 let target_uri = target.info.file_id().try_conv_with(world)?;
565 let src_line_index = world.analysis().file_line_index(src_file_id)?;
566 let tgt_line_index = world.analysis().file_line_index(target.info.file_id())?;
568 let target_range = target.info.full_range().conv_with(&tgt_line_index);
570 let target_selection_range = target
573 .map(|it| it.conv_with(&tgt_line_index))
574 .unwrap_or(target_range);
576 let res = LocationLink {
577 origin_selection_range: Some(target.range.conv_with(&src_line_index)),
580 target_selection_range,
586 impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<Vec<NavigationTarget>>) {
587 type Output = req::GotoDefinitionResponse;
588 fn try_conv_with(self, world: &WorldSnapshot) -> Result<req::GotoTypeDefinitionResponse> {
589 let (file_id, RangeInfo { range, info: navs }) = self;
592 .map(|nav| (file_id, RangeInfo::new(range, nav)))
593 .try_conv_with_to_vec(world)?;
594 if world.config.client_caps.location_link {
597 let locations: Vec<Location> = links
599 .map(|link| Location { uri: link.target_uri, range: link.target_selection_range })
606 pub fn to_call_hierarchy_item(
609 world: &WorldSnapshot,
610 line_index: &LineIndex,
611 nav: NavigationTarget,
612 ) -> Result<lsp_types::CallHierarchyItem> {
613 Ok(lsp_types::CallHierarchyItem {
614 name: nav.name().to_string(),
615 kind: nav.kind().conv(),
617 detail: nav.description().map(|it| it.to_string()),
618 uri: file_id.try_conv_with(&world)?,
619 range: nav.range().conv_with(&line_index),
620 selection_range: range.conv_with(&line_index),
627 world: &WorldSnapshot,
628 line_index: &LineIndex,
629 ) -> Result<Location> {
630 let url = file_id.try_conv_with(world)?;
631 let loc = Location::new(url, range.conv_with(line_index));
635 pub trait MapConvWith<CTX>: Sized {
638 fn map_conv_with(self, ctx: CTX) -> ConvWithIter<Self, CTX> {
639 ConvWithIter { iter: self, ctx }
643 impl<CTX, I> MapConvWith<CTX> for I
646 I::Item: ConvWith<CTX>,
648 type Output = <I::Item as ConvWith<CTX>>::Output;
651 pub struct ConvWithIter<I, CTX> {
656 impl<I, CTX> Iterator for ConvWithIter<I, CTX>
659 I::Item: ConvWith<CTX>,
662 type Item = <I::Item as ConvWith<CTX>>::Output;
664 fn next(&mut self) -> Option<Self::Item> {
665 self.iter.next().map(|item| item.conv_with(self.ctx))
669 pub trait TryConvWithToVec<CTX>: Sized {
672 fn try_conv_with_to_vec(self, ctx: CTX) -> Result<Vec<Self::Output>>;
675 impl<I, CTX> TryConvWithToVec<CTX> for I
678 I::Item: TryConvWith<CTX>,
681 type Output = <I::Item as TryConvWith<CTX>>::Output;
683 fn try_conv_with_to_vec(self, ctx: CTX) -> Result<Vec<Self::Output>> {
684 self.map(|it| it.try_conv_with(ctx)).collect()
691 use test_utils::extract_ranges;
694 fn conv_fold_line_folding_only_fixup() {
695 let text = r#"<fold>mod a;
702 }</fold> else <fold>{
707 let (ranges, text) = extract_ranges(text, "fold");
708 assert_eq!(ranges.len(), 4);
710 Fold { range: ranges[0], kind: FoldKind::Mods },
711 Fold { range: ranges[1], kind: FoldKind::Block },
712 Fold { range: ranges[2], kind: FoldKind::Block },
713 Fold { range: ranges[3], kind: FoldKind::Block },
716 let line_index = LineIndex::new(&text);
717 let ctx = FoldConvCtx { text: &text, line_index: &line_index, line_folding_only: true };
718 let converted: Vec<_> = folds.into_iter().map_conv_with(&ctx).collect();
720 let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)];
721 assert_eq!(converted.len(), expected_lines.len());
722 for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) {
723 assert_eq!(folding_range.start_line, *start_line);
724 assert_eq!(folding_range.start_character, None);
725 assert_eq!(folding_range.end_line, *end_line);
726 assert_eq!(folding_range.end_character, None);