Compare commits

..

8 Commits

Author SHA1 Message Date
Yasuhiro Matsumoto
105d65e29b patch 9.2.0339: regexp: nfa_regmatch() allocates and frees too often
Problem:  nfa_regmatch() allocates and frees two list buffers on every
          call, causing unnecessary memory allocation overhead for
          frequently used patterns.
Solution: Cache the list buffers in the regprog struct and reuse them
          on subsequent top-level calls. Recursive calls still allocate
          their own buffers. Free cached buffers in nfa_regfree()
          (Yasuhiro Matsumoto).

Benchmark: 10K lines, `:%s` x50 iterations

| Pattern | Before | After | Improvement |
|---|---|---|---|
| `\<\(\w\+\%(ing\|tion\|ed\|ly\)\|\w\{3,}\)\>` (many matches) | 4.384s | 4.299s | -2% |
| `\(foo\|bar\|baz\)\{3,}\(qux\|quux\|corge\)\{2,}...` (no match, high nstate) | 16.927s | 3.015s | -82% |

closes: #19956

Signed-off-by: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
2026-04-12 16:12:22 +00:00
Emilia
86ae6858ab runtime(jjdescription): allow to configure summary width
Allow to configure max length for the summary line and fall back to gits
setting.

closes: #19905

Signed-off-by: Emilia <emilia@bewitching.dev>
Signed-off-by: Christian Brabandt <cb@256bit.org>
2026-04-11 15:37:40 +00:00
Yasuhiro Matsumoto
0802e00f2a patch 9.2.0338: Cannot handle mouseclicks in the tabline
Problem:  Cannot handle mouseclicks in the tabline
Solution: Support %[FuncName] click regions in 'tabline', add "area" key
          to the click info dict (Yasuhiro Matsumoto).

The previous implementation resolved and stored click regions only for
per-window statuslines; the tabline path in win_redr_custom() (wp==NULL)
parsed %[FuncName] but discarded the regions, and tabline clicks were
dispatched via TabPageIdxs[] which didn't know about them.

Add a global tabline_stl_click array populated from the tabline path,
refactor stl_click_handler() to take the regions directly, and dispatch
matching clicks from do_mouse() before falling through to tab selection.
The winid entry in the callback dict is 0 for tabline clicks.

related: #19841
closes:  #19950

Supported by AI.

Signed-off-by: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
2026-04-11 15:26:14 +00:00
James McCoy
8fd37e42a6 patch 9.2.0337: list indexing broken on big-endian 32-bit platforms
Problem:  check_range_index_one() expects a long * but n1 is a
          varnumber_T. Casting varnumber_T * to long * is undefined
          behaviour and reads the wrong bytes on big-endian platforms
          (John Paul Adrian Glaubitz)
Solution: Use a local long variable and pass that pointer to
          check_range_index_one() (James McCoy)

fixes:  #19798
closes: #19952

Signed-off-by: James McCoy <jamessan@jamessan.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
2026-04-11 15:04:00 +00:00
Christian Brabandt
01be43047f runtime(hlyank): verify winid in lambda before matchdelete()
Signed-off-by: Christian Brabandt <cb@256bit.org>
2026-04-11 14:53:48 +00:00
Cimbali
e29f33ef51 patch 9.2.0336: libvterm: no terminal reflow support
Problem:  libvterm: no terminal reflow support
Solution: Support for reflowing, sync libvterm to revision 843
          (Cimbali)

fixes:  #2865
closes: #8365
closes: #19863

Co-authored-by: Paul "LeoNerd" Evans <leonerd@leonerd.org.uk>
Signed-off-by: Cimbali <me@cimba.li>
Signed-off-by: Christian Brabandt <cb@256bit.org>
2026-04-10 22:15:36 +00:00
Yasuhiro Matsumoto
71c10dcd58 patch 9.2.0335: json_encode() uses recursive algorithm
Problem:  json_encode() uses recursive algorithm
Solution: Convert from recursive to iterative algorithm to prevent
          stack overflow on deep recursive levels
          (Yasuhiro Matsumoto).

closes: #19839

Signed-off-by: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
2026-04-10 21:37:44 +00:00
Gary Johnson
dd40b1af5b patch 9.2.0334: GTK: window geometry shrinks with with client-side decorations
Problem:  On GTK3 with client-side decorations the window opens with
          wrong &columns/&lines, and each :tabnew/:tabclose cycle
          shrinks the size further.
Solution: Measure and compensate for the CSD frame offset, discard
          spurious configure events from tabline show/hide
          (Gary Johnson).

closes: #19853

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Gary Johnson <garyjohn@spocom.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
2026-04-10 21:23:38 +00:00
32 changed files with 1186 additions and 215 deletions

View File

@@ -440,6 +440,7 @@ SRC_ALL = \
src/libvterm/t/66screen_extent.test \
src/libvterm/t/67screen_dbl_wh.test \
src/libvterm/t/68screen_termprops.test \
src/libvterm/t/69screen_pushline.test \
src/libvterm/t/69screen_reflow.test \
src/libvterm/t/90vttest_01-movement-1.test \
src/libvterm/t/90vttest_01-movement-2.test \

View File

@@ -1,4 +1,4 @@
*filetype.txt* For Vim version 9.2. Last change: 2026 Apr 09
*filetype.txt* For Vim version 9.2. Last change: 2026 Apr 11
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -806,6 +806,14 @@ To enable the recognition of Markdown comments each time after removing
re-source "javaformat.vim" for Vim versions greater than `8.2.1397`: >
runtime autoload/javaformat.vim
<
JUJUTSU DESCRIPTION *ft-jjdescription-plugin*
The length of the first line of the commit message used for syntax
highlighting can be configured via `g:jjcommit_summary_length`, and will
fallback to `g:gitcommit_summary_length`. The default is 50. Example: >
let g:jjcommit_summary_length = 70
<
JSON-FORMAT *ft-json-plugin*
JSON filetype can be extended to use 'formatexpr' and "json.FormatExpr()"
@@ -816,7 +824,7 @@ Add following lines to $HOME/.vim/ftplugin/json.vim: >
vim9script
import autoload 'dist/json.vim'
setl formatexpr=json.FormatExpr()
<
LUA *ft-lua-plugin* *g:lua_folding*
You can enable folding of Lua functions using |fold-expr| by: >

View File

@@ -1,4 +1,4 @@
*options.txt* For Vim version 9.2. Last change: 2026 Apr 09
*options.txt* For Vim version 9.2. Last change: 2026 Apr 11
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -8626,7 +8626,8 @@ A jump table for the options with a short description can be found at |Q_op|.
*stl-%[FuncName]*
%[ defines clickable regions in the statusline. When the user clicks
on a region with the mouse, the specified function is called.
on a region with the mouse, the specified function is called. The
same syntax can also be used in 'tabline'.
%[FuncName] Start of a clickable region. "FuncName" is the name
of a Vim function to call when the region is clicked.
@@ -8644,11 +8645,17 @@ A jump table for the options with a short description can be found at |Q_op|.
"button" Mouse button: "l" (left), "m" (middle), "r" (right).
"mods" Modifier keys: combination of "s" (shift), "c" (ctrl),
"a" (alt). Empty string if no modifiers.
"winid" |window-ID| of the window whose statusline was clicked.
"winid" |window-ID| of the window whose statusline was clicked,
or 0 when the click was in 'tabline'.
"area" "statusline" or "tabline". Indicates which option the
clicked region belongs to. Useful when a single
callback is shared between 'statusline' and 'tabline'.
If the function returns non-zero, the statusline is redrawn.
Dragging the statusline to resize the window still works even when
click handlers are defined.
click handlers are defined. When used in 'tabline', clicks in
%[FuncName] regions are dispatched to the callback instead of the
default tab-selection behavior.
Example: >
func! ClickFile(info)

View File

@@ -7601,6 +7601,7 @@ ft-inform-syntax syntax.txt /*ft-inform-syntax*
ft-java-plugin filetype.txt /*ft-java-plugin*
ft-java-syntax syntax.txt /*ft-java-syntax*
ft-javascript-omni insert.txt /*ft-javascript-omni*
ft-jjdescription-plugin filetype.txt /*ft-jjdescription-plugin*
ft-jq-syntax syntax.txt /*ft-jq-syntax*
ft-json-plugin filetype.txt /*ft-json-plugin*
ft-json-syntax syntax.txt /*ft-json-syntax*

View File

@@ -1,4 +1,4 @@
*version9.txt* For Vim version 9.2. Last change: 2026 Apr 09
*version9.txt* For Vim version 9.2. Last change: 2026 Apr 10
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -52615,6 +52615,7 @@ Other ~
bypassing the shell completely.
- Allow mouse clickable regions in the |status-line| using the
|stl-%[FuncName]| atom.
- Enable reflow support in the |:terminal|.
Platform specific ~
-----------------

View File

@@ -1,7 +1,7 @@
vim9script
# Highlight Yank plugin
# Last Change: 2025 Mar 22
# Last Change: 2026 Apr 11
def HighlightedYank()
@@ -28,7 +28,11 @@ def HighlightedYank()
return [v[0][1], col_beg, col_end - col_beg]
}))
var winid = win_getid()
timer_start(duration, (_) => m->matchdelete(winid))
timer_start(duration, (_) => {
if winbufnr(winid) != -1
m->matchdelete(winid)
endif
})
endif
enddef

View File

@@ -5,6 +5,7 @@
" 2025 Apr 17 by Vim Project (don't require space to start comments, #17130)
" 2026 Apr 09 by Vim Project (anchor status regex to beginning of line, #19879)
" 2026 Apr 09 by Vim Project (detect renames of files, #19879)
" 2026 Apr 11 by Vim Project (configure summary length, #19905)
if exists('b:current_syntax')
finish
@@ -20,10 +21,21 @@ syn region jjComment start="^JJ:" end="$" contains=jjAdded,jjRemoved,jjChanged,j
syn include @jjCommitDiff syntax/diff.vim
syn region jjCommitDiff start=/\%(^diff --\%(git\|cc\|combined\) \)\@=/ end=/^\%(diff --\|$\|@@\@!\|[^[:alnum:]\ +-]\S\@!\)\@=/ fold contains=@jjCommitDiff
hi def link jjComment Comment
hi def link jjAdded Added
hi def link jjRemoved Removed
hi def link jjChanged Changed
hi def link jjRenamed Changed
if get(g:, 'jjcommit_summary_length', get(g:, 'gitcommit_summary_length', 0)) < 0
syn match jjdescriptionSummary "^.*$" contained containedin=jjcommitFirstLine nextgroup=jjcommitOverflow contains=@Spell
elseif get(g:, 'jjcommit_summary_length', get(g:, 'gitcommit_summary_length', 1)) > 0
exe 'syn match jjdescriptionSummary "^.*\%<' . (get(g:, 'jjcommit_summary_length', get(:g, 'gitcommit_summary_length', 50) + 1) . 'v." contained containedin=jjcommitFirstLine nextgroup=jjcommitOverflow contains=@Spell'
endif
syn match jjcommitOverflow ".*" contained contains=@Spell
syn match jjcommitBlank "^.\+" contained contains=@Spell
syn match jjcommitFirstLine "\%^.*" nextgroup=jjcommitBlank,jjComment skipnl
hi def link jjcommitSummary Keyword
hi def link jjComment Comment
hi def link jjAdded Added
hi def link jjRemove Removed
hi def link jjChange Changed
hi def link jjRenamed Changed
hi def link jjcommitBlank Error
let b:current_syntax = 'jjdescription'

View File

@@ -104,6 +104,10 @@ EXTERN int redrawing_for_callback INIT(= 0);
*/
EXTERN short *TabPageIdxs INIT(= NULL);
// Click regions for 'tabline' (%[FuncName]).
EXTERN stl_click_region_T *tabline_stl_click INIT(= NULL);
EXTERN int tabline_stl_click_count INIT(= 0);
#ifdef FEAT_PROP_POPUP
// Array with size Rows x Columns containing zindex of popups.
EXTERN short *popup_mask INIT(= NULL);

View File

@@ -1659,9 +1659,11 @@ again:
gui_may_resize_shell(void)
{
if (new_pixel_height)
{
// careful: gui_resize_shell() may postpone the resize again if we
// were called indirectly by it
gui_resize_shell(new_pixel_width, new_pixel_height);
}
}
int
@@ -3737,12 +3739,24 @@ gui_init_which_components(char_u *oldval UNUSED)
// Don't do this while starting up though.
// Don't change Rows when adding menu/toolbar/tabline.
// Don't change Columns when adding vertical toolbar.
if (!gui.starting && need_set_size != (RESIZE_VERT | RESIZE_HOR))
(void)char_avail();
if ((need_set_size & RESIZE_VERT) == 0)
Rows = prev_Rows;
if ((need_set_size & RESIZE_HOR) == 0)
Columns = prev_Columns;
{
// Save the size gui_set_shellsize() determined before
// char_avail() processes spurious configure events that may
// corrupt Rows/Columns.
long post_Rows = Rows;
long post_Columns = Columns;
if (!gui.starting && need_set_size != (RESIZE_VERT | RESIZE_HOR))
(void)char_avail();
if ((need_set_size & RESIZE_VERT) == 0)
Rows = prev_Rows;
else
Rows = post_Rows;
if ((need_set_size & RESIZE_HOR) == 0)
Columns = prev_Columns;
else
Columns = post_Columns;
}
#endif
}
// When the console tabline appears or disappears the window positions
@@ -3793,14 +3807,29 @@ gui_update_tabline(void)
out_flush();
if (!showit != !shown)
{
// Save Rows/Columns before showing/hiding the tabline.
// gui_mch_show_tabline() processes GTK events (via
// gui_mch_update) which may trigger a spurious configure event
// that temporarily corrupts Rows. Restore the original values so
// that gui_set_shellsize() uses the correct row/column count.
int save_Rows = Rows;
int save_Columns = Columns;
gui_mch_show_tabline(showit);
Rows = save_Rows;
Columns = save_Columns;
// Resize the outer window to compensate for the tabline height
// change. This is also needed when called from draw_tabline()
// (e.g. after :tabclose), where no caller will do the resize.
// When called from gui_init_which_components() the subsequent
// gui_set_shellsize() call will redo this to the same dimensions,
// which is harmless.
gui_set_shellsize(FALSE, showit, RESIZE_VERT);
}
if (showit != 0)
gui_mch_update_tabline();
// When the tabs change from hidden to shown or from shown to
// hidden the size of the text area should remain the same.
if (!showit != !shown)
gui_set_shellsize(FALSE, showit, RESIZE_VERT);
}
}

View File

@@ -408,11 +408,13 @@ static int using_gnome = 0;
* Width and height are of gui.mainwin.
*/
typedef struct resize_history {
int used; // If true, can't match for discard. Only matches once.
int used; // If true, can't match for discard. Only matches once.
int width;
int height;
int from_shellsize; // TRUE if recorded by gui_mch_set_shellsize (not
// a pre-record from gui_mch_show_tabline).
# ifdef ENABLE_RESIZE_HISTORY_LOG
int seq; // for ch_log messages
int seq; // for ch_log messages
# endif
struct resize_history *next;
} resize_hist_T;
@@ -422,13 +424,28 @@ static resize_hist_T *latest_resize_hist;
// list of stale resize requests
static resize_hist_T *old_resize_hists;
// On Wayland (and GTK3 with CSD), gtk_window_resize(w, h) results in
// gtk_window_get_size() returning (w - csd_w, h - csd_h). These offsets are
// computed once from the first confirmed resize response and applied to all
// subsequent gtk_window_resize() calls so the form gets the intended size.
static int mch_csd_width = 0;
static int mch_csd_height = 0;
// Expected form widget width for the most recent gui_mch_set_shellsize()
// call (set only after mch_csd_width is known). Used in
// form_configure_event() to clamp slightly-narrow configure responses that
// result from CSD under-compensation, preventing column loss.
static int mch_pending_form_w = 0;
/*
* Used when calling gtk_window_resize().
* Create a resize request history item, put previous request on stale list.
* Width/height are the size of the request for the gui.mainwin.
* from_shellsize is TRUE when called from gui_mch_set_shellsize (vs a
* stale pre-record from gui_mch_show_tabline).
*/
static void
alloc_resize_hist(int width, int height)
alloc_resize_hist(int width, int height, int from_shellsize)
{
// alloc a new resize hist, save current in list of old history
resize_hist_T *prev_hist = latest_resize_hist;
@@ -436,6 +453,7 @@ alloc_resize_hist(int width, int height)
new_hist->width = width;
new_hist->height = height;
new_hist->from_shellsize = from_shellsize;
latest_resize_hist = new_hist;
// previous hist item becomes head of list
@@ -3102,7 +3120,25 @@ get_item_dimensions(GtkWidget *widget, GtkOrientation orientation)
GtkAllocation allocation;
gtk_widget_get_allocation(widget, &allocation);
if (allocation.height > 1)
return allocation.height;
// Allocation hasn't been updated yet (widget just became visible,
// e.g. tab bar shown asynchronously on Wayland). Query the preferred
// height so the caller gets a valid value before the layout pass
// runs. Use the maximum of minimum and natural height: GTK may
// allocate min_h even when natural_h is smaller (e.g. GtkNotebook
// tab bar has min_h > natural_h due to CSS).
# if GTK_CHECK_VERSION(3,0,0)
{
gint min_h = 0, natural_h = 0;
gtk_widget_get_preferred_height(widget, &min_h, &natural_h);
return MAX(min_h, natural_h);
}
# else
return allocation.height;
# endif
# else
if (orientation == GTK_ORIENTATION_HORIZONTAL)
return widget->allocation.height;
@@ -3147,7 +3183,11 @@ get_menu_tool_height(void)
height += get_item_dimensions(gui.toolbar, GTK_ORIENTATION_HORIZONTAL);
#endif
#ifdef FEAT_GUI_TABLINE
if (gui.tabline != NULL)
// Only include the tabline height when tabs are actually shown. After
// gtk_notebook_set_show_tabs(FALSE) the widget allocation is not updated
// until the GTK main loop runs, so reading it would give a stale value.
if (gui.tabline != NULL
&& gtk_notebook_get_show_tabs(GTK_NOTEBOOK(gui.tabline)))
height += get_item_dimensions(gui.tabline, GTK_ORIENTATION_HORIZONTAL);
#endif
@@ -3513,6 +3553,19 @@ gui_mch_show_tabline(int showit)
if (!showit != !gtk_notebook_get_show_tabs(GTK_NOTEBOOK(gui.tabline)))
{
# ifdef TRACK_RESIZE_HISTORY
// When the tabline visibility changes, GTK defers the formwin
// relayout. The resulting configure event fires while the mainwin is
// still at its current size. Pre-record that size so it becomes a
// stale (discardable) entry once gui_mch_set_shellsize() records the
// new target size.
{
int w, h;
gtk_window_get_size(GTK_WINDOW(gui.mainwin), &w, &h);
alloc_resize_hist(w, h, FALSE);
}
# endif
// Note: this may cause a resize event
gtk_notebook_set_show_tabs(GTK_NOTEBOOK(gui.tabline), showit);
update_window_manager_hints(0, 0);
@@ -4284,6 +4337,22 @@ gui_mch_new_colors(void)
}
}
/*
* One-shot idle callback to issue a corrective gtk_window_resize() after the
* startup configure event has been processed. At startup
* gui_mch_set_shellsize() runs before the CSD offsets are known (they are
* measured from the first configure response), so the window ends up
* mch_csd_height pixels too short and mch_csd_width pixels too narrow. This
* callback re-issues the shellsize with the now-known offsets so the physical
* window matches Vim's model.
*/
static gboolean
startup_resize_correction_cb(gpointer data UNUSED)
{
gui_set_shellsize(FALSE, FALSE, RESIZE_BOTH);
return FALSE; // one-shot
}
/*
* This signal informs us about the need to rearrange our sub-widgets.
*/
@@ -4321,6 +4390,41 @@ form_configure_event(GtkWidget *widget UNUSED,
&& match_stale_width_height(w, h))
// discard stale event
return TRUE;
// On Wayland (GTK3 CSD), gtk_window_resize(w, req_h) results in
// gtk_window_get_size() returning (req_w - csd_w, req_h - csd_h).
// Compute the offsets once from the first confirmed resize response.
if ((mch_csd_height == 0 || mch_csd_width == 0)
&& latest_resize_hist != NULL
&& !latest_resize_hist->used
&& latest_resize_hist->from_shellsize
&& gui.char_height > 0)
{
int pot_csd_w = latest_resize_hist->width - w;
int pot_csd_h = latest_resize_hist->height - h;
if (pot_csd_w > 0 && pot_csd_w < gui.char_width)
{
mch_csd_width = pot_csd_w;
// The resize that triggered this startup event was issued without
// CSD compensation; retroactively set the expected form width so
// that the clamp below corrects Columns for this same event.
mch_pending_form_w = (int)Columns * gui.char_width
+ gui_get_base_width();
}
if (pot_csd_h > 0 && pot_csd_h < gui.char_height)
{
mch_csd_height = pot_csd_h;
// Similarly, correct the form height for this event so that Rows
// is computed correctly despite the missing CSD compensation.
usable_height += mch_csd_height;
}
// The window was resized without CSD compensation and is physically
// too small. Schedule a corrective resize (now that offsets are
// known) so the window actually fits the geometry Vim has just set.
if ((mch_csd_height > 0 || mch_csd_width > 0) && gtk_socket_id == 0)
g_idle_add(startup_resize_correction_cb, NULL);
}
clear_resize_hists();
#endif
@@ -4354,9 +4458,22 @@ form_configure_event(GtkWidget *widget UNUSED,
if (gtk_socket_id != 0)
usable_height -= (gui.char_height - (gui.char_height/2)); // sic.
gui_gtk_form_freeze(GTK_FORM(gui.formwin));
gui_resize_shell(event->width, usable_height);
gui_gtk_form_thaw(GTK_FORM(gui.formwin));
// If the configure event delivers a form width that is slightly less than
// the width we intended (mch_pending_form_w), the difference is within
// one char_width and is due to CSD under-compensation. Clamp to the
// intended width so that column count does not drift downward.
{
int use_width = event->width;
#ifdef TRACK_RESIZE_HISTORY
if (mch_pending_form_w > event->width
&& mch_pending_form_w - event->width < gui.char_width)
use_width = mch_pending_form_w;
#endif
gui_gtk_form_freeze(GTK_FORM(gui.formwin));
gui_resize_shell(use_width, usable_height);
gui_gtk_form_thaw(GTK_FORM(gui.formwin));
}
return TRUE;
}
@@ -4855,8 +4972,22 @@ gui_mch_set_shellsize(int width, int height,
width += get_menu_tool_width();
height += get_menu_tool_height();
// Compensate for CSD frame: on Wayland (GTK3 with CSD), the compositor
// subtracts the decoration margin from the requested size. mch_csd_width
// and mch_csd_height are computed from the first startup resize response
// and are 0 on X11.
width += mch_csd_width;
height += mch_csd_height;
// Record the form width we expect the compositor to deliver. Used in
// form_configure_event() to clamp configure responses that are slightly
// narrower than intended (due to CSD startup underestimate), preventing
// column loss. Only meaningful once mch_csd_width has been measured.
mch_pending_form_w = (mch_csd_width > 0)
? width - get_menu_tool_width() - mch_csd_width : 0;
#ifdef TRACK_RESIZE_HISTORY
alloc_resize_hist(width, height); // track the resize request
alloc_resize_hist(width, height, TRUE);
#endif
if (gtk_socket_id == 0)
gtk_window_resize(GTK_WINDOW(gui.mainwin), width, height);

View File

@@ -18,7 +18,7 @@
#if defined(FEAT_EVAL)
static int json_encode_item(garray_T *gap, typval_T *val, int copyID, int options, int depth);
static int json_encode_item(garray_T *gap, typval_T *val, int copyID, int options);
/*
* Encode "val" into a JSON format string.
@@ -28,7 +28,7 @@ static int json_encode_item(garray_T *gap, typval_T *val, int copyID, int option
static int
json_encode_gap(garray_T *gap, typval_T *val, int options)
{
if (json_encode_item(gap, val, get_copyID(), options, 0) == FAIL)
if (json_encode_item(gap, val, get_copyID(), options) == FAIL)
{
ga_clear(gap);
gap->ga_data = vim_strsave((char_u *)"");
@@ -264,31 +264,61 @@ is_simple_key(char_u *key)
return TRUE;
}
typedef enum {
ENC_LIST,
ENC_TUPLE,
ENC_DICT,
} json_enc_type_T;
typedef struct {
json_enc_type_T je_type;
int je_options; // options when this container was entered
union {
struct {
list_T *list;
listitem_T *li; // current item
} l;
struct {
tuple_T *tuple;
int idx; // current index
int len;
} t;
struct {
dict_T *dict;
hashitem_T *hi; // current entry
int todo; // remaining entries
} d;
} je_u;
} json_enc_frame_T;
/*
* Encode "val" into "gap".
* Uses an explicit stack to avoid deep recursion on nested structures.
* Return FAIL or OK.
*/
static int
json_encode_item(garray_T *gap, typval_T *val, int copyID, int options, int depth)
json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
{
char_u numbuf[NUMBUFLEN];
char_u *res;
blob_T *b;
list_T *l;
tuple_T *tuple;
dict_T *d;
int i;
char_u numbuf[NUMBUFLEN];
char_u *res;
blob_T *b;
list_T *l;
tuple_T *tuple;
dict_T *d;
int i;
garray_T stack;
typval_T *cur_val;
json_enc_frame_T *frame;
if (depth > p_mfd)
ga_init2(&stack, sizeof(json_enc_frame_T), 100);
cur_val = val;
for (;;)
{
emsg(_(e_function_call_depth_is_higher_than_maxfuncdepth));
return FAIL;
}
switch (val->v_type)
switch (cur_val->v_type)
{
case VAR_BOOL:
switch ((long)val->vval.v_number)
switch ((long)cur_val->vval.v_number)
{
case VVAL_FALSE: GA_CONCAT_LITERAL(gap, "false"); break;
case VVAL_TRUE: GA_CONCAT_LITERAL(gap, "true"); break;
@@ -296,7 +326,7 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options, int dept
break;
case VAR_SPECIAL:
switch ((long)val->vval.v_number)
switch ((long)cur_val->vval.v_number)
{
case VVAL_NONE: if ((options & JSON_JS) != 0
&& (options & JSON_NO_NONE) == 0)
@@ -312,13 +342,13 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options, int dept
size_t numbuflen;
numbuflen = vim_snprintf_safelen((char *)numbuf, sizeof(numbuf),
"%lld", (varnumber_T)val->vval.v_number);
"%lld", (varnumber_T)cur_val->vval.v_number);
ga_concat_len(gap, numbuf, numbuflen);
}
break;
case VAR_STRING:
res = val->vval.v_string;
res = cur_val->vval.v_string;
write_string(gap, res);
break;
@@ -330,11 +360,11 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options, int dept
case VAR_CLASS:
case VAR_OBJECT:
case VAR_TYPEALIAS:
semsg(_(e_cannot_json_encode_str), vartype_name(val->v_type));
return FAIL;
semsg(_(e_cannot_json_encode_str), vartype_name(cur_val->v_type));
goto theend;
case VAR_BLOB:
b = val->vval.v_blob;
b = cur_val->vval.v_blob;
if (b == NULL || b->bv_ga.ga_len == 0)
GA_CONCAT_LITERAL(gap, "[]");
else
@@ -366,7 +396,7 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options, int dept
break;
case VAR_LIST:
l = val->vval.v_list;
l = cur_val->vval.v_list;
if (l == NULL)
GA_CONCAT_LITERAL(gap, "[]");
else
@@ -375,26 +405,28 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options, int dept
GA_CONCAT_LITERAL(gap, "[]");
else
{
listitem_T *li;
l->lv_copyID = copyID;
ga_append(gap, '[');
CHECK_LIST_MATERIALIZE(l);
for (li = l->lv_first; li != NULL && !got_int; )
if (l->lv_first != NULL)
{
if (json_encode_item(gap, &li->li_tv, copyID,
options & JSON_JS,
depth + 1) == FAIL)
return FAIL;
if ((options & JSON_JS)
&& li->li_next == NULL
&& li->li_tv.v_type == VAR_SPECIAL
&& li->li_tv.vval.v_number == VVAL_NONE)
// add an extra comma if the last item is v:none
ga_append(gap, ',');
li = li->li_next;
if (li != NULL)
ga_append(gap, ',');
if (stack.ga_len >= p_mfd)
{
emsg(_(e_function_call_depth_is_higher_than_maxfuncdepth));
goto theend;
}
if (ga_grow(&stack, 1) == FAIL)
goto theend;
frame = ((json_enc_frame_T *)stack.ga_data)
+ stack.ga_len;
frame->je_type = ENC_LIST;
frame->je_options = options;
frame->je_u.l.list = l;
frame->je_u.l.li = l->lv_first;
++stack.ga_len;
options = options & JSON_JS;
cur_val = &l->lv_first->li_tv;
continue;
}
ga_append(gap, ']');
l->lv_copyID = 0;
@@ -403,7 +435,7 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options, int dept
break;
case VAR_TUPLE:
tuple = val->vval.v_tuple;
tuple = cur_val->vval.v_tuple;
if (tuple == NULL)
GA_CONCAT_LITERAL(gap, "[]");
else
@@ -414,33 +446,37 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options, int dept
{
int len = TUPLE_LEN(tuple);
tuple->tv_copyID = copyID;
ga_append(gap, '[');
for (i = 0; i < len && !got_int; i++)
if (len > 0)
{
typval_T *t_item = TUPLE_ITEM(tuple, i);
if (json_encode_item(gap, t_item, copyID,
options & JSON_JS,
depth + 1) == FAIL)
return FAIL;
if ((options & JSON_JS)
&& i == len - 1
&& t_item->v_type == VAR_SPECIAL
&& t_item->vval.v_number == VVAL_NONE)
// add an extra comma if the last item is v:none
ga_append(gap, ',');
if (i <= len - 2)
ga_append(gap, ',');
tuple->tv_copyID = copyID;
ga_append(gap, '[');
if (stack.ga_len >= p_mfd)
{
emsg(_(e_function_call_depth_is_higher_than_maxfuncdepth));
goto theend;
}
if (ga_grow(&stack, 1) == FAIL)
goto theend;
frame = ((json_enc_frame_T *)stack.ga_data)
+ stack.ga_len;
frame->je_type = ENC_TUPLE;
frame->je_options = options;
frame->je_u.t.tuple = tuple;
frame->je_u.t.idx = 0;
frame->je_u.t.len = len;
++stack.ga_len;
options = options & JSON_JS;
cur_val = TUPLE_ITEM(tuple, 0);
continue;
}
ga_append(gap, ']');
tuple->tv_copyID = 0;
else
GA_CONCAT_LITERAL(gap, "[]");
}
}
break;
case VAR_DICT:
d = val->vval.v_dict;
d = cur_val->vval.v_dict;
if (d == NULL)
GA_CONCAT_LITERAL(gap, "{}");
else
@@ -449,46 +485,60 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options, int dept
GA_CONCAT_LITERAL(gap, "{}");
else
{
int first = TRUE;
int todo = (int)d->dv_hashtab.ht_used;
hashitem_T *hi;
d->dv_copyID = copyID;
ga_append(gap, '{');
if (todo > 0)
{
d->dv_copyID = copyID;
ga_append(gap, '{');
for (hi = d->dv_hashtab.ht_array; todo > 0 && !got_int;
++hi)
if (!HASHITEM_EMPTY(hi))
{
--todo;
if (first)
first = FALSE;
else
ga_append(gap, ',');
if ((options & JSON_JS)
// Find first non-empty hash entry
for (hi = d->dv_hashtab.ht_array;
HASHITEM_EMPTY(hi); ++hi)
;
--todo;
// Write first key
if ((options & JSON_JS)
&& is_simple_key(hi->hi_key))
ga_concat(gap, hi->hi_key);
else
write_string(gap, hi->hi_key);
ga_append(gap, ':');
if (json_encode_item(gap, &dict_lookup(hi)->di_tv,
copyID, options | JSON_NO_NONE,
depth + 1) == FAIL)
return FAIL;
ga_concat(gap, hi->hi_key);
else
write_string(gap, hi->hi_key);
ga_append(gap, ':');
if (stack.ga_len >= p_mfd)
{
emsg(_(e_function_call_depth_is_higher_than_maxfuncdepth));
goto theend;
}
ga_append(gap, '}');
d->dv_copyID = 0;
if (ga_grow(&stack, 1) == FAIL)
goto theend;
frame = ((json_enc_frame_T *)stack.ga_data)
+ stack.ga_len;
frame->je_type = ENC_DICT;
frame->je_options = options;
frame->je_u.d.dict = d;
frame->je_u.d.hi = hi;
frame->je_u.d.todo = todo;
++stack.ga_len;
options = options | JSON_NO_NONE;
cur_val = &dict_lookup(hi)->di_tv;
continue;
}
else
GA_CONCAT_LITERAL(gap, "{}");
}
}
break;
case VAR_FLOAT:
#if defined(HAVE_MATH_H)
if (isnan(val->vval.v_float))
if (isnan(cur_val->vval.v_float))
GA_CONCAT_LITERAL(gap, "NaN");
else if (isinf(val->vval.v_float))
else if (isinf(cur_val->vval.v_float))
{
if (val->vval.v_float < 0.0)
if (cur_val->vval.v_float < 0.0)
GA_CONCAT_LITERAL(gap, "-Infinity");
else
GA_CONCAT_LITERAL(gap, "Infinity");
@@ -499,17 +549,151 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options, int dept
size_t numbuflen;
numbuflen = vim_snprintf_safelen((char *)numbuf, sizeof(numbuf),
"%g", val->vval.v_float);
"%g", cur_val->vval.v_float);
ga_concat_len(gap, numbuf, numbuflen);
}
break;
case VAR_UNKNOWN:
case VAR_ANY:
case VAR_VOID:
internal_error_no_abort("json_encode_item()");
return FAIL;
goto theend;
}
return OK;
// Process the completed item by advancing containers or
// returning when the stack is empty.
for (;;)
{
int advance = FALSE;
if (stack.ga_len == 0)
{
ga_clear(&stack);
return OK;
}
frame = ((json_enc_frame_T *)stack.ga_data)
+ stack.ga_len - 1;
switch (frame->je_type)
{
case ENC_LIST:
{
listitem_T *li = frame->je_u.l.li;
// JSON_JS: add trailing comma if last item is v:none
if ((frame->je_options & JSON_JS)
&& li->li_next == NULL
&& li->li_tv.v_type == VAR_SPECIAL
&& li->li_tv.vval.v_number == VVAL_NONE)
ga_append(gap, ',');
li = li->li_next;
frame->je_u.l.li = li;
if (li != NULL && !got_int)
{
ga_append(gap, ',');
options = frame->je_options & JSON_JS;
cur_val = &li->li_tv;
advance = TRUE;
break;
}
ga_append(gap, ']');
frame->je_u.l.list->lv_copyID = 0;
--stack.ga_len;
break;
}
case ENC_TUPLE:
{
int idx = frame->je_u.t.idx;
int len = frame->je_u.t.len;
// JSON_JS: add trailing comma if last item is v:none
if ((frame->je_options & JSON_JS) && idx == len - 1)
{
typval_T *t_item =
TUPLE_ITEM(frame->je_u.t.tuple, idx);
if (t_item->v_type == VAR_SPECIAL
&& t_item->vval.v_number == VVAL_NONE)
ga_append(gap, ',');
}
++idx;
frame->je_u.t.idx = idx;
if (idx < len && !got_int)
{
ga_append(gap, ',');
options = frame->je_options & JSON_JS;
cur_val = TUPLE_ITEM(frame->je_u.t.tuple, idx);
advance = TRUE;
break;
}
ga_append(gap, ']');
frame->je_u.t.tuple->tv_copyID = 0;
--stack.ga_len;
break;
}
case ENC_DICT:
{
hashitem_T *hi = frame->je_u.d.hi;
int todo = frame->je_u.d.todo;
if (todo > 0 && !got_int)
{
// Advance to next non-empty entry
for (++hi; HASHITEM_EMPTY(hi); ++hi)
;
--todo;
frame->je_u.d.hi = hi;
frame->je_u.d.todo = todo;
ga_append(gap, ',');
if ((frame->je_options & JSON_JS)
&& is_simple_key(hi->hi_key))
ga_concat(gap, hi->hi_key);
else
write_string(gap, hi->hi_key);
ga_append(gap, ':');
options = frame->je_options | JSON_NO_NONE;
cur_val = &dict_lookup(hi)->di_tv;
advance = TRUE;
break;
}
ga_append(gap, '}');
frame->je_u.d.dict->dv_copyID = 0;
--stack.ga_len;
break;
}
}
if (advance)
break;
}
}
theend:
// Clean up copyIDs for remaining frames on error/failure
for (i = 0; i < stack.ga_len; i++)
{
frame = ((json_enc_frame_T *)stack.ga_data) + i;
switch (frame->je_type)
{
case ENC_LIST:
frame->je_u.l.list->lv_copyID = 0;
break;
case ENC_TUPLE:
frame->je_u.t.tuple->tv_copyID = 0;
break;
case ENC_DICT:
frame->je_u.d.dict->dv_copyID = 0;
break;
}
}
ga_clear(&stack);
return FAIL;
}
/*

View File

@@ -7,7 +7,7 @@ The original can be found:
https://github.com/neovim/libvterm
Modifications:
- revisions up to 839 have been included
- revisions up to 843 have been included
- Added a .gitignore file.
- use TRUE and FALSE instead of true and false
- use int or unsigned int instead of bool

View File

@@ -452,6 +452,8 @@ typedef struct {
int (*resize)(int rows, int cols, VTermStateFields *fields, void *user);
int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user);
int (*sb_clear)(void *user);
// ABI-compat only enabled if vterm_state_callbacks_has_premove() is invoked
int (*premove)(VTermRect dest, void *user);
} VTermStateCallbacks;
// VIM: added
@@ -488,6 +490,8 @@ VTermState *vterm_obtain_state(VTerm *vt);
void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user);
void *vterm_state_get_cbdata(VTermState *state);
void vterm_state_callbacks_has_premove(VTermState *state);
void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks, void *user);
void *vterm_state_get_unrecognised_fbdata(VTermState *state);
@@ -578,6 +582,8 @@ typedef struct {
int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user);
int (*sb_popline)(int cols, VTermScreenCell *cells, void *user);
int (*sb_clear)(void* user);
/* ABI-compat this is only used if vterm_screen_callbacks_has_pushline4() is called */
int (*sb_pushline4)(int cols, const VTermScreenCell *cells, int continuation, void *user);
} VTermScreenCallbacks;
// Return the screen of the vterm.
@@ -590,6 +596,8 @@ VTermScreen *vterm_obtain_screen(VTerm *vt);
void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user);
void *vterm_screen_get_cbdata(VTermScreen *screen);
void vterm_screen_callbacks_has_pushline4(VTermScreen *screen);
void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user);
void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen);

View File

@@ -49,6 +49,7 @@ struct VTermScreen
const VTermScreenCallbacks *callbacks;
void *cbdata;
int callbacks_has_pushline4;
VTermDamageSize damage_merge;
/* start_row == -1 => no damage */
@@ -209,28 +210,41 @@ static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
return 1;
}
static void sb_pushline_from_row(VTermScreen *screen, int row)
static void sb_pushline_from_row(VTermScreen *screen, int row, int continuation)
{
VTermPos pos;
pos.row = row;
for(pos.col = 0; pos.col < screen->cols; pos.col++)
vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col);
(screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata);
if(screen->callbacks_has_pushline4 && screen->callbacks->sb_pushline4)
(screen->callbacks->sb_pushline4)(screen->cols, screen->sb_buffer, continuation, screen->cbdata);
else
(screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata);
}
static int premove(VTermRect rect, void *user)
{
VTermScreen *screen = user;
if(((screen->callbacks && screen->callbacks->sb_pushline) ||
(screen->callbacks_has_pushline4 && screen->callbacks && screen->callbacks->sb_pushline4)) &&
rect.start_row == 0 && rect.start_col == 0 && // starts top-left corner
rect.end_col == screen->cols && // full width
screen->buffer == screen->buffers[BUFIDX_PRIMARY]) { // not altscreen
for(int row = 0; row < rect.end_row; row++) {
const VTermLineInfo *lineinfo = vterm_state_get_lineinfo(screen->state, row);
sb_pushline_from_row(screen, row, lineinfo->continuation);
}
}
return 1;
}
static int moverect_internal(VTermRect dest, VTermRect src, void *user)
{
VTermScreen *screen = user;
if(screen->callbacks && screen->callbacks->sb_pushline &&
dest.start_row == 0 && dest.start_col == 0 && // starts top-left corner
dest.end_col == screen->cols && // full width
screen->buffer == screen->buffers[BUFIDX_PRIMARY]) { // not altscreen
for(int row = 0; row < src.start_row; row++)
sb_pushline_from_row(screen, row);
}
int cols = src.end_col - src.start_col;
int downward = src.start_row - dest.start_row;
@@ -685,9 +699,12 @@ static void resize_buffer(VTermScreen *screen, int bufidx, int new_rows, int new
if(old_row >= 0 && bufidx == BUFIDX_PRIMARY) {
/* Push spare lines to scrollback buffer */
if(screen->callbacks && screen->callbacks->sb_pushline)
for(int row = 0; row <= old_row; row++)
sb_pushline_from_row(screen, row);
if((screen->callbacks && screen->callbacks->sb_pushline) ||
(screen->callbacks_has_pushline4 && screen->callbacks && screen->callbacks->sb_pushline4))
for(int row = 0; row <= old_row; row++) {
const VTermLineInfo *lineinfo = old_lineinfo + row;
sb_pushline_from_row(screen, row, lineinfo->continuation);
}
if(active)
statefields->pos.row -= (old_row + 1);
}
@@ -880,6 +897,7 @@ static VTermStateCallbacks state_cbs = {
&resize, // resize
&setlineinfo, // setlineinfo
&sb_clear, //sb_clear
&premove, // premove
};
/*
@@ -914,6 +932,7 @@ static VTermScreen *screen_new(VTerm *vt)
screen->callbacks = NULL;
screen->cbdata = NULL;
screen->callbacks_has_pushline4 = 0;
screen->buffers[BUFIDX_PRIMARY] = alloc_buffer(screen, rows, cols);
@@ -927,6 +946,7 @@ static VTermScreen *screen_new(VTerm *vt)
}
vterm_state_set_callbacks(screen->state, &state_cbs, screen);
vterm_state_callbacks_has_premove(screen->state);
return screen;
}
@@ -1127,6 +1147,11 @@ void *vterm_screen_get_cbdata(VTermScreen *screen)
return screen->cbdata;
}
void vterm_screen_callbacks_has_pushline4(VTermScreen *screen)
{
screen->callbacks_has_pushline4 = 1;
}
void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user)
{
vterm_state_set_unrecognised_fallbacks(screen->state, fallbacks, user);

View File

@@ -78,6 +78,7 @@ static VTermState *vterm_state_new(VTerm *vt)
state->callbacks = NULL;
state->cbdata = NULL;
state->callbacks_has_premove = 0;
state->selection.callbacks = NULL;
state->selection.user = NULL;
@@ -133,6 +134,33 @@ static void scroll(VTermState *state, VTermRect rect, int downward, int rightwar
else if(rightward < -cols)
rightward = -cols;
if(state->callbacks_has_premove && state->callbacks && state->callbacks->premove) {
// TODO: technically this logic is wrong if both downward != 0 and rightward != 0
/* Work out what subsection of the destination area is about to be destroyed */
if(downward > 0)
/* about to destroy the top */
(*state->callbacks->premove)((VTermRect){
.start_row = rect.start_row, .end_row = rect.start_row + downward,
.start_col = rect.start_col, .end_col = rect.end_col}, state->cbdata);
else if(downward < 0)
/* about to destroy the bottom */
(*state->callbacks->premove)((VTermRect){
.start_row = rect.end_row + downward, .end_row = rect.end_row,
.start_col = rect.start_col, .end_col = rect.end_col}, state->cbdata);
if(rightward > 0)
/* about to destroy the left */
(*state->callbacks->premove)((VTermRect){
.start_row = rect.start_row, .end_row = rect.end_row,
.start_col = rect.start_col, .end_col = rect.start_col + rightward}, state->cbdata);
else if(rightward < 0)
/* about to destroy the right */
(*state->callbacks->premove)((VTermRect){
.start_row = rect.start_row, .end_row = rect.end_row,
.start_col = rect.end_col + rightward, .end_col = rect.end_col}, state->cbdata);
}
// Update lineinfo if full line
if(rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) {
int height = rect.end_row - rect.start_row - abs(downward);
@@ -2319,6 +2347,11 @@ void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *cal
}
}
void vterm_state_callbacks_has_premove(VTermState *state)
{
state->callbacks_has_premove = 1;
}
void *vterm_state_get_cbdata(VTermState *state)
{
return state->cbdata;

View File

@@ -70,6 +70,7 @@ struct VTermState
const VTermStateCallbacks *callbacks;
void *cbdata;
int callbacks_has_premove;
const VTermStateFallbacks *fallbacks;
void *fbdata;

View File

@@ -127,24 +127,28 @@ PUSH "\e[100;105r\eD"
PUSH "\e[5;2r\eD"
RESET
WANTSTATE -s+me
WANTSTATE -s+Pme
!Scroll Down move+erase emulation
PUSH "\e[S"
premove 0..1,0..80
moverect 1..25,0..80 -> 0..24,0..80
erase 24..25,0..80
?cursor = 0,0
PUSH "\e[2S"
premove 0..2,0..80
moverect 2..25,0..80 -> 0..23,0..80
erase 23..25,0..80
?cursor = 0,0
!Scroll Up move+erase emulation
PUSH "\e[T"
premove 24..25,0..80
moverect 0..24,0..80 -> 1..25,0..80
erase 0..1,0..80
?cursor = 0,0
PUSH "\e[2T"
premove 23..25,0..80
moverect 0..23,0..80 -> 2..25,0..80
erase 0..2,0..80
?cursor = 0,0

View File

@@ -268,7 +268,7 @@ PUSH "\e[2\"q"
PUSH "\eP\$q\"q\e\\"
output "\eP1\$r2\"q\e\\"
WANTSTATE -s+m
WANTSTATE -s+Pm
!ICH move+erase emuation
RESET
@@ -278,12 +278,14 @@ PUSH "ACD"
PUSH "\e[2D"
?cursor = 0,1
PUSH "\e[@"
premove 0..1,79..80
moverect 0..1,1..79 -> 0..1,2..80
erase 0..1,1..2
?cursor = 0,1
PUSH "B"
?cursor = 0,2
PUSH "\e[3@"
premove 0..1,77..80
moverect 0..1,2..77 -> 0..1,5..80
erase 0..1,2..5
@@ -295,10 +297,12 @@ PUSH "ABBC"
PUSH "\e[3D"
?cursor = 0,1
PUSH "\e[P"
premove 0..1,1..2
moverect 0..1,2..80 -> 0..1,1..79
erase 0..1,79..80
?cursor = 0,1
PUSH "\e[3P"
premove 0..1,1..4
moverect 0..1,4..80 -> 0..1,1..77
erase 0..1,77..80
?cursor = 0,1

View File

@@ -146,9 +146,9 @@ PUSH "\e[25H\r\nABCDE\b\b\b\e[2P\r\n"
sb_pushline 80 =
moverect 1..25,0..80 -> 0..24,0..80
damage 24..25,0..80 = 24<41 42 43 44 45>
sb_pushline 80 =
moverect 24..25,4..80 -> 24..25,2..78
damage 24..25,78..80
sb_pushline 80 =
DAMAGEFLUSH
moverect 1..25,0..80 -> 0..24,0..80
damage 24..25,0..80

View File

@@ -0,0 +1,17 @@
INIT
WANTSTATE
WANTSCREEN b
RESET
!Spillover text marks continuation on second line
PUSH "A"x85
PUSH "\r\n"
?lineinfo 0 =
?lineinfo 1 = cont
!Continuation mark sent to sb_pushline
PUSH "\n"x23
sb_pushline 80 = 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
PUSH "\n"
sb_pushline 80 cont = 41 41 41 41 41

View File

@@ -326,6 +326,18 @@ static int movecursor(VTermPos pos, VTermPos oldpos UNUSED, int visible UNUSED,
return 1;
}
static int want_premove = 0;
static int premove(VTermRect rect, void *user UNUSED)
{
if(!want_premove)
return 0;
printf("premove %d..%d,%d..%d\n",
rect.start_row, rect.end_row, rect.start_col, rect.end_col);
return 1;
}
static int want_scrollrect = 0;
static int scrollrect(VTermRect rect, int downward, int rightward, void *user UNUSED)
{
@@ -509,6 +521,7 @@ VTermStateCallbacks state_cbs = {
NULL, // resize
state_setlineinfo, // setlineinfo
state_sb_clear, // sb_clear
premove, // premove
};
static int selection_set(VTermSelectionMask mask, VTermStringFragment frag, void *user UNUSED)
@@ -590,7 +603,7 @@ static int screen_damage(VTermRect rect, void *user UNUSED)
}
static int want_screen_scrollback = 0;
static int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user UNUSED)
static int screen_sb_pushline4(int cols, const VTermScreenCell *cells, int continuation, void *user UNUSED)
{
int eol;
int c;
@@ -602,7 +615,7 @@ static int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user
while(eol && !cells[eol-1].chars[0])
eol--;
printf("sb_pushline %d =", cols);
printf("sb_pushline %d%s =", cols, continuation ? " cont" : "");
for(c = 0; c < eol; c++)
printf(" %02X", cells[c].chars[0]);
printf("\n");
@@ -647,9 +660,10 @@ VTermScreenCallbacks screen_cbs = {
settermprop, // settermprop
NULL, // bell
NULL, // resize
screen_sb_pushline, // sb_pushline
NULL, // sb_pushline
screen_sb_popline, // sb_popline
screen_sb_clear, // sb_clear
screen_sb_pushline4, // sb_pushline4
};
int main(int argc UNUSED, char **argv UNUSED)
@@ -689,6 +703,7 @@ int main(int argc UNUSED, char **argv UNUSED)
if(!state) {
state = vterm_obtain_state(vt);
vterm_state_set_callbacks(state, &state_cbs, NULL);
vterm_state_callbacks_has_premove(state);
/* In some tests we want to check the behaviour of overflowing the
* buffer, so make it nicely small
*/
@@ -713,6 +728,9 @@ int main(int argc UNUSED, char **argv UNUSED)
case 's':
want_scrollrect = sense;
break;
case 'P':
want_premove = sense;
break;
case 'm':
want_moverect = sense;
break;
@@ -740,6 +758,7 @@ int main(int argc UNUSED, char **argv UNUSED)
if(!screen)
screen = vterm_obtain_screen(vt);
vterm_screen_set_callbacks(screen, &screen_cbs, NULL);
vterm_screen_callbacks_has_pushline4(screen);
while(line[i] == ' ')
i++;

View File

@@ -124,7 +124,7 @@ sub do_line
elsif( $line =~ m/^putglyph (\S+) (.*)$/ ) {
$line = "putglyph " . join( ",", map sprintf("%x", $_), eval($1) ) . " $2";
}
elsif( $line =~ m/^(?:movecursor|scrollrect|moverect|erase|damage|sb_pushline|sb_popline|sb_clear|settermprop|setmousefunc|selection-query) ?/ ) {
elsif( $line =~ m/^(?:movecursor|scrollrect|premove|moverect|erase|damage|sb_pushline|sb_popline|sb_clear|settermprop|setmousefunc|selection-query) ?/ ) {
# no conversion
}
elsif( $line =~ m/^(selection-set) (.*?) (\[?)(.*?)(\]?)$/ ) {

View File

@@ -1368,7 +1368,8 @@ list_slice_or_index(
{
// copy the item to "var1" to avoid that freeing the list makes it
// invalid.
listitem_T *li = check_range_index_one(list, (long *)&n1, TRUE, TRUE);
long index = n1;
listitem_T *li = check_range_index_one(list, &index, TRUE, TRUE);
if (li == NULL)
return FAIL;
copy_tv(&li->li_tv, &var1);

View File

@@ -21,6 +21,9 @@ static long mouse_hor_step = 6;
static long mouse_vert_step = 3;
static win_T *dragwin = NULL; // window being dragged
static int stl_click_handler(win_T *wp, int mcol, int which_button, int mods);
static int stl_click_handler_regions(stl_click_region_T *regions,
int region_count, int winid, int mcol,
int which_button, int mods);
void
mouse_set_vert_scroll_step(long step)
@@ -502,6 +505,16 @@ do_mouse(
// Check for clicking in the tab page line.
if (TabPageIdxs != NULL && mouse_row == 0 && firstwin->w_winrow > 0)
{
// Dispatch 'tabline' %[FuncName] click regions before falling through
// to tab-page selection. On drag events fall through to the normal
// tab-drag handling.
if (is_click && !is_drag
&& stl_click_handler_regions(tabline_stl_click,
tabline_stl_click_count,
0, mouse_col, which_button,
mod_mask))
return FALSE;
tp_label.just_in = true;
tp_label.nr = TabPageIdxs[mouse_col];
@@ -1640,11 +1653,19 @@ mouse_model_popup(void)
}
/*
* Call a statusline click handler function.
* Call a click-region callback function.
* "regions"/"region_count" describe the resolved click regions,
* "winid" is stored as the "winid" key in the info dict (0 for tabline).
* Returns TRUE if the function was called and handled the click.
*/
static int
stl_click_handler(win_T *wp, int mcol, int which_button, int mods)
stl_click_handler_regions(
stl_click_region_T *regions,
int region_count,
int winid,
int mcol,
int which_button,
int mods)
{
#ifdef FEAT_EVAL
int n;
@@ -1658,17 +1679,16 @@ stl_click_handler(win_T *wp, int mcol, int which_button, int mods)
funcexe_T funcexe;
int col = mcol;
if (wp == NULL || wp->w_stl_click == NULL || wp->w_stl_click_count == 0)
if (regions == NULL || region_count == 0)
return FALSE;
// Find the click region at the given column.
for (n = 0; n < wp->w_stl_click_count; n++)
for (n = 0; n < region_count; n++)
{
if (col >= wp->w_stl_click[n].col_start
&& col < wp->w_stl_click[n].col_end)
if (col >= regions[n].col_start && col < regions[n].col_end)
break;
}
if (n >= wp->w_stl_click_count || wp->w_stl_click[n].funcname == NULL)
if (n >= region_count || regions[n].funcname == NULL)
return FALSE;
// Build the info dictionary.
@@ -1676,7 +1696,7 @@ stl_click_handler(win_T *wp, int mcol, int which_button, int mods)
if (info == NULL)
return FALSE;
dict_add_number(info, "minwid", wp->w_stl_click[n].minwid);
dict_add_number(info, "minwid", regions[n].minwid);
// Determine number of clicks.
// MOD_MASK_2CLICK=0x20, MOD_MASK_3CLICK=0x40, MOD_MASK_4CLICK=0x60
@@ -1705,7 +1725,13 @@ stl_click_handler(win_T *wp, int mcol, int which_button, int mods)
mods_str[mi] = NUL;
dict_add_string(info, "mods", mods_str);
dict_add_number(info, "winid", wp->w_id);
dict_add_number(info, "winid", winid);
// "area": which option the clicked region belongs to. Lets a shared
// dispatcher distinguish 'statusline' from 'tabline' (and future areas)
// without having to overload winid == 0.
dict_add_string(info, "area",
winid == 0 ? (char_u *)"tabline" : (char_u *)"statusline");
// Call the function with the info dict as argument.
argvars[0].v_type = VAR_DICT;
@@ -1718,7 +1744,7 @@ stl_click_handler(win_T *wp, int mcol, int which_button, int mods)
CLEAR_FIELD(funcexe);
funcexe.fe_evaluate = TRUE;
(void)call_func(wp->w_stl_click[n].funcname, -1,
(void)call_func(regions[n].funcname, -1,
&rettv, 1, argvars, &funcexe);
n = (int)rettv.vval.v_number;
@@ -1726,11 +1752,20 @@ stl_click_handler(win_T *wp, int mcol, int which_button, int mods)
dict_unref(info);
if (n != 0)
{
// Make sure the tabline gets redrawn too when the callback asks for
// a redraw (redraw_statuslines() only redraws the tabline when
// redraw_tabline is set).
if (winid == 0)
redraw_tabline = TRUE;
redraw_statuslines();
}
return TRUE;
#else
(void)wp;
(void)regions;
(void)region_count;
(void)winid;
(void)mcol;
(void)which_button;
(void)mods;
@@ -1738,6 +1773,19 @@ stl_click_handler(win_T *wp, int mcol, int which_button, int mods)
#endif
}
/*
* Call a statusline click handler function for window "wp".
* Returns TRUE if the function was called and handled the click.
*/
static int
stl_click_handler(win_T *wp, int mcol, int which_button, int mods)
{
if (wp == NULL)
return FALSE;
return stl_click_handler_regions(wp->w_stl_click, wp->w_stl_click_count,
wp->w_id, mcol, which_button, mods);
}
// dragwin is declared near the top of the file
/*

View File

@@ -123,6 +123,8 @@ typedef struct
char_u *pattern;
int nsubexp; // number of ()
int nstate;
void *listbuf[2]; // cached list buffers for
// nfa_regmatch()
nfa_state_T state[1]; // actually longer..
} nfa_regprog_T;

View File

@@ -5797,9 +5797,21 @@ nfa_regmatch(
// Allocate memory for the lists of nodes.
size = (prog->nstate + 1) * sizeof(nfa_thread_T);
list[0].t = alloc(size);
// Reuse cached list buffers from prog when available (top-level call).
// Recursive calls must allocate their own buffers.
if (toplevel && prog->listbuf[0] != NULL)
{
list[0].t = (nfa_thread_T *)prog->listbuf[0];
list[1].t = (nfa_thread_T *)prog->listbuf[1];
prog->listbuf[0] = NULL;
prog->listbuf[1] = NULL;
}
else
{
list[0].t = alloc(size);
list[1].t = alloc(size);
}
list[0].len = prog->nstate + 1;
list[1].t = alloc(size);
list[1].len = prog->nstate + 1;
if (list[0].t == NULL || list[1].t == NULL)
goto theend;
@@ -7287,9 +7299,18 @@ nextchar:
#endif
theend:
// Free memory
vim_free(list[0].t);
vim_free(list[1].t);
// Cache list buffers in prog for reuse, or free if prog already has
// cached buffers (recursive call case).
if (prog->listbuf[0] == NULL && list[0].t != NULL && list[1].t != NULL)
{
prog->listbuf[0] = list[0].t;
prog->listbuf[1] = list[1].t;
}
else
{
vim_free(list[0].t);
vim_free(list[1].t);
}
vim_free(listids);
#undef ADD_STATE_IF_MATCH
#ifdef NFA_REGEXP_DEBUG_LOG
@@ -7644,6 +7665,8 @@ nfa_regcomp(char_u *expr, int re_flags)
goto fail;
state_ptr = prog->state;
prog->re_in_use = FALSE;
prog->listbuf[0] = NULL;
prog->listbuf[1] = NULL;
/*
* PASS 2
@@ -7707,6 +7730,8 @@ nfa_regfree(regprog_T *prog)
vim_free(((nfa_regprog_T *)prog)->match_text);
vim_free(((nfa_regprog_T *)prog)->pattern);
vim_free(((nfa_regprog_T *)prog)->listbuf[0]);
vim_free(((nfa_regprog_T *)prog)->listbuf[1]);
vim_free(prog);
}

View File

@@ -1486,23 +1486,41 @@ win_redr_custom(
TabPageIdxs[col++] = fillchar;
}
// Resolve click function regions for statusline.
if (wp != NULL && !draw_ruler)
// Resolve click function regions for statusline or tabline.
if (!draw_ruler)
{
int click_count = 0;
stl_click_region_T **out_regions;
int *out_count;
int base_col;
int click_count = 0;
if (wp != NULL)
{
out_regions = &wp->w_stl_click;
out_count = &wp->w_stl_click_count;
base_col = wp->w_wincol;
}
else
{
// 'tabline': store regions in global state since there is no
// associated window.
out_regions = &tabline_stl_click;
out_count = &tabline_stl_click_count;
base_col = firstwin->w_wincol;
}
// Count the click regions.
for (n = 0; clicktab[n].start != NULL; n++)
click_count++;
// Free old click regions.
if (wp->w_stl_click != NULL)
if (*out_regions != NULL)
{
for (n = 0; n < wp->w_stl_click_count; n++)
vim_free(wp->w_stl_click[n].funcname);
VIM_CLEAR(wp->w_stl_click);
for (n = 0; n < *out_count; n++)
vim_free((*out_regions)[n].funcname);
VIM_CLEAR(*out_regions);
}
wp->w_stl_click_count = 0;
*out_count = 0;
if (click_count > 0)
{
@@ -1514,7 +1532,7 @@ win_redr_custom(
{
char_u *cur_funcname = NULL;
int cur_minwid = 0;
int region_start = wp->w_wincol;
int region_start = base_col;
// Walk through click records converting buffer positions
// to screen columns.
@@ -1530,7 +1548,7 @@ win_redr_custom(
if (cur_funcname != NULL)
{
regions[rcount].col_start = region_start;
regions[rcount].col_end = wp->w_wincol + len;
regions[rcount].col_end = base_col + len;
regions[rcount].funcname =
vim_strsave(cur_funcname);
regions[rcount].minwid = cur_minwid;
@@ -1539,22 +1557,22 @@ win_redr_custom(
cur_funcname = clicktab[n].funcname;
cur_minwid = clicktab[n].minwid;
region_start = wp->w_wincol + len;
region_start = base_col + len;
}
// Close final region if it extends to the end.
if (cur_funcname != NULL)
{
regions[rcount].col_start = region_start;
regions[rcount].col_end = wp->w_wincol + maxwidth;
regions[rcount].col_end = base_col + maxwidth;
regions[rcount].funcname =
vim_strsave(cur_funcname);
regions[rcount].minwid = cur_minwid;
rcount++;
}
wp->w_stl_click = regions;
wp->w_stl_click_count = rcount;
*out_regions = regions;
*out_count = rcount;
}
}

View File

@@ -61,9 +61,11 @@ typedef struct {
typedef struct sb_line_S {
int sb_cols; // can differ per line
int sb_bytes; // length in bytes of text
cellattr_T *sb_cells; // allocated
cellattr_T sb_fill_attr; // for short line
char_u *sb_text; // for tl_scrollback_postponed
char_u continuation;
} sb_line_T;
#ifdef MSWIN
@@ -148,6 +150,8 @@ struct terminal_S {
garray_T tl_scrollback;
int tl_scrollback_scrolled;
garray_T tl_scrollback_postponed;
int tl_scrollback_snapshot;
int tl_buffer_scrolled;
char_u *tl_highlight_name; // replaces "Terminal"; allocated
@@ -1386,6 +1390,112 @@ update_cursor(term_T *term, int redraw)
}
}
/*
* Find the location of a scrollbackline in the buffer
*/
static void
scrollbackline_pos_in_buf(term_T *term, int row, linenr_T *lnum, int *start_col, size_t *start_pos)
{
sb_line_T *lines = (sb_line_T *)term->tl_scrollback.ga_data;
linenr_T calc_lnum = term->tl_buffer_scrolled;
size_t calc_pos = 0;
int calc_col = 0;
int i;
if (row < 0 || row >= term->tl_scrollback.ga_len)
return;
if (row > term->tl_scrollback_scrolled)
{
// Lookback how far along in the top line we are
for (i = term->tl_scrollback_scrolled + 1; i > 0 && lines[i].continuation; --i)
{
calc_pos += lines[i - 1].sb_bytes;
calc_col += lines[i - 1].sb_cols;
}
i = term->tl_scrollback_scrolled + 1;
calc_lnum = term->tl_buffer_scrolled + 1;
// Do not count this line's bytes/cols twice
if (lines[i].continuation)
++i;
}
else
{
i = 1;
calc_lnum = 1;
}
for (; i <= row; ++i)
{
if (!lines[i].continuation)
{
++calc_lnum;
calc_pos = 0;
calc_col = 0;
}
else
{
calc_pos += lines[i - 1].sb_bytes;
calc_col += lines[i - 1].sb_cols;
}
}
*lnum = calc_lnum;
if (start_col)
*start_col = calc_col;
if (start_pos)
*start_pos = calc_pos;
}
/*
* Find the location of a buffer line in the scrollback
*/
static void
bufline_pos_in_scrollback(term_T *term, linenr_T lnum, int col, int *row, int *wrapped_col)
{
buf_T *buf = term->tl_buffer;
sb_line_T *lines = (sb_line_T *)term->tl_scrollback.ga_data;
linenr_T calc_row = term->tl_scrollback_scrolled;
int calc_col = col;
linenr_T l;
if (lnum > buf->b_ml.ml_line_count)
return;
if (lnum > term->tl_buffer_scrolled)
{
calc_row = term->tl_scrollback_scrolled;
l = term->tl_buffer_scrolled + 1;
while (calc_row < term->tl_scrollback.ga_len && lines[calc_row].continuation)
++calc_row;
}
else
{
calc_row = 0;
l = 1;
}
while (calc_row < term->tl_scrollback.ga_len && l < lnum)
{
++calc_row;
if (!lines[calc_row].continuation)
++l;
}
while (calc_row + 1 < term->tl_scrollback.ga_len && lines[calc_row + 1].continuation
&& calc_col >= lines[calc_row].sb_cols)
{
calc_col -= lines[calc_row].sb_cols;
++calc_row;
}
if (row)
*row = calc_row;
if (wrapped_col)
*wrapped_col = calc_col;
}
/*
* Invoked when "msg" output from a job was received. Write it to the terminal
* of "buffer".
@@ -1908,13 +2018,14 @@ term_try_stop_job(buf_T *buf)
* Add the last line of the scrollback buffer to the buffer in the window.
*/
static void
add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
add_scrollback_line_to_buffer(term_T *term, char_u *text, int len, int append)
{
buf_T *buf = term->tl_buffer;
int empty = (buf->b_ml.ml_flags & ML_EMPTY);
linenr_T lnum = buf->b_ml.ml_line_count;
#ifdef MSWIN
char_u *tmp = text;
if (!enc_utf8 && enc_codepage > 0)
{
WCHAR *ret = NULL;
@@ -1927,13 +2038,32 @@ add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
WideCharToMultiByte_alloc(enc_codepage, 0,
ret, length, (char **)&text, &len, 0, 0);
vim_free(ret);
ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
vim_free(text);
}
}
else
#endif
ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
if (append)
{
char_u *prev_text = ml_get_buf(buf, lnum, FALSE);
size_t prev_len = STRLEN(prev_text);
char_u *both = alloc(len + prev_len + 2);
if (both == NULL)
return;
vim_strncpy(both, prev_text, prev_len + 1);
vim_strncpy(both + prev_len, text, len + 1);
curbuf = buf;
ml_replace(lnum, both, FALSE);
curbuf = curwin->w_buffer;
}
else
ml_append_buf(buf, lnum, text, len + 1, FALSE);
#ifdef MSWIN
if (tmp != text)
vim_free(text);
#endif
if (empty)
{
// Delete the empty line that was in the empty buffer.
@@ -1989,6 +2119,7 @@ add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
}
}
line->sb_cols = 0;
line->sb_bytes = 0;
line->sb_cells = NULL;
line->sb_fill_attr = *fill_attr;
++term->tl_scrollback.ga_len;
@@ -2005,16 +2136,33 @@ cleanup_scrollback(term_T *term)
{
sb_line_T *line;
garray_T *gap;
char_u *bufline;
size_t bufline_length;
curbuf = term->tl_buffer;
gap = &term->tl_scrollback;
while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
&& gap->ga_len > 0)
bufline = ml_get_buf(curbuf, curbuf->b_ml.ml_line_count, FALSE);
bufline_length = STRLEN(bufline);
while (term->tl_scrollback_snapshot && gap->ga_len > 0)
{
ml_delete(curbuf->b_ml.ml_line_count);
line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
if (line->sb_bytes < 0 || (size_t)line->sb_bytes > bufline_length)
break;
bufline_length -= line->sb_bytes;
if (!bufline_length)
{
ml_delete(curbuf->b_ml.ml_line_count);
bufline = ml_get_buf(curbuf, curbuf->b_ml.ml_line_count, FALSE);
bufline_length = STRLEN(bufline);
}
vim_free(line->sb_cells);
--gap->ga_len;
--term->tl_scrollback_snapshot;
}
if (bufline_length < STRLEN(bufline))
{
char_u *shortened = vim_strnsave(bufline, bufline_length);
ml_replace(curbuf->b_ml.ml_line_count, shortened, FALSE);
}
curbuf = curwin->w_buffer;
if (curbuf == term->tl_buffer)
@@ -2028,6 +2176,7 @@ cleanup_scrollback(term_T *term)
update_snapshot(term_T *term)
{
VTermScreen *screen;
VTermState *state;
int len;
int lines_skipped = 0;
VTermPos pos;
@@ -2043,6 +2192,7 @@ update_snapshot(term_T *term)
cleanup_scrollback(term);
screen = vterm_obtain_screen(term->tl_vterm);
state = vterm_obtain_state(term->tl_vterm);
fill_attr = new_fill_attr = term->tl_default_color;
for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
{
@@ -2067,7 +2217,10 @@ update_snapshot(term_T *term)
// Line was skipped, add an empty line.
--lines_skipped;
if (add_empty_scrollback(term, &fill_attr, 0) == OK)
add_scrollback_line_to_buffer(term, (char_u *)"", 0);
{
add_scrollback_line_to_buffer(term, (char_u *)"", 0, 0);
++term->tl_scrollback_snapshot;
}
}
if (len == 0)
@@ -2079,9 +2232,12 @@ update_snapshot(term_T *term)
{
garray_T ga;
int width;
const VTermLineInfo *lineinfo;
sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
+ term->tl_scrollback.ga_len;
lineinfo = vterm_state_get_lineinfo(state, pos.row);
ga_init2(&ga, 1, 100);
for (pos.col = 0; pos.col < len; pos.col += width)
{
@@ -2116,17 +2272,21 @@ update_snapshot(term_T *term)
}
}
line->sb_cols = len;
line->sb_bytes = ga.ga_len;
line->sb_cells = p;
line->sb_fill_attr = new_fill_attr;
line->continuation = (char_u)lineinfo->continuation;
fill_attr = new_fill_attr;
++term->tl_scrollback.ga_len;
++term->tl_scrollback_snapshot;
if (ga_grow(&ga, 1) == FAIL)
add_scrollback_line_to_buffer(term, (char_u *)"", 0);
add_scrollback_line_to_buffer(term, (char_u *)"", 0, 0);
else
{
*((char_u *)ga.ga_data + ga.ga_len) = NUL;
add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len,
lineinfo->continuation);
}
ga_clear(&ga);
}
@@ -2141,7 +2301,10 @@ update_snapshot(term_T *term)
++pos.row)
{
if (add_empty_scrollback(term, &fill_attr, 0) == OK)
add_scrollback_line_to_buffer(term, (char_u *)"", 0);
{
add_scrollback_line_to_buffer(term, (char_u *)"", 0, 0);
++term->tl_scrollback_snapshot;
}
}
term->tl_dirty_snapshot = FALSE;
@@ -2186,7 +2349,7 @@ may_move_terminal_to_buffer(term_T *term, int redraw)
// Update the snapshot only if something changes or the buffer does not
// have all the lines.
if (term->tl_dirty_snapshot || term->tl_buffer->b_ml.ml_line_count
<= term->tl_scrollback_scrolled)
<= term->tl_buffer_scrolled)
update_snapshot(term);
if (redraw)
@@ -2282,7 +2445,9 @@ cleanup_vterm(term_T *term)
static void
term_enter_normal_mode(void)
{
term_T *term = curbuf->b_term;
term_T *term = curbuf->b_term;
linenr_T lnum;
int col;
set_terminal_mode(term, TRUE);
@@ -2291,15 +2456,16 @@ term_enter_normal_mode(void)
// Move the window cursor to the position of the cursor in the
// terminal.
curwin->w_cursor.lnum = term->tl_scrollback_scrolled
+ term->tl_cursor_pos.row + 1;
lnum = term->tl_buffer_scrolled + 1 + term->tl_cursor_pos.row;
col = term->tl_cursor_pos.col;
scrollbackline_pos_in_buf(term, term->tl_cursor_pos.row + term->tl_scrollback_scrolled, &lnum, &col, NULL);
curwin->w_cursor.lnum = lnum;
check_cursor();
if (coladvance(term->tl_cursor_pos.col) == FAIL)
if (coladvance(col) == FAIL)
coladvance(MAXCOL);
curwin->w_set_curswant = TRUE;
// Display the same lines as in the terminal.
curwin->w_topline = term->tl_scrollback_scrolled + 1;
curwin->w_topline = term->tl_buffer_scrolled + 1;
}
/*
@@ -3483,20 +3649,27 @@ limit_scrollback(term_T *term, garray_T *gap, int update_buffer)
int todo = MAX(term->tl_buffer->b_p_twsl / 10,
gap->ga_len - term->tl_buffer->b_p_twsl);
int i;
sb_line_T *sb_lines = (sb_line_T *)gap->ga_data;
curbuf = term->tl_buffer;
for (i = 0; i < todo; ++i)
{
vim_free(((sb_line_T *)gap->ga_data + i)->sb_cells);
if (update_buffer)
if (update_buffer && (!sb_lines[i].continuation || !i))
{
ml_delete(1);
--term->tl_buffer_scrolled;
}
vim_free(sb_lines[i].sb_cells);
}
// Continue until end of wrapped line
for (; todo < gap->ga_len && sb_lines[todo].continuation; ++todo)
vim_free(sb_lines[todo].sb_cells);
curbuf = curwin->w_buffer;
gap->ga_len -= todo;
mch_memmove(gap->ga_data,
(sb_line_T *)gap->ga_data + todo,
sizeof(sb_line_T) * gap->ga_len);
(sb_line_T *)gap->ga_data + todo,
sizeof(sb_line_T) * gap->ga_len);
if (update_buffer)
{
win_T *curwin_save = curwin;
@@ -3521,7 +3694,7 @@ limit_scrollback(term_T *term, garray_T *gap, int update_buffer)
* Handle a line that is pushed off the top of the screen.
*/
static int
handle_pushline(int cols, const VTermScreenCell *cells, void *user)
handle_pushline(int cols, const VTermScreenCell *cells, int continuation, void *user)
{
term_T *term = (term_T *)user;
garray_T *gap;
@@ -3600,16 +3773,20 @@ handle_pushline(int cols, const VTermScreenCell *cells, void *user)
*(text + text_len) = NUL;
}
if (update_buffer)
add_scrollback_line_to_buffer(term, text, text_len);
add_scrollback_line_to_buffer(term, text, text_len, continuation);
line = (sb_line_T *)gap->ga_data + gap->ga_len;
line->sb_cols = len;
line->sb_bytes = text_len;
line->sb_cells = p;
line->sb_fill_attr = fill_attr;
line->continuation = (char_u)continuation;
if (update_buffer)
{
line->sb_text = NULL;
++term->tl_scrollback_scrolled;
if (!continuation)
++term->tl_buffer_scrolled;
ga_clear(&ga); // free the text
}
else
@@ -3651,17 +3828,20 @@ handle_postponed_scrollback(term_T *term)
text = pp_line->sb_text;
if (text == NULL)
text = (char_u *)"";
add_scrollback_line_to_buffer(term, text, (int)STRLEN(text));
add_scrollback_line_to_buffer(term, text, (int)STRLEN(text), pp_line->continuation);
vim_free(pp_line->sb_text);
line = (sb_line_T *)term->tl_scrollback.ga_data
+ term->tl_scrollback.ga_len;
line->sb_cols = pp_line->sb_cols;
line->sb_bytes = pp_line->sb_bytes;
line->sb_cells = pp_line->sb_cells;
line->sb_fill_attr = pp_line->sb_fill_attr;
line->sb_text = NULL;
++term->tl_scrollback_scrolled;
++term->tl_scrollback.ga_len;
if (!pp_line->continuation)
++term->tl_buffer_scrolled;
}
ga_clear(&term->tl_scrollback_postponed);
@@ -3685,9 +3865,10 @@ static VTermScreenCallbacks screen_callbacks = {
handle_settermprop, // settermprop
handle_bell, // bell
handle_resize, // resize
handle_pushline, // sb_pushline
NULL, // sb_pushline
NULL, // sb_popline
NULL // sb_clear
NULL, // sb_clear
handle_pushline // sb_pushline4
};
/*
@@ -4243,16 +4424,21 @@ term_get_attr(win_T *wp, linenr_T lnum, int col)
term_T *term = buf->b_term;
sb_line_T *line;
cellattr_T *cellattr;
int sb_line = -1;
int sb_col = col;
if (lnum > term->tl_scrollback.ga_len)
if (term->tl_scrollback.ga_len)
bufline_pos_in_scrollback(term, lnum, col, &sb_line, &sb_col);
if (sb_line < 0)
cellattr = &term->tl_default_color;
else
{
line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
if (col < 0 || col >= line->sb_cols)
line = (sb_line_T *)term->tl_scrollback.ga_data + sb_line;
if (sb_col < 0 || sb_col >= line->sb_cols)
cellattr = &line->sb_fill_attr;
else
cellattr = line->sb_cells + col;
cellattr = line->sb_cells + sb_col;
}
return cell2attr(term, wp, &cellattr->attrs, &cellattr->fg, &cellattr->bg);
}
@@ -4964,6 +5150,7 @@ create_vterm(term_T *term, int rows, int cols)
vterm_screen_set_callbacks(screen, &screen_callbacks, term);
vterm_screen_set_damage_merge(screen, VTERM_DAMAGE_SCROLL);
vterm_screen_callbacks_has_pushline4(screen);
// TODO: depends on 'encoding'.
vterm_set_utf8(vterm, 1);
@@ -5478,8 +5665,10 @@ read_dump_file(FILE *fd, VTermPos *cursor_pos)
if (max_cells < ga_cell.ga_len)
max_cells = ga_cell.ga_len;
line->sb_cols = ga_cell.ga_len;
line->sb_bytes = ga_text.ga_len;
line->sb_cells = ga_cell.ga_data;
line->sb_fill_attr = term->tl_default_color;
line->continuation = 0;
++term->tl_scrollback.ga_len;
ga_init(&ga_cell);
@@ -6300,11 +6489,24 @@ f_term_getline(typval_T *argvars, typval_T *rettv)
if (term->tl_vterm == NULL)
{
linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
linenr_T lnum = 0;
size_t offset = 0;
int sb_row = term->tl_scrollback_scrolled + row;
sb_line_T *line;
if (sb_row < 0 || sb_row >= term->tl_scrollback.ga_len)
return;
line = (sb_line_T *)term->tl_scrollback.ga_data + sb_row;
scrollbackline_pos_in_buf(term, sb_row, &lnum, NULL, &offset);
// vterm is finished, get the text from the buffer
if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
{
char_u *p = ml_get_buf(buf, lnum, FALSE);
if (STRLEN(p) >= offset + line->sb_bytes)
rettv->vval.v_string = vim_strnsave(p + offset, line->sb_bytes);
}
}
else
{
@@ -6343,7 +6545,7 @@ f_term_getscrolled(typval_T *argvars, typval_T *rettv)
buf = term_get_buf(argvars, "term_getscrolled()");
if (buf == NULL)
return;
rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
rettv->vval.v_number = buf->b_term->tl_buffer_scrolled;
}
/*
@@ -6557,12 +6759,22 @@ f_term_scrape(typval_T *argvars, typval_T *rettv)
}
else
{
linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
int sb_row = term->tl_scrollback_scrolled + pos.row;
linenr_T lnum = 0;
size_t offset = 0;
if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
scrollbackline_pos_in_buf(term, sb_row, &lnum, NULL, &offset);
if (sb_row >= term->tl_scrollback.ga_len || lnum <= 0 || lnum > buf->b_ml.ml_line_count)
return;
p = ml_get_buf(buf, lnum + 1, FALSE);
line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
line = (sb_line_T *)term->tl_scrollback.ga_data + sb_row;
p = ml_get_buf(buf, lnum, FALSE);
if (STRLEN(p) < offset + line->sb_bytes)
return;
p += offset;
}
for (pos.col = 0; pos.col < term->tl_cols; )

View File

@@ -1889,4 +1889,82 @@ func Test_guioptions_clipboard()
let &guioptions = save_guioptions
endfunc
" Tests for GUI window geometry: initial size from -geometry option and
" size stability after :tabnew / :tabclose.
"
" Background: on GTK3 with client-side decorations (Wayland), the window
" compositor subtracts the CSD frame from the requested size, causing the
" window to open a few pixels too small (wrong &columns/&lines) and to shrink
" further with each :tabnew/:tabclose cycle.
" Test that a GUI window opened with -geometry=WxH has exactly W columns
" and H lines.
"
" Without the CSD fix, on GTK3/Wayland the compositor subtracts the frame
" margin from the requested pixel size, so the window is a character cell too
" narrow and too short.
func Test_geometry_exact_size()
CheckCanRunGui
CheckFeature gui_gtk
let after =<< trim [CODE]
call writefile([string(&columns), string(&lines)], 'Xtest_geomsize')
qall
[CODE]
" Hide the menu bar so it does not widen the minimum window size.
if RunVim(['set guioptions-=m'], after, '-f -g -geometry 40x15')
let result = readfile('Xtest_geomsize')
call assert_equal('40', result[0], 'columns should match -geometry width')
call assert_equal('15', result[1], 'lines should match -geometry height')
endif
call delete('Xtest_geomsize')
endfunc
" Test that the window size is unchanged after opening and closing a tab.
"
" Each :tabnew/:tabclose cycle triggers a tabline show/hide, which causes
" asynchronous GTK layout events. Without the fix, stale configure events
" from these layout passes are mis-interpreted as user resizes, reducing
" &columns and &lines with every cycle. Three cycles are performed to
" amplify any drift.
func Test_tabnew_tabclose_size_stable()
CheckCanRunGui
CheckFeature gui_gtk
let after =<< trim [CODE]
let cols0 = &columns
let rows0 = &lines
tabnew
sleep 300m
tabclose
sleep 300m
tabnew
sleep 300m
tabclose
sleep 300m
tabnew
sleep 300m
tabclose
sleep 300m
call writefile([string(cols0), string(rows0), string(&columns), string(&lines)], 'Xtest_tabsize')
qall
[CODE]
if RunVim(['set guioptions-=m'], after, '-f -g -geometry 40x15')
let result = readfile('Xtest_tabsize')
call assert_equal('40', result[0], 'initial columns should match -geometry width')
call assert_equal('15', result[1], 'initial lines should match -geometry height')
call assert_equal(result[0], result[2],
\ 'columns changed after 3x tabnew/tabclose: '
\ .. result[0] .. ' -> ' .. result[2])
call assert_equal(result[1], result[3],
\ 'lines changed after 3x tabnew/tabclose: '
\ .. result[1] .. ' -> ' .. result[3])
endif
call delete('Xtest_tabsize')
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

@@ -752,6 +752,7 @@ func Test_statusline_click_handler()
call assert_equal(1, g:stl_click_info.nclicks)
call assert_equal(0, g:stl_click_info.minwid)
call assert_equal(win_getid(), g:stl_click_info.winid)
call assert_equal('statusline', g:stl_click_info.area)
unlet! g:stl_click_info
" Click outside click region (on the filename part)
@@ -873,4 +874,60 @@ func Test_statusline_click_linebreak_still_works()
let &laststatus = save_ls
endfunc
func Test_tabline_click_handler()
let save_mouse = &mouse
let save_tal = &tabline
let save_stal = &showtabline
if has('gui')
let save_go = &guioptions
set guioptions-=e
endif
set mouse=a
set showtabline=2
" Two adjacent click regions in 'tabline' with different minwid.
set tabline=%1[StlClickTestFunc][AAA]%[]%2[StlClickTestFunc][BBB]%[]
redraw!
" Click on [AAA] region (tabline is row 1).
call test_setmouse(1, 2)
call feedkeys("\<LeftMouse>\<LeftRelease>", 'xt')
call assert_true(exists('g:stl_click_info'))
call assert_equal('l', g:stl_click_info.button)
call assert_equal(1, g:stl_click_info.nclicks)
call assert_equal(1, g:stl_click_info.minwid)
" winid is 0 for tabline clicks (no associated window).
call assert_equal(0, g:stl_click_info.winid)
call assert_equal('tabline', g:stl_click_info.area)
unlet! g:stl_click_info
" Click on [BBB] region.
call test_setmouse(1, 7)
call feedkeys("\<LeftMouse>\<LeftRelease>", 'xt')
call assert_true(exists('g:stl_click_info'))
call assert_equal(2, g:stl_click_info.minwid)
unlet! g:stl_click_info
" Middle click on [AAA].
call test_setmouse(1, 2)
call feedkeys("\<MiddleMouse>\<MiddleRelease>", 'xt')
call assert_true(exists('g:stl_click_info'))
call assert_equal('m', g:stl_click_info.button)
unlet! g:stl_click_info
" Click outside any %[...] region: no callback, no error.
set tabline=xxx%1[StlClickTestFunc][YYY]%[]
redraw!
call test_setmouse(1, 1)
call feedkeys("\<LeftMouse>\<LeftRelease>", 'xt')
call assert_false(exists('g:stl_click_info'))
let &mouse = save_mouse
let &tabline = save_tal
let &showtabline = save_stal
if has('gui')
let &guioptions = save_go
endif
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

@@ -2451,4 +2451,29 @@ func Test_terminal_disable_kitty_keyboard()
bwipe!
endfunc
func Test_terminal_unwraps()
CheckNotMSWindows
30vnew
redraw
let buf = term_start("echo 1+2+3+4+5+6+7+8+9+10+11+12+13+14+15")
" Wait until both wrapped lines have appeared in the terminal
call WaitForAssert({-> assert_equal('14+15', term_getline(buf, 2))})
" A long wrapped line appears as 2 lines in libvterm
let l = term_getline(buf, 1)
call assert_equal('1+2+3+4+5+6+7+8+9+10+11+12+13+', l)
let l = term_getline(buf, 2)
call assert_equal('14+15', l)
call TermWait(buf)
" It should appear as a single buffer line in vim
let lastline = getline('$')
call assert_equal('1+2+3+4+5+6+7+8+9+10+11+12+13+14+15', lastline)
bwipe!
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

@@ -734,6 +734,18 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
339,
/**/
338,
/**/
337,
/**/
336,
/**/
335,
/**/
334,
/**/
333,
/**/