Compare commits

...

9 Commits

Author SHA1 Message Date
Luuk van Baal
bd0f3e6da5 patch 9.2.0413: Scrolling wrong with 'splitkeep' when changing 'cmdheight'
Problem:  Cursor is not adjusted when 'cmdheight' is changed to cover
          the cursor with 'splitkeep' ~= "cursor".
Solution: Handle window resize for 'splitkeep' after changing 'cmdheight'.
          Ensure previous window height is set when changing 'splitkeep'
          (Luuk van Baal).

closes: #20043

Signed-off-by: Luuk van Baal <luukvbaal@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
2026-04-28 21:14:50 +00:00
Hirohito Higashi
41c3379bdf patch 9.2.0412: channel: term_start() out_cb/err_cb no longer deliver raw chunks
Problem:  channel: term_start() out_cb/err_cb no longer deliver raw
          chunks (regression from patch 9.2.0224, breaks callers like
          vim-fugitive that parse multi-line output)
          (D. Ben Knoble, after v9.2.0224)
Solution: Remove the PTY-specific per-line splitting in
          may_invoke_callback() so RAW callbacks again receive the
          raw chunk as returned by read(), preserving embedded NL.
          If per-line handling is desired, the callback must split
          "msg" on NL and strip the trailing CR itself; document
          this behavior in term_start().  Replace
          Test_term_start_cb_per_line() with
          Test_term_start_cb_raw_chunk() to verify the raw-chunk
          contract.

fixes:  #20041
closes: #20045

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
2026-04-28 21:06:45 +00:00
Yasuhiro Matsumoto
e7745b7cbf 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>
2026-04-28 20:31:34 +00:00
Jesse Rosenstock
6146f3382f patch 9.2.0410: test suite races when run with parallel make
Problem:  Running "make test" with -jN causes spurious failures because
          the old-style tests share filenames (test.ok, test.out, X*,
          viminfo) in the working directory.
Solution: Add .NOTPARALLEL to the testdir Makefile to prevent parallel
          execution of tests (Jesse Rosenstock).

closes: #20082

Co-authored-by: Gemini
Signed-off-by: Jesse Rosenstock <jmr@google.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
2026-04-28 19:23:41 +00:00
Hirohito Higashi
30b4240730 runtime(doc): Update docs related to tabpanel
closes: #20083

Signed-off-by: Hirohito Higashi <h.east.727@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
2026-04-28 19:19:53 +00:00
glepnir
6cb4173294 patch 9.2.0409: memory leaks in copy_substring_from_pos()
Problem:  Memory leak in error path of copy_substring_from_pos().
Solution: Free the garray on OOM in copy_substring_from_pos()
          (glepnir).

closes: #20086

Signed-off-by: glepnir <glephunter@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
2026-04-28 19:14:22 +00:00
Jaehwang Jung
e47daed442 patch 9.2.0408: Insert-mode <Cmd> edits can corrupt undo
Problem:  A <Cmd> command in Insert mode can edit the current buffer,
          e.g., with setline(). That edit appends to the current undo
          block, but Insert mode does not know that the cursor line may
          need to be saved again before the next typed edit. If the next
          typed edit is a <BS> at the start of a line, it can join away
          the line that was changed by the <Cmd> command before Insert
          mode saves that updated line. The newest undo entry can then
          still refer to the joined-away line, so undo sees a range past
          the end of the buffer and fails with E438.
Solution: If a <Cmd> command in Insert mode changes the buffer, set
          ins_need_undo so stop_arrow() refreshes Insstart. This lets
          the next edit properly decide whether a new undo entry is
          needed (Jaehwang Jung)

closes: #20087
AI-assisted: Codex

Signed-off-by: Jaehwang Jung <tomtomjhj@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
2026-04-28 19:06:15 +00:00
Hirohito Higashi
2d43240659 patch 9.2.0407: tabpanel: A few issues with the tabpanel
Problem:  Several issues around the tabpanel scrollbar:
          1. :set tabpanelopt= completion did not offer "scroll" and
              "scrollbar".
          2. gt/gT and other tab switches did not update the scrollbar
              thumb; the current tab could move outside the visible
              panel range without the view following.
          3. When tpl_scroll_offset was at its maximum, the thumb's
              bottom did not reach the last screen row due to integer
              truncation in thumb_top (e.g. 31 tabs on 24 rows + :tablast
              left a one-row gap).
          4. For align:right the scrollbar was drawn on the panel's
              left edge (adjacent to the buffer area), which breaks the
              common convention that a vertical scrollbar sits on the
              right.
Solution: - Add "scroll" and "scrollbar" to the 'tabpanelopt' expansion
            list.  Cover the completion in test_options.vim and extend
            util/gen_opt_test.vim with the new valid/invalid values;
            drop the now-redundant acceptance test from
            test_tabpanel.vim.
          - In draw_tabpanel(), remember the last-drawn curtab and,
            when it changes, adjust tpl_scroll_offset so curtab_row
            falls inside [offset, offset + Rows).  Mouse wheel and
            drag leave curtab unchanged, so the user's chosen offset
            is preserved.
          - In draw_tabpanel_scrollbar(), compute thumb_top as
            (Rows - thumb_height) * tpl_scroll_offset
            / (tpl_total_rows - Rows), mirroring the mapping already
            used by tabpanel_drag_scrollbar().  This guarantees the
            thumb's bottom reaches the last row at the maximum offset.
          - In draw_tabpanel(), place the scrollbar at the tabpanel's
            right edge for both align:left and align:right (previously
            align:right put it on the panel's left edge next to the
            vertical separator).  For align:right this means the
            scrollbar now sits at the screen's right edge.
          - Update :h tabpanel-scroll to describe the new, align-
            independent placement.
          - Add Test_tabpanel_scrollbar_follows_curtab() and
            Test_tabpanel_scrollbar_reaches_bottom() to exercise the
            regressions fixed by items 2 and 3.

closes: #20052

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Hirohito Higashi <h.east.727@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
2026-04-27 21:16:56 +00:00
Shane Harper
7e76514896 patch 9.2.0406: VisualNOS not used when Wayland selection ownership lost
Problem:  VisualNOS not used when Wayland selection ownership lost
          (lilydjwg)
Solution: Don't require X_DISPLAY != NULL to use VisualNOS
          (Shane Harper).

fixes:   #19914
related: #19812
related: #19659
closes:  #20066

Signed-off-by: Shane Harper <shane@shaneharper.net>
Signed-off-by: Christian Brabandt <cb@256bit.org>
2026-04-27 19:58:46 +00:00
28 changed files with 590 additions and 211 deletions

View File

@@ -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.

View File

@@ -1,4 +1,4 @@
*channel.txt* For Vim version 9.2. Last change: 2026 Apr 15
*channel.txt* For Vim version 9.2. Last change: 2026 Apr 28
VIM REFERENCE MANUAL by Bram Moolenaar

View File

@@ -1,4 +1,4 @@
*options.txt* For Vim version 9.2. Last change: 2026 Apr 21
*options.txt* For Vim version 9.2. Last change: 2026 Apr 28
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -9052,41 +9052,22 @@ A jump table for the options with a short description can be found at |Q_op|.
Optional settings for the |tabpanel|, It can consist of the following
items. Items must be separated by a comma.
align:{text} Specifies the position of the tabpanel.
Currently supported positions are:
left left-side
align:{text} Specifies the position of the tabpanel.
Currently supported positions are:
left left-side (default)
right right-side
(default "left")
columns:{n} Number of columns of the tabpanel.
If this value is 0 or less than 'columns', the
tab panel will not be displayed.
(default 20)
scroll Enable mouse wheel scrolling over the tabpanel
area when the tab list exceeds the visible
screen height. The scroll step is controlled
by 'mousescroll'. When disabled (the default),
the tabpanel shows the page containing the
current tab, with no way to view tabs outside
that page.
scrollbar Reserve a one-column scrollbar in the tabpanel
showing the current scroll position. The
scrollbar uses the |hl-PmenuSbar| and
|hl-PmenuThumb| highlight groups for the track
and thumb respectively. Clicking on the
scrollbar column jumps the thumb to that
position; the thumb can also be dragged.
Implies "scroll".
vert Use a vertical separator for tabpanel.
The vertical separator character is taken from
"tpl_vert" in 'fillchars'.
(default off)
columns:{n} Number of columns of the tabpanel. (default 20)
If this value is 0 or less than 'columns', the
tabpanel will not be displayed.
scrollbar A one-column scrollbar is always displayed at the
right edge of the tabpanel, regardless of the
"align:" setting. (default off)
See |tabpanel-scroll|.
vert The vertical separator is drawn at the boundary
between the tabpanel and the buffer area.
(default off)
The character to be drawn uses "tpl_vert" from
'fillchars'.
Examples: >
:set tabpanelopt=columns:16,align:right
:set tabpanelopt=

View File

@@ -1,4 +1,4 @@
*syntax.txt* For Vim version 9.2. Last change: 2026 Apr 23
*syntax.txt* For Vim version 9.2. Last change: 2026 Apr 27
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -6155,7 +6155,8 @@ TitleBarNC Title bar for inactive Gui's window.
Visual Visual mode selection.
*hl-VisualNOS*
VisualNOS Visual mode selection when vim is "Not Owning the Selection".
Only X11 Gui's |gui-x11| and |xterm-clipboard| supports this.
Only X11 Gui's |gui-x11|, |xterm-clipboard| and |wayland-selections|
supports this.
*hl-WarningMsg*
WarningMsg Warning messages.
*hl-WildMenu*

View File

@@ -1,4 +1,4 @@
*tabpage.txt* For Vim version 9.2. Last change: 2026 Apr 26
*tabpage.txt* For Vim version 9.2. Last change: 2026 Apr 28
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -482,42 +482,37 @@ The vertical separator character is taken from "tpl_vert" in 'fillchars'.
You can customize the appearance of the tab page labels using the highlight
groups: |hl-TabPanel| |hl-TabPanelSel| |hl-TabPanelFill|
SCROLLING IN THE TABPANEL: *tabpanel-scroll*
SCROLLING IN THE TABPANEL *tabpanel-scroll*
When the total height of the tab page list exceeds the visible screen height,
the tabpanel by default displays the "page" that contains the current tab page
and offers no way to view tab pages outside that page.
mouse wheel events over the tabpanel area scroll the tab page list up or down.
The scroll step follows the 'mousescroll' setting. Wheel events inside the
tabpanel area are consumed by the tabpanel and do not trigger
|<ScrollWheelUp>| or |<ScrollWheelDown>| mappings.
To make the tabpanel scrollable, add "scroll" to 'tabpanelopt': >
:set tabpanelopt+=scroll
The current tab page is always brought into view: when the selected tab page
changes (by |gt|, |gT|, |:tabnext| etc.), the panel scrolls so the current
entry is visible.
With "scroll" enabled, mouse wheel events over the tabpanel area scroll the
tab page list up or down. The scroll step follows the 'mousescroll' setting.
Wheel events inside the tabpanel area are consumed by the tabpanel and do not
trigger |<ScrollWheelUp>| or |<ScrollWheelDown>| mappings.
To additionally show a vertical scrollbar indicating the current scroll
position, use "scrollbar": >
To show a vertical scrollbar indicating the current scroll position, add
"scrollbar" to 'tabpanelopt': >
:set tabpanelopt+=scrollbar
The "scrollbar" value implies "scroll". A one-column scrollbar is reserved at
the edge of the tabpanel; clicking on the scrollbar column moves the thumb to
the click position, and the thumb can be dragged to scroll continuously.
When "vert" is combined with "scrollbar", the scrollbar is drawn next to the
vertical separator, on the panel side.
A one-column scrollbar is always displayed at the right edge of the tabpanel,
regardless of the "align:" setting in 'tabpanelopt'. Clicking on the
scrollbar column moves the thumb to the click position, and the thumb can be
dragged to scroll continuously.
The scrollbar uses the |hl-PmenuSbar| highlight group for the track and
|hl-PmenuThumb| for the thumb.
The scroll offset is remembered across redraws but is reset when "scroll" or
"scrollbar" is toggled off and back on.
The scroll offset is remembered across redraws.
MOUSE CLICKS IN THE TABPANEL: *tabpanel-mouse*
MOUSE CLICKS IN THE TABPANEL *tabpanel-mouse*
A left click on a row in the tabpanel selects the tab page that the row
belongs to. Unlike the tabline, a double click in the tabpanel does not
open a new, empty tab page; it is treated the same as a single click.
belongs to. Unlike the tabline, a double click in the tabpanel does not open
a new, empty tab page; it is treated the same as a single click.
For finer-grained control, the 'tabpanel' value may contain |stl-%[FuncName]|
click regions. Clicks on those regions are dispatched to the callback

View File

@@ -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*

View File

@@ -965,6 +965,20 @@ term_start({cmd} [, {options}]) *term_start()*
input and one output handle, with no separate handle for
stderr.
Note: term_start() always uses RAW mode for its callbacks.
"out_cb" and "err_cb" receive the raw chunk of data as read
from the OS. A single callback invocation may contain
multiple lines separated by NL, and (for stdout via a pty)
each line may have a trailing CR from the line discipline
(ONLCR). If per-line handling is desired, the callback must
split "msg" on NL and strip the trailing CR itself.
Example: >
func Handle(ch, msg)
for line in split(a:msg, "\n")
echom substitute(line, '\r$', '', '')
endfor
endfunc
<
There are extra options:
"term_name" name to use for the buffer name, instead
of the command name.

View File

@@ -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

View File

@@ -1,4 +1,4 @@
*version9.txt* For Vim version 9.2. Last change: 2026 Apr 26
*version9.txt* For Vim version 9.2. Last change: 2026 Apr 28
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -52616,8 +52616,8 @@ Other ~
- Allow mouse clickable regions in the 'statusline', 'tabline' and the
'tabpanel' using the |stl-%[FuncName]| atom.
- Enable reflow support in the |:terminal|.
- Added "scroll" and "scrollbar" sub-options to 'tabpanelopt' so the tabpanel
can scroll when the tab page list exceeds the visible screen height.
- Enabled scrolling for the tabpanel when the tab page list exceeds the screen
height. Also added the "scrollbar" sub-option to 'tabpanelopt'.
Platform specific ~
-----------------
@@ -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: ~

View File

@@ -2,7 +2,7 @@
" Language: Vim script
" Maintainer: Hirohito Higashi <h.east.727 ATMARK gmail.com>
" Doug Kearns <dougkearns@gmail.com>
" 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=''

View File

@@ -3510,46 +3510,7 @@ may_invoke_callback(channel_T *channel, ch_part_T part)
// invoke the channel callback
ch_log(channel, "Invoking channel callback %s",
(char *)callback->cb_name);
#ifdef FEAT_TERMINAL
// For a terminal job in RAW mode (term_start()), split msg on
// NL and invoke the callback once per line with trailing CR
// stripped. This ensures out_cb/err_cb receive one line at a
// time regardless of how much data arrives in a single read.
if (ch_mode == CH_MODE_RAW && msg != NULL
&& channel->ch_job != NULL
&& channel->ch_job->jv_tty_out != NULL)
{
char_u *cp = msg;
char_u *nl;
while ((nl = vim_strchr(cp, NL)) != NULL)
{
long_u len = (long_u)(nl - cp);
if (len > 0 && cp[len - 1] == CAR)
--len;
argv[1].vval.v_string = vim_strnsave(cp, len);
if (argv[1].vval.v_string != NULL)
invoke_callback(channel, callback, argv);
vim_free(argv[1].vval.v_string);
cp = nl + 1;
}
if (*cp != NUL)
{
long_u len = STRLEN(cp);
if (len > 0 && cp[len - 1] == CAR)
--len;
argv[1].vval.v_string = vim_strnsave(cp, len);
if (argv[1].vval.v_string != NULL)
invoke_callback(channel, callback, argv);
vim_free(argv[1].vval.v_string);
}
argv[1].vval.v_string = msg;
}
else
#endif
invoke_callback(channel, callback, argv);
invoke_callback(channel, callback, argv);
}
}
}

View File

@@ -4771,7 +4771,7 @@ copy_substring_from_pos(pos_T *start, pos_T *end, char_u **match,
segment_len = is_single_line ? (end->col - start->col)
: (int)(ml_get_len(start->lnum) - start->col);
if (ga_grow(&ga, segment_len + 2) != OK)
return FAIL;
goto fail;
ga_concat_len(&ga, start_ptr, segment_len);
if (!is_single_line)
@@ -4806,13 +4806,13 @@ copy_substring_from_pos(pos_T *start, pos_T *end, char_u **match,
word_end = find_word_end(end_line + end->col);
segment_len = (int)(word_end - end_line);
if (ga_grow(&ga, segment_len) != OK)
return FAIL;
goto fail;
ga_concat_len(&ga, end_line + (is_single_line ? end->col : 0),
segment_len - (is_single_line ? end->col : 0));
// Null-terminate
if (ga_grow(&ga, 1) != OK)
return FAIL;
goto fail;
ga_append(&ga, NUL);
*match = (char_u *)ga.ga_data;
@@ -4820,6 +4820,10 @@ copy_substring_from_pos(pos_T *start, pos_T *end, char_u **match,
match_end->col = segment_len;
return OK;
fail:
ga_clear(&ga);
return FAIL;
}
/*

View File

@@ -1464,12 +1464,11 @@ win_line(
{
area_highlighting = TRUE;
vi_attr = HL_ATTR(HLF_V);
#if defined(FEAT_CLIPBOARD) && defined(FEAT_X11)
if (X_DISPLAY &&
((clip_star.available && !clip_star.owned
#if defined(FEAT_CLIPBOARD) && (defined(FEAT_X11) || defined(FEAT_WAYLAND_CLIPBOARD))
if ((clip_star.available && !clip_star.owned
&& clip_isautosel_star())
|| (clip_plus.available && !clip_plus.owned
&& clip_isautosel_plus())))
|| (clip_plus.available && !clip_plus.owned
&& clip_isautosel_plus()))
vi_attr = HL_ATTR(HLF_VNC);
#endif
}

View File

@@ -1132,6 +1132,10 @@ doESCkey:
case K_COMMAND: // <Cmd>command<CR>
case K_SCRIPT_COMMAND: // <ScriptCmd>command<CR>
{
bufref_T save_curbuf;
varnumber_T tick = CHANGEDTICK(curbuf);
set_bufref(&save_curbuf, curbuf);
do_cmdkey_command(c, 0);
#ifdef FEAT_TERMINAL
@@ -1139,10 +1143,15 @@ doESCkey:
// Started a terminal that gets the input, exit Insert mode.
goto doESCkey;
#endif
if (curbuf->b_u_synced)
// The command caused undo to be synced. Need to save the
// line for undo before inserting the next char.
if (curbuf->b_u_synced
|| (bufref_valid(&save_curbuf)
&& curbuf == save_curbuf.br_buf
&& tick != CHANGEDTICK(curbuf)))
{
// The command synced undo or changed this buffer.
// Save the cursor line before the next typed edit.
ins_need_undo = TRUE;
}
}
break;
@@ -2503,7 +2512,9 @@ stop_arrow(void)
{
if (u_save_cursor() == OK)
{
// A command or event may have moved the cursor after syncing undo.
// A command or event may have moved the cursor or edited the
// buffer. Update Insstart so that later edits can properly decide
// whether an extra undo entry is needed.
Insstart = curwin->w_cursor;
Insstart_textlen = (colnr_T)linetabsize_str(ml_get_curline());
ins_need_undo = FALSE;

View File

@@ -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,

View File

@@ -30,7 +30,7 @@ static char *(p_briopt_values[]) = {"shift:", "min:", "sbr", "list:", "column:",
#endif
#if defined(FEAT_TABPANEL)
// Note: Keep this in sync with tabpanelopt_changed()
static char *(p_tplo_values[]) = {"align:", "columns:", "vert", NULL};
static char *(p_tplo_values[]) = {"align:", "columns:", "scrollbar", "vert", NULL};
static char *(p_tplo_align_values[]) = {"left", "right", NULL};
#endif
#if defined(FEAT_DIFF)
@@ -4384,6 +4384,10 @@ expand_set_spellsuggest(optexpand_T *args, int *numMatches, char_u ***matches)
char *
did_set_splitkeep(optset_T *args UNUSED)
{
win_T *wp;
tabpage_T *tp;
FOR_ALL_TAB_WINDOWS(tp, wp)
wp->w_prev_height = wp->w_height;
return did_set_opt_strings(p_spk, p_spk_values, FALSE);
}

View File

@@ -1,5 +1,6 @@
/* tabpanel.c */
int tabpanelopt_changed(void);
void tabpanel_forget_tabpage(const tabpage_T *tp);
int tabpanel_width(void);
int tabpanel_leftcol(void);
void draw_tabpanel(void);
@@ -8,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 : */

View File

@@ -43,11 +43,11 @@ static int opt_scope = OPT_LOCAL;
static int tpl_align = ALIGN_LEFT;
static int tpl_columns = 20;
static bool tpl_is_vert = false;
static bool tpl_scroll = false;
static bool tpl_scrollbar = false;
static int tpl_scroll_offset = 0;
static int tpl_total_rows = 0;
static int tpl_scrollbar_col = -1; // screen column of scrollbar, -1 if none
static int tpl_scrollbar_col = -1; // screen column of scrollbar, -1 if none
static tabpage_T *tpl_last_curtab = NULL; // last curtab seen by draw_tabpanel
typedef struct {
win_T *wp;
@@ -69,7 +69,6 @@ tabpanelopt_changed(void)
int new_align = ALIGN_LEFT;
long new_columns = 20;
bool new_is_vert = false;
bool new_scroll = false;
bool new_scrollbar = false;
p = p_tplo;
@@ -107,12 +106,6 @@ tabpanelopt_changed(void)
{
p += 9;
new_scrollbar = true;
new_scroll = true;
}
else if (STRNCMP(p, "scroll", 6) == 0)
{
p += 6;
new_scroll = true;
}
if (*p != ',' && *p != NUL)
@@ -124,15 +117,26 @@ tabpanelopt_changed(void)
tpl_align = new_align;
tpl_columns = new_columns;
tpl_is_vert = new_is_vert;
if (tpl_scroll != new_scroll)
tpl_scroll_offset = 0;
tpl_scroll = new_scroll;
tpl_scrollbar = new_scrollbar;
// Re-center the current tab on the next redraw.
tpl_last_curtab = NULL;
shell_new_columns();
return OK;
}
/*
* Drop any internal reference to "tp", so draw_tabpanel() never compares
* against a dangling pointer after the tabpage has been freed.
*/
void
tabpanel_forget_tabpage(const tabpage_T *tp)
{
if (tpl_last_curtab == tp)
tpl_last_curtab = NULL;
}
/*
* Return the width of tabpanel.
*/
@@ -264,6 +268,31 @@ tabpanel_append_click_regions(
}
}
/*
* Ensure the current tab is visible by adjusting tpl_scroll_offset when
* the selected tab has changed since the previous redraw. Mouse wheel or
* scrollbar drag operations leave curtab unchanged, so the user's chosen
* offset is preserved in those cases.
*/
static void
follow_curtab_if_needed(int curtab_row)
{
if (Rows <= 0 || curtab == tpl_last_curtab)
return;
if (curtab_row < tpl_scroll_offset)
tpl_scroll_offset = curtab_row;
else if (curtab_row >= tpl_scroll_offset + Rows)
tpl_scroll_offset = curtab_row - Rows + 1;
int max_offset = tpl_total_rows > Rows ? tpl_total_rows - Rows : 0;
if (tpl_scroll_offset < 0)
tpl_scroll_offset = 0;
else if (tpl_scroll_offset > max_offset)
tpl_scroll_offset = max_offset;
}
/*
* draw the tabpanel.
*/
@@ -293,30 +322,36 @@ draw_tabpanel(void)
int sb_len = tpl_scrollbar ? SCROLL_LEN : 0;
int sb_screen_col = -1;
// The scrollbar is always placed at the right edge of the tabpanel,
// regardless of 'align'. The vertical separator sits at the panel's
// boundary with the buffer area (left edge for align:right, right edge
// for align:left).
if (tpl_is_vert)
{
if (is_right)
{
// draw main contents in tabpanel
do_by_tplmode(TPLMODE_GET_CURTAB_ROW, VERT_LEN + sb_len,
maxwidth - VERT_LEN, &curtab_row, NULL);
do_by_tplmode(TPLMODE_REDRAW, VERT_LEN + sb_len, maxwidth,
// Panel on the right: vert at panel's left edge, scrollbar at
// panel's right edge (= screen's right edge).
do_by_tplmode(TPLMODE_GET_CURTAB_ROW, VERT_LEN,
maxwidth - sb_len, &curtab_row, NULL);
follow_curtab_if_needed(curtab_row);
do_by_tplmode(TPLMODE_REDRAW, VERT_LEN, maxwidth - sb_len,
&curtab_row, NULL);
// draw vert separator in tabpanel
for (vsrow = 0; vsrow < Rows; vsrow++)
screen_putchar(curwin->w_fill_chars.tpl_vert, vsrow,
topframe->fr_width, vs_attr);
if (tpl_scrollbar)
sb_screen_col = topframe->fr_width + VERT_LEN;
sb_screen_col = topframe->fr_width + maxwidth - SCROLL_LEN;
}
else
{
// draw main contents in tabpanel
// Panel on the left: scrollbar just left of vert, vert at
// panel's right edge (boundary with buffer).
do_by_tplmode(TPLMODE_GET_CURTAB_ROW, 0,
maxwidth - VERT_LEN - sb_len, &curtab_row, NULL);
follow_curtab_if_needed(curtab_row);
do_by_tplmode(TPLMODE_REDRAW, 0, maxwidth - VERT_LEN - sb_len,
&curtab_row, NULL);
// draw vert separator in tabpanel
for (vsrow = 0; vsrow < Rows; vsrow++)
screen_putchar(curwin->w_fill_chars.tpl_vert, vsrow,
maxwidth - VERT_LEN, vs_attr);
@@ -328,16 +363,20 @@ draw_tabpanel(void)
{
if (is_right)
{
do_by_tplmode(TPLMODE_GET_CURTAB_ROW, sb_len, maxwidth,
// Panel on the right, no vert: scrollbar at screen's right edge.
do_by_tplmode(TPLMODE_GET_CURTAB_ROW, 0, maxwidth - sb_len,
&curtab_row, NULL);
follow_curtab_if_needed(curtab_row);
do_by_tplmode(TPLMODE_REDRAW, 0, maxwidth - sb_len,
&curtab_row, NULL);
do_by_tplmode(TPLMODE_REDRAW, sb_len, maxwidth, &curtab_row, NULL);
if (tpl_scrollbar)
sb_screen_col = topframe->fr_width;
sb_screen_col = topframe->fr_width + maxwidth - SCROLL_LEN;
}
else
{
do_by_tplmode(TPLMODE_GET_CURTAB_ROW, 0, maxwidth - sb_len,
&curtab_row, NULL);
follow_curtab_if_needed(curtab_row);
do_by_tplmode(TPLMODE_REDRAW, 0, maxwidth - sb_len,
&curtab_row, NULL);
if (tpl_scrollbar)
@@ -354,6 +393,7 @@ draw_tabpanel(void)
// A user function may reset KeyTyped, restore it.
KeyTyped = saved_KeyTyped;
tpl_last_curtab = curtab;
redraw_tabpanel = FALSE;
}
@@ -606,13 +646,7 @@ do_by_tplmode(
args.col_end = col_end;
if (tplmode != TPLMODE_GET_CURTAB_ROW && args.maxrow > 0)
{
if (tpl_scroll)
args.offsetrow = tpl_scroll_offset;
else
while (args.offsetrow + args.maxrow <= *pcurtab_row)
args.offsetrow += args.maxrow;
}
args.offsetrow = tpl_scroll_offset;
tp = first_tabpage;
@@ -632,16 +666,9 @@ do_by_tplmode(
{
args.attr = attr_tpls;
if (tplmode == TPLMODE_GET_CURTAB_ROW)
{
// Capture the row of the current tab and keep iterating so
// tpl_total_rows receives the true content height below.
*pcurtab_row = row;
// When scroll mode is active keep iterating so tpl_total_rows
// receives the true content height; otherwise bail out early.
if (!tpl_scroll)
{
do_unlet((char_u *)"g:actual_curtabpage", TRUE);
break;
}
}
}
else
args.attr = attr_tpl;
@@ -742,7 +769,7 @@ do_by_tplmode(
// Capture the true content height during the GET_CURTAB_ROW pass, which
// ignores maxrow and therefore walks every tab. REDRAW stops at the
// visible edge so its "row" is clamped and unusable here.
if (tplmode == TPLMODE_GET_CURTAB_ROW && tpl_scroll)
if (tplmode == TPLMODE_GET_CURTAB_ROW)
tpl_total_rows = row;
}
@@ -761,10 +788,21 @@ draw_tabpanel_scrollbar(int screen_col)
if (tpl_total_rows > Rows && Rows > 0)
{
int max_offset = tpl_total_rows - Rows;
int track_range;
thumb_height = Rows * Rows / tpl_total_rows;
if (thumb_height < 1)
thumb_height = 1;
thumb_top = Rows * tpl_scroll_offset / tpl_total_rows;
// Map tpl_scroll_offset onto the track: at offset 0 the thumb's top
// is at row 0, at the maximum offset its bottom reaches the last
// row. This is the exact inverse of tabpanel_drag_scrollbar().
track_range = Rows - thumb_height;
if (track_range > 0 && max_offset > 0)
thumb_top = track_range * tpl_scroll_offset / max_offset;
else
thumb_top = 0;
if (thumb_top + thumb_height > Rows)
thumb_top = Rows - thumb_height;
if (thumb_top < 0)
@@ -848,7 +886,6 @@ tabpanel_drag_scrollbar(int screen_row)
/*
* Scroll the tabpanel by 'count' rows in direction 'dir' (1 = down, -1 = up).
* Returns true if the offset changed and a redraw was scheduled.
* Has no effect unless 'tabpanelopt' contains "scroll".
*/
bool
tabpanel_scroll(int dir, int count)
@@ -856,7 +893,7 @@ tabpanel_scroll(int dir, int count)
int max_offset;
int new_offset;
if (!tpl_scroll || tabpanel_width() == 0)
if (tabpanel_width() == 0)
return false;
max_offset = tpl_total_rows - Rows;
@@ -876,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

View File

@@ -2,6 +2,11 @@
# Makefile to run all tests for Vim
#
# The old-style tests (*.in) use shared filenames in the working directory
# (test.ok, test.out, X*, viminfo), so running them in parallel causes races
# and spurious failures.
.NOTPARALLEL:
# Use console or GUI.
VIMPROG = ../vim
XXDPROG = ../xxd/xxd

View File

@@ -1,10 +1,10 @@
|1+2&#ffffff0@1|:|t|a|b| @3> +0&&@34
|5+8#0000001#e0e0e08|:|t|a|b| @4> +0#0000000#ffffff0@34
|6+8#0000001#e0e0e08|:|t|a|b| @4|~+0#4040ff13#ffffff0| @33
|7+8#0000001#e0e0e08|:|t|a|b| @4|~+0#4040ff13#ffffff0| @33
|8+8#0000001#e0e0e08|:|t|a|b| @4|~+0#4040ff13#ffffff0| @33
|9+8#0000001#e0e0e08|:|t|a|b| @4|~+0#4040ff13#ffffff0| @33
|1+8#0000001#e0e0e08|0|:|t|a|b| @3|~+0#4040ff13#ffffff0| @33
|1+2#0000000&@1|:|t|a|b| @3|~+0#4040ff13&| @33
|1+8#0000001#e0e0e08|2|:|t|a|b| @3|~+0#4040ff13#ffffff0| @33
|1+8#0000001#e0e0e08|3|:|t|a|b| @3|~+0#4040ff13#ffffff0| @33
|1+8#0000001#e0e0e08|4|:|t|a|b| @3|~+0#4040ff13#ffffff0| @33
|1+8#0000001#e0e0e08|5|:|t|a|b| @3|~+0#4040ff13#ffffff0| @33
|1+8#0000001#e0e0e08|6|:|t|a|b| @3|~+0#4040ff13#ffffff0| @33
|1+8#0000001#e0e0e08|7|:|t|a|b| @3|~+0#4040ff13#ffffff0| @33
|1+8#0000001#e0e0e08|8|:|t|a|b| @3|~+0#4040ff13#ffffff0| @33
|1+8#0000001#e0e0e08|9|:|t|a|b| @3|~+0#4040ff13#ffffff0| @33
|2+8#0000001#e0e0e08|0|:|t|a|b| @3|:+0#0000000#ffffff0|t|a|b|n|e|x|t| |-|3| @6|0|,|0|-|1| @7|A|l@1|
|1+8#0000001#e0e0e08|4|:|t|a|b| @3|:+0#0000000#ffffff0|t|a|b|n|e|x|t| |-|3| @6|0|,|0|-|1| @7|A|l@1|

View File

@@ -2933,13 +2933,15 @@ func Test_error_callback_terminal()
unlet! g:out g:error
endfunc
" Verify that term_start() with out_cb/err_cb delivers one line per callback
" call (no embedded newlines, no trailing CR), matching the user's expectation.
func Test_term_start_cb_per_line()
" Verify that term_start() with out_cb/err_cb delivers data in RAW mode,
" preserving embedded newlines in the raw chunk received from read(). If
" per-line handling is desired, it is the callback's responsibility to split
" on NL and strip the trailing CR.
func Test_term_start_cb_raw_chunk()
CheckUnix
CheckFeature terminal
let g:Ch_msgs = []
let script_file = 'Xterm_cb_per_line.sh'
let script_file = 'Xterm_cb_raw_chunk.sh'
call writefile(["#!/bin/sh",
\ "printf 'err:1\\nerr:2\\n' >&2",
\ "printf 'out:3\\n'"], script_file, 'D')
@@ -2947,10 +2949,16 @@ func Test_term_start_cb_per_line()
let ptybuf = term_start('./' .. script_file, {
\ 'out_cb': {ch, msg -> add(g:Ch_msgs, msg)},
\ 'err_cb': {ch, msg -> add(g:Ch_msgs, msg)}})
call WaitForAssert({-> assert_equal(3, len(g:Ch_msgs))}, 5000)
" Each line must arrive as a separate callback call with no embedded CR/NL.
call assert_equal(['err:1', 'err:2', 'out:3'], g:Ch_msgs)
" Wait until both the raw stderr chunk and a stdout chunk have arrived.
call WaitForAssert({-> assert_true(
\ index(g:Ch_msgs, "err:1\nerr:2\n") >= 0
\ && match(g:Ch_msgs, 'out:3') >= 0)}, 5000)
" stderr (via pipe) arrives as a single raw chunk with embedded NL,
" not split per line. stdout (via PTY) is delivered, but its exact
" CR/LF shape depends on the PTY line discipline, so we only check that
" 'out:3' appears somewhere in the received chunks.
call job_stop(term_getjob(ptybuf))
exe 'bwipe! ' .. ptybuf
unlet g:Ch_msgs
endfunc

View File

@@ -593,6 +593,12 @@ func Test_set_completion_string_values()
if exists('+tabclose')
call assert_equal('left uselast', join(sort(getcompletion('set tabclose=', 'cmdline'))), ' ')
endif
if has('tabpanel')
call assert_equal(['align:', 'columns:', 'scrollbar', 'vert'],
\ getcompletion('set tabpanelopt=', 'cmdline'))
call assert_equal(['left', 'right'],
\ getcompletion('set tabpanelopt=align:', 'cmdline'))
endif
if exists('+termwintype')
call assert_equal('conpty', getcompletion('set termwintype=', 'cmdline')[1])
endif

View File

@@ -963,49 +963,26 @@ func Test_tabpanel_large_columns()
call assert_fails(':set tabpanelopt=columns:-1', 'E474:')
endfunc
func Test_tabpanel_scrollopt_accepted()
" 'scroll' / 'scrollbar' must be accepted in 'tabpanelopt'.
set tabpanelopt=scroll
call assert_equal('scroll', &tabpanelopt)
set tabpanelopt=scrollbar
call assert_equal('scrollbar', &tabpanelopt)
" Combination with other values.
set tabpanelopt=align:right,scroll
call assert_equal('align:right,scroll', &tabpanelopt)
set tabpanelopt=columns:15,vert,scrollbar
call assert_equal('columns:15,vert,scrollbar', &tabpanelopt)
set tabpanelopt=align:right,columns:12,vert,scrollbar
call assert_equal('align:right,columns:12,vert,scrollbar', &tabpanelopt)
" Unknown values must still fail.
call assert_fails(':set tabpanelopt=scrol', 'E474:')
call assert_fails(':set tabpanelopt=scrollbarx', 'E474:')
call s:reset()
endfunc
func Test_tabpanel_scroll_many_tabs()
let save_lines = &lines
let save_showtabpanel = &showtabpanel
let save_tabpanelopt = &tabpanelopt
" Make the screen short so the tab list exceeds the visible height.
" Make the screen short so the tab page list exceeds the visible height.
set lines=8
set showtabpanel=2
set tabpanelopt=scroll
set tabpanelopt=
for i in range(20)
tabnew
endfor
" Should not crash with many tabs and scroll enabled.
" Should not crash with many tabs (scroll behaviour is always on).
redraw!
" Switching to scrollbar resets the offset but must also not crash.
" Toggling scrollbar must also not crash.
set tabpanelopt=scrollbar
redraw!
" Disabling scroll returns to normal behavior.
set tabpanelopt=
redraw!
@@ -1024,6 +1001,94 @@ func Test_tabpanel_scroll_many_tabs()
let &lines = save_lines
endfunc
" The scrollbar thumb must follow the current tab when it is moved by
" gt/gT/:tabnext/:tablast, so that the selected tab is always visible.
func Test_tabpanel_scrollbar_follows_curtab()
let save_lines = &lines
let save_columns = &columns
let save_showtabpanel = &showtabpanel
let save_tabpanelopt = &tabpanelopt
set lines=10 columns=40
set showtabpanel=2 tabpanelopt=scrollbar,columns:8
for i in range(49)
tabnew
endfor
let sb_col = 8
" With curtab at the top of the list, row 1 shows the thumb and the
" last row shows the track. Record the two attrs for comparison.
tabfirst
redraw
let attr_thumb = screenattr(1, sb_col)
let attr_track = screenattr(&lines, sb_col)
call assert_notequal(attr_thumb, attr_track)
" Jump to a tab far outside the visible range: thumb must leave the top.
30tabnext
redraw
call assert_equal(attr_track, screenattr(1, sb_col))
" Back to the first tab: thumb returns to the top.
tabfirst
redraw
call assert_equal(attr_thumb, screenattr(1, sb_col))
call assert_equal(attr_track, screenattr(&lines, sb_col))
" gT from the first tab wraps to the last: thumb moves to the bottom.
normal! gT
redraw
call assert_equal(attr_track, screenattr(1, sb_col))
call assert_equal(attr_thumb, screenattr(&lines, sb_col))
" gt from the last tab wraps to the first: thumb returns to the top.
normal! gt
redraw
call assert_equal(attr_thumb, screenattr(1, sb_col))
call assert_equal(attr_track, screenattr(&lines, sb_col))
%bwipeout!
let &tabpanelopt = save_tabpanelopt
let &showtabpanel = save_showtabpanel
let &lines = save_lines
let &columns = save_columns
endfunc
" With 31 tabs on 24 rows, :tablast must place the scrollbar thumb's
" bottom at the last screen row. Before the fix, integer truncation in
" thumb_top left a one-row gap at the bottom.
func Test_tabpanel_scrollbar_reaches_bottom()
let save_lines = &lines
let save_columns = &columns
let save_showtabpanel = &showtabpanel
let save_tabpanelopt = &tabpanelopt
set lines=24 columns=40
set showtabpanel=2 tabpanelopt=scrollbar,columns:8
for i in range(30)
tabnew
endfor
let sb_col = 8
" Identify the thumb attr while the thumb is at the top.
tabfirst
redraw
let attr_thumb = screenattr(1, sb_col)
let attr_track = screenattr(&lines, sb_col)
call assert_notequal(attr_thumb, attr_track)
" :tablast must push the thumb all the way to the bottom.
tablast
redraw
call assert_equal(attr_thumb, screenattr(&lines, sb_col))
%bwipeout!
let &tabpanelopt = save_tabpanelopt
let &showtabpanel = save_showtabpanel
let &lines = save_lines
let &columns = save_columns
endfunc
func Test_tabpanel_variable_height()
let save_lines = &lines
@@ -1064,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

View File

@@ -939,4 +939,32 @@ func Test_undo_line_backspace_after_insert_cmd_cursor_movement()
bwipe!
endfunc
func Test_undo_line_backspace_after_insert_func_edit()
new
setlocal backspace=eol undolevels=100
let v:errmsg = ''
call feedkeys("i\<CR>"
\ .. "\<Cmd>call setline(2, 'abc')\<CR>"
\ .. "\<BS>\<Esc>u", 'xt')
call assert_equal('', v:errmsg)
call assert_equal([''], getline(1, '$'))
bwipe!
endfunc
func Test_undo_line_backspace_after_insert_cmd_edit()
new
setlocal backspace=eol undolevels=100
let v:errmsg = ''
call feedkeys("i\<CR>"
\ .. "\<Cmd>s/.*/abc/\<CR>"
\ .. "\<BS>\<Esc>u", 'xt')
call assert_equal('', v:errmsg)
call assert_equal([''], getline(1, '$'))
bwipe!
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

@@ -1992,6 +1992,16 @@ func Test_splitkeep_screen_cursor_pos()
set splitkeep&
endfunc
func Test_splitkeep_cmdheight()
set splitkeep=screen
call setline(1, range(&lines))
norm! G
set cmdheight=2
call assert_equal(&lines - 1, line('.'))
%bwipeout!
set splitkeep& cmdheight&
endfunc
func Test_splitkeep_cursor()
CheckScreendump
let lines =<< trim END

View File

@@ -330,9 +330,11 @@ let test_values = {
\ 'tabline': [['', 'xxx'], ['%$', '%{', '%{%', '%{%}', '%(', '%)']],
\ 'tabpanel': [['', 'aaa', 'bbb'], []],
\ 'tabpanelopt': [['', 'align:left', 'align:right', 'vert', 'columns:0',
\ 'columns:20', 'columns:999'],
\ 'columns:20', 'columns:999', 'scrollbar',
\ 'columns:15,vert,scrollbar',
\ 'align:right,columns:12,vert,scrollbar'],
\ ['xxx', 'align:', 'align:middle', 'colomns:', 'cols:10',
\ 'cols:-1']],
\ 'cols:-1', 'scroll', 'scrol', 'scrollbarx']],
\ 'tagcase': [['followic', 'followscs', 'ignore', 'match', 'smart'],
\ ['', 'xxx', 'smart,match']],
\ 'termencoding': [has('gui_gtk') ? [] : ['', 'utf-8'], ['xxx']],

View File

@@ -729,6 +729,22 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
413,
/**/
412,
/**/
411,
/**/
410,
/**/
409,
/**/
408,
/**/
407,
/**/
406,
/**/
405,
/**/

View File

@@ -4784,6 +4784,9 @@ free_tabpage(tabpage_T *tp)
if (tp == lastused_tabpage)
lastused_tabpage = NULL;
#ifdef FEAT_TABPANEL
tabpanel_forget_tabpage(tp);
#endif
vim_free(tp->tp_localdir);
vim_free(tp->tp_prevdir);
@@ -7588,6 +7591,7 @@ command_height(void)
}
if (p_ch < old_p_ch && command_frame_height && frp != NULL)
frame_add_height(frp, (int)(old_p_ch - p_ch));
win_fix_scroll(true);
// Recompute window positions.
win_comp_pos();