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