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