Skip to main content

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