ra_ap_rust_analyzer/lsp/
to_proto.rs

1//! Conversion of rust-analyzer specific types to lsp_types equivalents.
2use std::{
3    iter::once,
4    mem,
5    ops::Not as _,
6    sync::atomic::{AtomicU32, Ordering},
7};
8
9use base64::{Engine, prelude::BASE64_STANDARD};
10use ide::{
11    Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionFieldsToResolve,
12    CompletionItem, CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange,
13    FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel,
14    InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayKind, LazyProperty,
15    Markup, NavigationTarget, ReferenceCategory, RenameError, Runnable, Severity, SignatureHelp,
16    SnippetEdit, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize,
17    UpdateTest,
18};
19use ide_db::{FxHasher, assists, rust_doc::format_docs, source_change::ChangeAnnotationId};
20use itertools::Itertools;
21use paths::{Utf8Component, Utf8Prefix};
22use semver::VersionReq;
23use serde_json::to_value;
24use vfs::AbsPath;
25
26use crate::{
27    config::{CallInfoConfig, Config},
28    global_state::GlobalStateSnapshot,
29    line_index::{LineEndings, LineIndex, PositionEncoding},
30    lsp::{
31        LspError, completion_item_hash,
32        ext::ShellRunnableArgs,
33        semantic_tokens::{self, standard_fallback_type},
34        utils::invalid_params_error,
35    },
36    lsp_ext::{self, SnippetTextEdit},
37    target_spec::{CargoTargetSpec, TargetSpec},
38};
39
40pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position {
41    let line_col = line_index.index.line_col(offset);
42    match line_index.encoding {
43        PositionEncoding::Utf8 => lsp_types::Position::new(line_col.line, line_col.col),
44        PositionEncoding::Wide(enc) => {
45            let line_col = line_index.index.to_wide(enc, line_col).unwrap();
46            lsp_types::Position::new(line_col.line, line_col.col)
47        }
48    }
49}
50
51pub(crate) fn range(line_index: &LineIndex, range: TextRange) -> lsp_types::Range {
52    let start = position(line_index, range.start());
53    let end = position(line_index, range.end());
54    lsp_types::Range::new(start, end)
55}
56
57pub(crate) fn symbol_kind(symbol_kind: SymbolKind) -> lsp_types::SymbolKind {
58    match symbol_kind {
59        SymbolKind::Function => lsp_types::SymbolKind::FUNCTION,
60        SymbolKind::Method => lsp_types::SymbolKind::METHOD,
61        SymbolKind::Struct => lsp_types::SymbolKind::STRUCT,
62        SymbolKind::Enum => lsp_types::SymbolKind::ENUM,
63        SymbolKind::Variant => lsp_types::SymbolKind::ENUM_MEMBER,
64        SymbolKind::Trait | SymbolKind::TraitAlias => lsp_types::SymbolKind::INTERFACE,
65        SymbolKind::Macro
66        | SymbolKind::ProcMacro
67        | SymbolKind::BuiltinAttr
68        | SymbolKind::Attribute
69        | SymbolKind::Derive
70        | SymbolKind::DeriveHelper => lsp_types::SymbolKind::FUNCTION,
71        SymbolKind::Module | SymbolKind::ToolModule => lsp_types::SymbolKind::MODULE,
72        SymbolKind::TypeAlias | SymbolKind::TypeParam | SymbolKind::SelfType => {
73            lsp_types::SymbolKind::TYPE_PARAMETER
74        }
75        SymbolKind::Field => lsp_types::SymbolKind::FIELD,
76        SymbolKind::Static => lsp_types::SymbolKind::CONSTANT,
77        SymbolKind::Const => lsp_types::SymbolKind::CONSTANT,
78        SymbolKind::ConstParam => lsp_types::SymbolKind::CONSTANT,
79        SymbolKind::Impl => lsp_types::SymbolKind::OBJECT,
80        SymbolKind::Local
81        | SymbolKind::SelfParam
82        | SymbolKind::LifetimeParam
83        | SymbolKind::ValueParam
84        | SymbolKind::Label => lsp_types::SymbolKind::VARIABLE,
85        SymbolKind::Union => lsp_types::SymbolKind::STRUCT,
86        SymbolKind::InlineAsmRegOrRegClass => lsp_types::SymbolKind::VARIABLE,
87    }
88}
89
90pub(crate) fn structure_node_kind(kind: StructureNodeKind) -> lsp_types::SymbolKind {
91    match kind {
92        StructureNodeKind::SymbolKind(symbol) => symbol_kind(symbol),
93        StructureNodeKind::Region => lsp_types::SymbolKind::NAMESPACE,
94        StructureNodeKind::ExternBlock => lsp_types::SymbolKind::NAMESPACE,
95    }
96}
97
98pub(crate) fn document_highlight_kind(
99    category: ReferenceCategory,
100) -> Option<lsp_types::DocumentHighlightKind> {
101    if category.contains(ReferenceCategory::WRITE) {
102        return Some(lsp_types::DocumentHighlightKind::WRITE);
103    }
104    if category.contains(ReferenceCategory::READ) {
105        return Some(lsp_types::DocumentHighlightKind::READ);
106    }
107    None
108}
109
110pub(crate) fn diagnostic_severity(severity: Severity) -> lsp_types::DiagnosticSeverity {
111    match severity {
112        Severity::Error => lsp_types::DiagnosticSeverity::ERROR,
113        Severity::Warning => lsp_types::DiagnosticSeverity::WARNING,
114        Severity::WeakWarning => lsp_types::DiagnosticSeverity::HINT,
115        // unreachable
116        Severity::Allow => lsp_types::DiagnosticSeverity::INFORMATION,
117    }
118}
119
120pub(crate) fn documentation(documentation: Documentation) -> lsp_types::Documentation {
121    let value = format_docs(&documentation);
122    let markup_content = lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value };
123    lsp_types::Documentation::MarkupContent(markup_content)
124}
125
126pub(crate) fn completion_item_kind(
127    completion_item_kind: CompletionItemKind,
128) -> lsp_types::CompletionItemKind {
129    match completion_item_kind {
130        CompletionItemKind::Binding => lsp_types::CompletionItemKind::VARIABLE,
131        CompletionItemKind::BuiltinType => lsp_types::CompletionItemKind::STRUCT,
132        CompletionItemKind::InferredType => lsp_types::CompletionItemKind::SNIPPET,
133        CompletionItemKind::Keyword => lsp_types::CompletionItemKind::KEYWORD,
134        CompletionItemKind::Snippet => lsp_types::CompletionItemKind::SNIPPET,
135        CompletionItemKind::UnresolvedReference => lsp_types::CompletionItemKind::REFERENCE,
136        CompletionItemKind::Expression => lsp_types::CompletionItemKind::SNIPPET,
137        CompletionItemKind::SymbolKind(symbol) => match symbol {
138            SymbolKind::Attribute => lsp_types::CompletionItemKind::FUNCTION,
139            SymbolKind::Method => lsp_types::CompletionItemKind::METHOD,
140            SymbolKind::Const => lsp_types::CompletionItemKind::CONSTANT,
141            SymbolKind::ConstParam => lsp_types::CompletionItemKind::TYPE_PARAMETER,
142            SymbolKind::Derive => lsp_types::CompletionItemKind::FUNCTION,
143            SymbolKind::DeriveHelper => lsp_types::CompletionItemKind::FUNCTION,
144            SymbolKind::Enum => lsp_types::CompletionItemKind::ENUM,
145            SymbolKind::Field => lsp_types::CompletionItemKind::FIELD,
146            SymbolKind::Function => lsp_types::CompletionItemKind::FUNCTION,
147            SymbolKind::Impl => lsp_types::CompletionItemKind::TEXT,
148            SymbolKind::Label => lsp_types::CompletionItemKind::VARIABLE,
149            SymbolKind::LifetimeParam => lsp_types::CompletionItemKind::TYPE_PARAMETER,
150            SymbolKind::Local => lsp_types::CompletionItemKind::VARIABLE,
151            SymbolKind::Macro => lsp_types::CompletionItemKind::FUNCTION,
152            SymbolKind::ProcMacro => lsp_types::CompletionItemKind::FUNCTION,
153            SymbolKind::Module => lsp_types::CompletionItemKind::MODULE,
154            SymbolKind::SelfParam => lsp_types::CompletionItemKind::VALUE,
155            SymbolKind::SelfType => lsp_types::CompletionItemKind::TYPE_PARAMETER,
156            SymbolKind::Static => lsp_types::CompletionItemKind::VALUE,
157            SymbolKind::Struct => lsp_types::CompletionItemKind::STRUCT,
158            SymbolKind::Trait => lsp_types::CompletionItemKind::INTERFACE,
159            SymbolKind::TraitAlias => lsp_types::CompletionItemKind::INTERFACE,
160            SymbolKind::TypeAlias => lsp_types::CompletionItemKind::STRUCT,
161            SymbolKind::TypeParam => lsp_types::CompletionItemKind::TYPE_PARAMETER,
162            SymbolKind::Union => lsp_types::CompletionItemKind::STRUCT,
163            SymbolKind::ValueParam => lsp_types::CompletionItemKind::VALUE,
164            SymbolKind::Variant => lsp_types::CompletionItemKind::ENUM_MEMBER,
165            SymbolKind::BuiltinAttr => lsp_types::CompletionItemKind::FUNCTION,
166            SymbolKind::ToolModule => lsp_types::CompletionItemKind::MODULE,
167            SymbolKind::InlineAsmRegOrRegClass => lsp_types::CompletionItemKind::KEYWORD,
168        },
169    }
170}
171
172pub(crate) fn text_edit(line_index: &LineIndex, indel: Indel) -> lsp_types::TextEdit {
173    let range = range(line_index, indel.delete);
174    let new_text = match line_index.endings {
175        LineEndings::Unix => indel.insert,
176        LineEndings::Dos => indel.insert.replace('\n', "\r\n"),
177    };
178    lsp_types::TextEdit { range, new_text }
179}
180
181pub(crate) fn completion_text_edit(
182    line_index: &LineIndex,
183    insert_replace_support: Option<lsp_types::Position>,
184    indel: Indel,
185) -> lsp_types::CompletionTextEdit {
186    let text_edit = text_edit(line_index, indel);
187    match insert_replace_support {
188        Some(cursor_pos) => lsp_types::InsertReplaceEdit {
189            new_text: text_edit.new_text,
190            insert: lsp_types::Range { start: text_edit.range.start, end: cursor_pos },
191            replace: text_edit.range,
192        }
193        .into(),
194        None => text_edit.into(),
195    }
196}
197
198pub(crate) fn snippet_text_edit(
199    line_index: &LineIndex,
200    is_snippet: bool,
201    indel: Indel,
202    annotation: Option<ChangeAnnotationId>,
203    client_supports_annotations: bool,
204) -> lsp_ext::SnippetTextEdit {
205    let annotation_id = annotation.filter(|_| client_supports_annotations).map(|it| it.to_string());
206    let text_edit = text_edit(line_index, indel);
207    let insert_text_format =
208        if is_snippet { Some(lsp_types::InsertTextFormat::SNIPPET) } else { None };
209    lsp_ext::SnippetTextEdit {
210        range: text_edit.range,
211        new_text: text_edit.new_text,
212        insert_text_format,
213        annotation_id,
214    }
215}
216
217pub(crate) fn text_edit_vec(
218    line_index: &LineIndex,
219    text_edit: TextEdit,
220) -> Vec<lsp_types::TextEdit> {
221    text_edit.into_iter().map(|indel| self::text_edit(line_index, indel)).collect()
222}
223
224pub(crate) fn snippet_text_edit_vec(
225    line_index: &LineIndex,
226    is_snippet: bool,
227    text_edit: TextEdit,
228    clients_support_annotations: bool,
229) -> Vec<lsp_ext::SnippetTextEdit> {
230    let annotation = text_edit.change_annotation();
231    text_edit
232        .into_iter()
233        .map(|indel| {
234            self::snippet_text_edit(
235                line_index,
236                is_snippet,
237                indel,
238                annotation,
239                clients_support_annotations,
240            )
241        })
242        .collect()
243}
244
245pub(crate) fn completion_items(
246    config: &Config,
247    fields_to_resolve: &CompletionFieldsToResolve,
248    line_index: &LineIndex,
249    version: Option<i32>,
250    tdpp: lsp_types::TextDocumentPositionParams,
251    completion_trigger_character: Option<char>,
252    mut items: Vec<CompletionItem>,
253) -> Vec<lsp_types::CompletionItem> {
254    if config.completion_hide_deprecated() {
255        items.retain(|item| !item.deprecated);
256    }
257
258    let max_relevance = items.iter().map(|it| it.relevance.score()).max().unwrap_or_default();
259    let mut res = Vec::with_capacity(items.len());
260    for item in items {
261        completion_item(
262            &mut res,
263            config,
264            fields_to_resolve,
265            line_index,
266            version,
267            &tdpp,
268            max_relevance,
269            completion_trigger_character,
270            item,
271        );
272    }
273
274    if let Some(limit) = config.completion(None).limit {
275        res.sort_by(|item1, item2| item1.sort_text.cmp(&item2.sort_text));
276        res.truncate(limit);
277    }
278
279    res
280}
281
282fn completion_item(
283    acc: &mut Vec<lsp_types::CompletionItem>,
284    config: &Config,
285    fields_to_resolve: &CompletionFieldsToResolve,
286    line_index: &LineIndex,
287    version: Option<i32>,
288    tdpp: &lsp_types::TextDocumentPositionParams,
289    max_relevance: u32,
290    completion_trigger_character: Option<char>,
291    item: CompletionItem,
292) {
293    let insert_replace_support = config.insert_replace_support().then_some(tdpp.position);
294    let ref_match = item.ref_match();
295
296    let mut additional_text_edits = Vec::new();
297    let mut something_to_resolve = false;
298
299    let filter_text = if fields_to_resolve.resolve_filter_text {
300        something_to_resolve |= !item.lookup().is_empty();
301        None
302    } else {
303        Some(item.lookup().to_owned())
304    };
305
306    let text_edit = if fields_to_resolve.resolve_text_edit {
307        something_to_resolve |= true;
308        None
309    } else {
310        // LSP does not allow arbitrary edits in completion, so we have to do a
311        // non-trivial mapping here.
312        let mut text_edit = None;
313        let source_range = item.source_range;
314        for indel in &item.text_edit {
315            if indel.delete.contains_range(source_range) {
316                // Extract this indel as the main edit
317                text_edit = Some(if indel.delete == source_range {
318                    self::completion_text_edit(line_index, insert_replace_support, indel.clone())
319                } else {
320                    assert!(source_range.end() == indel.delete.end());
321                    let range1 = TextRange::new(indel.delete.start(), source_range.start());
322                    let range2 = source_range;
323                    let indel1 = Indel::delete(range1);
324                    let indel2 = Indel::replace(range2, indel.insert.clone());
325                    additional_text_edits.push(self::text_edit(line_index, indel1));
326                    self::completion_text_edit(line_index, insert_replace_support, indel2)
327                })
328            } else {
329                assert!(source_range.intersect(indel.delete).is_none());
330                let text_edit = self::text_edit(line_index, indel.clone());
331                additional_text_edits.push(text_edit);
332            }
333        }
334        Some(text_edit.unwrap())
335    };
336
337    let insert_text_format = item.is_snippet.then_some(lsp_types::InsertTextFormat::SNIPPET);
338    let tags = if fields_to_resolve.resolve_tags {
339        something_to_resolve |= item.deprecated;
340        None
341    } else {
342        item.deprecated.then(|| vec![lsp_types::CompletionItemTag::DEPRECATED])
343    };
344    let command = if item.trigger_call_info && config.client_commands().trigger_parameter_hints {
345        if fields_to_resolve.resolve_command {
346            something_to_resolve |= true;
347            None
348        } else {
349            Some(command::trigger_parameter_hints())
350        }
351    } else {
352        None
353    };
354
355    let detail = if fields_to_resolve.resolve_detail {
356        something_to_resolve |= item.detail.is_some();
357        None
358    } else {
359        item.detail.clone()
360    };
361
362    let documentation = if fields_to_resolve.resolve_documentation {
363        something_to_resolve |= item.documentation.is_some();
364        None
365    } else {
366        item.documentation.clone().map(documentation)
367    };
368
369    let mut lsp_item = lsp_types::CompletionItem {
370        label: item.label.primary.to_string(),
371        detail,
372        filter_text,
373        kind: Some(completion_item_kind(item.kind)),
374        text_edit,
375        additional_text_edits: additional_text_edits
376            .is_empty()
377            .not()
378            .then_some(additional_text_edits),
379        documentation,
380        deprecated: item.deprecated.then_some(item.deprecated),
381        tags,
382        command,
383        insert_text_format,
384        ..Default::default()
385    };
386
387    if config.completion_label_details_support() {
388        let has_label_details =
389            item.label.detail_left.is_some() || item.label.detail_right.is_some();
390        if fields_to_resolve.resolve_label_details {
391            something_to_resolve |= has_label_details;
392        } else if has_label_details {
393            lsp_item.label_details = Some(lsp_types::CompletionItemLabelDetails {
394                detail: item.label.detail_left.clone(),
395                description: item.label.detail_right.clone(),
396            });
397        }
398    } else if let Some(label_detail) = &item.label.detail_left {
399        lsp_item.label.push_str(label_detail.as_str());
400    }
401
402    set_score(&mut lsp_item, max_relevance, item.relevance);
403
404    let imports =
405        if config.completion(None).enable_imports_on_the_fly && !item.import_to_add.is_empty() {
406            item.import_to_add
407                .clone()
408                .into_iter()
409                .map(|import_path| lsp_ext::CompletionImport { full_import_path: import_path })
410                .collect()
411        } else {
412            Vec::new()
413        };
414    let (ref_resolve_data, resolve_data) = if something_to_resolve || !imports.is_empty() {
415        let ref_resolve_data = if ref_match.is_some() {
416            let ref_resolve_data = lsp_ext::CompletionResolveData {
417                position: tdpp.clone(),
418                imports: Vec::new(),
419                version,
420                trigger_character: completion_trigger_character,
421                for_ref: true,
422                hash: BASE64_STANDARD.encode(completion_item_hash(&item, true)),
423            };
424            Some(to_value(ref_resolve_data).unwrap())
425        } else {
426            None
427        };
428        let resolve_data = lsp_ext::CompletionResolveData {
429            position: tdpp.clone(),
430            imports,
431            version,
432            trigger_character: completion_trigger_character,
433            for_ref: false,
434            hash: BASE64_STANDARD.encode(completion_item_hash(&item, false)),
435        };
436        (ref_resolve_data, Some(to_value(resolve_data).unwrap()))
437    } else {
438        (None, None)
439    };
440
441    if let Some((label, indel, relevance)) = ref_match {
442        let mut lsp_item_with_ref =
443            lsp_types::CompletionItem { label, data: ref_resolve_data, ..lsp_item.clone() };
444        lsp_item_with_ref
445            .additional_text_edits
446            .get_or_insert_with(Default::default)
447            .push(self::text_edit(line_index, indel));
448        set_score(&mut lsp_item_with_ref, max_relevance, relevance);
449        acc.push(lsp_item_with_ref);
450    };
451
452    lsp_item.data = resolve_data;
453    acc.push(lsp_item);
454
455    fn set_score(
456        res: &mut lsp_types::CompletionItem,
457        max_relevance: u32,
458        relevance: CompletionRelevance,
459    ) {
460        if relevance.is_relevant() && relevance.score() == max_relevance {
461            res.preselect = Some(true);
462        }
463        // The relevance needs to be inverted to come up with a sort score
464        // because the client will sort ascending.
465        let sort_score = relevance.score() ^ 0xFF_FF_FF_FF;
466        // Zero pad the string to ensure values can be properly sorted
467        // by the client. Hex format is used because it is easier to
468        // visually compare very large values, which the sort text
469        // tends to be since it is the opposite of the score.
470        res.sort_text = Some(format!("{sort_score:08x}"));
471    }
472}
473
474pub(crate) fn signature_help(
475    call_info: SignatureHelp,
476    config: CallInfoConfig,
477    label_offsets: bool,
478) -> lsp_types::SignatureHelp {
479    let (label, parameters) = match (config.params_only, label_offsets) {
480        (concise, false) => {
481            let params = call_info
482                .parameter_labels()
483                .map(|label| lsp_types::ParameterInformation {
484                    label: lsp_types::ParameterLabel::Simple(label.to_owned()),
485                    documentation: None,
486                })
487                .collect::<Vec<_>>();
488            let label =
489                if concise { call_info.parameter_labels().join(", ") } else { call_info.signature };
490            (label, params)
491        }
492        (false, true) => {
493            let params = call_info
494                .parameter_ranges()
495                .iter()
496                .map(|it| {
497                    let start = call_info.signature[..it.start().into()].chars().count() as u32;
498                    let end = call_info.signature[..it.end().into()].chars().count() as u32;
499                    [start, end]
500                })
501                .map(|label_offsets| lsp_types::ParameterInformation {
502                    label: lsp_types::ParameterLabel::LabelOffsets(label_offsets),
503                    documentation: None,
504                })
505                .collect::<Vec<_>>();
506            (call_info.signature, params)
507        }
508        (true, true) => {
509            let mut params = Vec::new();
510            let mut label = String::new();
511            let mut first = true;
512            for param in call_info.parameter_labels() {
513                if !first {
514                    label.push_str(", ");
515                }
516                first = false;
517                let start = label.chars().count() as u32;
518                label.push_str(param);
519                let end = label.chars().count() as u32;
520                params.push(lsp_types::ParameterInformation {
521                    label: lsp_types::ParameterLabel::LabelOffsets([start, end]),
522                    documentation: None,
523                });
524            }
525
526            (label, params)
527        }
528    };
529
530    let documentation = call_info.doc.filter(|_| config.docs).map(|doc| {
531        lsp_types::Documentation::MarkupContent(lsp_types::MarkupContent {
532            kind: lsp_types::MarkupKind::Markdown,
533            value: format_docs(&doc),
534        })
535    });
536
537    let active_parameter = call_info.active_parameter.map(|it| it as u32);
538
539    let signature = lsp_types::SignatureInformation {
540        label,
541        documentation,
542        parameters: Some(parameters),
543        active_parameter,
544    };
545    lsp_types::SignatureHelp {
546        signatures: vec![signature],
547        active_signature: Some(0),
548        active_parameter,
549    }
550}
551
552pub(crate) fn inlay_hint(
553    snap: &GlobalStateSnapshot,
554    fields_to_resolve: &InlayFieldsToResolve,
555    line_index: &LineIndex,
556    file_id: FileId,
557    mut inlay_hint: InlayHint,
558) -> Cancellable<lsp_types::InlayHint> {
559    let hint_needs_resolve = |hint: &InlayHint| -> Option<TextRange> {
560        hint.resolve_parent.filter(|_| {
561            hint.text_edit.as_ref().is_some_and(LazyProperty::is_lazy)
562                || hint.label.parts.iter().any(|part| {
563                    part.linked_location.as_ref().is_some_and(LazyProperty::is_lazy)
564                        || part.tooltip.as_ref().is_some_and(LazyProperty::is_lazy)
565                })
566        })
567    };
568
569    let resolve_range_and_hash = hint_needs_resolve(&inlay_hint).map(|range| {
570        (
571            range,
572            std::hash::BuildHasher::hash_one(
573                &std::hash::BuildHasherDefault::<FxHasher>::default(),
574                &inlay_hint,
575            ),
576        )
577    });
578
579    let mut something_to_resolve = false;
580    let text_edits = inlay_hint
581        .text_edit
582        .take()
583        .and_then(|it| match it {
584            LazyProperty::Computed(it) => Some(it),
585            LazyProperty::Lazy => {
586                something_to_resolve |=
587                    snap.config.visual_studio_code_version().is_none_or(|version| {
588                        VersionReq::parse(">=1.86.0").unwrap().matches(version)
589                    }) && resolve_range_and_hash.is_some()
590                        && fields_to_resolve.resolve_text_edits;
591                None
592            }
593        })
594        .map(|it| text_edit_vec(line_index, it));
595    let (label, tooltip) = inlay_hint_label(
596        snap,
597        fields_to_resolve,
598        &mut something_to_resolve,
599        resolve_range_and_hash.is_some(),
600        inlay_hint.label,
601    )?;
602
603    let data = match resolve_range_and_hash {
604        Some((resolve_range, hash)) if something_to_resolve => Some(
605            to_value(lsp_ext::InlayHintResolveData {
606                file_id: file_id.index(),
607                hash: hash.to_string(),
608                version: snap.file_version(file_id),
609                resolve_range: range(line_index, resolve_range),
610            })
611            .unwrap(),
612        ),
613        _ => None,
614    };
615
616    Ok(lsp_types::InlayHint {
617        position: match inlay_hint.position {
618            ide::InlayHintPosition::Before => position(line_index, inlay_hint.range.start()),
619            ide::InlayHintPosition::After => position(line_index, inlay_hint.range.end()),
620        },
621        padding_left: Some(inlay_hint.pad_left),
622        padding_right: Some(inlay_hint.pad_right),
623        kind: match inlay_hint.kind {
624            InlayKind::Parameter | InlayKind::GenericParameter => {
625                Some(lsp_types::InlayHintKind::PARAMETER)
626            }
627            InlayKind::Type | InlayKind::Chaining => Some(lsp_types::InlayHintKind::TYPE),
628            _ => None,
629        },
630        text_edits,
631        data,
632        tooltip,
633        label,
634    })
635}
636
637fn inlay_hint_label(
638    snap: &GlobalStateSnapshot,
639    fields_to_resolve: &InlayFieldsToResolve,
640    something_to_resolve: &mut bool,
641    needs_resolve: bool,
642    mut label: InlayHintLabel,
643) -> Cancellable<(lsp_types::InlayHintLabel, Option<lsp_types::InlayHintTooltip>)> {
644    let (label, tooltip) = match &*label.parts {
645        [InlayHintLabelPart { linked_location: None, .. }] => {
646            let InlayHintLabelPart { text, tooltip, .. } = label.parts.pop().unwrap();
647            let tooltip = tooltip.and_then(|it| match it {
648                LazyProperty::Computed(it) => Some(it),
649                LazyProperty::Lazy => {
650                    *something_to_resolve |=
651                        needs_resolve && fields_to_resolve.resolve_hint_tooltip;
652                    None
653                }
654            });
655            let hint_tooltip = match tooltip {
656                Some(ide::InlayTooltip::String(s)) => Some(lsp_types::InlayHintTooltip::String(s)),
657                Some(ide::InlayTooltip::Markdown(s)) => {
658                    Some(lsp_types::InlayHintTooltip::MarkupContent(lsp_types::MarkupContent {
659                        kind: lsp_types::MarkupKind::Markdown,
660                        value: s,
661                    }))
662                }
663                None => None,
664            };
665            (lsp_types::InlayHintLabel::String(text), hint_tooltip)
666        }
667        _ => {
668            let parts = label
669                .parts
670                .into_iter()
671                .map(|part| {
672                    let tooltip = part.tooltip.and_then(|it| match it {
673                        LazyProperty::Computed(it) => Some(it),
674                        LazyProperty::Lazy => {
675                            *something_to_resolve |= fields_to_resolve.resolve_label_tooltip;
676                            None
677                        }
678                    });
679                    let tooltip = match tooltip {
680                        Some(ide::InlayTooltip::String(s)) => {
681                            Some(lsp_types::InlayHintLabelPartTooltip::String(s))
682                        }
683                        Some(ide::InlayTooltip::Markdown(s)) => {
684                            Some(lsp_types::InlayHintLabelPartTooltip::MarkupContent(
685                                lsp_types::MarkupContent {
686                                    kind: lsp_types::MarkupKind::Markdown,
687                                    value: s,
688                                },
689                            ))
690                        }
691                        None => None,
692                    };
693                    let location = part
694                        .linked_location
695                        .and_then(|it| match it {
696                            LazyProperty::Computed(it) => Some(it),
697                            LazyProperty::Lazy => {
698                                *something_to_resolve |= fields_to_resolve.resolve_label_location;
699                                None
700                            }
701                        })
702                        .map(|range| location(snap, range))
703                        .transpose()?;
704                    Ok(lsp_types::InlayHintLabelPart {
705                        value: part.text,
706                        tooltip,
707                        location,
708                        command: None,
709                    })
710                })
711                .collect::<Cancellable<_>>()?;
712            (lsp_types::InlayHintLabel::LabelParts(parts), None)
713        }
714    };
715    Ok((label, tooltip))
716}
717
718static TOKEN_RESULT_COUNTER: AtomicU32 = AtomicU32::new(1);
719
720pub(crate) fn semantic_tokens(
721    text: &str,
722    line_index: &LineIndex,
723    highlights: Vec<HlRange>,
724    semantics_tokens_augments_syntax_tokens: bool,
725    non_standard_tokens: bool,
726) -> lsp_types::SemanticTokens {
727    let id = TOKEN_RESULT_COUNTER.fetch_add(1, Ordering::SeqCst).to_string();
728    let mut builder = semantic_tokens::SemanticTokensBuilder::new(id);
729
730    for highlight_range in highlights {
731        if highlight_range.highlight.is_empty() {
732            continue;
733        }
734
735        if semantics_tokens_augments_syntax_tokens {
736            match highlight_range.highlight.tag {
737                HlTag::BoolLiteral
738                | HlTag::ByteLiteral
739                | HlTag::CharLiteral
740                | HlTag::Comment
741                | HlTag::Keyword
742                | HlTag::NumericLiteral
743                | HlTag::Operator(_)
744                | HlTag::Punctuation(_)
745                | HlTag::StringLiteral
746                | HlTag::None
747                    if highlight_range.highlight.mods.is_empty() =>
748                {
749                    continue;
750                }
751                _ => (),
752            }
753        }
754
755        let (mut ty, mut mods) = semantic_token_type_and_modifiers(highlight_range.highlight);
756
757        if !non_standard_tokens {
758            ty = match standard_fallback_type(ty) {
759                Some(ty) => ty,
760                None => continue,
761            };
762            mods.standard_fallback();
763        }
764        let token_index = semantic_tokens::type_index(ty);
765        let modifier_bitset = mods.0;
766
767        for mut text_range in line_index.index.lines(highlight_range.range) {
768            if text[text_range].ends_with('\n') {
769                text_range =
770                    TextRange::new(text_range.start(), text_range.end() - TextSize::of('\n'));
771            }
772            let range = range(line_index, text_range);
773            builder.push(range, token_index, modifier_bitset);
774        }
775    }
776
777    builder.build()
778}
779
780pub(crate) fn semantic_token_delta(
781    previous: &lsp_types::SemanticTokens,
782    current: &lsp_types::SemanticTokens,
783) -> lsp_types::SemanticTokensDelta {
784    let result_id = current.result_id.clone();
785    let edits = semantic_tokens::diff_tokens(&previous.data, &current.data);
786    lsp_types::SemanticTokensDelta { result_id, edits }
787}
788
789fn semantic_token_type_and_modifiers(
790    highlight: Highlight,
791) -> (lsp_types::SemanticTokenType, semantic_tokens::ModifierSet) {
792    use semantic_tokens::{modifiers as mods, types};
793
794    let ty = match highlight.tag {
795        HlTag::Symbol(symbol) => match symbol {
796            SymbolKind::Attribute => types::DECORATOR,
797            SymbolKind::Derive => types::DERIVE,
798            SymbolKind::DeriveHelper => types::DERIVE_HELPER,
799            SymbolKind::Module => types::NAMESPACE,
800            SymbolKind::Impl => types::TYPE_ALIAS,
801            SymbolKind::Field => types::PROPERTY,
802            SymbolKind::TypeParam => types::TYPE_PARAMETER,
803            SymbolKind::ConstParam => types::CONST_PARAMETER,
804            SymbolKind::LifetimeParam => types::LIFETIME,
805            SymbolKind::Label => types::LABEL,
806            SymbolKind::ValueParam => types::PARAMETER,
807            SymbolKind::SelfParam => types::SELF_KEYWORD,
808            SymbolKind::SelfType => types::SELF_TYPE_KEYWORD,
809            SymbolKind::Local => types::VARIABLE,
810            SymbolKind::Method => types::METHOD,
811            SymbolKind::Function => types::FUNCTION,
812            SymbolKind::Const => types::CONST,
813            SymbolKind::Static => types::STATIC,
814            SymbolKind::Struct => types::STRUCT,
815            SymbolKind::Enum => types::ENUM,
816            SymbolKind::Variant => types::ENUM_MEMBER,
817            SymbolKind::Union => types::UNION,
818            SymbolKind::TypeAlias => types::TYPE_ALIAS,
819            SymbolKind::Trait => types::INTERFACE,
820            SymbolKind::TraitAlias => types::INTERFACE,
821            SymbolKind::Macro => types::MACRO,
822            SymbolKind::ProcMacro => types::PROC_MACRO,
823            SymbolKind::BuiltinAttr => types::BUILTIN_ATTRIBUTE,
824            SymbolKind::ToolModule => types::TOOL_MODULE,
825            SymbolKind::InlineAsmRegOrRegClass => types::KEYWORD,
826        },
827        HlTag::AttributeBracket => types::ATTRIBUTE_BRACKET,
828        HlTag::BoolLiteral => types::BOOLEAN,
829        HlTag::BuiltinType => types::BUILTIN_TYPE,
830        HlTag::ByteLiteral | HlTag::NumericLiteral => types::NUMBER,
831        HlTag::CharLiteral => types::CHAR,
832        HlTag::Comment => types::COMMENT,
833        HlTag::EscapeSequence => types::ESCAPE_SEQUENCE,
834        HlTag::InvalidEscapeSequence => types::INVALID_ESCAPE_SEQUENCE,
835        HlTag::FormatSpecifier => types::FORMAT_SPECIFIER,
836        HlTag::Keyword => types::KEYWORD,
837        HlTag::None => types::GENERIC,
838        HlTag::Operator(op) => match op {
839            HlOperator::Bitwise => types::BITWISE,
840            HlOperator::Arithmetic => types::ARITHMETIC,
841            HlOperator::Logical => types::LOGICAL,
842            HlOperator::Comparison => types::COMPARISON,
843            HlOperator::Other => types::OPERATOR,
844        },
845        HlTag::StringLiteral => types::STRING,
846        HlTag::UnresolvedReference => types::UNRESOLVED_REFERENCE,
847        HlTag::Punctuation(punct) => match punct {
848            HlPunct::Bracket => types::BRACKET,
849            HlPunct::Brace => types::BRACE,
850            HlPunct::Parenthesis => types::PARENTHESIS,
851            HlPunct::Angle => types::ANGLE,
852            HlPunct::Comma => types::COMMA,
853            HlPunct::Dot => types::DOT,
854            HlPunct::Colon => types::COLON,
855            HlPunct::Semi => types::SEMICOLON,
856            HlPunct::Other => types::PUNCTUATION,
857            HlPunct::MacroBang => types::MACRO_BANG,
858        },
859    };
860
861    let mut mods = semantic_tokens::ModifierSet::default();
862    for modifier in highlight.mods.iter() {
863        let modifier = match modifier {
864            HlMod::Associated => mods::ASSOCIATED,
865            HlMod::Async => mods::ASYNC,
866            HlMod::Attribute => mods::ATTRIBUTE_MODIFIER,
867            HlMod::Callable => mods::CALLABLE,
868            HlMod::Const => mods::CONSTANT,
869            HlMod::Consuming => mods::CONSUMING,
870            HlMod::ControlFlow => mods::CONTROL_FLOW,
871            HlMod::CrateRoot => mods::CRATE_ROOT,
872            HlMod::DefaultLibrary => mods::DEFAULT_LIBRARY,
873            HlMod::Definition => mods::DECLARATION,
874            HlMod::Documentation => mods::DOCUMENTATION,
875            HlMod::Injected => mods::INJECTED,
876            HlMod::IntraDocLink => mods::INTRA_DOC_LINK,
877            HlMod::Library => mods::LIBRARY,
878            HlMod::Macro => mods::MACRO_MODIFIER,
879            HlMod::ProcMacro => mods::PROC_MACRO_MODIFIER,
880            HlMod::Mutable => mods::MUTABLE,
881            HlMod::Public => mods::PUBLIC,
882            HlMod::Reference => mods::REFERENCE,
883            HlMod::Static => mods::STATIC,
884            HlMod::Trait => mods::TRAIT_MODIFIER,
885            HlMod::Unsafe => mods::UNSAFE,
886        };
887        mods |= modifier;
888    }
889
890    (ty, mods)
891}
892
893pub(crate) fn folding_range(
894    text: &str,
895    line_index: &LineIndex,
896    line_folding_only: bool,
897    fold: Fold,
898) -> lsp_types::FoldingRange {
899    let kind = match fold.kind {
900        FoldKind::Comment => Some(lsp_types::FoldingRangeKind::Comment),
901        FoldKind::Imports => Some(lsp_types::FoldingRangeKind::Imports),
902        FoldKind::Region => Some(lsp_types::FoldingRangeKind::Region),
903        FoldKind::Modules
904        | FoldKind::Block
905        | FoldKind::ArgList
906        | FoldKind::Consts
907        | FoldKind::Statics
908        | FoldKind::TypeAliases
909        | FoldKind::WhereClause
910        | FoldKind::ReturnType
911        | FoldKind::Array
912        | FoldKind::TraitAliases
913        | FoldKind::ExternCrates
914        | FoldKind::MatchArm
915        | FoldKind::Function => None,
916    };
917
918    let range = range(line_index, fold.range);
919
920    if line_folding_only {
921        // Clients with line_folding_only == true (such as VSCode) will fold the whole end line
922        // even if it contains text not in the folding range. To prevent that we exclude
923        // range.end.line from the folding region if there is more text after range.end
924        // on the same line.
925        let has_more_text_on_end_line = text[TextRange::new(fold.range.end(), TextSize::of(text))]
926            .chars()
927            .take_while(|it| *it != '\n')
928            .any(|it| !it.is_whitespace());
929
930        let end_line = if has_more_text_on_end_line {
931            range.end.line.saturating_sub(1)
932        } else {
933            range.end.line
934        };
935
936        lsp_types::FoldingRange {
937            start_line: range.start.line,
938            start_character: None,
939            end_line,
940            end_character: None,
941            kind,
942            collapsed_text: None,
943        }
944    } else {
945        lsp_types::FoldingRange {
946            start_line: range.start.line,
947            start_character: Some(range.start.character),
948            end_line: range.end.line,
949            end_character: Some(range.end.character),
950            kind,
951            collapsed_text: None,
952        }
953    }
954}
955
956pub(crate) fn url(snap: &GlobalStateSnapshot, file_id: FileId) -> lsp_types::Url {
957    snap.file_id_to_url(file_id)
958}
959
960/// Returns a `Url` object from a given path, will lowercase drive letters if present.
961/// This will only happen when processing windows paths.
962///
963/// When processing non-windows path, this is essentially the same as `Url::from_file_path`.
964pub(crate) fn url_from_abs_path(path: &AbsPath) -> lsp_types::Url {
965    let url = lsp_types::Url::from_file_path(path).unwrap();
966    match path.components().next() {
967        Some(Utf8Component::Prefix(prefix))
968            if matches!(prefix.kind(), Utf8Prefix::Disk(_) | Utf8Prefix::VerbatimDisk(_)) =>
969        {
970            // Need to lowercase driver letter
971        }
972        _ => return url,
973    }
974
975    let driver_letter_range = {
976        let (scheme, drive_letter, _rest) = match url.as_str().splitn(3, ':').collect_tuple() {
977            Some(it) => it,
978            None => return url,
979        };
980        let start = scheme.len() + ':'.len_utf8();
981        start..(start + drive_letter.len())
982    };
983
984    // Note: lowercasing the `path` itself doesn't help, the `Url::parse`
985    // machinery *also* canonicalizes the drive letter. So, just massage the
986    // string in place.
987    let mut url: String = url.into();
988    url[driver_letter_range].make_ascii_lowercase();
989    lsp_types::Url::parse(&url).unwrap()
990}
991
992pub(crate) fn optional_versioned_text_document_identifier(
993    snap: &GlobalStateSnapshot,
994    file_id: FileId,
995) -> lsp_types::OptionalVersionedTextDocumentIdentifier {
996    let url = url(snap, file_id);
997    let version = snap.url_file_version(&url);
998    lsp_types::OptionalVersionedTextDocumentIdentifier { uri: url, version }
999}
1000
1001pub(crate) fn location(
1002    snap: &GlobalStateSnapshot,
1003    frange: FileRange,
1004) -> Cancellable<lsp_types::Location> {
1005    let url = url(snap, frange.file_id);
1006    let line_index = snap.file_line_index(frange.file_id)?;
1007    let range = range(&line_index, frange.range);
1008    let loc = lsp_types::Location::new(url, range);
1009    Ok(loc)
1010}
1011
1012/// Prefer using `location_link`, if the client has the cap.
1013pub(crate) fn location_from_nav(
1014    snap: &GlobalStateSnapshot,
1015    nav: NavigationTarget,
1016) -> Cancellable<lsp_types::Location> {
1017    let url = url(snap, nav.file_id);
1018    let line_index = snap.file_line_index(nav.file_id)?;
1019    let range = range(&line_index, nav.focus_or_full_range());
1020    let loc = lsp_types::Location::new(url, range);
1021    Ok(loc)
1022}
1023
1024pub(crate) fn location_link(
1025    snap: &GlobalStateSnapshot,
1026    src: Option<FileRange>,
1027    target: NavigationTarget,
1028) -> Cancellable<lsp_types::LocationLink> {
1029    let origin_selection_range = match src {
1030        Some(src) => {
1031            let line_index = snap.file_line_index(src.file_id)?;
1032            let range = range(&line_index, src.range);
1033            Some(range)
1034        }
1035        None => None,
1036    };
1037    let (target_uri, target_range, target_selection_range) = location_info(snap, target)?;
1038    let res = lsp_types::LocationLink {
1039        origin_selection_range,
1040        target_uri,
1041        target_range,
1042        target_selection_range,
1043    };
1044    Ok(res)
1045}
1046
1047fn location_info(
1048    snap: &GlobalStateSnapshot,
1049    target: NavigationTarget,
1050) -> Cancellable<(lsp_types::Url, lsp_types::Range, lsp_types::Range)> {
1051    let line_index = snap.file_line_index(target.file_id)?;
1052
1053    let target_uri = url(snap, target.file_id);
1054    let target_range = range(&line_index, target.full_range);
1055    let target_selection_range =
1056        target.focus_range.map(|it| range(&line_index, it)).unwrap_or(target_range);
1057    Ok((target_uri, target_range, target_selection_range))
1058}
1059
1060pub(crate) fn goto_definition_response(
1061    snap: &GlobalStateSnapshot,
1062    src: Option<FileRange>,
1063    targets: Vec<NavigationTarget>,
1064) -> Cancellable<lsp_types::GotoDefinitionResponse> {
1065    if snap.config.location_link() {
1066        let links = targets
1067            .into_iter()
1068            .unique_by(|nav| (nav.file_id, nav.full_range, nav.focus_range))
1069            .map(|nav| location_link(snap, src, nav))
1070            .collect::<Cancellable<Vec<_>>>()?;
1071        Ok(links.into())
1072    } else {
1073        let locations = targets
1074            .into_iter()
1075            .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
1076            .unique()
1077            .map(|range| location(snap, range))
1078            .collect::<Cancellable<Vec<_>>>()?;
1079        Ok(locations.into())
1080    }
1081}
1082
1083fn outside_workspace_annotation_id() -> String {
1084    String::from("OutsideWorkspace")
1085}
1086
1087fn merge_text_and_snippet_edits(
1088    line_index: &LineIndex,
1089    edit: TextEdit,
1090    snippet_edit: SnippetEdit,
1091    client_supports_annotations: bool,
1092) -> Vec<SnippetTextEdit> {
1093    let mut edits: Vec<SnippetTextEdit> = vec![];
1094    let mut snippets = snippet_edit.into_edit_ranges().into_iter().peekable();
1095    let annotation = edit.change_annotation();
1096    let text_edits = edit.into_iter();
1097    // offset to go from the final source location to the original source location
1098    let mut source_text_offset = 0i32;
1099
1100    let offset_range = |range: TextRange, offset: i32| -> TextRange {
1101        // map the snippet range from the target location into the original source location
1102        let start = u32::from(range.start()).checked_add_signed(offset).unwrap_or(0);
1103        let end = u32::from(range.end()).checked_add_signed(offset).unwrap_or(0);
1104
1105        TextRange::new(start.into(), end.into())
1106    };
1107
1108    for current_indel in text_edits {
1109        let new_range = {
1110            let insert_len =
1111                TextSize::try_from(current_indel.insert.len()).unwrap_or(TextSize::from(u32::MAX));
1112            TextRange::at(current_indel.delete.start(), insert_len)
1113        };
1114
1115        // figure out how much this Indel will shift future ranges from the initial source
1116        let offset_adjustment =
1117            u32::from(current_indel.delete.len()) as i32 - u32::from(new_range.len()) as i32;
1118
1119        // insert any snippets before the text edit
1120        for (snippet_index, snippet_range) in snippets.peeking_take_while(|(_, range)| {
1121            offset_range(*range, source_text_offset).end() < new_range.start()
1122        }) {
1123            // adjust the snippet range into the corresponding initial source location
1124            let snippet_range = offset_range(snippet_range, source_text_offset);
1125
1126            let snippet_range = if !stdx::always!(
1127                snippet_range.is_empty(),
1128                "placeholder range {:?} is before current text edit range {:?}",
1129                snippet_range,
1130                new_range
1131            ) {
1132                // only possible for tabstops, so make sure it's an empty/insert range
1133                TextRange::empty(snippet_range.start())
1134            } else {
1135                snippet_range
1136            };
1137
1138            edits.push(snippet_text_edit(
1139                line_index,
1140                true,
1141                Indel { insert: format!("${snippet_index}"), delete: snippet_range },
1142                annotation,
1143                client_supports_annotations,
1144            ))
1145        }
1146
1147        if snippets.peek().is_some_and(|(_, range)| {
1148            new_range.intersect(offset_range(*range, source_text_offset)).is_some()
1149        }) {
1150            // at least one snippet edit intersects this text edit,
1151            // so gather all of the edits that intersect this text edit
1152            let mut all_snippets = snippets
1153                .peeking_take_while(|(_, range)| {
1154                    new_range.intersect(offset_range(*range, source_text_offset)).is_some()
1155                })
1156                .map(|(tabstop, range)| (tabstop, offset_range(range, source_text_offset)))
1157                .collect_vec();
1158
1159            // ensure all of the ranges are wholly contained inside of the new range
1160            all_snippets.retain(|(_, range)| {
1161                    stdx::always!(
1162                        new_range.contains_range(*range),
1163                        "found placeholder range {:?} which wasn't fully inside of text edit's new range {:?}", range, new_range
1164                    )
1165                });
1166
1167            let mut new_text = current_indel.insert;
1168
1169            // find which snippet bits need to be escaped
1170            let escape_places =
1171                new_text.rmatch_indices(['\\', '$', '}']).map(|(insert, _)| insert).collect_vec();
1172            let mut escape_places = escape_places.into_iter().peekable();
1173            let mut escape_prior_bits = |new_text: &mut String, up_to: usize| {
1174                for before in escape_places.peeking_take_while(|insert| *insert >= up_to) {
1175                    new_text.insert(before, '\\');
1176                }
1177            };
1178
1179            // insert snippets, and escaping any needed bits along the way
1180            for (index, range) in all_snippets.iter().rev() {
1181                let text_range = range - new_range.start();
1182                let (start, end) = (text_range.start().into(), text_range.end().into());
1183
1184                if range.is_empty() {
1185                    escape_prior_bits(&mut new_text, start);
1186                    new_text.insert_str(start, &format!("${index}"));
1187                } else {
1188                    escape_prior_bits(&mut new_text, end);
1189                    new_text.insert(end, '}');
1190                    escape_prior_bits(&mut new_text, start);
1191                    new_text.insert_str(start, &format!("${{{index}:"));
1192                }
1193            }
1194
1195            // escape any remaining bits
1196            escape_prior_bits(&mut new_text, 0);
1197
1198            edits.push(snippet_text_edit(
1199                line_index,
1200                true,
1201                Indel { insert: new_text, delete: current_indel.delete },
1202                annotation,
1203                client_supports_annotations,
1204            ))
1205        } else {
1206            // snippet edit was beyond the current one
1207            // since it wasn't consumed, it's available for the next pass
1208            edits.push(snippet_text_edit(
1209                line_index,
1210                false,
1211                current_indel,
1212                annotation,
1213                client_supports_annotations,
1214            ));
1215        }
1216
1217        // update the final source -> initial source mapping offset
1218        source_text_offset += offset_adjustment;
1219    }
1220
1221    // insert any remaining tabstops
1222    edits.extend(snippets.map(|(snippet_index, snippet_range)| {
1223        // adjust the snippet range into the corresponding initial source location
1224        let snippet_range = offset_range(snippet_range, source_text_offset);
1225
1226        let snippet_range = if !stdx::always!(
1227            snippet_range.is_empty(),
1228            "found placeholder snippet {:?} without a text edit",
1229            snippet_range
1230        ) {
1231            TextRange::empty(snippet_range.start())
1232        } else {
1233            snippet_range
1234        };
1235
1236        snippet_text_edit(
1237            line_index,
1238            true,
1239            Indel { insert: format!("${snippet_index}"), delete: snippet_range },
1240            annotation,
1241            client_supports_annotations,
1242        )
1243    }));
1244
1245    edits
1246}
1247
1248pub(crate) fn snippet_text_document_edit(
1249    snap: &GlobalStateSnapshot,
1250    is_snippet: bool,
1251    file_id: FileId,
1252    edit: TextEdit,
1253    snippet_edit: Option<SnippetEdit>,
1254) -> Cancellable<lsp_ext::SnippetTextDocumentEdit> {
1255    let text_document = optional_versioned_text_document_identifier(snap, file_id);
1256    let line_index = snap.file_line_index(file_id)?;
1257    let client_supports_annotations = snap.config.change_annotation_support();
1258    let mut edits = if let Some(snippet_edit) = snippet_edit {
1259        merge_text_and_snippet_edits(&line_index, edit, snippet_edit, client_supports_annotations)
1260    } else {
1261        let annotation = edit.change_annotation();
1262        edit.into_iter()
1263            .map(|it| {
1264                snippet_text_edit(
1265                    &line_index,
1266                    is_snippet,
1267                    it,
1268                    annotation,
1269                    client_supports_annotations,
1270                )
1271            })
1272            .collect()
1273    };
1274
1275    if snap.analysis.is_library_file(file_id)? && snap.config.change_annotation_support() {
1276        for edit in &mut edits {
1277            edit.annotation_id = Some(outside_workspace_annotation_id())
1278        }
1279    }
1280    Ok(lsp_ext::SnippetTextDocumentEdit { text_document, edits })
1281}
1282
1283pub(crate) fn snippet_text_document_ops(
1284    snap: &GlobalStateSnapshot,
1285    file_system_edit: FileSystemEdit,
1286) -> Cancellable<Vec<lsp_ext::SnippetDocumentChangeOperation>> {
1287    let mut ops = Vec::new();
1288    match file_system_edit {
1289        FileSystemEdit::CreateFile { dst, initial_contents } => {
1290            let uri = snap.anchored_path(&dst);
1291            let create_file = lsp_types::ResourceOp::Create(lsp_types::CreateFile {
1292                uri: uri.clone(),
1293                options: None,
1294                annotation_id: None,
1295            });
1296            ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(create_file));
1297            if !initial_contents.is_empty() {
1298                let text_document =
1299                    lsp_types::OptionalVersionedTextDocumentIdentifier { uri, version: None };
1300                let text_edit = lsp_ext::SnippetTextEdit {
1301                    range: lsp_types::Range::default(),
1302                    new_text: initial_contents,
1303                    insert_text_format: Some(lsp_types::InsertTextFormat::PLAIN_TEXT),
1304                    annotation_id: None,
1305                };
1306                let edit_file =
1307                    lsp_ext::SnippetTextDocumentEdit { text_document, edits: vec![text_edit] };
1308                ops.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit_file));
1309            }
1310        }
1311        FileSystemEdit::MoveFile { src, dst } => {
1312            let old_uri = snap.file_id_to_url(src);
1313            let new_uri = snap.anchored_path(&dst);
1314            let mut rename_file =
1315                lsp_types::RenameFile { old_uri, new_uri, options: None, annotation_id: None };
1316            if snap.analysis.is_library_file(src).ok() == Some(true)
1317                && snap.config.change_annotation_support()
1318            {
1319                rename_file.annotation_id = Some(outside_workspace_annotation_id())
1320            }
1321            ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(lsp_types::ResourceOp::Rename(
1322                rename_file,
1323            )))
1324        }
1325        FileSystemEdit::MoveDir { src, src_id, dst } => {
1326            let old_uri = snap.anchored_path(&src);
1327            let new_uri = snap.anchored_path(&dst);
1328            let mut rename_file =
1329                lsp_types::RenameFile { old_uri, new_uri, options: None, annotation_id: None };
1330            if snap.analysis.is_library_file(src_id).ok() == Some(true)
1331                && snap.config.change_annotation_support()
1332            {
1333                rename_file.annotation_id = Some(outside_workspace_annotation_id())
1334            }
1335            ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(lsp_types::ResourceOp::Rename(
1336                rename_file,
1337            )))
1338        }
1339    }
1340    Ok(ops)
1341}
1342
1343pub(crate) fn snippet_workspace_edit(
1344    snap: &GlobalStateSnapshot,
1345    mut source_change: SourceChange,
1346) -> Cancellable<lsp_ext::SnippetWorkspaceEdit> {
1347    let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new();
1348
1349    for op in &mut source_change.file_system_edits {
1350        if let FileSystemEdit::CreateFile { dst, initial_contents } = op {
1351            // replace with a placeholder to avoid cloneing the edit
1352            let op = FileSystemEdit::CreateFile {
1353                dst: dst.clone(),
1354                initial_contents: mem::take(initial_contents),
1355            };
1356            let ops = snippet_text_document_ops(snap, op)?;
1357            document_changes.extend_from_slice(&ops);
1358        }
1359    }
1360    for (file_id, (edit, snippet_edit)) in source_change.source_file_edits {
1361        let edit = snippet_text_document_edit(
1362            snap,
1363            source_change.is_snippet,
1364            file_id,
1365            edit,
1366            snippet_edit,
1367        )?;
1368        document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit));
1369    }
1370    for op in source_change.file_system_edits {
1371        if !matches!(op, FileSystemEdit::CreateFile { .. }) {
1372            let ops = snippet_text_document_ops(snap, op)?;
1373            document_changes.extend_from_slice(&ops);
1374        }
1375    }
1376    let mut workspace_edit = lsp_ext::SnippetWorkspaceEdit {
1377        changes: None,
1378        document_changes: Some(document_changes),
1379        change_annotations: None,
1380    };
1381    if snap.config.change_annotation_support() {
1382        workspace_edit.change_annotations = Some(
1383            once((
1384                outside_workspace_annotation_id(),
1385                lsp_types::ChangeAnnotation {
1386                    label: String::from("Edit outside of the workspace"),
1387                    needs_confirmation: Some(true),
1388                    description: Some(String::from(
1389                        "This edit lies outside of the workspace and may affect dependencies",
1390                    )),
1391                },
1392            ))
1393            .chain(source_change.annotations.into_iter().map(|(id, annotation)| {
1394                (
1395                    id.to_string(),
1396                    lsp_types::ChangeAnnotation {
1397                        label: annotation.label,
1398                        description: annotation.description,
1399                        needs_confirmation: Some(annotation.needs_confirmation),
1400                    },
1401                )
1402            }))
1403            .collect(),
1404        )
1405    }
1406    Ok(workspace_edit)
1407}
1408
1409pub(crate) fn workspace_edit(
1410    snap: &GlobalStateSnapshot,
1411    source_change: SourceChange,
1412) -> Cancellable<lsp_types::WorkspaceEdit> {
1413    assert!(!source_change.is_snippet);
1414    snippet_workspace_edit(snap, source_change).map(|it| it.into())
1415}
1416
1417impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit {
1418    fn from(snippet_workspace_edit: lsp_ext::SnippetWorkspaceEdit) -> lsp_types::WorkspaceEdit {
1419        lsp_types::WorkspaceEdit {
1420            changes: None,
1421            document_changes: snippet_workspace_edit.document_changes.map(|changes| {
1422                lsp_types::DocumentChanges::Operations(
1423                    changes
1424                        .into_iter()
1425                        .map(|change| match change {
1426                            lsp_ext::SnippetDocumentChangeOperation::Op(op) => {
1427                                lsp_types::DocumentChangeOperation::Op(op)
1428                            }
1429                            lsp_ext::SnippetDocumentChangeOperation::Edit(edit) => {
1430                                lsp_types::DocumentChangeOperation::Edit(
1431                                    lsp_types::TextDocumentEdit {
1432                                        text_document: edit.text_document,
1433                                        edits: edit.edits.into_iter().map(From::from).collect(),
1434                                    },
1435                                )
1436                            }
1437                        })
1438                        .collect(),
1439                )
1440            }),
1441            change_annotations: snippet_workspace_edit.change_annotations,
1442        }
1443    }
1444}
1445
1446impl From<lsp_ext::SnippetTextEdit>
1447    for lsp_types::OneOf<lsp_types::TextEdit, lsp_types::AnnotatedTextEdit>
1448{
1449    fn from(
1450        lsp_ext::SnippetTextEdit { annotation_id, insert_text_format:_, new_text, range }: lsp_ext::SnippetTextEdit,
1451    ) -> Self {
1452        match annotation_id {
1453            Some(annotation_id) => lsp_types::OneOf::Right(lsp_types::AnnotatedTextEdit {
1454                text_edit: lsp_types::TextEdit { range, new_text },
1455                annotation_id,
1456            }),
1457            None => lsp_types::OneOf::Left(lsp_types::TextEdit { range, new_text }),
1458        }
1459    }
1460}
1461
1462pub(crate) fn call_hierarchy_item(
1463    snap: &GlobalStateSnapshot,
1464    target: NavigationTarget,
1465) -> Cancellable<lsp_types::CallHierarchyItem> {
1466    let name = target.name.to_string();
1467    let detail = target.description.clone();
1468    let kind = target.kind.map(symbol_kind).unwrap_or(lsp_types::SymbolKind::FUNCTION);
1469    let (uri, range, selection_range) = location_info(snap, target)?;
1470    Ok(lsp_types::CallHierarchyItem {
1471        name,
1472        kind,
1473        tags: None,
1474        detail,
1475        uri,
1476        range,
1477        selection_range,
1478        data: None,
1479    })
1480}
1481
1482pub(crate) fn code_action_kind(kind: AssistKind) -> lsp_types::CodeActionKind {
1483    match kind {
1484        AssistKind::Generate => lsp_types::CodeActionKind::EMPTY,
1485        AssistKind::QuickFix => lsp_types::CodeActionKind::QUICKFIX,
1486        AssistKind::Refactor => lsp_types::CodeActionKind::REFACTOR,
1487        AssistKind::RefactorExtract => lsp_types::CodeActionKind::REFACTOR_EXTRACT,
1488        AssistKind::RefactorInline => lsp_types::CodeActionKind::REFACTOR_INLINE,
1489        AssistKind::RefactorRewrite => lsp_types::CodeActionKind::REFACTOR_REWRITE,
1490    }
1491}
1492
1493pub(crate) fn code_action(
1494    snap: &GlobalStateSnapshot,
1495    assist: Assist,
1496    resolve_data: Option<(usize, lsp_types::CodeActionParams, Option<i32>)>,
1497) -> Cancellable<lsp_ext::CodeAction> {
1498    let mut res = lsp_ext::CodeAction {
1499        title: assist.label.to_string(),
1500        group: assist.group.filter(|_| snap.config.code_action_group()).map(|gr| gr.0),
1501        kind: Some(code_action_kind(assist.id.1)),
1502        edit: None,
1503        is_preferred: None,
1504        data: None,
1505        command: None,
1506    };
1507
1508    let commands = snap.config.client_commands();
1509    res.command = match assist.command {
1510        Some(assists::Command::TriggerParameterHints) if commands.trigger_parameter_hints => {
1511            Some(command::trigger_parameter_hints())
1512        }
1513        Some(assists::Command::Rename) if commands.rename => Some(command::rename()),
1514        _ => None,
1515    };
1516
1517    match (assist.source_change, resolve_data) {
1518        (Some(it), _) => res.edit = Some(snippet_workspace_edit(snap, it)?),
1519        (None, Some((index, code_action_params, version))) => {
1520            res.data = Some(lsp_ext::CodeActionData {
1521                id: format!(
1522                    "{}:{}:{index}:{}",
1523                    assist.id.0,
1524                    assist.id.1.name(),
1525                    assist.id.2.map(|x| x.to_string()).unwrap_or("".to_owned())
1526                ),
1527                code_action_params,
1528                version,
1529            });
1530        }
1531        (None, None) => {
1532            stdx::never!("assist should always be resolved if client can't do lazy resolving")
1533        }
1534    };
1535    Ok(res)
1536}
1537
1538pub(crate) fn runnable(
1539    snap: &GlobalStateSnapshot,
1540    runnable: Runnable,
1541) -> Cancellable<Option<lsp_ext::Runnable>> {
1542    let target_spec = TargetSpec::for_file(snap, runnable.nav.file_id)?;
1543    let source_root = snap.analysis.source_root_id(runnable.nav.file_id).ok();
1544    let config = snap.config.runnables(source_root);
1545
1546    match target_spec {
1547        Some(TargetSpec::Cargo(spec)) => {
1548            let workspace_root = spec.workspace_root.clone();
1549
1550            let target = spec.target.clone();
1551
1552            let (cargo_args, executable_args) = CargoTargetSpec::runnable_args(
1553                snap,
1554                Some(spec.clone()),
1555                &runnable.kind,
1556                &runnable.cfg,
1557            );
1558
1559            let cwd = match runnable.kind {
1560                ide::RunnableKind::Bin => workspace_root.clone(),
1561                _ => spec.cargo_toml.parent().to_owned(),
1562            };
1563
1564            let label = runnable.label(Some(&target));
1565            let location = location_link(snap, None, runnable.nav)?;
1566
1567            Ok(Some(lsp_ext::Runnable {
1568                label,
1569                location: Some(location),
1570                kind: lsp_ext::RunnableKind::Cargo,
1571                args: lsp_ext::RunnableArgs::Cargo(lsp_ext::CargoRunnableArgs {
1572                    workspace_root: Some(workspace_root.into()),
1573                    override_cargo: config.override_cargo,
1574                    cargo_args,
1575                    cwd: cwd.into(),
1576                    executable_args,
1577                    environment: spec
1578                        .sysroot_root
1579                        .map(|root| ("RUSTC_TOOLCHAIN".to_owned(), root.to_string()))
1580                        .into_iter()
1581                        .collect(),
1582                }),
1583            }))
1584        }
1585        Some(TargetSpec::ProjectJson(spec)) => {
1586            let label = runnable.label(Some(&spec.label));
1587            let location = location_link(snap, None, runnable.nav)?;
1588
1589            match spec.runnable_args(&runnable.kind) {
1590                Some(json_shell_runnable_args) => {
1591                    let runnable_args = ShellRunnableArgs {
1592                        program: json_shell_runnable_args.program,
1593                        args: json_shell_runnable_args.args,
1594                        cwd: json_shell_runnable_args.cwd,
1595                        environment: Default::default(),
1596                    };
1597                    Ok(Some(lsp_ext::Runnable {
1598                        label,
1599                        location: Some(location),
1600                        kind: lsp_ext::RunnableKind::Shell,
1601                        args: lsp_ext::RunnableArgs::Shell(runnable_args),
1602                    }))
1603                }
1604                None => Ok(None),
1605            }
1606        }
1607        None => {
1608            let Some(path) = snap.file_id_to_file_path(runnable.nav.file_id).parent() else {
1609                return Ok(None);
1610            };
1611            let (cargo_args, executable_args) =
1612                CargoTargetSpec::runnable_args(snap, None, &runnable.kind, &runnable.cfg);
1613
1614            let label = runnable.label(None);
1615            let location = location_link(snap, None, runnable.nav)?;
1616
1617            Ok(Some(lsp_ext::Runnable {
1618                label,
1619                location: Some(location),
1620                kind: lsp_ext::RunnableKind::Cargo,
1621                args: lsp_ext::RunnableArgs::Cargo(lsp_ext::CargoRunnableArgs {
1622                    workspace_root: None,
1623                    override_cargo: config.override_cargo,
1624                    cargo_args,
1625                    cwd: path.as_path().unwrap().to_path_buf().into(),
1626                    executable_args,
1627                    environment: Default::default(),
1628                }),
1629            }))
1630        }
1631    }
1632}
1633
1634pub(crate) fn code_lens(
1635    acc: &mut Vec<lsp_types::CodeLens>,
1636    snap: &GlobalStateSnapshot,
1637    annotation: Annotation,
1638) -> Cancellable<()> {
1639    let client_commands_config = snap.config.client_commands();
1640    match annotation.kind {
1641        AnnotationKind::Runnable(run) => {
1642            let line_index = snap.file_line_index(run.nav.file_id)?;
1643            let annotation_range = range(&line_index, annotation.range);
1644
1645            let update_test = run.update_test;
1646            let title = run.title();
1647            let can_debug = match run.kind {
1648                ide::RunnableKind::DocTest { .. } => false,
1649                ide::RunnableKind::TestMod { .. }
1650                | ide::RunnableKind::Test { .. }
1651                | ide::RunnableKind::Bench { .. }
1652                | ide::RunnableKind::Bin => true,
1653            };
1654            let r = runnable(snap, run)?;
1655
1656            if let Some(r) = r {
1657                let has_root = match &r.args {
1658                    lsp_ext::RunnableArgs::Cargo(c) => c.workspace_root.is_some(),
1659                    lsp_ext::RunnableArgs::Shell(_) => true,
1660                };
1661
1662                let lens_config = snap.config.lens();
1663
1664                if has_root {
1665                    if lens_config.run && client_commands_config.run_single {
1666                        let command = command::run_single(&r, &title);
1667                        acc.push(lsp_types::CodeLens {
1668                            range: annotation_range,
1669                            command: Some(command),
1670                            data: None,
1671                        })
1672                    }
1673                    if lens_config.debug && can_debug && client_commands_config.debug_single {
1674                        let command = command::debug_single(&r);
1675                        acc.push(lsp_types::CodeLens {
1676                            range: annotation_range,
1677                            command: Some(command),
1678                            data: None,
1679                        })
1680                    }
1681                    if lens_config.update_test && client_commands_config.run_single {
1682                        let label = update_test.label();
1683                        if let Some(r) = make_update_runnable(&r, update_test) {
1684                            let command = command::run_single(&r, label.unwrap().as_str());
1685                            acc.push(lsp_types::CodeLens {
1686                                range: annotation_range,
1687                                command: Some(command),
1688                                data: None,
1689                            })
1690                        }
1691                    }
1692                }
1693
1694                if lens_config.interpret {
1695                    let command = command::interpret_single(&r);
1696                    acc.push(lsp_types::CodeLens {
1697                        range: annotation_range,
1698                        command: Some(command),
1699                        data: None,
1700                    })
1701                }
1702            }
1703        }
1704        AnnotationKind::HasImpls { pos, data } => {
1705            if !client_commands_config.show_reference {
1706                return Ok(());
1707            }
1708            let line_index = snap.file_line_index(pos.file_id)?;
1709            let annotation_range = range(&line_index, annotation.range);
1710            let url = url(snap, pos.file_id);
1711            let pos = position(&line_index, pos.offset);
1712
1713            let id = lsp_types::TextDocumentIdentifier { uri: url.clone() };
1714
1715            let doc_pos = lsp_types::TextDocumentPositionParams::new(id, pos);
1716
1717            let goto_params = lsp_types::request::GotoImplementationParams {
1718                text_document_position_params: doc_pos,
1719                work_done_progress_params: Default::default(),
1720                partial_result_params: Default::default(),
1721            };
1722
1723            let command = data.map(|ranges| {
1724                let locations: Vec<lsp_types::Location> = ranges
1725                    .into_iter()
1726                    .filter_map(|target| {
1727                        location(
1728                            snap,
1729                            FileRange { file_id: target.file_id, range: target.full_range },
1730                        )
1731                        .ok()
1732                    })
1733                    .collect();
1734
1735                command::show_references(
1736                    implementation_title(locations.len()),
1737                    &url,
1738                    pos,
1739                    locations,
1740                )
1741            });
1742
1743            acc.push(lsp_types::CodeLens {
1744                range: annotation_range,
1745                command,
1746                data: (|| {
1747                    let version = snap.url_file_version(&url)?;
1748                    Some(
1749                        to_value(lsp_ext::CodeLensResolveData {
1750                            version,
1751                            kind: lsp_ext::CodeLensResolveDataKind::Impls(goto_params),
1752                        })
1753                        .unwrap(),
1754                    )
1755                })(),
1756            })
1757        }
1758        AnnotationKind::HasReferences { pos, data } => {
1759            if !client_commands_config.show_reference {
1760                return Ok(());
1761            }
1762            let line_index = snap.file_line_index(pos.file_id)?;
1763            let annotation_range = range(&line_index, annotation.range);
1764            let url = url(snap, pos.file_id);
1765            let pos = position(&line_index, pos.offset);
1766
1767            let id = lsp_types::TextDocumentIdentifier { uri: url.clone() };
1768
1769            let doc_pos = lsp_types::TextDocumentPositionParams::new(id, pos);
1770
1771            let command = data.map(|ranges| {
1772                let locations: Vec<lsp_types::Location> =
1773                    ranges.into_iter().filter_map(|range| location(snap, range).ok()).collect();
1774
1775                command::show_references(reference_title(locations.len()), &url, pos, locations)
1776            });
1777
1778            acc.push(lsp_types::CodeLens {
1779                range: annotation_range,
1780                command,
1781                data: (|| {
1782                    let version = snap.url_file_version(&url)?;
1783                    Some(
1784                        to_value(lsp_ext::CodeLensResolveData {
1785                            version,
1786                            kind: lsp_ext::CodeLensResolveDataKind::References(doc_pos),
1787                        })
1788                        .unwrap(),
1789                    )
1790                })(),
1791            })
1792        }
1793    }
1794    Ok(())
1795}
1796
1797pub(crate) fn test_item(
1798    snap: &GlobalStateSnapshot,
1799    test_item: ide::TestItem,
1800    line_index: Option<&LineIndex>,
1801) -> Option<lsp_ext::TestItem> {
1802    Some(lsp_ext::TestItem {
1803        id: test_item.id,
1804        label: test_item.label,
1805        kind: match test_item.kind {
1806            ide::TestItemKind::Crate(id) => match snap.target_spec_for_crate(id) {
1807                Some(target_spec) => match target_spec.target_kind() {
1808                    project_model::TargetKind::Bin
1809                    | project_model::TargetKind::Lib { .. }
1810                    | project_model::TargetKind::Example
1811                    | project_model::TargetKind::BuildScript
1812                    | project_model::TargetKind::Other => lsp_ext::TestItemKind::Package,
1813                    project_model::TargetKind::Test => lsp_ext::TestItemKind::Test,
1814                    // benches are not tests needed to be shown in the test explorer
1815                    project_model::TargetKind::Bench => return None,
1816                },
1817                None => lsp_ext::TestItemKind::Package,
1818            },
1819            ide::TestItemKind::Module => lsp_ext::TestItemKind::Module,
1820            ide::TestItemKind::Function => lsp_ext::TestItemKind::Test,
1821        },
1822        can_resolve_children: matches!(
1823            test_item.kind,
1824            ide::TestItemKind::Crate(_) | ide::TestItemKind::Module
1825        ),
1826        parent: test_item.parent,
1827        text_document: test_item
1828            .file
1829            .map(|f| lsp_types::TextDocumentIdentifier { uri: url(snap, f) }),
1830        range: line_index.and_then(|l| Some(range(l, test_item.text_range?))),
1831        runnable: test_item.runnable.and_then(|r| runnable(snap, r).ok()).flatten(),
1832    })
1833}
1834
1835pub(crate) mod command {
1836    use ide::{FileRange, NavigationTarget};
1837    use serde_json::to_value;
1838
1839    use crate::{
1840        global_state::GlobalStateSnapshot,
1841        lsp::to_proto::{location, location_link},
1842        lsp_ext,
1843    };
1844
1845    pub(crate) fn show_references(
1846        title: String,
1847        uri: &lsp_types::Url,
1848        position: lsp_types::Position,
1849        locations: Vec<lsp_types::Location>,
1850    ) -> lsp_types::Command {
1851        // We cannot use the 'editor.action.showReferences' command directly
1852        // because that command requires vscode types which we convert in the handler
1853        // on the client side.
1854
1855        lsp_types::Command {
1856            title,
1857            command: "rust-analyzer.showReferences".into(),
1858            arguments: Some(vec![
1859                to_value(uri).unwrap(),
1860                to_value(position).unwrap(),
1861                to_value(locations).unwrap(),
1862            ]),
1863        }
1864    }
1865
1866    pub(crate) fn run_single(runnable: &lsp_ext::Runnable, title: &str) -> lsp_types::Command {
1867        lsp_types::Command {
1868            title: title.to_owned(),
1869            command: "rust-analyzer.runSingle".into(),
1870            arguments: Some(vec![to_value(runnable).unwrap()]),
1871        }
1872    }
1873
1874    pub(crate) fn debug_single(runnable: &lsp_ext::Runnable) -> lsp_types::Command {
1875        lsp_types::Command {
1876            title: "âš™\u{fe0e} Debug".into(),
1877            command: "rust-analyzer.debugSingle".into(),
1878            arguments: Some(vec![to_value(runnable).unwrap()]),
1879        }
1880    }
1881
1882    pub(crate) fn interpret_single(_runnable: &lsp_ext::Runnable) -> lsp_types::Command {
1883        lsp_types::Command {
1884            title: "Interpret".into(),
1885            command: "rust-analyzer.interpretFunction".into(),
1886            // FIXME: use the `_runnable` here.
1887            arguments: Some(vec![]),
1888        }
1889    }
1890
1891    pub(crate) fn goto_location(
1892        snap: &GlobalStateSnapshot,
1893        nav: &NavigationTarget,
1894    ) -> Option<lsp_types::Command> {
1895        let value = if snap.config.location_link() {
1896            let link = location_link(snap, None, nav.clone()).ok()?;
1897            to_value(link).ok()?
1898        } else {
1899            let range = FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() };
1900            let location = location(snap, range).ok()?;
1901            to_value(location).ok()?
1902        };
1903
1904        Some(lsp_types::Command {
1905            title: nav.name.to_string(),
1906            command: "rust-analyzer.gotoLocation".into(),
1907            arguments: Some(vec![value]),
1908        })
1909    }
1910
1911    pub(crate) fn trigger_parameter_hints() -> lsp_types::Command {
1912        lsp_types::Command {
1913            title: "triggerParameterHints".into(),
1914            command: "rust-analyzer.triggerParameterHints".into(),
1915            arguments: None,
1916        }
1917    }
1918
1919    pub(crate) fn rename() -> lsp_types::Command {
1920        lsp_types::Command {
1921            title: "rename".into(),
1922            command: "rust-analyzer.rename".into(),
1923            arguments: None,
1924        }
1925    }
1926}
1927
1928pub(crate) fn make_update_runnable(
1929    runnable: &lsp_ext::Runnable,
1930    update_test: UpdateTest,
1931) -> Option<lsp_ext::Runnable> {
1932    let label = update_test.label()?;
1933
1934    let mut runnable = runnable.clone();
1935    runnable.label = format!("{} + {}", runnable.label, label);
1936
1937    let lsp_ext::RunnableArgs::Cargo(r) = &mut runnable.args else {
1938        return None;
1939    };
1940
1941    r.environment.extend(update_test.env().iter().map(|(k, v)| (k.to_string(), v.to_string())));
1942
1943    if update_test.insta {
1944        r.cargo_args.insert(0, "insta".to_owned());
1945    }
1946
1947    Some(runnable)
1948}
1949
1950pub(crate) fn implementation_title(count: usize) -> String {
1951    if count == 1 { "1 implementation".into() } else { format!("{count} implementations") }
1952}
1953
1954pub(crate) fn reference_title(count: usize) -> String {
1955    if count == 1 { "1 reference".into() } else { format!("{count} references") }
1956}
1957
1958pub(crate) fn markup_content(
1959    markup: Markup,
1960    kind: ide::HoverDocFormat,
1961) -> lsp_types::MarkupContent {
1962    let kind = match kind {
1963        ide::HoverDocFormat::Markdown => lsp_types::MarkupKind::Markdown,
1964        ide::HoverDocFormat::PlainText => lsp_types::MarkupKind::PlainText,
1965    };
1966    let value = format_docs(&Documentation::new(markup.into()));
1967    lsp_types::MarkupContent { kind, value }
1968}
1969
1970pub(crate) fn rename_error(err: RenameError) -> LspError {
1971    // This is wrong, but we don't have a better alternative I suppose?
1972    // https://github.com/microsoft/language-server-protocol/issues/1341
1973    invalid_params_error(err.to_string())
1974}
1975
1976#[cfg(test)]
1977mod tests {
1978    use expect_test::{Expect, expect};
1979    use ide::{Analysis, FilePosition};
1980    use ide_db::source_change::Snippet;
1981    use test_utils::extract_offset;
1982    use triomphe::Arc;
1983
1984    use super::*;
1985
1986    #[test]
1987    fn conv_fold_line_folding_only_fixup() {
1988        let text = r#"mod a;
1989mod b;
1990mod c;
1991
1992fn main() {
1993    if cond {
1994        a::do_a();
1995    } else {
1996        b::do_b();
1997    }
1998}"#;
1999
2000        let (analysis, file_id) = Analysis::from_single_file(text.to_owned());
2001        let folds = analysis.folding_ranges(file_id).unwrap();
2002        assert_eq!(folds.len(), 4);
2003
2004        let line_index = LineIndex {
2005            index: Arc::new(ide::LineIndex::new(text)),
2006            endings: LineEndings::Unix,
2007            encoding: PositionEncoding::Utf8,
2008        };
2009        let converted: Vec<lsp_types::FoldingRange> =
2010            folds.into_iter().map(|it| folding_range(text, &line_index, true, it)).collect();
2011
2012        let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)];
2013        assert_eq!(converted.len(), expected_lines.len());
2014        for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) {
2015            assert_eq!(folding_range.start_line, *start_line);
2016            assert_eq!(folding_range.start_character, None);
2017            assert_eq!(folding_range.end_line, *end_line);
2018            assert_eq!(folding_range.end_character, None);
2019        }
2020    }
2021
2022    #[test]
2023    fn calling_function_with_ignored_code_in_signature() {
2024        let text = r#"
2025fn foo() {
2026    bar($0);
2027}
2028/// ```
2029/// # use crate::bar;
2030/// bar(5);
2031/// ```
2032fn bar(_: usize) {}
2033"#;
2034
2035        let (offset, text) = extract_offset(text);
2036        let (analysis, file_id) = Analysis::from_single_file(text);
2037        let help = signature_help(
2038            analysis.signature_help(FilePosition { file_id, offset }).unwrap().unwrap(),
2039            CallInfoConfig { params_only: false, docs: true },
2040            false,
2041        );
2042        let docs = match &help.signatures[help.active_signature.unwrap() as usize].documentation {
2043            Some(lsp_types::Documentation::MarkupContent(content)) => &content.value,
2044            _ => panic!("documentation contains markup"),
2045        };
2046        assert!(docs.contains("bar(5)"));
2047        assert!(!docs.contains("use crate::bar"));
2048    }
2049
2050    #[track_caller]
2051    fn check_rendered_snippets(edit: TextEdit, snippets: SnippetEdit, expect: Expect) {
2052        check_rendered_snippets_in_source(
2053            r"/* place to put all ranges in */",
2054            edit,
2055            snippets,
2056            expect,
2057        );
2058    }
2059
2060    #[track_caller]
2061    fn check_rendered_snippets_in_source(
2062        #[ra_ap_rust_analyzer::rust_fixture] ra_fixture: &str,
2063        edit: TextEdit,
2064        snippets: SnippetEdit,
2065        expect: Expect,
2066    ) {
2067        let source = stdx::trim_indent(ra_fixture);
2068        let endings = if source.contains('\r') { LineEndings::Dos } else { LineEndings::Unix };
2069        let line_index = LineIndex {
2070            index: Arc::new(ide::LineIndex::new(&source)),
2071            endings,
2072            encoding: PositionEncoding::Utf8,
2073        };
2074
2075        let res = merge_text_and_snippet_edits(&line_index, edit, snippets, true);
2076
2077        // Ensure that none of the ranges overlap
2078        {
2079            let mut sorted = res.clone();
2080            sorted.sort_by_key(|edit| (edit.range.start, edit.range.end));
2081            let disjoint_ranges = sorted
2082                .iter()
2083                .zip(sorted.iter().skip(1))
2084                .all(|(l, r)| l.range.end <= r.range.start || l == r);
2085            assert!(disjoint_ranges, "ranges overlap for {res:#?}");
2086        }
2087
2088        expect.assert_debug_eq(&res);
2089    }
2090
2091    #[test]
2092    fn snippet_rendering_only_tabstops() {
2093        let edit = TextEdit::builder().finish();
2094        let snippets = SnippetEdit::new(vec![
2095            Snippet::Tabstop(0.into()),
2096            Snippet::Tabstop(0.into()),
2097            Snippet::Tabstop(1.into()),
2098            Snippet::Tabstop(1.into()),
2099        ]);
2100
2101        check_rendered_snippets(
2102            edit,
2103            snippets,
2104            expect![[r#"
2105            [
2106                SnippetTextEdit {
2107                    range: Range {
2108                        start: Position {
2109                            line: 0,
2110                            character: 0,
2111                        },
2112                        end: Position {
2113                            line: 0,
2114                            character: 0,
2115                        },
2116                    },
2117                    new_text: "$1",
2118                    insert_text_format: Some(
2119                        Snippet,
2120                    ),
2121                    annotation_id: None,
2122                },
2123                SnippetTextEdit {
2124                    range: Range {
2125                        start: Position {
2126                            line: 0,
2127                            character: 0,
2128                        },
2129                        end: Position {
2130                            line: 0,
2131                            character: 0,
2132                        },
2133                    },
2134                    new_text: "$2",
2135                    insert_text_format: Some(
2136                        Snippet,
2137                    ),
2138                    annotation_id: None,
2139                },
2140                SnippetTextEdit {
2141                    range: Range {
2142                        start: Position {
2143                            line: 0,
2144                            character: 1,
2145                        },
2146                        end: Position {
2147                            line: 0,
2148                            character: 1,
2149                        },
2150                    },
2151                    new_text: "$3",
2152                    insert_text_format: Some(
2153                        Snippet,
2154                    ),
2155                    annotation_id: None,
2156                },
2157                SnippetTextEdit {
2158                    range: Range {
2159                        start: Position {
2160                            line: 0,
2161                            character: 1,
2162                        },
2163                        end: Position {
2164                            line: 0,
2165                            character: 1,
2166                        },
2167                    },
2168                    new_text: "$0",
2169                    insert_text_format: Some(
2170                        Snippet,
2171                    ),
2172                    annotation_id: None,
2173                },
2174            ]
2175        "#]],
2176        );
2177    }
2178
2179    #[test]
2180    fn snippet_rendering_only_text_edits() {
2181        let mut edit = TextEdit::builder();
2182        edit.insert(0.into(), "abc".to_owned());
2183        edit.insert(3.into(), "def".to_owned());
2184        let edit = edit.finish();
2185        let snippets = SnippetEdit::new(vec![]);
2186
2187        check_rendered_snippets(
2188            edit,
2189            snippets,
2190            expect![[r#"
2191            [
2192                SnippetTextEdit {
2193                    range: Range {
2194                        start: Position {
2195                            line: 0,
2196                            character: 0,
2197                        },
2198                        end: Position {
2199                            line: 0,
2200                            character: 0,
2201                        },
2202                    },
2203                    new_text: "abc",
2204                    insert_text_format: None,
2205                    annotation_id: None,
2206                },
2207                SnippetTextEdit {
2208                    range: Range {
2209                        start: Position {
2210                            line: 0,
2211                            character: 3,
2212                        },
2213                        end: Position {
2214                            line: 0,
2215                            character: 3,
2216                        },
2217                    },
2218                    new_text: "def",
2219                    insert_text_format: None,
2220                    annotation_id: None,
2221                },
2222            ]
2223        "#]],
2224        );
2225    }
2226
2227    #[test]
2228    fn snippet_rendering_tabstop_after_text_edit() {
2229        let mut edit = TextEdit::builder();
2230        edit.insert(0.into(), "abc".to_owned());
2231        let edit = edit.finish();
2232        // Note: tabstops are positioned in the source where all text edits have been applied
2233        let snippets = SnippetEdit::new(vec![Snippet::Tabstop(10.into())]);
2234
2235        check_rendered_snippets(
2236            edit,
2237            snippets,
2238            expect![[r#"
2239            [
2240                SnippetTextEdit {
2241                    range: Range {
2242                        start: Position {
2243                            line: 0,
2244                            character: 0,
2245                        },
2246                        end: Position {
2247                            line: 0,
2248                            character: 0,
2249                        },
2250                    },
2251                    new_text: "abc",
2252                    insert_text_format: None,
2253                    annotation_id: None,
2254                },
2255                SnippetTextEdit {
2256                    range: Range {
2257                        start: Position {
2258                            line: 0,
2259                            character: 7,
2260                        },
2261                        end: Position {
2262                            line: 0,
2263                            character: 7,
2264                        },
2265                    },
2266                    new_text: "$0",
2267                    insert_text_format: Some(
2268                        Snippet,
2269                    ),
2270                    annotation_id: None,
2271                },
2272            ]
2273        "#]],
2274        );
2275    }
2276
2277    #[test]
2278    fn snippet_rendering_tabstops_before_text_edit() {
2279        let mut edit = TextEdit::builder();
2280        edit.insert(2.into(), "abc".to_owned());
2281        let edit = edit.finish();
2282        let snippets =
2283            SnippetEdit::new(vec![Snippet::Tabstop(0.into()), Snippet::Tabstop(0.into())]);
2284
2285        check_rendered_snippets(
2286            edit,
2287            snippets,
2288            expect![[r#"
2289                [
2290                    SnippetTextEdit {
2291                        range: Range {
2292                            start: Position {
2293                                line: 0,
2294                                character: 0,
2295                            },
2296                            end: Position {
2297                                line: 0,
2298                                character: 0,
2299                            },
2300                        },
2301                        new_text: "$1",
2302                        insert_text_format: Some(
2303                            Snippet,
2304                        ),
2305                        annotation_id: None,
2306                    },
2307                    SnippetTextEdit {
2308                        range: Range {
2309                            start: Position {
2310                                line: 0,
2311                                character: 0,
2312                            },
2313                            end: Position {
2314                                line: 0,
2315                                character: 0,
2316                            },
2317                        },
2318                        new_text: "$0",
2319                        insert_text_format: Some(
2320                            Snippet,
2321                        ),
2322                        annotation_id: None,
2323                    },
2324                    SnippetTextEdit {
2325                        range: Range {
2326                            start: Position {
2327                                line: 0,
2328                                character: 2,
2329                            },
2330                            end: Position {
2331                                line: 0,
2332                                character: 2,
2333                            },
2334                        },
2335                        new_text: "abc",
2336                        insert_text_format: None,
2337                        annotation_id: None,
2338                    },
2339                ]
2340            "#]],
2341        );
2342    }
2343
2344    #[test]
2345    fn snippet_rendering_tabstops_between_text_edits() {
2346        let mut edit = TextEdit::builder();
2347        edit.insert(0.into(), "abc".to_owned());
2348        edit.insert(7.into(), "abc".to_owned());
2349        let edit = edit.finish();
2350        // Note: tabstops are positioned in the source where all text edits have been applied
2351        let snippets =
2352            SnippetEdit::new(vec![Snippet::Tabstop(7.into()), Snippet::Tabstop(7.into())]);
2353
2354        check_rendered_snippets(
2355            edit,
2356            snippets,
2357            expect![[r#"
2358            [
2359                SnippetTextEdit {
2360                    range: Range {
2361                        start: Position {
2362                            line: 0,
2363                            character: 0,
2364                        },
2365                        end: Position {
2366                            line: 0,
2367                            character: 0,
2368                        },
2369                    },
2370                    new_text: "abc",
2371                    insert_text_format: None,
2372                    annotation_id: None,
2373                },
2374                SnippetTextEdit {
2375                    range: Range {
2376                        start: Position {
2377                            line: 0,
2378                            character: 4,
2379                        },
2380                        end: Position {
2381                            line: 0,
2382                            character: 4,
2383                        },
2384                    },
2385                    new_text: "$1",
2386                    insert_text_format: Some(
2387                        Snippet,
2388                    ),
2389                    annotation_id: None,
2390                },
2391                SnippetTextEdit {
2392                    range: Range {
2393                        start: Position {
2394                            line: 0,
2395                            character: 4,
2396                        },
2397                        end: Position {
2398                            line: 0,
2399                            character: 4,
2400                        },
2401                    },
2402                    new_text: "$0",
2403                    insert_text_format: Some(
2404                        Snippet,
2405                    ),
2406                    annotation_id: None,
2407                },
2408                SnippetTextEdit {
2409                    range: Range {
2410                        start: Position {
2411                            line: 0,
2412                            character: 7,
2413                        },
2414                        end: Position {
2415                            line: 0,
2416                            character: 7,
2417                        },
2418                    },
2419                    new_text: "abc",
2420                    insert_text_format: None,
2421                    annotation_id: None,
2422                },
2423            ]
2424        "#]],
2425        );
2426    }
2427
2428    #[test]
2429    fn snippet_rendering_multiple_tabstops_in_text_edit() {
2430        let mut edit = TextEdit::builder();
2431        edit.insert(0.into(), "abcdefghijkl".to_owned());
2432        let edit = edit.finish();
2433        let snippets = SnippetEdit::new(vec![
2434            Snippet::Tabstop(0.into()),
2435            Snippet::Tabstop(5.into()),
2436            Snippet::Tabstop(12.into()),
2437        ]);
2438
2439        check_rendered_snippets(
2440            edit,
2441            snippets,
2442            expect![[r#"
2443            [
2444                SnippetTextEdit {
2445                    range: Range {
2446                        start: Position {
2447                            line: 0,
2448                            character: 0,
2449                        },
2450                        end: Position {
2451                            line: 0,
2452                            character: 0,
2453                        },
2454                    },
2455                    new_text: "$1abcde$2fghijkl$0",
2456                    insert_text_format: Some(
2457                        Snippet,
2458                    ),
2459                    annotation_id: None,
2460                },
2461            ]
2462        "#]],
2463        );
2464    }
2465
2466    #[test]
2467    fn snippet_rendering_multiple_placeholders_in_text_edit() {
2468        let mut edit = TextEdit::builder();
2469        edit.insert(0.into(), "abcdefghijkl".to_owned());
2470        let edit = edit.finish();
2471        let snippets = SnippetEdit::new(vec![
2472            Snippet::Placeholder(TextRange::new(0.into(), 3.into())),
2473            Snippet::Placeholder(TextRange::new(5.into(), 7.into())),
2474            Snippet::Placeholder(TextRange::new(10.into(), 12.into())),
2475        ]);
2476
2477        check_rendered_snippets(
2478            edit,
2479            snippets,
2480            expect![[r#"
2481            [
2482                SnippetTextEdit {
2483                    range: Range {
2484                        start: Position {
2485                            line: 0,
2486                            character: 0,
2487                        },
2488                        end: Position {
2489                            line: 0,
2490                            character: 0,
2491                        },
2492                    },
2493                    new_text: "${1:abc}de${2:fg}hij${0:kl}",
2494                    insert_text_format: Some(
2495                        Snippet,
2496                    ),
2497                    annotation_id: None,
2498                },
2499            ]
2500        "#]],
2501        );
2502    }
2503
2504    #[test]
2505    fn snippet_rendering_escape_snippet_bits() {
2506        // only needed for snippet formats
2507        let mut edit = TextEdit::builder();
2508        edit.insert(0.into(), r"$ab{}$c\def".to_owned());
2509        edit.insert(8.into(), r"ghi\jk<-check_insert_here$".to_owned());
2510        edit.insert(10.into(), r"a\\b\\c{}$".to_owned());
2511        let edit = edit.finish();
2512        let snippets = SnippetEdit::new(vec![
2513            Snippet::Placeholder(TextRange::new(1.into(), 9.into())),
2514            Snippet::Tabstop(25.into()),
2515        ]);
2516
2517        check_rendered_snippets(
2518            edit,
2519            snippets,
2520            expect![[r#"
2521                [
2522                    SnippetTextEdit {
2523                        range: Range {
2524                            start: Position {
2525                                line: 0,
2526                                character: 0,
2527                            },
2528                            end: Position {
2529                                line: 0,
2530                                character: 0,
2531                            },
2532                        },
2533                        new_text: "\\$${1:ab{\\}\\$c\\\\d}ef",
2534                        insert_text_format: Some(
2535                            Snippet,
2536                        ),
2537                        annotation_id: None,
2538                    },
2539                    SnippetTextEdit {
2540                        range: Range {
2541                            start: Position {
2542                                line: 0,
2543                                character: 8,
2544                            },
2545                            end: Position {
2546                                line: 0,
2547                                character: 8,
2548                            },
2549                        },
2550                        new_text: "ghi\\\\jk$0<-check_insert_here\\$",
2551                        insert_text_format: Some(
2552                            Snippet,
2553                        ),
2554                        annotation_id: None,
2555                    },
2556                    SnippetTextEdit {
2557                        range: Range {
2558                            start: Position {
2559                                line: 0,
2560                                character: 10,
2561                            },
2562                            end: Position {
2563                                line: 0,
2564                                character: 10,
2565                            },
2566                        },
2567                        new_text: "a\\\\b\\\\c{}$",
2568                        insert_text_format: None,
2569                        annotation_id: None,
2570                    },
2571                ]
2572            "#]],
2573        );
2574    }
2575
2576    #[test]
2577    fn snippet_rendering_tabstop_adjust_offset_deleted() {
2578        // negative offset from inserting a smaller range
2579        let mut edit = TextEdit::builder();
2580        edit.replace(TextRange::new(47.into(), 56.into()), "let".to_owned());
2581        edit.replace(
2582            TextRange::new(57.into(), 89.into()),
2583            "disabled = false;\n    ProcMacro {\n        disabled,\n    }".to_owned(),
2584        );
2585        let edit = edit.finish();
2586        let snippets = SnippetEdit::new(vec![Snippet::Tabstop(51.into())]);
2587
2588        check_rendered_snippets_in_source(
2589            r"
2590fn expander_to_proc_macro() -> ProcMacro {
2591    ProcMacro {
2592        disabled: false,
2593    }
2594}
2595
2596struct ProcMacro {
2597    disabled: bool,
2598}",
2599            edit,
2600            snippets,
2601            expect![[r#"
2602                [
2603                    SnippetTextEdit {
2604                        range: Range {
2605                            start: Position {
2606                                line: 1,
2607                                character: 4,
2608                            },
2609                            end: Position {
2610                                line: 1,
2611                                character: 13,
2612                            },
2613                        },
2614                        new_text: "let",
2615                        insert_text_format: None,
2616                        annotation_id: None,
2617                    },
2618                    SnippetTextEdit {
2619                        range: Range {
2620                            start: Position {
2621                                line: 1,
2622                                character: 14,
2623                            },
2624                            end: Position {
2625                                line: 3,
2626                                character: 5,
2627                            },
2628                        },
2629                        new_text: "$0disabled = false;\n    ProcMacro {\n        disabled,\n    \\}",
2630                        insert_text_format: Some(
2631                            Snippet,
2632                        ),
2633                        annotation_id: None,
2634                    },
2635                ]
2636            "#]],
2637        );
2638    }
2639
2640    #[test]
2641    fn snippet_rendering_tabstop_adjust_offset_added() {
2642        // positive offset from inserting a larger range
2643        let mut edit = TextEdit::builder();
2644        edit.replace(TextRange::new(39.into(), 40.into()), "let".to_owned());
2645        edit.replace(
2646            TextRange::new(41.into(), 73.into()),
2647            "disabled = false;\n    ProcMacro {\n        disabled,\n    }".to_owned(),
2648        );
2649        let edit = edit.finish();
2650        let snippets = SnippetEdit::new(vec![Snippet::Tabstop(43.into())]);
2651
2652        check_rendered_snippets_in_source(
2653            r"
2654fn expander_to_proc_macro() -> P {
2655    P {
2656        disabled: false,
2657    }
2658}
2659
2660struct P {
2661    disabled: bool,
2662}",
2663            edit,
2664            snippets,
2665            expect![[r#"
2666                [
2667                    SnippetTextEdit {
2668                        range: Range {
2669                            start: Position {
2670                                line: 1,
2671                                character: 4,
2672                            },
2673                            end: Position {
2674                                line: 1,
2675                                character: 5,
2676                            },
2677                        },
2678                        new_text: "let",
2679                        insert_text_format: None,
2680                        annotation_id: None,
2681                    },
2682                    SnippetTextEdit {
2683                        range: Range {
2684                            start: Position {
2685                                line: 1,
2686                                character: 6,
2687                            },
2688                            end: Position {
2689                                line: 3,
2690                                character: 5,
2691                            },
2692                        },
2693                        new_text: "$0disabled = false;\n    ProcMacro {\n        disabled,\n    \\}",
2694                        insert_text_format: Some(
2695                            Snippet,
2696                        ),
2697                        annotation_id: None,
2698                    },
2699                ]
2700            "#]],
2701        );
2702    }
2703
2704    #[test]
2705    fn snippet_rendering_placeholder_adjust_offset_deleted() {
2706        // negative offset from inserting a smaller range
2707        let mut edit = TextEdit::builder();
2708        edit.replace(TextRange::new(47.into(), 56.into()), "let".to_owned());
2709        edit.replace(
2710            TextRange::new(57.into(), 89.into()),
2711            "disabled = false;\n    ProcMacro {\n        disabled,\n    }".to_owned(),
2712        );
2713        let edit = edit.finish();
2714        let snippets =
2715            SnippetEdit::new(vec![Snippet::Placeholder(TextRange::new(51.into(), 59.into()))]);
2716
2717        check_rendered_snippets_in_source(
2718            r"
2719fn expander_to_proc_macro() -> ProcMacro {
2720    ProcMacro {
2721        disabled: false,
2722    }
2723}
2724
2725struct ProcMacro {
2726    disabled: bool,
2727}",
2728            edit,
2729            snippets,
2730            expect![[r#"
2731                [
2732                    SnippetTextEdit {
2733                        range: Range {
2734                            start: Position {
2735                                line: 1,
2736                                character: 4,
2737                            },
2738                            end: Position {
2739                                line: 1,
2740                                character: 13,
2741                            },
2742                        },
2743                        new_text: "let",
2744                        insert_text_format: None,
2745                        annotation_id: None,
2746                    },
2747                    SnippetTextEdit {
2748                        range: Range {
2749                            start: Position {
2750                                line: 1,
2751                                character: 14,
2752                            },
2753                            end: Position {
2754                                line: 3,
2755                                character: 5,
2756                            },
2757                        },
2758                        new_text: "${0:disabled} = false;\n    ProcMacro {\n        disabled,\n    \\}",
2759                        insert_text_format: Some(
2760                            Snippet,
2761                        ),
2762                        annotation_id: None,
2763                    },
2764                ]
2765            "#]],
2766        );
2767    }
2768
2769    #[test]
2770    fn snippet_rendering_placeholder_adjust_offset_added() {
2771        // positive offset from inserting a larger range
2772        let mut edit = TextEdit::builder();
2773        edit.replace(TextRange::new(39.into(), 40.into()), "let".to_owned());
2774        edit.replace(
2775            TextRange::new(41.into(), 73.into()),
2776            "disabled = false;\n    ProcMacro {\n        disabled,\n    }".to_owned(),
2777        );
2778        let edit = edit.finish();
2779        let snippets =
2780            SnippetEdit::new(vec![Snippet::Placeholder(TextRange::new(43.into(), 51.into()))]);
2781
2782        check_rendered_snippets_in_source(
2783            r"
2784fn expander_to_proc_macro() -> P {
2785    P {
2786        disabled: false,
2787    }
2788}
2789
2790struct P {
2791    disabled: bool,
2792}",
2793            edit,
2794            snippets,
2795            expect![[r#"
2796                [
2797                    SnippetTextEdit {
2798                        range: Range {
2799                            start: Position {
2800                                line: 1,
2801                                character: 4,
2802                            },
2803                            end: Position {
2804                                line: 1,
2805                                character: 5,
2806                            },
2807                        },
2808                        new_text: "let",
2809                        insert_text_format: None,
2810                        annotation_id: None,
2811                    },
2812                    SnippetTextEdit {
2813                        range: Range {
2814                            start: Position {
2815                                line: 1,
2816                                character: 6,
2817                            },
2818                            end: Position {
2819                                line: 3,
2820                                character: 5,
2821                            },
2822                        },
2823                        new_text: "${0:disabled} = false;\n    ProcMacro {\n        disabled,\n    \\}",
2824                        insert_text_format: Some(
2825                            Snippet,
2826                        ),
2827                        annotation_id: None,
2828                    },
2829                ]
2830            "#]],
2831        );
2832    }
2833
2834    #[test]
2835    fn snippet_rendering_tabstop_adjust_offset_between_text_edits() {
2836        // inserting between edits, tabstop should be at (1, 14)
2837        let mut edit = TextEdit::builder();
2838        edit.replace(TextRange::new(47.into(), 56.into()), "let".to_owned());
2839        edit.replace(
2840            TextRange::new(58.into(), 90.into()),
2841            "disabled = false;\n    ProcMacro {\n        disabled,\n    }".to_owned(),
2842        );
2843        let edit = edit.finish();
2844        let snippets = SnippetEdit::new(vec![Snippet::Tabstop(51.into())]);
2845
2846        // add an extra space between `ProcMacro` and `{` to insert the tabstop at
2847        check_rendered_snippets_in_source(
2848            r"
2849fn expander_to_proc_macro() -> ProcMacro {
2850    ProcMacro  {
2851        disabled: false,
2852    }
2853}
2854
2855struct ProcMacro {
2856    disabled: bool,
2857}",
2858            edit,
2859            snippets,
2860            expect![[r#"
2861    [
2862        SnippetTextEdit {
2863            range: Range {
2864                start: Position {
2865                    line: 1,
2866                    character: 4,
2867                },
2868                end: Position {
2869                    line: 1,
2870                    character: 13,
2871                },
2872            },
2873            new_text: "let",
2874            insert_text_format: None,
2875            annotation_id: None,
2876        },
2877        SnippetTextEdit {
2878            range: Range {
2879                start: Position {
2880                    line: 1,
2881                    character: 14,
2882                },
2883                end: Position {
2884                    line: 1,
2885                    character: 14,
2886                },
2887            },
2888            new_text: "$0",
2889            insert_text_format: Some(
2890                Snippet,
2891            ),
2892            annotation_id: None,
2893        },
2894        SnippetTextEdit {
2895            range: Range {
2896                start: Position {
2897                    line: 1,
2898                    character: 15,
2899                },
2900                end: Position {
2901                    line: 3,
2902                    character: 5,
2903                },
2904            },
2905            new_text: "disabled = false;\n    ProcMacro {\n        disabled,\n    }",
2906            insert_text_format: None,
2907            annotation_id: None,
2908        },
2909    ]
2910"#]],
2911        );
2912    }
2913
2914    #[test]
2915    fn snippet_rendering_tabstop_adjust_offset_after_text_edits() {
2916        // inserting after edits, tabstop should be before the closing curly of the fn
2917        let mut edit = TextEdit::builder();
2918        edit.replace(TextRange::new(47.into(), 56.into()), "let".to_owned());
2919        edit.replace(
2920            TextRange::new(57.into(), 89.into()),
2921            "disabled = false;\n    ProcMacro {\n        disabled,\n    }".to_owned(),
2922        );
2923        let edit = edit.finish();
2924        let snippets = SnippetEdit::new(vec![Snippet::Tabstop(109.into())]);
2925
2926        check_rendered_snippets_in_source(
2927            r"
2928fn expander_to_proc_macro() -> ProcMacro {
2929    ProcMacro {
2930        disabled: false,
2931    }
2932}
2933
2934struct ProcMacro {
2935    disabled: bool,
2936}",
2937            edit,
2938            snippets,
2939            expect![[r#"
2940    [
2941        SnippetTextEdit {
2942            range: Range {
2943                start: Position {
2944                    line: 1,
2945                    character: 4,
2946                },
2947                end: Position {
2948                    line: 1,
2949                    character: 13,
2950                },
2951            },
2952            new_text: "let",
2953            insert_text_format: None,
2954            annotation_id: None,
2955        },
2956        SnippetTextEdit {
2957            range: Range {
2958                start: Position {
2959                    line: 1,
2960                    character: 14,
2961                },
2962                end: Position {
2963                    line: 3,
2964                    character: 5,
2965                },
2966            },
2967            new_text: "disabled = false;\n    ProcMacro {\n        disabled,\n    }",
2968            insert_text_format: None,
2969            annotation_id: None,
2970        },
2971        SnippetTextEdit {
2972            range: Range {
2973                start: Position {
2974                    line: 4,
2975                    character: 0,
2976                },
2977                end: Position {
2978                    line: 4,
2979                    character: 0,
2980                },
2981            },
2982            new_text: "$0",
2983            insert_text_format: Some(
2984                Snippet,
2985            ),
2986            annotation_id: None,
2987        },
2988    ]
2989"#]],
2990        );
2991    }
2992
2993    #[test]
2994    fn snippet_rendering_handle_dos_line_endings() {
2995        // unix -> dos conversion should be handled after placing snippets
2996        let mut edit = TextEdit::builder();
2997        edit.insert(6.into(), "\n\n->".to_owned());
2998
2999        let edit = edit.finish();
3000        let snippets = SnippetEdit::new(vec![Snippet::Tabstop(10.into())]);
3001
3002        check_rendered_snippets_in_source(
3003            "yeah\r\n<-tabstop here",
3004            edit,
3005            snippets,
3006            expect![[r#"
3007            [
3008                SnippetTextEdit {
3009                    range: Range {
3010                        start: Position {
3011                            line: 1,
3012                            character: 0,
3013                        },
3014                        end: Position {
3015                            line: 1,
3016                            character: 0,
3017                        },
3018                    },
3019                    new_text: "\r\n\r\n->$0",
3020                    insert_text_format: Some(
3021                        Snippet,
3022                    ),
3023                    annotation_id: None,
3024                },
3025            ]
3026        "#]],
3027        )
3028    }
3029
3030    // `Url` is not able to parse windows paths on unix machines.
3031    #[test]
3032    #[cfg(target_os = "windows")]
3033    fn test_lowercase_drive_letter() {
3034        use paths::Utf8Path;
3035
3036        let url = url_from_abs_path(Utf8Path::new("C:\\Test").try_into().unwrap());
3037        assert_eq!(url.to_string(), "file:///c:/Test");
3038
3039        let url = url_from_abs_path(Utf8Path::new(r#"\\localhost\C$\my_dir"#).try_into().unwrap());
3040        assert_eq!(url.to_string(), "file://localhost/C$/my_dir");
3041    }
3042}