]> git.lizzy.rs Git - rust.git/blob - crates/ra_lsp_server/src/conv.rs
Merge #310
[rust.git] / crates / ra_lsp_server / src / conv.rs
1 use languageserver_types::{
2     self, Location, Position, Range, SymbolKind, TextDocumentEdit, TextDocumentIdentifier,
3     TextDocumentItem, TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier, InsertTextFormat,
4 };
5 use ra_analysis::{FileId, FileSystemEdit, SourceChange, SourceFileEdit, FilePosition, CompletionItem, CompletionItemKind, InsertText};
6 use ra_editor::{LineCol, LineIndex};
7 use ra_text_edit::{AtomTextEdit, TextEdit};
8 use ra_syntax::{SyntaxKind, TextRange, TextUnit};
9
10 use crate::{req, server_world::ServerWorld, Result};
11
12 pub trait Conv {
13     type Output;
14     fn conv(self) -> Self::Output;
15 }
16
17 pub trait ConvWith {
18     type Ctx;
19     type Output;
20     fn conv_with(self, ctx: &Self::Ctx) -> Self::Output;
21 }
22
23 pub trait TryConvWith {
24     type Ctx;
25     type Output;
26     fn try_conv_with(self, ctx: &Self::Ctx) -> Result<Self::Output>;
27 }
28
29 impl Conv for SyntaxKind {
30     type Output = SymbolKind;
31
32     fn conv(self) -> <Self as Conv>::Output {
33         match self {
34             SyntaxKind::FN_DEF => SymbolKind::Function,
35             SyntaxKind::STRUCT_DEF => SymbolKind::Struct,
36             SyntaxKind::ENUM_DEF => SymbolKind::Enum,
37             SyntaxKind::TRAIT_DEF => SymbolKind::Interface,
38             SyntaxKind::MODULE => SymbolKind::Module,
39             SyntaxKind::TYPE_DEF => SymbolKind::TypeParameter,
40             SyntaxKind::STATIC_DEF => SymbolKind::Constant,
41             SyntaxKind::CONST_DEF => SymbolKind::Constant,
42             SyntaxKind::IMPL_ITEM => SymbolKind::Object,
43             _ => SymbolKind::Variable,
44         }
45     }
46 }
47
48 impl Conv for CompletionItemKind {
49     type Output = ::languageserver_types::CompletionItemKind;
50
51     fn conv(self) -> <Self as Conv>::Output {
52         use ::languageserver_types::CompletionItemKind::*;
53         match self {
54             CompletionItemKind::Keyword => Keyword,
55             CompletionItemKind::Snippet => Snippet,
56             CompletionItemKind::Module => Module,
57             CompletionItemKind::Function => Function,
58             CompletionItemKind::Binding => Variable,
59         }
60     }
61 }
62
63 impl Conv for CompletionItem {
64     type Output = ::languageserver_types::CompletionItem;
65
66     fn conv(self) -> <Self as Conv>::Output {
67         let mut res = ::languageserver_types::CompletionItem {
68             label: self.label().to_string(),
69             filter_text: Some(self.lookup().to_string()),
70             kind: self.kind().map(|it| it.conv()),
71             ..Default::default()
72         };
73         match self.insert_text() {
74             InsertText::PlainText { text } => {
75                 res.insert_text = Some(text);
76                 res.insert_text_format = Some(InsertTextFormat::PlainText);
77             }
78             InsertText::Snippet { text } => {
79                 res.insert_text = Some(text);
80                 res.insert_text_format = Some(InsertTextFormat::Snippet);
81                 res.kind = Some(languageserver_types::CompletionItemKind::Keyword);
82             }
83         }
84         res
85     }
86 }
87
88 impl ConvWith for Position {
89     type Ctx = LineIndex;
90     type Output = TextUnit;
91
92     fn conv_with(self, line_index: &LineIndex) -> TextUnit {
93         let line_col = LineCol {
94             line: self.line as u32,
95             col_utf16: self.character as u32,
96         };
97         line_index.offset(line_col)
98     }
99 }
100
101 impl ConvWith for TextUnit {
102     type Ctx = LineIndex;
103     type Output = Position;
104
105     fn conv_with(self, line_index: &LineIndex) -> Position {
106         let line_col = line_index.line_col(self);
107         Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16))
108     }
109 }
110
111 impl ConvWith for TextRange {
112     type Ctx = LineIndex;
113     type Output = Range;
114
115     fn conv_with(self, line_index: &LineIndex) -> Range {
116         Range::new(
117             self.start().conv_with(line_index),
118             self.end().conv_with(line_index),
119         )
120     }
121 }
122
123 impl ConvWith for Range {
124     type Ctx = LineIndex;
125     type Output = TextRange;
126
127     fn conv_with(self, line_index: &LineIndex) -> TextRange {
128         TextRange::from_to(
129             self.start.conv_with(line_index),
130             self.end.conv_with(line_index),
131         )
132     }
133 }
134
135 impl ConvWith for TextEdit {
136     type Ctx = LineIndex;
137     type Output = Vec<languageserver_types::TextEdit>;
138
139     fn conv_with(self, line_index: &LineIndex) -> Vec<languageserver_types::TextEdit> {
140         self.as_atoms()
141             .into_iter()
142             .map_conv_with(line_index)
143             .collect()
144     }
145 }
146
147 impl<'a> ConvWith for &'a AtomTextEdit {
148     type Ctx = LineIndex;
149     type Output = languageserver_types::TextEdit;
150
151     fn conv_with(self, line_index: &LineIndex) -> languageserver_types::TextEdit {
152         languageserver_types::TextEdit {
153             range: self.delete.conv_with(line_index),
154             new_text: self.insert.clone(),
155         }
156     }
157 }
158
159 impl<T: ConvWith> ConvWith for Option<T> {
160     type Ctx = <T as ConvWith>::Ctx;
161     type Output = Option<<T as ConvWith>::Output>;
162     fn conv_with(self, ctx: &Self::Ctx) -> Self::Output {
163         self.map(|x| ConvWith::conv_with(x, ctx))
164     }
165 }
166
167 impl<'a> TryConvWith for &'a Url {
168     type Ctx = ServerWorld;
169     type Output = FileId;
170     fn try_conv_with(self, world: &ServerWorld) -> Result<FileId> {
171         world.uri_to_file_id(self)
172     }
173 }
174
175 impl TryConvWith for FileId {
176     type Ctx = ServerWorld;
177     type Output = Url;
178     fn try_conv_with(self, world: &ServerWorld) -> Result<Url> {
179         world.file_id_to_uri(self)
180     }
181 }
182
183 impl<'a> TryConvWith for &'a TextDocumentItem {
184     type Ctx = ServerWorld;
185     type Output = FileId;
186     fn try_conv_with(self, world: &ServerWorld) -> Result<FileId> {
187         self.uri.try_conv_with(world)
188     }
189 }
190
191 impl<'a> TryConvWith for &'a VersionedTextDocumentIdentifier {
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 TextDocumentIdentifier {
200     type Ctx = ServerWorld;
201     type Output = FileId;
202     fn try_conv_with(self, world: &ServerWorld) -> Result<FileId> {
203         world.uri_to_file_id(&self.uri)
204     }
205 }
206
207 impl<'a> TryConvWith for &'a TextDocumentPositionParams {
208     type Ctx = ServerWorld;
209     type Output = FilePosition;
210     fn try_conv_with(self, world: &ServerWorld) -> Result<FilePosition> {
211         let file_id = self.text_document.try_conv_with(world)?;
212         let line_index = world.analysis().file_line_index(file_id);
213         let offset = self.position.conv_with(&line_index);
214         Ok(FilePosition { file_id, offset })
215     }
216 }
217
218 impl<T: TryConvWith> TryConvWith for Vec<T> {
219     type Ctx = <T as TryConvWith>::Ctx;
220     type Output = Vec<<T as TryConvWith>::Output>;
221     fn try_conv_with(self, ctx: &Self::Ctx) -> Result<Self::Output> {
222         let mut res = Vec::with_capacity(self.len());
223         for item in self {
224             res.push(item.try_conv_with(ctx)?);
225         }
226         Ok(res)
227     }
228 }
229
230 impl TryConvWith for SourceChange {
231     type Ctx = ServerWorld;
232     type Output = req::SourceChange;
233     fn try_conv_with(self, world: &ServerWorld) -> Result<req::SourceChange> {
234         let cursor_position = match self.cursor_position {
235             None => None,
236             Some(pos) => {
237                 let line_index = world.analysis().file_line_index(pos.file_id);
238                 let edits = self
239                     .source_file_edits
240                     .iter()
241                     .find(|it| it.file_id == pos.file_id)
242                     .map(|it| it.edit.as_atoms())
243                     .unwrap_or(&[]);
244                 let line_col = translate_offset_with_edit(&*line_index, pos.offset, edits);
245                 let position =
246                     Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16));
247                 Some(TextDocumentPositionParams {
248                     text_document: TextDocumentIdentifier::new(pos.file_id.try_conv_with(world)?),
249                     position,
250                 })
251             }
252         };
253         let source_file_edits = self.source_file_edits.try_conv_with(world)?;
254         let file_system_edits = self.file_system_edits.try_conv_with(world)?;
255         Ok(req::SourceChange {
256             label: self.label,
257             source_file_edits,
258             file_system_edits,
259             cursor_position,
260         })
261     }
262 }
263
264 // HACK: we should translate offset to line/column using linde_index *with edits applied*.
265 // A naive version of this function would be to apply `edits` to the original text,
266 // construct a new line index and use that, but it would be slow.
267 //
268 // Writing fast & correct version is issue #105, let's use a quick hack in the meantime
269 fn translate_offset_with_edit(
270     pre_edit_index: &LineIndex,
271     offset: TextUnit,
272     edits: &[AtomTextEdit],
273 ) -> LineCol {
274     let fallback = pre_edit_index.line_col(offset);
275     let edit = match edits.first() {
276         None => return fallback,
277         Some(edit) => edit,
278     };
279     let end_offset = edit.delete.start() + TextUnit::of_str(&edit.insert);
280     if !(edit.delete.start() <= offset && offset <= end_offset) {
281         return fallback;
282     }
283     let rel_offset = offset - edit.delete.start();
284     let in_edit_line_col = LineIndex::new(&edit.insert).line_col(rel_offset);
285     let edit_line_col = pre_edit_index.line_col(edit.delete.start());
286     if in_edit_line_col.line == 0 {
287         LineCol {
288             line: edit_line_col.line,
289             col_utf16: edit_line_col.col_utf16 + in_edit_line_col.col_utf16,
290         }
291     } else {
292         LineCol {
293             line: edit_line_col.line + in_edit_line_col.line,
294             col_utf16: in_edit_line_col.col_utf16,
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 = req::FileSystemEdit;
324     fn try_conv_with(self, world: &ServerWorld) -> Result<req::FileSystemEdit> {
325         let res = match self {
326             FileSystemEdit::CreateFile { source_root, path } => {
327                 let uri = world.path_to_uri(source_root, &path)?;
328                 req::FileSystemEdit::CreateFile { uri }
329             }
330             FileSystemEdit::MoveFile {
331                 src,
332                 dst_source_root,
333                 dst_path,
334             } => {
335                 let src = world.file_id_to_uri(src)?;
336                 let dst = world.path_to_uri(dst_source_root, &dst_path)?;
337                 req::FileSystemEdit::MoveFile { src, dst }
338             }
339         };
340         Ok(res)
341     }
342 }
343
344 pub fn to_location(
345     file_id: FileId,
346     range: TextRange,
347     world: &ServerWorld,
348     line_index: &LineIndex,
349 ) -> Result<Location> {
350     let url = file_id.try_conv_with(world)?;
351     let loc = Location::new(url, range.conv_with(line_index));
352     Ok(loc)
353 }
354
355 pub trait MapConvWith<'a>: Sized + 'a {
356     type Ctx;
357     type Output;
358
359     fn map_conv_with(self, ctx: &'a Self::Ctx) -> ConvWithIter<'a, Self, Self::Ctx> {
360         ConvWithIter { iter: self, ctx }
361     }
362 }
363
364 impl<'a, I> MapConvWith<'a> for I
365 where
366     I: Iterator + 'a,
367     I::Item: ConvWith,
368 {
369     type Ctx = <I::Item as ConvWith>::Ctx;
370     type Output = <I::Item as ConvWith>::Output;
371 }
372
373 pub struct ConvWithIter<'a, I, Ctx: 'a> {
374     iter: I,
375     ctx: &'a Ctx,
376 }
377
378 impl<'a, I, Ctx> Iterator for ConvWithIter<'a, I, Ctx>
379 where
380     I: Iterator,
381     I::Item: ConvWith<Ctx = Ctx>,
382 {
383     type Item = <I::Item as ConvWith>::Output;
384
385     fn next(&mut self) -> Option<Self::Item> {
386         self.iter.next().map(|item| item.conv_with(self.ctx))
387     }
388 }