fix(marks): make jumpoptions=view work with 'smoothscroll' (#38339)

Problem:  'jumpoptions' "view" doesn't remember skipcol and may lead to
          glitched display with 'smoothscroll'.
Solution: Save skipcol in the mark view. Also make sure skipcol doesn't
          exceed line size.
This commit is contained in:
zeertzjq
2026-03-17 21:52:25 +08:00
committed by GitHub
parent a5b8cf145d
commit 5773f0e994
8 changed files with 171 additions and 101 deletions

View File

@@ -2764,7 +2764,7 @@ void buflist_setfpos(buf_T *const buf, win_T *const win, linenr_T lnum, colnr_T
wip->wi_mark.mark.lnum = lnum;
wip->wi_mark.mark.col = col;
if (win != NULL) {
wip->wi_mark.view = mark_view_make(win->w_topline, wip->wi_mark.mark);
wip->wi_mark.view = mark_view_make(win, wip->wi_mark.mark);
}
}
if (win != NULL) {

View File

@@ -254,7 +254,7 @@ static void changed_common(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnum
if (curwin->w_buffer == buf) {
if (lnum >= curwin->w_topline && lnum <= curwin->w_botline) {
view = mark_view_make(curwin->w_topline, curwin->w_cursor);
view = mark_view_make(curwin, curwin->w_cursor);
}
}
RESET_FMARK(&buf->b_last_change, ((pos_T) { lnum, col, 0 }), buf->handle, view);

View File

@@ -3087,7 +3087,7 @@ static bool ins_esc(int *count, int cmdchar, bool nomove)
// Remember the last Insert position in the '^ mark.
if ((cmdmod.cmod_flags & CMOD_KEEPJUMPS) == 0) {
fmarkv_T view = mark_view_make(curwin->w_topline, curwin->w_cursor);
fmarkv_T view = mark_view_make(curwin, curwin->w_cursor);
RESET_FMARK(&curbuf->b_last_insert, curwin->w_cursor, curbuf->b_fnum, view);
}

View File

@@ -41,6 +41,7 @@
#include "nvim/os/time.h"
#include "nvim/os/time_defs.h"
#include "nvim/path.h"
#include "nvim/plines.h"
#include "nvim/pos_defs.h"
#include "nvim/quickfix.h"
#include "nvim/strings.h"
@@ -63,7 +64,7 @@
// Returns OK on success, FAIL if bad name given.
int setmark(int c)
{
fmarkv_T view = mark_view_make(curwin->w_topline, curwin->w_cursor);
fmarkv_T view = mark_view_make(curwin, curwin->w_cursor);
return setmark_pos(c, &curwin->w_cursor, curbuf->b_fnum, &view);
}
@@ -283,7 +284,7 @@ void setpcmark(void)
curwin->w_jumplistidx = curwin->w_jumplistlen;
fm = &curwin->w_jumplist[curwin->w_jumplistlen - 1];
fmarkv_T view = mark_view_make(curwin->w_topline, curwin->w_pcmark);
fmarkv_T view = mark_view_make(curwin, curwin->w_pcmark);
SET_XFMARK(fm, curwin->w_pcmark, curbuf->b_fnum, view, NULL);
}
@@ -691,13 +692,17 @@ void mark_view_restore(fmark_T *fm)
// and this check can prevent restoring mark view in that case.
if (topline >= 1) {
set_topline(curwin, topline);
curwin->w_skipcol = (fm->view.skipcol > 0
&& !hasFolding(curwin, topline, NULL, NULL)
&& fm->view.skipcol < linetabsize_eol(curwin, topline))
? fm->view.skipcol : 0;
}
}
}
fmarkv_T mark_view_make(linenr_T topline, pos_T pos)
fmarkv_T mark_view_make(const win_T *wp, pos_T pos)
{
return (fmarkv_T){ pos.lnum - topline };
return (fmarkv_T){ pos.lnum - wp->w_topline, wp->w_skipcol };
}
/// Search for the next named mark in the current file from a start position.

View File

@@ -74,9 +74,10 @@ typedef enum {
typedef struct {
linenr_T topline_offset; ///< Amount of lines from the mark lnum to the top of the window.
///< Use MAXLNUM to indicate that the mark does not have a view.
colnr_T skipcol;
} fmarkv_T;
#define INIT_FMARKV { MAXLNUM }
#define INIT_FMARKV { MAXLNUM, 0 }
/// Structure defining single local mark
typedef struct {

View File

@@ -534,7 +534,7 @@ void do_tag(char *tag, int type, int count, int forceit, bool verbose)
if (save_pos) {
tagstack[tagstackidx].fmark.mark = curwin->w_cursor;
tagstack[tagstackidx].fmark.fnum = curbuf->b_fnum;
tagstack[tagstackidx].fmark.view = mark_view_make(curwin->w_topline, curwin->w_cursor);
tagstack[tagstackidx].fmark.view = mark_view_make(curwin, curwin->w_cursor);
}
// Curwin will change in the call to jumpto_tag() if ":stag" was

View File

@@ -55,44 +55,39 @@ describe('jumplist', function()
write_file(fname1, ('foobar\n'):rep(100))
write_file(fname2, 'baz')
local screen = Screen.new(5, 25)
local screen = Screen.new(12, 25)
command('set number')
command('edit ' .. fname1)
feed('35gg')
command('edit ' .. fname2)
feed('<C-O>')
screen:expect {
grid = [[
{1: 24 }foobar |
{1: 25 }foobar |
{1: 26 }foobar |
{1: 27 }foobar |
{1: 28 }foobar |
{1: 29 }foobar |
{1: 30 }foobar |
{1: 31 }foobar |
{1: 32 }foobar |
{1: 33 }foobar |
{1: 34 }foobar |
{1: 35 }^foobar |
{1: 36 }foobar |
{1: 37 }foobar |
{1: 38 }foobar |
{1: 39 }foobar |
{1: 40 }foobar |
{1: 41 }foobar |
{1: 42 }foobar |
{1: 43 }foobar |
{1: 44 }foobar |
{1: 45 }foobar |
{1: 46 }foobar |
{1: 47 }foobar |
screen:expect([[
{8: 24 }foobar |
{8: 25 }foobar |
{8: 26 }foobar |
{8: 27 }foobar |
{8: 28 }foobar |
{8: 29 }foobar |
{8: 30 }foobar |
{8: 31 }foobar |
{8: 32 }foobar |
{8: 33 }foobar |
{8: 34 }foobar |
{8: 35 }^foobar |
{8: 36 }foobar |
{8: 37 }foobar |
{8: 38 }foobar |
{8: 39 }foobar |
{8: 40 }foobar |
{8: 41 }foobar |
{8: 42 }foobar |
{8: 43 }foobar |
{8: 44 }foobar |
{8: 45 }foobar |
{8: 46 }foobar |
{8: 47 }foobar |
|
]],
attr_ids = {
[1] = { foreground = Screen.colors.Brown },
},
}
]])
end)
end)
@@ -384,65 +379,134 @@ describe('jumpoptions=view', function()
end)
it('restores the view', function()
local screen = Screen.new(5, 8)
local screen = Screen.new(12, 8)
command('edit ' .. file1)
feed('12Gztj')
feed('gg<C-o>')
screen:expect([[
12 line |
^13 line |
14 line |
15 line |
16 line |
17 line |
18 line |
|
12 line |
^13 line |
14 line |
15 line |
16 line |
17 line |
18 line |
|
]])
end)
it("restores the view with 'smoothscroll'", function()
local screen = Screen.new(12, 8)
command('edit ' .. file1)
command('setlocal smoothscroll')
command([[call setline(24, repeat('a', 55))]])
feed('12Gzz')
local s1 = [[
9 line |
10 line |
11 line |
^12 line |
13 line |
14 line |
15 line |
|
]]
screen:expect(s1)
feed('G')
local s2 = [[
{1:<<<}aaaa |
25 line |
26 line |
27 line |
28 line |
29 line |
^30 line |
|
]]
screen:expect(s2)
feed('<C-o>')
screen:expect(s1)
feed('<C-i>')
screen:expect(s2)
feed('<C-o>')
screen:expect(s1)
command([[call setline(10, repeat('a', 55))]])
feed('zz')
local s3 = [[
{1:<<<}aaaaaaaaa|
aaaaaaa |
11 line |
^12 line |
13 line |
14 line |
15 line |
|
]]
screen:expect(s3)
feed('G')
screen:expect(s2)
feed('<C-o>')
screen:expect(s3)
feed('<C-i>')
screen:expect(s2)
command([[call setline(10, '10 line')]])
feed('<C-o>')
-- Topline should still be line 10, but skipcol should be reset.
screen:expect([[
10 line |
11 line |
^12 line |
13 line |
14 line |
15 line |
16 line |
|
]])
end)
it('restores the view across files', function()
local screen = Screen.new(5, 5)
local screen = Screen.new(12, 5)
command('args ' .. file1 .. ' ' .. file2)
feed('12Gzt')
command('next')
feed('G')
screen:expect([[
27 line |
28 line |
29 line |
^30 line |
|
27 line |
28 line |
29 line |
^30 line |
|
]])
feed('<C-o><C-o>')
screen:expect([[
^12 line |
13 line |
14 line |
15 line |
|
^12 line |
13 line |
14 line |
15 line |
|
]])
end)
it('restores the view across files with <C-^>/:bprevious/:bnext', function()
local screen = Screen.new(5, 5)
local screen = Screen.new(12, 5)
command('args ' .. file1 .. ' ' .. file2)
feed('12Gzt')
local s1 = [[
^12 line |
13 line |
14 line |
15 line |
|
^12 line |
13 line |
14 line |
15 line |
|
]]
screen:expect(s1)
command('next')
feed('G')
local s2 = [[
27 line |
28 line |
29 line |
^30 line |
|
27 line |
28 line |
29 line |
^30 line |
|
]]
screen:expect(s2)
feed('<C-^>')
@@ -456,7 +520,7 @@ describe('jumpoptions=view', function()
end)
it("falls back to standard behavior when view can't be recovered", function()
local screen = Screen.new(5, 8)
local screen = Screen.new(12, 8)
command('edit ' .. file1)
feed('7GzbG')
api.nvim_buf_set_lines(0, 0, 2, true, {})
@@ -468,19 +532,19 @@ describe('jumpoptions=view', function()
-- Therefore falls back to standard behavior which is centering the view/line.
feed('<C-o>')
screen:expect([[
4 line |
5 line |
6 line |
^7 line |
8 line |
9 line |
10 line |
|
4 line |
5 line |
6 line |
^7 line |
8 line |
9 line |
10 line |
|
]])
end)
it('falls back to standard behavior for a mark without a view', function()
local screen = Screen.new(5, 8)
local screen = Screen.new(12, 8)
command('edit ' .. file1)
feed('10ggzzvwy')
screen:expect([[
@@ -524,7 +588,7 @@ describe('jumpoptions=view', function()
end)
it('restores the view', function()
local screen = Screen.new(5, 6)
local screen = Screen.new(12, 6)
command('set laststatus=2 | set statusline=%f | edit ' .. file1)
feed('10Gzb<C-]>30Gzt<C-]>')
screen:expect([[

View File

@@ -350,7 +350,7 @@ describe('named marks view', function()
end)
it('is restored in normal mode but not op-pending mode', function()
local screen = Screen.new(5, 8)
local screen = Screen.new(12, 8)
command('edit ' .. file1)
feed('<C-e>jWma')
feed("G'a")
@@ -363,7 +363,7 @@ describe('named marks view', function()
7 line |
8 line |
|
]]
]]
screen:expect({ grid = expected })
feed('G`a')
screen:expect([[
@@ -375,7 +375,7 @@ describe('named marks view', function()
7 line |
8 line |
|
]])
]])
-- not in op-pending mode #20886
feed('ggj=`a')
screen:expect([[
@@ -387,35 +387,35 @@ describe('named marks view', function()
6 line |
7 line |
|
]])
]])
end)
it('is restored across files', function()
local screen = Screen.new(5, 5)
local screen = Screen.new(12, 5)
command('args ' .. file1 .. ' ' .. file2)
feed('<C-e>mA')
local mark_view = [[
^2 line |
3 line |
4 line |
5 line |
|
^2 line |
3 line |
4 line |
5 line |
|
]]
screen:expect(mark_view)
command('next')
screen:expect([[
^1 line |
2 line |
3 line |
4 line |
|
^1 line |
2 line |
3 line |
4 line |
|
]])
feed("'A")
screen:expect(mark_view)
end)
it("fallback to standard behavior when view can't be recovered", function()
local screen = Screen.new(10, 10)
local screen = Screen.new(12, 10)
command('edit ' .. file1)
feed('7GzbmaG') -- Seven lines from the top
command('new') -- Screen size for window is now half the height can't be restored
@@ -429,11 +429,11 @@ describe('named marks view', function()
8 line |
{3:<itor-marks }|
|
]])
]])
end)
it('fallback to standard behavior when mark is loaded from shada', function()
local screen = Screen.new(10, 6)
local screen = Screen.new(12, 6)
command('edit ' .. file1)
feed('G')
feed('mA')