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