1 //! Conversion of rust-analyzer specific types to lsp_types equivalents.
2 use std::{
3 iter::once,
4 path,
5 sync::atomic::{AtomicU32, Ordering},
6 };
7
8 use ide::{
9 Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionItem,
10 CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit,
11 Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, InlayHint,
12 InlayHintLabel, InlayHintLabelPart, InlayKind, Markup, NavigationTarget, ReferenceCategory,
13 RenameError, Runnable, Severity, SignatureHelp, SourceChange, StructureNodeKind, SymbolKind,
14 TextEdit, TextRange, TextSize,
15 };
16 use itertools::Itertools;
17 use serde_json::to_value;
18 use vfs::AbsPath;
19
20 use crate::{
21 cargo_target_spec::CargoTargetSpec,
22 config::{CallInfoConfig, Config},
23 global_state::GlobalStateSnapshot,
24 line_index::{LineEndings, LineIndex, PositionEncoding},
25 lsp_ext,
26 lsp_utils::invalid_params_error,
27 semantic_tokens::{self, standard_fallback_type},
28 };
29
position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position30 pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position {
31 let line_col = line_index.index.line_col(offset);
32 match line_index.encoding {
33 PositionEncoding::Utf8 => lsp_types::Position::new(line_col.line, line_col.col),
34 PositionEncoding::Wide(enc) => {
35 let line_col = line_index.index.to_wide(enc, line_col).unwrap();
36 lsp_types::Position::new(line_col.line, line_col.col)
37 }
38 }
39 }
40
range(line_index: &LineIndex, range: TextRange) -> lsp_types::Range41 pub(crate) fn range(line_index: &LineIndex, range: TextRange) -> lsp_types::Range {
42 let start = position(line_index, range.start());
43 let end = position(line_index, range.end());
44 lsp_types::Range::new(start, end)
45 }
46
symbol_kind(symbol_kind: SymbolKind) -> lsp_types::SymbolKind47 pub(crate) fn symbol_kind(symbol_kind: SymbolKind) -> lsp_types::SymbolKind {
48 match symbol_kind {
49 SymbolKind::Function => lsp_types::SymbolKind::FUNCTION,
50 SymbolKind::Struct => lsp_types::SymbolKind::STRUCT,
51 SymbolKind::Enum => lsp_types::SymbolKind::ENUM,
52 SymbolKind::Variant => lsp_types::SymbolKind::ENUM_MEMBER,
53 SymbolKind::Trait | SymbolKind::TraitAlias => lsp_types::SymbolKind::INTERFACE,
54 SymbolKind::Macro
55 | SymbolKind::BuiltinAttr
56 | SymbolKind::Attribute
57 | SymbolKind::Derive
58 | SymbolKind::DeriveHelper => lsp_types::SymbolKind::FUNCTION,
59 SymbolKind::Module | SymbolKind::ToolModule => lsp_types::SymbolKind::MODULE,
60 SymbolKind::TypeAlias | SymbolKind::TypeParam | SymbolKind::SelfType => {
61 lsp_types::SymbolKind::TYPE_PARAMETER
62 }
63 SymbolKind::Field => lsp_types::SymbolKind::FIELD,
64 SymbolKind::Static => lsp_types::SymbolKind::CONSTANT,
65 SymbolKind::Const => lsp_types::SymbolKind::CONSTANT,
66 SymbolKind::ConstParam => lsp_types::SymbolKind::CONSTANT,
67 SymbolKind::Impl => lsp_types::SymbolKind::OBJECT,
68 SymbolKind::Local
69 | SymbolKind::SelfParam
70 | SymbolKind::LifetimeParam
71 | SymbolKind::ValueParam
72 | SymbolKind::Label => lsp_types::SymbolKind::VARIABLE,
73 SymbolKind::Union => lsp_types::SymbolKind::STRUCT,
74 }
75 }
76
structure_node_kind(kind: StructureNodeKind) -> lsp_types::SymbolKind77 pub(crate) fn structure_node_kind(kind: StructureNodeKind) -> lsp_types::SymbolKind {
78 match kind {
79 StructureNodeKind::SymbolKind(symbol) => symbol_kind(symbol),
80 StructureNodeKind::Region => lsp_types::SymbolKind::NAMESPACE,
81 }
82 }
83
document_highlight_kind( category: ReferenceCategory, ) -> Option<lsp_types::DocumentHighlightKind>84 pub(crate) fn document_highlight_kind(
85 category: ReferenceCategory,
86 ) -> Option<lsp_types::DocumentHighlightKind> {
87 match category {
88 ReferenceCategory::Read => Some(lsp_types::DocumentHighlightKind::READ),
89 ReferenceCategory::Write => Some(lsp_types::DocumentHighlightKind::WRITE),
90 ReferenceCategory::Import => None,
91 }
92 }
93
diagnostic_severity(severity: Severity) -> lsp_types::DiagnosticSeverity94 pub(crate) fn diagnostic_severity(severity: Severity) -> lsp_types::DiagnosticSeverity {
95 match severity {
96 Severity::Error => lsp_types::DiagnosticSeverity::ERROR,
97 Severity::WeakWarning => lsp_types::DiagnosticSeverity::HINT,
98 }
99 }
100
documentation(documentation: Documentation) -> lsp_types::Documentation101 pub(crate) fn documentation(documentation: Documentation) -> lsp_types::Documentation {
102 let value = crate::markdown::format_docs(documentation.as_str());
103 let markup_content = lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value };
104 lsp_types::Documentation::MarkupContent(markup_content)
105 }
106
completion_item_kind( completion_item_kind: CompletionItemKind, ) -> lsp_types::CompletionItemKind107 pub(crate) fn completion_item_kind(
108 completion_item_kind: CompletionItemKind,
109 ) -> lsp_types::CompletionItemKind {
110 match completion_item_kind {
111 CompletionItemKind::Binding => lsp_types::CompletionItemKind::VARIABLE,
112 CompletionItemKind::BuiltinType => lsp_types::CompletionItemKind::STRUCT,
113 CompletionItemKind::InferredType => lsp_types::CompletionItemKind::SNIPPET,
114 CompletionItemKind::Keyword => lsp_types::CompletionItemKind::KEYWORD,
115 CompletionItemKind::Method => lsp_types::CompletionItemKind::METHOD,
116 CompletionItemKind::Snippet => lsp_types::CompletionItemKind::SNIPPET,
117 CompletionItemKind::UnresolvedReference => lsp_types::CompletionItemKind::REFERENCE,
118 CompletionItemKind::SymbolKind(symbol) => match symbol {
119 SymbolKind::Attribute => lsp_types::CompletionItemKind::FUNCTION,
120 SymbolKind::Const => lsp_types::CompletionItemKind::CONSTANT,
121 SymbolKind::ConstParam => lsp_types::CompletionItemKind::TYPE_PARAMETER,
122 SymbolKind::Derive => lsp_types::CompletionItemKind::FUNCTION,
123 SymbolKind::DeriveHelper => lsp_types::CompletionItemKind::FUNCTION,
124 SymbolKind::Enum => lsp_types::CompletionItemKind::ENUM,
125 SymbolKind::Field => lsp_types::CompletionItemKind::FIELD,
126 SymbolKind::Function => lsp_types::CompletionItemKind::FUNCTION,
127 SymbolKind::Impl => lsp_types::CompletionItemKind::TEXT,
128 SymbolKind::Label => lsp_types::CompletionItemKind::VARIABLE,
129 SymbolKind::LifetimeParam => lsp_types::CompletionItemKind::TYPE_PARAMETER,
130 SymbolKind::Local => lsp_types::CompletionItemKind::VARIABLE,
131 SymbolKind::Macro => lsp_types::CompletionItemKind::FUNCTION,
132 SymbolKind::Module => lsp_types::CompletionItemKind::MODULE,
133 SymbolKind::SelfParam => lsp_types::CompletionItemKind::VALUE,
134 SymbolKind::SelfType => lsp_types::CompletionItemKind::TYPE_PARAMETER,
135 SymbolKind::Static => lsp_types::CompletionItemKind::VALUE,
136 SymbolKind::Struct => lsp_types::CompletionItemKind::STRUCT,
137 SymbolKind::Trait => lsp_types::CompletionItemKind::INTERFACE,
138 SymbolKind::TraitAlias => lsp_types::CompletionItemKind::INTERFACE,
139 SymbolKind::TypeAlias => lsp_types::CompletionItemKind::STRUCT,
140 SymbolKind::TypeParam => lsp_types::CompletionItemKind::TYPE_PARAMETER,
141 SymbolKind::Union => lsp_types::CompletionItemKind::STRUCT,
142 SymbolKind::ValueParam => lsp_types::CompletionItemKind::VALUE,
143 SymbolKind::Variant => lsp_types::CompletionItemKind::ENUM_MEMBER,
144 SymbolKind::BuiltinAttr => lsp_types::CompletionItemKind::FUNCTION,
145 SymbolKind::ToolModule => lsp_types::CompletionItemKind::MODULE,
146 },
147 }
148 }
149
text_edit(line_index: &LineIndex, indel: Indel) -> lsp_types::TextEdit150 pub(crate) fn text_edit(line_index: &LineIndex, indel: Indel) -> lsp_types::TextEdit {
151 let range = range(line_index, indel.delete);
152 let new_text = match line_index.endings {
153 LineEndings::Unix => indel.insert,
154 LineEndings::Dos => indel.insert.replace('\n', "\r\n"),
155 };
156 lsp_types::TextEdit { range, new_text }
157 }
158
completion_text_edit( line_index: &LineIndex, insert_replace_support: Option<lsp_types::Position>, indel: Indel, ) -> lsp_types::CompletionTextEdit159 pub(crate) fn completion_text_edit(
160 line_index: &LineIndex,
161 insert_replace_support: Option<lsp_types::Position>,
162 indel: Indel,
163 ) -> lsp_types::CompletionTextEdit {
164 let text_edit = text_edit(line_index, indel);
165 match insert_replace_support {
166 Some(cursor_pos) => lsp_types::InsertReplaceEdit {
167 new_text: text_edit.new_text,
168 insert: lsp_types::Range { start: text_edit.range.start, end: cursor_pos },
169 replace: text_edit.range,
170 }
171 .into(),
172 None => text_edit.into(),
173 }
174 }
175
snippet_text_edit( line_index: &LineIndex, is_snippet: bool, indel: Indel, ) -> lsp_ext::SnippetTextEdit176 pub(crate) fn snippet_text_edit(
177 line_index: &LineIndex,
178 is_snippet: bool,
179 indel: Indel,
180 ) -> lsp_ext::SnippetTextEdit {
181 let text_edit = text_edit(line_index, indel);
182 let insert_text_format =
183 if is_snippet { Some(lsp_types::InsertTextFormat::SNIPPET) } else { None };
184 lsp_ext::SnippetTextEdit {
185 range: text_edit.range,
186 new_text: text_edit.new_text,
187 insert_text_format,
188 annotation_id: None,
189 }
190 }
191
text_edit_vec( line_index: &LineIndex, text_edit: TextEdit, ) -> Vec<lsp_types::TextEdit>192 pub(crate) fn text_edit_vec(
193 line_index: &LineIndex,
194 text_edit: TextEdit,
195 ) -> Vec<lsp_types::TextEdit> {
196 text_edit.into_iter().map(|indel| self::text_edit(line_index, indel)).collect()
197 }
198
snippet_text_edit_vec( line_index: &LineIndex, is_snippet: bool, text_edit: TextEdit, ) -> Vec<lsp_ext::SnippetTextEdit>199 pub(crate) fn snippet_text_edit_vec(
200 line_index: &LineIndex,
201 is_snippet: bool,
202 text_edit: TextEdit,
203 ) -> Vec<lsp_ext::SnippetTextEdit> {
204 text_edit
205 .into_iter()
206 .map(|indel| self::snippet_text_edit(line_index, is_snippet, indel))
207 .collect()
208 }
209
completion_items( config: &Config, line_index: &LineIndex, tdpp: lsp_types::TextDocumentPositionParams, items: Vec<CompletionItem>, ) -> Vec<lsp_types::CompletionItem>210 pub(crate) fn completion_items(
211 config: &Config,
212 line_index: &LineIndex,
213 tdpp: lsp_types::TextDocumentPositionParams,
214 items: Vec<CompletionItem>,
215 ) -> Vec<lsp_types::CompletionItem> {
216 let max_relevance = items.iter().map(|it| it.relevance.score()).max().unwrap_or_default();
217 let mut res = Vec::with_capacity(items.len());
218 for item in items {
219 completion_item(&mut res, config, line_index, &tdpp, max_relevance, item);
220 }
221
222 if let Some(limit) = config.completion().limit {
223 res.sort_by(|item1, item2| item1.sort_text.cmp(&item2.sort_text));
224 res.truncate(limit);
225 }
226
227 res
228 }
229
completion_item( acc: &mut Vec<lsp_types::CompletionItem>, config: &Config, line_index: &LineIndex, tdpp: &lsp_types::TextDocumentPositionParams, max_relevance: u32, item: CompletionItem, )230 fn completion_item(
231 acc: &mut Vec<lsp_types::CompletionItem>,
232 config: &Config,
233 line_index: &LineIndex,
234 tdpp: &lsp_types::TextDocumentPositionParams,
235 max_relevance: u32,
236 item: CompletionItem,
237 ) {
238 let insert_replace_support = config.insert_replace_support().then_some(tdpp.position);
239 let ref_match = item.ref_match();
240 let lookup = item.lookup().to_string();
241
242 let mut additional_text_edits = Vec::new();
243
244 // LSP does not allow arbitrary edits in completion, so we have to do a
245 // non-trivial mapping here.
246 let text_edit = {
247 let mut text_edit = None;
248 let source_range = item.source_range;
249 for indel in item.text_edit {
250 if indel.delete.contains_range(source_range) {
251 // Extract this indel as the main edit
252 text_edit = Some(if indel.delete == source_range {
253 self::completion_text_edit(line_index, insert_replace_support, indel.clone())
254 } else {
255 assert!(source_range.end() == indel.delete.end());
256 let range1 = TextRange::new(indel.delete.start(), source_range.start());
257 let range2 = source_range;
258 let indel1 = Indel::delete(range1);
259 let indel2 = Indel::replace(range2, indel.insert.clone());
260 additional_text_edits.push(self::text_edit(line_index, indel1));
261 self::completion_text_edit(line_index, insert_replace_support, indel2)
262 })
263 } else {
264 assert!(source_range.intersect(indel.delete).is_none());
265 let text_edit = self::text_edit(line_index, indel.clone());
266 additional_text_edits.push(text_edit);
267 }
268 }
269 text_edit.unwrap()
270 };
271
272 let insert_text_format = item.is_snippet.then_some(lsp_types::InsertTextFormat::SNIPPET);
273 let tags = item.deprecated.then(|| vec![lsp_types::CompletionItemTag::DEPRECATED]);
274 let command = if item.trigger_call_info && config.client_commands().trigger_parameter_hints {
275 Some(command::trigger_parameter_hints())
276 } else {
277 None
278 };
279
280 let mut lsp_item = lsp_types::CompletionItem {
281 label: item.label.to_string(),
282 detail: item.detail,
283 filter_text: Some(lookup),
284 kind: Some(completion_item_kind(item.kind)),
285 text_edit: Some(text_edit),
286 additional_text_edits: Some(additional_text_edits),
287 documentation: item.documentation.map(documentation),
288 deprecated: Some(item.deprecated),
289 tags,
290 command,
291 insert_text_format,
292 ..Default::default()
293 };
294
295 if config.completion_label_details_support() {
296 lsp_item.label_details = Some(lsp_types::CompletionItemLabelDetails {
297 detail: None,
298 description: lsp_item.detail.clone(),
299 });
300 }
301
302 set_score(&mut lsp_item, max_relevance, item.relevance);
303
304 if config.completion().enable_imports_on_the_fly {
305 if !item.import_to_add.is_empty() {
306 let imports: Vec<_> = item
307 .import_to_add
308 .into_iter()
309 .filter_map(|(import_path, import_name)| {
310 Some(lsp_ext::CompletionImport {
311 full_import_path: import_path,
312 imported_name: import_name,
313 })
314 })
315 .collect();
316 if !imports.is_empty() {
317 let data = lsp_ext::CompletionResolveData { position: tdpp.clone(), imports };
318 lsp_item.data = Some(to_value(data).unwrap());
319 }
320 }
321 }
322
323 if let Some((label, indel, relevance)) = ref_match {
324 let mut lsp_item_with_ref = lsp_types::CompletionItem { label, ..lsp_item.clone() };
325 lsp_item_with_ref
326 .additional_text_edits
327 .get_or_insert_with(Default::default)
328 .push(self::text_edit(line_index, indel));
329 set_score(&mut lsp_item_with_ref, max_relevance, relevance);
330 acc.push(lsp_item_with_ref);
331 };
332
333 acc.push(lsp_item);
334
335 fn set_score(
336 res: &mut lsp_types::CompletionItem,
337 max_relevance: u32,
338 relevance: CompletionRelevance,
339 ) {
340 if relevance.is_relevant() && relevance.score() == max_relevance {
341 res.preselect = Some(true);
342 }
343 // The relevance needs to be inverted to come up with a sort score
344 // because the client will sort ascending.
345 let sort_score = relevance.score() ^ 0xFF_FF_FF_FF;
346 // Zero pad the string to ensure values can be properly sorted
347 // by the client. Hex format is used because it is easier to
348 // visually compare very large values, which the sort text
349 // tends to be since it is the opposite of the score.
350 res.sort_text = Some(format!("{sort_score:08x}"));
351 }
352 }
353
signature_help( call_info: SignatureHelp, config: CallInfoConfig, label_offsets: bool, ) -> lsp_types::SignatureHelp354 pub(crate) fn signature_help(
355 call_info: SignatureHelp,
356 config: CallInfoConfig,
357 label_offsets: bool,
358 ) -> lsp_types::SignatureHelp {
359 let (label, parameters) = match (config.params_only, label_offsets) {
360 (concise, false) => {
361 let params = call_info
362 .parameter_labels()
363 .map(|label| lsp_types::ParameterInformation {
364 label: lsp_types::ParameterLabel::Simple(label.to_string()),
365 documentation: None,
366 })
367 .collect::<Vec<_>>();
368 let label =
369 if concise { call_info.parameter_labels().join(", ") } else { call_info.signature };
370 (label, params)
371 }
372 (false, true) => {
373 let params = call_info
374 .parameter_ranges()
375 .iter()
376 .map(|it| {
377 let start = call_info.signature[..it.start().into()].chars().count() as u32;
378 let end = call_info.signature[..it.end().into()].chars().count() as u32;
379 [start, end]
380 })
381 .map(|label_offsets| lsp_types::ParameterInformation {
382 label: lsp_types::ParameterLabel::LabelOffsets(label_offsets),
383 documentation: None,
384 })
385 .collect::<Vec<_>>();
386 (call_info.signature, params)
387 }
388 (true, true) => {
389 let mut params = Vec::new();
390 let mut label = String::new();
391 let mut first = true;
392 for param in call_info.parameter_labels() {
393 if !first {
394 label.push_str(", ");
395 }
396 first = false;
397 let start = label.chars().count() as u32;
398 label.push_str(param);
399 let end = label.chars().count() as u32;
400 params.push(lsp_types::ParameterInformation {
401 label: lsp_types::ParameterLabel::LabelOffsets([start, end]),
402 documentation: None,
403 });
404 }
405
406 (label, params)
407 }
408 };
409
410 let documentation = call_info.doc.filter(|_| config.docs).map(|doc| {
411 lsp_types::Documentation::MarkupContent(lsp_types::MarkupContent {
412 kind: lsp_types::MarkupKind::Markdown,
413 value: crate::markdown::format_docs(&doc),
414 })
415 });
416
417 let active_parameter = call_info.active_parameter.map(|it| it as u32);
418
419 let signature = lsp_types::SignatureInformation {
420 label,
421 documentation,
422 parameters: Some(parameters),
423 active_parameter,
424 };
425 lsp_types::SignatureHelp {
426 signatures: vec![signature],
427 active_signature: Some(0),
428 active_parameter,
429 }
430 }
431
inlay_hint( snap: &GlobalStateSnapshot, line_index: &LineIndex, inlay_hint: InlayHint, ) -> Cancellable<lsp_types::InlayHint>432 pub(crate) fn inlay_hint(
433 snap: &GlobalStateSnapshot,
434 line_index: &LineIndex,
435 inlay_hint: InlayHint,
436 ) -> Cancellable<lsp_types::InlayHint> {
437 let (label, tooltip) = inlay_hint_label(snap, inlay_hint.label)?;
438
439 Ok(lsp_types::InlayHint {
440 position: match inlay_hint.position {
441 ide::InlayHintPosition::Before => position(line_index, inlay_hint.range.start()),
442 ide::InlayHintPosition::After => position(line_index, inlay_hint.range.end()),
443 },
444 padding_left: Some(inlay_hint.pad_left),
445 padding_right: Some(inlay_hint.pad_right),
446 kind: match inlay_hint.kind {
447 InlayKind::Parameter => Some(lsp_types::InlayHintKind::PARAMETER),
448 InlayKind::Type | InlayKind::Chaining => Some(lsp_types::InlayHintKind::TYPE),
449 _ => None,
450 },
451 text_edits: inlay_hint.text_edit.map(|it| text_edit_vec(line_index, it)),
452 data: None,
453 tooltip,
454 label,
455 })
456 }
457
inlay_hint_label( snap: &GlobalStateSnapshot, mut label: InlayHintLabel, ) -> Cancellable<(lsp_types::InlayHintLabel, Option<lsp_types::InlayHintTooltip>)>458 fn inlay_hint_label(
459 snap: &GlobalStateSnapshot,
460 mut label: InlayHintLabel,
461 ) -> Cancellable<(lsp_types::InlayHintLabel, Option<lsp_types::InlayHintTooltip>)> {
462 let res = match &*label.parts {
463 [InlayHintLabelPart { linked_location: None, .. }] => {
464 let InlayHintLabelPart { text, tooltip, .. } = label.parts.pop().unwrap();
465 (
466 lsp_types::InlayHintLabel::String(text),
467 match tooltip {
468 Some(ide::InlayTooltip::String(s)) => {
469 Some(lsp_types::InlayHintTooltip::String(s))
470 }
471 Some(ide::InlayTooltip::Markdown(s)) => {
472 Some(lsp_types::InlayHintTooltip::MarkupContent(lsp_types::MarkupContent {
473 kind: lsp_types::MarkupKind::Markdown,
474 value: s,
475 }))
476 }
477 None => None,
478 },
479 )
480 }
481 _ => {
482 let parts = label
483 .parts
484 .into_iter()
485 .map(|part| {
486 part.linked_location.map(|range| location(snap, range)).transpose().map(
487 |location| lsp_types::InlayHintLabelPart {
488 value: part.text,
489 tooltip: match part.tooltip {
490 Some(ide::InlayTooltip::String(s)) => {
491 Some(lsp_types::InlayHintLabelPartTooltip::String(s))
492 }
493 Some(ide::InlayTooltip::Markdown(s)) => {
494 Some(lsp_types::InlayHintLabelPartTooltip::MarkupContent(
495 lsp_types::MarkupContent {
496 kind: lsp_types::MarkupKind::Markdown,
497 value: s,
498 },
499 ))
500 }
501 None => None,
502 },
503 location,
504 command: None,
505 },
506 )
507 })
508 .collect::<Cancellable<_>>()?;
509 (lsp_types::InlayHintLabel::LabelParts(parts), None)
510 }
511 };
512 Ok(res)
513 }
514
515 static TOKEN_RESULT_COUNTER: AtomicU32 = AtomicU32::new(1);
516
semantic_tokens( text: &str, line_index: &LineIndex, highlights: Vec<HlRange>, semantics_tokens_augments_syntax_tokens: bool, non_standard_tokens: bool, ) -> lsp_types::SemanticTokens517 pub(crate) fn semantic_tokens(
518 text: &str,
519 line_index: &LineIndex,
520 highlights: Vec<HlRange>,
521 semantics_tokens_augments_syntax_tokens: bool,
522 non_standard_tokens: bool,
523 ) -> lsp_types::SemanticTokens {
524 let id = TOKEN_RESULT_COUNTER.fetch_add(1, Ordering::SeqCst).to_string();
525 let mut builder = semantic_tokens::SemanticTokensBuilder::new(id);
526
527 for highlight_range in highlights {
528 if highlight_range.highlight.is_empty() {
529 continue;
530 }
531
532 if semantics_tokens_augments_syntax_tokens {
533 match highlight_range.highlight.tag {
534 HlTag::BoolLiteral
535 | HlTag::ByteLiteral
536 | HlTag::CharLiteral
537 | HlTag::Comment
538 | HlTag::Keyword
539 | HlTag::NumericLiteral
540 | HlTag::Operator(_)
541 | HlTag::Punctuation(_)
542 | HlTag::StringLiteral
543 | HlTag::None
544 if highlight_range.highlight.mods.is_empty() =>
545 {
546 continue
547 }
548 _ => (),
549 }
550 }
551
552 let (mut ty, mut mods) = semantic_token_type_and_modifiers(highlight_range.highlight);
553
554 if !non_standard_tokens {
555 ty = match standard_fallback_type(ty) {
556 Some(ty) => ty,
557 None => continue,
558 };
559 mods.standard_fallback();
560 }
561 let token_index = semantic_tokens::type_index(ty);
562 let modifier_bitset = mods.0;
563
564 for mut text_range in line_index.index.lines(highlight_range.range) {
565 if text[text_range].ends_with('\n') {
566 text_range =
567 TextRange::new(text_range.start(), text_range.end() - TextSize::of('\n'));
568 }
569 let range = range(line_index, text_range);
570 builder.push(range, token_index, modifier_bitset);
571 }
572 }
573
574 builder.build()
575 }
576
semantic_token_delta( previous: &lsp_types::SemanticTokens, current: &lsp_types::SemanticTokens, ) -> lsp_types::SemanticTokensDelta577 pub(crate) fn semantic_token_delta(
578 previous: &lsp_types::SemanticTokens,
579 current: &lsp_types::SemanticTokens,
580 ) -> lsp_types::SemanticTokensDelta {
581 let result_id = current.result_id.clone();
582 let edits = semantic_tokens::diff_tokens(&previous.data, ¤t.data);
583 lsp_types::SemanticTokensDelta { result_id, edits }
584 }
585
semantic_token_type_and_modifiers( highlight: Highlight, ) -> (lsp_types::SemanticTokenType, semantic_tokens::ModifierSet)586 fn semantic_token_type_and_modifiers(
587 highlight: Highlight,
588 ) -> (lsp_types::SemanticTokenType, semantic_tokens::ModifierSet) {
589 let mut mods = semantic_tokens::ModifierSet::default();
590 let type_ = match highlight.tag {
591 HlTag::Symbol(symbol) => match symbol {
592 SymbolKind::Attribute => semantic_tokens::DECORATOR,
593 SymbolKind::Derive => semantic_tokens::DERIVE,
594 SymbolKind::DeriveHelper => semantic_tokens::DERIVE_HELPER,
595 SymbolKind::Module => semantic_tokens::NAMESPACE,
596 SymbolKind::Impl => semantic_tokens::TYPE_ALIAS,
597 SymbolKind::Field => semantic_tokens::PROPERTY,
598 SymbolKind::TypeParam => semantic_tokens::TYPE_PARAMETER,
599 SymbolKind::ConstParam => semantic_tokens::CONST_PARAMETER,
600 SymbolKind::LifetimeParam => semantic_tokens::LIFETIME,
601 SymbolKind::Label => semantic_tokens::LABEL,
602 SymbolKind::ValueParam => semantic_tokens::PARAMETER,
603 SymbolKind::SelfParam => semantic_tokens::SELF_KEYWORD,
604 SymbolKind::SelfType => semantic_tokens::SELF_TYPE_KEYWORD,
605 SymbolKind::Local => semantic_tokens::VARIABLE,
606 SymbolKind::Function => {
607 if highlight.mods.contains(HlMod::Associated) {
608 semantic_tokens::METHOD
609 } else {
610 semantic_tokens::FUNCTION
611 }
612 }
613 SymbolKind::Const => {
614 mods |= semantic_tokens::CONSTANT;
615 mods |= semantic_tokens::STATIC;
616 semantic_tokens::VARIABLE
617 }
618 SymbolKind::Static => {
619 mods |= semantic_tokens::STATIC;
620 semantic_tokens::VARIABLE
621 }
622 SymbolKind::Struct => semantic_tokens::STRUCT,
623 SymbolKind::Enum => semantic_tokens::ENUM,
624 SymbolKind::Variant => semantic_tokens::ENUM_MEMBER,
625 SymbolKind::Union => semantic_tokens::UNION,
626 SymbolKind::TypeAlias => semantic_tokens::TYPE_ALIAS,
627 SymbolKind::Trait => semantic_tokens::INTERFACE,
628 SymbolKind::TraitAlias => semantic_tokens::INTERFACE,
629 SymbolKind::Macro => semantic_tokens::MACRO,
630 SymbolKind::BuiltinAttr => semantic_tokens::BUILTIN_ATTRIBUTE,
631 SymbolKind::ToolModule => semantic_tokens::TOOL_MODULE,
632 },
633 HlTag::AttributeBracket => semantic_tokens::ATTRIBUTE_BRACKET,
634 HlTag::BoolLiteral => semantic_tokens::BOOLEAN,
635 HlTag::BuiltinType => semantic_tokens::BUILTIN_TYPE,
636 HlTag::ByteLiteral | HlTag::NumericLiteral => semantic_tokens::NUMBER,
637 HlTag::CharLiteral => semantic_tokens::CHAR,
638 HlTag::Comment => semantic_tokens::COMMENT,
639 HlTag::EscapeSequence => semantic_tokens::ESCAPE_SEQUENCE,
640 HlTag::FormatSpecifier => semantic_tokens::FORMAT_SPECIFIER,
641 HlTag::Keyword => semantic_tokens::KEYWORD,
642 HlTag::None => semantic_tokens::GENERIC,
643 HlTag::Operator(op) => match op {
644 HlOperator::Bitwise => semantic_tokens::BITWISE,
645 HlOperator::Arithmetic => semantic_tokens::ARITHMETIC,
646 HlOperator::Logical => semantic_tokens::LOGICAL,
647 HlOperator::Comparison => semantic_tokens::COMPARISON,
648 HlOperator::Other => semantic_tokens::OPERATOR,
649 },
650 HlTag::StringLiteral => semantic_tokens::STRING,
651 HlTag::UnresolvedReference => semantic_tokens::UNRESOLVED_REFERENCE,
652 HlTag::Punctuation(punct) => match punct {
653 HlPunct::Bracket => semantic_tokens::BRACKET,
654 HlPunct::Brace => semantic_tokens::BRACE,
655 HlPunct::Parenthesis => semantic_tokens::PARENTHESIS,
656 HlPunct::Angle => semantic_tokens::ANGLE,
657 HlPunct::Comma => semantic_tokens::COMMA,
658 HlPunct::Dot => semantic_tokens::DOT,
659 HlPunct::Colon => semantic_tokens::COLON,
660 HlPunct::Semi => semantic_tokens::SEMICOLON,
661 HlPunct::Other => semantic_tokens::PUNCTUATION,
662 HlPunct::MacroBang => semantic_tokens::MACRO_BANG,
663 },
664 };
665
666 for modifier in highlight.mods.iter() {
667 let modifier = match modifier {
668 HlMod::Associated => continue,
669 HlMod::Async => semantic_tokens::ASYNC,
670 HlMod::Attribute => semantic_tokens::ATTRIBUTE_MODIFIER,
671 HlMod::Callable => semantic_tokens::CALLABLE,
672 HlMod::Consuming => semantic_tokens::CONSUMING,
673 HlMod::ControlFlow => semantic_tokens::CONTROL_FLOW,
674 HlMod::CrateRoot => semantic_tokens::CRATE_ROOT,
675 HlMod::DefaultLibrary => semantic_tokens::DEFAULT_LIBRARY,
676 HlMod::Definition => semantic_tokens::DECLARATION,
677 HlMod::Documentation => semantic_tokens::DOCUMENTATION,
678 HlMod::Injected => semantic_tokens::INJECTED,
679 HlMod::IntraDocLink => semantic_tokens::INTRA_DOC_LINK,
680 HlMod::Library => semantic_tokens::LIBRARY,
681 HlMod::Macro => semantic_tokens::MACRO_MODIFIER,
682 HlMod::Mutable => semantic_tokens::MUTABLE,
683 HlMod::Public => semantic_tokens::PUBLIC,
684 HlMod::Reference => semantic_tokens::REFERENCE,
685 HlMod::Static => semantic_tokens::STATIC,
686 HlMod::Trait => semantic_tokens::TRAIT_MODIFIER,
687 HlMod::Unsafe => semantic_tokens::UNSAFE,
688 };
689 mods |= modifier;
690 }
691
692 (type_, mods)
693 }
694
folding_range( text: &str, line_index: &LineIndex, line_folding_only: bool, fold: Fold, ) -> lsp_types::FoldingRange695 pub(crate) fn folding_range(
696 text: &str,
697 line_index: &LineIndex,
698 line_folding_only: bool,
699 fold: Fold,
700 ) -> lsp_types::FoldingRange {
701 let kind = match fold.kind {
702 FoldKind::Comment => Some(lsp_types::FoldingRangeKind::Comment),
703 FoldKind::Imports => Some(lsp_types::FoldingRangeKind::Imports),
704 FoldKind::Region => Some(lsp_types::FoldingRangeKind::Region),
705 FoldKind::Mods
706 | FoldKind::Block
707 | FoldKind::ArgList
708 | FoldKind::Consts
709 | FoldKind::Statics
710 | FoldKind::WhereClause
711 | FoldKind::ReturnType
712 | FoldKind::Array
713 | FoldKind::MatchArm => None,
714 };
715
716 let range = range(line_index, fold.range);
717
718 if line_folding_only {
719 // Clients with line_folding_only == true (such as VSCode) will fold the whole end line
720 // even if it contains text not in the folding range. To prevent that we exclude
721 // range.end.line from the folding region if there is more text after range.end
722 // on the same line.
723 let has_more_text_on_end_line = text[TextRange::new(fold.range.end(), TextSize::of(text))]
724 .chars()
725 .take_while(|it| *it != '\n')
726 .any(|it| !it.is_whitespace());
727
728 let end_line = if has_more_text_on_end_line {
729 range.end.line.saturating_sub(1)
730 } else {
731 range.end.line
732 };
733
734 lsp_types::FoldingRange {
735 start_line: range.start.line,
736 start_character: None,
737 end_line,
738 end_character: None,
739 kind,
740 collapsed_text: None,
741 }
742 } else {
743 lsp_types::FoldingRange {
744 start_line: range.start.line,
745 start_character: Some(range.start.character),
746 end_line: range.end.line,
747 end_character: Some(range.end.character),
748 kind,
749 collapsed_text: None,
750 }
751 }
752 }
753
url(snap: &GlobalStateSnapshot, file_id: FileId) -> lsp_types::Url754 pub(crate) fn url(snap: &GlobalStateSnapshot, file_id: FileId) -> lsp_types::Url {
755 snap.file_id_to_url(file_id)
756 }
757
758 /// Returns a `Url` object from a given path, will lowercase drive letters if present.
759 /// This will only happen when processing windows paths.
760 ///
761 /// When processing non-windows path, this is essentially the same as `Url::from_file_path`.
url_from_abs_path(path: &AbsPath) -> lsp_types::Url762 pub(crate) fn url_from_abs_path(path: &AbsPath) -> lsp_types::Url {
763 let url = lsp_types::Url::from_file_path(path).unwrap();
764 match path.as_ref().components().next() {
765 Some(path::Component::Prefix(prefix))
766 if matches!(prefix.kind(), path::Prefix::Disk(_) | path::Prefix::VerbatimDisk(_)) =>
767 {
768 // Need to lowercase driver letter
769 }
770 _ => return url,
771 }
772
773 let driver_letter_range = {
774 let (scheme, drive_letter, _rest) = match url.as_str().splitn(3, ':').collect_tuple() {
775 Some(it) => it,
776 None => return url,
777 };
778 let start = scheme.len() + ':'.len_utf8();
779 start..(start + drive_letter.len())
780 };
781
782 // Note: lowercasing the `path` itself doesn't help, the `Url::parse`
783 // machinery *also* canonicalizes the drive letter. So, just massage the
784 // string in place.
785 let mut url: String = url.into();
786 url[driver_letter_range].make_ascii_lowercase();
787 lsp_types::Url::parse(&url).unwrap()
788 }
789
optional_versioned_text_document_identifier( snap: &GlobalStateSnapshot, file_id: FileId, ) -> lsp_types::OptionalVersionedTextDocumentIdentifier790 pub(crate) fn optional_versioned_text_document_identifier(
791 snap: &GlobalStateSnapshot,
792 file_id: FileId,
793 ) -> lsp_types::OptionalVersionedTextDocumentIdentifier {
794 let url = url(snap, file_id);
795 let version = snap.url_file_version(&url);
796 lsp_types::OptionalVersionedTextDocumentIdentifier { uri: url, version }
797 }
798
location( snap: &GlobalStateSnapshot, frange: FileRange, ) -> Cancellable<lsp_types::Location>799 pub(crate) fn location(
800 snap: &GlobalStateSnapshot,
801 frange: FileRange,
802 ) -> Cancellable<lsp_types::Location> {
803 let url = url(snap, frange.file_id);
804 let line_index = snap.file_line_index(frange.file_id)?;
805 let range = range(&line_index, frange.range);
806 let loc = lsp_types::Location::new(url, range);
807 Ok(loc)
808 }
809
810 /// Prefer using `location_link`, if the client has the cap.
location_from_nav( snap: &GlobalStateSnapshot, nav: NavigationTarget, ) -> Cancellable<lsp_types::Location>811 pub(crate) fn location_from_nav(
812 snap: &GlobalStateSnapshot,
813 nav: NavigationTarget,
814 ) -> Cancellable<lsp_types::Location> {
815 let url = url(snap, nav.file_id);
816 let line_index = snap.file_line_index(nav.file_id)?;
817 let range = range(&line_index, nav.full_range);
818 let loc = lsp_types::Location::new(url, range);
819 Ok(loc)
820 }
821
location_link( snap: &GlobalStateSnapshot, src: Option<FileRange>, target: NavigationTarget, ) -> Cancellable<lsp_types::LocationLink>822 pub(crate) fn location_link(
823 snap: &GlobalStateSnapshot,
824 src: Option<FileRange>,
825 target: NavigationTarget,
826 ) -> Cancellable<lsp_types::LocationLink> {
827 let origin_selection_range = match src {
828 Some(src) => {
829 let line_index = snap.file_line_index(src.file_id)?;
830 let range = range(&line_index, src.range);
831 Some(range)
832 }
833 None => None,
834 };
835 let (target_uri, target_range, target_selection_range) = location_info(snap, target)?;
836 let res = lsp_types::LocationLink {
837 origin_selection_range,
838 target_uri,
839 target_range,
840 target_selection_range,
841 };
842 Ok(res)
843 }
844
location_info( snap: &GlobalStateSnapshot, target: NavigationTarget, ) -> Cancellable<(lsp_types::Url, lsp_types::Range, lsp_types::Range)>845 fn location_info(
846 snap: &GlobalStateSnapshot,
847 target: NavigationTarget,
848 ) -> Cancellable<(lsp_types::Url, lsp_types::Range, lsp_types::Range)> {
849 let line_index = snap.file_line_index(target.file_id)?;
850
851 let target_uri = url(snap, target.file_id);
852 let target_range = range(&line_index, target.full_range);
853 let target_selection_range =
854 target.focus_range.map(|it| range(&line_index, it)).unwrap_or(target_range);
855 Ok((target_uri, target_range, target_selection_range))
856 }
857
goto_definition_response( snap: &GlobalStateSnapshot, src: Option<FileRange>, targets: Vec<NavigationTarget>, ) -> Cancellable<lsp_types::GotoDefinitionResponse>858 pub(crate) fn goto_definition_response(
859 snap: &GlobalStateSnapshot,
860 src: Option<FileRange>,
861 targets: Vec<NavigationTarget>,
862 ) -> Cancellable<lsp_types::GotoDefinitionResponse> {
863 if snap.config.location_link() {
864 let links = targets
865 .into_iter()
866 .map(|nav| location_link(snap, src, nav))
867 .collect::<Cancellable<Vec<_>>>()?;
868 Ok(links.into())
869 } else {
870 let locations = targets
871 .into_iter()
872 .map(|nav| {
873 location(snap, FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
874 })
875 .collect::<Cancellable<Vec<_>>>()?;
876 Ok(locations.into())
877 }
878 }
879
outside_workspace_annotation_id() -> String880 fn outside_workspace_annotation_id() -> String {
881 String::from("OutsideWorkspace")
882 }
883
snippet_text_document_edit( snap: &GlobalStateSnapshot, is_snippet: bool, file_id: FileId, edit: TextEdit, ) -> Cancellable<lsp_ext::SnippetTextDocumentEdit>884 pub(crate) fn snippet_text_document_edit(
885 snap: &GlobalStateSnapshot,
886 is_snippet: bool,
887 file_id: FileId,
888 edit: TextEdit,
889 ) -> Cancellable<lsp_ext::SnippetTextDocumentEdit> {
890 let text_document = optional_versioned_text_document_identifier(snap, file_id);
891 let line_index = snap.file_line_index(file_id)?;
892 let mut edits: Vec<_> =
893 edit.into_iter().map(|it| snippet_text_edit(&line_index, is_snippet, it)).collect();
894
895 if snap.analysis.is_library_file(file_id)? && snap.config.change_annotation_support() {
896 for edit in &mut edits {
897 edit.annotation_id = Some(outside_workspace_annotation_id())
898 }
899 }
900 Ok(lsp_ext::SnippetTextDocumentEdit { text_document, edits })
901 }
902
snippet_text_document_ops( snap: &GlobalStateSnapshot, file_system_edit: FileSystemEdit, ) -> Cancellable<Vec<lsp_ext::SnippetDocumentChangeOperation>>903 pub(crate) fn snippet_text_document_ops(
904 snap: &GlobalStateSnapshot,
905 file_system_edit: FileSystemEdit,
906 ) -> Cancellable<Vec<lsp_ext::SnippetDocumentChangeOperation>> {
907 let mut ops = Vec::new();
908 match file_system_edit {
909 FileSystemEdit::CreateFile { dst, initial_contents } => {
910 let uri = snap.anchored_path(&dst);
911 let create_file = lsp_types::ResourceOp::Create(lsp_types::CreateFile {
912 uri: uri.clone(),
913 options: None,
914 annotation_id: None,
915 });
916 ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(create_file));
917 if !initial_contents.is_empty() {
918 let text_document =
919 lsp_types::OptionalVersionedTextDocumentIdentifier { uri, version: None };
920 let text_edit = lsp_ext::SnippetTextEdit {
921 range: lsp_types::Range::default(),
922 new_text: initial_contents,
923 insert_text_format: Some(lsp_types::InsertTextFormat::PLAIN_TEXT),
924 annotation_id: None,
925 };
926 let edit_file =
927 lsp_ext::SnippetTextDocumentEdit { text_document, edits: vec![text_edit] };
928 ops.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit_file));
929 }
930 }
931 FileSystemEdit::MoveFile { src, dst } => {
932 let old_uri = snap.file_id_to_url(src);
933 let new_uri = snap.anchored_path(&dst);
934 let mut rename_file =
935 lsp_types::RenameFile { old_uri, new_uri, options: None, annotation_id: None };
936 if snap.analysis.is_library_file(src).ok() == Some(true)
937 && snap.config.change_annotation_support()
938 {
939 rename_file.annotation_id = Some(outside_workspace_annotation_id())
940 }
941 ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(lsp_types::ResourceOp::Rename(
942 rename_file,
943 )))
944 }
945 FileSystemEdit::MoveDir { src, src_id, dst } => {
946 let old_uri = snap.anchored_path(&src);
947 let new_uri = snap.anchored_path(&dst);
948 let mut rename_file =
949 lsp_types::RenameFile { old_uri, new_uri, options: None, annotation_id: None };
950 if snap.analysis.is_library_file(src_id).ok() == Some(true)
951 && snap.config.change_annotation_support()
952 {
953 rename_file.annotation_id = Some(outside_workspace_annotation_id())
954 }
955 ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(lsp_types::ResourceOp::Rename(
956 rename_file,
957 )))
958 }
959 }
960 Ok(ops)
961 }
962
snippet_workspace_edit( snap: &GlobalStateSnapshot, source_change: SourceChange, ) -> Cancellable<lsp_ext::SnippetWorkspaceEdit>963 pub(crate) fn snippet_workspace_edit(
964 snap: &GlobalStateSnapshot,
965 source_change: SourceChange,
966 ) -> Cancellable<lsp_ext::SnippetWorkspaceEdit> {
967 let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new();
968
969 for op in source_change.file_system_edits {
970 let ops = snippet_text_document_ops(snap, op)?;
971 document_changes.extend_from_slice(&ops);
972 }
973 for (file_id, edit) in source_change.source_file_edits {
974 let edit = snippet_text_document_edit(snap, source_change.is_snippet, file_id, edit)?;
975 document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit));
976 }
977 let mut workspace_edit = lsp_ext::SnippetWorkspaceEdit {
978 changes: None,
979 document_changes: Some(document_changes),
980 change_annotations: None,
981 };
982 if snap.config.change_annotation_support() {
983 workspace_edit.change_annotations = Some(
984 once((
985 outside_workspace_annotation_id(),
986 lsp_types::ChangeAnnotation {
987 label: String::from("Edit outside of the workspace"),
988 needs_confirmation: Some(true),
989 description: Some(String::from(
990 "This edit lies outside of the workspace and may affect dependencies",
991 )),
992 },
993 ))
994 .collect(),
995 )
996 }
997 Ok(workspace_edit)
998 }
999
workspace_edit( snap: &GlobalStateSnapshot, source_change: SourceChange, ) -> Cancellable<lsp_types::WorkspaceEdit>1000 pub(crate) fn workspace_edit(
1001 snap: &GlobalStateSnapshot,
1002 source_change: SourceChange,
1003 ) -> Cancellable<lsp_types::WorkspaceEdit> {
1004 assert!(!source_change.is_snippet);
1005 snippet_workspace_edit(snap, source_change).map(|it| it.into())
1006 }
1007
1008 impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit {
from(snippet_workspace_edit: lsp_ext::SnippetWorkspaceEdit) -> lsp_types::WorkspaceEdit1009 fn from(snippet_workspace_edit: lsp_ext::SnippetWorkspaceEdit) -> lsp_types::WorkspaceEdit {
1010 lsp_types::WorkspaceEdit {
1011 changes: None,
1012 document_changes: snippet_workspace_edit.document_changes.map(|changes| {
1013 lsp_types::DocumentChanges::Operations(
1014 changes
1015 .into_iter()
1016 .map(|change| match change {
1017 lsp_ext::SnippetDocumentChangeOperation::Op(op) => {
1018 lsp_types::DocumentChangeOperation::Op(op)
1019 }
1020 lsp_ext::SnippetDocumentChangeOperation::Edit(edit) => {
1021 lsp_types::DocumentChangeOperation::Edit(
1022 lsp_types::TextDocumentEdit {
1023 text_document: edit.text_document,
1024 edits: edit.edits.into_iter().map(From::from).collect(),
1025 },
1026 )
1027 }
1028 })
1029 .collect(),
1030 )
1031 }),
1032 change_annotations: snippet_workspace_edit.change_annotations,
1033 }
1034 }
1035 }
1036
1037 impl From<lsp_ext::SnippetTextEdit>
1038 for lsp_types::OneOf<lsp_types::TextEdit, lsp_types::AnnotatedTextEdit>
1039 {
1040 fn from(
1041 lsp_ext::SnippetTextEdit { annotation_id, insert_text_format:_, new_text, range }: lsp_ext::SnippetTextEdit,
1042 ) -> Self {
1043 match annotation_id {
1044 Some(annotation_id) => lsp_types::OneOf::Right(lsp_types::AnnotatedTextEdit {
1045 text_edit: lsp_types::TextEdit { range, new_text },
1046 annotation_id,
1047 }),
1048 None => lsp_types::OneOf::Left(lsp_types::TextEdit { range, new_text }),
1049 }
1050 }
1051 }
1052
call_hierarchy_item( snap: &GlobalStateSnapshot, target: NavigationTarget, ) -> Cancellable<lsp_types::CallHierarchyItem>1053 pub(crate) fn call_hierarchy_item(
1054 snap: &GlobalStateSnapshot,
1055 target: NavigationTarget,
1056 ) -> Cancellable<lsp_types::CallHierarchyItem> {
1057 let name = target.name.to_string();
1058 let detail = target.description.clone();
1059 let kind = target.kind.map(symbol_kind).unwrap_or(lsp_types::SymbolKind::FUNCTION);
1060 let (uri, range, selection_range) = location_info(snap, target)?;
1061 Ok(lsp_types::CallHierarchyItem {
1062 name,
1063 kind,
1064 tags: None,
1065 detail,
1066 uri,
1067 range,
1068 selection_range,
1069 data: None,
1070 })
1071 }
1072
code_action_kind(kind: AssistKind) -> lsp_types::CodeActionKind1073 pub(crate) fn code_action_kind(kind: AssistKind) -> lsp_types::CodeActionKind {
1074 match kind {
1075 AssistKind::None | AssistKind::Generate => lsp_types::CodeActionKind::EMPTY,
1076 AssistKind::QuickFix => lsp_types::CodeActionKind::QUICKFIX,
1077 AssistKind::Refactor => lsp_types::CodeActionKind::REFACTOR,
1078 AssistKind::RefactorExtract => lsp_types::CodeActionKind::REFACTOR_EXTRACT,
1079 AssistKind::RefactorInline => lsp_types::CodeActionKind::REFACTOR_INLINE,
1080 AssistKind::RefactorRewrite => lsp_types::CodeActionKind::REFACTOR_REWRITE,
1081 }
1082 }
1083
code_action( snap: &GlobalStateSnapshot, assist: Assist, resolve_data: Option<(usize, lsp_types::CodeActionParams)>, ) -> Cancellable<lsp_ext::CodeAction>1084 pub(crate) fn code_action(
1085 snap: &GlobalStateSnapshot,
1086 assist: Assist,
1087 resolve_data: Option<(usize, lsp_types::CodeActionParams)>,
1088 ) -> Cancellable<lsp_ext::CodeAction> {
1089 let mut res = lsp_ext::CodeAction {
1090 title: assist.label.to_string(),
1091 group: assist.group.filter(|_| snap.config.code_action_group()).map(|gr| gr.0),
1092 kind: Some(code_action_kind(assist.id.1)),
1093 edit: None,
1094 is_preferred: None,
1095 data: None,
1096 command: None,
1097 };
1098
1099 if assist.trigger_signature_help && snap.config.client_commands().trigger_parameter_hints {
1100 res.command = Some(command::trigger_parameter_hints());
1101 }
1102
1103 match (assist.source_change, resolve_data) {
1104 (Some(it), _) => res.edit = Some(snippet_workspace_edit(snap, it)?),
1105 (None, Some((index, code_action_params))) => {
1106 res.data = Some(lsp_ext::CodeActionData {
1107 id: format!("{}:{}:{index}", assist.id.0, assist.id.1.name()),
1108 code_action_params,
1109 });
1110 }
1111 (None, None) => {
1112 stdx::never!("assist should always be resolved if client can't do lazy resolving")
1113 }
1114 };
1115 Ok(res)
1116 }
1117
runnable( snap: &GlobalStateSnapshot, runnable: Runnable, ) -> Cancellable<lsp_ext::Runnable>1118 pub(crate) fn runnable(
1119 snap: &GlobalStateSnapshot,
1120 runnable: Runnable,
1121 ) -> Cancellable<lsp_ext::Runnable> {
1122 let config = snap.config.runnables();
1123 let spec = CargoTargetSpec::for_file(snap, runnable.nav.file_id)?;
1124 let workspace_root = spec.as_ref().map(|it| it.workspace_root.clone());
1125 let target = spec.as_ref().map(|s| s.target.clone());
1126 let (cargo_args, executable_args) =
1127 CargoTargetSpec::runnable_args(snap, spec, &runnable.kind, &runnable.cfg);
1128 let label = runnable.label(target);
1129 let location = location_link(snap, None, runnable.nav)?;
1130
1131 Ok(lsp_ext::Runnable {
1132 label,
1133 location: Some(location),
1134 kind: lsp_ext::RunnableKind::Cargo,
1135 args: lsp_ext::CargoRunnable {
1136 workspace_root: workspace_root.map(|it| it.into()),
1137 override_cargo: config.override_cargo,
1138 cargo_args,
1139 cargo_extra_args: config.cargo_extra_args,
1140 executable_args,
1141 expect_test: None,
1142 },
1143 })
1144 }
1145
code_lens( acc: &mut Vec<lsp_types::CodeLens>, snap: &GlobalStateSnapshot, annotation: Annotation, ) -> Cancellable<()>1146 pub(crate) fn code_lens(
1147 acc: &mut Vec<lsp_types::CodeLens>,
1148 snap: &GlobalStateSnapshot,
1149 annotation: Annotation,
1150 ) -> Cancellable<()> {
1151 let client_commands_config = snap.config.client_commands();
1152 match annotation.kind {
1153 AnnotationKind::Runnable(run) => {
1154 let line_index = snap.file_line_index(run.nav.file_id)?;
1155 let annotation_range = range(&line_index, annotation.range);
1156
1157 let title = run.title();
1158 let can_debug = match run.kind {
1159 ide::RunnableKind::DocTest { .. } => false,
1160 ide::RunnableKind::TestMod { .. }
1161 | ide::RunnableKind::Test { .. }
1162 | ide::RunnableKind::Bench { .. }
1163 | ide::RunnableKind::Bin => true,
1164 };
1165 let r = runnable(snap, run)?;
1166
1167 let lens_config = snap.config.lens();
1168 if lens_config.run
1169 && client_commands_config.run_single
1170 && r.args.workspace_root.is_some()
1171 {
1172 let command = command::run_single(&r, &title);
1173 acc.push(lsp_types::CodeLens {
1174 range: annotation_range,
1175 command: Some(command),
1176 data: None,
1177 })
1178 }
1179 if lens_config.debug && can_debug && client_commands_config.debug_single {
1180 let command = command::debug_single(&r);
1181 acc.push(lsp_types::CodeLens {
1182 range: annotation_range,
1183 command: Some(command),
1184 data: None,
1185 })
1186 }
1187 if lens_config.interpret {
1188 let command = command::interpret_single(&r);
1189 acc.push(lsp_types::CodeLens {
1190 range: annotation_range,
1191 command: Some(command),
1192 data: None,
1193 })
1194 }
1195 }
1196 AnnotationKind::HasImpls { pos: file_range, data } => {
1197 if !client_commands_config.show_reference {
1198 return Ok(());
1199 }
1200 let line_index = snap.file_line_index(file_range.file_id)?;
1201 let annotation_range = range(&line_index, annotation.range);
1202 let url = url(snap, file_range.file_id);
1203
1204 let id = lsp_types::TextDocumentIdentifier { uri: url.clone() };
1205
1206 let doc_pos = lsp_types::TextDocumentPositionParams::new(id, annotation_range.start);
1207
1208 let goto_params = lsp_types::request::GotoImplementationParams {
1209 text_document_position_params: doc_pos,
1210 work_done_progress_params: Default::default(),
1211 partial_result_params: Default::default(),
1212 };
1213
1214 let command = data.map(|ranges| {
1215 let locations: Vec<lsp_types::Location> = ranges
1216 .into_iter()
1217 .filter_map(|target| {
1218 location(
1219 snap,
1220 FileRange { file_id: target.file_id, range: target.full_range },
1221 )
1222 .ok()
1223 })
1224 .collect();
1225
1226 command::show_references(
1227 implementation_title(locations.len()),
1228 &url,
1229 annotation_range.start,
1230 locations,
1231 )
1232 });
1233
1234 acc.push(lsp_types::CodeLens {
1235 range: annotation_range,
1236 command,
1237 data: (|| {
1238 let version = snap.url_file_version(&url)?;
1239 Some(
1240 to_value(lsp_ext::CodeLensResolveData {
1241 version,
1242 kind: lsp_ext::CodeLensResolveDataKind::Impls(goto_params),
1243 })
1244 .unwrap(),
1245 )
1246 })(),
1247 })
1248 }
1249 AnnotationKind::HasReferences { pos: file_range, data } => {
1250 if !client_commands_config.show_reference {
1251 return Ok(());
1252 }
1253 let line_index = snap.file_line_index(file_range.file_id)?;
1254 let annotation_range = range(&line_index, annotation.range);
1255 let url = url(snap, file_range.file_id);
1256
1257 let id = lsp_types::TextDocumentIdentifier { uri: url.clone() };
1258
1259 let doc_pos = lsp_types::TextDocumentPositionParams::new(id, annotation_range.start);
1260
1261 let command = data.map(|ranges| {
1262 let locations: Vec<lsp_types::Location> =
1263 ranges.into_iter().filter_map(|range| location(snap, range).ok()).collect();
1264
1265 command::show_references(
1266 reference_title(locations.len()),
1267 &url,
1268 annotation_range.start,
1269 locations,
1270 )
1271 });
1272
1273 acc.push(lsp_types::CodeLens {
1274 range: annotation_range,
1275 command,
1276 data: (|| {
1277 let version = snap.url_file_version(&url)?;
1278 Some(
1279 to_value(lsp_ext::CodeLensResolveData {
1280 version,
1281 kind: lsp_ext::CodeLensResolveDataKind::References(doc_pos),
1282 })
1283 .unwrap(),
1284 )
1285 })(),
1286 })
1287 }
1288 }
1289 Ok(())
1290 }
1291
1292 pub(crate) mod command {
1293 use ide::{FileRange, NavigationTarget};
1294 use serde_json::to_value;
1295
1296 use crate::{
1297 global_state::GlobalStateSnapshot,
1298 lsp_ext,
1299 to_proto::{location, location_link},
1300 };
1301
show_references( title: String, uri: &lsp_types::Url, position: lsp_types::Position, locations: Vec<lsp_types::Location>, ) -> lsp_types::Command1302 pub(crate) fn show_references(
1303 title: String,
1304 uri: &lsp_types::Url,
1305 position: lsp_types::Position,
1306 locations: Vec<lsp_types::Location>,
1307 ) -> lsp_types::Command {
1308 // We cannot use the 'editor.action.showReferences' command directly
1309 // because that command requires vscode types which we convert in the handler
1310 // on the client side.
1311
1312 lsp_types::Command {
1313 title,
1314 command: "rust-analyzer.showReferences".into(),
1315 arguments: Some(vec![
1316 to_value(uri).unwrap(),
1317 to_value(position).unwrap(),
1318 to_value(locations).unwrap(),
1319 ]),
1320 }
1321 }
1322
run_single(runnable: &lsp_ext::Runnable, title: &str) -> lsp_types::Command1323 pub(crate) fn run_single(runnable: &lsp_ext::Runnable, title: &str) -> lsp_types::Command {
1324 lsp_types::Command {
1325 title: title.to_string(),
1326 command: "rust-analyzer.runSingle".into(),
1327 arguments: Some(vec![to_value(runnable).unwrap()]),
1328 }
1329 }
1330
debug_single(runnable: &lsp_ext::Runnable) -> lsp_types::Command1331 pub(crate) fn debug_single(runnable: &lsp_ext::Runnable) -> lsp_types::Command {
1332 lsp_types::Command {
1333 title: "Debug".into(),
1334 command: "rust-analyzer.debugSingle".into(),
1335 arguments: Some(vec![to_value(runnable).unwrap()]),
1336 }
1337 }
1338
interpret_single(_runnable: &lsp_ext::Runnable) -> lsp_types::Command1339 pub(crate) fn interpret_single(_runnable: &lsp_ext::Runnable) -> lsp_types::Command {
1340 lsp_types::Command {
1341 title: "Interpret".into(),
1342 command: "rust-analyzer.interpretFunction".into(),
1343 // FIXME: use the `_runnable` here.
1344 arguments: Some(vec![]),
1345 }
1346 }
1347
goto_location( snap: &GlobalStateSnapshot, nav: &NavigationTarget, ) -> Option<lsp_types::Command>1348 pub(crate) fn goto_location(
1349 snap: &GlobalStateSnapshot,
1350 nav: &NavigationTarget,
1351 ) -> Option<lsp_types::Command> {
1352 let value = if snap.config.location_link() {
1353 let link = location_link(snap, None, nav.clone()).ok()?;
1354 to_value(link).ok()?
1355 } else {
1356 let range = FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() };
1357 let location = location(snap, range).ok()?;
1358 to_value(location).ok()?
1359 };
1360
1361 Some(lsp_types::Command {
1362 title: nav.name.to_string(),
1363 command: "rust-analyzer.gotoLocation".into(),
1364 arguments: Some(vec![value]),
1365 })
1366 }
1367
trigger_parameter_hints() -> lsp_types::Command1368 pub(crate) fn trigger_parameter_hints() -> lsp_types::Command {
1369 lsp_types::Command {
1370 title: "triggerParameterHints".into(),
1371 command: "rust-analyzer.triggerParameterHints".into(),
1372 arguments: None,
1373 }
1374 }
1375 }
1376
implementation_title(count: usize) -> String1377 pub(crate) fn implementation_title(count: usize) -> String {
1378 if count == 1 {
1379 "1 implementation".into()
1380 } else {
1381 format!("{count} implementations")
1382 }
1383 }
1384
reference_title(count: usize) -> String1385 pub(crate) fn reference_title(count: usize) -> String {
1386 if count == 1 {
1387 "1 reference".into()
1388 } else {
1389 format!("{count} references")
1390 }
1391 }
1392
markup_content( markup: Markup, kind: ide::HoverDocFormat, ) -> lsp_types::MarkupContent1393 pub(crate) fn markup_content(
1394 markup: Markup,
1395 kind: ide::HoverDocFormat,
1396 ) -> lsp_types::MarkupContent {
1397 let kind = match kind {
1398 ide::HoverDocFormat::Markdown => lsp_types::MarkupKind::Markdown,
1399 ide::HoverDocFormat::PlainText => lsp_types::MarkupKind::PlainText,
1400 };
1401 let value = crate::markdown::format_docs(markup.as_str());
1402 lsp_types::MarkupContent { kind, value }
1403 }
1404
rename_error(err: RenameError) -> crate::LspError1405 pub(crate) fn rename_error(err: RenameError) -> crate::LspError {
1406 // This is wrong, but we don't have a better alternative I suppose?
1407 // https://github.com/microsoft/language-server-protocol/issues/1341
1408 invalid_params_error(err.to_string())
1409 }
1410
1411 #[cfg(test)]
1412 mod tests {
1413 use ide::{Analysis, FilePosition};
1414 use test_utils::extract_offset;
1415 use triomphe::Arc;
1416
1417 use super::*;
1418
1419 #[test]
conv_fold_line_folding_only_fixup()1420 fn conv_fold_line_folding_only_fixup() {
1421 let text = r#"mod a;
1422 mod b;
1423 mod c;
1424
1425 fn main() {
1426 if cond {
1427 a::do_a();
1428 } else {
1429 b::do_b();
1430 }
1431 }"#;
1432
1433 let (analysis, file_id) = Analysis::from_single_file(text.to_string());
1434 let folds = analysis.folding_ranges(file_id).unwrap();
1435 assert_eq!(folds.len(), 4);
1436
1437 let line_index = LineIndex {
1438 index: Arc::new(ide::LineIndex::new(text)),
1439 endings: LineEndings::Unix,
1440 encoding: PositionEncoding::Utf8,
1441 };
1442 let converted: Vec<lsp_types::FoldingRange> =
1443 folds.into_iter().map(|it| folding_range(text, &line_index, true, it)).collect();
1444
1445 let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)];
1446 assert_eq!(converted.len(), expected_lines.len());
1447 for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) {
1448 assert_eq!(folding_range.start_line, *start_line);
1449 assert_eq!(folding_range.start_character, None);
1450 assert_eq!(folding_range.end_line, *end_line);
1451 assert_eq!(folding_range.end_character, None);
1452 }
1453 }
1454
1455 #[test]
calling_function_with_ignored_code_in_signature()1456 fn calling_function_with_ignored_code_in_signature() {
1457 let text = r#"
1458 fn foo() {
1459 bar($0);
1460 }
1461 /// ```
1462 /// # use crate::bar;
1463 /// bar(5);
1464 /// ```
1465 fn bar(_: usize) {}
1466 "#;
1467
1468 let (offset, text) = extract_offset(text);
1469 let (analysis, file_id) = Analysis::from_single_file(text);
1470 let help = signature_help(
1471 analysis.signature_help(FilePosition { file_id, offset }).unwrap().unwrap(),
1472 CallInfoConfig { params_only: false, docs: true },
1473 false,
1474 );
1475 let docs = match &help.signatures[help.active_signature.unwrap() as usize].documentation {
1476 Some(lsp_types::Documentation::MarkupContent(content)) => &content.value,
1477 _ => panic!("documentation contains markup"),
1478 };
1479 assert!(docs.contains("bar(5)"));
1480 assert!(!docs.contains("use crate::bar"));
1481 }
1482
1483 // `Url` is not able to parse windows paths on unix machines.
1484 #[test]
1485 #[cfg(target_os = "windows")]
test_lowercase_drive_letter()1486 fn test_lowercase_drive_letter() {
1487 use std::path::Path;
1488
1489 let url = url_from_abs_path(Path::new("C:\\Test").try_into().unwrap());
1490 assert_eq!(url.to_string(), "file:///c:/Test");
1491
1492 let url = url_from_abs_path(Path::new(r#"\\localhost\C$\my_dir"#).try_into().unwrap());
1493 assert_eq!(url.to_string(), "file://localhost/C$/my_dir");
1494 }
1495 }
1496