//! Conversion of rust-analyzer specific types to lsp_types equivalents.
use std::{
iter::once,
- path::{self, Path},
+ path,
sync::atomic::{AtomicU32, Ordering},
};
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 {
}
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,
}
}
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 {
.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(),
..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(
};
lsp_types::SignatureHelp {
signatures: vec![signature],
- active_signature: None,
+ active_signature: Some(0),
active_parameter,
}
}
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::Mutable => semantic_tokens::MUTABLE,
HlMod::Public => semantic_tokens::PUBLIC,
- HlMod::Unsafe => semantic_tokens::UNSAFE,
- HlMod::Callable => semantic_tokens::CALLABLE,
+ 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;
}
/// 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(_)) =>
{
}
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,
};
+ let r = runnable(snap, run)?;
- Ok(lsp_types::CodeLens { range: annotation_range, command: Some(command), data: None })
+ 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);
)
});
- 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);
)
});
- 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 {
}
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)]
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,
- skip_glob_imports: 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;
// `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");
}
}