]> git.lizzy.rs Git - rust.git/blob - crates/ra_lsp_server/src/conv.rs
8c87f5195173ae6e4a4468e32dfff85d547dcf89
[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 documentation = self.documentation().map(|value| {
91             Documentation::MarkupContent(MarkupContent {
92                 kind: MarkupKind::Markdown,
93                 value: value.to_string(),
94             })
95         });
96
97         let mut res = lsp_types::CompletionItem {
98             label: self.label().to_string(),
99             detail: self.detail().map(|it| it.to_string()),
100             filter_text: Some(self.lookup().to_string()),
101             kind: self.kind().map(|it| it.conv()),
102             text_edit: Some(text_edit),
103             additional_text_edits,
104             documentation: documentation,
105             ..Default::default()
106         };
107         res.insert_text_format = Some(match self.insert_text_format() {
108             InsertTextFormat::Snippet => lsp_types::InsertTextFormat::Snippet,
109             InsertTextFormat::PlainText => lsp_types::InsertTextFormat::PlainText,
110         });
111
112         res
113     }
114 }
115
116 impl ConvWith for Position {
117     type Ctx = LineIndex;
118     type Output = TextUnit;
119
120     fn conv_with(self, line_index: &LineIndex) -> TextUnit {
121         let line_col = LineCol {
122             line: self.line as u32,
123             col_utf16: self.character as u32,
124         };
125         line_index.offset(line_col)
126     }
127 }
128
129 impl ConvWith for TextUnit {
130     type Ctx = LineIndex;
131     type Output = Position;
132
133     fn conv_with(self, line_index: &LineIndex) -> Position {
134         let line_col = line_index.line_col(self);
135         Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16))
136     }
137 }
138
139 impl ConvWith for TextRange {
140     type Ctx = LineIndex;
141     type Output = Range;
142
143     fn conv_with(self, line_index: &LineIndex) -> Range {
144         Range::new(
145             self.start().conv_with(line_index),
146             self.end().conv_with(line_index),
147         )
148     }
149 }
150
151 impl ConvWith for Range {
152     type Ctx = LineIndex;
153     type Output = TextRange;
154
155     fn conv_with(self, line_index: &LineIndex) -> TextRange {
156         TextRange::from_to(
157             self.start.conv_with(line_index),
158             self.end.conv_with(line_index),
159         )
160     }
161 }
162
163 impl ConvWith for TextEdit {
164     type Ctx = LineIndex;
165     type Output = Vec<lsp_types::TextEdit>;
166
167     fn conv_with(self, line_index: &LineIndex) -> Vec<lsp_types::TextEdit> {
168         self.as_atoms()
169             .into_iter()
170             .map_conv_with(line_index)
171             .collect()
172     }
173 }
174
175 impl<'a> ConvWith for &'a AtomTextEdit {
176     type Ctx = LineIndex;
177     type Output = lsp_types::TextEdit;
178
179     fn conv_with(self, line_index: &LineIndex) -> lsp_types::TextEdit {
180         lsp_types::TextEdit {
181             range: self.delete.conv_with(line_index),
182             new_text: self.insert.clone(),
183         }
184     }
185 }
186
187 impl<T: ConvWith> ConvWith for Option<T> {
188     type Ctx = <T as ConvWith>::Ctx;
189     type Output = Option<<T as ConvWith>::Output>;
190     fn conv_with(self, ctx: &Self::Ctx) -> Self::Output {
191         self.map(|x| ConvWith::conv_with(x, ctx))
192     }
193 }
194
195 impl<'a> TryConvWith for &'a Url {
196     type Ctx = ServerWorld;
197     type Output = FileId;
198     fn try_conv_with(self, world: &ServerWorld) -> Result<FileId> {
199         world.uri_to_file_id(self)
200     }
201 }
202
203 impl TryConvWith for FileId {
204     type Ctx = ServerWorld;
205     type Output = Url;
206     fn try_conv_with(self, world: &ServerWorld) -> Result<Url> {
207         world.file_id_to_uri(self)
208     }
209 }
210
211 impl<'a> TryConvWith for &'a TextDocumentItem {
212     type Ctx = ServerWorld;
213     type Output = FileId;
214     fn try_conv_with(self, world: &ServerWorld) -> Result<FileId> {
215         self.uri.try_conv_with(world)
216     }
217 }
218
219 impl<'a> TryConvWith for &'a VersionedTextDocumentIdentifier {
220     type Ctx = ServerWorld;
221     type Output = FileId;
222     fn try_conv_with(self, world: &ServerWorld) -> Result<FileId> {
223         self.uri.try_conv_with(world)
224     }
225 }
226
227 impl<'a> TryConvWith for &'a TextDocumentIdentifier {
228     type Ctx = ServerWorld;
229     type Output = FileId;
230     fn try_conv_with(self, world: &ServerWorld) -> Result<FileId> {
231         world.uri_to_file_id(&self.uri)
232     }
233 }
234
235 impl<'a> TryConvWith for &'a TextDocumentPositionParams {
236     type Ctx = ServerWorld;
237     type Output = FilePosition;
238     fn try_conv_with(self, world: &ServerWorld) -> Result<FilePosition> {
239         let file_id = self.text_document.try_conv_with(world)?;
240         let line_index = world.analysis().file_line_index(file_id);
241         let offset = self.position.conv_with(&line_index);
242         Ok(FilePosition { file_id, offset })
243     }
244 }
245
246 impl<'a> TryConvWith for (&'a TextDocumentIdentifier, Range) {
247     type Ctx = ServerWorld;
248     type Output = FileRange;
249     fn try_conv_with(self, world: &ServerWorld) -> Result<FileRange> {
250         let file_id = self.0.try_conv_with(world)?;
251         let line_index = world.analysis().file_line_index(file_id);
252         let range = self.1.conv_with(&line_index);
253         Ok(FileRange { file_id, range })
254     }
255 }
256
257 impl<T: TryConvWith> TryConvWith for Vec<T> {
258     type Ctx = <T as TryConvWith>::Ctx;
259     type Output = Vec<<T as TryConvWith>::Output>;
260     fn try_conv_with(self, ctx: &Self::Ctx) -> Result<Self::Output> {
261         let mut res = Vec::with_capacity(self.len());
262         for item in self {
263             res.push(item.try_conv_with(ctx)?);
264         }
265         Ok(res)
266     }
267 }
268
269 impl TryConvWith for SourceChange {
270     type Ctx = ServerWorld;
271     type Output = req::SourceChange;
272     fn try_conv_with(self, world: &ServerWorld) -> Result<req::SourceChange> {
273         let cursor_position = match self.cursor_position {
274             None => None,
275             Some(pos) => {
276                 let line_index = world.analysis().file_line_index(pos.file_id);
277                 let edit = self
278                     .source_file_edits
279                     .iter()
280                     .find(|it| it.file_id == pos.file_id)
281                     .map(|it| &it.edit);
282                 let line_col = match edit {
283                     Some(edit) => translate_offset_with_edit(&*line_index, pos.offset, edit),
284                     None => line_index.line_col(pos.offset),
285                 };
286                 let position =
287                     Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16));
288                 Some(TextDocumentPositionParams {
289                     text_document: TextDocumentIdentifier::new(pos.file_id.try_conv_with(world)?),
290                     position,
291                 })
292             }
293         };
294         let mut document_changes: Vec<DocumentChangeOperation> = Vec::new();
295         for resource_op in self.file_system_edits.try_conv_with(world)? {
296             document_changes.push(DocumentChangeOperation::Op(resource_op));
297         }
298         for text_document_edit in self.source_file_edits.try_conv_with(world)? {
299             document_changes.push(DocumentChangeOperation::Edit(text_document_edit));
300         }
301         let workspace_edit = WorkspaceEdit {
302             changes: None,
303             document_changes: Some(DocumentChanges::Operations(document_changes)),
304         };
305         Ok(req::SourceChange {
306             label: self.label,
307             workspace_edit,
308             cursor_position,
309         })
310     }
311 }
312
313 impl TryConvWith for SourceFileEdit {
314     type Ctx = ServerWorld;
315     type Output = TextDocumentEdit;
316     fn try_conv_with(self, world: &ServerWorld) -> Result<TextDocumentEdit> {
317         let text_document = VersionedTextDocumentIdentifier {
318             uri: self.file_id.try_conv_with(world)?,
319             version: None,
320         };
321         let line_index = world.analysis().file_line_index(self.file_id);
322         let edits = self
323             .edit
324             .as_atoms()
325             .iter()
326             .map_conv_with(&line_index)
327             .collect();
328         Ok(TextDocumentEdit {
329             text_document,
330             edits,
331         })
332     }
333 }
334
335 impl TryConvWith for FileSystemEdit {
336     type Ctx = ServerWorld;
337     type Output = ResourceOp;
338     fn try_conv_with(self, world: &ServerWorld) -> Result<ResourceOp> {
339         let res = match self {
340             FileSystemEdit::CreateFile { source_root, path } => {
341                 let uri = world.path_to_uri(source_root, &path)?;
342                 ResourceOp::Create(CreateFile { uri, options: None })
343             }
344             FileSystemEdit::MoveFile {
345                 src,
346                 dst_source_root,
347                 dst_path,
348             } => {
349                 let old_uri = world.file_id_to_uri(src)?;
350                 let new_uri = world.path_to_uri(dst_source_root, &dst_path)?;
351                 ResourceOp::Rename(RenameFile {
352                     old_uri,
353                     new_uri,
354                     options: None,
355                 })
356             }
357         };
358         Ok(res)
359     }
360 }
361
362 impl TryConvWith for &NavigationTarget {
363     type Ctx = ServerWorld;
364     type Output = Location;
365     fn try_conv_with(self, world: &ServerWorld) -> Result<Location> {
366         let line_index = world.analysis().file_line_index(self.file_id());
367         let range = self.focus_range().unwrap_or(self.full_range());
368         to_location(self.file_id(), range, &world, &line_index)
369     }
370 }
371
372 pub fn to_location_link(
373     target: &RangeInfo<NavigationTarget>,
374     world: &ServerWorld,
375     // line index for original range file
376     line_index: &LineIndex,
377 ) -> Result<LocationLink> {
378     let target_uri = target.info.file_id().try_conv_with(world)?;
379     let tgt_line_index = world.analysis().file_line_index(target.info.file_id());
380
381     let target_range = target.info.full_range().conv_with(&tgt_line_index);
382
383     let target_selection_range = target
384         .info
385         .focus_range()
386         .map(|it| it.conv_with(&tgt_line_index))
387         .unwrap_or(target_range);
388
389     let res = LocationLink {
390         origin_selection_range: Some(target.range.conv_with(line_index)),
391         target_uri,
392         target_range,
393         target_selection_range: target_selection_range,
394     };
395     Ok(res)
396 }
397
398 pub fn to_location(
399     file_id: FileId,
400     range: TextRange,
401     world: &ServerWorld,
402     line_index: &LineIndex,
403 ) -> Result<Location> {
404     let url = file_id.try_conv_with(world)?;
405     let loc = Location::new(url, range.conv_with(line_index));
406     Ok(loc)
407 }
408
409 pub trait MapConvWith<'a>: Sized + 'a {
410     type Ctx;
411     type Output;
412
413     fn map_conv_with(self, ctx: &'a Self::Ctx) -> ConvWithIter<'a, Self, Self::Ctx> {
414         ConvWithIter { iter: self, ctx }
415     }
416 }
417
418 impl<'a, I> MapConvWith<'a> for I
419 where
420     I: Iterator + 'a,
421     I::Item: ConvWith,
422 {
423     type Ctx = <I::Item as ConvWith>::Ctx;
424     type Output = <I::Item as ConvWith>::Output;
425 }
426
427 pub struct ConvWithIter<'a, I, Ctx: 'a> {
428     iter: I,
429     ctx: &'a Ctx,
430 }
431
432 impl<'a, I, Ctx> Iterator for ConvWithIter<'a, I, Ctx>
433 where
434     I: Iterator,
435     I::Item: ConvWith<Ctx = Ctx>,
436 {
437     type Item = <I::Item as ConvWith>::Output;
438
439     fn next(&mut self) -> Option<Self::Item> {
440         self.iter.next().map(|item| item.conv_with(self.ctx))
441     }
442 }