diff options
Diffstat (limited to 'crates/rust-analyzer/src/to_proto.rs')
-rw-r--r-- | crates/rust-analyzer/src/to_proto.rs | 592 |
1 files changed, 592 insertions, 0 deletions
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs new file mode 100644 index 000000000..4500d4982 --- /dev/null +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -0,0 +1,592 @@ | |||
1 | //! Conversion of rust-analyzer specific types to lsp_types equivalents. | ||
2 | use ra_db::{FileId, FileRange}; | ||
3 | use ra_ide::{ | ||
4 | translate_offset_with_edit, Assist, CompletionItem, CompletionItemKind, Documentation, | ||
5 | FileSystemEdit, Fold, FoldKind, FunctionSignature, Highlight, HighlightModifier, HighlightTag, | ||
6 | HighlightedRange, InlayHint, InlayKind, InsertTextFormat, LineIndex, NavigationTarget, | ||
7 | ReferenceAccess, Severity, SourceChange, SourceFileEdit, | ||
8 | }; | ||
9 | use ra_syntax::{SyntaxKind, TextRange, TextSize}; | ||
10 | use ra_text_edit::{Indel, TextEdit}; | ||
11 | use ra_vfs::LineEndings; | ||
12 | |||
13 | use crate::{lsp_ext, semantic_tokens, world::WorldSnapshot, Result}; | ||
14 | |||
15 | pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position { | ||
16 | let line_col = line_index.line_col(offset); | ||
17 | let line = u64::from(line_col.line); | ||
18 | let character = u64::from(line_col.col_utf16); | ||
19 | lsp_types::Position::new(line, character) | ||
20 | } | ||
21 | |||
22 | pub(crate) fn range(line_index: &LineIndex, range: TextRange) -> lsp_types::Range { | ||
23 | let start = position(line_index, range.start()); | ||
24 | let end = position(line_index, range.end()); | ||
25 | lsp_types::Range::new(start, end) | ||
26 | } | ||
27 | |||
28 | pub(crate) fn symbol_kind(syntax_kind: SyntaxKind) -> lsp_types::SymbolKind { | ||
29 | match syntax_kind { | ||
30 | SyntaxKind::FN_DEF => lsp_types::SymbolKind::Function, | ||
31 | SyntaxKind::STRUCT_DEF => lsp_types::SymbolKind::Struct, | ||
32 | SyntaxKind::ENUM_DEF => lsp_types::SymbolKind::Enum, | ||
33 | SyntaxKind::ENUM_VARIANT => lsp_types::SymbolKind::EnumMember, | ||
34 | SyntaxKind::TRAIT_DEF => lsp_types::SymbolKind::Interface, | ||
35 | SyntaxKind::MACRO_CALL => lsp_types::SymbolKind::Function, | ||
36 | SyntaxKind::MODULE => lsp_types::SymbolKind::Module, | ||
37 | SyntaxKind::TYPE_ALIAS_DEF => lsp_types::SymbolKind::TypeParameter, | ||
38 | SyntaxKind::RECORD_FIELD_DEF => lsp_types::SymbolKind::Field, | ||
39 | SyntaxKind::STATIC_DEF => lsp_types::SymbolKind::Constant, | ||
40 | SyntaxKind::CONST_DEF => lsp_types::SymbolKind::Constant, | ||
41 | SyntaxKind::IMPL_DEF => lsp_types::SymbolKind::Object, | ||
42 | _ => lsp_types::SymbolKind::Variable, | ||
43 | } | ||
44 | } | ||
45 | |||
46 | pub(crate) fn document_highlight_kind( | ||
47 | reference_access: ReferenceAccess, | ||
48 | ) -> lsp_types::DocumentHighlightKind { | ||
49 | match reference_access { | ||
50 | ReferenceAccess::Read => lsp_types::DocumentHighlightKind::Read, | ||
51 | ReferenceAccess::Write => lsp_types::DocumentHighlightKind::Write, | ||
52 | } | ||
53 | } | ||
54 | |||
55 | pub(crate) fn diagnostic_severity(severity: Severity) -> lsp_types::DiagnosticSeverity { | ||
56 | match severity { | ||
57 | Severity::Error => lsp_types::DiagnosticSeverity::Error, | ||
58 | Severity::WeakWarning => lsp_types::DiagnosticSeverity::Hint, | ||
59 | } | ||
60 | } | ||
61 | |||
62 | pub(crate) fn documentation(documentation: Documentation) -> lsp_types::Documentation { | ||
63 | let value = crate::markdown::format_docs(documentation.as_str()); | ||
64 | let markup_content = lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value }; | ||
65 | lsp_types::Documentation::MarkupContent(markup_content) | ||
66 | } | ||
67 | |||
68 | pub(crate) fn insert_text_format( | ||
69 | insert_text_format: InsertTextFormat, | ||
70 | ) -> lsp_types::InsertTextFormat { | ||
71 | match insert_text_format { | ||
72 | InsertTextFormat::Snippet => lsp_types::InsertTextFormat::Snippet, | ||
73 | InsertTextFormat::PlainText => lsp_types::InsertTextFormat::PlainText, | ||
74 | } | ||
75 | } | ||
76 | |||
77 | pub(crate) fn completion_item_kind( | ||
78 | completion_item_kind: CompletionItemKind, | ||
79 | ) -> lsp_types::CompletionItemKind { | ||
80 | match completion_item_kind { | ||
81 | CompletionItemKind::Keyword => lsp_types::CompletionItemKind::Keyword, | ||
82 | CompletionItemKind::Snippet => lsp_types::CompletionItemKind::Snippet, | ||
83 | CompletionItemKind::Module => lsp_types::CompletionItemKind::Module, | ||
84 | CompletionItemKind::Function => lsp_types::CompletionItemKind::Function, | ||
85 | CompletionItemKind::Struct => lsp_types::CompletionItemKind::Struct, | ||
86 | CompletionItemKind::Enum => lsp_types::CompletionItemKind::Enum, | ||
87 | CompletionItemKind::EnumVariant => lsp_types::CompletionItemKind::EnumMember, | ||
88 | CompletionItemKind::BuiltinType => lsp_types::CompletionItemKind::Struct, | ||
89 | CompletionItemKind::Binding => lsp_types::CompletionItemKind::Variable, | ||
90 | CompletionItemKind::Field => lsp_types::CompletionItemKind::Field, | ||
91 | CompletionItemKind::Trait => lsp_types::CompletionItemKind::Interface, | ||
92 | CompletionItemKind::TypeAlias => lsp_types::CompletionItemKind::Struct, | ||
93 | CompletionItemKind::Const => lsp_types::CompletionItemKind::Constant, | ||
94 | CompletionItemKind::Static => lsp_types::CompletionItemKind::Value, | ||
95 | CompletionItemKind::Method => lsp_types::CompletionItemKind::Method, | ||
96 | CompletionItemKind::TypeParam => lsp_types::CompletionItemKind::TypeParameter, | ||
97 | CompletionItemKind::Macro => lsp_types::CompletionItemKind::Method, | ||
98 | CompletionItemKind::Attribute => lsp_types::CompletionItemKind::EnumMember, | ||
99 | } | ||
100 | } | ||
101 | |||
102 | pub(crate) fn text_edit( | ||
103 | line_index: &LineIndex, | ||
104 | line_endings: LineEndings, | ||
105 | indel: Indel, | ||
106 | ) -> lsp_types::TextEdit { | ||
107 | let range = range(line_index, indel.delete); | ||
108 | let new_text = match line_endings { | ||
109 | LineEndings::Unix => indel.insert, | ||
110 | LineEndings::Dos => indel.insert.replace('\n', "\r\n"), | ||
111 | }; | ||
112 | lsp_types::TextEdit { range, new_text } | ||
113 | } | ||
114 | |||
115 | pub(crate) fn text_edit_vec( | ||
116 | line_index: &LineIndex, | ||
117 | line_endings: LineEndings, | ||
118 | text_edit: TextEdit, | ||
119 | ) -> Vec<lsp_types::TextEdit> { | ||
120 | text_edit | ||
121 | .as_indels() | ||
122 | .iter() | ||
123 | .map(|it| self::text_edit(line_index, line_endings, it.clone())) | ||
124 | .collect() | ||
125 | } | ||
126 | |||
127 | pub(crate) fn completion_item( | ||
128 | line_index: &LineIndex, | ||
129 | line_endings: LineEndings, | ||
130 | completion_item: CompletionItem, | ||
131 | ) -> lsp_types::CompletionItem { | ||
132 | let mut additional_text_edits = Vec::new(); | ||
133 | let mut text_edit = None; | ||
134 | // LSP does not allow arbitrary edits in completion, so we have to do a | ||
135 | // non-trivial mapping here. | ||
136 | let source_range = completion_item.source_range(); | ||
137 | for indel in completion_item.text_edit().as_indels() { | ||
138 | if indel.delete.contains_range(source_range) { | ||
139 | text_edit = Some(if indel.delete == source_range { | ||
140 | self::text_edit(line_index, line_endings, indel.clone()) | ||
141 | } else { | ||
142 | assert!(source_range.end() == indel.delete.end()); | ||
143 | let range1 = TextRange::new(indel.delete.start(), source_range.start()); | ||
144 | let range2 = source_range; | ||
145 | let indel1 = Indel::replace(range1, String::new()); | ||
146 | let indel2 = Indel::replace(range2, indel.insert.clone()); | ||
147 | additional_text_edits.push(self::text_edit(line_index, line_endings, indel1)); | ||
148 | self::text_edit(line_index, line_endings, indel2) | ||
149 | }) | ||
150 | } else { | ||
151 | assert!(source_range.intersect(indel.delete).is_none()); | ||
152 | let text_edit = self::text_edit(line_index, line_endings, indel.clone()); | ||
153 | additional_text_edits.push(text_edit); | ||
154 | } | ||
155 | } | ||
156 | let text_edit = text_edit.unwrap(); | ||
157 | |||
158 | let mut res = lsp_types::CompletionItem { | ||
159 | label: completion_item.label().to_string(), | ||
160 | detail: completion_item.detail().map(|it| it.to_string()), | ||
161 | filter_text: Some(completion_item.lookup().to_string()), | ||
162 | kind: completion_item.kind().map(completion_item_kind), | ||
163 | text_edit: Some(text_edit.into()), | ||
164 | additional_text_edits: Some(additional_text_edits), | ||
165 | documentation: completion_item.documentation().map(documentation), | ||
166 | deprecated: Some(completion_item.deprecated()), | ||
167 | command: if completion_item.trigger_call_info() { | ||
168 | let cmd = lsp_types::Command { | ||
169 | title: "triggerParameterHints".into(), | ||
170 | command: "editor.action.triggerParameterHints".into(), | ||
171 | arguments: None, | ||
172 | }; | ||
173 | Some(cmd) | ||
174 | } else { | ||
175 | None | ||
176 | }, | ||
177 | ..Default::default() | ||
178 | }; | ||
179 | |||
180 | if completion_item.score().is_some() { | ||
181 | res.preselect = Some(true) | ||
182 | } | ||
183 | |||
184 | if completion_item.deprecated() { | ||
185 | res.tags = Some(vec![lsp_types::CompletionItemTag::Deprecated]) | ||
186 | } | ||
187 | |||
188 | res.insert_text_format = Some(insert_text_format(completion_item.insert_text_format())); | ||
189 | |||
190 | res | ||
191 | } | ||
192 | |||
193 | pub(crate) fn signature_information( | ||
194 | signature: FunctionSignature, | ||
195 | concise: bool, | ||
196 | ) -> lsp_types::SignatureInformation { | ||
197 | let (label, documentation, params) = if concise { | ||
198 | let mut params = signature.parameters; | ||
199 | if signature.has_self_param { | ||
200 | params.remove(0); | ||
201 | } | ||
202 | (params.join(", "), None, params) | ||
203 | } else { | ||
204 | (signature.to_string(), signature.doc.map(documentation), signature.parameters) | ||
205 | }; | ||
206 | |||
207 | let parameters: Vec<lsp_types::ParameterInformation> = params | ||
208 | .into_iter() | ||
209 | .map(|param| lsp_types::ParameterInformation { | ||
210 | label: lsp_types::ParameterLabel::Simple(param), | ||
211 | documentation: None, | ||
212 | }) | ||
213 | .collect(); | ||
214 | |||
215 | lsp_types::SignatureInformation { label, documentation, parameters: Some(parameters) } | ||
216 | } | ||
217 | |||
218 | pub(crate) fn inlay_int(line_index: &LineIndex, inlay_hint: InlayHint) -> lsp_ext::InlayHint { | ||
219 | lsp_ext::InlayHint { | ||
220 | label: inlay_hint.label.to_string(), | ||
221 | range: range(line_index, inlay_hint.range), | ||
222 | kind: match inlay_hint.kind { | ||
223 | InlayKind::ParameterHint => lsp_ext::InlayKind::ParameterHint, | ||
224 | InlayKind::TypeHint => lsp_ext::InlayKind::TypeHint, | ||
225 | InlayKind::ChainingHint => lsp_ext::InlayKind::ChainingHint, | ||
226 | }, | ||
227 | } | ||
228 | } | ||
229 | |||
230 | pub(crate) fn semantic_tokens( | ||
231 | text: &str, | ||
232 | line_index: &LineIndex, | ||
233 | highlights: Vec<HighlightedRange>, | ||
234 | ) -> lsp_types::SemanticTokens { | ||
235 | let mut builder = semantic_tokens::SemanticTokensBuilder::default(); | ||
236 | |||
237 | for highlight_range in highlights { | ||
238 | let (type_, mods) = semantic_token_type_and_modifiers(highlight_range.highlight); | ||
239 | let token_index = semantic_tokens::type_index(type_); | ||
240 | let modifier_bitset = mods.0; | ||
241 | |||
242 | for mut text_range in line_index.lines(highlight_range.range) { | ||
243 | if text[text_range].ends_with('\n') { | ||
244 | text_range = | ||
245 | TextRange::new(text_range.start(), text_range.end() - TextSize::of('\n')); | ||
246 | } | ||
247 | let range = range(&line_index, text_range); | ||
248 | builder.push(range, token_index, modifier_bitset); | ||
249 | } | ||
250 | } | ||
251 | |||
252 | builder.build() | ||
253 | } | ||
254 | |||
255 | fn semantic_token_type_and_modifiers( | ||
256 | highlight: Highlight, | ||
257 | ) -> (lsp_types::SemanticTokenType, semantic_tokens::ModifierSet) { | ||
258 | let mut mods = semantic_tokens::ModifierSet::default(); | ||
259 | let type_ = match highlight.tag { | ||
260 | HighlightTag::Struct => lsp_types::SemanticTokenType::STRUCT, | ||
261 | HighlightTag::Enum => lsp_types::SemanticTokenType::ENUM, | ||
262 | HighlightTag::Union => semantic_tokens::UNION, | ||
263 | HighlightTag::TypeAlias => semantic_tokens::TYPE_ALIAS, | ||
264 | HighlightTag::Trait => lsp_types::SemanticTokenType::INTERFACE, | ||
265 | HighlightTag::BuiltinType => semantic_tokens::BUILTIN_TYPE, | ||
266 | HighlightTag::SelfType => lsp_types::SemanticTokenType::TYPE, | ||
267 | HighlightTag::Field => lsp_types::SemanticTokenType::MEMBER, | ||
268 | HighlightTag::Function => lsp_types::SemanticTokenType::FUNCTION, | ||
269 | HighlightTag::Module => lsp_types::SemanticTokenType::NAMESPACE, | ||
270 | HighlightTag::Constant => { | ||
271 | mods |= semantic_tokens::CONSTANT; | ||
272 | mods |= lsp_types::SemanticTokenModifier::STATIC; | ||
273 | lsp_types::SemanticTokenType::VARIABLE | ||
274 | } | ||
275 | HighlightTag::Static => { | ||
276 | mods |= lsp_types::SemanticTokenModifier::STATIC; | ||
277 | lsp_types::SemanticTokenType::VARIABLE | ||
278 | } | ||
279 | HighlightTag::EnumVariant => semantic_tokens::ENUM_MEMBER, | ||
280 | HighlightTag::Macro => lsp_types::SemanticTokenType::MACRO, | ||
281 | HighlightTag::Local => lsp_types::SemanticTokenType::VARIABLE, | ||
282 | HighlightTag::TypeParam => lsp_types::SemanticTokenType::TYPE_PARAMETER, | ||
283 | HighlightTag::Lifetime => semantic_tokens::LIFETIME, | ||
284 | HighlightTag::ByteLiteral | HighlightTag::NumericLiteral => { | ||
285 | lsp_types::SemanticTokenType::NUMBER | ||
286 | } | ||
287 | HighlightTag::CharLiteral | HighlightTag::StringLiteral => { | ||
288 | lsp_types::SemanticTokenType::STRING | ||
289 | } | ||
290 | HighlightTag::Comment => lsp_types::SemanticTokenType::COMMENT, | ||
291 | HighlightTag::Attribute => semantic_tokens::ATTRIBUTE, | ||
292 | HighlightTag::Keyword => lsp_types::SemanticTokenType::KEYWORD, | ||
293 | HighlightTag::UnresolvedReference => semantic_tokens::UNRESOLVED_REFERENCE, | ||
294 | HighlightTag::FormatSpecifier => semantic_tokens::FORMAT_SPECIFIER, | ||
295 | }; | ||
296 | |||
297 | for modifier in highlight.modifiers.iter() { | ||
298 | let modifier = match modifier { | ||
299 | HighlightModifier::Definition => lsp_types::SemanticTokenModifier::DECLARATION, | ||
300 | HighlightModifier::ControlFlow => semantic_tokens::CONTROL_FLOW, | ||
301 | HighlightModifier::Mutable => semantic_tokens::MUTABLE, | ||
302 | HighlightModifier::Unsafe => semantic_tokens::UNSAFE, | ||
303 | }; | ||
304 | mods |= modifier; | ||
305 | } | ||
306 | |||
307 | (type_, mods) | ||
308 | } | ||
309 | |||
310 | pub(crate) fn folding_range( | ||
311 | text: &str, | ||
312 | line_index: &LineIndex, | ||
313 | line_folding_only: bool, | ||
314 | fold: Fold, | ||
315 | ) -> lsp_types::FoldingRange { | ||
316 | let kind = match fold.kind { | ||
317 | FoldKind::Comment => Some(lsp_types::FoldingRangeKind::Comment), | ||
318 | FoldKind::Imports => Some(lsp_types::FoldingRangeKind::Imports), | ||
319 | FoldKind::Mods | FoldKind::Block => None, | ||
320 | }; | ||
321 | |||
322 | let range = range(line_index, fold.range); | ||
323 | |||
324 | if line_folding_only { | ||
325 | // Clients with line_folding_only == true (such as VSCode) will fold the whole end line | ||
326 | // even if it contains text not in the folding range. To prevent that we exclude | ||
327 | // range.end.line from the folding region if there is more text after range.end | ||
328 | // on the same line. | ||
329 | let has_more_text_on_end_line = text[TextRange::new(fold.range.end(), TextSize::of(text))] | ||
330 | .chars() | ||
331 | .take_while(|it| *it != '\n') | ||
332 | .any(|it| !it.is_whitespace()); | ||
333 | |||
334 | let end_line = if has_more_text_on_end_line { | ||
335 | range.end.line.saturating_sub(1) | ||
336 | } else { | ||
337 | range.end.line | ||
338 | }; | ||
339 | |||
340 | lsp_types::FoldingRange { | ||
341 | start_line: range.start.line, | ||
342 | start_character: None, | ||
343 | end_line, | ||
344 | end_character: None, | ||
345 | kind, | ||
346 | } | ||
347 | } else { | ||
348 | lsp_types::FoldingRange { | ||
349 | start_line: range.start.line, | ||
350 | start_character: Some(range.start.character), | ||
351 | end_line: range.end.line, | ||
352 | end_character: Some(range.end.character), | ||
353 | kind, | ||
354 | } | ||
355 | } | ||
356 | } | ||
357 | |||
358 | pub(crate) fn url(world: &WorldSnapshot, file_id: FileId) -> Result<lsp_types::Url> { | ||
359 | world.file_id_to_uri(file_id) | ||
360 | } | ||
361 | |||
362 | pub(crate) fn text_document_identifier( | ||
363 | world: &WorldSnapshot, | ||
364 | file_id: FileId, | ||
365 | ) -> Result<lsp_types::TextDocumentIdentifier> { | ||
366 | let res = lsp_types::TextDocumentIdentifier { uri: url(world, file_id)? }; | ||
367 | Ok(res) | ||
368 | } | ||
369 | |||
370 | pub(crate) fn versioned_text_document_identifier( | ||
371 | world: &WorldSnapshot, | ||
372 | file_id: FileId, | ||
373 | version: Option<i64>, | ||
374 | ) -> Result<lsp_types::VersionedTextDocumentIdentifier> { | ||
375 | let res = lsp_types::VersionedTextDocumentIdentifier { uri: url(world, file_id)?, version }; | ||
376 | Ok(res) | ||
377 | } | ||
378 | |||
379 | pub(crate) fn location(world: &WorldSnapshot, frange: FileRange) -> Result<lsp_types::Location> { | ||
380 | let url = url(world, frange.file_id)?; | ||
381 | let line_index = world.analysis().file_line_index(frange.file_id)?; | ||
382 | let range = range(&line_index, frange.range); | ||
383 | let loc = lsp_types::Location::new(url, range); | ||
384 | Ok(loc) | ||
385 | } | ||
386 | |||
387 | pub(crate) fn location_link( | ||
388 | world: &WorldSnapshot, | ||
389 | src: FileRange, | ||
390 | target: NavigationTarget, | ||
391 | ) -> Result<lsp_types::LocationLink> { | ||
392 | let src_location = location(world, src)?; | ||
393 | let (target_uri, target_range, target_selection_range) = location_info(world, target)?; | ||
394 | let res = lsp_types::LocationLink { | ||
395 | origin_selection_range: Some(src_location.range), | ||
396 | target_uri, | ||
397 | target_range, | ||
398 | target_selection_range, | ||
399 | }; | ||
400 | Ok(res) | ||
401 | } | ||
402 | |||
403 | fn location_info( | ||
404 | world: &WorldSnapshot, | ||
405 | target: NavigationTarget, | ||
406 | ) -> Result<(lsp_types::Url, lsp_types::Range, lsp_types::Range)> { | ||
407 | let line_index = world.analysis().file_line_index(target.file_id())?; | ||
408 | |||
409 | let target_uri = url(world, target.file_id())?; | ||
410 | let target_range = range(&line_index, target.full_range()); | ||
411 | let target_selection_range = | ||
412 | target.focus_range().map(|it| range(&line_index, it)).unwrap_or(target_range); | ||
413 | Ok((target_uri, target_range, target_selection_range)) | ||
414 | } | ||
415 | |||
416 | pub(crate) fn goto_definition_response( | ||
417 | world: &WorldSnapshot, | ||
418 | src: FileRange, | ||
419 | targets: Vec<NavigationTarget>, | ||
420 | ) -> Result<lsp_types::GotoDefinitionResponse> { | ||
421 | if world.config.client_caps.location_link { | ||
422 | let links = targets | ||
423 | .into_iter() | ||
424 | .map(|nav| location_link(world, src, nav)) | ||
425 | .collect::<Result<Vec<_>>>()?; | ||
426 | Ok(links.into()) | ||
427 | } else { | ||
428 | let locations = targets | ||
429 | .into_iter() | ||
430 | .map(|nav| { | ||
431 | location( | ||
432 | world, | ||
433 | FileRange { | ||
434 | file_id: nav.file_id(), | ||
435 | range: nav.focus_range().unwrap_or(nav.range()), | ||
436 | }, | ||
437 | ) | ||
438 | }) | ||
439 | .collect::<Result<Vec<_>>>()?; | ||
440 | Ok(locations.into()) | ||
441 | } | ||
442 | } | ||
443 | |||
444 | pub(crate) fn text_document_edit( | ||
445 | world: &WorldSnapshot, | ||
446 | source_file_edit: SourceFileEdit, | ||
447 | ) -> Result<lsp_types::TextDocumentEdit> { | ||
448 | let text_document = versioned_text_document_identifier(world, source_file_edit.file_id, None)?; | ||
449 | let line_index = world.analysis().file_line_index(source_file_edit.file_id)?; | ||
450 | let line_endings = world.file_line_endings(source_file_edit.file_id); | ||
451 | let edits = source_file_edit | ||
452 | .edit | ||
453 | .as_indels() | ||
454 | .iter() | ||
455 | .map(|it| text_edit(&line_index, line_endings, it.clone())) | ||
456 | .collect(); | ||
457 | Ok(lsp_types::TextDocumentEdit { text_document, edits }) | ||
458 | } | ||
459 | |||
460 | pub(crate) fn resource_op( | ||
461 | world: &WorldSnapshot, | ||
462 | file_system_edit: FileSystemEdit, | ||
463 | ) -> Result<lsp_types::ResourceOp> { | ||
464 | let res = match file_system_edit { | ||
465 | FileSystemEdit::CreateFile { source_root, path } => { | ||
466 | let uri = world.path_to_uri(source_root, &path)?; | ||
467 | lsp_types::ResourceOp::Create(lsp_types::CreateFile { uri, options: None }) | ||
468 | } | ||
469 | FileSystemEdit::MoveFile { src, dst_source_root, dst_path } => { | ||
470 | let old_uri = world.file_id_to_uri(src)?; | ||
471 | let new_uri = world.path_to_uri(dst_source_root, &dst_path)?; | ||
472 | lsp_types::ResourceOp::Rename(lsp_types::RenameFile { old_uri, new_uri, options: None }) | ||
473 | } | ||
474 | }; | ||
475 | Ok(res) | ||
476 | } | ||
477 | |||
478 | pub(crate) fn source_change( | ||
479 | world: &WorldSnapshot, | ||
480 | source_change: SourceChange, | ||
481 | ) -> Result<lsp_ext::SourceChange> { | ||
482 | let cursor_position = match source_change.cursor_position { | ||
483 | None => None, | ||
484 | Some(pos) => { | ||
485 | let line_index = world.analysis().file_line_index(pos.file_id)?; | ||
486 | let edit = source_change | ||
487 | .source_file_edits | ||
488 | .iter() | ||
489 | .find(|it| it.file_id == pos.file_id) | ||
490 | .map(|it| &it.edit); | ||
491 | let line_col = match edit { | ||
492 | Some(edit) => translate_offset_with_edit(&*line_index, pos.offset, edit), | ||
493 | None => line_index.line_col(pos.offset), | ||
494 | }; | ||
495 | let position = | ||
496 | lsp_types::Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16)); | ||
497 | Some(lsp_types::TextDocumentPositionParams { | ||
498 | text_document: text_document_identifier(world, pos.file_id)?, | ||
499 | position, | ||
500 | }) | ||
501 | } | ||
502 | }; | ||
503 | let mut document_changes: Vec<lsp_types::DocumentChangeOperation> = Vec::new(); | ||
504 | for op in source_change.file_system_edits { | ||
505 | let op = resource_op(&world, op)?; | ||
506 | document_changes.push(lsp_types::DocumentChangeOperation::Op(op)); | ||
507 | } | ||
508 | for edit in source_change.source_file_edits { | ||
509 | let edit = text_document_edit(&world, edit)?; | ||
510 | document_changes.push(lsp_types::DocumentChangeOperation::Edit(edit)); | ||
511 | } | ||
512 | let workspace_edit = lsp_types::WorkspaceEdit { | ||
513 | changes: None, | ||
514 | document_changes: Some(lsp_types::DocumentChanges::Operations(document_changes)), | ||
515 | }; | ||
516 | Ok(lsp_ext::SourceChange { label: source_change.label, workspace_edit, cursor_position }) | ||
517 | } | ||
518 | |||
519 | pub fn call_hierarchy_item( | ||
520 | world: &WorldSnapshot, | ||
521 | target: NavigationTarget, | ||
522 | ) -> Result<lsp_types::CallHierarchyItem> { | ||
523 | let name = target.name().to_string(); | ||
524 | let detail = target.description().map(|it| it.to_string()); | ||
525 | let kind = symbol_kind(target.kind()); | ||
526 | let (uri, range, selection_range) = location_info(world, target)?; | ||
527 | Ok(lsp_types::CallHierarchyItem { name, kind, tags: None, detail, uri, range, selection_range }) | ||
528 | } | ||
529 | |||
530 | #[cfg(test)] | ||
531 | mod tests { | ||
532 | use test_utils::extract_ranges; | ||
533 | |||
534 | use super::*; | ||
535 | |||
536 | #[test] | ||
537 | fn conv_fold_line_folding_only_fixup() { | ||
538 | let text = r#"<fold>mod a; | ||
539 | mod b; | ||
540 | mod c;</fold> | ||
541 | |||
542 | fn main() <fold>{ | ||
543 | if cond <fold>{ | ||
544 | a::do_a(); | ||
545 | }</fold> else <fold>{ | ||
546 | b::do_b(); | ||
547 | }</fold> | ||
548 | }</fold>"#; | ||
549 | |||
550 | let (ranges, text) = extract_ranges(text, "fold"); | ||
551 | assert_eq!(ranges.len(), 4); | ||
552 | let folds = vec![ | ||
553 | Fold { range: ranges[0], kind: FoldKind::Mods }, | ||
554 | Fold { range: ranges[1], kind: FoldKind::Block }, | ||
555 | Fold { range: ranges[2], kind: FoldKind::Block }, | ||
556 | Fold { range: ranges[3], kind: FoldKind::Block }, | ||
557 | ]; | ||
558 | |||
559 | let line_index = LineIndex::new(&text); | ||
560 | let converted: Vec<lsp_types::FoldingRange> = | ||
561 | folds.into_iter().map(|it| folding_range(&text, &line_index, true, it)).collect(); | ||
562 | |||
563 | let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)]; | ||
564 | assert_eq!(converted.len(), expected_lines.len()); | ||
565 | for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) { | ||
566 | assert_eq!(folding_range.start_line, *start_line); | ||
567 | assert_eq!(folding_range.start_character, None); | ||
568 | assert_eq!(folding_range.end_line, *end_line); | ||
569 | assert_eq!(folding_range.end_character, None); | ||
570 | } | ||
571 | } | ||
572 | } | ||
573 | |||
574 | pub(crate) fn code_action(world: &WorldSnapshot, assist: Assist) -> Result<lsp_types::CodeAction> { | ||
575 | let source_change = source_change(&world, assist.source_change)?; | ||
576 | let arg = serde_json::to_value(source_change)?; | ||
577 | let title = assist.label; | ||
578 | let command = lsp_types::Command { | ||
579 | title: title.clone(), | ||
580 | command: "rust-analyzer.applySourceChange".to_string(), | ||
581 | arguments: Some(vec![arg]), | ||
582 | }; | ||
583 | |||
584 | Ok(lsp_types::CodeAction { | ||
585 | title, | ||
586 | kind: Some(String::new()), | ||
587 | diagnostics: None, | ||
588 | edit: None, | ||
589 | command: Some(command), | ||
590 | is_preferred: None, | ||
591 | }) | ||
592 | } | ||