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 * before where we started looking, extend it. If it
* starts at another line, update nested folds to keep * starts at another line, update nested folds to keep
* their position, compensating for the new fd_top. */ * their position, compensating for the new fd_top. */
if (fp->fd_top >= startlnum && fp->fd_top != firstlnum) { if (fp->fd_top == firstlnum) {
if (fp->fd_top > firstlnum) // We have found a fold beginning exactly where we want one.
/* like lines are inserted */ } 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, foldMarkAdjustRecurse(&fp->fd_nested,
(linenr_T)0, (linenr_T)MAXLNUM, (linenr_T)0, (linenr_T)MAXLNUM,
(long)(fp->fd_top - firstlnum), 0L); (long)(fp->fd_top - firstlnum), 0L);
else } else {
/* like lines are deleted */ // Will move fold down, move nested folds relatively up.
foldMarkAdjustRecurse(&fp->fd_nested, foldMarkAdjustRecurse(&fp->fd_nested,
(linenr_T)0, (linenr_T)0,
(long)(firstlnum - fp->fd_top - 1), (long)(firstlnum - fp->fd_top - 1),
(linenr_T)MAXLNUM, (linenr_T)MAXLNUM,
(long)(fp->fd_top - firstlnum)); (long)(fp->fd_top - firstlnum));
}
fp->fd_len += fp->fd_top - firstlnum; fp->fd_len += fp->fd_top - firstlnum;
fp->fd_top = firstlnum; fp->fd_top = firstlnum;
fold_changed = TRUE; fold_changed = true;
} else if (flp->start != 0 && lvl == level } else if ((flp->start != 0 && lvl == level)
&& fp->fd_top != firstlnum) { || (firstlnum != startlnum)) {
/* Existing fold that includes startlnum must stop // Before there was a fold spanning from above startlnum to below
* if we find the start of a new fold at the same // firstlnum. This fold is valid above startlnum (because we are
* level. Split it. Delete contained folds at // not updating that range), but there is now a break in it.
* this point to split them too. */ // If the break is because we are now forced to start a new fold
foldRemove(&fp->fd_nested, flp->lnum - fp->fd_top, // at the level "level" at line fline->lnum, then we need to
flp->lnum - fp->fd_top); // 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); 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; fp = (fold_T *)gap->ga_data + i + 1;
/* If using the "marker" or "syntax" method, we /* If using the "marker" or "syntax" method, we
* need to continue until the end of the fold is * 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) || getlevel == foldlevelSyntax)
finish = TRUE; 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; break;
} }
if (fp->fd_top >= startlnum) { if (fp->fd_top >= startlnum) {

View File

@ -100,22 +100,6 @@ func! Test_indent_fold2()
bw! bw!
endfunc 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() func Test_manual_fold_with_filter()
if !executable('cat') if !executable('cat')
return return
@ -138,6 +122,108 @@ func Test_manual_fold_with_filter()
endfor endfor
endfunc 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() func! Test_move_folds_around_manual()
new new
let input = PrepIndent("a") + PrepIndent("b") + PrepIndent("c") let input = PrepIndent("a") + PrepIndent("b") + PrepIndent("c")

View File

@ -6,12 +6,15 @@ local feed = helpers.feed
local expect = helpers.expect local expect = helpers.expect
local execute = helpers.execute local execute = helpers.execute
local funcs = helpers.funcs local funcs = helpers.funcs
local foldlevel, foldclosedend = funcs.foldlevel, funcs.foldclosedend local foldlevel = funcs.foldlevel
local foldclosedend = funcs.foldclosedend
local eq = helpers.eq local eq = helpers.eq
describe('Folds', function() describe('Folds', function()
local tempfname = 'Xtest-fold.txt'
clear() clear()
before_each(function() execute('enew!') end) before_each(function() execute('enew!') end)
after_each(function() os.remove(tempfname) end)
it('manual folding adjusts with filter', function() it('manual folding adjusts with filter', function()
insert([[ insert([[
1 1
@ -230,4 +233,111 @@ a]], '2,3m0')
eq({1, 2, 0, 0, 0}, get_folds()) eq({1, 2, 0, 0, 0}, get_folds())
end) end)
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) end)