aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_lsp_server/src/conv.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_lsp_server/src/conv.rs')
-rw-r--r--crates/ra_lsp_server/src/conv.rs629
1 files changed, 0 insertions, 629 deletions
diff --git a/crates/ra_lsp_server/src/conv.rs b/crates/ra_lsp_server/src/conv.rs
deleted file mode 100644
index 8af74b211..000000000
--- a/crates/ra_lsp_server/src/conv.rs
+++ /dev/null
@@ -1,629 +0,0 @@
1//! Convenience module responsible for translating between rust-analyzer's types and LSP types.
2
3use lsp_types::{
4 self, CreateFile, DiagnosticSeverity, DocumentChangeOperation, DocumentChanges, Documentation,
5 Location, LocationLink, MarkupContent, MarkupKind, Position, Range, RenameFile, ResourceOp,
6 SymbolKind, TextDocumentEdit, TextDocumentIdentifier, TextDocumentItem,
7 TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier, WorkspaceEdit,
8};
9use ra_ide::{
10 translate_offset_with_edit, CompletionItem, CompletionItemKind, FileId, FilePosition,
11 FileRange, FileSystemEdit, Fold, FoldKind, InsertTextFormat, LineCol, LineIndex,
12 NavigationTarget, RangeInfo, ReferenceAccess, Severity, SourceChange, SourceFileEdit,
13};
14use ra_syntax::{SyntaxKind, TextRange, TextUnit};
15use ra_text_edit::{AtomTextEdit, TextEdit};
16use ra_vfs::LineEndings;
17
18use crate::{req, world::WorldSnapshot, Result};
19
20pub trait Conv {
21 type Output;
22 fn conv(self) -> Self::Output;
23}
24
25pub trait ConvWith<CTX> {
26 type Output;
27 fn conv_with(self, ctx: CTX) -> Self::Output;
28}
29
30pub trait TryConvWith<CTX> {
31 type Output;
32 fn try_conv_with(self, ctx: CTX) -> Result<Self::Output>;
33}
34
35impl Conv for SyntaxKind {
36 type Output = SymbolKind;
37
38 fn conv(self) -> <Self as Conv>::Output {
39 match self {
40 SyntaxKind::FN_DEF => SymbolKind::Function,
41 SyntaxKind::STRUCT_DEF => SymbolKind::Struct,
42 SyntaxKind::ENUM_DEF => SymbolKind::Enum,
43 SyntaxKind::ENUM_VARIANT => SymbolKind::EnumMember,
44 SyntaxKind::TRAIT_DEF => SymbolKind::Interface,
45 SyntaxKind::MACRO_CALL => SymbolKind::Function,
46 SyntaxKind::MODULE => SymbolKind::Module,
47 SyntaxKind::TYPE_ALIAS_DEF => SymbolKind::TypeParameter,
48 SyntaxKind::RECORD_FIELD_DEF => SymbolKind::Field,
49 SyntaxKind::STATIC_DEF => SymbolKind::Constant,
50 SyntaxKind::CONST_DEF => SymbolKind::Constant,
51 SyntaxKind::IMPL_BLOCK => SymbolKind::Object,
52 _ => SymbolKind::Variable,
53 }
54 }
55}
56
57impl Conv for ReferenceAccess {
58 type Output = ::lsp_types::DocumentHighlightKind;
59
60 fn conv(self) -> Self::Output {
61 use lsp_types::DocumentHighlightKind;
62 match self {
63 ReferenceAccess::Read => DocumentHighlightKind::Read,
64 ReferenceAccess::Write => DocumentHighlightKind::Write,
65 }
66 }
67}
68
69impl Conv for CompletionItemKind {
70 type Output = ::lsp_types::CompletionItemKind;
71
72 fn conv(self) -> <Self as Conv>::Output {
73 use lsp_types::CompletionItemKind::*;
74 match self {
75 CompletionItemKind::Keyword => Keyword,
76 CompletionItemKind::Snippet => Snippet,
77 CompletionItemKind::Module => Module,
78 CompletionItemKind::Function => Function,
79 CompletionItemKind::Struct => Struct,
80 CompletionItemKind::Enum => Enum,
81 CompletionItemKind::EnumVariant => EnumMember,
82 CompletionItemKind::BuiltinType => Struct,
83 CompletionItemKind::Binding => Variable,
84 CompletionItemKind::Field => Field,
85 CompletionItemKind::Trait => Interface,
86 CompletionItemKind::TypeAlias => Struct,
87 CompletionItemKind::Const => Constant,
88 CompletionItemKind::Static => Value,
89 CompletionItemKind::Method => Method,
90 CompletionItemKind::TypeParam => TypeParameter,
91 CompletionItemKind::Macro => Method,
92 }
93 }
94}
95
96impl Conv for Severity {
97 type Output = DiagnosticSeverity;
98 fn conv(self) -> DiagnosticSeverity {
99 match self {
100 Severity::Error => DiagnosticSeverity::Error,
101 Severity::WeakWarning => DiagnosticSeverity::Hint,
102 }
103 }
104}
105
106impl ConvWith<(&LineIndex, LineEndings)> for CompletionItem {
107 type Output = ::lsp_types::CompletionItem;
108
109 fn conv_with(self, ctx: (&LineIndex, LineEndings)) -> ::lsp_types::CompletionItem {
110 let mut additional_text_edits = Vec::new();
111 let mut text_edit = None;
112 // LSP does not allow arbitrary edits in completion, so we have to do a
113 // non-trivial mapping here.
114 for atom_edit in self.text_edit().as_atoms() {
115 if self.source_range().is_subrange(&atom_edit.delete) {
116 text_edit = Some(if atom_edit.delete == self.source_range() {
117 atom_edit.conv_with(ctx)
118 } else {
119 assert!(self.source_range().end() == atom_edit.delete.end());
120 let range1 =
121 TextRange::from_to(atom_edit.delete.start(), self.source_range().start());
122 let range2 = self.source_range();
123 let edit1 = AtomTextEdit::replace(range1, String::new());
124 let edit2 = AtomTextEdit::replace(range2, atom_edit.insert.clone());
125 additional_text_edits.push(edit1.conv_with(ctx));
126 edit2.conv_with(ctx)
127 })
128 } else {
129 assert!(self.source_range().intersection(&atom_edit.delete).is_none());
130 additional_text_edits.push(atom_edit.conv_with(ctx));
131 }
132 }
133 let text_edit = text_edit.unwrap();
134
135 let mut res = lsp_types::CompletionItem {
136 label: self.label().to_string(),
137 detail: self.detail().map(|it| it.to_string()),
138 filter_text: Some(self.lookup().to_string()),
139 kind: self.kind().map(|it| it.conv()),
140 text_edit: Some(text_edit),
141 additional_text_edits: Some(additional_text_edits),
142 documentation: self.documentation().map(|it| it.conv()),
143 deprecated: Some(self.deprecated()),
144 ..Default::default()
145 };
146
147 if self.deprecated() {
148 res.tags = Some(vec![lsp_types::CompletionItemTag::Deprecated])
149 }
150
151 res.insert_text_format = Some(match self.insert_text_format() {
152 InsertTextFormat::Snippet => lsp_types::InsertTextFormat::Snippet,
153 InsertTextFormat::PlainText => lsp_types::InsertTextFormat::PlainText,
154 });
155
156 res
157 }
158}
159
160impl ConvWith<&LineIndex> for Position {
161 type Output = TextUnit;
162
163 fn conv_with(self, line_index: &LineIndex) -> TextUnit {
164 let line_col = LineCol { line: self.line as u32, col_utf16: self.character as u32 };
165 line_index.offset(line_col)
166 }
167}
168
169impl ConvWith<&LineIndex> for TextUnit {
170 type Output = Position;
171
172 fn conv_with(self, line_index: &LineIndex) -> Position {
173 let line_col = line_index.line_col(self);
174 Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16))
175 }
176}
177
178impl ConvWith<&LineIndex> for TextRange {
179 type Output = Range;
180
181 fn conv_with(self, line_index: &LineIndex) -> Range {
182 Range::new(self.start().conv_with(line_index), self.end().conv_with(line_index))
183 }
184}
185
186impl ConvWith<&LineIndex> for Range {
187 type Output = TextRange;
188
189 fn conv_with(self, line_index: &LineIndex) -> TextRange {
190 TextRange::from_to(self.start.conv_with(line_index), self.end.conv_with(line_index))
191 }
192}
193
194impl Conv for ra_ide::Documentation {
195 type Output = lsp_types::Documentation;
196 fn conv(self) -> Documentation {
197 Documentation::MarkupContent(MarkupContent {
198 kind: MarkupKind::Markdown,
199 value: crate::markdown::format_docs(self.as_str()),
200 })
201 }
202}
203
204impl Conv for ra_ide::FunctionSignature {
205 type Output = lsp_types::SignatureInformation;
206 fn conv(self) -> Self::Output {
207 use lsp_types::{ParameterInformation, ParameterLabel, SignatureInformation};
208
209 let label = self.to_string();
210
211 let documentation = self.doc.map(|it| it.conv());
212
213 let parameters: Vec<ParameterInformation> = self
214 .parameters
215 .into_iter()
216 .map(|param| ParameterInformation {
217 label: ParameterLabel::Simple(param),
218 documentation: None,
219 })
220 .collect();
221
222 SignatureInformation { label, documentation, parameters: Some(parameters) }
223 }
224}
225
226impl ConvWith<(&LineIndex, LineEndings)> for TextEdit {
227 type Output = Vec<lsp_types::TextEdit>;
228
229 fn conv_with(self, ctx: (&LineIndex, LineEndings)) -> Vec<lsp_types::TextEdit> {
230 self.as_atoms().iter().map_conv_with(ctx).collect()
231 }
232}
233
234impl ConvWith<(&LineIndex, LineEndings)> for &AtomTextEdit {
235 type Output = lsp_types::TextEdit;
236
237 fn conv_with(
238 self,
239 (line_index, line_endings): (&LineIndex, LineEndings),
240 ) -> lsp_types::TextEdit {
241 let mut new_text = self.insert.clone();
242 if line_endings == LineEndings::Dos {
243 new_text = new_text.replace('\n', "\r\n");
244 }
245 lsp_types::TextEdit { range: self.delete.conv_with(line_index), new_text }
246 }
247}
248
249pub(crate) struct FoldConvCtx<'a> {
250 pub(crate) text: &'a str,
251 pub(crate) line_index: &'a LineIndex,
252 pub(crate) line_folding_only: bool,
253}
254
255impl ConvWith<&FoldConvCtx<'_>> for Fold {
256 type Output = lsp_types::FoldingRange;
257
258 fn conv_with(self, ctx: &FoldConvCtx) -> lsp_types::FoldingRange {
259 let kind = match self.kind {
260 FoldKind::Comment => Some(lsp_types::FoldingRangeKind::Comment),
261 FoldKind::Imports => Some(lsp_types::FoldingRangeKind::Imports),
262 FoldKind::Mods => None,
263 FoldKind::Block => None,
264 };
265
266 let range = self.range.conv_with(&ctx.line_index);
267
268 if ctx.line_folding_only {
269 // Clients with line_folding_only == true (such as VSCode) will fold the whole end line
270 // even if it contains text not in the folding range. To prevent that we exclude
271 // range.end.line from the folding region if there is more text after range.end
272 // on the same line.
273 let has_more_text_on_end_line = ctx.text
274 [TextRange::from_to(self.range.end(), TextUnit::of_str(ctx.text))]
275 .chars()
276 .take_while(|it| *it != '\n')
277 .any(|it| !it.is_whitespace());
278
279 let end_line = if has_more_text_on_end_line {
280 range.end.line.saturating_sub(1)
281 } else {
282 range.end.line
283 };
284
285 lsp_types::FoldingRange {
286 start_line: range.start.line,
287 start_character: None,
288 end_line,
289 end_character: None,
290 kind,
291 }
292 } else {
293 lsp_types::FoldingRange {
294 start_line: range.start.line,
295 start_character: Some(range.start.character),
296 end_line: range.end.line,
297 end_character: Some(range.end.character),
298 kind,
299 }
300 }
301 }
302}
303
304impl<T: ConvWith<CTX>, CTX> ConvWith<CTX> for Option<T> {
305 type Output = Option<T::Output>;
306
307 fn conv_with(self, ctx: CTX) -> Self::Output {
308 self.map(|x| ConvWith::conv_with(x, ctx))
309 }
310}
311
312impl TryConvWith<&WorldSnapshot> for &Url {
313 type Output = FileId;
314 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
315 world.uri_to_file_id(self)
316 }
317}
318
319impl TryConvWith<&WorldSnapshot> for FileId {
320 type Output = Url;
321 fn try_conv_with(self, world: &WorldSnapshot) -> Result<Url> {
322 world.file_id_to_uri(self)
323 }
324}
325
326impl TryConvWith<&WorldSnapshot> for &TextDocumentItem {
327 type Output = FileId;
328 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
329 self.uri.try_conv_with(world)
330 }
331}
332
333impl TryConvWith<&WorldSnapshot> for &VersionedTextDocumentIdentifier {
334 type Output = FileId;
335 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
336 self.uri.try_conv_with(world)
337 }
338}
339
340impl TryConvWith<&WorldSnapshot> for &TextDocumentIdentifier {
341 type Output = FileId;
342 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
343 world.uri_to_file_id(&self.uri)
344 }
345}
346
347impl TryConvWith<&WorldSnapshot> for &TextDocumentPositionParams {
348 type Output = FilePosition;
349 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FilePosition> {
350 let file_id = self.text_document.try_conv_with(world)?;
351 let line_index = world.analysis().file_line_index(file_id)?;
352 let offset = self.position.conv_with(&line_index);
353 Ok(FilePosition { file_id, offset })
354 }
355}
356
357impl TryConvWith<&WorldSnapshot> for (&TextDocumentIdentifier, Range) {
358 type Output = FileRange;
359 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileRange> {
360 let file_id = self.0.try_conv_with(world)?;
361 let line_index = world.analysis().file_line_index(file_id)?;
362 let range = self.1.conv_with(&line_index);
363 Ok(FileRange { file_id, range })
364 }
365}
366
367impl<T: TryConvWith<CTX>, CTX: Copy> TryConvWith<CTX> for Vec<T> {
368 type Output = Vec<<T as TryConvWith<CTX>>::Output>;
369 fn try_conv_with(self, ctx: CTX) -> Result<Self::Output> {
370 let mut res = Vec::with_capacity(self.len());
371 for item in self {
372 res.push(item.try_conv_with(ctx)?);
373 }
374 Ok(res)
375 }
376}
377
378impl TryConvWith<&WorldSnapshot> for SourceChange {
379 type Output = req::SourceChange;
380 fn try_conv_with(self, world: &WorldSnapshot) -> Result<req::SourceChange> {
381 let cursor_position = match self.cursor_position {
382 None => None,
383 Some(pos) => {
384 let line_index = world.analysis().file_line_index(pos.file_id)?;
385 let edit = self
386 .source_file_edits
387 .iter()
388 .find(|it| it.file_id == pos.file_id)
389 .map(|it| &it.edit);
390 let line_col = match edit {
391 Some(edit) => translate_offset_with_edit(&*line_index, pos.offset, edit),
392 None => line_index.line_col(pos.offset),
393 };
394 let position =
395 Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16));
396 Some(TextDocumentPositionParams {
397 text_document: TextDocumentIdentifier::new(pos.file_id.try_conv_with(world)?),
398 position,
399 })
400 }
401 };
402 let mut document_changes: Vec<DocumentChangeOperation> = Vec::new();
403 for resource_op in self.file_system_edits.try_conv_with(world)? {
404 document_changes.push(DocumentChangeOperation::Op(resource_op));
405 }
406 for text_document_edit in self.source_file_edits.try_conv_with(world)? {
407 document_changes.push(DocumentChangeOperation::Edit(text_document_edit));
408 }
409 let workspace_edit = WorkspaceEdit {
410 changes: None,
411 document_changes: Some(DocumentChanges::Operations(document_changes)),
412 };
413 Ok(req::SourceChange { label: self.label, workspace_edit, cursor_position })
414 }
415}
416
417impl TryConvWith<&WorldSnapshot> for SourceFileEdit {
418 type Output = TextDocumentEdit;
419 fn try_conv_with(self, world: &WorldSnapshot) -> Result<TextDocumentEdit> {
420 let text_document = VersionedTextDocumentIdentifier {
421 uri: self.file_id.try_conv_with(world)?,
422 version: None,
423 };
424 let line_index = world.analysis().file_line_index(self.file_id)?;
425 let line_endings = world.file_line_endings(self.file_id);
426 let edits =
427 self.edit.as_atoms().iter().map_conv_with((&line_index, line_endings)).collect();
428 Ok(TextDocumentEdit { text_document, edits })
429 }
430}
431
432impl TryConvWith<&WorldSnapshot> for FileSystemEdit {
433 type Output = ResourceOp;
434 fn try_conv_with(self, world: &WorldSnapshot) -> Result<ResourceOp> {
435 let res = match self {
436 FileSystemEdit::CreateFile { source_root, path } => {
437 let uri = world.path_to_uri(source_root, &path)?;
438 ResourceOp::Create(CreateFile { uri, options: None })
439 }
440 FileSystemEdit::MoveFile { src, dst_source_root, dst_path } => {
441 let old_uri = world.file_id_to_uri(src)?;
442 let new_uri = world.path_to_uri(dst_source_root, &dst_path)?;
443 ResourceOp::Rename(RenameFile { old_uri, new_uri, options: None })
444 }
445 };
446 Ok(res)
447 }
448}
449
450impl TryConvWith<&WorldSnapshot> for &NavigationTarget {
451 type Output = Location;
452 fn try_conv_with(self, world: &WorldSnapshot) -> Result<Location> {
453 let line_index = world.analysis().file_line_index(self.file_id())?;
454 let range = self.range();
455 to_location(self.file_id(), range, &world, &line_index)
456 }
457}
458
459impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<NavigationTarget>) {
460 type Output = LocationLink;
461 fn try_conv_with(self, world: &WorldSnapshot) -> Result<LocationLink> {
462 let (src_file_id, target) = self;
463
464 let target_uri = target.info.file_id().try_conv_with(world)?;
465 let src_line_index = world.analysis().file_line_index(src_file_id)?;
466 let tgt_line_index = world.analysis().file_line_index(target.info.file_id())?;
467
468 let target_range = target.info.full_range().conv_with(&tgt_line_index);
469
470 let target_selection_range = target
471 .info
472 .focus_range()
473 .map(|it| it.conv_with(&tgt_line_index))
474 .unwrap_or(target_range);
475
476 let res = LocationLink {
477 origin_selection_range: Some(target.range.conv_with(&src_line_index)),
478 target_uri,
479 target_range,
480 target_selection_range,
481 };
482 Ok(res)
483 }
484}
485
486impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<Vec<NavigationTarget>>) {
487 type Output = req::GotoDefinitionResponse;
488 fn try_conv_with(self, world: &WorldSnapshot) -> Result<req::GotoTypeDefinitionResponse> {
489 let (file_id, RangeInfo { range, info: navs }) = self;
490 let links = navs
491 .into_iter()
492 .map(|nav| (file_id, RangeInfo::new(range, nav)))
493 .try_conv_with_to_vec(world)?;
494 if world.options.supports_location_link {
495 Ok(links.into())
496 } else {
497 let locations: Vec<Location> = links
498 .into_iter()
499 .map(|link| Location { uri: link.target_uri, range: link.target_selection_range })
500 .collect();
501 Ok(locations.into())
502 }
503 }
504}
505
506pub fn to_call_hierarchy_item(
507 file_id: FileId,
508 range: TextRange,
509 world: &WorldSnapshot,
510 line_index: &LineIndex,
511 nav: NavigationTarget,
512) -> Result<lsp_types::CallHierarchyItem> {
513 Ok(lsp_types::CallHierarchyItem {
514 name: nav.name().to_string(),
515 kind: nav.kind().conv(),
516 tags: None,
517 detail: nav.description().map(|it| it.to_string()),
518 uri: file_id.try_conv_with(&world)?,
519 range: nav.range().conv_with(&line_index),
520 selection_range: range.conv_with(&line_index),
521 })
522}
523
524pub fn to_location(
525 file_id: FileId,
526 range: TextRange,
527 world: &WorldSnapshot,
528 line_index: &LineIndex,
529) -> Result<Location> {
530 let url = file_id.try_conv_with(world)?;
531 let loc = Location::new(url, range.conv_with(line_index));
532 Ok(loc)
533}
534
535pub trait MapConvWith<CTX>: Sized {
536 type Output;
537
538 fn map_conv_with(self, ctx: CTX) -> ConvWithIter<Self, CTX> {
539 ConvWithIter { iter: self, ctx }
540 }
541}
542
543impl<CTX, I> MapConvWith<CTX> for I
544where
545 I: Iterator,
546 I::Item: ConvWith<CTX>,
547{
548 type Output = <I::Item as ConvWith<CTX>>::Output;
549}
550
551pub struct ConvWithIter<I, CTX> {
552 iter: I,
553 ctx: CTX,
554}
555
556impl<I, CTX> Iterator for ConvWithIter<I, CTX>
557where
558 I: Iterator,
559 I::Item: ConvWith<CTX>,
560 CTX: Copy,
561{
562 type Item = <I::Item as ConvWith<CTX>>::Output;
563
564 fn next(&mut self) -> Option<Self::Item> {
565 self.iter.next().map(|item| item.conv_with(self.ctx))
566 }
567}
568
569pub trait TryConvWithToVec<CTX>: Sized {
570 type Output;
571
572 fn try_conv_with_to_vec(self, ctx: CTX) -> Result<Vec<Self::Output>>;
573}
574
575impl<I, CTX> TryConvWithToVec<CTX> for I
576where
577 I: Iterator,
578 I::Item: TryConvWith<CTX>,
579 CTX: Copy,
580{
581 type Output = <I::Item as TryConvWith<CTX>>::Output;
582
583 fn try_conv_with_to_vec(self, ctx: CTX) -> Result<Vec<Self::Output>> {
584 self.map(|it| it.try_conv_with(ctx)).collect()
585 }
586}
587
588#[cfg(test)]
589mod tests {
590 use super::*;
591 use test_utils::extract_ranges;
592
593 #[test]
594 fn conv_fold_line_folding_only_fixup() {
595 let text = r#"<fold>mod a;
596mod b;
597mod c;</fold>
598
599fn main() <fold>{
600 if cond <fold>{
601 a::do_a();
602 }</fold> else <fold>{
603 b::do_b();
604 }</fold>
605}</fold>"#;
606
607 let (ranges, text) = extract_ranges(text, "fold");
608 assert_eq!(ranges.len(), 4);
609 let folds = vec![
610 Fold { range: ranges[0], kind: FoldKind::Mods },
611 Fold { range: ranges[1], kind: FoldKind::Block },
612 Fold { range: ranges[2], kind: FoldKind::Block },
613 Fold { range: ranges[3], kind: FoldKind::Block },
614 ];
615
616 let line_index = LineIndex::new(&text);
617 let ctx = FoldConvCtx { text: &text, line_index: &line_index, line_folding_only: true };
618 let converted: Vec<_> = folds.into_iter().map_conv_with(&ctx).collect();
619
620 let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)];
621 assert_eq!(converted.len(), expected_lines.len());
622 for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) {
623 assert_eq!(folding_range.start_line, *start_line);
624 assert_eq!(folding_range.start_character, None);
625 assert_eq!(folding_range.end_line, *end_line);
626 assert_eq!(folding_range.end_character, None);
627 }
628 }
629}