fold.c: more edge-cases when updating (#6207)

When foldUpdateIEMSRecurse() re-uses an existing fold, it misses the
case where the existing fold spans from before startlnum to after
firstlnum, the new fold does not span this range, and there is no
"forced start" of a fold. We add a case for this in.

Ensure that if there was no forced break in folds, we merge folds that
now touch each other.

Include testing for a tricky foldmethod=expr case that has never been a
bug. This case works at the moment because of some effects that are not
obvious when reading the code.
A test for this could be useful to ensure a regression doesn't happen.

vim-patch:8.0.0408
This commit is contained in:
Matthew Malcomson 2017-03-31 00:21:26 +01:00 committed by Justin M. Keyes
parent 831eb2a9bf
commit 3a9dd13f9e
3 changed files with 263 additions and 38 deletions

View File

@ -2232,32 +2232,51 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *gap, int level,
* before where we started looking, extend it. If it
* starts at another line, update nested folds to keep
* their position, compensating for the new fd_top. */
if (fp->fd_top >= startlnum && fp->fd_top != firstlnum) {
if (fp->fd_top > firstlnum)
/* like lines are inserted */
if (fp->fd_top == firstlnum) {
// We have found a fold beginning exactly where we want one.
} else if (fp->fd_top >= startlnum) {
if (fp->fd_top > firstlnum) {
// We will move the start of this fold up, hence we move all
// nested folds (with relative line numbers) down.
foldMarkAdjustRecurse(&fp->fd_nested,
(linenr_T)0, (linenr_T)MAXLNUM,
(long)(fp->fd_top - firstlnum), 0L);
else
/* like lines are deleted */
} else {
// Will move fold down, move nested folds relatively up.
foldMarkAdjustRecurse(&fp->fd_nested,
(linenr_T)0,
(long)(firstlnum - fp->fd_top - 1),
(linenr_T)MAXLNUM,
(long)(fp->fd_top - firstlnum));
}
fp->fd_len += fp->fd_top - firstlnum;
fp->fd_top = firstlnum;
fold_changed = TRUE;
} else if (flp->start != 0 && lvl == level
&& fp->fd_top != firstlnum) {
/* Existing fold that includes startlnum must stop
* if we find the start of a new fold at the same
* level. Split it. Delete contained folds at
* this point to split them too. */
foldRemove(&fp->fd_nested, flp->lnum - fp->fd_top,
flp->lnum - fp->fd_top);
fold_changed = true;
} else if ((flp->start != 0 && lvl == level)
|| (firstlnum != startlnum)) {
// Before there was a fold spanning from above startlnum to below
// firstlnum. This fold is valid above startlnum (because we are
// not updating that range), but there is now a break in it.
// If the break is because we are now forced to start a new fold
// at the level "level" at line fline->lnum, then we need to
// split the fold at fline->lnum.
// If the break is because the range [startlnum, firstlnum) is
// now at a lower indent than "level", we need to split the fold
// in this range.
// Any splits have to be done recursively.
linenr_T breakstart;
linenr_T breakend;
if (firstlnum != startlnum) {
breakstart = startlnum;
breakend = firstlnum;
} else {
breakstart = flp->lnum;
breakend = flp->lnum;
}
foldRemove(&fp->fd_nested, breakstart - fp->fd_top,
breakend - fp->fd_top);
i = (int)(fp - (fold_T *)gap->ga_data);
foldSplit(gap, i, flp->lnum, flp->lnum - 1);
foldSplit(gap, i, breakstart, breakend - 1);
fp = (fold_T *)gap->ga_data + i + 1;
/* If using the "marker" or "syntax" method, we
* need to continue until the end of the fold is
@ -2267,6 +2286,16 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *gap, int level,
|| getlevel == foldlevelSyntax)
finish = TRUE;
}
if (fp->fd_top == startlnum && concat) {
i = (int)(fp - (fold_T *)gap->ga_data);
if (i != 0) {
fp2 = fp - 1;
if (fp2->fd_top + fp2->fd_len == fp->fd_top) {
foldMerge(fp2, gap, fp);
fp = fp2;
}
}
}
break;
}
if (fp->fd_top >= startlnum) {

View File

@ -100,22 +100,6 @@ func! Test_indent_fold2()
bw!
endfunc
func Test_folds_marker_in_comment()
new
call setline(1, ['" foo', 'bar', 'baz'])
setl fen fdm=marker
setl com=sO:\"\ -,mO:\"\ \ ,eO:\"\",:\" cms=\"%s
norm! zf2j
setl nofen
:1y
call assert_equal(['" foo{{{'], getreg(0,1,1))
:+2y
call assert_equal(['baz"}}}'], getreg(0,1,1))
set foldmethod&
bwipe!
endfunc
func Test_manual_fold_with_filter()
if !executable('cat')
return
@ -138,6 +122,108 @@ func Test_manual_fold_with_filter()
endfor
endfunc
func! Test_indent_fold_with_read()
new
set foldmethod=indent
call setline(1, repeat(["\<Tab>a"], 4))
for n in range(1, 4)
call assert_equal(1, foldlevel(n))
endfor
call writefile(["a", "", "\<Tab>a"], 'Xfile')
foldopen
2read Xfile
%foldclose
call assert_equal(1, foldlevel(1))
call assert_equal(2, foldclosedend(1))
call assert_equal(0, foldlevel(3))
call assert_equal(0, foldlevel(4))
call assert_equal(1, foldlevel(5))
call assert_equal(7, foldclosedend(5))
bwipe!
set foldmethod&
call delete('Xfile')
endfunc
func Test_combining_folds_indent()
new
let one = "\<Tab>a"
let zero = 'a'
call setline(1, [one, one, zero, zero, zero, one, one, one])
set foldmethod=indent
3,5d
%foldclose
call assert_equal(5, foldclosedend(1))
set foldmethod&
bwipe!
endfunc
func Test_combining_folds_marker()
new
call setline(1, ['{{{', '}}}', '', '', '', '{{{', '', '}}}'])
set foldmethod=marker
3,5d
%foldclose
call assert_equal(2, foldclosedend(1))
set foldmethod&
bwipe!
endfunc
func Test_folds_marker_in_comment()
new
call setline(1, ['" foo', 'bar', 'baz'])
setl fen fdm=marker
setl com=sO:\"\ -,mO:\"\ \ ,eO:\"\",:\" cms=\"%s
norm! zf2j
setl nofen
:1y
call assert_equal(['" foo{{{'], getreg(0,1,1))
:+2y
call assert_equal(['baz"}}}'], getreg(0,1,1))
set foldmethod&
bwipe!
endfunc
func s:TestFoldExpr(lnum)
let thisline = getline(a:lnum)
if thisline == 'a'
return 1
elseif thisline == 'b'
return 0
elseif thisline == 'c'
return '<1'
elseif thisline == 'd'
return '>1'
endif
return 0
endfunction
func Test_update_folds_expr_read()
new
call setline(1, ['a', 'a', 'a', 'a', 'a', 'a'])
set foldmethod=expr
set foldexpr=s:TestFoldExpr(v:lnum)
2
foldopen
call writefile(['b', 'b', 'a', 'a', 'd', 'a', 'a', 'c'], 'Xfile')
read Xfile
%foldclose
call assert_equal(2, foldclosedend(1))
call assert_equal(0, foldlevel(3))
call assert_equal(0, foldlevel(4))
call assert_equal(6, foldclosedend(5))
call assert_equal(10, foldclosedend(7))
call assert_equal(14, foldclosedend(11))
call delete('Xfile')
bwipe!
set foldmethod& foldexpr&
endfunc
func! Test_move_folds_around_manual()
new
let input = PrepIndent("a") + PrepIndent("b") + PrepIndent("c")

View File

@ -6,12 +6,15 @@ local feed = helpers.feed
local expect = helpers.expect
local execute = helpers.execute
local funcs = helpers.funcs
local foldlevel, foldclosedend = funcs.foldlevel, funcs.foldclosedend
local foldlevel = funcs.foldlevel
local foldclosedend = funcs.foldclosedend
local eq = helpers.eq
describe('Folds', function()
local tempfname = 'Xtest-fold.txt'
clear()
before_each(function() execute('enew!') end)
after_each(function() os.remove(tempfname) end)
it('manual folding adjusts with filter', function()
insert([[
1
@ -230,4 +233,111 @@ a]], '2,3m0')
eq({1, 2, 0, 0, 0}, get_folds())
end)
end)
it('updates correctly on :read', function()
-- luacheck: ignore 621
helpers.write_file(tempfname, [[
a
a]])
insert([[
a
a
a
a
]])
execute('set foldmethod=indent', '2', '%foldopen')
execute('read ' .. tempfname)
-- Just to check we have the correct file text.
expect([[
a
a
a
a
a
a
]])
for i = 1,2 do
eq(1, funcs.foldlevel(i))
end
for i = 3,5 do
eq(0, funcs.foldlevel(i))
end
for i = 6,8 do
eq(1, funcs.foldlevel(i))
end
end)
it('combines folds when removing separating space', function()
-- luacheck: ignore 621
insert([[
a
a
a
a
a
a
a
a
]])
execute('set foldmethod=indent', '3,5d')
eq(5, funcs.foldclosedend(1))
end)
it("doesn't combine folds that have a specified end", function()
insert([[
{{{
}}}
{{{
}}}
]])
execute('set foldmethod=marker', '3,5d', '%foldclose')
eq(2, funcs.foldclosedend(1))
end)
it('splits folds according to >N and <N with foldexpr', function()
helpers.source([[
function TestFoldExpr(lnum)
let thisline = getline(a:lnum)
if thisline == 'a'
return 1
elseif thisline == 'b'
return 0
elseif thisline == 'c'
return '<1'
elseif thisline == 'd'
return '>1'
endif
return 0
endfunction
]])
helpers.write_file(tempfname, [[
b
b
a
a
d
a
a
c]])
insert([[
a
a
a
a
a
a
]])
execute('set foldmethod=expr', 'set foldexpr=TestFoldExpr(v:lnum)', '2', 'foldopen')
execute('read ' .. tempfname, '%foldclose')
eq(2, funcs.foldclosedend(1))
eq(0, funcs.foldlevel(3))
eq(0, funcs.foldlevel(4))
eq(6, funcs.foldclosedend(5))
eq(10, funcs.foldclosedend(7))
eq(14, funcs.foldclosedend(11))
end)
end)