Merge pull request #21147 from Wilfred/imports_granularity_dropdown

fix: rust-analyzer.imports.granularity.group should get a dropdown UI
diff --git a/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs
index 61d8449..f8b9bb6 100644
--- a/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs
+++ b/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs
@@ -1197,4 +1197,57 @@
 "#,
         );
     }
+
+    #[test]
+    fn regression_issue_21020() {
+        check_assist(
+            convert_tuple_struct_to_named_struct,
+            r#"
+pub struct S$0(pub ());
+
+trait T {
+    fn id(&self) -> usize;
+}
+
+trait T2 {
+    fn foo(&self) -> usize;
+}
+
+impl T for S {
+    fn id(&self) -> usize {
+        self.0.len()
+    }
+}
+
+impl T2 for S {
+    fn foo(&self) -> usize {
+        self.0.len()
+    }
+}
+            "#,
+            r#"
+pub struct S { pub field1: () }
+
+trait T {
+    fn id(&self) -> usize;
+}
+
+trait T2 {
+    fn foo(&self) -> usize;
+}
+
+impl T for S {
+    fn id(&self) -> usize {
+        self.field1.len()
+    }
+}
+
+impl T2 for S {
+    fn foo(&self) -> usize {
+        self.field1.len()
+    }
+}
+            "#,
+        );
+    }
 }
diff --git a/crates/ide/src/inlay_hints/closing_brace.rs b/crates/ide/src/inlay_hints/closing_brace.rs
index ab3ce5b..49d7d45 100644
--- a/crates/ide/src/inlay_hints/closing_brace.rs
+++ b/crates/ide/src/inlay_hints/closing_brace.rs
@@ -16,6 +16,8 @@
     inlay_hints::LazyProperty,
 };
 
+const ELLIPSIS: &str = "…";
+
 pub(super) fn hints(
     acc: &mut Vec<InlayHint>,
     sema: &Semantics<'_, RootDatabase>,
@@ -60,6 +62,12 @@
 
         let module = ast::Module::cast(list.syntax().parent()?)?;
         (format!("mod {}", module.name()?), module.name().map(name))
+    } else if let Some(match_arm_list) = ast::MatchArmList::cast(node.clone()) {
+        closing_token = match_arm_list.r_curly_token()?;
+
+        let match_expr = ast::MatchExpr::cast(match_arm_list.syntax().parent()?)?;
+        let label = format_match_label(&match_expr, config)?;
+        (label, None)
     } else if let Some(label) = ast::Label::cast(node.clone()) {
         // in this case, `ast::Label` could be seen as a part of `ast::BlockExpr`
         // the actual number of lines in this case should be the line count of the parent BlockExpr,
@@ -91,7 +99,7 @@
         match_ast! {
             match parent {
                 ast::Fn(it) => {
-                    (format!("fn {}", it.name()?), it.name().map(name))
+                    (format!("{}fn {}", fn_qualifiers(&it), it.name()?), it.name().map(name))
                 },
                 ast::Static(it) => (format!("static {}", it.name()?), it.name().map(name)),
                 ast::Const(it) => {
@@ -101,6 +109,33 @@
                         (format!("const {}", it.name()?), it.name().map(name))
                     }
                 },
+                ast::LoopExpr(loop_expr) => {
+                    if loop_expr.label().is_some() {
+                        return None;
+                    }
+                    ("loop".into(), None)
+                },
+                ast::WhileExpr(while_expr) => {
+                    if while_expr.label().is_some() {
+                        return None;
+                    }
+                    (keyword_with_condition("while", while_expr.condition(), config), None)
+                },
+                ast::ForExpr(for_expr) => {
+                    if for_expr.label().is_some() {
+                        return None;
+                    }
+                    let label = format_for_label(&for_expr, config)?;
+                    (label, None)
+                },
+                ast::IfExpr(if_expr) => {
+                    let label = label_for_if_block(&if_expr, &block, config)?;
+                    (label, None)
+                },
+                ast::LetElse(let_else) => {
+                    let label = format_let_else_label(&let_else, config)?;
+                    (label, None)
+                },
                 _ => return None,
             }
         }
@@ -154,11 +189,117 @@
     None
 }
 
+fn fn_qualifiers(func: &ast::Fn) -> String {
+    let mut qualifiers = String::new();
+    if func.const_token().is_some() {
+        qualifiers.push_str("const ");
+    }
+    if func.async_token().is_some() {
+        qualifiers.push_str("async ");
+    }
+    if func.unsafe_token().is_some() {
+        qualifiers.push_str("unsafe ");
+    }
+    qualifiers
+}
+
+fn keyword_with_condition(
+    keyword: &str,
+    condition: Option<ast::Expr>,
+    config: &InlayHintsConfig<'_>,
+) -> String {
+    if let Some(expr) = condition {
+        return format!("{keyword} {}", snippet_from_node(expr.syntax(), config));
+    }
+    keyword.to_owned()
+}
+
+fn format_for_label(for_expr: &ast::ForExpr, config: &InlayHintsConfig<'_>) -> Option<String> {
+    let pat = for_expr.pat()?;
+    let iterable = for_expr.iterable()?;
+    Some(format!(
+        "for {} in {}",
+        snippet_from_node(pat.syntax(), config),
+        snippet_from_node(iterable.syntax(), config)
+    ))
+}
+
+fn format_match_label(
+    match_expr: &ast::MatchExpr,
+    config: &InlayHintsConfig<'_>,
+) -> Option<String> {
+    let expr = match_expr.expr()?;
+    Some(format!("match {}", snippet_from_node(expr.syntax(), config)))
+}
+
+fn label_for_if_block(
+    if_expr: &ast::IfExpr,
+    block: &ast::BlockExpr,
+    config: &InlayHintsConfig<'_>,
+) -> Option<String> {
+    if if_expr.then_branch().is_some_and(|then_branch| then_branch.syntax() == block.syntax()) {
+        Some(keyword_with_condition("if", if_expr.condition(), config))
+    } else if matches!(
+        if_expr.else_branch(),
+        Some(ast::ElseBranch::Block(else_block)) if else_block.syntax() == block.syntax()
+    ) {
+        Some("else".into())
+    } else {
+        None
+    }
+}
+
+fn format_let_else_label(let_else: &ast::LetElse, config: &InlayHintsConfig<'_>) -> Option<String> {
+    let stmt = let_else.syntax().parent().and_then(ast::LetStmt::cast)?;
+    let pat = stmt.pat()?;
+    let initializer = stmt.initializer()?;
+    Some(format!(
+        "let {} = {} else",
+        snippet_from_node(pat.syntax(), config),
+        snippet_from_node(initializer.syntax(), config)
+    ))
+}
+
+fn snippet_from_node(node: &SyntaxNode, config: &InlayHintsConfig<'_>) -> String {
+    let mut text = node.text().to_string();
+    if text.contains('\n') {
+        return ELLIPSIS.into();
+    }
+
+    let Some(limit) = config.max_length else {
+        return text;
+    };
+    if limit == 0 {
+        return ELLIPSIS.into();
+    }
+
+    if text.len() <= limit {
+        return text;
+    }
+
+    let boundary = text.floor_char_boundary(limit.min(text.len()));
+    if boundary == text.len() {
+        return text;
+    }
+
+    let cut = text[..boundary]
+        .char_indices()
+        .rev()
+        .find(|&(_, ch)| ch == ' ')
+        .map(|(idx, _)| idx)
+        .unwrap_or(0);
+    text.truncate(cut);
+    text.push_str(ELLIPSIS);
+    text
+}
+
 #[cfg(test)]
 mod tests {
+    use expect_test::expect;
+
     use crate::{
         InlayHintsConfig,
-        inlay_hints::tests::{DISABLED_CONFIG, check_with_config},
+        inlay_hints::tests::{DISABLED_CONFIG, check_expect, check_with_config},
     };
 
     #[test]
@@ -179,6 +320,10 @@
   }
 //^ fn h
 
+async fn async_fn() {
+  }
+//^ async fn async_fn
+
 trait Tr {
     fn f();
     fn g() {
@@ -260,4 +405,124 @@
 "#,
         );
     }
+
+    #[test]
+    fn hints_closing_brace_additional_blocks() {
+        check_expect(
+            InlayHintsConfig { closing_brace_hints_min_lines: Some(2), ..DISABLED_CONFIG },
+            r#"
+fn demo() {
+    loop {
+
+    }
+
+    while let Some(value) = next() {
+
+    }
+
+    for value in iter {
+
+    }
+
+    if cond {
+
+    }
+
+    if let Some(x) = maybe {
+
+    }
+
+    if other {
+    } else {
+
+    }
+
+    let Some(v) = maybe else {
+
+    };
+
+    match maybe {
+        Some(v) => {
+
+        }
+        value if check(value) => {
+
+        }
+        None => {}
+    }
+}
+"#,
+            expect![[r#"
+                [
+                    (
+                        364..365,
+                        [
+                            InlayHintLabelPart {
+                                text: "fn demo",
+                                linked_location: Some(
+                                    Computed(
+                                        FileRangeWrapper {
+                                            file_id: FileId(
+                                                0,
+                                            ),
+                                            range: 3..7,
+                                        },
+                                    ),
+                                ),
+                                tooltip: "",
+                            },
+                        ],
+                    ),
+                    (
+                        28..29,
+                        [
+                            "loop",
+                        ],
+                    ),
+                    (
+                        73..74,
+                        [
+                            "while let Some(value) = next()",
+                        ],
+                    ),
+                    (
+                        105..106,
+                        [
+                            "for value in iter",
+                        ],
+                    ),
+                    (
+                        127..128,
+                        [
+                            "if cond",
+                        ],
+                    ),
+                    (
+                        164..165,
+                        [
+                            "if let Some(x) = maybe",
+                        ],
+                    ),
+                    (
+                        200..201,
+                        [
+                            "else",
+                        ],
+                    ),
+                    (
+                        240..241,
+                        [
+                            "let Some(v) = maybe else",
+                        ],
+                    ),
+                    (
+                        362..363,
+                        [
+                            "match maybe",
+                        ],
+                    ),
+                ]
+            "#]],
+        );
+    }
 }
diff --git a/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs b/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs
index d22e3f1..425bd9b 100644
--- a/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs
+++ b/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs
@@ -216,16 +216,7 @@
             text: Vec::new(),
             version,
         };
-        let group = proc_macro_srv::Group {
-            delimiter: proc_macro_srv::Delimiter::None,
-            stream: Some(tokenstream),
-            span: proc_macro_srv::DelimSpan {
-                open: call_site,
-                close: call_site,
-                entire: call_site,
-            },
-        };
-        w.write_tokenstream(&group);
+        w.write_tokenstream(call_site, &tokenstream);
 
         FlatTree {
             subtree: if version >= ENCODE_CLOSE_SPAN_VERSION {
@@ -267,16 +258,7 @@
             text: Vec::new(),
             version,
         };
-        let group = proc_macro_srv::Group {
-            delimiter: proc_macro_srv::Delimiter::None,
-            stream: Some(tokenstream),
-            span: proc_macro_srv::DelimSpan {
-                open: call_site,
-                close: call_site,
-                entire: call_site,
-            },
-        };
-        w.write_tokenstream(&group);
+        w.write_tokenstream(call_site, &tokenstream);
 
         FlatTree {
             subtree: if version >= ENCODE_CLOSE_SPAN_VERSION {
@@ -491,7 +473,7 @@
 }
 
 struct Writer<'a, 'span, S: SpanTransformer, W> {
-    work: VecDeque<(usize, W)>,
+    work: VecDeque<(usize, usize, W)>,
     string_table: FxHashMap<std::borrow::Cow<'a, str>, u32>,
     span_data_table: &'span mut S::Table,
     version: u32,
@@ -508,14 +490,13 @@
     fn write_subtree(&mut self, root: tt::SubtreeView<'a, T::Span>) {
         let subtree = root.top_subtree();
         self.enqueue(subtree, root.iter());
-        while let Some((idx, subtree)) = self.work.pop_front() {
-            self.subtree(idx, subtree);
+        while let Some((idx, len, subtree)) = self.work.pop_front() {
+            self.subtree(idx, len, subtree);
         }
     }
 
-    fn subtree(&mut self, idx: usize, subtree: tt::iter::TtIter<'a, T::Span>) {
+    fn subtree(&mut self, idx: usize, n_tt: usize, subtree: tt::iter::TtIter<'a, T::Span>) {
         let mut first_tt = self.token_tree.len();
-        let n_tt = subtree.clone().count(); // FIXME: `count()` walks over the entire iterator.
         self.token_tree.resize(first_tt + n_tt, !0);
 
         self.subtree[idx].tt = [first_tt as u32, (first_tt + n_tt) as u32];
@@ -594,7 +575,8 @@
         let close = self.token_id_of(subtree.delimiter.close);
         let delimiter_kind = subtree.delimiter.kind;
         self.subtree.push(SubtreeRepr { open, close, kind: delimiter_kind, tt: [!0, !0] });
-        self.work.push_back((idx, contents));
+        // FIXME: `count()` walks over the entire iterator.
+        self.work.push_back((idx, contents.clone().count(), contents));
         idx as u32
     }
 }
@@ -624,26 +606,43 @@
 }
 
 #[cfg(feature = "sysroot-abi")]
-impl<'a, T: SpanTransformer> Writer<'a, '_, T, &'a proc_macro_srv::Group<T::Span>> {
-    fn write_tokenstream(&mut self, root: &'a proc_macro_srv::Group<T::Span>) {
-        self.enqueue_group(root);
+impl<'a, T: SpanTransformer>
+    Writer<'a, '_, T, Option<proc_macro_srv::TokenStreamIter<'a, T::Span>>>
+{
+    fn write_tokenstream(
+        &mut self,
+        call_site: T::Span,
+        root: &'a proc_macro_srv::TokenStream<T::Span>,
+    ) {
+        let call_site = self.token_id_of(call_site);
+        self.subtree.push(SubtreeRepr {
+            open: call_site,
+            close: call_site,
+            kind: tt::DelimiterKind::Invisible,
+            tt: [!0, !0],
+        });
+        self.work.push_back((0, root.len(), Some(root.iter())));
 
-        while let Some((idx, group)) = self.work.pop_front() {
-            self.group(idx, group);
+        while let Some((idx, len, group)) = self.work.pop_front() {
+            self.group(idx, len, group);
         }
     }
 
-    fn group(&mut self, idx: usize, group: &'a proc_macro_srv::Group<T::Span>) {
+    fn group(
+        &mut self,
+        idx: usize,
+        n_tt: usize,
+        group: Option<proc_macro_srv::TokenStreamIter<'a, T::Span>>,
+    ) {
         let mut first_tt = self.token_tree.len();
-        let n_tt = group.stream.as_ref().map_or(0, |it| it.len());
         self.token_tree.resize(first_tt + n_tt, !0);
 
         self.subtree[idx].tt = [first_tt as u32, (first_tt + n_tt) as u32];
 
-        for tt in group.stream.iter().flat_map(|it| it.iter()) {
+        for tt in group.into_iter().flatten() {
             let idx_tag = match tt {
                 proc_macro_srv::TokenTree::Group(group) => {
-                    let idx = self.enqueue_group(group);
+                    let idx = self.enqueue(group);
                     idx << 2
                 }
                 proc_macro_srv::TokenTree::Literal(lit) => {
@@ -706,7 +705,7 @@
         }
     }
 
-    fn enqueue_group(&mut self, group: &'a proc_macro_srv::Group<T::Span>) -> u32 {
+    fn enqueue(&mut self, group: &'a proc_macro_srv::Group<T::Span>) -> u32 {
         let idx = self.subtree.len();
         let open = self.token_id_of(group.span.open);
         let close = self.token_id_of(group.span.close);
@@ -717,7 +716,11 @@
             proc_macro_srv::Delimiter::None => tt::DelimiterKind::Invisible,
         };
         self.subtree.push(SubtreeRepr { open, close, kind: delimiter_kind, tt: [!0, !0] });
-        self.work.push_back((idx, group));
+        self.work.push_back((
+            idx,
+            group.stream.as_ref().map_or(0, |stream| stream.len()),
+            group.stream.as_ref().map(|ts| ts.iter()),
+        ));
         idx as u32
     }
 }
@@ -959,7 +962,6 @@
             };
             res[i] = Some(g);
         }
-        // FIXME: double check this
         proc_macro_srv::TokenStream::new(vec![proc_macro_srv::TokenTree::Group(
             res[0].take().unwrap(),
         )])
diff --git a/crates/proc-macro-srv/src/lib.rs b/crates/proc-macro-srv/src/lib.rs
index a96cf2b..0ac58d7 100644
--- a/crates/proc-macro-srv/src/lib.rs
+++ b/crates/proc-macro-srv/src/lib.rs
@@ -56,7 +56,7 @@
 
 pub use crate::bridge::*;
 pub use crate::server_impl::literal_from_str;
-pub use crate::token_stream::{TokenStream, literal_to_string};
+pub use crate::token_stream::{TokenStream, TokenStreamIter, literal_to_string};
 
 #[derive(Copy, Clone, Eq, PartialEq, Debug)]
 pub enum ProcMacroKind {
diff --git a/crates/syntax/src/syntax_editor.rs b/crates/syntax/src/syntax_editor.rs
index 0b35887..5683d89 100644
--- a/crates/syntax/src/syntax_editor.rs
+++ b/crates/syntax/src/syntax_editor.rs
@@ -653,4 +653,58 @@
         let expect = expect![["fn it() {\n    \n}"]];
         expect.assert_eq(&edit.new_root.to_string());
     }
+
+    #[test]
+    fn test_more_times_replace_node_to_mutable_token() {
+        let arg_list =
+            make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]);
+
+        let mut editor = SyntaxEditor::new(arg_list.syntax().clone());
+        let target_expr = make::token(parser::SyntaxKind::UNDERSCORE);
+
+        for arg in arg_list.args() {
+            editor.replace(arg.syntax(), &target_expr);
+        }
+
+        let edit = editor.finish();
+
+        let expect = expect![["(_, _)"]];
+        expect.assert_eq(&edit.new_root.to_string());
+    }
+
+    #[test]
+    fn test_more_times_replace_node_to_mutable() {
+        let arg_list =
+            make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]);
+
+        let mut editor = SyntaxEditor::new(arg_list.syntax().clone());
+        let target_expr = make::expr_literal("3").clone_for_update();
+
+        for arg in arg_list.args() {
+            editor.replace(arg.syntax(), target_expr.syntax());
+        }
+
+        let edit = editor.finish();
+
+        let expect = expect![["(3, 3)"]];
+        expect.assert_eq(&edit.new_root.to_string());
+    }
+
+    #[test]
+    fn test_more_times_insert_node_to_mutable() {
+        let arg_list =
+            make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]);
+
+        let mut editor = SyntaxEditor::new(arg_list.syntax().clone());
+        let target_expr = make::ext::expr_unit().clone_for_update();
+
+        for arg in arg_list.args() {
+            editor.insert(Position::before(arg.syntax()), target_expr.syntax());
+        }
+
+        let edit = editor.finish();
+
+        let expect = expect![["(()1, ()2)"]];
+        expect.assert_eq(&edit.new_root.to_string());
+    }
 }
diff --git a/crates/syntax/src/syntax_editor/edit_algo.rs b/crates/syntax/src/syntax_editor/edit_algo.rs
index 01c1f0d..e697d97 100644
--- a/crates/syntax/src/syntax_editor/edit_algo.rs
+++ b/crates/syntax/src/syntax_editor/edit_algo.rs
@@ -150,6 +150,35 @@
     // Map change targets to the correct syntax nodes
     let tree_mutator = TreeMutator::new(&root);
     let mut changed_elements = vec![];
+    let mut changed_elements_set = rustc_hash::FxHashSet::default();
+    let mut deduplicate_node = |node_or_token: &mut SyntaxElement| {
+        let node;
+        let node = match node_or_token {
+            SyntaxElement::Token(token) => match token.parent() {
+                None => return,
+                Some(parent) => {
+                    node = parent;
+                    &node
+                }
+            },
+            SyntaxElement::Node(node) => node,
+        };
+        if changed_elements_set.contains(node) {
+            let new_node = node.clone_subtree().clone_for_update();
+            match node_or_token {
+                SyntaxElement::Node(node) => *node = new_node,
+                SyntaxElement::Token(token) => {
+                    *token = new_node
+                        .children_with_tokens()
+                        .filter_map(SyntaxElement::into_token)
+                        .find(|it| it.kind() == token.kind() && it.text() == token.text())
+                        .unwrap();
+                }
+            }
+        } else {
+            changed_elements_set.insert(node.clone());
+        }
+    };
 
     for index in independent_changes {
         match &mut changes[index as usize] {
@@ -180,6 +209,18 @@
             }
         }
 
+        match &mut changes[index as usize] {
+            Change::Insert(_, element) | Change::Replace(_, Some(element)) => {
+                deduplicate_node(element);
+            }
+            Change::InsertAll(_, elements)
+            | Change::ReplaceWithMany(_, elements)
+            | Change::ReplaceAll(_, elements) => {
+                elements.iter_mut().for_each(&mut deduplicate_node);
+            }
+            Change::Replace(_, None) => (),
+        }
+
         // Collect changed elements
         match &changes[index as usize] {
             Change::Insert(_, element) => changed_elements.push(element.clone()),