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