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 SymbolKind, TextDocumentEdit, TextDocumentIdentifier, TextDocumentItem,
8 TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier, WorkspaceEdit,
11 translate_offset_with_edit, CompletionItem, CompletionItemKind, FileId, FilePosition,
12 FileRange, FileSystemEdit, Fold, FoldKind, InsertTextFormat, LineCol, LineIndex,
13 NavigationTarget, RangeInfo, ReferenceAccess, Severity, SourceChange, SourceFileEdit,
15 use ra_syntax::{SyntaxKind, TextRange, TextUnit};
16 use ra_text_edit::{AtomTextEdit, TextEdit};
17 use ra_vfs::LineEndings;
19 use crate::{req, world::WorldSnapshot, Result};
23 fn conv(self) -> Self::Output;
26 pub trait ConvWith<CTX> {
28 fn conv_with(self, ctx: CTX) -> Self::Output;
31 pub trait TryConvWith<CTX> {
33 fn try_conv_with(self, ctx: CTX) -> Result<Self::Output>;
36 impl Conv for SyntaxKind {
37 type Output = SymbolKind;
39 fn conv(self) -> <Self as Conv>::Output {
41 SyntaxKind::FN_DEF => SymbolKind::Function,
42 SyntaxKind::STRUCT_DEF => SymbolKind::Struct,
43 SyntaxKind::ENUM_DEF => SymbolKind::Enum,
44 SyntaxKind::ENUM_VARIANT => SymbolKind::EnumMember,
45 SyntaxKind::TRAIT_DEF => SymbolKind::Interface,
46 SyntaxKind::MACRO_CALL => SymbolKind::Function,
47 SyntaxKind::MODULE => SymbolKind::Module,
48 SyntaxKind::TYPE_ALIAS_DEF => SymbolKind::TypeParameter,
49 SyntaxKind::RECORD_FIELD_DEF => SymbolKind::Field,
50 SyntaxKind::STATIC_DEF => SymbolKind::Constant,
51 SyntaxKind::CONST_DEF => SymbolKind::Constant,
52 SyntaxKind::IMPL_BLOCK => SymbolKind::Object,
53 _ => SymbolKind::Variable,
58 impl Conv for ReferenceAccess {
59 type Output = ::lsp_types::DocumentHighlightKind;
61 fn conv(self) -> Self::Output {
62 use lsp_types::DocumentHighlightKind;
64 ReferenceAccess::Read => DocumentHighlightKind::Read,
65 ReferenceAccess::Write => DocumentHighlightKind::Write,
70 impl Conv for CompletionItemKind {
71 type Output = ::lsp_types::CompletionItemKind;
73 fn conv(self) -> <Self as Conv>::Output {
74 use lsp_types::CompletionItemKind::*;
76 CompletionItemKind::Keyword => Keyword,
77 CompletionItemKind::Snippet => Snippet,
78 CompletionItemKind::Module => Module,
79 CompletionItemKind::Function => Function,
80 CompletionItemKind::Struct => Struct,
81 CompletionItemKind::Enum => Enum,
82 CompletionItemKind::EnumVariant => EnumMember,
83 CompletionItemKind::BuiltinType => Struct,
84 CompletionItemKind::Binding => Variable,
85 CompletionItemKind::Field => Field,
86 CompletionItemKind::Trait => Interface,
87 CompletionItemKind::TypeAlias => Struct,
88 CompletionItemKind::Const => Constant,
89 CompletionItemKind::Static => Value,
90 CompletionItemKind::Method => Method,
91 CompletionItemKind::TypeParam => TypeParameter,
92 CompletionItemKind::Macro => Method,
97 impl Conv for Severity {
98 type Output = DiagnosticSeverity;
99 fn conv(self) -> DiagnosticSeverity {
101 Severity::Error => DiagnosticSeverity::Error,
102 Severity::WeakWarning => DiagnosticSeverity::Hint,
107 impl ConvWith<(&LineIndex, LineEndings)> for CompletionItem {
108 type Output = ::lsp_types::CompletionItem;
110 fn conv_with(self, ctx: (&LineIndex, LineEndings)) -> ::lsp_types::CompletionItem {
111 let mut additional_text_edits = Vec::new();
112 let mut text_edit = None;
113 // LSP does not allow arbitrary edits in completion, so we have to do a
114 // non-trivial mapping here.
115 for atom_edit in self.text_edit().as_atoms() {
116 if self.source_range().is_subrange(&atom_edit.delete) {
117 text_edit = Some(if atom_edit.delete == self.source_range() {
118 atom_edit.conv_with(ctx)
120 assert!(self.source_range().end() == atom_edit.delete.end());
122 TextRange::from_to(atom_edit.delete.start(), self.source_range().start());
123 let range2 = self.source_range();
124 let edit1 = AtomTextEdit::replace(range1, String::new());
125 let edit2 = AtomTextEdit::replace(range2, atom_edit.insert.clone());
126 additional_text_edits.push(edit1.conv_with(ctx));
130 assert!(self.source_range().intersection(&atom_edit.delete).is_none());
131 additional_text_edits.push(atom_edit.conv_with(ctx));
134 let text_edit = text_edit.unwrap();
136 let mut res = lsp_types::CompletionItem {
137 label: self.label().to_string(),
138 detail: self.detail().map(|it| it.to_string()),
139 filter_text: Some(self.lookup().to_string()),
140 kind: self.kind().map(|it| it.conv()),
141 text_edit: Some(text_edit),
142 additional_text_edits: Some(additional_text_edits),
143 documentation: self.documentation().map(|it| it.conv()),
144 deprecated: Some(self.deprecated()),
148 if self.deprecated() {
149 res.tags = Some(vec![lsp_types::CompletionItemTag::Deprecated])
152 res.insert_text_format = Some(match self.insert_text_format() {
153 InsertTextFormat::Snippet => lsp_types::InsertTextFormat::Snippet,
154 InsertTextFormat::PlainText => lsp_types::InsertTextFormat::PlainText,
161 impl ConvWith<&LineIndex> for Position {
162 type Output = TextUnit;
164 fn conv_with(self, line_index: &LineIndex) -> TextUnit {
165 let line_col = LineCol { line: self.line as u32, col_utf16: self.character as u32 };
166 line_index.offset(line_col)
170 impl ConvWith<&LineIndex> for TextUnit {
171 type Output = Position;
173 fn conv_with(self, line_index: &LineIndex) -> Position {
174 let line_col = line_index.line_col(self);
175 Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16))
179 impl ConvWith<&LineIndex> for TextRange {
182 fn conv_with(self, line_index: &LineIndex) -> Range {
183 Range::new(self.start().conv_with(line_index), self.end().conv_with(line_index))
187 impl ConvWith<&LineIndex> for Range {
188 type Output = TextRange;
190 fn conv_with(self, line_index: &LineIndex) -> TextRange {
191 TextRange::from_to(self.start.conv_with(line_index), self.end.conv_with(line_index))
195 impl Conv for ra_ide::Documentation {
196 type Output = lsp_types::Documentation;
197 fn conv(self) -> Documentation {
198 Documentation::MarkupContent(MarkupContent {
199 kind: MarkupKind::Markdown,
200 value: crate::markdown::format_docs(self.as_str()),
205 impl Conv for ra_ide::FunctionSignature {
206 type Output = lsp_types::SignatureInformation;
207 fn conv(self) -> Self::Output {
208 use lsp_types::{ParameterInformation, ParameterLabel, SignatureInformation};
210 let label = self.to_string();
212 let documentation = self.doc.map(|it| it.conv());
214 let parameters: Vec<ParameterInformation> = self
217 .map(|param| ParameterInformation {
218 label: ParameterLabel::Simple(param),
223 SignatureInformation { label, documentation, parameters: Some(parameters) }
227 impl ConvWith<(&LineIndex, LineEndings)> for TextEdit {
228 type Output = Vec<lsp_types::TextEdit>;
230 fn conv_with(self, ctx: (&LineIndex, LineEndings)) -> Vec<lsp_types::TextEdit> {
231 self.as_atoms().iter().map_conv_with(ctx).collect()
235 impl ConvWith<(&LineIndex, LineEndings)> for &AtomTextEdit {
236 type Output = lsp_types::TextEdit;
240 (line_index, line_endings): (&LineIndex, LineEndings),
241 ) -> lsp_types::TextEdit {
242 let mut new_text = self.insert.clone();
243 if line_endings == LineEndings::Dos {
244 new_text = new_text.replace('\n', "\r\n");
246 lsp_types::TextEdit { range: self.delete.conv_with(line_index), new_text }
250 pub(crate) struct FoldConvCtx<'a> {
251 pub(crate) text: &'a str,
252 pub(crate) line_index: &'a LineIndex,
253 pub(crate) line_folding_only: bool,
256 impl ConvWith<&FoldConvCtx<'_>> for Fold {
257 type Output = lsp_types::FoldingRange;
259 fn conv_with(self, ctx: &FoldConvCtx) -> lsp_types::FoldingRange {
260 let kind = match self.kind {
261 FoldKind::Comment => Some(lsp_types::FoldingRangeKind::Comment),
262 FoldKind::Imports => Some(lsp_types::FoldingRangeKind::Imports),
263 FoldKind::Mods => None,
264 FoldKind::Block => None,
267 let range = self.range.conv_with(&ctx.line_index);
269 if ctx.line_folding_only {
270 // Clients with line_folding_only == true (such as VSCode) will fold the whole end line
271 // even if it contains text not in the folding range. To prevent that we exclude
272 // range.end.line from the folding region if there is more text after range.end
274 let has_more_text_on_end_line = ctx.text
275 [TextRange::from_to(self.range.end(), TextUnit::of_str(ctx.text))]
277 .take_while(|it| *it != '\n')
278 .any(|it| !it.is_whitespace());
280 let end_line = if has_more_text_on_end_line {
281 range.end.line.saturating_sub(1)
286 lsp_types::FoldingRange {
287 start_line: range.start.line,
288 start_character: None,
294 lsp_types::FoldingRange {
295 start_line: range.start.line,
296 start_character: Some(range.start.character),
297 end_line: range.end.line,
298 end_character: Some(range.end.character),
305 impl<T: ConvWith<CTX>, CTX> ConvWith<CTX> for Option<T> {
306 type Output = Option<T::Output>;
308 fn conv_with(self, ctx: CTX) -> Self::Output {
309 self.map(|x| ConvWith::conv_with(x, ctx))
313 impl TryConvWith<&WorldSnapshot> for &Url {
314 type Output = FileId;
315 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
316 world.uri_to_file_id(self)
320 impl TryConvWith<&WorldSnapshot> for FileId {
322 fn try_conv_with(self, world: &WorldSnapshot) -> Result<Url> {
323 world.file_id_to_uri(self)
327 impl TryConvWith<&WorldSnapshot> for &TextDocumentItem {
328 type Output = FileId;
329 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
330 self.uri.try_conv_with(world)
334 impl TryConvWith<&WorldSnapshot> for &VersionedTextDocumentIdentifier {
335 type Output = FileId;
336 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
337 self.uri.try_conv_with(world)
341 impl TryConvWith<&WorldSnapshot> for &TextDocumentIdentifier {
342 type Output = FileId;
343 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
344 world.uri_to_file_id(&self.uri)
348 impl TryConvWith<&WorldSnapshot> for &TextDocumentPositionParams {
349 type Output = FilePosition;
350 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FilePosition> {
351 let file_id = self.text_document.try_conv_with(world)?;
352 let line_index = world.analysis().file_line_index(file_id)?;
353 let offset = self.position.conv_with(&line_index);
354 Ok(FilePosition { file_id, offset })
358 impl TryConvWith<&WorldSnapshot> for (&TextDocumentIdentifier, Range) {
359 type Output = FileRange;
360 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileRange> {
361 let file_id = self.0.try_conv_with(world)?;
362 let line_index = world.analysis().file_line_index(file_id)?;
363 let range = self.1.conv_with(&line_index);
364 Ok(FileRange { file_id, range })
368 impl<T: TryConvWith<CTX>, CTX: Copy> TryConvWith<CTX> for Vec<T> {
369 type Output = Vec<<T as TryConvWith<CTX>>::Output>;
370 fn try_conv_with(self, ctx: CTX) -> Result<Self::Output> {
371 let mut res = Vec::with_capacity(self.len());
373 res.push(item.try_conv_with(ctx)?);
379 impl TryConvWith<&WorldSnapshot> for SourceChange {
380 type Output = req::SourceChange;
381 fn try_conv_with(self, world: &WorldSnapshot) -> Result<req::SourceChange> {
382 let cursor_position = match self.cursor_position {
385 let line_index = world.analysis().file_line_index(pos.file_id)?;
389 .find(|it| it.file_id == pos.file_id)
391 let line_col = match edit {
392 Some(edit) => translate_offset_with_edit(&*line_index, pos.offset, edit),
393 None => line_index.line_col(pos.offset),
396 Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16));
397 Some(TextDocumentPositionParams {
398 text_document: TextDocumentIdentifier::new(pos.file_id.try_conv_with(world)?),
403 let mut document_changes: Vec<DocumentChangeOperation> = Vec::new();
404 for resource_op in self.file_system_edits.try_conv_with(world)? {
405 document_changes.push(DocumentChangeOperation::Op(resource_op));
407 for text_document_edit in self.source_file_edits.try_conv_with(world)? {
408 document_changes.push(DocumentChangeOperation::Edit(text_document_edit));
410 let workspace_edit = WorkspaceEdit {
412 document_changes: Some(DocumentChanges::Operations(document_changes)),
414 Ok(req::SourceChange { label: self.label, workspace_edit, cursor_position })
418 impl TryConvWith<&WorldSnapshot> for SourceFileEdit {
419 type Output = TextDocumentEdit;
420 fn try_conv_with(self, world: &WorldSnapshot) -> Result<TextDocumentEdit> {
421 let text_document = VersionedTextDocumentIdentifier {
422 uri: self.file_id.try_conv_with(world)?,
425 let line_index = world.analysis().file_line_index(self.file_id)?;
426 let line_endings = world.file_line_endings(self.file_id);
428 self.edit.as_atoms().iter().map_conv_with((&line_index, line_endings)).collect();
429 Ok(TextDocumentEdit { text_document, edits })
433 impl TryConvWith<&WorldSnapshot> for FileSystemEdit {
434 type Output = ResourceOp;
435 fn try_conv_with(self, world: &WorldSnapshot) -> Result<ResourceOp> {
436 let res = match self {
437 FileSystemEdit::CreateFile { source_root, path } => {
438 let uri = world.path_to_uri(source_root, &path)?;
439 ResourceOp::Create(CreateFile { uri, options: None })
441 FileSystemEdit::MoveFile { src, dst_source_root, dst_path } => {
442 let old_uri = world.file_id_to_uri(src)?;
443 let new_uri = world.path_to_uri(dst_source_root, &dst_path)?;
444 ResourceOp::Rename(RenameFile { old_uri, new_uri, options: None })
451 impl TryConvWith<&WorldSnapshot> for &NavigationTarget {
452 type Output = Location;
453 fn try_conv_with(self, world: &WorldSnapshot) -> Result<Location> {
454 let line_index = world.analysis().file_line_index(self.file_id())?;
455 let range = self.range();
456 to_location(self.file_id(), range, &world, &line_index)
460 impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<NavigationTarget>) {
461 type Output = LocationLink;
462 fn try_conv_with(self, world: &WorldSnapshot) -> Result<LocationLink> {
463 let (src_file_id, target) = self;
465 let target_uri = target.info.file_id().try_conv_with(world)?;
466 let src_line_index = world.analysis().file_line_index(src_file_id)?;
467 let tgt_line_index = world.analysis().file_line_index(target.info.file_id())?;
469 let target_range = target.info.full_range().conv_with(&tgt_line_index);
471 let target_selection_range = target
474 .map(|it| it.conv_with(&tgt_line_index))
475 .unwrap_or(target_range);
477 let res = LocationLink {
478 origin_selection_range: Some(target.range.conv_with(&src_line_index)),
481 target_selection_range,
487 impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<Vec<NavigationTarget>>) {
488 type Output = req::GotoDefinitionResponse;
489 fn try_conv_with(self, world: &WorldSnapshot) -> Result<req::GotoTypeDefinitionResponse> {
490 let (file_id, RangeInfo { range, info: navs }) = self;
493 .map(|nav| (file_id, RangeInfo::new(range, nav)))
494 .try_conv_with_to_vec(world)?;
495 if world.options.supports_location_link {
498 let locations: Vec<Location> = links
500 .map(|link| Location { uri: link.target_uri, range: link.target_selection_range })
507 pub fn to_call_hierarchy_item(
510 world: &WorldSnapshot,
511 line_index: &LineIndex,
512 nav: NavigationTarget,
513 ) -> Result<lsp_types::CallHierarchyItem> {
514 Ok(lsp_types::CallHierarchyItem {
515 name: nav.name().to_string(),
516 kind: nav.kind().conv(),
518 detail: nav.description().map(|it| it.to_string()),
519 uri: file_id.try_conv_with(&world)?,
520 range: nav.range().conv_with(&line_index),
521 selection_range: range.conv_with(&line_index),
528 world: &WorldSnapshot,
529 line_index: &LineIndex,
530 ) -> Result<Location> {
531 let url = file_id.try_conv_with(world)?;
532 let loc = Location::new(url, range.conv_with(line_index));
536 pub trait MapConvWith<CTX>: Sized {
539 fn map_conv_with(self, ctx: CTX) -> ConvWithIter<Self, CTX> {
540 ConvWithIter { iter: self, ctx }
544 impl<CTX, I> MapConvWith<CTX> for I
547 I::Item: ConvWith<CTX>,
549 type Output = <I::Item as ConvWith<CTX>>::Output;
552 pub struct ConvWithIter<I, CTX> {
557 impl<I, CTX> Iterator for ConvWithIter<I, CTX>
560 I::Item: ConvWith<CTX>,
563 type Item = <I::Item as ConvWith<CTX>>::Output;
565 fn next(&mut self) -> Option<Self::Item> {
566 self.iter.next().map(|item| item.conv_with(self.ctx))
570 pub trait TryConvWithToVec<CTX>: Sized {
573 fn try_conv_with_to_vec(self, ctx: CTX) -> Result<Vec<Self::Output>>;
576 impl<I, CTX> TryConvWithToVec<CTX> for I
579 I::Item: TryConvWith<CTX>,
582 type Output = <I::Item as TryConvWith<CTX>>::Output;
584 fn try_conv_with_to_vec(self, ctx: CTX) -> Result<Vec<Self::Output>> {
585 self.map(|it| it.try_conv_with(ctx)).collect()
592 use test_utils::extract_ranges;
595 fn conv_fold_line_folding_only_fixup() {
596 let text = r#"<fold>mod a;
603 }</fold> else <fold>{
608 let (ranges, text) = extract_ranges(text, "fold");
609 assert_eq!(ranges.len(), 4);
611 Fold { range: ranges[0], kind: FoldKind::Mods },
612 Fold { range: ranges[1], kind: FoldKind::Block },
613 Fold { range: ranges[2], kind: FoldKind::Block },
614 Fold { range: ranges[3], kind: FoldKind::Block },
617 let line_index = LineIndex::new(&text);
618 let ctx = FoldConvCtx { text: &text, line_index: &line_index, line_folding_only: true };
619 let converted: Vec<_> = folds.into_iter().map_conv_with(&ctx).collect();
621 let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)];
622 assert_eq!(converted.len(), expected_lines.len());
623 for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) {
624 assert_eq!(folding_range.start_line, *start_line);
625 assert_eq!(folding_range.start_character, None);
626 assert_eq!(folding_range.end_line, *end_line);
627 assert_eq!(folding_range.end_character, None);