rust_analyzer/lsp/
to_proto.rs

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