summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/rust-analyzer/src
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/rust-analyzer/crates/rust-analyzer/src')
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/bin/logger.rs2
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs1
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/bin/rustc_wrapper.rs7
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/cli.rs1
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs2
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs16
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs448
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs61
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs37
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs2
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs40
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/handlers.rs67
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs4
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_ext.rs8
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs497
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs79
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/semantic_tokens.rs191
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/to_proto.rs114
18 files changed, 1153 insertions, 424 deletions
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/logger.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/logger.rs
index 0b69f75bc..298814af5 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/logger.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/logger.rs
@@ -52,7 +52,7 @@ impl Logger {
// merge chalk filter to our main filter (from RA_LOG env).
//
// The acceptable syntax of CHALK_DEBUG is `target[span{field=value}]=level`.
- // As the value should only affect chalk crates, we'd better mannually
+ // As the value should only affect chalk crates, we'd better manually
// specify the target. And for simplicity, CHALK_DEBUG only accept the value
// that specify level.
let chalk_level_dir = std::env::var("CHALK_DEBUG")
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs
index e9de23cb3..f6a680297 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs
@@ -93,6 +93,7 @@ fn try_main() -> Result<()> {
flags::RustAnalyzerCmd::Ssr(cmd) => cmd.run()?,
flags::RustAnalyzerCmd::Search(cmd) => cmd.run()?,
flags::RustAnalyzerCmd::Lsif(cmd) => cmd.run()?,
+ flags::RustAnalyzerCmd::Scip(cmd) => cmd.run()?,
}
Ok(())
}
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/rustc_wrapper.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/rustc_wrapper.rs
index 2f6d4706d..38e9c7dd7 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/rustc_wrapper.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/rustc_wrapper.rs
@@ -17,6 +17,11 @@ pub(crate) fn run_rustc_skipping_cargo_checking(
rustc_executable: OsString,
args: Vec<OsString>,
) -> io::Result<ExitCode> {
+ // `CARGO_CFG_TARGET_ARCH` is only set by cargo when executing build scripts
+ // We don't want to exit out checks unconditionally with success if a build
+ // script tries to invoke checks themselves
+ // See https://github.com/rust-lang/rust-analyzer/issues/12973 for context
+ let not_invoked_by_build_script = std::env::var_os("CARGO_CFG_TARGET_ARCH").is_none();
let is_cargo_check = args.iter().any(|arg| {
let arg = arg.to_string_lossy();
// `cargo check` invokes `rustc` with `--emit=metadata` argument.
@@ -29,7 +34,7 @@ pub(crate) fn run_rustc_skipping_cargo_checking(
// The default output filename is CRATE_NAME.rmeta.
arg.starts_with("--emit=") && arg.contains("metadata") && !arg.contains("link")
});
- if is_cargo_check {
+ if not_invoked_by_build_script && is_cargo_check {
return Ok(ExitCode(Some(0)));
}
run_rustc(rustc_executable, args)
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli.rs
index 6ccdaa86d..60ba67e25 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli.rs
@@ -9,6 +9,7 @@ mod analysis_stats;
mod diagnostics;
mod ssr;
mod lsif;
+mod scip;
mod progress_report;
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs
index 52511ceb5..247007db0 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs
@@ -43,7 +43,7 @@ impl flags::Diagnostics {
println!("processing crate: {}, module: {}", crate_name, _vfs.file_path(file_id));
for diagnostic in analysis
.diagnostics(
- &DiagnosticsConfig::default(),
+ &DiagnosticsConfig::test_sample(),
AssistResolveStrategy::None,
file_id,
)
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs
index 19907ebdd..aa32654fb 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs
@@ -10,6 +10,10 @@ xflags::xflags! {
src "./src/cli/flags.rs"
/// LSP server for the Rust programming language.
+ ///
+ /// Subcommands and their flags do not provide any stability guarantees and may be removed or
+ /// changed without notice. Top-level flags that are not are marked as [Unstable] provide
+ /// backwards-compatibility and may be relied on.
cmd rust-analyzer {
/// Verbosity level, can be repeated multiple times.
repeated -v, --verbose
@@ -21,7 +25,7 @@ xflags::xflags! {
/// Flush log records to the file immediately.
optional --no-log-buffering
- /// Wait until a debugger is attached to (requires debug build).
+ /// [Unstable] Wait until a debugger is attached to (requires debug build).
optional --wait-dbg
default cmd lsp-server {
@@ -108,6 +112,10 @@ xflags::xflags! {
cmd lsif
required path: PathBuf
{}
+
+ cmd scip
+ required path: PathBuf
+ {}
}
}
@@ -136,6 +144,7 @@ pub enum RustAnalyzerCmd {
Search(Search),
ProcMacro(ProcMacro),
Lsif(Lsif),
+ Scip(Scip),
}
#[derive(Debug)]
@@ -203,6 +212,11 @@ pub struct Lsif {
pub path: PathBuf,
}
+#[derive(Debug)]
+pub struct Scip {
+ pub path: PathBuf,
+}
+
impl RustAnalyzer {
pub const HELP: &'static str = Self::HELP_;
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs
new file mode 100644
index 000000000..65cc993c4
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs
@@ -0,0 +1,448 @@
+//! SCIP generator
+
+use std::{
+ collections::{HashMap, HashSet},
+ time::Instant,
+};
+
+use crate::line_index::{LineEndings, LineIndex, OffsetEncoding};
+use hir::Name;
+use ide::{
+ LineCol, MonikerDescriptorKind, MonikerResult, StaticIndex, StaticIndexedFile, TextRange,
+ TokenId,
+};
+use ide_db::LineIndexDatabase;
+use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
+use scip::types as scip_types;
+use std::env;
+
+use crate::cli::{
+ flags,
+ load_cargo::{load_workspace, LoadCargoConfig},
+ Result,
+};
+
+impl flags::Scip {
+ pub fn run(self) -> Result<()> {
+ eprintln!("Generating SCIP start...");
+ let now = Instant::now();
+ let cargo_config = CargoConfig::default();
+
+ let no_progress = &|s| (eprintln!("rust-analyzer: Loading {}", s));
+ let load_cargo_config = LoadCargoConfig {
+ load_out_dirs_from_check: true,
+ with_proc_macro: true,
+ prefill_caches: true,
+ };
+ let path = vfs::AbsPathBuf::assert(env::current_dir()?.join(&self.path));
+ let rootpath = path.normalize();
+ let manifest = ProjectManifest::discover_single(&path)?;
+
+ let workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress)?;
+
+ let (host, vfs, _) = load_workspace(workspace, &load_cargo_config)?;
+ let db = host.raw_database();
+ let analysis = host.analysis();
+
+ let si = StaticIndex::compute(&analysis);
+
+ let mut index = scip_types::Index {
+ metadata: Some(scip_types::Metadata {
+ version: scip_types::ProtocolVersion::UnspecifiedProtocolVersion.into(),
+ tool_info: Some(scip_types::ToolInfo {
+ name: "rust-analyzer".to_owned(),
+ version: "0.1".to_owned(),
+ arguments: vec![],
+ ..Default::default()
+ })
+ .into(),
+ project_root: format!(
+ "file://{}",
+ path.normalize()
+ .as_os_str()
+ .to_str()
+ .ok_or(anyhow::anyhow!("Unable to normalize project_root path"))?
+ .to_string()
+ ),
+ text_document_encoding: scip_types::TextEncoding::UTF8.into(),
+ ..Default::default()
+ })
+ .into(),
+ ..Default::default()
+ };
+
+ let mut symbols_emitted: HashSet<TokenId> = HashSet::default();
+ let mut tokens_to_symbol: HashMap<TokenId, String> = HashMap::new();
+
+ for file in si.files {
+ let mut local_count = 0;
+ let mut new_local_symbol = || {
+ let new_symbol = scip::types::Symbol::new_local(local_count);
+ local_count += 1;
+
+ new_symbol
+ };
+
+ let StaticIndexedFile { file_id, tokens, .. } = file;
+ let relative_path = match get_relative_filepath(&vfs, &rootpath, file_id) {
+ Some(relative_path) => relative_path,
+ None => continue,
+ };
+
+ let line_index = LineIndex {
+ index: db.line_index(file_id),
+ encoding: OffsetEncoding::Utf8,
+ endings: LineEndings::Unix,
+ };
+
+ let mut doc = scip_types::Document {
+ relative_path,
+ language: "rust".to_string(),
+ ..Default::default()
+ };
+
+ tokens.into_iter().for_each(|(range, id)| {
+ let token = si.tokens.get(id).unwrap();
+
+ let mut occurrence = scip_types::Occurrence::default();
+ occurrence.range = text_range_to_scip_range(&line_index, range);
+ occurrence.symbol = match tokens_to_symbol.get(&id) {
+ Some(symbol) => symbol.clone(),
+ None => {
+ let symbol = match &token.moniker {
+ Some(moniker) => moniker_to_symbol(&moniker),
+ None => new_local_symbol(),
+ };
+
+ let symbol = scip::symbol::format_symbol(symbol);
+ tokens_to_symbol.insert(id, symbol.clone());
+ symbol
+ }
+ };
+
+ if let Some(def) = token.definition {
+ if def.range == range {
+ occurrence.symbol_roles |= scip_types::SymbolRole::Definition as i32;
+ }
+
+ if !symbols_emitted.contains(&id) {
+ symbols_emitted.insert(id);
+
+ let mut symbol_info = scip_types::SymbolInformation::default();
+ symbol_info.symbol = occurrence.symbol.clone();
+ if let Some(hover) = &token.hover {
+ if !hover.markup.as_str().is_empty() {
+ symbol_info.documentation = vec![hover.markup.as_str().to_string()];
+ }
+ }
+
+ doc.symbols.push(symbol_info)
+ }
+ }
+
+ doc.occurrences.push(occurrence);
+ });
+
+ if doc.occurrences.is_empty() {
+ continue;
+ }
+
+ index.documents.push(doc);
+ }
+
+ scip::write_message_to_file("index.scip", index)
+ .map_err(|err| anyhow::anyhow!("Failed to write scip to file: {}", err))?;
+
+ eprintln!("Generating SCIP finished {:?}", now.elapsed());
+ Ok(())
+ }
+}
+
+fn get_relative_filepath(
+ vfs: &vfs::Vfs,
+ rootpath: &vfs::AbsPathBuf,
+ file_id: ide::FileId,
+) -> Option<String> {
+ Some(vfs.file_path(file_id).as_path()?.strip_prefix(&rootpath)?.as_ref().to_str()?.to_string())
+}
+
+// SCIP Ranges have a (very large) optimization that ranges if they are on the same line
+// only encode as a vector of [start_line, start_col, end_col].
+//
+// This transforms a line index into the optimized SCIP Range.
+fn text_range_to_scip_range(line_index: &LineIndex, range: TextRange) -> Vec<i32> {
+ let LineCol { line: start_line, col: start_col } = line_index.index.line_col(range.start());
+ let LineCol { line: end_line, col: end_col } = line_index.index.line_col(range.end());
+
+ if start_line == end_line {
+ vec![start_line as i32, start_col as i32, end_col as i32]
+ } else {
+ vec![start_line as i32, start_col as i32, end_line as i32, end_col as i32]
+ }
+}
+
+fn new_descriptor_str(
+ name: &str,
+ suffix: scip_types::descriptor::Suffix,
+) -> scip_types::Descriptor {
+ scip_types::Descriptor {
+ name: name.to_string(),
+ disambiguator: "".to_string(),
+ suffix: suffix.into(),
+ ..Default::default()
+ }
+}
+
+fn new_descriptor(name: Name, suffix: scip_types::descriptor::Suffix) -> scip_types::Descriptor {
+ let mut name = name.to_string();
+ if name.contains("'") {
+ name = format!("`{}`", name);
+ }
+
+ new_descriptor_str(name.as_str(), suffix)
+}
+
+/// Loosely based on `def_to_moniker`
+///
+/// Only returns a Symbol when it's a non-local symbol.
+/// So if the visibility isn't outside of a document, then it will return None
+fn moniker_to_symbol(moniker: &MonikerResult) -> scip_types::Symbol {
+ use scip_types::descriptor::Suffix::*;
+
+ let package_name = moniker.package_information.name.clone();
+ let version = moniker.package_information.version.clone();
+ let descriptors = moniker
+ .identifier
+ .description
+ .iter()
+ .map(|desc| {
+ new_descriptor(
+ desc.name.clone(),
+ match desc.desc {
+ MonikerDescriptorKind::Namespace => Namespace,
+ MonikerDescriptorKind::Type => Type,
+ MonikerDescriptorKind::Term => Term,
+ MonikerDescriptorKind::Method => Method,
+ MonikerDescriptorKind::TypeParameter => TypeParameter,
+ MonikerDescriptorKind::Parameter => Parameter,
+ MonikerDescriptorKind::Macro => Macro,
+ MonikerDescriptorKind::Meta => Meta,
+ },
+ )
+ })
+ .collect();
+
+ scip_types::Symbol {
+ scheme: "rust-analyzer".into(),
+ package: Some(scip_types::Package {
+ manager: "cargo".to_string(),
+ name: package_name,
+ version,
+ ..Default::default()
+ })
+ .into(),
+ descriptors,
+ ..Default::default()
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use hir::Semantics;
+ use ide::{AnalysisHost, FilePosition};
+ use ide_db::defs::IdentClass;
+ use ide_db::{base_db::fixture::ChangeFixture, helpers::pick_best_token};
+ use scip::symbol::format_symbol;
+ use syntax::SyntaxKind::*;
+ use syntax::{AstNode, T};
+
+ fn position(ra_fixture: &str) -> (AnalysisHost, FilePosition) {
+ let mut host = AnalysisHost::default();
+ let change_fixture = ChangeFixture::parse(ra_fixture);
+ host.raw_database_mut().apply_change(change_fixture.change);
+ let (file_id, range_or_offset) =
+ change_fixture.file_position.expect("expected a marker ($0)");
+ let offset = range_or_offset.expect_offset();
+ (host, FilePosition { file_id, offset })
+ }
+
+ /// If expected == "", then assert that there are no symbols (this is basically local symbol)
+ #[track_caller]
+ fn check_symbol(ra_fixture: &str, expected: &str) {
+ let (host, position) = position(ra_fixture);
+
+ let FilePosition { file_id, offset } = position;
+
+ let db = host.raw_database();
+ let sema = &Semantics::new(db);
+ let file = sema.parse(file_id).syntax().clone();
+ let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
+ IDENT
+ | INT_NUMBER
+ | LIFETIME_IDENT
+ | T![self]
+ | T![super]
+ | T![crate]
+ | T![Self]
+ | COMMENT => 2,
+ kind if kind.is_trivia() => 0,
+ _ => 1,
+ })
+ .expect("OK OK");
+
+ let navs = sema
+ .descend_into_macros(original_token.clone())
+ .into_iter()
+ .filter_map(|token| {
+ IdentClass::classify_token(sema, &token).map(IdentClass::definitions).map(|it| {
+ it.into_iter().flat_map(|def| {
+ let module = def.module(db).unwrap();
+ let current_crate = module.krate();
+
+ match MonikerResult::from_def(sema.db, def, current_crate) {
+ Some(moniker_result) => Some(moniker_to_symbol(&moniker_result)),
+ None => None,
+ }
+ })
+ })
+ })
+ .flatten()
+ .collect::<Vec<_>>();
+
+ if expected == "" {
+ assert_eq!(0, navs.len(), "must have no symbols {:?}", navs);
+ return;
+ }
+
+ assert_eq!(1, navs.len(), "must have one symbol {:?}", navs);
+
+ let res = navs.get(0).unwrap();
+ let formatted = format_symbol(res.clone());
+ assert_eq!(formatted, expected);
+ }
+
+ #[test]
+ fn basic() {
+ check_symbol(
+ r#"
+//- /lib.rs crate:main deps:foo
+use foo::example_mod::func;
+fn main() {
+ func$0();
+}
+//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+pub mod example_mod {
+ pub fn func() {}
+}
+"#,
+ "rust-analyzer cargo foo 0.1.0 example_mod/func().",
+ );
+ }
+
+ #[test]
+ fn symbol_for_trait() {
+ check_symbol(
+ r#"
+//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+pub mod module {
+ pub trait MyTrait {
+ pub fn func$0() {}
+ }
+}
+"#,
+ "rust-analyzer cargo foo 0.1.0 module/MyTrait#func().",
+ );
+ }
+
+ #[test]
+ fn symbol_for_trait_constant() {
+ check_symbol(
+ r#"
+ //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+ pub mod module {
+ pub trait MyTrait {
+ const MY_CONST$0: u8;
+ }
+ }
+ "#,
+ "rust-analyzer cargo foo 0.1.0 module/MyTrait#MY_CONST.",
+ );
+ }
+
+ #[test]
+ fn symbol_for_trait_type() {
+ check_symbol(
+ r#"
+ //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+ pub mod module {
+ pub trait MyTrait {
+ type MyType$0;
+ }
+ }
+ "#,
+ // "foo::module::MyTrait::MyType",
+ "rust-analyzer cargo foo 0.1.0 module/MyTrait#[MyType]",
+ );
+ }
+
+ #[test]
+ fn symbol_for_trait_impl_function() {
+ check_symbol(
+ r#"
+ //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+ pub mod module {
+ pub trait MyTrait {
+ pub fn func() {}
+ }
+
+ struct MyStruct {}
+
+ impl MyTrait for MyStruct {
+ pub fn func$0() {}
+ }
+ }
+ "#,
+ // "foo::module::MyStruct::MyTrait::func",
+ "rust-analyzer cargo foo 0.1.0 module/MyStruct#MyTrait#func().",
+ );
+ }
+
+ #[test]
+ fn symbol_for_field() {
+ check_symbol(
+ r#"
+ //- /lib.rs crate:main deps:foo
+ use foo::St;
+ fn main() {
+ let x = St { a$0: 2 };
+ }
+ //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+ pub struct St {
+ pub a: i32,
+ }
+ "#,
+ "rust-analyzer cargo foo 0.1.0 St#a.",
+ );
+ }
+
+ #[test]
+ fn local_symbol_for_local() {
+ check_symbol(
+ r#"
+ //- /lib.rs crate:main deps:foo
+ use foo::module::func;
+ fn main() {
+ func();
+ }
+ //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+ pub mod module {
+ pub fn func() {
+ let x$0 = 2;
+ }
+ }
+ "#,
+ "",
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
index ac0fdf85a..54dcb42d9 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
@@ -12,8 +12,8 @@ use std::{ffi::OsString, fmt, iter, path::PathBuf};
use flycheck::FlycheckConfig;
use ide::{
AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode,
- HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayHintsConfig, JoinLinesConfig,
- Snippet, SnippetScope,
+ HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayHintsConfig,
+ JoinLinesConfig, Snippet, SnippetScope,
};
use ide_db::{
imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
@@ -45,7 +45,8 @@ mod patch_old_style;
// - foo_command = overrides the subcommand, foo_overrideCommand allows full overwriting, extra args only applies for foo_command
// Defines the server-side configuration of the rust-analyzer. We generate
-// *parts* of VS Code's `package.json` config from this.
+// *parts* of VS Code's `package.json` config from this. Run `cargo test` to
+// re-generate that file.
//
// However, editor specific config, which the server doesn't know about, should
// be specified directly in `package.json`.
@@ -120,6 +121,10 @@ config_data! {
/// Cargo, you might also want to change
/// `#rust-analyzer.cargo.buildScripts.overrideCommand#`.
///
+ /// If there are multiple linked projects, this command is invoked for
+ /// each of them, with the working directory being the project root
+ /// (i.e., the folder containing the `Cargo.toml`).
+ ///
/// An example command would be:
///
/// ```bash
@@ -243,7 +248,10 @@ config_data! {
hover_actions_run_enable: bool = "true",
/// Whether to show documentation on hover.
- hover_documentation_enable: bool = "true",
+ hover_documentation_enable: bool = "true",
+ /// Whether to show keyword hover popups. Only applies when
+ /// `#rust-analyzer.hover.documentation.enable#` is set.
+ hover_documentation_keywords_enable: bool = "true",
/// Use markdown syntax for links in hover.
hover_links_enable: bool = "true",
@@ -377,6 +385,34 @@ config_data! {
/// available on a nightly build.
rustfmt_rangeFormatting_enable: bool = "false",
+ /// Inject additional highlighting into doc comments.
+ ///
+ /// When enabled, rust-analyzer will highlight rust source in doc comments as well as intra
+ /// doc links.
+ semanticHighlighting_doc_comment_inject_enable: bool = "true",
+ /// Use semantic tokens for operators.
+ ///
+ /// When disabled, rust-analyzer will emit semantic tokens only for operator tokens when
+ /// they are tagged with modifiers.
+ semanticHighlighting_operator_enable: bool = "true",
+ /// Use specialized semantic tokens for operators.
+ ///
+ /// When enabled, rust-analyzer will emit special token types for operator tokens instead
+ /// of the generic `operator` token type.
+ semanticHighlighting_operator_specialization_enable: bool = "false",
+ /// Use semantic tokens for punctuations.
+ ///
+ /// When disabled, rust-analyzer will emit semantic tokens only for punctuation tokens when
+ /// they are tagged with modifiers or have a special role.
+ semanticHighlighting_punctuation_enable: bool = "false",
+ /// When enabled, rust-analyzer will emit a punctuation semantic token for the `!` of macro
+ /// calls.
+ semanticHighlighting_punctuation_separate_macro_bang: bool = "false",
+ /// Use specialized semantic tokens for punctuations.
+ ///
+ /// When enabled, rust-analyzer will emit special token types for punctuation tokens instead
+ /// of the generic `punctuation` token type.
+ semanticHighlighting_punctuation_specialization_enable: bool = "false",
/// Use semantic tokens for strings.
///
/// In some editors (e.g. vscode) semantic tokens override other highlighting grammars.
@@ -881,6 +917,7 @@ impl Config {
ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
},
+ insert_use: self.insert_use_config(),
}
}
@@ -1162,8 +1199,19 @@ impl Config {
}
}
- pub fn highlighting_strings(&self) -> bool {
- self.data.semanticHighlighting_strings_enable
+ pub fn highlighting_config(&self) -> HighlightConfig {
+ HighlightConfig {
+ strings: self.data.semanticHighlighting_strings_enable,
+ punctuation: self.data.semanticHighlighting_punctuation_enable,
+ specialize_punctuation: self
+ .data
+ .semanticHighlighting_punctuation_specialization_enable,
+ macro_bang: self.data.semanticHighlighting_punctuation_separate_macro_bang,
+ operator: self.data.semanticHighlighting_operator_enable,
+ specialize_operator: self.data.semanticHighlighting_operator_specialization_enable,
+ inject_doc_comment: self.data.semanticHighlighting_doc_comment_inject_enable,
+ syntactic_name_ref_highlighting: false,
+ }
}
pub fn hover(&self) -> HoverConfig {
@@ -1186,6 +1234,7 @@ impl Config {
HoverDocFormat::PlainText
}
}),
+ keywords: self.data.hover_documentation_keywords_enable,
}
}
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs
index 202a01adf..f516c194d 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs
@@ -4,11 +4,12 @@ pub(crate) mod to_proto;
use std::{mem, sync::Arc};
use ide::FileId;
-use rustc_hash::{FxHashMap, FxHashSet};
+use ide_db::FxHashMap;
+use stdx::hash::{NoHashHashMap, NoHashHashSet};
use crate::lsp_ext;
-pub(crate) type CheckFixes = Arc<FxHashMap<FileId, Vec<Fix>>>;
+pub(crate) type CheckFixes = Arc<NoHashHashMap<usize, NoHashHashMap<FileId, Vec<Fix>>>>;
#[derive(Debug, Default, Clone)]
pub struct DiagnosticsMapConfig {
@@ -19,12 +20,12 @@ pub struct DiagnosticsMapConfig {
#[derive(Debug, Default, Clone)]
pub(crate) struct DiagnosticCollection {
- // FIXME: should be FxHashMap<FileId, Vec<ra_id::Diagnostic>>
- pub(crate) native: FxHashMap<FileId, Vec<lsp_types::Diagnostic>>,
+ // FIXME: should be NoHashHashMap<FileId, Vec<ra_id::Diagnostic>>
+ pub(crate) native: NoHashHashMap<FileId, Vec<lsp_types::Diagnostic>>,
// FIXME: should be Vec<flycheck::Diagnostic>
- pub(crate) check: FxHashMap<FileId, Vec<lsp_types::Diagnostic>>,
+ pub(crate) check: NoHashHashMap<usize, NoHashHashMap<FileId, Vec<lsp_types::Diagnostic>>>,
pub(crate) check_fixes: CheckFixes,
- changes: FxHashSet<FileId>,
+ changes: NoHashHashSet<FileId>,
}
#[derive(Debug, Clone)]
@@ -35,9 +36,19 @@ pub(crate) struct Fix {
}
impl DiagnosticCollection {
- pub(crate) fn clear_check(&mut self) {
+ pub(crate) fn clear_check(&mut self, flycheck_id: usize) {
+ if let Some(it) = Arc::make_mut(&mut self.check_fixes).get_mut(&flycheck_id) {
+ it.clear();
+ }
+ if let Some(it) = self.check.get_mut(&flycheck_id) {
+ self.changes.extend(it.drain().map(|(key, _value)| key));
+ }
+ }
+
+ pub(crate) fn clear_check_all(&mut self) {
Arc::make_mut(&mut self.check_fixes).clear();
- self.changes.extend(self.check.drain().map(|(key, _value)| key))
+ self.changes
+ .extend(self.check.values_mut().flat_map(|it| it.drain().map(|(key, _value)| key)))
}
pub(crate) fn clear_native_for(&mut self, file_id: FileId) {
@@ -47,11 +58,12 @@ impl DiagnosticCollection {
pub(crate) fn add_check_diagnostic(
&mut self,
+ flycheck_id: usize,
file_id: FileId,
diagnostic: lsp_types::Diagnostic,
fix: Option<Fix>,
) {
- let diagnostics = self.check.entry(file_id).or_default();
+ let diagnostics = self.check.entry(flycheck_id).or_default().entry(file_id).or_default();
for existing_diagnostic in diagnostics.iter() {
if are_diagnostics_equal(existing_diagnostic, &diagnostic) {
return;
@@ -59,7 +71,7 @@ impl DiagnosticCollection {
}
let check_fixes = Arc::make_mut(&mut self.check_fixes);
- check_fixes.entry(file_id).or_default().extend(fix);
+ check_fixes.entry(flycheck_id).or_default().entry(file_id).or_default().extend(fix);
diagnostics.push(diagnostic);
self.changes.insert(file_id);
}
@@ -89,11 +101,12 @@ impl DiagnosticCollection {
file_id: FileId,
) -> impl Iterator<Item = &lsp_types::Diagnostic> {
let native = self.native.get(&file_id).into_iter().flatten();
- let check = self.check.get(&file_id).into_iter().flatten();
+ let check =
+ self.check.values().filter_map(move |it| it.get(&file_id)).into_iter().flatten();
native.chain(check)
}
- pub(crate) fn take_changes(&mut self) -> Option<FxHashSet<FileId>> {
+ pub(crate) fn take_changes(&mut self) -> Option<NoHashHashSet<FileId>> {
if self.changes.is_empty() {
return None;
}
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs
index cff4bd7f6..74689fd87 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -512,7 +512,7 @@ fn clippy_code_description(code: Option<&str>) -> Option<lsp_types::CodeDescript
#[cfg(test)]
#[cfg(not(windows))]
mod tests {
- use std::{convert::TryInto, path::Path};
+ use std::path::Path;
use crate::{config::Config, global_state::GlobalState};
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs
index 8f881cba4..92df4d70f 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs
@@ -14,6 +14,7 @@ use parking_lot::{Mutex, RwLock};
use proc_macro_api::ProcMacroServer;
use project_model::{CargoWorkspace, ProjectWorkspace, Target, WorkspaceBuildScripts};
use rustc_hash::FxHashMap;
+use stdx::hash::NoHashHashMap;
use vfs::AnchoredPathBuf;
use crate::{
@@ -67,7 +68,7 @@ pub(crate) struct GlobalState {
pub(crate) flycheck_sender: Sender<flycheck::Message>,
pub(crate) flycheck_receiver: Receiver<flycheck::Message>,
- pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
+ pub(crate) vfs: Arc<RwLock<(vfs::Vfs, NoHashHashMap<FileId, LineEndings>)>>,
pub(crate) vfs_config_version: u32,
pub(crate) vfs_progress_config_version: u32,
pub(crate) vfs_progress_n_total: usize,
@@ -113,8 +114,9 @@ pub(crate) struct GlobalStateSnapshot {
pub(crate) check_fixes: CheckFixes,
mem_docs: MemDocs,
pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
- vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
+ vfs: Arc<RwLock<(vfs::Vfs, NoHashHashMap<FileId, LineEndings>)>>,
pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
+ pub(crate) proc_macros_loaded: bool,
}
impl std::panic::UnwindSafe for GlobalStateSnapshot {}
@@ -157,7 +159,7 @@ impl GlobalState {
flycheck_sender,
flycheck_receiver,
- vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))),
+ vfs: Arc::new(RwLock::new((vfs::Vfs::default(), NoHashHashMap::default()))),
vfs_config_version: 0,
vfs_progress_config_version: 0,
vfs_progress_n_total: 0,
@@ -176,9 +178,9 @@ impl GlobalState {
pub(crate) fn process_changes(&mut self) -> bool {
let _p = profile::span("GlobalState::process_changes");
- let mut fs_changes = Vec::new();
// A file was added or deleted
let mut has_structure_changes = false;
+ let mut workspace_structure_change = None;
let (change, changed_files) = {
let mut change = Change::new();
@@ -192,15 +194,14 @@ impl GlobalState {
if let Some(path) = vfs.file_path(file.file_id).as_path() {
let path = path.to_path_buf();
if reload::should_refresh_for_change(&path, file.change_kind) {
- self.fetch_workspaces_queue
- .request_op(format!("vfs file change: {}", path.display()));
+ workspace_structure_change = Some(path);
}
- fs_changes.push((path, file.change_kind));
if file.is_created_or_deleted() {
has_structure_changes = true;
}
}
+ // Clear native diagnostics when their file gets deleted
if !file.exists() {
self.diagnostics.clear_native_for(file.file_id);
}
@@ -226,14 +227,24 @@ impl GlobalState {
self.analysis_host.apply_change(change);
- let raw_database = &self.analysis_host.raw_database();
- self.proc_macro_changed =
- changed_files.iter().filter(|file| !file.is_created_or_deleted()).any(|file| {
- let crates = raw_database.relevant_crates(file.file_id);
- let crate_graph = raw_database.crate_graph();
+ {
+ let raw_database = self.analysis_host.raw_database();
+ // FIXME: ideally we should only trigger a workspace fetch for non-library changes
+ // but somethings going wrong with the source root business when we add a new local
+ // crate see https://github.com/rust-lang/rust-analyzer/issues/13029
+ if let Some(path) = workspace_structure_change {
+ self.fetch_workspaces_queue
+ .request_op(format!("workspace vfs file change: {}", path.display()));
+ }
+ self.proc_macro_changed =
+ changed_files.iter().filter(|file| !file.is_created_or_deleted()).any(|file| {
+ let crates = raw_database.relevant_crates(file.file_id);
+ let crate_graph = raw_database.crate_graph();
+
+ crates.iter().any(|&krate| crate_graph[krate].is_proc_macro)
+ });
+ }
- crates.iter().any(|&krate| crate_graph[krate].is_proc_macro)
- });
true
}
@@ -246,6 +257,7 @@ impl GlobalState {
check_fixes: Arc::clone(&self.diagnostics.check_fixes),
mem_docs: self.mem_docs.clone(),
semantic_tokens_cache: Arc::clone(&self.semantic_tokens_cache),
+ proc_macros_loaded: !self.fetch_build_data_queue.last_op_result().0.is_empty(),
}
}
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers.rs
index deb777c95..e79cf3d3f 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers.rs
@@ -51,6 +51,12 @@ pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> Result<
Ok(())
}
+pub(crate) fn handle_cancel_flycheck(state: &mut GlobalState, _: ()) -> Result<()> {
+ let _p = profile::span("handle_stop_flycheck");
+ state.flycheck.iter().for_each(|flycheck| flycheck.cancel());
+ Ok(())
+}
+
pub(crate) fn handle_analyzer_status(
snap: GlobalStateSnapshot,
params: lsp_ext::AnalyzerStatusParams,
@@ -703,10 +709,8 @@ pub(crate) fn handle_runnables(
let mut res = Vec::new();
for runnable in snap.analysis.runnables(file_id)? {
- if let Some(offset) = offset {
- if !runnable.nav.full_range.contains_inclusive(offset) {
- continue;
- }
+ if should_skip_for_offset(&runnable, offset) {
+ continue;
}
if should_skip_target(&runnable, cargo_spec.as_ref()) {
continue;
@@ -772,6 +776,14 @@ pub(crate) fn handle_runnables(
Ok(res)
}
+fn should_skip_for_offset(runnable: &Runnable, offset: Option<TextSize>) -> bool {
+ match offset {
+ None => false,
+ _ if matches!(&runnable.kind, RunnableKind::TestMod { .. }) => false,
+ Some(offset) => !runnable.nav.full_range.contains_inclusive(offset),
+ }
+}
+
pub(crate) fn handle_related_tests(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentPositionParams,
@@ -1094,7 +1106,9 @@ pub(crate) fn handle_code_action(
}
// Fixes from `cargo check`.
- for fix in snap.check_fixes.get(&frange.file_id).into_iter().flatten() {
+ for fix in
+ snap.check_fixes.values().filter_map(|it| it.get(&frange.file_id)).into_iter().flatten()
+ {
// FIXME: this mapping is awkward and shouldn't exist. Refactor
// `snap.check_fixes` to not convert to LSP prematurely.
let intersect_fix_range = fix
@@ -1318,8 +1332,7 @@ pub(crate) fn publish_diagnostics(
.unwrap(),
}),
source: Some("rust-analyzer".to_string()),
- // https://github.com/rust-lang/rust-analyzer/issues/11404
- message: if !d.message.is_empty() { d.message } else { " ".to_string() },
+ message: d.message,
related_information: None,
tags: if d.unused { Some(vec![DiagnosticTag::UNNECESSARY]) } else { None },
data: None,
@@ -1349,7 +1362,7 @@ pub(crate) fn handle_inlay_hints(
.map(|it| {
to_proto::inlay_hint(&snap, &line_index, inlay_hints_config.render_colons, it)
})
- .collect(),
+ .collect::<Result<Vec<_>>>()?,
))
}
@@ -1491,10 +1504,12 @@ pub(crate) fn handle_semantic_tokens_full(
let text = snap.analysis.file_text(file_id)?;
let line_index = snap.file_line_index(file_id)?;
- let highlights = snap.analysis.highlight(file_id)?;
- let highlight_strings = snap.config.highlighting_strings();
- let semantic_tokens =
- to_proto::semantic_tokens(&text, &line_index, highlights, highlight_strings);
+ let mut highlight_config = snap.config.highlighting_config();
+ // Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet.
+ highlight_config.syntactic_name_ref_highlighting = !snap.proc_macros_loaded;
+
+ let highlights = snap.analysis.highlight(highlight_config, file_id)?;
+ let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
// Unconditionally cache the tokens
snap.semantic_tokens_cache.lock().insert(params.text_document.uri, semantic_tokens.clone());
@@ -1512,10 +1527,12 @@ pub(crate) fn handle_semantic_tokens_full_delta(
let text = snap.analysis.file_text(file_id)?;
let line_index = snap.file_line_index(file_id)?;
- let highlights = snap.analysis.highlight(file_id)?;
- let highlight_strings = snap.config.highlighting_strings();
- let semantic_tokens =
- to_proto::semantic_tokens(&text, &line_index, highlights, highlight_strings);
+ let mut highlight_config = snap.config.highlighting_config();
+ // Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet.
+ highlight_config.syntactic_name_ref_highlighting = !snap.proc_macros_loaded;
+
+ let highlights = snap.analysis.highlight(highlight_config, file_id)?;
+ let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
let mut cache = snap.semantic_tokens_cache.lock();
let cached_tokens = cache.entry(params.text_document.uri).or_default();
@@ -1543,10 +1560,8 @@ pub(crate) fn handle_semantic_tokens_range(
let text = snap.analysis.file_text(frange.file_id)?;
let line_index = snap.file_line_index(frange.file_id)?;
- let highlights = snap.analysis.highlight_range(frange)?;
- let highlight_strings = snap.config.highlighting_strings();
- let semantic_tokens =
- to_proto::semantic_tokens(&text, &line_index, highlights, highlight_strings);
+ let highlights = snap.analysis.highlight_range(snap.config.highlighting_config(), frange)?;
+ let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
Ok(Some(semantic_tokens.into()))
}
@@ -1764,7 +1779,7 @@ fn run_rustfmt(
let line_index = snap.file_line_index(file_id)?;
- let mut rustfmt = match snap.config.rustfmt() {
+ let mut command = match snap.config.rustfmt() {
RustfmtConfig::Rustfmt { extra_args, enable_range_formatting } => {
let mut cmd = process::Command::new(toolchain::rustfmt());
cmd.args(extra_args);
@@ -1829,12 +1844,12 @@ fn run_rustfmt(
}
};
- let mut rustfmt = rustfmt
+ let mut rustfmt = command
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
- .context(format!("Failed to spawn {:?}", rustfmt))?;
+ .context(format!("Failed to spawn {:?}", command))?;
rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
@@ -1853,7 +1868,11 @@ fn run_rustfmt(
// formatting because otherwise an error is surfaced to the user on top of the
// syntax error diagnostics they're already receiving. This is especially jarring
// if they have format on save enabled.
- tracing::info!("rustfmt exited with status 1, assuming parse error and ignoring");
+ tracing::warn!(
+ ?command,
+ %captured_stderr,
+ "rustfmt exited with status 1"
+ );
Ok(None)
}
_ => {
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs
index 47cdd8dfc..e49a98685 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs
@@ -6,8 +6,8 @@
//! code here exercise this specific completion, and thus have a fast
//! edit/compile/test cycle.
//!
-//! Note that "Rust Analyzer: Run" action does not allow running a single test
-//! in release mode in VS Code. There's however "Rust Analyzer: Copy Run Command Line"
+//! Note that "rust-analyzer: Run" action does not allow running a single test
+//! in release mode in VS Code. There's however "rust-analyzer: Copy Run Command Line"
//! which you can use to paste the command in terminal and add `--release` manually.
use std::sync::Arc;
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_ext.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_ext.rs
index 5f0e10862..e61c8b643 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_ext.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_ext.rs
@@ -129,6 +129,14 @@ pub struct ExpandedMacro {
pub expansion: String,
}
+pub enum CancelFlycheck {}
+
+impl Request for CancelFlycheck {
+ type Params = ();
+ type Result = ();
+ const METHOD: &'static str = "rust-analyzer/cancelFlycheck";
+}
+
pub enum MatchingBrace {}
impl Request for MatchingBrace {
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs
index 5845cf712..3cfbc2e4e 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs
@@ -2,13 +2,16 @@
//! requests/replies and notifications back to the client.
use std::{
fmt,
+ ops::Deref,
sync::Arc,
time::{Duration, Instant},
};
use always_assert::always;
use crossbeam_channel::{select, Receiver};
-use ide_db::base_db::{SourceDatabaseExt, VfsPath};
+use flycheck::FlycheckHandle;
+use ide_db::base_db::{SourceDatabase, SourceDatabaseExt, VfsPath};
+use itertools::Itertools;
use lsp_server::{Connection, Notification, Request};
use lsp_types::notification::Notification as _;
use vfs::{ChangeKind, FileId};
@@ -203,81 +206,14 @@ impl GlobalState {
}
lsp_server::Message::Response(resp) => self.complete_request(resp),
},
- Event::Task(mut task) => {
+ Event::Task(task) => {
let _p = profile::span("GlobalState::handle_event/task");
let mut prime_caches_progress = Vec::new();
- loop {
- match task {
- Task::Response(response) => self.respond(response),
- Task::Retry(req) => self.on_request(req),
- Task::Diagnostics(diagnostics_per_file) => {
- for (file_id, diagnostics) in diagnostics_per_file {
- self.diagnostics.set_native_diagnostics(file_id, diagnostics)
- }
- }
- Task::PrimeCaches(progress) => match progress {
- PrimeCachesProgress::Begin => prime_caches_progress.push(progress),
- PrimeCachesProgress::Report(_) => {
- match prime_caches_progress.last_mut() {
- Some(last @ PrimeCachesProgress::Report(_)) => {
- // Coalesce subsequent update events.
- *last = progress;
- }
- _ => prime_caches_progress.push(progress),
- }
- }
- PrimeCachesProgress::End { .. } => prime_caches_progress.push(progress),
- },
- Task::FetchWorkspace(progress) => {
- let (state, msg) = match progress {
- ProjectWorkspaceProgress::Begin => (Progress::Begin, None),
- ProjectWorkspaceProgress::Report(msg) => {
- (Progress::Report, Some(msg))
- }
- ProjectWorkspaceProgress::End(workspaces) => {
- self.fetch_workspaces_queue.op_completed(workspaces);
-
- let old = Arc::clone(&self.workspaces);
- self.switch_workspaces("fetched workspace".to_string());
- let workspaces_updated = !Arc::ptr_eq(&old, &self.workspaces);
- if self.config.run_build_scripts() && workspaces_updated {
- self.fetch_build_data_queue
- .request_op(format!("workspace updated"));
- }
-
- (Progress::End, None)
- }
- };
-
- self.report_progress("Fetching", state, msg, None);
- }
- Task::FetchBuildData(progress) => {
- let (state, msg) = match progress {
- BuildDataProgress::Begin => (Some(Progress::Begin), None),
- BuildDataProgress::Report(msg) => {
- (Some(Progress::Report), Some(msg))
- }
- BuildDataProgress::End(build_data_result) => {
- self.fetch_build_data_queue.op_completed(build_data_result);
-
- self.switch_workspaces("fetched build data".to_string());
-
- (Some(Progress::End), None)
- }
- };
-
- if let Some(state) = state {
- self.report_progress("Loading", state, msg, None);
- }
- }
- }
-
- // Coalesce multiple task events into one loop turn
- task = match self.task_pool.receiver.try_recv() {
- Ok(task) => task,
- Err(_) => break,
- };
+ self.handle_task(&mut prime_caches_progress, task);
+ // Coalesce multiple task events into one loop turn
+ while let Ok(task) = self.task_pool.receiver.try_recv() {
+ self.handle_task(&mut prime_caches_progress, task);
}
for progress in prime_caches_progress {
@@ -324,118 +260,20 @@ impl GlobalState {
self.report_progress("Indexing", state, message, Some(fraction));
}
}
- Event::Vfs(mut task) => {
+ Event::Vfs(message) => {
let _p = profile::span("GlobalState::handle_event/vfs");
- loop {
- match task {
- vfs::loader::Message::Loaded { files } => {
- let vfs = &mut self.vfs.write().0;
- for (path, contents) in files {
- let path = VfsPath::from(path);
- if !self.mem_docs.contains(&path) {
- vfs.set_file_contents(path, contents);
- }
- }
- }
- vfs::loader::Message::Progress { n_total, n_done, config_version } => {
- always!(config_version <= self.vfs_config_version);
-
- self.vfs_progress_config_version = config_version;
- self.vfs_progress_n_total = n_total;
- self.vfs_progress_n_done = n_done;
-
- let state = if n_done == 0 {
- Progress::Begin
- } else if n_done < n_total {
- Progress::Report
- } else {
- assert_eq!(n_done, n_total);
- Progress::End
- };
- self.report_progress(
- "Roots Scanned",
- state,
- Some(format!("{}/{}", n_done, n_total)),
- Some(Progress::fraction(n_done, n_total)),
- )
- }
- }
- // Coalesce many VFS event into a single loop turn
- task = match self.loader.receiver.try_recv() {
- Ok(task) => task,
- Err(_) => break,
- }
+ self.handle_vfs_msg(message);
+ // Coalesce many VFS event into a single loop turn
+ while let Ok(message) = self.loader.receiver.try_recv() {
+ self.handle_vfs_msg(message);
}
}
- Event::Flycheck(mut task) => {
+ Event::Flycheck(message) => {
let _p = profile::span("GlobalState::handle_event/flycheck");
- loop {
- match task {
- flycheck::Message::AddDiagnostic { workspace_root, diagnostic } => {
- let snap = self.snapshot();
- let diagnostics =
- crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp(
- &self.config.diagnostics_map(),
- &diagnostic,
- &workspace_root,
- &snap,
- );
- for diag in diagnostics {
- match url_to_file_id(&self.vfs.read().0, &diag.url) {
- Ok(file_id) => self.diagnostics.add_check_diagnostic(
- file_id,
- diag.diagnostic,
- diag.fix,
- ),
- Err(err) => {
- tracing::error!(
- "File with cargo diagnostic not found in VFS: {}",
- err
- );
- }
- };
- }
- }
-
- flycheck::Message::Progress { id, progress } => {
- let (state, message) = match progress {
- flycheck::Progress::DidStart => {
- self.diagnostics.clear_check();
- (Progress::Begin, None)
- }
- flycheck::Progress::DidCheckCrate(target) => {
- (Progress::Report, Some(target))
- }
- flycheck::Progress::DidCancel => (Progress::End, None),
- flycheck::Progress::DidFinish(result) => {
- if let Err(err) = result {
- self.show_and_log_error(
- "cargo check failed".to_string(),
- Some(err.to_string()),
- );
- }
- (Progress::End, None)
- }
- };
-
- // When we're running multiple flychecks, we have to include a disambiguator in
- // the title, or the editor complains. Note that this is a user-facing string.
- let title = if self.flycheck.len() == 1 {
- match self.config.flycheck() {
- Some(config) => format!("{}", config),
- None => "cargo check".to_string(),
- }
- } else {
- format!("cargo check (#{})", id + 1)
- };
- self.report_progress(&title, state, message, None);
- }
- }
- // Coalesce many flycheck updates into a single loop turn
- task = match self.flycheck_receiver.try_recv() {
- Ok(task) => task,
- Err(_) => break,
- }
+ self.handle_flycheck_msg(message);
+ // Coalesce many flycheck updates into a single loop turn
+ while let Ok(message) = self.flycheck_receiver.try_recv() {
+ self.handle_flycheck_msg(message);
}
}
}
@@ -444,10 +282,13 @@ impl GlobalState {
let memdocs_added_or_removed = self.mem_docs.take_changes();
if self.is_quiescent() {
- if !was_quiescent {
- for flycheck in &self.flycheck {
- flycheck.update();
- }
+ let became_quiescent = !(was_quiescent
+ || self.fetch_workspaces_queue.op_requested()
+ || self.fetch_build_data_queue.op_requested());
+
+ if became_quiescent {
+ // Project has loaded properly, kick off initial flycheck
+ self.flycheck.iter().for_each(FlycheckHandle::restart);
if self.config.prefill_caches() {
self.prime_caches_queue.request_op("became quiescent".to_string());
}
@@ -486,28 +327,40 @@ impl GlobalState {
continue;
}
- let url = file_id_to_url(&self.vfs.read().0, file_id);
+ let uri = file_id_to_url(&self.vfs.read().0, file_id);
let mut diagnostics =
self.diagnostics.diagnostics_for(file_id).cloned().collect::<Vec<_>>();
- // https://github.com/rust-lang/rust-analyzer/issues/11404
- for d in &mut diagnostics {
- if d.message.is_empty() {
- d.message = " ".to_string();
+
+ // VSCode assumes diagnostic messages to be non-empty strings, so we need to patch
+ // empty diagnostics. Neither the docs of VSCode nor the LSP spec say whether
+ // diagnostic messages are actually allowed to be empty or not and patching this
+ // in the VSCode client does not work as the assertion happens in the protocol
+ // conversion. So this hack is here to stay, and will be considered a hack
+ // until the LSP decides to state that empty messages are allowed.
+
+ // See https://github.com/rust-lang/rust-analyzer/issues/11404
+ // See https://github.com/rust-lang/rust-analyzer/issues/13130
+ let patch_empty = |message: &mut String| {
+ if message.is_empty() {
+ *message = " ".to_string();
}
- if let Some(rds) = d.related_information.as_mut() {
- for rd in rds {
- if rd.message.is_empty() {
- rd.message = " ".to_string();
- }
+ };
+
+ for d in &mut diagnostics {
+ patch_empty(&mut d.message);
+ if let Some(dri) = &mut d.related_information {
+ for dri in dri {
+ patch_empty(&mut dri.message);
}
}
}
- let version = from_proto::vfs_path(&url)
+
+ let version = from_proto::vfs_path(&uri)
.map(|path| self.mem_docs.get(&path).map(|it| it.version))
.unwrap_or_default();
self.send_notification::<lsp_types::notification::PublishDiagnostics>(
- lsp_types::PublishDiagnosticsParams { uri: url, diagnostics, version },
+ lsp_types::PublishDiagnosticsParams { uri, diagnostics, version },
);
}
}
@@ -569,11 +422,178 @@ impl GlobalState {
Ok(())
}
+ fn handle_task(&mut self, prime_caches_progress: &mut Vec<PrimeCachesProgress>, task: Task) {
+ match task {
+ Task::Response(response) => self.respond(response),
+ Task::Retry(req) => self.on_request(req),
+ Task::Diagnostics(diagnostics_per_file) => {
+ for (file_id, diagnostics) in diagnostics_per_file {
+ self.diagnostics.set_native_diagnostics(file_id, diagnostics)
+ }
+ }
+ Task::PrimeCaches(progress) => match progress {
+ PrimeCachesProgress::Begin => prime_caches_progress.push(progress),
+ PrimeCachesProgress::Report(_) => {
+ match prime_caches_progress.last_mut() {
+ Some(last @ PrimeCachesProgress::Report(_)) => {
+ // Coalesce subsequent update events.
+ *last = progress;
+ }
+ _ => prime_caches_progress.push(progress),
+ }
+ }
+ PrimeCachesProgress::End { .. } => prime_caches_progress.push(progress),
+ },
+ Task::FetchWorkspace(progress) => {
+ let (state, msg) = match progress {
+ ProjectWorkspaceProgress::Begin => (Progress::Begin, None),
+ ProjectWorkspaceProgress::Report(msg) => (Progress::Report, Some(msg)),
+ ProjectWorkspaceProgress::End(workspaces) => {
+ self.fetch_workspaces_queue.op_completed(workspaces);
+
+ let old = Arc::clone(&self.workspaces);
+ self.switch_workspaces("fetched workspace".to_string());
+ let workspaces_updated = !Arc::ptr_eq(&old, &self.workspaces);
+
+ if self.config.run_build_scripts() && workspaces_updated {
+ self.fetch_build_data_queue.request_op(format!("workspace updated"));
+ }
+
+ (Progress::End, None)
+ }
+ };
+
+ self.report_progress("Fetching", state, msg, None);
+ }
+ Task::FetchBuildData(progress) => {
+ let (state, msg) = match progress {
+ BuildDataProgress::Begin => (Some(Progress::Begin), None),
+ BuildDataProgress::Report(msg) => (Some(Progress::Report), Some(msg)),
+ BuildDataProgress::End(build_data_result) => {
+ self.fetch_build_data_queue.op_completed(build_data_result);
+
+ self.switch_workspaces("fetched build data".to_string());
+
+ (Some(Progress::End), None)
+ }
+ };
+
+ if let Some(state) = state {
+ self.report_progress("Loading", state, msg, None);
+ }
+ }
+ }
+ }
+
+ fn handle_vfs_msg(&mut self, message: vfs::loader::Message) {
+ match message {
+ vfs::loader::Message::Loaded { files } => {
+ let vfs = &mut self.vfs.write().0;
+ for (path, contents) in files {
+ let path = VfsPath::from(path);
+ if !self.mem_docs.contains(&path) {
+ vfs.set_file_contents(path, contents);
+ }
+ }
+ }
+ vfs::loader::Message::Progress { n_total, n_done, config_version } => {
+ always!(config_version <= self.vfs_config_version);
+
+ self.vfs_progress_config_version = config_version;
+ self.vfs_progress_n_total = n_total;
+ self.vfs_progress_n_done = n_done;
+
+ let state = if n_done == 0 {
+ Progress::Begin
+ } else if n_done < n_total {
+ Progress::Report
+ } else {
+ assert_eq!(n_done, n_total);
+ Progress::End
+ };
+ self.report_progress(
+ "Roots Scanned",
+ state,
+ Some(format!("{}/{}", n_done, n_total)),
+ Some(Progress::fraction(n_done, n_total)),
+ )
+ }
+ }
+ }
+
+ fn handle_flycheck_msg(&mut self, message: flycheck::Message) {
+ match message {
+ flycheck::Message::AddDiagnostic { id, workspace_root, diagnostic } => {
+ let snap = self.snapshot();
+ let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp(
+ &self.config.diagnostics_map(),
+ &diagnostic,
+ &workspace_root,
+ &snap,
+ );
+ for diag in diagnostics {
+ match url_to_file_id(&self.vfs.read().0, &diag.url) {
+ Ok(file_id) => self.diagnostics.add_check_diagnostic(
+ id,
+ file_id,
+ diag.diagnostic,
+ diag.fix,
+ ),
+ Err(err) => {
+ tracing::error!("File with cargo diagnostic not found in VFS: {}", err);
+ }
+ };
+ }
+ }
+
+ flycheck::Message::Progress { id, progress } => {
+ let (state, message) = match progress {
+ flycheck::Progress::DidStart => {
+ self.diagnostics.clear_check(id);
+ (Progress::Begin, None)
+ }
+ flycheck::Progress::DidCheckCrate(target) => (Progress::Report, Some(target)),
+ flycheck::Progress::DidCancel => (Progress::End, None),
+ flycheck::Progress::DidFailToRestart(err) => {
+ self.show_and_log_error(
+ "cargo check failed".to_string(),
+ Some(err.to_string()),
+ );
+ return;
+ }
+ flycheck::Progress::DidFinish(result) => {
+ if let Err(err) = result {
+ self.show_and_log_error(
+ "cargo check failed".to_string(),
+ Some(err.to_string()),
+ );
+ }
+ (Progress::End, None)
+ }
+ };
+
+ // When we're running multiple flychecks, we have to include a disambiguator in
+ // the title, or the editor complains. Note that this is a user-facing string.
+ let title = if self.flycheck.len() == 1 {
+ match self.config.flycheck() {
+ Some(config) => format!("{}", config),
+ None => "cargo check".to_string(),
+ }
+ } else {
+ format!("cargo check (#{})", id + 1)
+ };
+ self.report_progress(&title, state, message, None);
+ }
+ }
+ }
+
+ /// Registers and handles a request. This should only be called once per incoming request.
fn on_new_request(&mut self, request_received: Instant, req: Request) {
self.register_request(&req, request_received);
self.on_request(req);
}
+ /// Handles a request.
fn on_request(&mut self, req: Request) {
if self.shutdown_requested {
self.respond(lsp_server::Response::new_err(
@@ -602,6 +622,7 @@ impl GlobalState {
.on_sync_mut::<lsp_ext::ReloadWorkspace>(handlers::handle_workspace_reload)
.on_sync_mut::<lsp_ext::MemoryUsage>(handlers::handle_memory_usage)
.on_sync_mut::<lsp_ext::ShuffleCrateGraph>(handlers::handle_shuffle_crate_graph)
+ .on_sync_mut::<lsp_ext::CancelFlycheck>(handlers::handle_cancel_flycheck)
.on_sync::<lsp_ext::JoinLines>(handlers::handle_join_lines)
.on_sync::<lsp_ext::OnEnter>(handlers::handle_on_enter)
.on_sync::<lsp_types::request::SelectionRangeRequest>(handlers::handle_selection_range)
@@ -664,6 +685,7 @@ impl GlobalState {
.finish();
}
+ /// Handles an incoming notification.
fn on_notification(&mut self, not: Notification) -> Result<()> {
NotificationDispatcher { not: Some(not), global_state: self }
.on::<lsp_types::notification::Cancel>(|this, params| {
@@ -734,13 +756,82 @@ impl GlobalState {
Ok(())
})?
.on::<lsp_types::notification::DidSaveTextDocument>(|this, params| {
- for flycheck in &this.flycheck {
- flycheck.update();
+ let mut updated = false;
+ if let Ok(vfs_path) = from_proto::vfs_path(&params.text_document.uri) {
+ let (vfs, _) = &*this.vfs.read();
+
+ // Trigger flychecks for all workspaces that depend on the saved file
+ if let Some(file_id) = vfs.file_id(&vfs_path) {
+ let analysis = this.analysis_host.analysis();
+ // Crates containing or depending on the saved file
+ let crate_ids: Vec<_> = analysis
+ .crate_for(file_id)?
+ .into_iter()
+ .flat_map(|id| {
+ this.analysis_host
+ .raw_database()
+ .crate_graph()
+ .transitive_rev_deps(id)
+ })
+ .sorted()
+ .unique()
+ .collect();
+
+ let crate_root_paths: Vec<_> = crate_ids
+ .iter()
+ .filter_map(|&crate_id| {
+ analysis
+ .crate_root(crate_id)
+ .map(|file_id| {
+ vfs.file_path(file_id).as_path().map(ToOwned::to_owned)
+ })
+ .transpose()
+ })
+ .collect::<ide::Cancellable<_>>()?;
+ let crate_root_paths: Vec<_> =
+ crate_root_paths.iter().map(Deref::deref).collect();
+
+ // Find all workspaces that have at least one target containing the saved file
+ let workspace_ids =
+ this.workspaces.iter().enumerate().filter(|(_, ws)| match ws {
+ project_model::ProjectWorkspace::Cargo { cargo, .. } => {
+ cargo.packages().any(|pkg| {
+ cargo[pkg].targets.iter().any(|&it| {
+ crate_root_paths.contains(&cargo[it].root.as_path())
+ })
+ })
+ }
+ project_model::ProjectWorkspace::Json { project, .. } => project
+ .crates()
+ .any(|(c, _)| crate_ids.iter().any(|&crate_id| crate_id == c)),
+ project_model::ProjectWorkspace::DetachedFiles { .. } => false,
+ });
+
+ // Find and trigger corresponding flychecks
+ for flycheck in &this.flycheck {
+ for (id, _) in workspace_ids.clone() {
+ if id == flycheck.id() {
+ updated = true;
+ flycheck.restart();
+ continue;
+ }
+ }
+ }
+ }
+
+ // Re-fetch workspaces if a workspace related file has changed
+ if let Some(abs_path) = vfs_path.as_path() {
+ if reload::should_refresh_for_change(&abs_path, ChangeKind::Modify) {
+ this.fetch_workspaces_queue
+ .request_op(format!("DidSaveTextDocument {}", abs_path.display()));
+ }
+ }
}
- if let Ok(abs_path) = from_proto::abs_path(&params.text_document.uri) {
- if reload::should_refresh_for_change(&abs_path, ChangeKind::Modify) {
- this.fetch_workspaces_queue
- .request_op(format!("DidSaveTextDocument {}", abs_path.display()));
+
+ // No specific flycheck was triggered, so let's trigger all of them.
+ if !updated {
+ for flycheck in &this.flycheck {
+ flycheck.restart();
}
}
Ok(())
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs
index eaab275bc..e47f70fff 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs
@@ -196,10 +196,7 @@ impl GlobalState {
}
if let Err(error) = self.fetch_build_data_error() {
- self.show_and_log_error(
- "rust-analyzer failed to run build scripts".to_string(),
- Some(error),
- );
+ self.show_and_log_error("failed to run build scripts".to_string(), Some(error));
}
let workspaces = self
@@ -222,6 +219,7 @@ impl GlobalState {
cfg_overrides,
build_scripts: _,
+ toolchain: _,
} => Some((cargo, sysroot, rustc, rustc_cfg, cfg_overrides)),
_ => None,
};
@@ -308,6 +306,7 @@ impl GlobalState {
if self.proc_macro_clients.is_empty() {
if let Some((path, args)) = self.config.proc_macro_srv() {
+ tracing::info!("Spawning proc-macro servers");
self.proc_macro_clients = self
.workspaces
.iter()
@@ -315,21 +314,23 @@ impl GlobalState {
let mut args = args.clone();
let mut path = path.clone();
- if let ProjectWorkspace::Cargo { sysroot, .. } = ws {
- tracing::info!("Found a cargo workspace...");
+ if let ProjectWorkspace::Cargo { sysroot, .. }
+ | ProjectWorkspace::Json { sysroot, .. } = ws
+ {
+ tracing::debug!("Found a cargo workspace...");
if let Some(sysroot) = sysroot.as_ref() {
- tracing::info!("Found a cargo workspace with a sysroot...");
+ tracing::debug!("Found a cargo workspace with a sysroot...");
let server_path =
sysroot.root().join("libexec").join(&standalone_server_name);
if std::fs::metadata(&server_path).is_ok() {
- tracing::info!(
+ tracing::debug!(
"And the server exists at {}",
server_path.display()
);
path = server_path;
args = vec![];
} else {
- tracing::info!(
+ tracing::debug!(
"And the server does not exist at {}",
server_path.display()
);
@@ -337,14 +338,10 @@ impl GlobalState {
}
}
- tracing::info!(
- "Using proc-macro server at {} with args {:?}",
- path.display(),
- args
- );
+ tracing::info!(?args, "Using proc-macro server at {}", path.display(),);
ProcMacroServer::spawn(path.clone(), args.clone()).map_err(|err| {
let error = format!(
- "Failed to run proc_macro_srv from path {}, error: {:?}",
+ "Failed to run proc-macro server from path {}, error: {:?}",
path.display(),
err
);
@@ -352,8 +349,8 @@ impl GlobalState {
error
})
})
- .collect();
- }
+ .collect()
+ };
}
let watch = match files_config.watcher {
@@ -458,7 +455,7 @@ impl GlobalState {
Some(it) => it,
None => {
self.flycheck = Vec::new();
- self.diagnostics.clear_check();
+ self.diagnostics.clear_check_all();
return;
}
};
@@ -621,7 +618,10 @@ pub(crate) fn load_proc_macro(
};
let expander: Arc<dyn ProcMacroExpander> =
if dummy_replace.iter().any(|replace| &**replace == name) {
- Arc::new(DummyExpander)
+ match kind {
+ ProcMacroKind::Attr => Arc::new(IdentityExpander),
+ _ => Arc::new(EmptyExpander),
+ }
} else {
Arc::new(Expander(expander))
};
@@ -647,11 +647,11 @@ pub(crate) fn load_proc_macro(
}
}
- /// Dummy identity expander, used for proc-macros that are deliberately ignored by the user.
+ /// Dummy identity expander, used for attribute proc-macros that are deliberately ignored by the user.
#[derive(Debug)]
- struct DummyExpander;
+ struct IdentityExpander;
- impl ProcMacroExpander for DummyExpander {
+ impl ProcMacroExpander for IdentityExpander {
fn expand(
&self,
subtree: &tt::Subtree,
@@ -661,27 +661,46 @@ pub(crate) fn load_proc_macro(
Ok(subtree.clone())
}
}
+
+ /// Empty expander, used for proc-macros that are deliberately ignored by the user.
+ #[derive(Debug)]
+ struct EmptyExpander;
+
+ impl ProcMacroExpander for EmptyExpander {
+ fn expand(
+ &self,
+ _: &tt::Subtree,
+ _: Option<&tt::Subtree>,
+ _: &Env,
+ ) -> Result<tt::Subtree, ProcMacroExpansionError> {
+ Ok(tt::Subtree::default())
+ }
+ }
}
pub(crate) fn should_refresh_for_change(path: &AbsPath, change_kind: ChangeKind) -> bool {
const IMPLICIT_TARGET_FILES: &[&str] = &["build.rs", "src/main.rs", "src/lib.rs"];
const IMPLICIT_TARGET_DIRS: &[&str] = &["src/bin", "examples", "tests", "benches"];
- let file_name = path.file_name().unwrap_or_default();
- if file_name == "Cargo.toml" || file_name == "Cargo.lock" {
+ let file_name = match path.file_name().unwrap_or_default().to_str() {
+ Some(it) => it,
+ None => return false,
+ };
+
+ if let "Cargo.toml" | "Cargo.lock" = file_name {
return true;
}
if change_kind == ChangeKind::Modify {
return false;
}
+
+ // .cargo/config{.toml}
if path.extension().unwrap_or_default() != "rs" {
- if (file_name == "config.toml" || file_name == "config")
- && path.parent().map(|parent| parent.as_ref().ends_with(".cargo")) == Some(true)
- {
- return true;
- }
- return false;
+ let is_cargo_config = matches!(file_name, "config.toml" | "config")
+ && path.parent().map(|parent| parent.as_ref().ends_with(".cargo")).unwrap_or(false);
+ return is_cargo_config;
}
+
if IMPLICIT_TARGET_FILES.iter().any(|it| path.as_ref().ends_with(it)) {
return true;
}
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/semantic_tokens.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/semantic_tokens.rs
index 6c78b5df1..c48410ed5 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/semantic_tokens.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/semantic_tokens.rs
@@ -8,107 +8,130 @@ use lsp_types::{
};
macro_rules! define_semantic_token_types {
- ($(($ident:ident, $string:literal)),*$(,)?) => {
- $(pub(crate) const $ident: SemanticTokenType = SemanticTokenType::new($string);)*
+ (
+ standard {
+ $($standard:ident),*$(,)?
+ }
+ custom {
+ $(($custom:ident, $string:literal)),*$(,)?
+ }
+
+ ) => {
+ $(pub(crate) const $standard: SemanticTokenType = SemanticTokenType::$standard;)*
+ $(pub(crate) const $custom: SemanticTokenType = SemanticTokenType::new($string);)*
pub(crate) const SUPPORTED_TYPES: &[SemanticTokenType] = &[
- SemanticTokenType::COMMENT,
- SemanticTokenType::KEYWORD,
- SemanticTokenType::STRING,
- SemanticTokenType::NUMBER,
- SemanticTokenType::REGEXP,
- SemanticTokenType::OPERATOR,
- SemanticTokenType::NAMESPACE,
- SemanticTokenType::TYPE,
- SemanticTokenType::STRUCT,
- SemanticTokenType::CLASS,
- SemanticTokenType::INTERFACE,
- SemanticTokenType::ENUM,
- SemanticTokenType::ENUM_MEMBER,
- SemanticTokenType::TYPE_PARAMETER,
- SemanticTokenType::FUNCTION,
- SemanticTokenType::METHOD,
- SemanticTokenType::PROPERTY,
- SemanticTokenType::MACRO,
- SemanticTokenType::VARIABLE,
- SemanticTokenType::PARAMETER,
- $($ident),*
+ $(SemanticTokenType::$standard,)*
+ $($custom),*
];
};
}
define_semantic_token_types![
- (ANGLE, "angle"),
- (ARITHMETIC, "arithmetic"),
- (ATTRIBUTE, "attribute"),
- (ATTRIBUTE_BRACKET, "attributeBracket"),
- (BITWISE, "bitwise"),
- (BOOLEAN, "boolean"),
- (BRACE, "brace"),
- (BRACKET, "bracket"),
- (BUILTIN_ATTRIBUTE, "builtinAttribute"),
- (BUILTIN_TYPE, "builtinType"),
- (CHAR, "character"),
- (COLON, "colon"),
- (COMMA, "comma"),
- (COMPARISON, "comparison"),
- (CONST_PARAMETER, "constParameter"),
- (DERIVE, "derive"),
- (DERIVE_HELPER, "deriveHelper"),
- (DOT, "dot"),
- (ESCAPE_SEQUENCE, "escapeSequence"),
- (FORMAT_SPECIFIER, "formatSpecifier"),
- (GENERIC, "generic"),
- (LABEL, "label"),
- (LIFETIME, "lifetime"),
- (LOGICAL, "logical"),
- (MACRO_BANG, "macroBang"),
- (OPERATOR, "operator"),
- (PARENTHESIS, "parenthesis"),
- (PUNCTUATION, "punctuation"),
- (SELF_KEYWORD, "selfKeyword"),
- (SELF_TYPE_KEYWORD, "selfTypeKeyword"),
- (SEMICOLON, "semicolon"),
- (TYPE_ALIAS, "typeAlias"),
- (TOOL_MODULE, "toolModule"),
- (UNION, "union"),
- (UNRESOLVED_REFERENCE, "unresolvedReference"),
+ standard {
+ COMMENT,
+ DECORATOR,
+ ENUM_MEMBER,
+ ENUM,
+ FUNCTION,
+ INTERFACE,
+ KEYWORD,
+ MACRO,
+ METHOD,
+ NAMESPACE,
+ NUMBER,
+ OPERATOR,
+ PARAMETER,
+ PROPERTY,
+ STRING,
+ STRUCT,
+ TYPE_PARAMETER,
+ VARIABLE,
+ }
+
+ custom {
+ (ANGLE, "angle"),
+ (ARITHMETIC, "arithmetic"),
+ (ATTRIBUTE, "attribute"),
+ (ATTRIBUTE_BRACKET, "attributeBracket"),
+ (BITWISE, "bitwise"),
+ (BOOLEAN, "boolean"),
+ (BRACE, "brace"),
+ (BRACKET, "bracket"),
+ (BUILTIN_ATTRIBUTE, "builtinAttribute"),
+ (BUILTIN_TYPE, "builtinType"),
+ (CHAR, "character"),
+ (COLON, "colon"),
+ (COMMA, "comma"),
+ (COMPARISON, "comparison"),
+ (CONST_PARAMETER, "constParameter"),
+ (DERIVE, "derive"),
+ (DERIVE_HELPER, "deriveHelper"),
+ (DOT, "dot"),
+ (ESCAPE_SEQUENCE, "escapeSequence"),
+ (FORMAT_SPECIFIER, "formatSpecifier"),
+ (GENERIC, "generic"),
+ (LABEL, "label"),
+ (LIFETIME, "lifetime"),
+ (LOGICAL, "logical"),
+ (MACRO_BANG, "macroBang"),
+ (PARENTHESIS, "parenthesis"),
+ (PUNCTUATION, "punctuation"),
+ (SELF_KEYWORD, "selfKeyword"),
+ (SELF_TYPE_KEYWORD, "selfTypeKeyword"),
+ (SEMICOLON, "semicolon"),
+ (TYPE_ALIAS, "typeAlias"),
+ (TOOL_MODULE, "toolModule"),
+ (UNION, "union"),
+ (UNRESOLVED_REFERENCE, "unresolvedReference"),
+ }
];
macro_rules! define_semantic_token_modifiers {
- ($(($ident:ident, $string:literal)),*$(,)?) => {
- $(pub(crate) const $ident: SemanticTokenModifier = SemanticTokenModifier::new($string);)*
+ (
+ standard {
+ $($standard:ident),*$(,)?
+ }
+ custom {
+ $(($custom:ident, $string:literal)),*$(,)?
+ }
+
+ ) => {
+
+ $(pub(crate) const $standard: SemanticTokenModifier = SemanticTokenModifier::$standard;)*
+ $(pub(crate) const $custom: SemanticTokenModifier = SemanticTokenModifier::new($string);)*
pub(crate) const SUPPORTED_MODIFIERS: &[SemanticTokenModifier] = &[
- SemanticTokenModifier::DOCUMENTATION,
- SemanticTokenModifier::DECLARATION,
- SemanticTokenModifier::DEFINITION,
- SemanticTokenModifier::STATIC,
- SemanticTokenModifier::ABSTRACT,
- SemanticTokenModifier::DEPRECATED,
- SemanticTokenModifier::READONLY,
- SemanticTokenModifier::DEFAULT_LIBRARY,
- $($ident),*
+ $(SemanticTokenModifier::$standard,)*
+ $($custom),*
];
};
}
define_semantic_token_modifiers![
- (ASYNC, "async"),
- (ATTRIBUTE_MODIFIER, "attribute"),
- (CALLABLE, "callable"),
- (CONSTANT, "constant"),
- (CONSUMING, "consuming"),
- (CONTROL_FLOW, "controlFlow"),
- (CRATE_ROOT, "crateRoot"),
- (INJECTED, "injected"),
- (INTRA_DOC_LINK, "intraDocLink"),
- (LIBRARY, "library"),
- (MUTABLE, "mutable"),
- (PUBLIC, "public"),
- (REFERENCE, "reference"),
- (TRAIT_MODIFIER, "trait"),
- (UNSAFE, "unsafe"),
+ standard {
+ DOCUMENTATION,
+ DECLARATION,
+ STATIC,
+ DEFAULT_LIBRARY,
+ }
+ custom {
+ (ASYNC, "async"),
+ (ATTRIBUTE_MODIFIER, "attribute"),
+ (CALLABLE, "callable"),
+ (CONSTANT, "constant"),
+ (CONSUMING, "consuming"),
+ (CONTROL_FLOW, "controlFlow"),
+ (CRATE_ROOT, "crateRoot"),
+ (INJECTED, "injected"),
+ (INTRA_DOC_LINK, "intraDocLink"),
+ (LIBRARY, "library"),
+ (MUTABLE, "mutable"),
+ (PUBLIC, "public"),
+ (REFERENCE, "reference"),
+ (TRAIT_MODIFIER, "trait"),
+ (UNSAFE, "unsafe"),
+ }
];
#[derive(Default)]
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/to_proto.rs
index 7f4fa57fa..e083b9d0e 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/to_proto.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/to_proto.rs
@@ -9,8 +9,9 @@ use ide::{
Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionItem,
CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit,
Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, InlayHint,
- InlayKind, Markup, NavigationTarget, ReferenceCategory, RenameError, Runnable, Severity,
- SignatureHelp, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize,
+ InlayHintLabel, InlayKind, Markup, NavigationTarget, ReferenceCategory, RenameError, Runnable,
+ Severity, SignatureHelp, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange,
+ TextSize,
};
use itertools::Itertools;
use serde_json::to_value;
@@ -426,9 +427,16 @@ pub(crate) fn inlay_hint(
snap: &GlobalStateSnapshot,
line_index: &LineIndex,
render_colons: bool,
- inlay_hint: InlayHint,
-) -> lsp_types::InlayHint {
- lsp_types::InlayHint {
+ mut inlay_hint: InlayHint,
+) -> Result<lsp_types::InlayHint> {
+ match inlay_hint.kind {
+ InlayKind::ParameterHint if render_colons => inlay_hint.label.append_str(":"),
+ InlayKind::TypeHint if render_colons => inlay_hint.label.prepend_str(": "),
+ InlayKind::ClosureReturnTypeHint => inlay_hint.label.prepend_str(" -> "),
+ _ => {}
+ }
+
+ Ok(lsp_types::InlayHint {
position: match inlay_hint.kind {
// before annotated thing
InlayKind::ParameterHint
@@ -459,15 +467,9 @@ pub(crate) fn inlay_hint(
| InlayKind::ImplicitReborrowHint
| InlayKind::TypeHint
| InlayKind::ClosingBraceHint => false,
- InlayKind::BindingModeHint => inlay_hint.label != "&",
+ InlayKind::BindingModeHint => inlay_hint.label.as_simple_str() != Some("&"),
InlayKind::ParameterHint | InlayKind::LifetimeHint => true,
}),
- label: lsp_types::InlayHintLabel::String(match inlay_hint.kind {
- InlayKind::ParameterHint if render_colons => format!("{}:", inlay_hint.label),
- InlayKind::TypeHint if render_colons => format!(": {}", inlay_hint.label),
- InlayKind::ClosureReturnTypeHint => format!(" -> {}", inlay_hint.label),
- _ => inlay_hint.label.clone(),
- }),
kind: match inlay_hint.kind {
InlayKind::ParameterHint => Some(lsp_types::InlayHintKind::PARAMETER),
InlayKind::ClosureReturnTypeHint | InlayKind::TypeHint | InlayKind::ChainingHint => {
@@ -506,9 +508,36 @@ pub(crate) fn inlay_hint(
})(),
tooltip: Some(match inlay_hint.tooltip {
Some(ide::InlayTooltip::String(s)) => lsp_types::InlayHintTooltip::String(s),
- _ => lsp_types::InlayHintTooltip::String(inlay_hint.label),
+ _ => lsp_types::InlayHintTooltip::String(inlay_hint.label.to_string()),
}),
- }
+ label: inlay_hint_label(snap, inlay_hint.label)?,
+ })
+}
+
+fn inlay_hint_label(
+ snap: &GlobalStateSnapshot,
+ label: InlayHintLabel,
+) -> Result<lsp_types::InlayHintLabel> {
+ Ok(match label.as_simple_str() {
+ Some(s) => lsp_types::InlayHintLabel::String(s.into()),
+ None => lsp_types::InlayHintLabel::LabelParts(
+ label
+ .parts
+ .into_iter()
+ .map(|part| {
+ Ok(lsp_types::InlayHintLabelPart {
+ value: part.text,
+ tooltip: None,
+ location: part
+ .linked_location
+ .map(|range| location(snap, range))
+ .transpose()?,
+ command: None,
+ })
+ })
+ .collect::<Result<Vec<_>>>()?,
+ ),
+ })
}
static TOKEN_RESULT_COUNTER: AtomicU32 = AtomicU32::new(1);
@@ -517,7 +546,6 @@ pub(crate) fn semantic_tokens(
text: &str,
line_index: &LineIndex,
highlights: Vec<HlRange>,
- highlight_strings: bool,
) -> lsp_types::SemanticTokens {
let id = TOKEN_RESULT_COUNTER.fetch_add(1, Ordering::SeqCst).to_string();
let mut builder = semantic_tokens::SemanticTokensBuilder::new(id);
@@ -526,10 +554,8 @@ pub(crate) fn semantic_tokens(
if highlight_range.highlight.is_empty() {
continue;
}
+
let (ty, mods) = semantic_token_type_and_modifiers(highlight_range.highlight);
- if !highlight_strings && ty == lsp_types::SemanticTokenType::STRING {
- continue;
- }
let token_index = semantic_tokens::type_index(ty);
let modifier_bitset = mods.0;
@@ -561,55 +587,55 @@ fn semantic_token_type_and_modifiers(
let mut mods = semantic_tokens::ModifierSet::default();
let type_ = match highlight.tag {
HlTag::Symbol(symbol) => match symbol {
- SymbolKind::Attribute => semantic_tokens::ATTRIBUTE,
+ SymbolKind::Attribute => semantic_tokens::DECORATOR,
SymbolKind::Derive => semantic_tokens::DERIVE,
SymbolKind::DeriveHelper => semantic_tokens::DERIVE_HELPER,
- SymbolKind::Module => lsp_types::SemanticTokenType::NAMESPACE,
+ SymbolKind::Module => semantic_tokens::NAMESPACE,
SymbolKind::Impl => semantic_tokens::TYPE_ALIAS,
- SymbolKind::Field => lsp_types::SemanticTokenType::PROPERTY,
- SymbolKind::TypeParam => lsp_types::SemanticTokenType::TYPE_PARAMETER,
+ SymbolKind::Field => semantic_tokens::PROPERTY,
+ SymbolKind::TypeParam => semantic_tokens::TYPE_PARAMETER,
SymbolKind::ConstParam => semantic_tokens::CONST_PARAMETER,
SymbolKind::LifetimeParam => semantic_tokens::LIFETIME,
SymbolKind::Label => semantic_tokens::LABEL,
- SymbolKind::ValueParam => lsp_types::SemanticTokenType::PARAMETER,
+ SymbolKind::ValueParam => semantic_tokens::PARAMETER,
SymbolKind::SelfParam => semantic_tokens::SELF_KEYWORD,
SymbolKind::SelfType => semantic_tokens::SELF_TYPE_KEYWORD,
- SymbolKind::Local => lsp_types::SemanticTokenType::VARIABLE,
+ SymbolKind::Local => semantic_tokens::VARIABLE,
SymbolKind::Function => {
if highlight.mods.contains(HlMod::Associated) {
- lsp_types::SemanticTokenType::METHOD
+ semantic_tokens::METHOD
} else {
- lsp_types::SemanticTokenType::FUNCTION
+ semantic_tokens::FUNCTION
}
}
SymbolKind::Const => {
mods |= semantic_tokens::CONSTANT;
- mods |= lsp_types::SemanticTokenModifier::STATIC;
- lsp_types::SemanticTokenType::VARIABLE
+ mods |= semantic_tokens::STATIC;
+ semantic_tokens::VARIABLE
}
SymbolKind::Static => {
- mods |= lsp_types::SemanticTokenModifier::STATIC;
- lsp_types::SemanticTokenType::VARIABLE
+ mods |= semantic_tokens::STATIC;
+ semantic_tokens::VARIABLE
}
- SymbolKind::Struct => lsp_types::SemanticTokenType::STRUCT,
- SymbolKind::Enum => lsp_types::SemanticTokenType::ENUM,
- SymbolKind::Variant => lsp_types::SemanticTokenType::ENUM_MEMBER,
+ SymbolKind::Struct => semantic_tokens::STRUCT,
+ SymbolKind::Enum => semantic_tokens::ENUM,
+ SymbolKind::Variant => semantic_tokens::ENUM_MEMBER,
SymbolKind::Union => semantic_tokens::UNION,
SymbolKind::TypeAlias => semantic_tokens::TYPE_ALIAS,
- SymbolKind::Trait => lsp_types::SemanticTokenType::INTERFACE,
- SymbolKind::Macro => lsp_types::SemanticTokenType::MACRO,
+ SymbolKind::Trait => semantic_tokens::INTERFACE,
+ SymbolKind::Macro => semantic_tokens::MACRO,
SymbolKind::BuiltinAttr => semantic_tokens::BUILTIN_ATTRIBUTE,
SymbolKind::ToolModule => semantic_tokens::TOOL_MODULE,
},
HlTag::AttributeBracket => semantic_tokens::ATTRIBUTE_BRACKET,
HlTag::BoolLiteral => semantic_tokens::BOOLEAN,
HlTag::BuiltinType => semantic_tokens::BUILTIN_TYPE,
- HlTag::ByteLiteral | HlTag::NumericLiteral => lsp_types::SemanticTokenType::NUMBER,
+ HlTag::ByteLiteral | HlTag::NumericLiteral => semantic_tokens::NUMBER,
HlTag::CharLiteral => semantic_tokens::CHAR,
- HlTag::Comment => lsp_types::SemanticTokenType::COMMENT,
+ HlTag::Comment => semantic_tokens::COMMENT,
HlTag::EscapeSequence => semantic_tokens::ESCAPE_SEQUENCE,
HlTag::FormatSpecifier => semantic_tokens::FORMAT_SPECIFIER,
- HlTag::Keyword => lsp_types::SemanticTokenType::KEYWORD,
+ HlTag::Keyword => semantic_tokens::KEYWORD,
HlTag::None => semantic_tokens::GENERIC,
HlTag::Operator(op) => match op {
HlOperator::Bitwise => semantic_tokens::BITWISE,
@@ -618,7 +644,7 @@ fn semantic_token_type_and_modifiers(
HlOperator::Comparison => semantic_tokens::COMPARISON,
HlOperator::Other => semantic_tokens::OPERATOR,
},
- HlTag::StringLiteral => lsp_types::SemanticTokenType::STRING,
+ HlTag::StringLiteral => semantic_tokens::STRING,
HlTag::UnresolvedReference => semantic_tokens::UNRESOLVED_REFERENCE,
HlTag::Punctuation(punct) => match punct {
HlPunct::Bracket => semantic_tokens::BRACKET,
@@ -643,16 +669,16 @@ fn semantic_token_type_and_modifiers(
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::DefaultLibrary => semantic_tokens::DEFAULT_LIBRARY,
+ HlMod::Definition => semantic_tokens::DECLARATION,
+ HlMod::Documentation => semantic_tokens::DOCUMENTATION,
HlMod::Injected => semantic_tokens::INJECTED,
HlMod::IntraDocLink => semantic_tokens::INTRA_DOC_LINK,
HlMod::Library => semantic_tokens::LIBRARY,
HlMod::Mutable => semantic_tokens::MUTABLE,
HlMod::Public => semantic_tokens::PUBLIC,
HlMod::Reference => semantic_tokens::REFERENCE,
- HlMod::Static => lsp_types::SemanticTokenModifier::STATIC,
+ HlMod::Static => semantic_tokens::STATIC,
HlMod::Trait => semantic_tokens::TRAIT_MODIFIER,
HlMod::Unsafe => semantic_tokens::UNSAFE,
};
@@ -1386,7 +1412,7 @@ fn main() {
#[test]
#[cfg(target_os = "windows")]
fn test_lowercase_drive_letter() {
- use std::{convert::TryInto, path::Path};
+ use std::path::Path;
let url = url_from_abs_path(Path::new("C:\\Test").try_into().unwrap());
assert_eq!(url.to_string(), "file:///c:/Test");