Skip to content

Commit e7745b7

Browse files
mattnchrisbra
authored andcommitted
patch 9.2.0411: tabpanel: no Vim script functions for the tabpanel
Problem: tabpanel: no Vim script functions for the tabpanel Solution: Add tabpanel_getinfo() and tabpanel_scroll() (Yasuhiro Matsumoto). tabpanel_getinfo() returns a dict describing the tabpanel (align, columns, scrollbar, offset, total, max_offset). tabpanel_scroll(n) scrolls the tabpanel by n rows (positive for down, negative for up). With {absolute: 1} the argument is used as the new absolute offset instead of a delta. The offset is clamped to the valid range; returns true when it actually changes. closes: #20056 Signed-off-by: Yasuhiro Matsumoto <mattn.jp@gmail.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
1 parent 6146f33 commit e7745b7

10 files changed

Lines changed: 235 additions & 6 deletions

File tree

‎runtime/doc/builtin.txt‎

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*builtin.txt* For Vim version 9.2. Last change: 2026 Apr 27
1+
*builtin.txt* For Vim version 9.2. Last change: 2026 Apr 28
22

33

44
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -705,6 +705,8 @@ tabpagebuflist([{arg}]) List list of buffer numbers in tab page
705705
tabpagenr([{arg}]) Number number of current or last tab page
706706
tabpagewinnr({tabarg} [, {arg}])
707707
Number number of current window in tab page
708+
tabpanel_getinfo() Dict get current state of the tabpanel
709+
tabpanel_scroll({n} [, {opts}]) Bool scroll the tabpanel
708710
tagfiles() List tags files used
709711
taglist({expr} [, {filename}]) List list of tags matching {expr}
710712
tan({expr}) Float tangent of {expr}
@@ -11884,6 +11886,38 @@ tabpagewinnr({tabarg} [, {arg}]) *tabpagewinnr()*
1188411886
Return type: |Number|
1188511887

1188611888

11889+
tabpanel_getinfo() *tabpanel_getinfo()*
11890+
Return a |Dictionary| describing the current state of the
11891+
tabpanel (see |tabpanel|). The dictionary has these keys:
11892+
align "left" or "right"
11893+
columns width in screen columns
11894+
scrollbar |TRUE| if a scrollbar is shown
11895+
offset current scroll offset in rows
11896+
total total number of rows rendered
11897+
max_offset largest valid value for "offset"
11898+
11899+
The "total" and "max_offset" values are only accurate after
11900+
the tabpanel has been drawn at least once.
11901+
11902+
Return type: dict<any>
11903+
11904+
11905+
tabpanel_scroll({n} [, {opts}]) *tabpanel_scroll()*
11906+
Scroll the tabpanel by {n} rows. Positive values scroll down
11907+
(later tabs become visible), negative values scroll up. The
11908+
new offset is clamped to the valid range.
11909+
11910+
When {opts} is a |Dictionary| and its "absolute" entry is
11911+
|TRUE|, {n} is used as the new absolute scroll offset
11912+
instead of a delta.
11913+
11914+
Returns |TRUE| if the scroll offset changed, |FALSE|
11915+
otherwise (for example when the tabpanel is not shown, or
11916+
the offset is already at the requested value).
11917+
11918+
Return type: |vim9-boolean|
11919+
11920+
1188711921
tagfiles() *tagfiles()*
1188811922
Returns a |List| with the file names used to search for tags
1188911923
for the current buffer. This is the 'tags' option expanded.

‎runtime/doc/tags‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10896,6 +10896,8 @@ tabpagewinnr() builtin.txt /*tabpagewinnr()*
1089610896
tabpanel tabpage.txt /*tabpanel*
1089710897
tabpanel-mouse tabpage.txt /*tabpanel-mouse*
1089810898
tabpanel-scroll tabpage.txt /*tabpanel-scroll*
10899+
tabpanel_getinfo() builtin.txt /*tabpanel_getinfo()*
10900+
tabpanel_scroll() builtin.txt /*tabpanel_scroll()*
1089910901
tag tagsrch.txt /*tag*
1090010902
tag-! tagsrch.txt /*tag-!*
1090110903
tag-binary-search tagsrch.txt /*tag-binary-search*

‎runtime/doc/usr_41.txt‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*usr_41.txt* For Vim version 9.2. Last change: 2026 Feb 16
1+
*usr_41.txt* For Vim version 9.2. Last change: 2026 Apr 28
22

33

44
VIM USER MANUAL by Bram Moolenaar
@@ -1081,6 +1081,8 @@ Buffers, windows and the argument list:
10811081
tabpagebuflist() return List of buffers in a tab page
10821082
tabpagenr() get the number of a tab page
10831083
tabpagewinnr() like winnr() for a specified tab page
1084+
tabpanel_getinfo() get current state of the tabpanel
1085+
tabpanel_scroll() scroll the tabpanel
10841086
winnr() get the window number for the current window
10851087
bufwinid() get the window ID of a specific buffer
10861088
bufwinnr() get the window number of a specific buffer

‎runtime/doc/version9.txt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52651,6 +52651,8 @@ Various syntax, indent and other plugins were added.
5265152651
Functions: ~
5265252652

5265352653
|ch_listen()| listen on {address}
52654+
|tabpanel_getinfo()| get current state of the |tabpanel|
52655+
|tabpanel_scroll()| scroll the |tabpanel|
5265452656

5265552657
Autocommands: ~
5265652658

‎runtime/syntax/vim.vim‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
" Language: Vim script
33
" Maintainer: Hirohito Higashi <h.east.727 ATMARK gmail.com>
44
" Doug Kearns <dougkearns@gmail.com>
5-
" Last Change: 2026 Apr 20
5+
" Last Change: 2026 Apr 28
66
" Former Maintainer: Charles E. Campbell
77

88
" DO NOT CHANGE DIRECTLY.
@@ -159,9 +159,9 @@ syn keyword vimFuncName contained getbufline getbufoneline getbufvar getcellpixe
159159
syn keyword vimFuncName contained histget histnr hlID hlexists hlget hlset hostname iconv id indent index indexof input inputdialog inputlist inputrestore inputsave inputsecret insert instanceof interrupt invert isabsolutepath isdirectory isinf islocked isnan items job_getchannel job_info job_setoptions job_start job_status job_stop join js_decode js_encode json_decode json_encode keys keytrans len libcall libcallnr line line2byte lispindent list2blob list2str list2tuple listener_add listener_flush listener_remove localtime log log10 luaeval map maparg mapcheck maplist mapnew mapset match matchadd matchaddpos matcharg matchbufline matchdelete matchend matchfuzzy matchfuzzypos matchlist matchstr matchstrlist matchstrpos max menu_info min mkdir mode mzeval nextnonblank
160160
syn keyword vimFuncName contained ngettext nr2char or pathshorten perleval popup_atcursor popup_beval popup_clear popup_close popup_create popup_dialog popup_filter_menu popup_filter_yesno popup_findecho popup_findinfo popup_findpreview popup_getoptions popup_getpos popup_hide popup_list popup_locate popup_menu popup_move popup_notification popup_setbuf popup_setoptions popup_settext popup_show pow preinserted prevnonblank printf prompt_getprompt prompt_setcallback prompt_setinterrupt prompt_setprompt prop_add prop_add_list prop_clear prop_find prop_list prop_remove prop_type_add prop_type_change prop_type_delete prop_type_get prop_type_list pum_getpos pumvisible py3eval pyeval pyxeval rand range readblob readdir readdirex readfile redraw_listener_add redraw_listener_remove
161161
syn keyword vimFuncName contained reduce reg_executing reg_recording reltime reltimefloat reltimestr remote_expr remote_foreground remote_peek remote_read remote_send remote_startserver remove rename repeat resolve reverse round rubyeval screenattr screenchar screenchars screencol screenpos screenrow screenstring search searchcount searchdecl searchpair searchpairpos searchpos server2client serverlist setbufline setbufvar setcellwidths setcharpos setcharsearch setcmdline setcmdpos setcursorcharpos setenv setfperm setline setloclist setmatches setpos setqflist setreg settabvar settabwinvar settagstack setwinvar sha256 shellescape shiftwidth sign_define sign_getdefined sign_getplaced sign_jump sign_place sign_placelist sign_undefine sign_unplace sign_unplacelist
162-
syn keyword vimFuncName contained simplify sin sinh slice sort sound_clear sound_playevent sound_playfile sound_stop soundfold spellbadword spellsuggest split sqrt srand state str2blob str2float str2list str2nr strcharlen strcharpart strchars strdisplaywidth strftime strgetchar stridx string strlen strpart strptime strridx strtrans strutf16len strwidth submatch substitute swapfilelist swapinfo swapname synID synIDattr synIDtrans synconcealed synstack system systemlist tabpagebuflist tabpagenr tabpagewinnr tagfiles taglist tan tanh tempname term_dumpdiff term_dumpload term_dumpwrite term_getaltscreen term_getansicolors term_getattr term_getcursor term_getjob term_getline term_getscrolled term_getsize term_getstatus term_gettitle term_gettty term_list term_scrape
163-
syn keyword vimFuncName contained term_sendkeys term_setansicolors term_setapi term_setkill term_setrestore term_setsize term_start term_wait terminalprops test_alloc_fail test_autochdir test_feedinput test_garbagecollect_now test_garbagecollect_soon test_getvalue test_gui_event test_ignore_error test_mswin_event test_null_blob test_null_channel test_null_dict test_null_function test_null_job test_null_list test_null_partial test_null_string test_null_tuple test_option_not_set test_override test_refcount test_setmouse test_settime test_srand_seed test_unknown test_void timer_info timer_pause timer_start timer_stop timer_stopall tolower toupper tr trim trunc tuple2list type typename undofile undotree uniq uri_decode uri_encode utf16idx values virtcol virtcol2col
164-
syn keyword vimFuncName contained visualmode wildmenumode wildtrigger win_execute win_findbuf win_getid win_gettype win_gotoid win_id2tabwin win_id2win win_move_separator win_move_statusline win_screenpos win_splitmove winbufnr wincol windowsversion winheight winlayout winline winnr winrestcmd winrestview winsaveview winwidth wordcount writefile xor
162+
syn keyword vimFuncName contained simplify sin sinh slice sort sound_clear sound_playevent sound_playfile sound_stop soundfold spellbadword spellsuggest split sqrt srand state str2blob str2float str2list str2nr strcharlen strcharpart strchars strdisplaywidth strftime strgetchar stridx string strlen strpart strptime strridx strtrans strutf16len strwidth submatch substitute swapfilelist swapinfo swapname synID synIDattr synIDtrans synconcealed synstack system systemlist tabpagebuflist tabpagenr tabpagewinnr tabpanel_getinfo tabpanel_scroll tagfiles taglist tan tanh tempname term_dumpdiff term_dumpload term_dumpwrite term_getaltscreen term_getansicolors term_getattr term_getcursor term_getjob term_getline term_getscrolled term_getsize term_getstatus term_gettitle
163+
syn keyword vimFuncName contained term_gettty term_list term_scrape term_sendkeys term_setansicolors term_setapi term_setkill term_setrestore term_setsize term_start term_wait terminalprops test_alloc_fail test_autochdir test_feedinput test_garbagecollect_now test_garbagecollect_soon test_getvalue test_gui_event test_ignore_error test_mswin_event test_null_blob test_null_channel test_null_dict test_null_function test_null_job test_null_list test_null_partial test_null_string test_null_tuple test_option_not_set test_override test_refcount test_setmouse test_settime test_srand_seed test_unknown test_void timer_info timer_pause timer_start timer_stop timer_stopall tolower toupper tr trim trunc tuple2list type typename undofile undotree uniq uri_decode uri_encode
164+
syn keyword vimFuncName contained utf16idx values virtcol virtcol2col visualmode wildmenumode wildtrigger win_execute win_findbuf win_getid win_gettype win_gotoid win_id2tabwin win_id2win win_move_separator win_move_statusline win_screenpos win_splitmove winbufnr wincol windowsversion winheight winlayout winline winnr winrestcmd winrestview winsaveview winwidth wordcount writefile xor
165165

166166
" Predefined variable names {{{2
167167
" GEN_SYN_VIM: vimVarName, START_STR='syn keyword vimVimVarName contained', END_STR=''

‎src/evalfunc.c‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1934,6 +1934,11 @@ typedef struct
19341934
#else
19351935
# define TERM_FUNC(name) NULL
19361936
#endif
1937+
#ifdef FEAT_TABPANEL
1938+
# define TABPANEL_FUNC(name) name
1939+
#else
1940+
# define TABPANEL_FUNC(name) NULL
1941+
#endif
19371942

19381943
static const funcentry_T global_functions[] =
19391944
{
@@ -2991,6 +2996,10 @@ static const funcentry_T global_functions[] =
29912996
ret_number, f_tabpagenr},
29922997
{"tabpagewinnr", 1, 2, FEARG_1, arg2_number_string,
29932998
ret_number, f_tabpagewinnr},
2999+
{"tabpanel_getinfo", 0, 0, 0, NULL,
3000+
ret_dict_any, TABPANEL_FUNC(f_tabpanel_getinfo)},
3001+
{"tabpanel_scroll", 1, 2, FEARG_1, arg2_number_dict_any,
3002+
ret_bool, TABPANEL_FUNC(f_tabpanel_scroll)},
29943003
{"tagfiles", 0, 0, 0, NULL,
29953004
ret_list_string, f_tagfiles},
29963005
{"taglist", 1, 2, FEARG_1, arg2_string,

‎src/proto/tabpanel.pro‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@ bool mouse_on_tabpanel(void);
99
bool mouse_on_tabpanel_scrollbar(void);
1010
bool tabpanel_drag_scrollbar(int screen_row);
1111
bool tabpanel_scroll(int dir, int count);
12+
bool tabpanel_set_offset(int offset);
13+
void f_tabpanel_getinfo(typval_T *argvars, typval_T *rettv);
14+
void f_tabpanel_scroll(typval_T *argvars, typval_T *rettv);
1215
/* vim: set ft=c : */

‎src/tabpanel.c‎

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,4 +913,102 @@ tabpanel_scroll(int dir, int count)
913913
return true;
914914
}
915915

916+
/*
917+
* Set the tabpanel scroll offset to "offset" (clamped to the valid range).
918+
* Returns true if the offset changed and a redraw was scheduled.
919+
*/
920+
bool
921+
tabpanel_set_offset(int offset)
922+
{
923+
int max_offset;
924+
925+
if (tabpanel_width() == 0)
926+
return false;
927+
928+
max_offset = tpl_total_rows - Rows;
929+
if (max_offset < 0)
930+
max_offset = 0;
931+
932+
if (offset < 0)
933+
offset = 0;
934+
if (offset > max_offset)
935+
offset = max_offset;
936+
if (offset == tpl_scroll_offset)
937+
return false;
938+
939+
tpl_scroll_offset = offset;
940+
redraw_tabpanel = TRUE;
941+
return true;
942+
}
943+
944+
/*
945+
* "tabpanel_getinfo()" function
946+
*/
947+
void
948+
f_tabpanel_getinfo(typval_T *argvars UNUSED, typval_T *rettv)
949+
{
950+
dict_T *d;
951+
int max_offset;
952+
953+
if (rettv_dict_alloc(rettv) == FAIL)
954+
return;
955+
d = rettv->vval.v_dict;
956+
957+
max_offset = tpl_total_rows - Rows;
958+
if (max_offset < 0)
959+
max_offset = 0;
960+
961+
dict_add_string(d, "align",
962+
(char_u *)(tpl_align == ALIGN_RIGHT ? "right" : "left"));
963+
dict_add_number(d, "columns", tabpanel_width());
964+
dict_add_bool(d, "scrollbar", tpl_scrollbar);
965+
dict_add_number(d, "offset", tpl_scroll_offset);
966+
dict_add_number(d, "total", tpl_total_rows);
967+
dict_add_number(d, "max_offset", max_offset);
968+
}
969+
970+
/*
971+
* "tabpanel_scroll()" function
972+
*/
973+
void
974+
f_tabpanel_scroll(typval_T *argvars, typval_T *rettv)
975+
{
976+
varnumber_T n;
977+
int absolute = 0;
978+
bool changed;
979+
980+
rettv->v_type = VAR_BOOL;
981+
rettv->vval.v_number = VVAL_FALSE;
982+
983+
if (in_vim9script()
984+
&& (check_for_number_arg(argvars, 0) == FAIL
985+
|| check_for_opt_dict_arg(argvars, 1) == FAIL))
986+
return;
987+
988+
n = tv_get_number_chk(&argvars[0], NULL);
989+
if (argvars[1].v_type != VAR_UNKNOWN)
990+
{
991+
if (argvars[1].v_type != VAR_DICT || argvars[1].vval.v_dict == NULL)
992+
{
993+
emsg(_(e_dictionary_required));
994+
return;
995+
}
996+
absolute = dict_get_bool(argvars[1].vval.v_dict, "absolute", FALSE);
997+
}
998+
999+
// Clamp to int range to avoid signed overflow when casting and negating.
1000+
if (n > INT_MAX)
1001+
n = INT_MAX;
1002+
else if (n < -INT_MAX)
1003+
n = -INT_MAX;
1004+
1005+
if (absolute)
1006+
changed = tabpanel_set_offset((int)n);
1007+
else
1008+
changed = tabpanel_scroll(n >= 0 ? 1 : -1,
1009+
(int)(n >= 0 ? n : -n));
1010+
1011+
rettv->vval.v_number = changed ? VVAL_TRUE : VVAL_FALSE;
1012+
}
1013+
9161014
#endif // FEAT_TABPANEL

‎src/testdir/test_tabpanel.vim‎

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,4 +1129,81 @@ func Test_tabpanel_empty()
11291129
set tabpanel&
11301130
endfunc
11311131

1132+
func Test_tabpanel_getinfo_and_scroll()
1133+
CheckScreendump
1134+
1135+
let lines =<< trim END
1136+
set showtabpanel=2 tabpanelopt=columns:20
1137+
set showtabline=0
1138+
for i in range(40)
1139+
tabnew
1140+
endfor
1141+
tabfirst
1142+
redraw
1143+
func WriteResult(label)
1144+
call writefile([a:label, string(tabpanel_getinfo())], 'Xresult', 'a')
1145+
endfunc
1146+
call delete('Xresult')
1147+
call WriteResult('initial')
1148+
let g:r1 = tabpanel_scroll(1)
1149+
redraw
1150+
call WriteResult('after_scroll_1')
1151+
call tabpanel_scroll(-10)
1152+
redraw
1153+
call WriteResult('after_scroll_minus10')
1154+
let g:max = tabpanel_getinfo().max_offset
1155+
call tabpanel_scroll(g:max, #{absolute: 1})
1156+
redraw
1157+
call WriteResult('after_abs_max')
1158+
call tabpanel_scroll(99999, #{absolute: 1})
1159+
redraw
1160+
call WriteResult('after_abs_overflow')
1161+
call tabpanel_scroll(-5, #{absolute: 1})
1162+
redraw
1163+
call WriteResult('after_abs_negative')
1164+
let g:r_zero = tabpanel_scroll(0, #{absolute: 1})
1165+
let g:r_neg_at_zero = tabpanel_scroll(-1)
1166+
call writefile([
1167+
\ 'r1=' .. g:r1,
1168+
\ 'max=' .. g:max,
1169+
\ 'r_zero=' .. g:r_zero,
1170+
\ 'r_neg_at_zero=' .. g:r_neg_at_zero,
1171+
\ ], 'Xflags')
1172+
END
1173+
call writefile(lines, 'XTest_tabpanel_getinfo', 'D')
1174+
1175+
let buf = RunVimInTerminal('-S XTest_tabpanel_getinfo', {'rows': 10, 'cols': 45})
1176+
call WaitForAssert({-> assert_true(filereadable('Xflags'))})
1177+
call StopVimInTerminal(buf)
1178+
1179+
let results = readfile('Xresult')
1180+
let flags = readfile('Xflags')
1181+
call delete('Xresult')
1182+
call delete('Xflags')
1183+
1184+
" Parse [label, dict] pairs.
1185+
let info = {}
1186+
for i in range(0, len(results) - 1, 2)
1187+
let info[results[i]] = eval(results[i + 1])
1188+
endfor
1189+
1190+
call assert_equal('left', info.initial.align)
1191+
call assert_equal(20, info.initial.columns)
1192+
call assert_false(has_key(info.initial, 'scroll'))
1193+
call assert_equal(0, info.initial.offset)
1194+
call assert_true(info.initial.total > 0)
1195+
call assert_true(info.initial.max_offset > 0)
1196+
1197+
call assert_equal(1, info.after_scroll_1.offset)
1198+
call assert_equal(0, info.after_scroll_minus10.offset)
1199+
1200+
let max = info.initial.max_offset
1201+
call assert_equal(max, info.after_abs_max.offset)
1202+
call assert_equal(max, info.after_abs_overflow.offset)
1203+
call assert_equal(0, info.after_abs_negative.offset)
1204+
1205+
call assert_equal(['r1=v:true', 'max=' .. max,
1206+
\ 'r_zero=v:false', 'r_neg_at_zero=v:false'], flags)
1207+
endfunc
1208+
11321209
" vim: shiftwidth=2 sts=2 expandtab

‎src/version.c‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,8 @@ static char *(features[]) =
729729

730730
static int included_patches[] =
731731
{ /* Add new patch number below this line */
732+
/**/
733+
411,
732734
/**/
733735
410,
734736
/**/

0 commit comments

Comments
 (0)