about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--crates/ide-assists/Cargo.toml1
-rw-r--r--crates/ide-assists/src/handlers/extract_module.rs82
3 files changed, 64 insertions, 20 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 275b77ce4ae..4c255b12b7f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -655,6 +655,7 @@ dependencies = [
  "ide-db",
  "itertools",
  "profile",
+ "smallvec",
  "sourcegen",
  "stdx",
  "syntax",
diff --git a/crates/ide-assists/Cargo.toml b/crates/ide-assists/Cargo.toml
index e781c0a016d..b9260473b12 100644
--- a/crates/ide-assists/Cargo.toml
+++ b/crates/ide-assists/Cargo.toml
@@ -14,6 +14,7 @@ cov-mark = "2.0.0-pre.1"
 
 itertools = "0.10.5"
 either = "1.7.0"
+smallvec = "1.10.0"
 
 stdx = { path = "../stdx", version = "0.0.0" }
 syntax = { path = "../syntax", version = "0.0.0" }
diff --git a/crates/ide-assists/src/handlers/extract_module.rs b/crates/ide-assists/src/handlers/extract_module.rs
index 56834394aeb..81df1908251 100644
--- a/crates/ide-assists/src/handlers/extract_module.rs
+++ b/crates/ide-assists/src/handlers/extract_module.rs
@@ -10,6 +10,8 @@ use ide_db::{
     defs::{Definition, NameClass, NameRefClass},
     search::{FileReference, SearchScope},
 };
+use itertools::Itertools;
+use smallvec::SmallVec;
 use stdx::format_to;
 use syntax::{
     algo::find_node_at_range,
@@ -657,28 +659,23 @@ impl Module {
 
 fn check_intersection_and_push(
     import_paths_to_be_removed: &mut Vec<TextRange>,
-    import_path: TextRange,
+    mut import_path: TextRange,
 ) {
-    if import_paths_to_be_removed.len() > 0 {
-        // Text ranges received here for imports are extended to the
-        // next/previous comma which can cause intersections among them
-        // and later deletion of these can cause panics similar
-        // to reported in #11766. So to mitigate it, we
-        // check for intersection between all current members
-        // and if it exists we combine both text ranges into
-        // one
-        let r = import_paths_to_be_removed
-            .into_iter()
-            .position(|it| it.intersect(import_path).is_some());
-        match r {
-            Some(it) => {
-                import_paths_to_be_removed[it] = import_paths_to_be_removed[it].cover(import_path)
-            }
-            None => import_paths_to_be_removed.push(import_path),
-        }
-    } else {
-        import_paths_to_be_removed.push(import_path);
+    // Text ranges received here for imports are extended to the
+    // next/previous comma which can cause intersections among them
+    // and later deletion of these can cause panics similar
+    // to reported in #11766. So to mitigate it, we
+    // check for intersection between all current members
+    // and combine all such ranges into one.
+    let s: SmallVec<[_; 2]> = import_paths_to_be_removed
+        .into_iter()
+        .positions(|it| it.intersect(import_path).is_some())
+        .collect();
+    for pos in s.into_iter().rev() {
+        let intersecting_path = import_paths_to_be_removed.swap_remove(pos);
+        import_path = import_path.cover(intersecting_path);
     }
+    import_paths_to_be_removed.push(import_path);
 }
 
 fn does_source_exists_outside_sel_in_same_mod(
@@ -1766,4 +1763,49 @@ mod modname {
         ",
         )
     }
+
+    #[test]
+    fn test_merge_multiple_intersections() {
+        check_assist(
+            extract_module,
+            r#"
+mod dep {
+    pub struct A;
+    pub struct B;
+    pub struct C;
+}
+
+use dep::{A, B, C};
+
+$0struct S {
+    inner: A,
+    state: C,
+    condvar: B,
+}$0
+"#,
+            r#"
+mod dep {
+    pub struct A;
+    pub struct B;
+    pub struct C;
+}
+
+use dep::{};
+
+mod modname {
+    use super::dep::B;
+
+    use super::dep::C;
+
+    use super::dep::A;
+
+    pub(crate) struct S {
+        pub(crate) inner: A,
+        pub(crate) state: C,
+        pub(crate) condvar: B,
+    }
+}
+"#,
+        );
+    }
 }