Skip to content

Commit

Permalink
Auto merge of rust-lang#13358 - btwotwo:feature/env-vars-autocompleti…
Browse files Browse the repository at this point in the history
…on, r=Veykril

feat: Autocomplete Cargo-defined env vars in `env!` and `option_env!` (rust-lang#12448)

Closes rust-lang#12448

Important to know:

- Variables are taken from https://doc.rust-lang.org/cargo/reference/environment-variables.html and hardcoded as a const array.
- For the sake of simplicity I didn't include the autocompletion of `CARGO_BIN_EXE_<name>` and `OUT_DIR` since it would require information about build.rs and binary name. If somebody knows an easy way of obtaining them I can add those vars as well :)
  • Loading branch information
bors committed Oct 11, 2022
2 parents d08f1c3 + 2b2b9f8 commit a0ab61f
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 5 deletions.
1 change: 1 addition & 0 deletions crates/ide-completion/src/completions.rs
Expand Up @@ -19,6 +19,7 @@ pub(crate) mod snippet;
pub(crate) mod r#type;
pub(crate) mod use_;
pub(crate) mod vis;
pub(crate) mod env_vars;

use std::iter;

Expand Down
150 changes: 150 additions & 0 deletions crates/ide-completion/src/completions/env_vars.rs
@@ -0,0 +1,150 @@
//! Completes environment variables defined by Cargo (https://doc.rust-lang.org/cargo/reference/environment-variables.html)
use hir::Semantics;
use ide_db::{syntax_helpers::node_ext::macro_call_for_string_token, RootDatabase};
use syntax::ast::{self, IsString};

use crate::{
completions::Completions, context::CompletionContext, CompletionItem, CompletionItemKind,
};

const CARGO_DEFINED_VARS: &[(&str, &str)] = &[
("CARGO","Path to the cargo binary performing the build"),
("CARGO_MANIFEST_DIR","The directory containing the manifest of your package"),
("CARGO_PKG_VERSION","The full version of your package"),
("CARGO_PKG_VERSION_MAJOR","The major version of your package"),
("CARGO_PKG_VERSION_MINOR","The minor version of your package"),
("CARGO_PKG_VERSION_PATCH","The patch version of your package"),
("CARGO_PKG_VERSION_PRE","The pre-release version of your package"),
("CARGO_PKG_AUTHORS","Colon separated list of authors from the manifest of your package"),
("CARGO_PKG_NAME","The name of your package"),
("CARGO_PKG_DESCRIPTION","The description from the manifest of your package"),
("CARGO_PKG_HOMEPAGE","The home page from the manifest of your package"),
("CARGO_PKG_REPOSITORY","The repository from the manifest of your package"),
("CARGO_PKG_LICENSE","The license from the manifest of your package"),
("CARGO_PKG_LICENSE_FILE","The license file from the manifest of your package"),
("CARGO_PKG_RUST_VERSION","The Rust version from the manifest of your package. Note that this is the minimum Rust version supported by the package, not the current Rust version"),
("CARGO_CRATE_NAME","The name of the crate that is currently being compiled"),
("CARGO_BIN_NAME","The name of the binary that is currently being compiled (if it is a binary). This name does not include any file extension, such as .exe"),
("CARGO_PRIMARY_PACKAGE","This environment variable will be set if the package being built is primary. Primary packages are the ones the user selected on the command-line, either with -p flags or the defaults based on the current directory and the default workspace members. This environment variable will not be set when building dependencies. This is only set when compiling the package (not when running binaries or tests)"),
("CARGO_TARGET_TMPDIR","Only set when building integration test or benchmark code. This is a path to a directory inside the target directory where integration tests or benchmarks are free to put any data needed by the tests/benches. Cargo initially creates this directory but doesn't manage its content in any way, this is the responsibility of the test code")
];

pub(crate) fn complete_cargo_env_vars(
acc: &mut Completions,
ctx: &CompletionContext<'_>,
expanded: &ast::String,
) -> Option<()> {
guard_env_macro(expanded, &ctx.sema)?;
let range = expanded.text_range_between_quotes()?;

CARGO_DEFINED_VARS.iter().for_each(|(var, detail)| {
let mut item = CompletionItem::new(CompletionItemKind::Keyword, range, var);
item.detail(*detail);
item.add_to(acc);
});

Some(())
}

fn guard_env_macro(string: &ast::String, semantics: &Semantics<'_, RootDatabase>) -> Option<()> {
let call = macro_call_for_string_token(string)?;
let name = call.path()?.segment()?.name_ref()?;
let makro = semantics.resolve_macro_call(&call)?;
let db = semantics.db;

match name.text().as_str() {
"env" | "option_env" if makro.kind(db) == hir::MacroKind::BuiltIn => Some(()),
_ => None,
}
}

#[cfg(test)]
mod tests {
use crate::tests::{check_edit, completion_list};

fn check(macro_name: &str) {
check_edit(
"CARGO_BIN_NAME",
&format!(
r#"
#[rustc_builtin_macro]
macro_rules! {} {{
($var:literal) => {{ 0 }}
}}
fn main() {{
let foo = {}!("CAR$0");
}}
"#,
macro_name, macro_name
),
&format!(
r#"
#[rustc_builtin_macro]
macro_rules! {} {{
($var:literal) => {{ 0 }}
}}
fn main() {{
let foo = {}!("CARGO_BIN_NAME");
}}
"#,
macro_name, macro_name
),
);
}
#[test]
fn completes_env_variable_in_env() {
check("env")
}

#[test]
fn completes_env_variable_in_option_env() {
check("option_env");
}

#[test]
fn doesnt_complete_in_random_strings() {
let fixture = r#"
fn main() {
let foo = "CA$0";
}
"#;

let completions = completion_list(fixture);
assert!(completions.is_empty(), "Completions weren't empty: {}", completions);
}

#[test]
fn doesnt_complete_in_random_macro() {
let fixture = r#"
macro_rules! bar {
($($arg:tt)*) => { 0 }
}
fn main() {
let foo = bar!("CA$0");
}
"#;

let completions = completion_list(fixture);
assert!(completions.is_empty(), "Completions weren't empty: {}", completions);
}

#[test]
fn doesnt_complete_for_shadowed_macro() {
let fixture = r#"
macro_rules! env {
($var:literal) => { 0 }
}
fn main() {
let foo = env!("CA$0");
}
"#;

let completions = completion_list(fixture);
assert!(completions.is_empty(), "Completions weren't empty: {}", completions)
}
}
1 change: 1 addition & 0 deletions crates/ide-completion/src/lib.rs
Expand Up @@ -183,6 +183,7 @@ pub fn completions(
CompletionAnalysis::String { original, expanded: Some(expanded) } => {
completions::extern_abi::complete_extern_abi(acc, ctx, expanded);
completions::format_string::format_string(acc, ctx, original, expanded);
completions::env_vars::complete_cargo_env_vars(acc, ctx, expanded);
}
CompletionAnalysis::UnexpandedAttrTT {
colon_prefix,
Expand Down
6 changes: 3 additions & 3 deletions crates/ide-db/src/syntax_helpers/format_string.rs
@@ -1,7 +1,8 @@
//! Tools to work with format string literals for the `format_args!` family of macros.
use crate::syntax_helpers::node_ext::macro_call_for_string_token;
use syntax::{
ast::{self, IsString},
AstNode, AstToken, TextRange, TextSize,
TextRange, TextSize,
};

pub fn is_format_string(string: &ast::String) -> bool {
Expand All @@ -14,8 +15,7 @@ pub fn is_format_string(string: &ast::String) -> bool {
// This setup lets us correctly highlight the components of `concat!("{}", "bla")` format
// strings. It still fails for `concat!("{", "}")`, but that is rare.
(|| {
let macro_call = string.syntax().parent_ancestors().find_map(ast::MacroCall::cast)?;
let name = macro_call.path()?.segment()?.name_ref()?;
let name = macro_call_for_string_token(string)?.path()?.segment()?.name_ref()?;

if !matches!(
name.text().as_str(),
Expand Down
9 changes: 7 additions & 2 deletions crates/ide-db/src/syntax_helpers/node_ext.rs
Expand Up @@ -2,8 +2,8 @@
use itertools::Itertools;
use parser::T;
use syntax::{
ast::{self, HasLoopBody, PathSegmentKind, VisibilityKind},
AstNode, Preorder, RustLanguage, WalkEvent,
ast::{self, HasLoopBody, MacroCall, PathSegmentKind, VisibilityKind},
AstNode, AstToken, Preorder, RustLanguage, WalkEvent,
};

pub fn expr_as_name_ref(expr: &ast::Expr) -> Option<ast::NameRef> {
Expand Down Expand Up @@ -457,3 +457,8 @@ pub fn parse_tt_as_comma_sep_paths(input: ast::TokenTree) -> Option<Vec<ast::Pat
.collect();
Some(paths)
}

pub fn macro_call_for_string_token(string: &ast::String) -> Option<MacroCall> {
let macro_call = string.syntax().parent_ancestors().find_map(ast::MacroCall::cast)?;
Some(macro_call)
}

0 comments on commit a0ab61f

Please sign in to comment.