# HG changeset patch # User Christian Brabandt # Date 1489525205 -3600 # Node ID 6b26e044b6f5875f9ad0856ebd8e21e2ce3e8259 # Parent 30a2d8730a13aeef7a8ddc35c6e9de018c36902e patch 8.0.0457: using :move messes up manual folds commit https://github.com/vim/vim/commit/88d298aed8682eac872ebfe40df3112a6acd83e8 Author: Bram Moolenaar Date: Tue Mar 14 21:53:58 2017 +0100 patch 8.0.0457: using :move messes up manual folds Problem: Using :move messes up manual folds. Solution: Split adjusting marks and folds. Add foldMoveRange(). (neovim patch #6221) diff --git a/src/ex_cmds.c b/src/ex_cmds.c --- a/src/ex_cmds.c +++ b/src/ex_cmds.c @@ -800,6 +800,8 @@ do_move(linenr_T line1, linenr_T line2, 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, * 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, 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; diff --git a/src/fold.c b/src/fold.c --- a/src/fold.c +++ b/src/fold.c @@ -64,6 +64,7 @@ static void deleteFoldMarkers(fold_T *fp 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, } } +/* 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). diff --git a/src/mark.c b/src/mark.c --- a/src/mark.c +++ b/src/mark.c @@ -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. @@ -1029,6 +1031,27 @@ mark_adjust( 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; linenr_T *lp; @@ -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 } } diff --git a/src/proto/fold.pro b/src/proto/fold.pro --- a/src/proto/fold.pro +++ b/src/proto/fold.pro @@ -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); diff --git a/src/proto/mark.pro b/src/proto/mark.pro --- a/src/proto/mark.pro +++ b/src/proto/mark.pro @@ -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); diff --git a/src/testdir/test_fold.vim b/src/testdir/test_fold.vim --- a/src/testdir/test_fold.vim +++ b/src/testdir/test_fold.vim @@ -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 diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -765,6 +765,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 457, +/**/ 456, /**/ 455,