To: vim_dev@googlegroups.com Subject: Patch 8.0.0457 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.0.0457 Problem: Using :move messes up manual folds. Solution: Split adjusting marks and folds. Add foldMoveRange(). (neovim patch #6221) Files: src/ex_cmds.c, src/fold.c, src/mark.c, src/proto/fold.pro, src/proto/mark.pro src/testdir/test_fold.vim *** ../vim-8.0.0456/src/ex_cmds.c 2017-03-12 20:09:59.468468376 +0100 --- src/ex_cmds.c 2017-03-14 21:38:54.972614770 +0100 *************** *** 800,805 **** --- 800,807 ---- 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,874 **** * 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); if (dest >= line2) { ! mark_adjust(line2 + 1, dest, -num_lines, 0L); 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); 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, -(last_line - dest - extra), 0L); - changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra); /* * Now we delete the original text -- webb --- 853,886 ---- * their final destination at the new text position -- webb */ last_line = curbuf->b_ml.ml_line_count; ! mark_adjust_nofold(line1, line2, last_line - line2, 0L); if (dest >= line2) { ! 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_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_nofold(last_line - num_lines + 1, last_line, -(last_line - dest - extra), 0L); /* * Now we delete the original text -- webb *** ../vim-8.0.0456/src/fold.c 2017-03-12 20:37:16.836943099 +0100 --- src/fold.c 2017-03-14 21:46:10.709460056 +0100 *************** *** 64,69 **** --- 64,70 ---- 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,1080 **** --- 1076,1087 ---- (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,2973 **** --- 2975,3156 ---- } } + /* 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). *** ../vim-8.0.0456/src/mark.c 2017-03-12 19:22:31.756584930 +0100 --- src/mark.c 2017-03-14 21:38:04.956977247 +0100 *************** *** 37,42 **** --- 37,44 ---- #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,1034 **** --- 1031,1057 ---- 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,1180 **** #ifdef FEAT_FOLDING /* adjust folds */ ! foldMarkAdjust(win, line1, line2, amount, amount_after); #endif } } --- 1197,1204 ---- #ifdef FEAT_FOLDING /* adjust folds */ ! if (adjust_folds) ! foldMarkAdjust(win, line1, line2, amount, amount_after); #endif } } *** ../vim-8.0.0456/src/proto/fold.pro 2016-09-12 13:04:04.000000000 +0200 --- src/proto/fold.pro 2017-03-14 21:38:04.956977247 +0100 *************** *** 31,36 **** --- 31,37 ---- 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); *** ../vim-8.0.0456/src/proto/mark.pro 2016-09-12 13:04:10.000000000 +0200 --- src/proto/mark.pro 2017-03-14 21:38:04.956977247 +0100 *************** *** 19,24 **** --- 19,25 ---- 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); *** ../vim-8.0.0456/src/testdir/test_fold.vim 2017-03-12 20:37:16.836943099 +0100 --- src/testdir/test_fold.vim 2017-03-14 21:33:43.970869978 +0100 *************** *** 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,221 **** --- 223,343 ---- 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 *** ../vim-8.0.0456/src/version.c 2017-03-13 22:41:38.376723909 +0100 --- src/version.c 2017-03-14 21:37:42.305141436 +0100 *************** *** 766,767 **** --- 766,769 ---- { /* Add new patch number below this line */ + /**/ + 457, /**/ -- hundred-and-one symptoms of being an internet addict: 123. You ask the car dealer to install an extra cigarette lighter on your new car to power your notebook. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///