mirror of
https://github.com/neovim/neovim.git
synced 2024-12-20 03:05:11 -07:00
fix(scrollbind): properly take filler/virtual lines into account
Problem: `'scrollbind'` does not work properly if the window being scrolled automatically contains any filler/virtual lines (except for diff filler lines). This is because when the scrollbind check is done, the logic only considers changes to topline which are represented as line numbers. Solution: Write the logic for determine the scroll amount to take into account filler/virtual lines. Fixes #29751
This commit is contained in:
parent
c9b129a02a
commit
573a71469d
@ -183,7 +183,13 @@ CHANGED FEATURES *news-changed*
|
|||||||
|
|
||||||
These existing features changed their behavior.
|
These existing features changed their behavior.
|
||||||
|
|
||||||
• N/A
|
• 'scrollbind' now works properly with buffers that contain virutal lines.
|
||||||
|
|
||||||
|
Scrollbind works by aligning to a target top line of each window in a tab
|
||||||
|
page. Previously this was done by calculating the difference between the old
|
||||||
|
top line and the target top line, and scrolling by that amount. Now the
|
||||||
|
top lines are calculated using screen line numbers which take virtual lines
|
||||||
|
into account.
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
REMOVED FEATURES *news-removed*
|
REMOVED FEATURES *news-removed*
|
||||||
|
@ -887,7 +887,8 @@ bool decor_redraw_eol(win_T *wp, DecorState *state, int *eol_attr, int eol_col)
|
|||||||
|
|
||||||
static const uint32_t lines_filter[4] = {[kMTMetaLines] = kMTFilterSelect };
|
static const uint32_t lines_filter[4] = {[kMTMetaLines] = kMTFilterSelect };
|
||||||
|
|
||||||
int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines)
|
/// @param apply_folds Only count virtual lines that are not in folds.
|
||||||
|
int decor_virt_lines(win_T *wp, int start_row, int end_row, VirtLines *lines, bool apply_folds)
|
||||||
{
|
{
|
||||||
buf_T *buf = wp->w_buffer;
|
buf_T *buf = wp->w_buffer;
|
||||||
if (!buf_meta_total(buf, kMTMetaLines)) {
|
if (!buf_meta_total(buf, kMTMetaLines)) {
|
||||||
@ -896,15 +897,14 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(lnum > 0);
|
|
||||||
int row = lnum - 1;
|
|
||||||
|
|
||||||
MarkTreeIter itr[1] = { 0 };
|
MarkTreeIter itr[1] = { 0 };
|
||||||
if (!marktree_itr_get_filter(buf->b_marktree, MAX(row - 1, 0), 0, row + 1, 0,
|
if (!marktree_itr_get_filter(buf->b_marktree, MAX(start_row - 1, 0), 0, end_row, 0,
|
||||||
lines_filter, itr)) {
|
lines_filter, itr)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(start_row >= 0);
|
||||||
|
|
||||||
int virt_lines = 0;
|
int virt_lines = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
MTKey mark = marktree_itr_current(itr);
|
MTKey mark = marktree_itr_current(itr);
|
||||||
@ -915,7 +915,8 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines)
|
|||||||
bool above = vt->flags & kVTLinesAbove;
|
bool above = vt->flags & kVTLinesAbove;
|
||||||
int mrow = mark.pos.row;
|
int mrow = mark.pos.row;
|
||||||
int draw_row = mrow + (above ? 0 : 1);
|
int draw_row = mrow + (above ? 0 : 1);
|
||||||
if (draw_row == row && !hasFolding(wp, mrow + 1, NULL, NULL)) {
|
if (draw_row >= start_row && draw_row < end_row
|
||||||
|
&& (!apply_folds || !hasFolding(wp, mrow + 1, NULL, NULL))) {
|
||||||
virt_lines += (int)kv_size(vt->data.virt_lines);
|
virt_lines += (int)kv_size(vt->data.virt_lines);
|
||||||
if (lines) {
|
if (lines) {
|
||||||
kv_splice(*lines, vt->data.virt_lines);
|
kv_splice(*lines, vt->data.virt_lines);
|
||||||
@ -926,7 +927,7 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!marktree_itr_next_filter(buf->b_marktree, itr, row + 1, 0, lines_filter)) {
|
if (!marktree_itr_next_filter(buf->b_marktree, itr, end_row, 0, lines_filter)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2143,7 +2143,11 @@ int diff_check_with_linestatus(win_T *wp, linenr_T lnum, int *linestatus)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dp->is_linematched && diff_linematch(dp)) {
|
// Don't run linematch when lnum is offscreen.
|
||||||
|
// Useful for scrollbind calculations which need to count all the filler lines
|
||||||
|
// above the screen.
|
||||||
|
if (lnum >= wp->w_topline && lnum < wp->w_botline
|
||||||
|
&& !dp->is_linematched && diff_linematch(dp)) {
|
||||||
run_linematch_algorithm(dp);
|
run_linematch_algorithm(dp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1156,7 +1156,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
|
|||||||
area_highlighting = true;
|
area_highlighting = true;
|
||||||
}
|
}
|
||||||
VirtLines virt_lines = KV_INITIAL_VALUE;
|
VirtLines virt_lines = KV_INITIAL_VALUE;
|
||||||
wlv.n_virt_lines = decor_virt_lines(wp, lnum, &virt_lines);
|
wlv.n_virt_lines = decor_virt_lines(wp, lnum - 1, lnum, &virt_lines, true);
|
||||||
wlv.filler_lines += wlv.n_virt_lines;
|
wlv.filler_lines += wlv.n_virt_lines;
|
||||||
if (lnum == wp->w_topline) {
|
if (lnum == wp->w_topline) {
|
||||||
wlv.filler_lines = wp->w_topfill;
|
wlv.filler_lines = wp->w_topfill;
|
||||||
|
@ -2695,7 +2695,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
|
|||||||
*so_ptr = 999; // force cursor to be vertically centered in the window
|
*so_ptr = 999; // force cursor to be vertically centered in the window
|
||||||
}
|
}
|
||||||
update_topline(curwin);
|
update_topline(curwin);
|
||||||
curwin->w_scbind_pos = curwin->w_topline;
|
curwin->w_scbind_pos = plines_m_win_fill(curwin, 1, curwin->w_topline);
|
||||||
*so_ptr = n;
|
*so_ptr = n;
|
||||||
redraw_curbuf_later(UPD_NOT_VALID); // redraw this buffer later
|
redraw_curbuf_later(UPD_NOT_VALID); // redraw this buffer later
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,7 @@
|
|||||||
#include "nvim/os/os_defs.h"
|
#include "nvim/os/os_defs.h"
|
||||||
#include "nvim/os/shell.h"
|
#include "nvim/os/shell.h"
|
||||||
#include "nvim/path.h"
|
#include "nvim/path.h"
|
||||||
|
#include "nvim/plines.h"
|
||||||
#include "nvim/popupmenu.h"
|
#include "nvim/popupmenu.h"
|
||||||
#include "nvim/pos_defs.h"
|
#include "nvim/pos_defs.h"
|
||||||
#include "nvim/profile.h"
|
#include "nvim/profile.h"
|
||||||
@ -5580,39 +5581,43 @@ static void ex_swapname(exarg_T *eap)
|
|||||||
/// (1998-11-02 16:21:01 R. Edward Ralston <eralston@computer.org>)
|
/// (1998-11-02 16:21:01 R. Edward Ralston <eralston@computer.org>)
|
||||||
static void ex_syncbind(exarg_T *eap)
|
static void ex_syncbind(exarg_T *eap)
|
||||||
{
|
{
|
||||||
linenr_T topline;
|
linenr_T vtopline; // Target topline (including fill)
|
||||||
|
|
||||||
linenr_T old_linenr = curwin->w_cursor.lnum;
|
linenr_T old_linenr = curwin->w_cursor.lnum;
|
||||||
|
|
||||||
setpcmark();
|
setpcmark();
|
||||||
|
|
||||||
// determine max topline
|
// determine max (virtual) topline
|
||||||
if (curwin->w_p_scb) {
|
if (curwin->w_p_scb) {
|
||||||
topline = curwin->w_topline;
|
vtopline = get_vtopline(curwin);
|
||||||
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
|
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
|
||||||
if (wp->w_p_scb && wp->w_buffer) {
|
if (wp->w_p_scb && wp->w_buffer) {
|
||||||
topline = MIN(topline, wp->w_buffer->b_ml.ml_line_count - get_scrolloff_value(curwin));
|
linenr_T y = plines_m_win_fill(wp, 1, wp->w_buffer->b_ml.ml_line_count)
|
||||||
|
- get_scrolloff_value(curwin);
|
||||||
|
vtopline = MIN(vtopline, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
topline = MAX(topline, 1);
|
vtopline = MAX(vtopline, 1);
|
||||||
} else {
|
} else {
|
||||||
topline = 1;
|
vtopline = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set all scrollbind windows to the same topline.
|
// Set all scrollbind windows to the same topline.
|
||||||
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
|
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
|
||||||
if (wp->w_p_scb) {
|
if (wp->w_p_scb) {
|
||||||
int y = topline - wp->w_topline;
|
int y = vtopline - get_vtopline(wp);
|
||||||
if (y > 0) {
|
if (y > 0) {
|
||||||
scrollup(wp, y, true);
|
scrollup(wp, y, true);
|
||||||
} else {
|
} else {
|
||||||
scrolldown(wp, -y, true);
|
scrolldown(wp, -y, true);
|
||||||
}
|
}
|
||||||
wp->w_scbind_pos = topline;
|
wp->w_scbind_pos = vtopline;
|
||||||
redraw_later(wp, UPD_VALID);
|
redraw_later(wp, UPD_VALID);
|
||||||
cursor_correct(wp);
|
cursor_correct(wp);
|
||||||
wp->w_redr_status = true;
|
wp->w_redr_status = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (curwin->w_p_scb) {
|
if (curwin->w_p_scb) {
|
||||||
did_syncbind = true;
|
did_syncbind = true;
|
||||||
checkpcmark();
|
checkpcmark();
|
||||||
|
@ -2092,17 +2092,23 @@ static void display_showcmd(void)
|
|||||||
grid_line_flush();
|
grid_line_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int get_vtopline(win_T *wp)
|
||||||
|
{
|
||||||
|
return plines_m_win_fill(wp, 1, wp->w_topline) - wp->w_topfill;
|
||||||
|
}
|
||||||
|
|
||||||
/// When "check" is false, prepare for commands that scroll the window.
|
/// When "check" is false, prepare for commands that scroll the window.
|
||||||
/// When "check" is true, take care of scroll-binding after the window has
|
/// When "check" is true, take care of scroll-binding after the window has
|
||||||
/// scrolled. Called from normal_cmd() and edit().
|
/// scrolled. Called from normal_cmd() and edit().
|
||||||
void do_check_scrollbind(bool check)
|
void do_check_scrollbind(bool check)
|
||||||
{
|
{
|
||||||
static win_T *old_curwin = NULL;
|
static win_T *old_curwin = NULL;
|
||||||
static linenr_T old_topline = 0;
|
static linenr_T old_vtopline = 0;
|
||||||
static int old_topfill = 0;
|
|
||||||
static buf_T *old_buf = NULL;
|
static buf_T *old_buf = NULL;
|
||||||
static colnr_T old_leftcol = 0;
|
static colnr_T old_leftcol = 0;
|
||||||
|
|
||||||
|
int vtopline = get_vtopline(curwin);
|
||||||
|
|
||||||
if (check && curwin->w_p_scb) {
|
if (check && curwin->w_p_scb) {
|
||||||
// If a ":syncbind" command was just used, don't scroll, only reset
|
// If a ":syncbind" command was just used, don't scroll, only reset
|
||||||
// the values.
|
// the values.
|
||||||
@ -2115,10 +2121,9 @@ void do_check_scrollbind(bool check)
|
|||||||
if ((curwin->w_buffer == old_buf
|
if ((curwin->w_buffer == old_buf
|
||||||
|| curwin->w_p_diff
|
|| curwin->w_p_diff
|
||||||
)
|
)
|
||||||
&& (curwin->w_topline != old_topline
|
&& (vtopline != old_vtopline
|
||||||
|| curwin->w_topfill != old_topfill
|
|
||||||
|| curwin->w_leftcol != old_leftcol)) {
|
|| curwin->w_leftcol != old_leftcol)) {
|
||||||
check_scrollbind(curwin->w_topline - old_topline, curwin->w_leftcol - old_leftcol);
|
check_scrollbind(vtopline - old_vtopline, curwin->w_leftcol - old_leftcol);
|
||||||
}
|
}
|
||||||
} else if (vim_strchr(p_sbo, 'j')) { // jump flag set in 'scrollopt'
|
} else if (vim_strchr(p_sbo, 'j')) { // jump flag set in 'scrollopt'
|
||||||
// When switching between windows, make sure that the relative
|
// When switching between windows, make sure that the relative
|
||||||
@ -2129,14 +2134,13 @@ void do_check_scrollbind(bool check)
|
|||||||
// resync is performed, some of the other 'scrollbind' windows may
|
// resync is performed, some of the other 'scrollbind' windows may
|
||||||
// need to jump so that the current window's relative position is
|
// need to jump so that the current window's relative position is
|
||||||
// visible on-screen.
|
// visible on-screen.
|
||||||
check_scrollbind(curwin->w_topline - (linenr_T)curwin->w_scbind_pos, 0);
|
check_scrollbind(vtopline - curwin->w_scbind_pos, 0);
|
||||||
}
|
}
|
||||||
curwin->w_scbind_pos = curwin->w_topline;
|
curwin->w_scbind_pos = vtopline;
|
||||||
}
|
}
|
||||||
|
|
||||||
old_curwin = curwin;
|
old_curwin = curwin;
|
||||||
old_topline = curwin->w_topline;
|
old_vtopline = vtopline;
|
||||||
old_topfill = curwin->w_topfill;
|
|
||||||
old_buf = curwin->w_buffer;
|
old_buf = curwin->w_buffer;
|
||||||
old_leftcol = curwin->w_leftcol;
|
old_leftcol = curwin->w_leftcol;
|
||||||
}
|
}
|
||||||
@ -2144,20 +2148,18 @@ void do_check_scrollbind(bool check)
|
|||||||
/// Synchronize any windows that have "scrollbind" set, based on the
|
/// Synchronize any windows that have "scrollbind" set, based on the
|
||||||
/// number of rows by which the current window has changed
|
/// number of rows by which the current window has changed
|
||||||
/// (1998-11-02 16:21:01 R. Edward Ralston <eralston@computer.org>)
|
/// (1998-11-02 16:21:01 R. Edward Ralston <eralston@computer.org>)
|
||||||
void check_scrollbind(linenr_T topline_diff, int leftcol_diff)
|
void check_scrollbind(linenr_T vtopline_diff, int leftcol_diff)
|
||||||
{
|
{
|
||||||
win_T *old_curwin = curwin;
|
win_T *old_curwin = curwin;
|
||||||
buf_T *old_curbuf = curbuf;
|
buf_T *old_curbuf = curbuf;
|
||||||
int old_VIsual_select = VIsual_select;
|
int old_VIsual_select = VIsual_select;
|
||||||
int old_VIsual_active = VIsual_active;
|
int old_VIsual_active = VIsual_active;
|
||||||
colnr_T tgt_leftcol = curwin->w_leftcol;
|
colnr_T tgt_leftcol = curwin->w_leftcol;
|
||||||
linenr_T topline;
|
|
||||||
linenr_T y;
|
|
||||||
|
|
||||||
// check 'scrollopt' string for vertical and horizontal scroll options
|
// check 'scrollopt' string for vertical and horizontal scroll options
|
||||||
bool want_ver = (vim_strchr(p_sbo, 'v') && topline_diff != 0);
|
bool want_ver = old_curwin->w_p_diff
|
||||||
want_ver |= old_curwin->w_p_diff;
|
|| (vim_strchr(p_sbo, 'v') && vtopline_diff != 0);
|
||||||
bool want_hor = (vim_strchr(p_sbo, 'h') && (leftcol_diff || topline_diff != 0));
|
bool want_hor = (vim_strchr(p_sbo, 'h') && (leftcol_diff || vtopline_diff != 0));
|
||||||
|
|
||||||
// loop through the scrollbound windows and scroll accordingly
|
// loop through the scrollbound windows and scroll accordingly
|
||||||
VIsual_select = VIsual_active = 0;
|
VIsual_select = VIsual_active = 0;
|
||||||
@ -2174,16 +2176,19 @@ void check_scrollbind(linenr_T topline_diff, int leftcol_diff)
|
|||||||
if (old_curwin->w_p_diff && curwin->w_p_diff) {
|
if (old_curwin->w_p_diff && curwin->w_p_diff) {
|
||||||
diff_set_topline(old_curwin, curwin);
|
diff_set_topline(old_curwin, curwin);
|
||||||
} else {
|
} else {
|
||||||
curwin->w_scbind_pos += topline_diff;
|
curwin->w_scbind_pos += vtopline_diff;
|
||||||
topline = (linenr_T)curwin->w_scbind_pos;
|
int curr_vtopline = get_vtopline(curwin);
|
||||||
if (topline > curbuf->b_ml.ml_line_count) {
|
|
||||||
topline = curbuf->b_ml.ml_line_count;
|
|
||||||
}
|
|
||||||
if (topline < 1) {
|
|
||||||
topline = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
y = topline - curwin->w_topline;
|
// Perf: reuse curr_vtopline to reduce the time in plines_m_win_fill().
|
||||||
|
// Equivalent to:
|
||||||
|
// int max_vtopline = plines_m_win_fill(curwin, 1, curbuf->b_ml.ml_line_count);
|
||||||
|
int max_vtopline = curr_vtopline + curwin->w_topfill
|
||||||
|
+ plines_m_win_fill(curwin, curwin->w_topline + 1,
|
||||||
|
curbuf->b_ml.ml_line_count);
|
||||||
|
|
||||||
|
int new_vtopline = MAX(MIN((linenr_T)curwin->w_scbind_pos, max_vtopline), 1);
|
||||||
|
|
||||||
|
int y = new_vtopline - curr_vtopline;
|
||||||
if (y > 0) {
|
if (y > 0) {
|
||||||
scrollup(curwin, y, false);
|
scrollup(curwin, y, false);
|
||||||
} else {
|
} else {
|
||||||
|
@ -88,6 +88,7 @@
|
|||||||
#include "nvim/os/os.h"
|
#include "nvim/os/os.h"
|
||||||
#include "nvim/os/os_defs.h"
|
#include "nvim/os/os_defs.h"
|
||||||
#include "nvim/path.h"
|
#include "nvim/path.h"
|
||||||
|
#include "nvim/plines.h"
|
||||||
#include "nvim/popupmenu.h"
|
#include "nvim/popupmenu.h"
|
||||||
#include "nvim/pos_defs.h"
|
#include "nvim/pos_defs.h"
|
||||||
#include "nvim/regexp.h"
|
#include "nvim/regexp.h"
|
||||||
@ -2474,7 +2475,7 @@ static const char *did_set_scrollbind(optset_T *args)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
do_check_scrollbind(false);
|
do_check_scrollbind(false);
|
||||||
win->w_scbind_pos = win->w_topline;
|
win->w_scbind_pos = get_vtopline(win);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -712,7 +712,7 @@ bool win_may_fill(win_T *wp)
|
|||||||
/// @return Number of filler lines above lnum
|
/// @return Number of filler lines above lnum
|
||||||
int win_get_fill(win_T *wp, linenr_T lnum)
|
int win_get_fill(win_T *wp, linenr_T lnum)
|
||||||
{
|
{
|
||||||
int virt_lines = decor_virt_lines(wp, lnum, NULL);
|
int virt_lines = decor_virt_lines(wp, lnum - 1, lnum, NULL, true);
|
||||||
|
|
||||||
// be quick when there are no filler lines
|
// be quick when there are no filler lines
|
||||||
if (diffopt_filler()) {
|
if (diffopt_filler()) {
|
||||||
@ -906,6 +906,25 @@ int plines_m_win(win_T *wp, linenr_T first, linenr_T last, int max)
|
|||||||
return MIN(max, count);
|
return MIN(max, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return number of window lines a physical line range will occupy.
|
||||||
|
/// Only considers real and filler lines.
|
||||||
|
///
|
||||||
|
/// Mainly used for calculating scrolling offsets.
|
||||||
|
int plines_m_win_fill(win_T *wp, linenr_T first, linenr_T last)
|
||||||
|
{
|
||||||
|
int count = last - first + 1 + decor_virt_lines(wp, first - 1, last, NULL, false);
|
||||||
|
|
||||||
|
if (diffopt_filler()) {
|
||||||
|
for (int lnum = first; lnum <= last; lnum++) {
|
||||||
|
// Note: this also considers folds.
|
||||||
|
int n = diff_check(wp, lnum);
|
||||||
|
count += MAX(n, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MAX(count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the number of screen lines a range of text will take in window "wp".
|
/// Get the number of screen lines a range of text will take in window "wp".
|
||||||
///
|
///
|
||||||
/// @param[in] start_lnum Starting line number, 1-based inclusive.
|
/// @param[in] start_lnum Starting line number, 1-based inclusive.
|
||||||
|
@ -835,9 +835,18 @@ function M.exec_capture(code)
|
|||||||
return M.api.nvim_exec2(code, { output = true }).output
|
return M.api.nvim_exec2(code, { output = true }).output
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @param code string
|
--- @param code string|function
|
||||||
--- @return any
|
--- @return any
|
||||||
function M.exec_lua(code, ...)
|
function M.exec_lua(code, ...)
|
||||||
|
if type(code) == 'function' then
|
||||||
|
return M.api.nvim_exec_lua(
|
||||||
|
[[
|
||||||
|
local code = ...
|
||||||
|
return loadstring(code)(select(2, ...))
|
||||||
|
]],
|
||||||
|
{ string.dump(code), ... }
|
||||||
|
)
|
||||||
|
end
|
||||||
return M.api.nvim_exec_lua(code, { ... })
|
return M.api.nvim_exec_lua(code, { ... })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
442
test/functional/ui/scrollbind_spec.lua
Normal file
442
test/functional/ui/scrollbind_spec.lua
Normal file
@ -0,0 +1,442 @@
|
|||||||
|
local t = require('test.testutil')
|
||||||
|
local n = require('test.functional.testnvim')()
|
||||||
|
local clear = n.clear
|
||||||
|
local Screen = require('test.functional.ui.screen')
|
||||||
|
|
||||||
|
before_each(clear)
|
||||||
|
|
||||||
|
describe('Scrollbind', function()
|
||||||
|
local screen --- @type test.functional.ui.screen
|
||||||
|
|
||||||
|
before_each(function()
|
||||||
|
screen = Screen.new(40, 12)
|
||||||
|
screen:attach()
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('works with one buffer with virtual lines', function()
|
||||||
|
n.exec_lua(function()
|
||||||
|
local lines = {} --- @type string[]
|
||||||
|
|
||||||
|
for i = 1, 20 do
|
||||||
|
lines[i] = tostring(i * 2 - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local ns = vim.api.nvim_create_namespace('test')
|
||||||
|
|
||||||
|
vim.api.nvim_buf_set_lines(0, 0, -1, false, lines)
|
||||||
|
vim.bo.buftype = 'nofile'
|
||||||
|
|
||||||
|
for i in ipairs(lines) do
|
||||||
|
vim.api.nvim_buf_set_extmark(0, ns, i - 1, 0, {
|
||||||
|
virt_lines = { { { tostring(2 * i) .. ' v' } } },
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.wo.scrollbind = true
|
||||||
|
vim.cmd.vsplit()
|
||||||
|
vim.wo.scrollbind = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
n.feed('<C-d>')
|
||||||
|
|
||||||
|
t.eq(5, n.api.nvim_get_option_value('scroll', {}))
|
||||||
|
|
||||||
|
screen:expect({
|
||||||
|
grid = [[
|
||||||
|
6 v │6 v |
|
||||||
|
7 │7 |
|
||||||
|
8 v │8 v |
|
||||||
|
9 │9 |
|
||||||
|
10 v │10 v |
|
||||||
|
^11 │11 |
|
||||||
|
12 v │12 v |
|
||||||
|
13 │13 |
|
||||||
|
14 v │14 v |
|
||||||
|
15 │15 |
|
||||||
|
{3:[Scratch] }{2:[Scratch] }|
|
||||||
|
|
|
||||||
|
]],
|
||||||
|
})
|
||||||
|
|
||||||
|
n.feed('<C-u>')
|
||||||
|
|
||||||
|
local line1_grid = [[
|
||||||
|
^1 │1 |
|
||||||
|
2 v │2 v |
|
||||||
|
3 │3 |
|
||||||
|
4 v │4 v |
|
||||||
|
5 │5 |
|
||||||
|
6 v │6 v |
|
||||||
|
7 │7 |
|
||||||
|
8 v │8 v |
|
||||||
|
9 │9 |
|
||||||
|
10 v │10 v |
|
||||||
|
{3:[Scratch] }{2:[Scratch] }|
|
||||||
|
|
|
||||||
|
]]
|
||||||
|
|
||||||
|
screen:expect({ grid = line1_grid })
|
||||||
|
|
||||||
|
n.api.nvim_set_option_value('scroll', 6, {})
|
||||||
|
|
||||||
|
n.feed('<C-d>')
|
||||||
|
|
||||||
|
screen:expect({
|
||||||
|
grid = [[
|
||||||
|
7 │7 |
|
||||||
|
8 v │8 v |
|
||||||
|
9 │9 |
|
||||||
|
10 v │10 v |
|
||||||
|
11 │11 |
|
||||||
|
12 v │12 v |
|
||||||
|
^13 │13 |
|
||||||
|
14 v │14 v |
|
||||||
|
15 │15 |
|
||||||
|
16 v │16 v |
|
||||||
|
{3:[Scratch] }{2:[Scratch] }|
|
||||||
|
|
|
||||||
|
]],
|
||||||
|
})
|
||||||
|
|
||||||
|
n.feed('<C-u>')
|
||||||
|
|
||||||
|
screen:expect({ grid = line1_grid })
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('works with two buffers with virtual lines on one side', function()
|
||||||
|
n.exec_lua(function()
|
||||||
|
local lines = {} --- @type string[]
|
||||||
|
|
||||||
|
for i = 1, 20 do
|
||||||
|
lines[i] = tostring(i)
|
||||||
|
end
|
||||||
|
|
||||||
|
local ns = vim.api.nvim_create_namespace('test')
|
||||||
|
|
||||||
|
vim.api.nvim_buf_set_lines(0, 0, -1, false, lines)
|
||||||
|
vim.bo.buftype = 'nofile'
|
||||||
|
|
||||||
|
vim.wo.scrollbind = true
|
||||||
|
vim.cmd.vnew()
|
||||||
|
|
||||||
|
lines = {} --- @type string[]
|
||||||
|
|
||||||
|
for i = 1, 20 do
|
||||||
|
lines[i] = tostring(i + (i > 3 and 4 or 0))
|
||||||
|
end
|
||||||
|
vim.api.nvim_buf_set_lines(0, 0, -1, false, lines)
|
||||||
|
vim.bo.buftype = 'nofile'
|
||||||
|
|
||||||
|
vim.api.nvim_buf_set_extmark(0, ns, 2, 0, {
|
||||||
|
virt_lines = {
|
||||||
|
{ { '4 v' } },
|
||||||
|
{ { '5 v' } },
|
||||||
|
{ { '6 v' } },
|
||||||
|
{ { '7 v' } },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.wo.scrollbind = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
n.feed('<C-d>')
|
||||||
|
|
||||||
|
t.eq(5, n.api.nvim_get_option_value('scroll', {}))
|
||||||
|
|
||||||
|
screen:expect({
|
||||||
|
grid = [[
|
||||||
|
6 v │6 |
|
||||||
|
7 v │7 |
|
||||||
|
8 │8 |
|
||||||
|
9 │9 |
|
||||||
|
^10 │10 |
|
||||||
|
11 │11 |
|
||||||
|
12 │12 |
|
||||||
|
13 │13 |
|
||||||
|
14 │14 |
|
||||||
|
15 │15 |
|
||||||
|
{3:[Scratch] }{2:[Scratch] }|
|
||||||
|
|
|
||||||
|
]],
|
||||||
|
})
|
||||||
|
|
||||||
|
n.feed('<C-u>')
|
||||||
|
|
||||||
|
local line1_grid = [[
|
||||||
|
^1 │1 |
|
||||||
|
2 │2 |
|
||||||
|
3 │3 |
|
||||||
|
4 v │4 |
|
||||||
|
5 v │5 |
|
||||||
|
6 v │6 |
|
||||||
|
7 v │7 |
|
||||||
|
8 │8 |
|
||||||
|
9 │9 |
|
||||||
|
10 │10 |
|
||||||
|
{3:[Scratch] }{2:[Scratch] }|
|
||||||
|
|
|
||||||
|
]]
|
||||||
|
|
||||||
|
screen:expect({ grid = line1_grid })
|
||||||
|
|
||||||
|
n.api.nvim_set_option_value('scroll', 6, {})
|
||||||
|
|
||||||
|
n.feed('<C-d>')
|
||||||
|
|
||||||
|
screen:expect({
|
||||||
|
grid = [[
|
||||||
|
7 v │7 |
|
||||||
|
8 │8 |
|
||||||
|
9 │9 |
|
||||||
|
10 │10 |
|
||||||
|
^11 │11 |
|
||||||
|
12 │12 |
|
||||||
|
13 │13 |
|
||||||
|
14 │14 |
|
||||||
|
15 │15 |
|
||||||
|
16 │16 |
|
||||||
|
{3:[Scratch] }{2:[Scratch] }|
|
||||||
|
|
|
||||||
|
]],
|
||||||
|
})
|
||||||
|
|
||||||
|
n.feed('<C-u>')
|
||||||
|
|
||||||
|
screen:expect({ grid = line1_grid })
|
||||||
|
|
||||||
|
-- Note: not the same as n.feed('4<C-e>')
|
||||||
|
n.feed('<C-e>')
|
||||||
|
n.feed('<C-e>')
|
||||||
|
n.feed('<C-e>')
|
||||||
|
n.feed('<C-e>')
|
||||||
|
|
||||||
|
screen:expect({
|
||||||
|
grid = [[
|
||||||
|
5 v │5 |
|
||||||
|
6 v │6 |
|
||||||
|
7 v │7 |
|
||||||
|
^8 │8 |
|
||||||
|
9 │9 |
|
||||||
|
10 │10 |
|
||||||
|
11 │11 |
|
||||||
|
12 │12 |
|
||||||
|
13 │13 |
|
||||||
|
14 │14 |
|
||||||
|
{3:[Scratch] }{2:[Scratch] }|
|
||||||
|
|
|
||||||
|
]],
|
||||||
|
})
|
||||||
|
|
||||||
|
n.feed('<C-e>')
|
||||||
|
|
||||||
|
screen:expect({
|
||||||
|
grid = [[
|
||||||
|
6 v │6 |
|
||||||
|
7 v │7 |
|
||||||
|
^8 │8 |
|
||||||
|
9 │9 |
|
||||||
|
10 │10 |
|
||||||
|
11 │11 |
|
||||||
|
12 │12 |
|
||||||
|
13 │13 |
|
||||||
|
14 │14 |
|
||||||
|
15 │15 |
|
||||||
|
{3:[Scratch] }{2:[Scratch] }|
|
||||||
|
|
|
||||||
|
]],
|
||||||
|
})
|
||||||
|
|
||||||
|
n.feed('<C-y>')
|
||||||
|
n.feed('<C-y>')
|
||||||
|
|
||||||
|
screen:expect({
|
||||||
|
grid = [[
|
||||||
|
4 v │4 |
|
||||||
|
5 v │5 |
|
||||||
|
6 v │6 |
|
||||||
|
7 v │7 |
|
||||||
|
^8 │8 |
|
||||||
|
9 │9 |
|
||||||
|
10 │10 |
|
||||||
|
11 │11 |
|
||||||
|
12 │12 |
|
||||||
|
13 │13 |
|
||||||
|
{3:[Scratch] }{2:[Scratch] }|
|
||||||
|
|
|
||||||
|
]],
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('works with buffers of different lengths', function()
|
||||||
|
n.exec_lua(function()
|
||||||
|
vim.api.nvim_buf_set_lines(0, 0, -1, false, { '1', '2', '3' })
|
||||||
|
vim.bo.buftype = 'nofile'
|
||||||
|
|
||||||
|
vim.wo.scrollbind = true
|
||||||
|
vim.cmd.vnew()
|
||||||
|
|
||||||
|
local lines = {} --- @type string[]
|
||||||
|
|
||||||
|
for i = 1, 50 do
|
||||||
|
lines[i] = tostring(i)
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.api.nvim_buf_set_lines(0, 0, -1, false, lines)
|
||||||
|
vim.bo.buftype = 'nofile'
|
||||||
|
vim.wo.scrollbind = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
n.feed('10<C-e>')
|
||||||
|
|
||||||
|
screen:expect({
|
||||||
|
grid = [[
|
||||||
|
^11 │3 |
|
||||||
|
12 │{1:~ }|
|
||||||
|
13 │{1:~ }|
|
||||||
|
14 │{1:~ }|
|
||||||
|
15 │{1:~ }|
|
||||||
|
16 │{1:~ }|
|
||||||
|
17 │{1:~ }|
|
||||||
|
18 │{1:~ }|
|
||||||
|
19 │{1:~ }|
|
||||||
|
20 │{1:~ }|
|
||||||
|
{3:[Scratch] }{2:[Scratch] }|
|
||||||
|
|
|
||||||
|
]],
|
||||||
|
})
|
||||||
|
|
||||||
|
n.feed('<C-y>')
|
||||||
|
|
||||||
|
screen:expect({
|
||||||
|
grid = [[
|
||||||
|
10 │3 |
|
||||||
|
^11 │{1:~ }|
|
||||||
|
12 │{1:~ }|
|
||||||
|
13 │{1:~ }|
|
||||||
|
14 │{1:~ }|
|
||||||
|
15 │{1:~ }|
|
||||||
|
16 │{1:~ }|
|
||||||
|
17 │{1:~ }|
|
||||||
|
18 │{1:~ }|
|
||||||
|
19 │{1:~ }|
|
||||||
|
{3:[Scratch] }{2:[Scratch] }|
|
||||||
|
|
|
||||||
|
]],
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('works with buffers of different lengths and virtual lines', function()
|
||||||
|
n.exec_lua(function()
|
||||||
|
vim.api.nvim_buf_set_lines(0, 0, -1, false, { '1', '5', '6' })
|
||||||
|
|
||||||
|
local ns = vim.api.nvim_create_namespace('test')
|
||||||
|
vim.api.nvim_buf_set_extmark(0, ns, 0, 0, {
|
||||||
|
virt_lines = {
|
||||||
|
{ { '2 v' } },
|
||||||
|
{ { '3 v' } },
|
||||||
|
{ { '4 v' } },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.bo.buftype = 'nofile'
|
||||||
|
|
||||||
|
vim.wo.scrollbind = true
|
||||||
|
vim.cmd.vnew()
|
||||||
|
|
||||||
|
local lines = {} --- @type string[]
|
||||||
|
|
||||||
|
for i = 1, 50 do
|
||||||
|
lines[i] = tostring(i)
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.api.nvim_buf_set_lines(0, 0, -1, false, lines)
|
||||||
|
vim.bo.buftype = 'nofile'
|
||||||
|
vim.wo.scrollbind = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
n.feed('<C-e>')
|
||||||
|
n.feed('<C-e>')
|
||||||
|
screen:expect({
|
||||||
|
grid = [[
|
||||||
|
^3 │3 v |
|
||||||
|
4 │4 v |
|
||||||
|
5 │5 |
|
||||||
|
6 │6 |
|
||||||
|
7 │{1:~ }|
|
||||||
|
8 │{1:~ }|
|
||||||
|
9 │{1:~ }|
|
||||||
|
10 │{1:~ }|
|
||||||
|
11 │{1:~ }|
|
||||||
|
12 │{1:~ }|
|
||||||
|
{3:[Scratch] }{2:[Scratch] }|
|
||||||
|
|
|
||||||
|
]],
|
||||||
|
})
|
||||||
|
|
||||||
|
n.feed('8<C-e>')
|
||||||
|
|
||||||
|
screen:expect({
|
||||||
|
grid = [[
|
||||||
|
^11 │6 |
|
||||||
|
12 │{1:~ }|
|
||||||
|
13 │{1:~ }|
|
||||||
|
14 │{1:~ }|
|
||||||
|
15 │{1:~ }|
|
||||||
|
16 │{1:~ }|
|
||||||
|
17 │{1:~ }|
|
||||||
|
18 │{1:~ }|
|
||||||
|
19 │{1:~ }|
|
||||||
|
20 │{1:~ }|
|
||||||
|
{3:[Scratch] }{2:[Scratch] }|
|
||||||
|
|
|
||||||
|
]],
|
||||||
|
})
|
||||||
|
|
||||||
|
n.feed('<C-y>')
|
||||||
|
n.feed('<C-y>')
|
||||||
|
n.feed('<C-y>')
|
||||||
|
n.feed('<C-y>')
|
||||||
|
n.feed('<C-y>')
|
||||||
|
|
||||||
|
t.eq(n.exec_lua [[return vim.fn.line('w0', 1001)]], 6)
|
||||||
|
t.eq(n.exec_lua [[return vim.fn.line('w0', 1000)]], 3)
|
||||||
|
|
||||||
|
screen:expect({
|
||||||
|
grid = [[
|
||||||
|
6 │6 |
|
||||||
|
7 │{1:~ }|
|
||||||
|
8 │{1:~ }|
|
||||||
|
9 │{1:~ }|
|
||||||
|
10 │{1:~ }|
|
||||||
|
^11 │{1:~ }|
|
||||||
|
12 │{1:~ }|
|
||||||
|
13 │{1:~ }|
|
||||||
|
14 │{1:~ }|
|
||||||
|
15 │{1:~ }|
|
||||||
|
{3:[Scratch] }{2:[Scratch] }|
|
||||||
|
|
|
||||||
|
]],
|
||||||
|
})
|
||||||
|
|
||||||
|
n.feed('<C-y>')
|
||||||
|
n.feed('<C-y>')
|
||||||
|
n.feed('<C-y>')
|
||||||
|
|
||||||
|
screen:expect({
|
||||||
|
grid = [[
|
||||||
|
3 │3 v |
|
||||||
|
4 │4 v |
|
||||||
|
5 │5 |
|
||||||
|
6 │6 |
|
||||||
|
7 │{1:~ }|
|
||||||
|
8 │{1:~ }|
|
||||||
|
9 │{1:~ }|
|
||||||
|
10 │{1:~ }|
|
||||||
|
^11 │{1:~ }|
|
||||||
|
12 │{1:~ }|
|
||||||
|
{3:[Scratch] }{2:[Scratch] }|
|
||||||
|
|
|
||||||
|
]],
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
end)
|
Loading…
Reference in New Issue
Block a user