]> git.lizzy.rs Git - rust.git/blob - crates/ra_lsp_server/src/conv.rs
Pass Documentation up to LSP and add "rust" to our codeblocks there
[rust.git] / crates / ra_lsp_server / src / conv.rs
1 use lsp_types::{
2     self, CreateFile, Documentation, DocumentChangeOperation, DocumentChanges, Location, LocationLink,
3     MarkupContent, MarkupKind, Position, Range, RenameFile, ResourceOp, SymbolKind, TextDocumentEdit, TextDocumentIdentifier,
4     TextDocumentItem, TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier,
5     WorkspaceEdit,
6 };
7 use ra_ide_api::{
8     CompletionItem, CompletionItemKind, FileId, FilePosition, FileRange, FileSystemEdit,
9     NavigationTarget, SourceChange, SourceFileEdit, RangeInfo,
10     LineCol, LineIndex, translate_offset_with_edit, InsertTextFormat
11 };
12 use ra_syntax::{SyntaxKind, TextRange, TextUnit};
13 use ra_text_edit::{AtomTextEdit, TextEdit};
14
15 use crate::{req, server_world::ServerWorld, Result};
16
17 pub trait Conv {
18     type Output;
19     fn conv(self) -> Self::Output;
20 }
21
22 pub trait ConvWith {
23     type Ctx;
24     type Output;
25     fn conv_with(self, ctx: &Self::Ctx) -> Self::Output;
26 }
27
28 pub trait TryConvWith {
29     type Ctx;
30     type Output;
31     fn try_conv_with(self, ctx: &Self::Ctx) -> Result<Self::Output>;
32 }
33
34 impl Conv for SyntaxKind {
35     type Output = SymbolKind;
36
37     fn conv(self) -> <Self as Conv>::Output {
38         match self {
39             SyntaxKind::FN_DEF => SymbolKind::Function,
40             SyntaxKind::STRUCT_DEF => SymbolKind::Struct,
41             SyntaxKind::ENUM_DEF => SymbolKind::Enum,
42             SyntaxKind::TRAIT_DEF => SymbolKind::Interface,
43             SyntaxKind::MODULE => SymbolKind::Module,
44             SyntaxKind::TYPE_DEF => SymbolKind::TypeParameter,
45             SyntaxKind::STATIC_DEF => SymbolKind::Constant,
46             SyntaxKind::CONST_DEF => SymbolKind::Constant,
47             SyntaxKind::IMPL_BLOCK => SymbolKind::Object,
48             _ => SymbolKind::Variable,
49         }
50     }
51 }
52
53 impl Conv for CompletionItemKind {
54     type Output = ::lsp_types::CompletionItemKind;
55
56     fn conv(self) -> <Self as Conv>::Output {
57         use lsp_types::CompletionItemKind::*;
58         match self {
59             CompletionItemKind::Keyword => Keyword,
60             CompletionItemKind::Snippet => Snippet,
61             CompletionItemKind::Module => Module,
62             CompletionItemKind::Function => Function,
63             CompletionItemKind::Struct => Struct,
64             CompletionItemKind::Enum => Enum,
65             CompletionItemKind::EnumVariant => EnumMember,
66             CompletionItemKind::Binding => Variable,
67             CompletionItemKind::Field => Field,
68             CompletionItemKind::Trait => Interface,
69             CompletionItemKind::TypeAlias => Struct,
70             CompletionItemKind::Const => Constant,
71             CompletionItemKind::Static => Value,
72             CompletionItemKind::Method => Method,
73         }
74     }
75 }
76
77 impl ConvWith for CompletionItem {
78     type Ctx = LineIndex;
79     type Output = ::lsp_types::CompletionItem;
80
81     fn conv_with(mut self, ctx: &LineIndex) -> ::lsp_types::CompletionItem {
82         let atom_text_edit = AtomTextEdit::replace(self.source_range(), self.insert_text());
83         let text_edit = (&atom_text_edit).conv_with(ctx);
84         let additional_text_edits = if let Some(edit) = self.take_text_edit() {
85             Some(edit.conv_with(ctx))
86         } else {
87             None
88         };
89
90         let mut res = lsp_types::CompletionItem {
91             label: self.label().to_string(),
92             detail: self.detail().map(|it| it.to_string()),
93             filter_text: Some(self.lookup().to_string()),
94             kind: self.kind().map(|it| it.conv()),
95             text_edit: Some(text_edit),
96             additional_text_edits,
97             documentation: self.documentation().map(|it| it.conv()),
98             ..Default::default()
99         };
100         res.insert_text_format = Some(match self.insert_text_format() {
101             InsertTextFormat::Snippet => lsp_types::InsertTextFormat::Snippet,
102             InsertTextFormat::PlainText => lsp_types::InsertTextFormat::PlainText,
103         });
104
105         res
106     }
107 }
108
109 impl ConvWith for Position {
110     type Ctx = LineIndex;
111     type Output = TextUnit;
112
113     fn conv_with(self, line_index: &LineIndex) -> TextUnit {
114         let line_col = LineCol {
115             line: self.line as u32,
116             col_utf16: self.character as u32,
117         };
118         line_index.offset(line_col)
119     }
120 }
121
122 impl ConvWith for TextUnit {
123     type Ctx = LineIndex;
124     type Output = Position;
125
126     fn conv_with(self, line_index: &LineIndex) -> Position {
127         let line_col = line_index.line_col(self);
128         Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16))
129     }
130 }
131
132 impl ConvWith for TextRange {
133     type Ctx = LineIndex;
134     type Output = Range;
135
136     fn conv_with(self, line_index: &LineIndex) -> Range {
137         Range::new(
138             self.start().conv_with(line_index),
139             self.end().conv_with(line_index),
140         )
141     }
142 }
143
144 impl ConvWith for Range {
145     type Ctx = LineIndex;
146     type Output = TextRange;
147
148     fn conv_with(self, line_index: &LineIndex) -> TextRange {
149         TextRange::from_to(
150             self.start.conv_with(line_index),
151             self.end.conv_with(line_index),
152         )
153     }
154 }
155
156 impl Conv for ra_ide_api::Documentation {
157     type Output = lsp_types::Documentation;
158     fn conv(self) -> Documentation {
159         Documentation::MarkupContent(MarkupContent {
160             kind: MarkupKind::Markdown,
161             value: crate::markdown::sanitize_markdown(self).into(),
162         })
163     }
164 }
165
166 impl ConvWith for TextEdit {
167     type Ctx = LineIndex;
168     type Output = Vec<lsp_types::TextEdit>;
169
170     fn conv_with(self, line_index: &LineIndex) -> Vec<lsp_types::TextEdit> {
171         self.as_atoms()
172             .into_iter()
173             .map_conv_with(line_index)
174             .collect()
175     }
176 }
177
178 impl<'a> ConvWith for &'a AtomTextEdit {
179     type Ctx = LineIndex;
180     type Output = lsp_types::TextEdit;
181
182     fn conv_with(self, line_index: &LineIndex) -> lsp_types::TextEdit {
183         lsp_types::TextEdit {
184             range: self.delete.conv_with(line_index),
185             new_text: self.insert.clone(),
186         }
187     }
188 }
189
190 impl<T: ConvWith> ConvWith for Option<T> {
191     type Ctx = <T as ConvWith>::Ctx;
192     type Output = Option<<T as ConvWith>::Output>;
193     fn conv_with(self, ctx: &Self::Ctx) -> Self::Output {
194         self.map(|x| ConvWith::conv_with(x, ctx))
195     }
196 }
197
198 impl<'a> TryConvWith for &'a Url {
199     type Ctx = ServerWorld;
200     type Output = FileId;
201     fn try_conv_with(self, world: &ServerWorld) -> Result<FileId> {
202         world.uri_to_file_id(self)
203     }
204 }
205
206 impl TryConvWith for FileId {
207     type Ctx = ServerWorld;
208     type Output = Url;
209     fn try_conv_with(self, world: &ServerWorld) -> Result<Url> {
210         world.file_id_to_uri(self)
211     }
212 }
213
214 impl<'a> TryConvWith for &'a TextDocumentItem {
215     type Ctx = ServerWorld;
216     type Output = FileId;
217     fn try_conv_with(self, world: &ServerWorld) -> Result<FileId> {
218         self.uri.try_conv_with(world)
219     }
220 }
221
222 impl<'a> TryConvWith for &'a VersionedTextDocumentIdentifier {
223     type Ctx = ServerWorld;
224     type Output = FileId;
225     fn try_conv_with(self, world: &ServerWorld) -> Result<FileId> {
226         self.uri.try_conv_with(world)
227     }
228 }
229
230 impl<'a> TryConvWith for &'a TextDocumentIdentifier {
231     type Ctx = ServerWorld;
232     type Output = FileId;
233     fn try_conv_with(self, world: &ServerWorld) -> Result<FileId> {
234         world.uri_to_file_id(&self.uri)
235     }
236 }
237
238 impl<'a> TryConvWith for &'a TextDocumentPositionParams {
239     type Ctx = ServerWorld;
240     type Output = FilePosition;
241     fn try_conv_with(self, world: &ServerWorld) -> Result<FilePosition> {
242         let file_id = self.text_document.try_conv_with(world)?;
243         let line_index = world.analysis().file_line_index(file_id);
244         let offset = self.position.conv_with(&line_index);
245         Ok(FilePosition { file_id, offset })
246     }
247 }
248
249 impl<'a> TryConvWith for (&'a TextDocumentIdentifier, Range) {
250     type Ctx = ServerWorld;
251     type Output = FileRange;
252     fn try_conv_with(self, world: &ServerWorld) -> Result<FileRange> {
253         let file_id = self.0.try_conv_with(world)?;
254         let line_index = world.analysis().file_line_index(file_id);
255         let range = self.1.conv_with(&line_index);
256         Ok(FileRange { file_id, range })
257     }
258 }
259
260 impl<T: TryConvWith> TryConvWith for Vec<T> {
261     type Ctx = <T as TryConvWith>::Ctx;
262     type Output = Vec<<T as TryConvWith>::Output>;
263     fn try_conv_with(self, ctx: &Self::Ctx) -> Result<Self::Output> {
264         let mut res = Vec::with_capacity(self.len());
265         for item in self {
266             res.push(item.try_conv_with(ctx)?);
267         }
268         Ok(res)
269     }
270 }
271
272 impl TryConvWith for SourceChange {
273     type Ctx = ServerWorld;
274     type Output = req::SourceChange;
275     fn try_conv_with(self, world: &ServerWorld) -> Result<req::SourceChange> {
276         let cursor_position = match self.cursor_position {
277             None => None,
278             Some(pos) => {
279                 let line_index = world.analysis().file_line_index(pos.file_id);
280                 let edit = self
281                     .source_file_edits
282                     .iter()
283                     .find(|it| it.file_id == pos.file_id)
284                     .map(|it| &it.edit);
285                 let line_col = match edit {
286                     Some(edit) => translate_offset_with_edit(&*line_index, pos.offset, edit),
287                     None => line_index.line_col(pos.offset),
288                 };
289                 let position =
290                     Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16));
291                 Some(TextDocumentPositionParams {
292                     text_document: TextDocumentIdentifier::new(pos.file_id.try_conv_with(world)?),
293                     position,
294                 })
295             }
296         };
297         let mut document_changes: Vec<DocumentChangeOperation> = Vec::new();
298         for resource_op in self.file_system_edits.try_conv_with(world)? {
299             document_changes.push(DocumentChangeOperation::Op(resource_op));
300         }
301         for text_document_edit in self.source_file_edits.try_conv_with(world)? {
302             document_changes.push(DocumentChangeOperation::Edit(text_document_edit));
303         }
304         let workspace_edit = WorkspaceEdit {
305             changes: None,
306             document_changes: Some(DocumentChanges::Operations(document_changes)),
307         };
308         Ok(req::SourceChange {
309             label: self.label,
310             workspace_edit,
311             cursor_position,
312         })
313     }
314 }
315
316 impl TryConvWith for SourceFileEdit {
317     type Ctx = ServerWorld;
318     type Output = TextDocumentEdit;
319     fn try_conv_with(self, world: &ServerWorld) -> Result<TextDocumentEdit> {
320         let text_document = VersionedTextDocumentIdentifier {
321             uri: self.file_id.try_conv_with(world)?,
322             version: None,
323         };
324         let line_index = world.analysis().file_line_index(self.file_id);
325         let edits = self
326             .edit
327             .as_atoms()
328             .iter()
329             .map_conv_with(&line_index)
330             .collect();
331         Ok(TextDocumentEdit {
332             text_document,
333             edits,
334         })
335     }
336 }
337
338 impl TryConvWith for FileSystemEdit {
339     type Ctx = ServerWorld;
340     type Output = ResourceOp;
341     fn try_conv_with(self, world: &ServerWorld) -> Result<ResourceOp> {
342         let res = match self {
343             FileSystemEdit::CreateFile { source_root, path } => {
344                 let uri = world.path_to_uri(source_root, &path)?;
345                 ResourceOp::Create(CreateFile { uri, options: None })
346             }
347             FileSystemEdit::MoveFile {
348                 src,
349                 dst_source_root,
350                 dst_path,
351             } => {
352                 let old_uri = world.file_id_to_uri(src)?;
353                 let new_uri = world.path_to_uri(dst_source_root, &dst_path)?;
354                 ResourceOp::Rename(RenameFile {
355                     old_uri,
356                     new_uri,
357                     options: None,
358                 })
359             }
360         };
361         Ok(res)
362     }
363 }
364
365 impl TryConvWith for &NavigationTarget {
366     type Ctx = ServerWorld;
367     type Output = Location;
368     fn try_conv_with(self, world: &ServerWorld) -> Result<Location> {
369         let line_index = world.analysis().file_line_index(self.file_id());
370         let range = self.focus_range().unwrap_or(self.full_range());
371         to_location(self.file_id(), range, &world, &line_index)
372     }
373 }
374
375 pub fn to_location_link(
376     target: &RangeInfo<NavigationTarget>,
377     world: &ServerWorld,
378     // line index for original range file
379     line_index: &LineIndex,
380 ) -> Result<LocationLink> {
381     let target_uri = target.info.file_id().try_conv_with(world)?;
382     let tgt_line_index = world.analysis().file_line_index(target.info.file_id());
383
384     let target_range = target.info.full_range().conv_with(&tgt_line_index);
385
386     let target_selection_range = target
387         .info
388         .focus_range()
389         .map(|it| it.conv_with(&tgt_line_index))
390         .unwrap_or(target_range);
391
392     let res = LocationLink {
393         origin_selection_range: Some(target.range.conv_with(line_index)),
394         target_uri,
395         target_range,
396         target_selection_range: target_selection_range,
397     };
398     Ok(res)
399 }
400
401 pub fn to_location(
402     file_id: FileId,
403     range: TextRange,
404     world: &ServerWorld,
405     line_index: &LineIndex,
406 ) -> Result<Location> {
407     let url = file_id.try_conv_with(world)?;
408     let loc = Location::new(url, range.conv_with(line_index));
409     Ok(loc)
410 }
411
412 pub trait MapConvWith<'a>: Sized + 'a {
413     type Ctx;
414     type Output;
415
416     fn map_conv_with(self, ctx: &'a Self::Ctx) -> ConvWithIter<'a, Self, Self::Ctx> {
417         ConvWithIter { iter: self, ctx }
418     }
419 }
420
421 impl<'a, I> MapConvWith<'a> for I
422 where
423     I: Iterator + 'a,
424     I::Item: ConvWith,
425 {
426     type Ctx = <I::Item as ConvWith>::Ctx;
427     type Output = <I::Item as ConvWith>::Output;
428 }
429
430 pub struct ConvWithIter<'a, I, Ctx: 'a> {
431     iter: I,
432     ctx: &'a Ctx,
433 }
434
435 impl<'a, I, Ctx> Iterator for ConvWithIter<'a, I, Ctx>
436 where
437     I: Iterator,
438     I::Item: ConvWith<Ctx = Ctx>,
439 {
440     type Item = <I::Item as ConvWith>::Output;
441
442     fn next(&mut self) -> Option<Self::Item> {
443         self.iter.next().map(|item| item.conv_with(self.ctx))
444     }
445 }