patch 9.2.0411: tabpanel: no Vim script functions for the tabpanel

Commit: 
https://github.com/vim/vim/commit/e7745b7cbf8fa4433083d2565af72e9ffca5026d
Author: Yasuhiro Matsumoto <[email protected]>
Date:   Tue Apr 28 20:25:30 2026 +0000

    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 <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 67ebc70f9..b294b6388 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -1,4 +1,4 @@
-*builtin.txt*  For Vim version 9.2.  Last change: 2026 Apr 27
+*builtin.txt*  For Vim version 9.2.  Last change: 2026 Apr 28
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -705,6 +705,8 @@ tabpagebuflist([{arg}])             List    list of buffer 
numbers in tab page
 tabpagenr([{arg}])             Number  number of current or last tab page
 tabpagewinnr({tabarg} [, {arg}])
                                Number  number of current window in tab page
+tabpanel_getinfo()             Dict    get current state of the tabpanel
+tabpanel_scroll({n} [, {opts}])        Bool    scroll the tabpanel
 tagfiles()                     List    tags files used
 taglist({expr} [, {filename}]) List    list of tags matching {expr}
 tan({expr})                    Float   tangent of {expr}
@@ -11884,6 +11886,38 @@ tabpagewinnr({tabarg} [, {arg}])                       
*tabpagewinnr()*
                Return type: |Number|
 
 
+tabpanel_getinfo()                                     *tabpanel_getinfo()*
+               Return a |Dictionary| describing the current state of the
+               tabpanel (see |tabpanel|).  The dictionary has these keys:
+                       align           "left" or "right"
+                       columns         width in screen columns
+                       scrollbar       |TRUE| if a scrollbar is shown
+                       offset          current scroll offset in rows
+                       total           total number of rows rendered
+                       max_offset      largest valid value for "offset"
+
+               The "total" and "max_offset" values are only accurate after
+               the tabpanel has been drawn at least once.
+
+               Return type: dict<any>
+
+
+tabpanel_scroll({n} [, {opts}])                                
*tabpanel_scroll()*
+               Scroll the tabpanel by {n} rows.  Positive values scroll down
+               (later tabs become visible), negative values scroll up.  The
+               new offset is clamped to the valid range.
+
+               When {opts} is a |Dictionary| and its "absolute" entry is
+               |TRUE|, {n} is used as the new absolute scroll offset
+               instead of a delta.
+
+               Returns |TRUE| if the scroll offset changed, |FALSE|
+               otherwise (for example when the tabpanel is not shown, or
+               the offset is already at the requested value).
+
+               Return type: |vim9-boolean|
+
+
 tagfiles()                                             *tagfiles()*
                Returns a |List| with the file names used to search for tags
                for the current buffer.  This is the 'tags' option expanded.
diff --git a/runtime/doc/tags b/runtime/doc/tags
index 6376edad2..a0689193a 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -10896,6 +10896,8 @@ tabpagewinnr()  builtin.txt     /*tabpagewinnr()*
 tabpanel       tabpage.txt     /*tabpanel*
 tabpanel-mouse tabpage.txt     /*tabpanel-mouse*
 tabpanel-scroll        tabpage.txt     /*tabpanel-scroll*
+tabpanel_getinfo()     builtin.txt     /*tabpanel_getinfo()*
+tabpanel_scroll()      builtin.txt     /*tabpanel_scroll()*
 tag    tagsrch.txt     /*tag*
 tag-!  tagsrch.txt     /*tag-!*
 tag-binary-search      tagsrch.txt     /*tag-binary-search*
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index e6ca80457..560f01c70 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -1,4 +1,4 @@
-*usr_41.txt*   For Vim version 9.2.  Last change: 2026 Feb 16
+*usr_41.txt*   For Vim version 9.2.  Last change: 2026 Apr 28
 
 
                     VIM USER MANUAL    by Bram Moolenaar
@@ -1081,6 +1081,8 @@ Buffers, windows and the argument list:
        tabpagebuflist()        return List of buffers in a tab page
        tabpagenr()             get the number of a tab page
        tabpagewinnr()          like winnr() for a specified tab page
+       tabpanel_getinfo()      get current state of the tabpanel
+       tabpanel_scroll()       scroll the tabpanel
        winnr()                 get the window number for the current window
        bufwinid()              get the window ID of a specific buffer
        bufwinnr()              get the window number of a specific buffer
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index cc2f5f2de..6b8d97b41 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -52651,6 +52651,8 @@ Various syntax, indent and other plugins were added.
 Functions: ~
 
 |ch_listen()|          listen on {address}
+|tabpanel_getinfo()|   get current state of the |tabpanel|
+|tabpanel_scroll()|    scroll the |tabpanel|
 
 Autocommands: ~
 
diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim
index 2906d07cd..baa574138 100644
--- a/runtime/syntax/vim.vim
+++ b/runtime/syntax/vim.vim
@@ -2,7 +2,7 @@
 " Language:       Vim script
 " Maintainer:     Hirohito Higashi <h.east.727 ATMARK gmail.com>
 "         Doug Kearns <[email protected]>
-" Last Change:    2026 Apr 20
+" Last Change:    2026 Apr 28
 " Former Maintainer: Charles E. Campbell
 
 " DO NOT CHANGE DIRECTLY.
@@ -159,9 +159,9 @@ syn keyword vimFuncName contained getbufline getbufoneline 
getbufvar getcellpixe
 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
 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
 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
-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
-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
-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
+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
+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
+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
 
 " Predefined variable names {{{2
 " GEN_SYN_VIM: vimVarName, START_STR='syn keyword vimVimVarName contained', 
END_STR=''
diff --git a/src/evalfunc.c b/src/evalfunc.c
index 5026d1b13..ed0359263 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -1934,6 +1934,11 @@ typedef struct
 #else
 # define TERM_FUNC(name) NULL
 #endif
+#ifdef FEAT_TABPANEL
+# define TABPANEL_FUNC(name) name
+#else
+# define TABPANEL_FUNC(name) NULL
+#endif
 
 static const funcentry_T global_functions[] =
 {
@@ -2991,6 +2996,10 @@ static const funcentry_T global_functions[] =
                        ret_number,         f_tabpagenr},
     {"tabpagewinnr",   1, 2, FEARG_1,      arg2_number_string,
                        ret_number,         f_tabpagewinnr},
+    {"tabpanel_getinfo", 0, 0, 0,          NULL,
+                       ret_dict_any,       TABPANEL_FUNC(f_tabpanel_getinfo)},
+    {"tabpanel_scroll",        1, 2, FEARG_1,      arg2_number_dict_any,
+                       ret_bool,           TABPANEL_FUNC(f_tabpanel_scroll)},
     {"tagfiles",       0, 0, 0,            NULL,
                        ret_list_string,    f_tagfiles},
     {"taglist",                1, 2, FEARG_1,      arg2_string,
diff --git a/src/proto/tabpanel.pro b/src/proto/tabpanel.pro
index e6328b177..42f4dd8b6 100644
--- a/src/proto/tabpanel.pro
+++ b/src/proto/tabpanel.pro
@@ -9,4 +9,7 @@ bool mouse_on_tabpanel(void);
 bool mouse_on_tabpanel_scrollbar(void);
 bool tabpanel_drag_scrollbar(int screen_row);
 bool tabpanel_scroll(int dir, int count);
+bool tabpanel_set_offset(int offset);
+void f_tabpanel_getinfo(typval_T *argvars, typval_T *rettv);
+void f_tabpanel_scroll(typval_T *argvars, typval_T *rettv);
 /* vim: set ft=c : */
diff --git a/src/tabpanel.c b/src/tabpanel.c
index 34372fe60..1e833c7b6 100644
--- a/src/tabpanel.c
+++ b/src/tabpanel.c
@@ -913,4 +913,102 @@ tabpanel_scroll(int dir, int count)
     return true;
 }
 
+/*
+ * Set the tabpanel scroll offset to "offset" (clamped to the valid range).
+ * Returns true if the offset changed and a redraw was scheduled.
+ */
+    bool
+tabpanel_set_offset(int offset)
+{
+    int max_offset;
+
+    if (tabpanel_width() == 0)
+       return false;
+
+    max_offset = tpl_total_rows - Rows;
+    if (max_offset < 0)
+       max_offset = 0;
+
+    if (offset < 0)
+       offset = 0;
+    if (offset > max_offset)
+       offset = max_offset;
+    if (offset == tpl_scroll_offset)
+       return false;
+
+    tpl_scroll_offset = offset;
+    redraw_tabpanel = TRUE;
+    return true;
+}
+
+/*
+ * "tabpanel_getinfo()" function
+ */
+    void
+f_tabpanel_getinfo(typval_T *argvars UNUSED, typval_T *rettv)
+{
+    dict_T     *d;
+    int                max_offset;
+
+    if (rettv_dict_alloc(rettv) == FAIL)
+       return;
+    d = rettv->vval.v_dict;
+
+    max_offset = tpl_total_rows - Rows;
+    if (max_offset < 0)
+       max_offset = 0;
+
+    dict_add_string(d, "align",
+           (char_u *)(tpl_align == ALIGN_RIGHT ? "right" : "left"));
+    dict_add_number(d, "columns", tabpanel_width());
+    dict_add_bool(d, "scrollbar", tpl_scrollbar);
+    dict_add_number(d, "offset", tpl_scroll_offset);
+    dict_add_number(d, "total", tpl_total_rows);
+    dict_add_number(d, "max_offset", max_offset);
+}
+
+/*
+ * "tabpanel_scroll()" function
+ */
+    void
+f_tabpanel_scroll(typval_T *argvars, typval_T *rettv)
+{
+    varnumber_T        n;
+    int                absolute = 0;
+    bool       changed;
+
+    rettv->v_type = VAR_BOOL;
+    rettv->vval.v_number = VVAL_FALSE;
+
+    if (in_vim9script()
+           && (check_for_number_arg(argvars, 0) == FAIL
+               || check_for_opt_dict_arg(argvars, 1) == FAIL))
+       return;
+
+    n = tv_get_number_chk(&argvars[0], NULL);
+    if (argvars[1].v_type != VAR_UNKNOWN)
+    {
+       if (argvars[1].v_type != VAR_DICT || argvars[1].vval.v_dict == NULL)
+       {
+           emsg(_(e_dictionary_required));
+           return;
+       }
+       absolute = dict_get_bool(argvars[1].vval.v_dict, "absolute", FALSE);
+    }
+
+    // Clamp to int range to avoid signed overflow when casting and negating.
+    if (n > INT_MAX)
+       n = INT_MAX;
+    else if (n < -INT_MAX)
+       n = -INT_MAX;
+
+    if (absolute)
+       changed = tabpanel_set_offset((int)n);
+    else
+       changed = tabpanel_scroll(n >= 0 ? 1 : -1,
+                                 (int)(n >= 0 ? n : -n));
+
+    rettv->vval.v_number = changed ? VVAL_TRUE : VVAL_FALSE;
+}
+
 #endif // FEAT_TABPANEL
diff --git a/src/testdir/test_tabpanel.vim b/src/testdir/test_tabpanel.vim
index f7e6bdb08..b5bf678a1 100644
--- a/src/testdir/test_tabpanel.vim
+++ b/src/testdir/test_tabpanel.vim
@@ -1129,4 +1129,81 @@ func Test_tabpanel_empty()
   set tabpanel&
 endfunc
 
+func Test_tabpanel_getinfo_and_scroll()
+  CheckScreendump
+
+  let lines =<< trim END
+    set showtabpanel=2 tabpanelopt=columns:20
+    set showtabline=0
+    for i in range(40)
+      tabnew
+    endfor
+    tabfirst
+    redraw
+    func WriteResult(label)
+      call writefile([a:label, string(tabpanel_getinfo())], 'Xresult', 'a')
+    endfunc
+    call delete('Xresult')
+    call WriteResult('initial')
+    let g:r1 = tabpanel_scroll(1)
+    redraw
+    call WriteResult('after_scroll_1')
+    call tabpanel_scroll(-10)
+    redraw
+    call WriteResult('after_scroll_minus10')
+    let g:max = tabpanel_getinfo().max_offset
+    call tabpanel_scroll(g:max, #{absolute: 1})
+    redraw
+    call WriteResult('after_abs_max')
+    call tabpanel_scroll(99999, #{absolute: 1})
+    redraw
+    call WriteResult('after_abs_overflow')
+    call tabpanel_scroll(-5, #{absolute: 1})
+    redraw
+    call WriteResult('after_abs_negative')
+    let g:r_zero = tabpanel_scroll(0, #{absolute: 1})
+    let g:r_neg_at_zero = tabpanel_scroll(-1)
+    call writefile([
+          \ 'r1=' .. g:r1,
+          \ 'max=' .. g:max,
+          \ 'r_zero=' .. g:r_zero,
+          \ 'r_neg_at_zero=' .. g:r_neg_at_zero,
+          \ ], 'Xflags')
+  END
+  call writefile(lines, 'XTest_tabpanel_getinfo', 'D')
+
+  let buf = RunVimInTerminal('-S XTest_tabpanel_getinfo', {'rows': 10, 'cols': 
45})
+  call WaitForAssert({-> assert_true(filereadable('Xflags'))})
+  call StopVimInTerminal(buf)
+
+  let results = readfile('Xresult')
+  let flags = readfile('Xflags')
+  call delete('Xresult')
+  call delete('Xflags')
+
+  " Parse [label, dict] pairs.
+  let info = {}
+  for i in range(0, len(results) - 1, 2)
+    let info[results[i]] = eval(results[i + 1])
+  endfor
+
+  call assert_equal('left', info.initial.align)
+  call assert_equal(20, info.initial.columns)
+  call assert_false(has_key(info.initial, 'scroll'))
+  call assert_equal(0, info.initial.offset)
+  call assert_true(info.initial.total > 0)
+  call assert_true(info.initial.max_offset > 0)
+
+  call assert_equal(1, info.after_scroll_1.offset)
+  call assert_equal(0, info.after_scroll_minus10.offset)
+
+  let max = info.initial.max_offset
+  call assert_equal(max, info.after_abs_max.offset)
+  call assert_equal(max, info.after_abs_overflow.offset)
+  call assert_equal(0, info.after_abs_negative.offset)
+
+  call assert_equal(['r1=v:true', 'max=' .. max,
+        \ 'r_zero=v:false', 'r_neg_at_zero=v:false'], flags)
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index a19d721d7..d3ed29922 100644
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    411,
 /**/
     410,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1wHpIn-006ElC-Te%40256bit.org.

Raspunde prin e-mail lui