]> granicus.if.org Git - vim/commitdiff
patch 8.0.0457: using :move messes up manual folds v8.0.0457
authorBram Moolenaar <Bram@vim.org>
Tue, 14 Mar 2017 20:53:58 +0000 (21:53 +0100)
committerBram Moolenaar <Bram@vim.org>
Tue, 14 Mar 2017 20:53:58 +0000 (21:53 +0100)
Problem:    Using :move messes up manual folds.
Solution:   Split adjusting marks and folds.  Add foldMoveRange(). (neovim
            patch #6221)

src/ex_cmds.c
src/fold.c
src/mark.c
src/proto/fold.pro
src/proto/mark.pro
src/testdir/test_fold.vim
src/version.c

index 64d7f6b847be4b6e108513b8decd4e941473fa74..8977302740fc3fdee0f9194b2b8d9e3c590e7124 100644 (file)
@@ -800,6 +800,8 @@ do_move(linenr_T line1, linenr_T line2, linenr_T dest)
     linenr_T   last_line;  /* Last line in file after adding new text */
 #ifdef FEAT_FOLDING
     int                isFolded;
+    win_T      *win;
+    tabpage_T  *tp;
 
     /* Moving lines seems to corrupt the folds, delete folding info now
      * and recreate it when finished.  Don't do this for manual folding, it
@@ -851,24 +853,34 @@ do_move(linenr_T line1, linenr_T line2, linenr_T dest)
      * their final destination at the new text position -- webb
      */
     last_line = curbuf->b_ml.ml_line_count;
-    mark_adjust(line1, line2, last_line - line2, 0L);
-    changed_lines(last_line - num_lines + 1, 0, last_line + 1, num_lines);
+    mark_adjust_nofold(line1, line2, last_line - line2, 0L);
     if (dest >= line2)
     {
-       mark_adjust(line2 + 1, dest, -num_lines, 0L);
+       mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L);
+#ifdef FEAT_FOLDING
+       FOR_ALL_TAB_WINDOWS(tp, win) {
+           if (win->w_buffer == curbuf)
+               foldMoveRange(&win->w_folds, line1, line2, dest);
+       }
+#endif
        curbuf->b_op_start.lnum = dest - num_lines + 1;
        curbuf->b_op_end.lnum = dest;
     }
     else
     {
-       mark_adjust(dest + 1, line1 - 1, num_lines, 0L);
+       mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L);
+#ifdef FEAT_FOLDING
+       FOR_ALL_TAB_WINDOWS(tp, win) {
+           if (win->w_buffer == curbuf)
+               foldMoveRange(&win->w_folds, dest + 1, line1 - 1, line2);
+       }
+#endif
        curbuf->b_op_start.lnum = dest + 1;
        curbuf->b_op_end.lnum = dest + num_lines;
     }
     curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
-    mark_adjust(last_line - num_lines + 1, last_line,
+    mark_adjust_nofold(last_line - num_lines + 1, last_line,
                                             -(last_line - dest - extra), 0L);
-    changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra);
 
     /*
      * Now we delete the original text -- webb
@@ -907,9 +919,9 @@ do_move(linenr_T line1, linenr_T line2, linenr_T dest)
        changed_lines(dest + 1, 0, line1 + num_lines, 0L);
 
 #ifdef FEAT_FOLDING
-       /* recreate folds */
-       if (isFolded)
-           foldUpdateAll(curwin);
+    /* recreate folds */
+    if (isFolded)
+       foldUpdateAll(curwin);
 #endif
 
     return OK;
index 826ad5c8dcf3542c3826817e4fbccfc40cad571b..6af71783b979407a58c6bee761007727ae33a1ba 100644 (file)
@@ -64,6 +64,7 @@ static void deleteFoldMarkers(fold_T *fp, int recursive, linenr_T lnum_off);
 static void foldDelMarker(linenr_T lnum, char_u *marker, int markerlen);
 static void foldUpdateIEMS(win_T *wp, linenr_T top, linenr_T bot);
 static void parseMarker(win_T *wp);
+static void foldMoveRange_int(garray_T *gap, linenr_T line1, linenr_T line2, linenr_T dest);
 
 static char *e_nofold = N_("E490: No fold found");
 
@@ -1075,6 +1076,12 @@ foldAdjustCursor(void)
     (void)hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL);
 }
 
+/* foldMoveRange() {{{2 */
+    void
+foldMoveRange(garray_T *gap, linenr_T line1, linenr_T line2, linenr_T dest)
+{
+    foldMoveRange_int(gap, line1, line2, dest);
+}
 /* Internal functions for "fold_T" {{{1 */
 /* cloneFoldGrowArray() {{{2 */
 /*
@@ -2968,6 +2975,182 @@ foldRemove(garray_T *gap, linenr_T top, linenr_T bot)
     }
 }
 
+/* foldReverseOrder() {{{2 */
+    static void
+foldReverseOrder(garray_T *gap, linenr_T start, linenr_T end)
+{
+    fold_T *left, *right;
+    fold_T tmp;
+
+    for (; start < end; start++, end--)
+    {
+       left = (fold_T *)gap->ga_data + start;
+       right = (fold_T *)gap->ga_data + end;
+       tmp  = *left;
+       *left = *right;
+       *right = tmp;
+    }
+}
+
+/* foldMoveRange_int() {{{2 */
+/*
+ * Move folds within the inclusive range "line1" to "line2" to after "dest"
+ * requires "line1" <= "line2" <= "dest"
+ *
+ * There are the following situations for the first fold at or below line1 - 1.
+ *       1  2  3  4
+ *       1  2  3  4
+ * line1    2  3  4
+ *          2  3  4  5  6  7
+ * line2       3  4  5  6  7
+ *             3  4     6  7  8  9
+ * dest           4        7  8  9
+ *                4        7  8    10
+ *                4        7  8    10
+ *
+ * In the following descriptions, "moved" means moving in the buffer, *and* in
+ * the fold array.
+ * Meanwhile, "shifted" just means moving in the buffer.
+ * 1. not changed
+ * 2. truncated above line1
+ * 3. length reduced by  line2 - line1, folds starting between the end of 3 and
+ *    dest are truncated and shifted up
+ * 4. internal folds moved (from [line1, line2] to dest)
+ * 5. moved to dest.
+ * 6. truncated below line2 and moved.
+ * 7. length reduced by line2 - dest, folds starting between line2 and dest are
+ *    removed, top is moved down by move_len.
+ * 8. truncated below dest and shifted up.
+ * 9. shifted up
+ * 10. not changed
+ */
+
+    static void
+truncate_fold(fold_T *fp, linenr_T end)
+{
+    foldRemove(&fp->fd_nested, end - fp->fd_top, MAXLNUM);
+    fp->fd_len = end - fp->fd_top + 1;
+}
+
+#define fold_end(fp) ((fp)->fd_top + (fp)->fd_len - 1)
+#define valid_fold(fp, gap) ((fp) < ((fold_T *)(gap)->ga_data + (gap)->ga_len))
+#define fold_index(fp, gap) ((size_t)(fp - ((fold_T *)(gap)->ga_data)))
+
+    static void
+foldMoveRange_int(garray_T *gap, linenr_T line1, linenr_T line2, linenr_T dest)
+{
+    fold_T *fp;
+    linenr_T range_len = line2 - line1 + 1;
+    linenr_T move_len = dest - line2;
+    int at_start = foldFind(gap, line1 - 1, &fp);
+    size_t move_start = 0, move_end = 0, dest_index = 0;
+
+    if (at_start)
+    {
+       if (fold_end(fp) > dest)
+       {
+           /* Case 4
+           * don't have to change this fold, but have to move nested folds.
+           */
+           foldMoveRange(&fp->fd_nested, line1 - fp->fd_top, line2 -
+                   fp->fd_top, dest - fp->fd_top);
+           return;
+       }
+       else if (fold_end(fp) > line2)
+       {
+           /* Case 3
+            * Remove nested folds between line1 and line2 & reduce the
+            * length of fold by "range_len".
+            * Folds after this one must be dealt with.
+            */
+           foldMarkAdjustRecurse(&fp->fd_nested, line1 - fp->fd_top, line2 -
+                   fp->fd_top, MAXLNUM, -range_len);
+           fp->fd_len -= range_len;
+       }
+       else
+           /* Case 2 truncate fold, folds after this one must be dealt with. */
+           truncate_fold(fp, line1);
+
+       /* Look at the next fold, and treat that one as if it were the first
+        * after  "line1" (because now it is). */
+       fp = fp + 1;
+    }
+
+    if (!valid_fold(fp, gap) || fp->fd_top > dest)
+    {
+       /* Case 10
+        * No folds after "line1" and before "dest"
+        */
+       return;
+    }
+    else if (fp->fd_top > line2)
+    {
+       for (; valid_fold(fp, gap) && fold_end(fp) < dest; fp++)
+       /* Case 9. (for all case 9's) -- shift up. */
+           fp->fd_top -= range_len;
+
+       if (valid_fold(fp, gap) && fp->fd_top < dest)
+       {
+           /* Case 8. -- ensure truncated at dest, shift up */
+           truncate_fold(fp, dest);
+           fp->fd_top -= range_len;
+       }
+       return;
+    }
+    else if (fold_end(fp) > dest)
+    {
+       /* Case 7 -- remove nested folds and shrink */
+       foldMarkAdjustRecurse(&fp->fd_nested, line2 + 1 - fp->fd_top, dest -
+               fp->fd_top, MAXLNUM, -move_len);
+       fp->fd_len -= move_len;
+       fp->fd_top += move_len;
+       return;
+    }
+
+    /* Case 5 or 6
+     * changes rely on whether there are folds between the end of 
+     * this fold and "dest".
+     */
+    move_start = fold_index(fp, gap);
+
+    for (; valid_fold(fp, gap) && fp->fd_top <= dest; fp++)
+    {
+       if (fp->fd_top <= line2)
+       {
+           /* 1. 2. or 3. */
+           if (fold_end(fp) > line2)
+               /* 2. or 3., truncate before moving */
+               truncate_fold(fp, line2);
+
+           fp->fd_top += move_len;
+           continue;
+       }
+
+       /* Record index of the first fold after the moved range. */
+       if (move_end == 0)
+           move_end = fold_index(fp, gap);
+
+       if (fold_end(fp) > dest)
+           truncate_fold(fp, dest);
+
+       fp->fd_top -= range_len;
+    }
+
+    dest_index = fold_index(fp, gap);
+
+    /*
+     * All folds are now correct, but they are not necessarily in the correct
+     * order. We have to swap folds in the range [move_end, dest_index) with
+     * those in the range [move_start, move_end).
+     */
+    foldReverseOrder(gap, move_start, dest_index - 1);
+    foldReverseOrder(gap, move_start, move_start + dest_index - move_end - 1);
+    foldReverseOrder(gap, move_start + dest_index - move_end, dest_index - 1);
+}
+#undef fold_end
+#undef valid_fold
+#undef fold_index
+
 /* foldMerge() {{{2 */
 /*
  * Merge two adjacent folds (and the nested ones in them).
index 194125eb0011522e067d6d94a5c3bffb52536862..0d21305bcf6785594a5756d223a3a9b9820ce823 100644 (file)
@@ -37,6 +37,8 @@ static void cleanup_jumplist(void);
 #ifdef FEAT_VIMINFO
 static void write_one_filemark(FILE *fp, xfmark_T *fm, int c1, int c2);
 #endif
+static void mark_adjust_internal(linenr_T line1, linenr_T line2, long amount,
+    long amount_after, int adjust_folds);
 
 /*
  * Set named mark "c" at current cursor position.
@@ -1028,6 +1030,27 @@ mark_adjust(
     linenr_T   line2,
     long       amount,
     long       amount_after)
+{
+    mark_adjust_internal(line1, line2, amount, amount_after, TRUE);
+}
+
+    void
+mark_adjust_nofold(
+    linenr_T line1,
+    linenr_T line2,
+    long amount,
+    long amount_after)
+{
+    mark_adjust_internal(line1, line2, amount, amount_after, FALSE);
+}
+
+    static void
+mark_adjust_internal(
+    linenr_T line1,
+    linenr_T line2,
+    long amount,
+    long amount_after,
+    int adjust_folds UNUSED)
 {
     int                i;
     int                fnum = curbuf->b_fnum;
@@ -1174,7 +1197,8 @@ mark_adjust(
 
 #ifdef FEAT_FOLDING
            /* adjust folds */
-           foldMarkAdjust(win, line1, line2, amount, amount_after);
+           if (adjust_folds)
+               foldMarkAdjust(win, line1, line2, amount, amount_after);
 #endif
        }
     }
index dce18038108acbf77178e9cb2d285deeb37baf22..cda536b27ecb040df9974f15e3a7e90cedd6fb35 100644 (file)
@@ -31,6 +31,7 @@ void foldInitWin(win_T *new_win);
 int find_wl_entry(win_T *win, linenr_T lnum);
 void foldAdjustVisual(void);
 void foldAdjustCursor(void);
+void foldMoveRange(garray_T *gap, linenr_T line1, linenr_T line2, linenr_T dest);
 void cloneFoldGrowArray(garray_T *from, garray_T *to);
 void deleteFoldRecurse(garray_T *gap);
 void foldMarkAdjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, long amount_after);
index 90be1a566ee5d99523f4028bfaa1381182cfae95..ff1f441e819408b5670abc68b6f06a965ff5c26e 100644 (file)
@@ -19,6 +19,7 @@ void ex_jumps(exarg_T *eap);
 void ex_clearjumps(exarg_T *eap);
 void ex_changes(exarg_T *eap);
 void mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_after);
+void mark_adjust_nofold(linenr_T line1, linenr_T line2, long amount, long amount_after);
 void mark_col_adjust(linenr_T lnum, colnr_T mincol, long lnum_amount, long col_amount);
 void copy_jumplist(win_T *from, win_T *to);
 void free_jumplist(win_T *wp);
index f10480bb4fd903708b00bd0d5aeb2f442583d173..19c94140d44bab944b811919cb6587c4292314b2 100644 (file)
@@ -1,5 +1,9 @@
 " Test for folding
 
+func! PrepIndent(arg)
+  return [a:arg] + repeat(["\t".a:arg], 5)
+endfu
+
 func! Test_address_fold()
   new
   call setline(1, ['int FuncName() {/*{{{*/', 1, 2, 3, 4, 5, '}/*}}}*/',
@@ -219,3 +223,121 @@ func Test_update_folds_expr_read()
   bwipe!
   set foldmethod& foldexpr&
 endfunc
+
+func! Test_move_folds_around_manual()
+  new
+  let input = PrepIndent("a") + PrepIndent("b") + PrepIndent("c")
+  call setline(1, PrepIndent("a") + PrepIndent("b") + PrepIndent("c"))
+  let folds=[-1, 2, 2, 2, 2, 2, -1, 8, 8, 8, 8, 8, -1, 14, 14, 14, 14, 14]
+  " all folds closed
+  set foldenable foldlevel=0 fdm=indent
+  " needs a forced redraw
+  redraw!
+  set fdm=manual
+  call assert_equal(folds, map(range(1, line('$')), 'foldclosed(v:val)'))
+  call assert_equal(input, getline(1, '$'))
+  7,12m0
+  call assert_equal(PrepIndent("b") + PrepIndent("a") + PrepIndent("c"), getline(1, '$'))
+  call assert_equal(folds, map(range(1, line('$')), 'foldclosed(v:val)'))
+  10,12m0
+  call assert_equal(PrepIndent("a")[1:] + PrepIndent("b") + ["a"] +  PrepIndent("c"), getline(1, '$'))
+  call assert_equal([1, 1, 1, 1, 1, -1, 7, 7, 7, 7, 7, -1, -1, 14, 14, 14, 14, 14], map(range(1, line('$')), 'foldclosed(v:val)'))
+  " moving should not close the folds
+  %d
+  call setline(1, PrepIndent("a") + PrepIndent("b") + PrepIndent("c"))
+  set fdm=indent
+  redraw!
+  set fdm=manual
+  call cursor(2, 1)
+  norm! zR
+  7,12m0
+  let folds=repeat([-1], 18)
+  call assert_equal(PrepIndent("b") + PrepIndent("a") + PrepIndent("c"), getline(1, '$'))
+  call assert_equal(folds, map(range(1, line('$')), 'foldclosed(v:val)'))
+  norm! zM
+  " folds are not corrupted and all have been closed
+  call assert_equal([-1, 2, 2, 2, 2, 2, -1, 8, 8, 8, 8, 8, -1, 14, 14, 14, 14, 14], map(range(1, line('$')), 'foldclosed(v:val)'))
+  %d
+  call setline(1, ["a", "\tb", "\tc", "\td", "\te"])
+  set fdm=indent
+  redraw!
+  set fdm=manual
+  %foldopen
+  3m4
+  %foldclose
+  call assert_equal(["a", "\tb", "\td", "\tc", "\te"], getline(1, '$'))
+  call assert_equal([-1, 5, 5, 5, 5], map(range(1, line('$')), 'foldclosedend(v:val)'))
+  %d
+  call setline(1, ["a", "\tb", "\tc", "\td", "\te", "z", "\ty", "\tx", "\tw", "\tv"])
+  set fdm=indent foldlevel=0
+  set fdm=manual
+  %foldopen
+  3m1
+  %foldclose
+  call assert_equal(["a", "\tc", "\tb", "\td", "\te", "z", "\ty", "\tx", "\tw", "\tv"], getline(1, '$'))
+  call assert_equal(0, foldlevel(2))
+  call assert_equal(5, foldclosedend(3))
+  call assert_equal([-1, -1, 3, 3, 3, -1, 7, 7, 7, 7], map(range(1, line('$')), 'foldclosed(v:val)'))
+  2,6m$
+  %foldclose
+  call assert_equal(5, foldclosedend(2))
+  call assert_equal(0, foldlevel(6))
+  call assert_equal(9, foldclosedend(7))
+  call assert_equal([-1, 2, 2, 2, 2, -1, 7, 7, 7, -1], map(range(1, line('$')), 'foldclosed(v:val)'))
+  bw!
+endfunc
+
+func! Test_move_folds_around_indent()
+  new
+  let input = PrepIndent("a") + PrepIndent("b") + PrepIndent("c")
+  call setline(1, PrepIndent("a") + PrepIndent("b") + PrepIndent("c"))
+  let folds=[-1, 2, 2, 2, 2, 2, -1, 8, 8, 8, 8, 8, -1, 14, 14, 14, 14, 14]
+  " all folds closed
+  set fdm=indent
+  call assert_equal(folds, map(range(1, line('$')), 'foldclosed(v:val)'))
+  call assert_equal(input, getline(1, '$'))
+  7,12m0
+  call assert_equal(PrepIndent("b") + PrepIndent("a") + PrepIndent("c"), getline(1, '$'))
+  call assert_equal(folds, map(range(1, line('$')), 'foldclosed(v:val)'))
+  10,12m0
+  call assert_equal(PrepIndent("a")[1:] + PrepIndent("b") + ["a"] +  PrepIndent("c"), getline(1, '$'))
+  call assert_equal([1, 1, 1, 1, 1, -1, 7, 7, 7, 7, 7, -1, -1, 14, 14, 14, 14, 14], map(range(1, line('$')), 'foldclosed(v:val)'))
+  " moving should not close the folds
+  %d
+  call setline(1, PrepIndent("a") + PrepIndent("b") + PrepIndent("c"))
+  set fdm=indent
+  call cursor(2, 1)
+  norm! zR
+  7,12m0
+  let folds=repeat([-1], 18)
+  call assert_equal(PrepIndent("b") + PrepIndent("a") + PrepIndent("c"), getline(1, '$'))
+  call assert_equal(folds, map(range(1, line('$')), 'foldclosed(v:val)'))
+  norm! zM
+  " folds are not corrupted and all have been closed
+  call assert_equal([-1, 2, 2, 2, 2, 2, -1, 8, 8, 8, 8, 8, -1, 14, 14, 14, 14, 14], map(range(1, line('$')), 'foldclosed(v:val)'))
+  %d
+  call setline(1, ["a", "\tb", "\tc", "\td", "\te"])
+  set fdm=indent
+  %foldopen
+  3m4
+  %foldclose
+  call assert_equal(["a", "\tb", "\td", "\tc", "\te"], getline(1, '$'))
+  call assert_equal([-1, 5, 5, 5, 5], map(range(1, line('$')), 'foldclosedend(v:val)'))
+  %d
+  call setline(1, ["a", "\tb", "\tc", "\td", "\te", "z", "\ty", "\tx", "\tw", "\tv"])
+  set fdm=indent foldlevel=0
+  %foldopen
+  3m1
+  %foldclose
+  call assert_equal(["a", "\tc", "\tb", "\td", "\te", "z", "\ty", "\tx", "\tw", "\tv"], getline(1, '$'))
+  call assert_equal(1, foldlevel(2))
+  call assert_equal(5, foldclosedend(3))
+  call assert_equal([-1, 2, 2, 2, 2, -1, 7, 7, 7, 7], map(range(1, line('$')), 'foldclosed(v:val)'))
+  2,6m$
+  %foldclose
+  call assert_equal(9, foldclosedend(2))
+  call assert_equal(1, foldlevel(6))
+  call assert_equal(9, foldclosedend(7))
+  call assert_equal([-1, 2, 2, 2, 2, 2, 2, 2, 2, -1], map(range(1, line('$')), 'foldclosed(v:val)'))
+  bw!
+endfunc
index 56c66b551795fad8d0a4a7b1f5c2002691296c4f..81ef80fb09701190dbfc62f0c949f52b3973cb64 100644 (file)
@@ -764,6 +764,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    457,
 /**/
     456,
 /**/