]> git.lizzy.rs Git - rust.git/blobdiff - crates/rust-analyzer/src/to_proto.rs
Merge #8432
[rust.git] / crates / rust-analyzer / src / to_proto.rs
index 2380e021a04d70d11c70392db54355eb9d989695..8d7cb9b74cec565188d3342b4f6ac7ca0f83a5a9 100644 (file)
@@ -6,11 +6,11 @@
 
 use ide::{
     Annotation, AnnotationKind, Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind,
-    Documentation, FileId, FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlPunct,
-    HlRange, HlTag, Indel, InlayHint, InlayKind, InsertTextFormat, Markup, NavigationTarget,
-    ReferenceAccess, RenameError, Runnable, Severity, SourceChange, TextEdit, TextRange, TextSize,
+    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,
 };
-use ide_db::SymbolKind;
 use itertools::Itertools;
 use serde_json::to_value;
 
@@ -62,6 +62,13 @@ pub(crate) fn symbol_kind(symbol_kind: SymbolKind) -> lsp_types::SymbolKind {
     }
 }
 
+pub(crate) fn structure_node_kind(kind: StructureNodeKind) -> lsp_types::SymbolKind {
+    match kind {
+        StructureNodeKind::SymbolKind(symbol) => symbol_kind(symbol),
+        StructureNodeKind::Region => lsp_types::SymbolKind::Namespace,
+    }
+}
+
 pub(crate) fn document_highlight_kind(
     reference_access: ReferenceAccess,
 ) -> lsp_types::DocumentHighlightKind {
@@ -138,6 +145,23 @@ pub(crate) fn text_edit(line_index: &LineIndex, indel: Indel) -> lsp_types::Text
     lsp_types::TextEdit { range, new_text }
 }
 
+pub(crate) fn completion_text_edit(
+    line_index: &LineIndex,
+    insert_replace_support: Option<lsp_types::Position>,
+    indel: Indel,
+) -> lsp_types::CompletionTextEdit {
+    let text_edit = text_edit(line_index, indel);
+    match insert_replace_support {
+        Some(cursor_pos) => lsp_types::InsertReplaceEdit {
+            new_text: text_edit.new_text,
+            insert: lsp_types::Range { start: text_edit.range.start, end: cursor_pos },
+            replace: text_edit.range,
+        }
+        .into(),
+        None => text_edit.into(),
+    }
+}
+
 pub(crate) fn snippet_text_edit(
     line_index: &LineIndex,
     is_snippet: bool,
@@ -172,6 +196,7 @@ pub(crate) fn snippet_text_edit_vec(
 }
 
 pub(crate) fn completion_item(
+    insert_replace_support: Option<lsp_types::Position>,
     line_index: &LineIndex,
     item: CompletionItem,
 ) -> Vec<lsp_types::CompletionItem> {
@@ -183,7 +208,7 @@ pub(crate) fn completion_item(
     for indel in item.text_edit().iter() {
         if indel.delete.contains_range(source_range) {
             text_edit = Some(if indel.delete == source_range {
-                self::text_edit(line_index, indel.clone())
+                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());
@@ -191,7 +216,7 @@ pub(crate) fn completion_item(
                 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::text_edit(line_index, indel2)
+                self::completion_text_edit(line_index, insert_replace_support, indel2)
             })
         } else {
             assert!(source_range.intersect(indel.delete).is_none());
@@ -206,19 +231,29 @@ pub(crate) fn completion_item(
         detail: item.detail().map(|it| it.to_string()),
         filter_text: Some(item.lookup().to_string()),
         kind: item.kind().map(completion_item_kind),
-        text_edit: Some(text_edit.into()),
+        text_edit: Some(text_edit),
         additional_text_edits: Some(additional_text_edits),
         documentation: item.documentation().map(documentation),
         deprecated: Some(item.deprecated()),
         ..Default::default()
     };
 
-    if item.score().is_some() {
-        lsp_item.preselect = Some(true);
-        // HACK: sort preselect items first
-        lsp_item.sort_text = Some(format!(" {}", item.label()));
+    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());
+
     if item.deprecated() {
         lsp_item.tags = Some(vec![lsp_types::CompletionItemTag::Deprecated])
     }
@@ -228,10 +263,9 @@ pub(crate) fn completion_item(
     }
 
     let mut res = match item.ref_match() {
-        Some(mutability) => {
+        Some((mutability, relevance)) => {
             let mut lsp_item_with_ref = lsp_item.clone();
-            lsp_item.preselect = Some(true);
-            lsp_item.sort_text = Some(format!(" {}", item.label()));
+            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(lsp_types::CompletionTextEdit::Edit(it)) = &mut lsp_item_with_ref.text_edit
@@ -271,7 +305,7 @@ pub(crate) fn signature_help(
             let params = call_info
                 .parameter_ranges()
                 .iter()
-                .map(|it| [u32::from(it.start()).into(), u32::from(it.end()).into()])
+                .map(|it| [u32::from(it.start()), u32::from(it.end())])
                 .map(|label_offsets| lsp_types::ParameterInformation {
                     label: lsp_types::ParameterLabel::LabelOffsets(label_offsets),
                     documentation: None,
@@ -419,19 +453,25 @@ fn semantic_token_type_and_modifiers(
             SymbolKind::Trait => lsp_types::SemanticTokenType::INTERFACE,
             SymbolKind::Macro => lsp_types::SemanticTokenType::MACRO,
         },
+        HlTag::Attribute => semantic_tokens::ATTRIBUTE,
+        HlTag::BoolLiteral => semantic_tokens::BOOLEAN,
         HlTag::BuiltinType => semantic_tokens::BUILTIN_TYPE,
-        HlTag::None => semantic_tokens::GENERIC,
         HlTag::ByteLiteral | HlTag::NumericLiteral => lsp_types::SemanticTokenType::NUMBER,
-        HlTag::BoolLiteral => semantic_tokens::BOOLEAN,
-        HlTag::StringLiteral => lsp_types::SemanticTokenType::STRING,
         HlTag::CharLiteral => semantic_tokens::CHAR_LITERAL,
         HlTag::Comment => lsp_types::SemanticTokenType::COMMENT,
-        HlTag::Attribute => semantic_tokens::ATTRIBUTE,
+        HlTag::EscapeSequence => semantic_tokens::ESCAPE_SEQUENCE,
+        HlTag::FormatSpecifier => semantic_tokens::FORMAT_SPECIFIER,
         HlTag::Keyword => lsp_types::SemanticTokenType::KEYWORD,
+        HlTag::None => semantic_tokens::GENERIC,
+        HlTag::Operator(op) => match op {
+            HlOperator::Bitwise => semantic_tokens::BITWISE,
+            HlOperator::Arithmetic => semantic_tokens::ARITHMETIC,
+            HlOperator::Logical => semantic_tokens::LOGICAL,
+            HlOperator::Comparision => semantic_tokens::COMPARISION,
+            HlOperator::Other => semantic_tokens::OPERATOR,
+        },
+        HlTag::StringLiteral => lsp_types::SemanticTokenType::STRING,
         HlTag::UnresolvedReference => semantic_tokens::UNRESOLVED_REFERENCE,
-        HlTag::FormatSpecifier => semantic_tokens::FORMAT_SPECIFIER,
-        HlTag::Operator => lsp_types::SemanticTokenType::OPERATOR,
-        HlTag::EscapeSequence => semantic_tokens::ESCAPE_SEQUENCE,
         HlTag::Punctuation(punct) => match punct {
             HlPunct::Bracket => semantic_tokens::BRACKET,
             HlPunct::Brace => semantic_tokens::BRACE,
@@ -457,6 +497,8 @@ fn semantic_token_type_and_modifiers(
             HlMod::Unsafe => semantic_tokens::UNSAFE,
             HlMod::Callable => semantic_tokens::CALLABLE,
             HlMod::Static => lsp_types::SemanticTokenModifier::STATIC,
+            HlMod::IntraDocLink => semantic_tokens::INTRA_DOC_LINK,
+            HlMod::Trait => semantic_tokens::TRAIT_MODIFIER,
             HlMod::Associated => continue,
         };
         mods |= modifier;
@@ -474,7 +516,13 @@ pub(crate) fn folding_range(
     let kind = match fold.kind {
         FoldKind::Comment => Some(lsp_types::FoldingRangeKind::Comment),
         FoldKind::Imports => Some(lsp_types::FoldingRangeKind::Imports),
-        FoldKind::Mods | FoldKind::Block | FoldKind::ArgList | FoldKind::Region => None,
+        FoldKind::Region => Some(lsp_types::FoldingRangeKind::Region),
+        FoldKind::Mods
+        | FoldKind::Block
+        | FoldKind::ArgList
+        | FoldKind::Consts
+        | FoldKind::Statics
+        | FoldKind::Array => None,
     };
 
     let range = range(line_index, fold.range);
@@ -640,6 +688,18 @@ pub(crate) fn goto_definition_response(
     }
 }
 
+pub(crate) fn text_document_edit(
+    snap: &GlobalStateSnapshot,
+    file_id: FileId,
+    edit: TextEdit,
+) -> Result<lsp_types::TextDocumentEdit> {
+    let text_document = optional_versioned_text_document_identifier(snap, file_id);
+    let line_index = snap.file_line_index(file_id)?;
+    let edits =
+        edit.into_iter().map(|it| lsp_types::OneOf::Left(text_edit(&line_index, it))).collect();
+    Ok(lsp_types::TextDocumentEdit { text_document, edits })
+}
+
 pub(crate) fn snippet_text_document_edit(
     snap: &GlobalStateSnapshot,
     is_snippet: bool,
@@ -789,50 +849,40 @@ pub(crate) fn code_action_kind(kind: AssistKind) -> lsp_types::CodeActionKind {
     }
 }
 
-pub(crate) fn unresolved_code_action(
+pub(crate) fn code_action(
     snap: &GlobalStateSnapshot,
-    code_action_params: lsp_types::CodeActionParams,
     assist: Assist,
-    index: usize,
+    resolve_data: Option<(usize, lsp_types::CodeActionParams)>,
 ) -> Result<lsp_ext::CodeAction> {
-    assert!(assist.source_change.is_none());
-    let res = lsp_ext::CodeAction {
+    let mut res = lsp_ext::CodeAction {
         title: assist.label.to_string(),
         group: assist.group.filter(|_| snap.config.code_action_group()).map(|gr| gr.0),
         kind: Some(code_action_kind(assist.id.1)),
         edit: None,
         is_preferred: None,
-        data: Some(lsp_ext::CodeActionData {
-            id: format!("{}:{}", assist.id.0, index.to_string()),
-            code_action_params,
-        }),
-    };
-    Ok(res)
-}
-
-pub(crate) fn resolved_code_action(
-    snap: &GlobalStateSnapshot,
-    assist: Assist,
-) -> Result<lsp_ext::CodeAction> {
-    let change = assist.source_change.unwrap();
-    let res = lsp_ext::CodeAction {
-        edit: Some(snippet_workspace_edit(snap, change)?),
-        title: assist.label.to_string(),
-        group: assist.group.filter(|_| snap.config.code_action_group()).map(|gr| gr.0),
-        kind: Some(code_action_kind(assist.id.1)),
-        is_preferred: None,
         data: None,
     };
+    match (assist.source_change, resolve_data) {
+        (Some(it), _) => res.edit = Some(snippet_workspace_edit(snap, it)?),
+        (None, Some((index, code_action_params))) => {
+            res.data = Some(lsp_ext::CodeActionData {
+                id: format!("{}:{}", assist.id.0, index.to_string()),
+                code_action_params,
+            });
+        }
+        (None, None) => {
+            stdx::never!("assist should always be resolved if client can't do lazy resolving")
+        }
+    };
     Ok(res)
 }
 
 pub(crate) fn runnable(
     snap: &GlobalStateSnapshot,
-    file_id: FileId,
     runnable: Runnable,
 ) -> Result<lsp_ext::Runnable> {
     let config = snap.config.runnables();
-    let spec = CargoTargetSpec::for_file(snap, file_id)?;
+    let spec = CargoTargetSpec::for_file(snap, runnable.nav.file_id)?;
     let workspace_root = spec.as_ref().map(|it| it.workspace_root.clone());
     let target = spec.as_ref().map(|s| s.target.clone());
     let (cargo_args, executable_args) =
@@ -865,7 +915,7 @@ pub(crate) fn code_lens(
             let annotation_range = range(&line_index, annotation.range);
 
             let action = run.action();
-            let r = runnable(&snap, run.nav.file_id, run)?;
+            let r = runnable(&snap, run)?;
 
             let command = if debug {
                 command::debug_single(&r)
@@ -1056,9 +1106,11 @@ pub(crate) fn rename_error(err: RenameError) -> crate::LspError {
 mod tests {
     use std::sync::Arc;
 
-    use hir::PrefixKind;
     use ide::Analysis;
-    use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap};
+    use ide_db::helpers::{
+        insert_use::{InsertUseConfig, PrefixKind},
+        SnippetCap,
+    };
 
     use super::*;
 
@@ -1099,19 +1151,21 @@ fn main() {
             .unwrap()
             .into_iter()
             .filter(|c| c.label().ends_with("arg"))
-            .map(|c| completion_item(&line_index, c))
+            .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",
-                    None,
+                    Some(
+                        "fffffff9",
+                    ),
                 ),
                 (
                     "arg",
                     Some(
-                        " arg",
+                        "fffffffd",
                     ),
                 ),
             ]