Keep folds at cursor open for "fold at level" (#39396) 
    
      
      
      
        
        Andrew Farkas 
      
        
           and 
        
        Conrad Irwin 
      
      created 1 month ago 
    
   
  
  
  Closes #39308
Also fixes a possible bug in `apply_selected_diff_hunks()` caused by
reversed selections.
Release Notes:
- Fixed "editor: fold at level" closing regions containing selections
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com> 
  
  
  
    
   
 
  Change summary 
  crates/editor/src/editor.rs                | 22 ++++++++
crates/editor/src/editor_tests.rs          | 57 ++++++++++++++++++++++++
crates/editor/src/selections_collection.rs |  3 +
3 files changed, 80 insertions(+), 2 deletions(-)
 
 
  Detailed changes 
  
  
    
    @@ -18126,6 +18126,13 @@ impl Editor {
         let mut to_fold = Vec::new();
         let mut stack = vec![(0, snapshot.max_row().0, 1)];
 
+        let row_ranges_to_keep: Vec<Range<u32>> = self 
+            .selections 
+            .all::<Point>(cx) 
+            .into_iter() 
+            .map(|sel| sel.start.row..sel.end.row) 
+            .collect(); 
+ 
         while let Some((mut start_row, end_row, current_level)) = stack.pop() {
             while start_row < end_row {
                 match self
@@ -18139,7 +18146,13 @@ impl Editor {
                         if current_level < fold_at_level {
                             stack.push((nested_start_row, nested_end_row, current_level + 1));
                         } else if current_level == fold_at_level {
-                            to_fold.push(crease);
+                            // Fold iff there is no selection completely contained within the fold region 
+                            if !row_ranges_to_keep.iter().any(|selection| { 
+                                selection.end >= nested_start_row 
+                                    && selection.start <= nested_end_row 
+                            }) { 
+                                to_fold.push(crease); 
+                            } 
                         }
 
                         start_row = nested_end_row + 1;
@@ -18846,7 +18859,12 @@ impl Editor {
     ) {
         self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
         let snapshot = self.snapshot(window, cx);
-        let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
+        let hunks = snapshot.hunks_for_ranges( 
+            self.selections 
+                .all(cx) 
+                .into_iter() 
+                .map(|selection| selection.range()), 
+        ); 
         let mut ranges_by_buffer = HashMap::default();
         self.transact(window, cx, |editor, _window, cx| {
             for hunk in hunks {
 
   
  
  
    
    @@ -1256,6 +1256,63 @@ fn test_fold_at_level(cx: &mut TestAppContext) {
             editor.display_text(cx),
             editor.buffer.read(cx).read(cx).text()
         );
+        let (_, positions) = marked_text_ranges( 
+            &" 
+                       class Foo: 
+                           # Hello! 
+ 
+                           def a(): 
+                              print(1) 
+ 
+                           def b(): 
+                               p«riˇ»nt(2) 
+ 
+ 
+                       class Bar: 
+                           # World! 
+ 
+                           def a(): 
+                               «ˇprint(1) 
+ 
+                           def b(): 
+                               print(2)» 
+ 
+ 
+                   " 
+            .unindent(), 
+            true, 
+        ); 
+ 
+        editor.change_selections(SelectionEffects::default(), window, cx, |s| { 
+            s.select_ranges(positions) 
+        }); 
+ 
+        editor.fold_at_level(&FoldAtLevel(2), window, cx); 
+        assert_eq!( 
+            editor.display_text(cx), 
+            " 
+                class Foo: 
+                    # Hello! 
+ 
+                    def a():⋯ 
+ 
+                    def b(): 
+                        print(2) 
+ 
+ 
+                class Bar: 
+                    # World! 
+ 
+                    def a(): 
+                        print(1) 
+ 
+                    def b(): 
+                        print(2) 
+ 
+ 
+            " 
+            .unindent(), 
+        ); 
     });
 }
 
 
   
  
  
    
    @@ -332,6 +332,9 @@ impl SelectionsCollection {
         self.all(cx).last().unwrap().clone()
     }
 
+    /// Returns a list of (potentially backwards!) ranges representing the selections. 
+    /// Useful for test assertions, but prefer `.all()` instead. 
+    #[cfg(any(test, feature = "test-support"))] 
     pub fn ranges<D: TextDimension + Ord + Sub<D, Output = D>>(
         &self,
         cx: &mut App,