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