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