mirror of
https://github.com/oxalica/nil.git
synced 2025-10-05 16:22:44 +02:00
feat: inline expression code action (#173)
This commit is contained in:
147
crates/ide/src/ide/assists/inline.rs
Normal file
147
crates/ide/src/ide/assists/inline.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use super::{AssistKind, AssistsCtx};
|
||||
use crate::def::{AstPtr, ResolveResult};
|
||||
use crate::TextEdit;
|
||||
use smol_str::{SmolStr, ToSmolStr};
|
||||
use syntax::ast::AstNode;
|
||||
use syntax::{ast, SyntaxNode};
|
||||
|
||||
pub(super) fn inline(ctx: &mut AssistsCtx<'_>) -> Option<()> {
|
||||
let file_id = ctx.frange.file_id;
|
||||
let parse = ctx.db.parse(file_id);
|
||||
let name_res = ctx.db.name_resolution(file_id);
|
||||
let source_map = ctx.db.source_map(file_id);
|
||||
|
||||
let mut rewrites: Vec<TextEdit> = vec![];
|
||||
|
||||
if let Some(usage) = ctx.covering_node::<ast::Ref>() {
|
||||
let ptr = AstPtr::new(usage.syntax());
|
||||
let expr_id = source_map.expr_for_node(ptr)?;
|
||||
let &ResolveResult::Definition(name) = name_res.get(expr_id)? else {
|
||||
return None;
|
||||
};
|
||||
let definition = {
|
||||
let nodes = source_map.nodes_for_name(name).collect::<Vec<_>>();
|
||||
// Only provide assist when there is only one node
|
||||
// i.e. `let a.b = 1; a.c = 2; in a` is not supported
|
||||
if let [ptr] = nodes.as_slice() {
|
||||
ptr.to_node(&parse.syntax_node())
|
||||
.ancestors()
|
||||
.flat_map(ast::AttrpathValue::cast)
|
||||
.find_map(|path_value| path_value.value())?
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
rewrites.push(TextEdit {
|
||||
delete: usage.syntax().text_range(),
|
||||
insert: maybe_parenthesize(&definition, usage.syntax()),
|
||||
});
|
||||
} else if let Some(definition) = ctx.covering_node::<ast::Attr>() {
|
||||
let ptr = AstPtr::new(definition.syntax());
|
||||
let name_id = source_map.name_for_node(ptr)?;
|
||||
let path_value = definition
|
||||
.syntax()
|
||||
.ancestors()
|
||||
.find_map(ast::AttrpathValue::cast)?;
|
||||
|
||||
// Don't provide assist when there are more than one attrname
|
||||
if path_value.attrpath()?.attrs().count() > 1 {
|
||||
return None;
|
||||
};
|
||||
|
||||
let definition = path_value.value()?;
|
||||
|
||||
let usages = name_res
|
||||
.iter()
|
||||
.filter_map(|(id, res)| match res {
|
||||
&ResolveResult::Definition(def) if def == name_id => source_map
|
||||
.node_for_expr(id)
|
||||
.map(|ptr| ptr.to_node(ctx.ast.syntax())),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let is_letin = ast::LetIn::cast(path_value.syntax().parent()?).is_some();
|
||||
if is_letin {
|
||||
rewrites.push(TextEdit {
|
||||
delete: path_value.syntax().text_range(),
|
||||
insert: Default::default(),
|
||||
});
|
||||
};
|
||||
|
||||
for usage in usages {
|
||||
rewrites.push(TextEdit {
|
||||
delete: usage.text_range(),
|
||||
insert: maybe_parenthesize(&definition, &usage),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
ctx.add(
|
||||
"inline",
|
||||
"Inline binding",
|
||||
AssistKind::RefactorInline,
|
||||
rewrites,
|
||||
);
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
// Parenthesize a node properly given the replacement context
|
||||
fn maybe_parenthesize(replacement: &ast::Expr, original: &SyntaxNode) -> SmolStr {
|
||||
let parent = original.parent().and_then(ast::Expr::cast);
|
||||
let need_paren = matches!(parent, Some(outer) if !outer.contains_without_paren(replacement));
|
||||
if need_paren {
|
||||
format!("({})", replacement.syntax()).to_smolstr()
|
||||
} else {
|
||||
replacement.syntax().to_smolstr()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::expect;
|
||||
define_check_assist!(super::inline);
|
||||
|
||||
#[test]
|
||||
fn let_in_ref() {
|
||||
check(
|
||||
r#"let a = "foo"; in $0a"#,
|
||||
expect![r#"let a = "foo"; in "foo""#],
|
||||
);
|
||||
check(
|
||||
"let a = x: x; in $0a 1",
|
||||
expect!["let a = x: x; in (x: x) 1"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_in_def() {
|
||||
check("let $0a = x: x; in a a", expect!["let in (x: x) (x: x)"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_let_in_multi() {
|
||||
check_no(r#"let a.b = "foo"; a.c = "bar"; in $0a"#);
|
||||
check_no(r#"let a.b$0 = "foo"; a.c = "bar"; in a"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attr_ref() {
|
||||
check(
|
||||
"rec { foo = 1; bar = $0foo; }",
|
||||
expect!["rec { foo = 1; bar = 1; }"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attr_def() {
|
||||
check(
|
||||
"rec { $0foo = 1; bar = foo; baz = foo; }",
|
||||
expect!["rec { foo = 1; bar = 1; baz = 1; }"],
|
||||
);
|
||||
}
|
||||
}
|
@@ -16,6 +16,7 @@ macro_rules! define_check_assist {
|
||||
mod add_to_top_level_lambda_param;
|
||||
mod convert_to_inherit;
|
||||
mod flatten_attrset;
|
||||
mod inline;
|
||||
mod pack_bindings;
|
||||
mod remove_empty_inherit;
|
||||
mod remove_empty_let_in;
|
||||
@@ -40,6 +41,7 @@ pub struct Assist {
|
||||
pub enum AssistKind {
|
||||
QuickFix,
|
||||
RefactorRewrite,
|
||||
RefactorInline,
|
||||
}
|
||||
|
||||
pub(crate) fn assists(db: &dyn DefDatabase, frange: FileRange) -> Vec<Assist> {
|
||||
@@ -56,6 +58,7 @@ pub(crate) fn assists(db: &dyn DefDatabase, frange: FileRange) -> Vec<Assist> {
|
||||
rewrite_string::rewrite_string_to_indented,
|
||||
rewrite_string::rewrite_uri_to_string,
|
||||
rewrite_string::unquote_attr,
|
||||
inline::inline,
|
||||
];
|
||||
|
||||
let mut ctx = AssistsCtx::new(db, frange);
|
||||
|
@@ -302,6 +302,7 @@ pub(crate) fn to_code_action(vfs: &Vfs, assist: Assist) -> CodeActionOrCommand {
|
||||
kind: Some(match assist.kind {
|
||||
AssistKind::QuickFix => CodeActionKind::QUICKFIX,
|
||||
AssistKind::RefactorRewrite => CodeActionKind::REFACTOR_REWRITE,
|
||||
AssistKind::RefactorInline => CodeActionKind::REFACTOR_INLINE,
|
||||
}),
|
||||
diagnostics: None,
|
||||
edit: Some(to_workspace_edit(vfs, assist.edits)),
|
||||
|
@@ -167,3 +167,14 @@ https://nixos.org
|
||||
```nix
|
||||
"https://nixos.org"
|
||||
```
|
||||
|
||||
### `inline`
|
||||
|
||||
Rewrite a binding to its definition.
|
||||
```nix
|
||||
let id = x: x; in a 1
|
||||
```
|
||||
=>
|
||||
```nix
|
||||
let id = x: x; in (x: x) 1
|
||||
```
|
||||
|
Reference in New Issue
Block a user