diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/nvim/ex_cmds.c | 35 | ||||
| -rw-r--r-- | src/nvim/fold.c | 148 | ||||
| -rw-r--r-- | src/nvim/mark.c | 22 | ||||
| -rw-r--r-- | src/nvim/testdir/test_fold.vim | 141 | 
4 files changed, 324 insertions, 22 deletions
| diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 1b83677807..c560ff9210 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -756,14 +756,6 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)    linenr_T num_lines;  // Num lines moved    linenr_T last_line;  // Last line in file after adding new text -  // Moving lines seems to corrupt the folds, delete folding info now -  // and recreate it when finished.  Don't do this for manual folding, it -  // would delete all folds. -  bool isFolded = hasAnyFolding(curwin) && !foldmethodIsManual(curwin); -  if (isFolded) { -    deleteFoldRecurse(&curwin->w_folds); -  } -    if (dest >= line1 && dest < line2) {      EMSG(_("E134: Move lines into themselves"));      return FAIL; @@ -801,21 +793,29 @@ int 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); +    FOR_ALL_TAB_WINDOWS(tab, win) { +      if (win->w_buffer == curbuf) { +        foldMoveRange(&win->w_folds, line1, line2, dest); +      } +    }      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); +    FOR_ALL_TAB_WINDOWS(tab, win) { +      if (win->w_buffer == curbuf) { +        foldMoveRange(&win->w_folds, dest + 1, line1 - 1, line2); +      } +    }      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); +  mark_adjust_nofold(last_line - num_lines + 1, last_line, +                     -(last_line - dest - extra), 0L);    /*     * Now we delete the original text -- webb @@ -851,11 +851,6 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)      changed_lines(dest + 1, 0, line1 + num_lines, 0L);    } -  // recreate folds -  if (isFolded) { -    foldUpdateAll(curwin); -  } -    return OK;  } diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 7c0283971e..e2d1b017b6 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -2597,6 +2597,154 @@ static void foldRemove(garray_T *gap, linenr_T top, linenr_T bot)    }  } +// foldMoveRange() {{{2 +static void reverse_fold_order(garray_T *gap, size_t start, size_t end) +{ +  for (; start < end; start++, end--) { +    fold_T *left = (fold_T *)gap->ga_data + start; +    fold_T *right = (fold_T *)gap->ga_data + end; +    fold_T tmp = *left; +    *left = *right; +    *right = tmp; +  } +} + +// Move folds within the inclusive range "line1" to "line2" to after "dest" +// require "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) +{ +  // I want to stop *at here*, foldRemove() stops *above* top +  end += 1; +  foldRemove(&fp->fd_nested, end - fp->fd_top, MAXLNUM); +  fp->fd_len = end - fp->fd_top; +} + +#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))) +void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2, +                   const linenr_T dest) +{ +  fold_T *fp; +  const linenr_T range_len = line2 - line1 + 1; +  const linenr_T move_len = dest - line2; +  const bool at_start = foldFind(gap, line1 - 1, &fp); + +  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 *above* line1. +      // Folds after this one must be dealt with. +      truncate_fold(fp, line1 - 1); +    } +    // 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) { +    // No folds after "line1" and before "dest" +    // Case 10. +    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". +  size_t move_start = fold_index(fp, gap); +  size_t move_end = 0, dest_index = 0; +  for (; valid_fold(fp, gap) && fp->fd_top <= dest; fp++) { +    if (fp->fd_top <= line2) { +      // 5, or 6 +      if (fold_end(fp) > line2) { +        // 6, 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). +  reverse_fold_order(gap, move_start, dest_index - 1); +  reverse_fold_order(gap, move_start, move_start + dest_index - move_end - 1); +  reverse_fold_order(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/nvim/mark.c b/src/nvim/mark.c index 4e05845eb5..de2fdd7f13 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -887,6 +887,23 @@ void ex_changes(exarg_T *eap)   */  void mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_after)  { +  mark_adjust_internal(line1, line2, amount, amount_after, true); +} + +// mark_adjust_nofold() does the same as mark_adjust() but without adjusting +// folds in any way. Folds must be adjusted manually by the caller. +// This is only useful when folds need to be moved in a way different to +// calling foldMarkAdjust() with arguments line1, line2, amount, amount_after, +// for an example of why this may be necessary, see do_move(). +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, bool adjust_folds) +{    int i;    int fnum = curbuf->b_fnum;    linenr_T    *lp; @@ -1011,8 +1028,9 @@ void mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_after)          }        } -      /* adjust folds */ -      foldMarkAdjust(win, line1, line2, amount, amount_after); +      if (adjust_folds) { +        foldMarkAdjust(win, line1, line2, amount, amount_after); +      }      }    } diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim index 7cb9faa75f..de07a05a2c 100644 --- a/src/nvim/testdir/test_fold.vim +++ b/src/nvim/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, '}/*}}}*/', @@ -128,3 +132,140 @@ func Test_manual_fold_with_filter()    call assert_equal(['1', '2', '3', '4', '5', '6', '7', '8', '9'], getline(1, '$'))    bwipe!  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) +  %foldopen +  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)')) +  %d +  " Ensure moving around the edges still works. +  call setline(1, PrepIndent("a") + repeat(["a"], 3) + ["\ta"]) +  set fdm=indent foldlevel=0 +  set fdm=manual +  %foldopen +  6m$ +  " The first fold has been truncated to the 5'th line. +  " Second fold has been moved up because the moved line is now below it. +  call assert_equal([0, 1, 1, 1, 1, 0, 0, 0, 1, 0], map(range(1, line('$')), 'foldlevel(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) +  %foldopen +  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)')) +  " Ensure moving around the edges still works. +  %d +  call setline(1, PrepIndent("a") + repeat(["a"], 3) + ["\ta"]) +  set fdm=indent foldlevel=0 +  %foldopen +  6m$ +  " The first fold has been truncated to the 5'th line. +  " Second fold has been moved up because the moved line is now below it. +  call assert_equal([0, 1, 1, 1, 1, 0, 0, 0, 1, 1], map(range(1, line('$')), 'foldlevel(v:val)')) +  bw! +endfunc | 
