fix(folds): foldcolumn is interrupted for virtual line above nested fold (#39999)

Problem:
`foldcolumn` is empty for virtual lines above the start of a nested
fold.

Solution:
For virtual lines, compute the outer fold level and display it by
reusing the logic from `fill_foldcolumn`.

(cherry picked from commit fe154f4d45)
This commit is contained in:
Matei Stroia
2026-06-11 16:42:18 +03:00
committed by github-actions[bot]
parent ed7acd1391
commit a0d7e80368
4 changed files with 168 additions and 19 deletions

View File

@@ -486,26 +486,37 @@ static void draw_foldcolumn(win_T *wp, winlinevars_T *wlv)
int fdc = compute_foldcolumn(wp, 0);
if (fdc > 0) {
int attr = win_hl_attr(wp, use_cursor_line_highlight(wp, wlv->lnum) ? HLF_CLF : HLF_FC);
// Only draw 'foldcolumn' for filler line if lnum is inside a fold that
// starts higher up. We don't want to show 'foldopen' or 'foldclose' twice.
foldinfo_T fi;
if (wlv->filler_todo <= 0 || wlv->foldinfo.fi_lnum < wlv->lnum) {
fi = wlv->foldinfo;
} else {
fi = (foldinfo_T){ 0 };
}
fill_foldcolumn(wp, fi, wlv->lnum, attr, fdc, &wlv->off, NULL, NULL);
bool is_virt = wlv->filler_todo > 0;
fill_foldcolumn(wp, wlv->foldinfo, wlv->lnum, attr, fdc, is_virt, &wlv->off, NULL, NULL);
}
}
/// Get foldcolumn char based on line fold level and column, either `foldsep`, `foldinner`,
/// or an overflow indicator. `foldopen` and `foldclosed` are set in `fill_foldcolumn`.
/// @param first_level Lowest fold level displayed on line
/// @param i Column index
static inline schar_T foldcolumn_sep_char(int first_level, int i, win_T *wp)
{
if (first_level == 1) {
return wp->w_p_fcs_chars.foldsep;
} else if (wp->w_p_fcs_chars.foldinner != NUL) {
return wp->w_p_fcs_chars.foldinner;
} else if (first_level + i <= 9) {
return schar_from_ascii('0' + first_level + i);
} else {
return schar_from_ascii('>');
}
}
/// Draw the foldcolumn or fill "out_buffer". Assume monocell characters.
///
/// @param fdc Current width of the foldcolumn
/// @param is_virt Whether the line is a filler line (diff or virtual)
/// @param[out] wlv_off Pointer to linebuf offset, incremented for default column
/// @param[out] out_buffer Char array to fill, only used for 'statuscolumn'
/// @param[out] out_vcol vcol array to fill, only used for 'statuscolumn'
void fill_foldcolumn(win_T *wp, foldinfo_T foldinfo, linenr_T lnum, int attr, int fdc, int *wlv_off,
colnr_T *out_vcol, schar_T *out_buffer)
void fill_foldcolumn(win_T *wp, foldinfo_T foldinfo, linenr_T lnum, int attr, int fdc, bool is_virt,
int *wlv_off, colnr_T *out_vcol, schar_T *out_buffer)
{
bool closed = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0;
int level = foldinfo.fi_level;
@@ -523,14 +534,20 @@ void fill_foldcolumn(win_T *wp, foldinfo_T foldinfo, linenr_T lnum, int attr, in
symbol = wp->w_p_fcs_chars.foldclosed;
} else if (foldinfo.fi_lnum == lnum && first_level + i >= foldinfo.fi_low_level) {
symbol = wp->w_p_fcs_chars.foldopen;
} else if (first_level == 1) {
symbol = wp->w_p_fcs_chars.foldsep;
} else if (wp->w_p_fcs_chars.foldinner != NUL) {
symbol = wp->w_p_fcs_chars.foldinner;
} else if (first_level + i <= 9) {
symbol = schar_from_ascii('0' + first_level + i);
} else {
symbol = schar_from_ascii('>');
symbol = foldcolumn_sep_char(first_level, i, wp);
}
// We don't want to show `foldopen` or `foldclose` twice, so we compute
// the fold level of `lnum - 1` and reuse the logic from above.
if (is_virt && foldinfo.fi_level != 0 && foldinfo.fi_lnum == lnum) {
int outer_level = MAX(foldinfo.fi_low_level - 1, 0);
int outer_first_level = MAX(outer_level - fdc + 1, 1);
if (i >= outer_level) {
symbol = schar_from_ascii(' ');
} else {
symbol = foldcolumn_sep_char(outer_first_level, i, wp);
}
}
int vcol = i >= level ? -1 : (i == closedcol - 1 && closed) ? -2 : -3;
@@ -1354,6 +1371,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
// Draw the 'statuscolumn' if option is set.
statuscol.draw = true;
statuscol.sattrs = wlv.sattrs;
statuscol.lnum = lnum;
statuscol.foldinfo = foldinfo;
statuscol.width = win_col_off(wp) - (wp == cmdwin_win);
statuscol.sign_cul_id = use_cursor_line_highlight(wp, lnum) ? wlv.sign_cul_attr : 0;

View File

@@ -19,6 +19,7 @@
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/vars.h"
#include "nvim/eval_defs.h"
#include "nvim/gettext_defs.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
@@ -1595,7 +1596,8 @@ stcsign:
if (fdc > 0) {
schar_T fold_buf[9];
fill_foldcolumn(wp, stcp->foldinfo, lnum, 0, fdc, NULL, stcp->fold_vcol, fold_buf);
fill_foldcolumn(wp, stcp->foldinfo, stcp->lnum, 0, fdc, get_vim_var_nr(VV_VIRTNUM) < 0,
NULL, stcp->fold_vcol, fold_buf);
stl_items[curitem].minwid = -(use_cursor_line_highlight(wp, lnum) ? HLF_CLF : HLF_FC);
size_t buflen = 0;
// TODO(bfredl): this is very backwards. we must support schar_T

View File

@@ -3,6 +3,7 @@
#include <stdbool.h>
#include "nvim/fold_defs.h"
#include "nvim/pos_defs.h"
#include "nvim/sign_defs.h"
/// 'statusline' item flags
@@ -101,6 +102,7 @@ struct stl_item {
/// Struct to hold info for 'statuscolumn'
typedef struct {
int width; ///< width of the status column
linenr_T lnum; ///< buffer line being drawn
int sign_cul_id; ///< cursorline sign highlight id
bool draw; ///< whether to draw the statuscolumn
stl_hlrec_t *hlrec; ///< highlight groups

View File

@@ -2304,6 +2304,10 @@ describe('folded lines', function()
|
]])
end
-- `foldcolumn` in `statuscolumn` should be identical
command('set statuscolumn=%C')
screen:expect_unchanged()
end)
it('foldcolumn is not interrupted when virt_lines are inside a fold', function()
@@ -2353,6 +2357,129 @@ describe('folded lines', function()
|
]])
end
command('set statuscolumn=%C')
screen:expect_unchanged()
end)
it('foldcolumn for a virt_line above a nested fold shows correct fold level', function()
fn.setline(1, 'line 1')
fn.setline(2, 'line 2')
fn.setline(3, 'line 3')
fn.setline(4, 'line 4')
local ns = api.nvim_create_namespace('ns')
api.nvim_buf_set_extmark(0, ns, 1, 0, { virt_lines = { { { 'below line 2', '' } } } })
api.nvim_buf_set_extmark(
0,
ns,
2,
0,
{ virt_lines_above = true, virt_lines = { { { 'above line 3', '' } } } }
)
command('1,4fold | 1,4foldopen')
command('3,4fold | 3,4foldopen ')
command('set foldcolumn=1')
if multigrid then
screen:expect([[
## grid 1
[2:---------------------------------------------]|*7
[3:---------------------------------------------]|
## grid 2
{7:-}^line 1 |
{7:│}line 2 |
{7:│}below line 2 |
{7:│}above line 3 |
{7:-}line 3 |
{7:2}line 4 |
{1:~ }|
## grid 3
|
]])
else
screen:expect([[
{7:-}^line 1 |
{7:│}line 2 |
{7:│}below line 2 |
{7:│}above line 3 |
{7:-}line 3 |
{7:2}line 4 |
{1:~ }|
|
]])
end
command('set statuscolumn=%C')
screen:expect_unchanged()
command('set statuscolumn=')
command('set foldcolumn=2')
if multigrid then
screen:expect([[
## grid 1
[2:---------------------------------------------]|*7
[3:---------------------------------------------]|
## grid 2
{7:- }^line 1 |
{7:│ }line 2 |
{7:│ }below line 2 |
{7:│ }above line 3 |
{7:│-}line 3 |
{7:││}line 4 |
{1:~ }|
## grid 3
|
]])
else
screen:expect([[
{7:- }^line 1 |
{7:│ }line 2 |
{7:│ }below line 2 |
{7:│ }above line 3 |
{7:│-}line 3 |
{7:││}line 4 |
{1:~ }|
|
]])
end
command('set statuscolumn=%C')
screen:expect_unchanged()
command('set statuscolumn=')
command('2,4fold | 2,4foldopen')
if multigrid then
screen:expect([[
## grid 1
[2:---------------------------------------------]|*7
[3:---------------------------------------------]|
## grid 2
{7:- }^line 1 |
{7:│-}line 2 |
{7:││}below line 2 |
{7:││}above line 3 |
{7:2-}line 3 |
{7:23}line 4 |
{1:~ }|
## grid 3
|
]])
else
screen:expect([[
{7:- }^line 1 |
{7:│-}line 2 |
{7:││}below line 2 |
{7:││}above line 3 |
{7:2-}line 3 |
{7:23}line 4 |
{1:~ }|
|
]])
end
command('set statuscolumn=%C')
screen:expect_unchanged()
end)
it('Folded and Visual highlights are combined #19691', function()