From 581d457e13d321c1f462f5e9fc830e9709b17348 Mon Sep 17 00:00:00 2001
From: DropDemBits <r3usrlnd@gmail.com>
Date: Thu, 15 Feb 2024 20:24:31 -0500
Subject: [PATCH] Add `add_placeholder_snippet_group`

Used for allowing newly generated syntax constructs to be renamed without having to go through a separate rename step.
---
 crates/ide-db/src/source_change.rs | 48 +++++++++++++++++++++++-------
 1 file changed, 37 insertions(+), 11 deletions(-)

diff --git a/crates/ide-db/src/source_change.rs b/crates/ide-db/src/source_change.rs
index 73be6a4071e..f09401f70af 100644
--- a/crates/ide-db/src/source_change.rs
+++ b/crates/ide-db/src/source_change.rs
@@ -138,7 +138,7 @@ impl SnippetEdit {
             .into_iter()
             .zip(1..)
             .with_position()
-            .map(|pos| {
+            .flat_map(|pos| {
                 let (snippet, index) = match pos {
                     (itertools::Position::First, it) | (itertools::Position::Middle, it) => it,
                     // last/only snippet gets index 0
@@ -146,11 +146,13 @@ impl SnippetEdit {
                     | (itertools::Position::Only, (snippet, _)) => (snippet, 0),
                 };
 
-                let range = match snippet {
-                    Snippet::Tabstop(pos) => TextRange::empty(pos),
-                    Snippet::Placeholder(range) => range,
-                };
-                (index, range)
+                match snippet {
+                    Snippet::Tabstop(pos) => vec![(index, TextRange::empty(pos))],
+                    Snippet::Placeholder(range) => vec![(index, range)],
+                    Snippet::PlaceholderGroup(ranges) => {
+                        ranges.into_iter().map(|range| (index, range)).collect()
+                    }
+                }
             })
             .collect_vec();
 
@@ -248,7 +250,7 @@ impl SourceChangeBuilder {
     fn commit(&mut self) {
         let snippet_edit = self.snippet_builder.take().map(|builder| {
             SnippetEdit::new(
-                builder.places.into_iter().map(PlaceSnippet::finalize_position).collect_vec(),
+                builder.places.into_iter().flat_map(PlaceSnippet::finalize_position).collect(),
             )
         });
 
@@ -356,6 +358,17 @@ impl SourceChangeBuilder {
         self.add_snippet(PlaceSnippet::Over(node.syntax().clone().into()))
     }
 
+    /// Adds a snippet to move the cursor selected over `nodes`
+    ///
+    /// This allows for renaming newly generated items without having to go
+    /// through a separate rename step.
+    pub fn add_placeholder_snippet_group(&mut self, _cap: SnippetCap, nodes: Vec<SyntaxNode>) {
+        assert!(nodes.iter().all(|node| node.parent().is_some()));
+        self.add_snippet(PlaceSnippet::OverGroup(
+            nodes.into_iter().map(|node| node.into()).collect(),
+        ))
+    }
+
     fn add_snippet(&mut self, snippet: PlaceSnippet) {
         let snippet_builder = self.snippet_builder.get_or_insert(SnippetBuilder { places: vec![] });
         snippet_builder.places.push(snippet);
@@ -400,6 +413,13 @@ pub enum Snippet {
     Tabstop(TextSize),
     /// A placeholder snippet (e.g. `${0:placeholder}`).
     Placeholder(TextRange),
+    /// A group of placeholder snippets, e.g.
+    ///
+    /// ```no_run
+    /// let ${0:new_var} = 4;
+    /// fun(1, 2, 3, ${0:new_var});
+    /// ```
+    PlaceholderGroup(Vec<TextRange>),
 }
 
 enum PlaceSnippet {
@@ -409,14 +429,20 @@ enum PlaceSnippet {
     After(SyntaxElement),
     /// Place a placeholder snippet in place of the element
     Over(SyntaxElement),
+    /// Place a group of placeholder snippets which are linked together
+    /// in place of the elements
+    OverGroup(Vec<SyntaxElement>),
 }
 
 impl PlaceSnippet {
-    fn finalize_position(self) -> Snippet {
+    fn finalize_position(self) -> Vec<Snippet> {
         match self {
-            PlaceSnippet::Before(it) => Snippet::Tabstop(it.text_range().start()),
-            PlaceSnippet::After(it) => Snippet::Tabstop(it.text_range().end()),
-            PlaceSnippet::Over(it) => Snippet::Placeholder(it.text_range()),
+            PlaceSnippet::Before(it) => vec![Snippet::Tabstop(it.text_range().start())],
+            PlaceSnippet::After(it) => vec![Snippet::Tabstop(it.text_range().end())],
+            PlaceSnippet::Over(it) => vec![Snippet::Placeholder(it.text_range())],
+            PlaceSnippet::OverGroup(it) => {
+                vec![Snippet::PlaceholderGroup(it.into_iter().map(|it| it.text_range()).collect())]
+            }
         }
     }
 }