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;
}
}
+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 {
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,
}
pub(crate) fn completion_item(
+ insert_replace_support: Option<lsp_types::Position>,
line_index: &LineIndex,
item: CompletionItem,
) -> Vec<lsp_types::CompletionItem> {
- fn set_score(lsp_item: &mut lsp_types::CompletionItem, label: &str) {
- lsp_item.preselect = Some(true);
- // HACK: sort preselect items first
- lsp_item.sort_text = Some(format!(" {}", label));
- }
-
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
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());
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());
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() {
- set_score(&mut lsp_item, 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])
}
}
let mut res = match item.ref_match() {
- Some(mutability) => {
- let mut refed = lsp_item.clone();
- let label = format!("&{}{}", mutability.as_keyword_for_ref(), refed.label);
- set_score(&mut refed, &label);
- refed.label = label;
- vec![lsp_item, refed]
+ 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(lsp_types::CompletionTextEdit::Edit(it)) = &mut lsp_item_with_ref.text_edit
+ {
+ it.new_text = format!("&{}{}", mutability.as_keyword_for_ref(), it.new_text);
+ }
+ vec![lsp_item_with_ref, lsp_item]
}
None => vec![lsp_item],
};
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,
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,
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;
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);
}
}
+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,
}
}
-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) =
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)
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::*;
.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,
+ "&arg",
+ Some(
+ "fffffff9",
+ ),
),
(
- "&arg",
+ "arg",
Some(
- " &arg",
+ "fffffffd",
),
),
]