From 284483b347d15bee3a7bf293d33e5f19a9740102 Mon Sep 17 00:00:00 2001 From: Jamie Cunliffe Date: Sun, 30 May 2021 14:52:19 +0100 Subject: [PATCH] Improve completion of cfg attributes The completion of cfg will look at the enabled cfg keys when performing completion. It will also look crate features when completing a feature cfg option. A fixed list of known values for some cfg options are provided. For unknown keys it will look at the enabled values for that cfg key, which means that completion will only show enabled options for those. --- crates/base_db/src/fixture.rs | 2 + crates/base_db/src/input.rs | 13 ++ crates/cfg/src/lib.rs | 20 +++ crates/hir/src/lib.rs | 4 + crates/ide/src/lib.rs | 1 + .../src/completions/attribute.rs | 15 +++ .../src/completions/attribute/cfg.rs | 126 ++++++++++++++++++ crates/project_model/src/workspace.rs | 4 + 8 files changed, 185 insertions(+) create mode 100644 crates/ide_completion/src/completions/attribute/cfg.rs diff --git a/crates/base_db/src/fixture.rs b/crates/base_db/src/fixture.rs index 6ce3777106a..6d3b1266e6f 100644 --- a/crates/base_db/src/fixture.rs +++ b/crates/base_db/src/fixture.rs @@ -131,6 +131,7 @@ pub fn parse(ra_fixture: &str) -> ChangeFixture { meta.cfg, meta.env, Default::default(), + Default::default(), ); let prev = crates.insert(crate_name.clone(), crate_id); assert!(prev.is_none()); @@ -160,6 +161,7 @@ pub fn parse(ra_fixture: &str) -> ChangeFixture { default_cfg, Env::default(), Default::default(), + Default::default(), ); } else { for (from, to) in crate_deps { diff --git a/crates/base_db/src/input.rs b/crates/base_db/src/input.rs index 23cb0c839ff..d99388f7163 100644 --- a/crates/base_db/src/input.rs +++ b/crates/base_db/src/input.rs @@ -192,6 +192,7 @@ pub struct CrateData { pub env: Env, pub dependencies: Vec, pub proc_macro: Vec, + pub features: FxHashMap>, } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -221,6 +222,7 @@ pub fn add_crate_root( cfg_options: CfgOptions, env: Env, proc_macro: Vec, + features: FxHashMap>, ) -> CrateId { let data = CrateData { root_file_id: file_id, @@ -230,6 +232,7 @@ pub fn add_crate_root( env, proc_macro, dependencies: Vec::new(), + features, }; let crate_id = CrateId(self.arena.len() as u32); let prev = self.arena.insert(crate_id, data); @@ -506,6 +509,7 @@ fn detect_cyclic_dependency_indirect() { CfgOptions::default(), Env::default(), Default::default(), + Default::default(), ); let crate2 = graph.add_crate_root( FileId(2u32), @@ -514,6 +518,7 @@ fn detect_cyclic_dependency_indirect() { CfgOptions::default(), Env::default(), Default::default(), + Default::default(), ); let crate3 = graph.add_crate_root( FileId(3u32), @@ -522,6 +527,7 @@ fn detect_cyclic_dependency_indirect() { CfgOptions::default(), Env::default(), Default::default(), + Default::default(), ); assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok()); assert!(graph.add_dep(crate2, CrateName::new("crate3").unwrap(), crate3).is_ok()); @@ -538,6 +544,7 @@ fn detect_cyclic_dependency_direct() { CfgOptions::default(), Env::default(), Default::default(), + Default::default(), ); let crate2 = graph.add_crate_root( FileId(2u32), @@ -546,6 +553,7 @@ fn detect_cyclic_dependency_direct() { CfgOptions::default(), Env::default(), Default::default(), + Default::default(), ); assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok()); assert!(graph.add_dep(crate2, CrateName::new("crate2").unwrap(), crate2).is_err()); @@ -561,6 +569,7 @@ fn it_works() { CfgOptions::default(), Env::default(), Default::default(), + Default::default(), ); let crate2 = graph.add_crate_root( FileId(2u32), @@ -569,6 +578,7 @@ fn it_works() { CfgOptions::default(), Env::default(), Default::default(), + Default::default(), ); let crate3 = graph.add_crate_root( FileId(3u32), @@ -577,6 +587,7 @@ fn it_works() { CfgOptions::default(), Env::default(), Default::default(), + Default::default(), ); assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok()); assert!(graph.add_dep(crate2, CrateName::new("crate3").unwrap(), crate3).is_ok()); @@ -592,6 +603,7 @@ fn dashes_are_normalized() { CfgOptions::default(), Env::default(), Default::default(), + Default::default(), ); let crate2 = graph.add_crate_root( FileId(2u32), @@ -600,6 +612,7 @@ fn dashes_are_normalized() { CfgOptions::default(), Env::default(), Default::default(), + Default::default(), ); assert!(graph .add_dep(crate1, CrateName::normalize_dashes("crate-name-with-dashes"), crate2) diff --git a/crates/cfg/src/lib.rs b/crates/cfg/src/lib.rs index 916d39a0b4a..9a4baa63694 100644 --- a/crates/cfg/src/lib.rs +++ b/crates/cfg/src/lib.rs @@ -50,6 +50,26 @@ pub fn apply_diff(&mut self, diff: CfgDiff) { self.enabled.remove(&atom); } } + + pub fn get_cfg_keys(&self) -> Vec<&SmolStr> { + self.enabled + .iter() + .map(|x| match x { + CfgAtom::Flag(key) => key, + CfgAtom::KeyValue { key, .. } => key, + }) + .collect() + } + + pub fn get_cfg_values(&self, cfg_key: &str) -> Vec<&SmolStr> { + self.enabled + .iter() + .filter_map(|x| match x { + CfgAtom::KeyValue { key, value } if cfg_key == key => Some(value), + _ => None, + }) + .collect() + } } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 88490fea98f..2b2aaec940b 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -233,6 +233,10 @@ pub fn get_html_root_url(self: &Crate, db: &dyn HirDatabase) -> Option { pub fn cfg(&self, db: &dyn HirDatabase) -> CfgOptions { db.crate_graph()[self.id].cfg_options.clone() } + + pub fn features(&self, db: &dyn HirDatabase) -> Vec { + db.crate_graph()[self.id].features.iter().map(|(feat, _)| feat.clone()).collect() + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 3798f32cc5c..0693869a208 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -220,6 +220,7 @@ pub fn from_single_file(text: String) -> (Analysis, FileId) { cfg_options, Env::default(), Default::default(), + Default::default(), ); change.change_file(file_id, Some(Arc::new(text))); change.set_crate_graph(crate_graph); diff --git a/crates/ide_completion/src/completions/attribute.rs b/crates/ide_completion/src/completions/attribute.rs index 78fc30e1674..cc4f4b2af72 100644 --- a/crates/ide_completion/src/completions/attribute.rs +++ b/crates/ide_completion/src/completions/attribute.rs @@ -15,6 +15,7 @@ Completions, }; +mod cfg; mod derive; mod lint; mod repr; @@ -30,6 +31,9 @@ pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) lint::complete_lint(acc, ctx, token_tree.clone(), DEFAULT_LINTS); lint::complete_lint(acc, ctx, token_tree, CLIPPY_LINTS); } + "cfg" => { + cfg::complete_cfg(acc, ctx); + } _ => (), }, (None, Some(_)) => (), @@ -852,4 +856,15 @@ fn complete_attribute_in_source_file_end() { "#]], ); } + + #[test] + fn test_cfg() { + check( + r#"#[cfg(target_endian = $0"#, + expect![[r#" + at little + at big +"#]], + ); + } } diff --git a/crates/ide_completion/src/completions/attribute/cfg.rs b/crates/ide_completion/src/completions/attribute/cfg.rs new file mode 100644 index 00000000000..71e659563cb --- /dev/null +++ b/crates/ide_completion/src/completions/attribute/cfg.rs @@ -0,0 +1,126 @@ +//! Completion for cfg + +use std::iter; + +use syntax::SyntaxKind; + +use crate::{ + completions::Completions, context::CompletionContext, item::CompletionKind, CompletionItem, + CompletionItemKind, +}; + +pub(crate) fn complete_cfg(acc: &mut Completions, ctx: &CompletionContext) { + let add_completion = |item: &&str| { + let mut completion = + CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), *item); + completion.insert_text(format!(r#""{}""#, item)); + completion.kind(CompletionItemKind::Attribute); + acc.add(completion.build()); + }; + + let previous = iter::successors(ctx.original_token.prev_token(), |t| { + (matches!(t.kind(), SyntaxKind::EQ) || t.kind().is_trivia()) + .then(|| t.prev_token()) + .flatten() + }) + .find(|t| matches!(t.kind(), SyntaxKind::IDENT)); + + match previous.as_ref().map(|p| p.text()) { + Some("feature") => { + ctx.krate.map(|krate| { + krate.features(ctx.db).iter().for_each(|f| { + let mut item = CompletionItem::new( + CompletionKind::Attribute, + ctx.source_range(), + f.clone(), + ); + item.insert_text(format!(r#""{}""#, f)); + + acc.add(item.build()) + }) + }); + } + Some("target_arch") => KNOWN_ARCH.iter().for_each(add_completion), + Some("target_env") => KNOWN_ENV.iter().for_each(add_completion), + Some("target_os") => KNOWN_OS.iter().for_each(add_completion), + Some("target_vendor") => KNOWN_VENDOR.iter().for_each(add_completion), + Some("target_endian") => ["little", "big"].iter().for_each(add_completion), + Some(name) => { + ctx.krate.map(|krate| { + krate.cfg(ctx.db).get_cfg_values(&name).iter().for_each(|s| { + let mut item = CompletionItem::new( + CompletionKind::Attribute, + ctx.source_range(), + s.as_str(), + ); + item.insert_text(format!(r#""{}""#, s)); + + acc.add(item.build()); + }) + }); + } + None => { + ctx.krate.map(|krate| { + krate.cfg(ctx.db).get_cfg_keys().iter().for_each(|s| { + let item = CompletionItem::new( + CompletionKind::Attribute, + ctx.source_range(), + s.as_str(), + ); + acc.add(item.build()); + }) + }); + } + }; +} + +const KNOWN_ARCH: [&'static str; 19] = [ + "aarch64", + "arm", + "avr", + "hexagon", + "mips", + "mips64", + "msp430", + "nvptx64", + "powerpc", + "powerpc64", + "riscv32", + "riscv64", + "s390x", + "sparc", + "sparc64", + "wasm32", + "wasm64", + "x86", + "x86_64", +]; + +const KNOWN_ENV: [&'static str; 7] = + ["eabihf", "gnu", "gnueabihf", "msvc", "relibc", "sgx", "uclibc"]; + +const KNOWN_OS: [&'static str; 20] = [ + "cuda", + "dragonfly", + "emscripten", + "freebsd", + "fuchsia", + "haiku", + "hermit", + "illumos", + "l4re", + "linux", + "netbsd", + "none", + "openbsd", + "psp", + "redox", + "solaris", + "uefi", + "unknown", + "vxworks", + "windows", +]; + +const KNOWN_VENDOR: [&'static str; 8] = + ["apple", "fortanix", "nvidia", "pc", "sony", "unknown", "wrs", "uwp"]; diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs index d8217f714eb..9ee3fc1a328 100644 --- a/crates/project_model/src/workspace.rs +++ b/crates/project_model/src/workspace.rs @@ -387,6 +387,7 @@ fn project_json_to_crate_graph( cfg_options, env, proc_macro.unwrap_or_default(), + Default::default(), ), ) }) @@ -582,6 +583,7 @@ fn detached_files_to_crate_graph( cfg_options.clone(), Env::default(), Vec::new(), + Default::default(), ); for (name, krate) in public_deps.iter() { @@ -726,6 +728,7 @@ fn add_target_crate_root( cfg_options, env, proc_macro, + pkg.features.clone(), ); crate_id @@ -755,6 +758,7 @@ fn sysroot_to_crate_graph( cfg_options.clone(), env, proc_macro, + Default::default(), ); Some((krate, crate_id)) }) -- 2.44.0