diff --git a/crates/ide/src/ide/assists/add_to_inherit.rs b/crates/ide/src/ide/assists/add_to_inherit.rs new file mode 100644 index 00000000..f3680ea9 --- /dev/null +++ b/crates/ide/src/ide/assists/add_to_inherit.rs @@ -0,0 +1,82 @@ +use super::{AssistKind, AssistsCtx}; +use crate::def::AstPtr; +use crate::TextEdit; +use syntax::ast::{AstNode, Binding, HasBindings}; +use syntax::{ast, TextRange}; + +// Add unknown symbols to inherit clauses +pub(super) fn add_to_inherit(ctx: &mut AssistsCtx<'_>) -> Option<()> { + let file_id = ctx.frange.file_id; + let unbound = ctx.covering_node::()?; + let unbound_text = unbound.syntax().text(); + let name_res = ctx.db.name_resolution(file_id); + let source_map = ctx.db.source_map(file_id); + + let ptr = AstPtr::new(unbound.syntax()); + let expr_id = source_map.expr_for_node(ptr)?; + + // The reference is defined, do nothing + if name_res.get(expr_id).is_some() { + return None; + } + + // We walk upwards from the current node, adding suggestion for each parent let in that has an + // inherit construct. + for node in unbound.syntax().ancestors() { + if let Some(let_in) = ast::LetIn::cast(node) { + let inherits = let_in + .bindings() + .filter_map(|binding| match binding { + Binding::Inherit(x) => Some(x), + _ => None, + }) + .collect::>(); + + // Start from the last one (closest one) to have a consistent order + for inherit in inherits.iter().rev() { + if let Some(inherit_from) = inherit.from_expr() { + let add_loc = inherit + .attrs() + .last() + .map(|last| last.syntax().text_range()) + .unwrap_or(inherit_from.syntax().text_range()); + + ctx.add( + "add_to_inherit", + format!( + "Inherit \"{}\" from {}", + unbound_text, + inherit_from.syntax().text() + ), + AssistKind::RefactorRewrite, + vec![TextEdit { + delete: TextRange::new(add_loc.end(), add_loc.end()), + insert: format!(" {unbound_text}").into(), + }], + ); + } + } + } + } + + Some(()) +} + +#[cfg(test)] +mod tests { + use expect_test::expect; + define_check_assist!(super::add_to_inherit); + + #[test] + fn simple() { + // We go from closest to furthest, the first edit would be the farthest + check( + "let inherit (lib) a; inherit (lib.types) b; in $0foo", + expect!["let inherit (lib) a foo; inherit (lib.types) b; in foo"], + ); + check( + "let inherit (lib.types) a; in $0foo", + expect!["let inherit (lib.types) a foo; in foo"], + ); + } +} diff --git a/crates/ide/src/ide/assists/mod.rs b/crates/ide/src/ide/assists/mod.rs index 79427ed6..ebcdea27 100644 --- a/crates/ide/src/ide/assists/mod.rs +++ b/crates/ide/src/ide/assists/mod.rs @@ -13,6 +13,7 @@ macro_rules! define_check_assist { }; } +mod add_to_inherit; mod add_to_top_level_lambda_param; mod convert_to_inherit; mod flatten_attrset; @@ -59,6 +60,7 @@ pub(crate) fn assists(db: &dyn DefDatabase, frange: FileRange) -> Vec { rewrite_string::rewrite_uri_to_string, rewrite_string::unquote_attr, inline::inline, + add_to_inherit::add_to_inherit, ]; let mut ctx = AssistsCtx::new(db, frange); diff --git a/docs/code_actions.md b/docs/code_actions.md index 1f77517d..58b604cd 100644 --- a/docs/code_actions.md +++ b/docs/code_actions.md @@ -178,3 +178,17 @@ let id = x: x; in a 1 ```nix let id = x: x; in (x: x) 1 ``` + + +### `add_to_inherit` + +Append an unbound variable to a let-inherit-in clause in scope. +```nix +let inherit (lib) mkMerge; +in mkForce +``` +=> +```nix +let inherit (lib) mkMerge mkForce; +in mkForce +```