]> git.lizzy.rs Git - rust.git/blobdiff - crates/rust-analyzer/src/to_proto.rs
fix: make signature info response conform to spec
[rust.git] / crates / rust-analyzer / src / to_proto.rs
index 7428a3043ba1944198ddd770e565edb159127f2f..9e94793a6187e64bcf95065f66c0e91b70d74fc0 100644 (file)
@@ -1,7 +1,7 @@
 //! Conversion of rust-analyzer specific types to lsp_types equivalents.
 use std::{
     iter::once,
-    path::{self, Path},
+    path,
     sync::atomic::{AtomicU32, Ordering},
 };
 
@@ -9,17 +9,21 @@
     Annotation, AnnotationKind, Assist, AssistKind, CallInfo, Cancellable, CompletionItem,
     CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit,
     Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, InlayHint,
-    InlayKind, InsertTextFormat, Markup, NavigationTarget, ReferenceAccess, RenameError, Runnable,
-    Severity, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize,
+    InlayKind, Markup, NavigationTarget, ReferenceCategory, RenameError, Runnable, Severity,
+    SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize,
 };
 use itertools::Itertools;
 use serde_json::to_value;
+use vfs::AbsPath;
 
 use crate::{
     cargo_target_spec::CargoTargetSpec,
+    config::Config,
     global_state::GlobalStateSnapshot,
     line_index::{LineEndings, LineIndex, OffsetEncoding},
-    lsp_ext, semantic_tokens, Result,
+    lsp_ext,
+    lsp_utils::invalid_params_error,
+    semantic_tokens, Result,
 };
 
 pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position {
@@ -71,11 +75,11 @@ pub(crate) fn structure_node_kind(kind: StructureNodeKind) -> lsp_types::SymbolK
 }
 
 pub(crate) fn document_highlight_kind(
-    reference_access: ReferenceAccess,
+    category: ReferenceCategory,
 ) -> lsp_types::DocumentHighlightKind {
-    match reference_access {
-        ReferenceAccess::Read => lsp_types::DocumentHighlightKind::Read,
-        ReferenceAccess::Write => lsp_types::DocumentHighlightKind::Write,
+    match category {
+        ReferenceCategory::Read => lsp_types::DocumentHighlightKind::Read,
+        ReferenceCategory::Write => lsp_types::DocumentHighlightKind::Write,
     }
 }
 
@@ -92,15 +96,6 @@ pub(crate) fn documentation(documentation: Documentation) -> lsp_types::Document
     lsp_types::Documentation::MarkupContent(markup_content)
 }
 
-pub(crate) fn insert_text_format(
-    insert_text_format: InsertTextFormat,
-) -> lsp_types::InsertTextFormat {
-    match insert_text_format {
-        InsertTextFormat::Snippet => lsp_types::InsertTextFormat::Snippet,
-        InsertTextFormat::PlainText => lsp_types::InsertTextFormat::PlainText,
-    }
-}
-
 pub(crate) fn completion_item_kind(
     completion_item_kind: CompletionItemKind,
 ) -> lsp_types::CompletionItemKind {
@@ -197,36 +192,57 @@ pub(crate) fn snippet_text_edit_vec(
         .collect()
 }
 
-pub(crate) fn completion_item(
-    insert_replace_support: Option<lsp_types::Position>,
+pub(crate) fn completion_items(
+    config: &Config,
     line_index: &LineIndex,
-    item: CompletionItem,
+    tdpp: lsp_types::TextDocumentPositionParams,
+    items: Vec<CompletionItem>,
 ) -> Vec<lsp_types::CompletionItem> {
+    let max_relevance = items.iter().map(|it| it.relevance().score()).max().unwrap_or_default();
+    let mut res = Vec::with_capacity(items.len());
+    for item in items {
+        completion_item(&mut res, config, line_index, &tdpp, max_relevance, item)
+    }
+    res
+}
+
+fn completion_item(
+    acc: &mut Vec<lsp_types::CompletionItem>,
+    config: &Config,
+    line_index: &LineIndex,
+    tdpp: &lsp_types::TextDocumentPositionParams,
+    max_relevance: u32,
+    item: CompletionItem,
+) {
     let mut additional_text_edits = Vec::new();
-    let mut text_edit = None;
+
     // LSP does not allow arbitrary edits in completion, so we have to do a
     // non-trivial mapping here.
-    let source_range = item.source_range();
-    for indel in item.text_edit().iter() {
-        if indel.delete.contains_range(source_range) {
-            text_edit = Some(if indel.delete == source_range {
-                self::completion_text_edit(line_index, insert_replace_support, indel.clone())
+    let text_edit = {
+        let mut text_edit = None;
+        let source_range = item.source_range();
+        for indel in item.text_edit().iter() {
+            if indel.delete.contains_range(source_range) {
+                let insert_replace_support = config.insert_replace_support().then(|| tdpp.position);
+                text_edit = Some(if indel.delete == source_range {
+                    self::completion_text_edit(line_index, insert_replace_support, indel.clone())
+                } else {
+                    assert!(source_range.end() == indel.delete.end());
+                    let range1 = TextRange::new(indel.delete.start(), source_range.start());
+                    let range2 = source_range;
+                    let indel1 = Indel::replace(range1, String::new());
+                    let indel2 = Indel::replace(range2, indel.insert.clone());
+                    additional_text_edits.push(self::text_edit(line_index, indel1));
+                    self::completion_text_edit(line_index, insert_replace_support, indel2)
+                })
             } else {
-                assert!(source_range.end() == indel.delete.end());
-                let range1 = TextRange::new(indel.delete.start(), source_range.start());
-                let range2 = source_range;
-                let indel1 = Indel::replace(range1, String::new());
-                let indel2 = Indel::replace(range2, indel.insert.clone());
-                additional_text_edits.push(self::text_edit(line_index, indel1));
-                self::completion_text_edit(line_index, insert_replace_support, indel2)
-            })
-        } else {
-            assert!(source_range.intersect(indel.delete).is_none());
-            let text_edit = self::text_edit(line_index, indel.clone());
-            additional_text_edits.push(text_edit);
+                assert!(source_range.intersect(indel.delete).is_none());
+                let text_edit = self::text_edit(line_index, indel.clone());
+                additional_text_edits.push(text_edit);
+            }
         }
-    }
-    let text_edit = text_edit.unwrap();
+        text_edit.unwrap()
+    };
 
     let mut lsp_item = lsp_types::CompletionItem {
         label: item.label().to_string(),
@@ -240,52 +256,74 @@ pub(crate) fn completion_item(
         ..Default::default()
     };
 
-    fn set_score(res: &mut lsp_types::CompletionItem, relevance: CompletionRelevance) {
-        if relevance.is_relevant() {
-            res.preselect = Some(true);
-        }
-        // The relevance needs to be inverted to come up with a sort score
-        // because the client will sort ascending.
-        let sort_score = relevance.score() ^ 0xFF_FF_FF_FF;
-        // Zero pad the string to ensure values can be properly sorted
-        // by the client. Hex format is used because it is easier to
-        // visually compare very large values, which the sort text
-        // tends to be since it is the opposite of the score.
-        res.sort_text = Some(format!("{:08x}", sort_score));
-    }
-
-    set_score(&mut lsp_item, item.relevance());
+    set_score(&mut lsp_item, max_relevance, item.relevance());
 
     if item.deprecated() {
         lsp_item.tags = Some(vec![lsp_types::CompletionItemTag::Deprecated])
     }
 
-    if item.trigger_call_info() {
+    if item.trigger_call_info() && config.client_commands().trigger_parameter_hints {
         lsp_item.command = Some(command::trigger_parameter_hints());
     }
 
-    let mut res = match item.ref_match() {
-        Some((mutability, relevance)) => {
-            let mut lsp_item_with_ref = lsp_item.clone();
-            set_score(&mut lsp_item_with_ref, relevance);
-            lsp_item_with_ref.label =
-                format!("&{}{}", mutability.as_keyword_for_ref(), lsp_item_with_ref.label);
-            if let Some(it) = &mut lsp_item_with_ref.text_edit {
-                let new_text = match it {
-                    lsp_types::CompletionTextEdit::Edit(it) => &mut it.new_text,
-                    lsp_types::CompletionTextEdit::InsertAndReplace(it) => &mut it.new_text,
-                };
-                *new_text = format!("&{}{}", mutability.as_keyword_for_ref(), new_text);
+    if item.is_snippet() {
+        lsp_item.insert_text_format = Some(lsp_types::InsertTextFormat::Snippet);
+    }
+    if config.completion().enable_imports_on_the_fly {
+        if let imports @ [_, ..] = item.imports_to_add() {
+            let imports: Vec<_> = imports
+                .iter()
+                .filter_map(|import_edit| {
+                    let import_path = &import_edit.import.import_path;
+                    let import_name = import_path.segments().last()?;
+                    Some(lsp_ext::CompletionImport {
+                        full_import_path: import_path.to_string(),
+                        imported_name: import_name.to_string(),
+                    })
+                })
+                .collect();
+            if !imports.is_empty() {
+                let data = lsp_ext::CompletionResolveData { position: tdpp.clone(), imports };
+                lsp_item.data = Some(to_value(data).unwrap());
             }
-            vec![lsp_item_with_ref, lsp_item]
         }
-        None => vec![lsp_item],
+    }
+
+    if let Some((mutability, relevance)) = item.ref_match() {
+        let mut lsp_item_with_ref = lsp_item.clone();
+        set_score(&mut lsp_item_with_ref, max_relevance, relevance);
+        lsp_item_with_ref.label =
+            format!("&{}{}", mutability.as_keyword_for_ref(), lsp_item_with_ref.label);
+        if let Some(it) = &mut lsp_item_with_ref.text_edit {
+            let new_text = match it {
+                lsp_types::CompletionTextEdit::Edit(it) => &mut it.new_text,
+                lsp_types::CompletionTextEdit::InsertAndReplace(it) => &mut it.new_text,
+            };
+            *new_text = format!("&{}{}", mutability.as_keyword_for_ref(), new_text);
+        }
+
+        acc.push(lsp_item_with_ref);
     };
 
-    for lsp_item in res.iter_mut() {
-        lsp_item.insert_text_format = Some(insert_text_format(item.insert_text_format()));
+    acc.push(lsp_item);
+
+    fn set_score(
+        res: &mut lsp_types::CompletionItem,
+        max_relevance: u32,
+        relevance: CompletionRelevance,
+    ) {
+        if relevance.is_relevant() && relevance.score() == max_relevance {
+            res.preselect = Some(true);
+        }
+        // The relevance needs to be inverted to come up with a sort score
+        // because the client will sort ascending.
+        let sort_score = relevance.score() ^ 0xFF_FF_FF_FF;
+        // Zero pad the string to ensure values can be properly sorted
+        // by the client. Hex format is used because it is easier to
+        // visually compare very large values, which the sort text
+        // tends to be since it is the opposite of the score.
+        res.sort_text = Some(format!("{:08x}", sort_score));
     }
-    res
 }
 
 pub(crate) fn signature_help(
@@ -361,7 +399,7 @@ pub(crate) fn signature_help(
     };
     lsp_types::SignatureHelp {
         signatures: vec![signature],
-        active_signature: None,
+        active_signature: Some(0),
         active_parameter,
     }
 }
@@ -405,7 +443,7 @@ pub(crate) fn semantic_tokens(
                 text_range =
                     TextRange::new(text_range.start(), text_range.end() - TextSize::of('\n'));
             }
-            let range = range(&line_index, text_range);
+            let range = range(line_index, text_range);
             builder.push(range, token_index, modifier_bitset);
         }
     }
@@ -464,6 +502,7 @@ fn semantic_token_type_and_modifiers(
         },
         HlTag::Attribute => semantic_tokens::ATTRIBUTE,
         HlTag::BoolLiteral => semantic_tokens::BOOLEAN,
+        HlTag::BuiltinAttr => semantic_tokens::BUILTIN_ATTRIBUTE,
         HlTag::BuiltinType => semantic_tokens::BUILTIN_TYPE,
         HlTag::ByteLiteral | HlTag::NumericLiteral => lsp_types::SemanticTokenType::NUMBER,
         HlTag::CharLiteral => semantic_tokens::CHAR,
@@ -496,21 +535,25 @@ fn semantic_token_type_and_modifiers(
 
     for modifier in highlight.mods.iter() {
         let modifier = match modifier {
+            HlMod::Associated => continue,
+            HlMod::Async => semantic_tokens::ASYNC,
             HlMod::Attribute => semantic_tokens::ATTRIBUTE_MODIFIER,
+            HlMod::Callable => semantic_tokens::CALLABLE,
+            HlMod::Consuming => semantic_tokens::CONSUMING,
+            HlMod::ControlFlow => semantic_tokens::CONTROL_FLOW,
+            HlMod::CrateRoot => semantic_tokens::CRATE_ROOT,
+            HlMod::DefaultLibrary => lsp_types::SemanticTokenModifier::DEFAULT_LIBRARY,
             HlMod::Definition => lsp_types::SemanticTokenModifier::DECLARATION,
             HlMod::Documentation => lsp_types::SemanticTokenModifier::DOCUMENTATION,
             HlMod::Injected => semantic_tokens::INJECTED,
-            HlMod::ControlFlow => semantic_tokens::CONTROL_FLOW,
-            HlMod::Mutable => semantic_tokens::MUTABLE,
-            HlMod::Consuming => semantic_tokens::CONSUMING,
-            HlMod::Async => semantic_tokens::ASYNC,
+            HlMod::IntraDocLink => semantic_tokens::INTRA_DOC_LINK,
             HlMod::Library => semantic_tokens::LIBRARY,
-            HlMod::Unsafe => semantic_tokens::UNSAFE,
-            HlMod::Callable => semantic_tokens::CALLABLE,
+            HlMod::Mutable => semantic_tokens::MUTABLE,
+            HlMod::Public => semantic_tokens::PUBLIC,
+            HlMod::Reference => semantic_tokens::REFERENCE,
             HlMod::Static => lsp_types::SemanticTokenModifier::STATIC,
-            HlMod::IntraDocLink => semantic_tokens::INTRA_DOC_LINK,
             HlMod::Trait => semantic_tokens::TRAIT_MODIFIER,
-            HlMod::Associated => continue,
+            HlMod::Unsafe => semantic_tokens::UNSAFE,
         };
         mods |= modifier;
     }
@@ -582,10 +625,9 @@ pub(crate) fn url(snap: &GlobalStateSnapshot, file_id: FileId) -> lsp_types::Url
 /// This will only happen when processing windows paths.
 ///
 /// When processing non-windows path, this is essentially the same as `Url::from_file_path`.
-pub(crate) fn url_from_abs_path(path: &Path) -> lsp_types::Url {
-    assert!(path.is_absolute());
+pub(crate) fn url_from_abs_path(path: &AbsPath) -> lsp_types::Url {
     let url = lsp_types::Url::from_file_path(path).unwrap();
-    match path.components().next() {
+    match path.as_ref().components().next() {
         Some(path::Component::Prefix(prefix))
             if matches!(prefix.kind(), path::Prefix::Disk(_) | path::Prefix::VerbatimDisk(_)) =>
         {
@@ -781,7 +823,7 @@ pub(crate) fn snippet_workspace_edit(
         document_changes.extend_from_slice(&ops);
     }
     for (file_id, edit) in source_change.source_file_edits {
-        let edit = snippet_text_document_edit(&snap, source_change.is_snippet, file_id, edit)?;
+        let edit = snippet_text_document_edit(snap, source_change.is_snippet, file_id, edit)?;
         document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit));
     }
     let mut workspace_edit = lsp_ext::SnippetWorkspaceEdit {
@@ -948,27 +990,48 @@ pub(crate) fn runnable(
 }
 
 pub(crate) fn code_lens(
+    acc: &mut Vec<lsp_types::CodeLens>,
     snap: &GlobalStateSnapshot,
     annotation: Annotation,
-) -> Result<lsp_types::CodeLens> {
+) -> Result<()> {
+    let client_commands_config = snap.config.client_commands();
     match annotation.kind {
-        AnnotationKind::Runnable { debug, runnable: run } => {
+        AnnotationKind::Runnable(run) => {
             let line_index = snap.file_line_index(run.nav.file_id)?;
             let annotation_range = range(&line_index, annotation.range);
 
-            let action = run.action();
-            let r = runnable(&snap, run)?;
-
-            let command = if debug {
-                command::debug_single(&r)
-            } else {
-                let title = action.run_title.to_string();
-                command::run_single(&r, &title)
+            let title = run.title();
+            let can_debug = match run.kind {
+                ide::RunnableKind::DocTest { .. } => false,
+                ide::RunnableKind::TestMod { .. }
+                | ide::RunnableKind::Test { .. }
+                | ide::RunnableKind::Bench { .. }
+                | ide::RunnableKind::Bin => true,
             };
-
-            Ok(lsp_types::CodeLens { range: annotation_range, command: Some(command), data: None })
+            let r = runnable(snap, run)?;
+
+            let lens_config = snap.config.lens();
+            if lens_config.run && client_commands_config.run_single {
+                let command = command::run_single(&r, &title);
+                acc.push(lsp_types::CodeLens {
+                    range: annotation_range,
+                    command: Some(command),
+                    data: None,
+                })
+            }
+            if lens_config.debug && can_debug && client_commands_config.debug_single {
+                let command = command::debug_single(&r);
+                acc.push(lsp_types::CodeLens {
+                    range: annotation_range,
+                    command: Some(command),
+                    data: None,
+                })
+            }
         }
         AnnotationKind::HasImpls { position: file_position, data } => {
+            if !client_commands_config.show_reference {
+                return Ok(());
+            }
             let line_index = snap.file_line_index(file_position.file_id)?;
             let annotation_range = range(&line_index, annotation.range);
             let url = url(snap, file_position.file_id);
@@ -1005,13 +1068,16 @@ pub(crate) fn code_lens(
                 )
             });
 
-            Ok(lsp_types::CodeLens {
+            acc.push(lsp_types::CodeLens {
                 range: annotation_range,
                 command,
                 data: Some(to_value(lsp_ext::CodeLensResolveData::Impls(goto_params)).unwrap()),
             })
         }
         AnnotationKind::HasReferences { position: file_position, data } => {
+            if !client_commands_config.show_reference {
+                return Ok(());
+            }
             let line_index = snap.file_line_index(file_position.file_id)?;
             let annotation_range = range(&line_index, annotation.range);
             let url = url(snap, file_position.file_id);
@@ -1034,13 +1100,14 @@ pub(crate) fn code_lens(
                 )
             });
 
-            Ok(lsp_types::CodeLens {
+            acc.push(lsp_types::CodeLens {
                 range: annotation_range,
                 command,
                 data: Some(to_value(lsp_ext::CodeLensResolveData::References(doc_pos)).unwrap()),
             })
         }
     }
+    Ok(())
 }
 
 pub(crate) mod command {
@@ -1141,7 +1208,9 @@ pub(crate) fn markup_content(markup: Markup) -> lsp_types::MarkupContent {
 }
 
 pub(crate) fn rename_error(err: RenameError) -> crate::LspError {
-    crate::LspError { code: lsp_server::ErrorCode::InvalidParams as i32, message: err.to_string() }
+    // This is wrong, but we don't have a better alternative I suppose?
+    // https://github.com/microsoft/language-server-protocol/issues/1341
+    invalid_params_error(err.to_string())
 }
 
 #[cfg(test)]
@@ -1149,74 +1218,9 @@ mod tests {
     use std::sync::Arc;
 
     use ide::Analysis;
-    use ide_db::helpers::{
-        insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
-        SnippetCap,
-    };
 
     use super::*;
 
-    #[test]
-    fn test_completion_with_ref() {
-        let fixture = r#"
-        struct Foo;
-        fn foo(arg: &Foo) {}
-        fn main() {
-            let arg = Foo;
-            foo($0)
-        }"#;
-
-        let (offset, text) = test_utils::extract_offset(fixture);
-        let line_index = LineIndex {
-            index: Arc::new(ide::LineIndex::new(&text)),
-            endings: LineEndings::Unix,
-            encoding: OffsetEncoding::Utf16,
-        };
-        let (analysis, file_id) = Analysis::from_single_file(text);
-        let completions: Vec<(String, Option<String>)> = analysis
-            .completions(
-                &ide::CompletionConfig {
-                    enable_postfix_completions: true,
-                    enable_imports_on_the_fly: true,
-                    enable_self_on_the_fly: true,
-                    add_call_parenthesis: true,
-                    add_call_argument_snippets: true,
-                    snippet_cap: SnippetCap::new(true),
-                    insert_use: InsertUseConfig {
-                        granularity: ImportGranularity::Item,
-                        prefix_kind: PrefixKind::Plain,
-                        enforce_granularity: true,
-                        group: true,
-                    },
-                },
-                ide_db::base_db::FilePosition { file_id, offset },
-            )
-            .unwrap()
-            .unwrap()
-            .into_iter()
-            .filter(|c| c.label().ends_with("arg"))
-            .map(|c| completion_item(None, &line_index, c))
-            .flat_map(|comps| comps.into_iter().map(|c| (c.label, c.sort_text)))
-            .collect();
-        expect_test::expect![[r#"
-            [
-                (
-                    "&arg",
-                    Some(
-                        "fffffff9",
-                    ),
-                ),
-                (
-                    "arg",
-                    Some(
-                        "fffffffd",
-                    ),
-                ),
-            ]
-        "#]]
-        .assert_debug_eq(&completions);
-    }
-
     #[test]
     fn conv_fold_line_folding_only_fixup() {
         let text = r#"mod a;
@@ -1236,12 +1240,12 @@ fn main() {
         assert_eq!(folds.len(), 4);
 
         let line_index = LineIndex {
-            index: Arc::new(ide::LineIndex::new(&text)),
+            index: Arc::new(ide::LineIndex::new(text)),
             endings: LineEndings::Unix,
             encoding: OffsetEncoding::Utf16,
         };
         let converted: Vec<lsp_types::FoldingRange> =
-            folds.into_iter().map(|it| folding_range(&text, &line_index, true, it)).collect();
+            folds.into_iter().map(|it| folding_range(text, &line_index, true, it)).collect();
 
         let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)];
         assert_eq!(converted.len(), expected_lines.len());
@@ -1256,15 +1260,13 @@ fn main() {
     // `Url` is not able to parse windows paths on unix machines.
     #[test]
     #[cfg(target_os = "windows")]
-    fn test_lowercase_drive_letter_with_drive() {
-        let url = url_from_abs_path(Path::new("C:\\Test"));
+    fn test_lowercase_drive_letter() {
+        use std::{convert::TryInto, path::Path};
+
+        let url = url_from_abs_path(Path::new("C:\\Test").try_into().unwrap());
         assert_eq!(url.to_string(), "file:///c:/Test");
-    }
 
-    #[test]
-    #[cfg(target_os = "windows")]
-    fn test_drive_without_colon_passthrough() {
-        let url = url_from_abs_path(Path::new(r#"\\localhost\C$\my_dir"#));
+        let url = url_from_abs_path(Path::new(r#"\\localhost\C$\my_dir"#).try_into().unwrap());
         assert_eq!(url.to_string(), "file://localhost/C$/my_dir");
     }
 }