From a10145ff65c8bc0d2b4a8df652217fe2447ef796 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 12 Oct 2025 14:31:07 +0800 Subject: [PATCH] Add ide-assist: move_for_binder Move for-binder to where predicate. Example --- ```rust fn foo() where F: $0for<'a> Fn(&'a str) {} ``` -> ```rust fn foo() where for<'a> F: Fn(&'a str) {} ``` --- .../src/handlers/move_for_binder.rs | 82 +++++++++++++++++++ crates/ide-assists/src/lib.rs | 2 + crates/ide-assists/src/tests/generated.rs | 19 +++++ 3 files changed, 103 insertions(+) create mode 100644 crates/ide-assists/src/handlers/move_for_binder.rs diff --git a/crates/ide-assists/src/handlers/move_for_binder.rs b/crates/ide-assists/src/handlers/move_for_binder.rs new file mode 100644 index 000000000000..32f407328992 --- /dev/null +++ b/crates/ide-assists/src/handlers/move_for_binder.rs @@ -0,0 +1,82 @@ +use ide_db::assists::AssistId; +use syntax::{ + SyntaxKind, + ast::{self, AstNode, make::tokens}, + syntax_editor::Position, +}; + +use crate::assist_context::{AssistContext, Assists}; + +// Assist: move_for_binder +// +// Move for-binder to where predicate. +// +// ``` +// fn foo() +// where +// F: $0for<'a> Fn(&'a str) +// {} +// ``` +// -> +// ``` +// fn foo() +// where +// for<'a> F: Fn(&'a str) +// {} +// ``` +pub(crate) fn move_for_binder(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let for_binder = ctx.find_node_at_offset::()?; + let where_pred = ctx.find_node_at_offset::()?; + + if where_pred.for_binder().is_some() || !ctx.has_empty_selection() { + return None; + } + + let label = "Move for-binder to where predicate"; + let target = for_binder.syntax().text_range(); + acc.add(AssistId::refactor_rewrite("move_for_binder"), label, target, |builder| { + let mut edit = builder.make_editor(for_binder.syntax()); + + if let Some(next) = for_binder.syntax().next_sibling_or_token() + && next.kind() == SyntaxKind::WHITESPACE + { + edit.delete(next); + } + + edit.delete(for_binder.syntax()); + edit.insert(Position::before(where_pred.syntax()), tokens::single_space()); + edit.insert(Position::before(where_pred.syntax()), for_binder.syntax().clone_for_update()); + + builder.add_file_edits(ctx.vfs_file_id(), edit); + }) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_assist_not_applicable; + + use super::*; + + #[test] + fn not_applicable_on_exists() { + check_assist_not_applicable( + move_for_binder, + r#" + fn foo() + where + for<'b> F: $0for<'a> Fn(&'a str) + {} + "#, + ); + + check_assist_not_applicable( + move_for_binder, + r#" + fn foo() + where + $0for<'a> F: Fn(&'a str) + {} + "#, + ); + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index 2977f8b8c2e7..e56e698cd4fe 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -189,6 +189,7 @@ mod handlers { mod merge_nested_if; mod move_bounds; mod move_const_to_impl; + mod move_for_binder; mod move_from_mod_rs; mod move_guard; mod move_module_to_file; @@ -326,6 +327,7 @@ mod handlers { merge_nested_if::merge_nested_if, move_bounds::move_bounds_to_where_clause, move_const_to_impl::move_const_to_impl, + move_for_binder::move_for_binder, move_from_mod_rs::move_from_mod_rs, move_guard::move_arm_cond_to_match_guard, move_guard::move_guard_to_arm_body, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index c7ae44124f23..3d1d12fb29fa 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -2640,6 +2640,25 @@ impl S { ) } +#[test] +fn doctest_move_for_binder() { + check_doc_test( + "move_for_binder", + r#####" +fn foo() +where + F: $0for<'a> Fn(&'a str) +{} +"#####, + r#####" +fn foo() +where + for<'a> F: Fn(&'a str) +{} +"#####, + ) +} + #[test] fn doctest_move_from_mod_rs() { check_doc_test(