Compare commits

..

24 Commits

Author SHA1 Message Date
zeertzjq
0c995c0efb vim-patch:9.1.1204: MS-Windows: crash when passing long string to expand() (#32902)
Problem:  MS-Windows: crash when passing long string to expand() with
          'wildignorecase'.
Solution: Use the same buflen as unix_expandpath() in dos_expandpath().
          Remove an unnecessary STRLEN() while at it (zeertzjq).

closes: vim/vim#16896

00a749bd90
(cherry picked from commit ec8fc28743)
2025-03-15 11:21:12 +00:00
zeertzjq
aab7129abe vim-patch:9.0.1458: buffer overflow when expanding long file name
Problem:    Buffer overflow when expanding long file name.
Solution:   Use a larger buffer and avoid overflowing it. (Yee Cheng Chin,
            closes vim/vim#12201)

a77670726e

Co-authored-by: Yee Cheng Chin <ychin.git@gmail.com>
(cherry picked from commit b0b61c42b3)
2025-03-15 01:11:50 +00:00
zeertzjq
52ad6adc8d vim-patch:8.2.4963: expanding path with "/**" may overrun end of buffer
Problem:    Expanding path with "/**" may overrun end of buffer.
Solution:   Use vim_snprintf().

386c24cd26

Co-authored-by: Bram Moolenaar <Bram@vim.org>
(cherry picked from commit ad5bced637)
2025-03-15 01:11:50 +00:00
zeertzjq
c5eeb1b9ee Merge pull request #32880 from zeertzjq/backport
Backport #32739 to release-0.10
2025-03-14 07:52:06 +08:00
zeertzjq
3e7f0a13e1 vim-patch:9.1.1172: [security]: overflow with 'nostartofline' and Ex command in tag file (#32739)
Problem:  heap-buffer-overflow with 'nostartofline' and Ex command in
          tag file.
Solution: Set cursor column when moving cursor to line 1 (zeertzjq).

closes: vim/vim#16796

3ed6659549
2025-03-14 07:32:33 +08:00
zeertzjq
2010f398f4 vim-patch:8.2.3579: CI sometimes fails for MinGW
Problem:    CI sometimes fails for MinGW.
Solution:   Use backslashes in HandleSwapExists(). (Christian Brabandt,
            closes vim/vim#9078)

4b2c804767

Co-authored-by: Christian Brabandt <cb@256bit.org>
(cherry picked from commit 24bd7a4a9c)
2025-03-11 01:51:54 +00:00
zeertzjq
f0790c565c vim-patch:8.2.3311: Vim9: check for DO_NOT_FREE_CNT is very slow
Problem:    Vim9: check for DO_NOT_FREE_CNT is very slow.
Solution:   Move to a separate function so it can be skipped by setting
            $TEST_SKIP_PAT.

dae453f339

Co-authored-by: Bram Moolenaar <Bram@vim.org>
(cherry picked from commit 1bf9a7ce95)
2025-03-11 01:51:54 +00:00
neovim-backports[bot]
ecaece926e vim-patch:9.1.1155: Mode message not cleared after :silent message (#32672)
Problem:  Mode message not cleared after :silent message
          (after 9.0.1634).
Solution: Don't reset mode_displayed when the message is empty.
          (zeertzjq)

fixes: neovim/neovim#32641
closes: vim/vim#16744

fce1fa5b61
(cherry picked from commit df0328521f)

Co-authored-by: zeertzjq <zeertzjq@outlook.com>
2025-02-28 00:22:31 +00:00
James McCoy
28a8d59cc7 fix(vim_snprintf): special-case handling of binary format
A binary format spec always expects a corresponding unsigned long long
value. However, that explicit handling didn't get included when porting
the code from Vim, so binary format spec was falling through to the
"unsigned" and "length_modifier = NUL" portion of the code:

        } else {
          // unsigned
          switch (length_modifier) {
          case NUL:
            uarg = (tvs
                    ? (unsigned)tv_nr(tvs, &arg_idx)
                    : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
                                   &arg_cur, fmt),
                       va_arg(ap, unsigned)));
            break;

This incorrectly read an "unsigned" value from an "unsigned long long"
variable, which would produce incorrect results on certain platforms.

(cherry picked from commit 453f2c52d2)
2025-02-26 11:53:08 +00:00
James McCoy
3c57ee079d test(unit/strings_spec): show ctx when vim_snprintf content check fails #32570
Same idea as a7be4b7bf8, but that only showed the context if the
length of the string differed. Since these tests check both string
length and string content, the ctx should be provided for both.

    ERROR    test/unit/testutil.lua @ 797: vim_snprintf() positional arguments
    test/unit/testutil.lua:769: test/unit/testutil.lua:753: (string) '
    test/unit/strings_spec.lua:159: snprintf(buf, 4, "%1$0.*2$b", 12ULL, cdata<int>: 0xf78c8ed8) = 001100
    Expected objects to be the same.
    Passed in:
    (string) '000'
    Expected:
    (string) '001''

(cherry picked from commit 1c81734871)
2025-02-23 01:39:41 +00:00
Sören Tempel
e6432b0094 fix(tests): filter out lines with __typeof__ keyword (#32524)
Problem: On 32-bit architectures, musl libc makes heavy use of
__typeof__ as part of its __REDIR macro for optional backwards
compatibility with 32-bit time_t values. Unfortunately, the
__typeof__ keyword is not supported by the LuaJIT C parser.

Solution: Filter out the keyword in filter_complex_blocks.
(cherry picked from commit db2c3d1143)
2025-02-22 00:09:59 +00:00
Riley Bruins
00d3956109 fix(treesitter): don't spam query errors in the highlighter
**Problem:** An erroneous query in the treesitter highlighter gives a
deluge of errors that makes the editor almost unusable.

**Solution:** Detach the highlighter after an error is detected, so that
it only gets displayed once (per highlighter instance).

(cherry picked from commit b0bbe25c48)
2025-02-20 01:32:10 +00:00
zeertzjq
d65ce60f49 Merge pull request #32501 from zeertzjq/backport
Backport #32483 to release-0.10
2025-02-18 07:14:58 +08:00
Sören Tempel
0f0959ca32 fix(tests): remove the __extension__ keyword in filter_complex_blocks (#32483)
Problem: This keyword is used by GCC and Clang to prevent -Wpedantic
(and other options) from emitting warnings for many GNU C extensions.
This is used heavily in Alpine Linux through musl libc and
foritfy-headers. Without filtering the __extension__ keyword some type
definitions are duplicated. For example, timeval is defined once as

  struct timeval { time_t tv_sec; suseconds_t tv_usec; };

and once as:

  __extension__ struct timeval { time_t tv_sec; suseconds_t tv_usec; };

Without this patch, the LuaJIT C parser doesn't recognize that these
definitions are equivalent, causing unit test to fail on Alpine Linux.

Solution: Filter out the keyword in filter_complex_blocks.
2025-02-18 06:48:28 +08:00
zeertzjq
6b6abb8969 vim-patch:9.1.1108: 'smoothscroll' gets stuck with 'listchars' "eol" (#32434)
Problem:  'smoothscroll' gets stuck with 'listchars' "eol".
Solution: Count size of 'listchars' "eol" in line size when scrolling.
          (zeertzjq)

related: neovim/neovim#32405
closes: vim/vim#16627

2c47ab8fcd
(cherry picked from commit 9f85dace94)
2025-02-13 23:48:31 +00:00
Christian Clason
6ca2ef8dfe ci(cirrus): update to freebsd-14-2
Previous `freebsd-14-0` image was dropped

(cherry picked from commit 8117db48ed)
2025-02-12 17:12:05 +00:00
luukvbaal
3fd08c2eb2 fix(memline): don't check line count for closed memline #32403
Problem:  Error thrown when for invalid line number which may be accessed
          in an `on_detach` callback at which point line count is
          intentionally set to 0.
Solution: Move empty memline check to before line number check.
(cherry picked from commit 15bc930fca)
2025-02-12 16:49:02 +00:00
Jaehwang Jung
7e5b7ae4e0 fix(lsp): prevent desync due to empty buffer (#29904)
Problem:
Some language servers (e.g., rust-analyzer, texlab) are desynced when
the user deletes the entire contents of the buffer. This is due to the
discrepancy between how nvim computes diff and how nvim treats empty
buffer.
* diff: If the buffer became empty, then the diff includes the last
  line's eol.
* empty buffer: Even if the buffer is empty, nvim regards it as having
  a single empty line with eol.

Solution:
Add special case for diff computation when the buffer becomes empty so
that it does not include the eol of the last line.
2025-02-08 10:48:31 +01:00
Evgeni Chasnovski
c40057f372 fix(lsp): check for valid buf before processing semantic tokens response
Problem: There is no check for buffer validity before processing
  semantic tokens response. This can lead to `Invalid buffer id` error
  if processing request takes a long time and the buffer is wiped out.

  For example, this can happen after by accident navigating to a buffer
  from different project which leads to first loading project's
  workspace and *then* processing semantic tokens. During that time
  a buffer can be wiped out, as navigation to it was by accident.

Solution: Add extra check for buffer validity before processing semantic
  tokens response.
(cherry picked from commit a9cdf76e3a)
2025-02-08 01:24:58 +00:00
zeertzjq
c7bb6bbdea vim-patch:9.1.1077: included syntax items do not understand contains=TOP (#32343)
Problem:  Syntax engine interpreted contains=TOP as matching nothing
          inside included files, since :syn-include forces HL_CONTAINED
          on for every included item. After 8.2.2761, interprets
          contains=TOP as contains=@INCLUDED, which is also not correct
          since it doesn't respect exclusions, and doesn't work if there
          is no @INCLUDED cluster.
Solution: revert patch 8.2.2761, instead track groups that have had
          HL_CONTAINED forced, and interpret contains=TOP and
          contains=CONTAINED using this. (Theodore Dubois)

fixes: vim/vim#11277
closes: vim/vim#16571

f50d5364d7

Co-authored-by: Theodore Dubois <tblodt@icloud.com>
(cherry picked from commit 878b3b89c3)
2025-02-06 00:33:50 +00:00
alex-tdrn
c3866cea60 feat(win32): embed executable icon
Problem: on windows, the neovim executable (and thus the filetypes
associated to open with neovim) has no embedded icon

Solution: create a windows resource file pointing to the icon, and
add it to the nvim binary target

(cherry picked from commit cb84cd5d9f)
2025-02-05 20:54:05 +00:00
James McCoy
d6da862ce0 test(unit/strings_spec): use correct type for binary values
When 9.0.1856 was ported, the numbers being formatted as binary were cast
to "unsigned int" rather than uvarnumber_T, as is done upstream.

(cherry picked from commit 1426f3f3ce)
2025-01-29 20:25:38 +00:00
James McCoy
452ed57b71 test(unit/strings_spec): provide context for vim_snprintf tests
Since these assertions all use a common function to perform the test
assertions, it's difficult to figure out which test failed:

    ERROR    test/unit/testutil.lua @ 785: vim_snprintf() positional arguments
    test/unit/testutil.lua:757: test/unit/testutil.lua:741: (string) '
    test/unit/strings_spec.lua:143: Expected objects to be the same.
    Passed in:
    (number) 6400
    Expected:
    (number) 6'
    exit code: 256

Adding context to the assertion makes it clearer what the problem is:

    ERROR    test/unit/testutil.lua @ 785: vim_snprintf() positional arguments
    test/unit/testutil.lua:757: test/unit/testutil.lua:741: (string) '
    test/unit/strings_spec.lua:149: snprintf(buf, 0, "%1$0.*2$b", cdata<unsigned int>: 0xf78d0f38, cdata<int>: 0xf78dc4e0) = 001100
    Expected objects to be the same.
    Passed in:
    (number) 6400
    Expected:
    (number) 6'
    exit code: 256

(cherry picked from commit a7be4b7bf8)
2025-01-29 20:25:38 +00:00
bfredl
44c6fbaf9a version bump 2025-01-29 11:20:06 +01:00
31 changed files with 415 additions and 99 deletions

View File

@@ -6,7 +6,7 @@ freebsd_task:
name: FreeBSD name: FreeBSD
only_if: $BRANCH != "master" only_if: $BRANCH != "master"
freebsd_instance: freebsd_instance:
image_family: freebsd-14-0 image_family: freebsd-14-2
timeout_in: 30m timeout_in: 30m
install_script: install_script:
- pkg install -y cmake gmake ninja unzip wget gettext python git - pkg install -y cmake gmake ninja unzip wget gettext python git

View File

@@ -145,8 +145,8 @@ endif()
# version string, else they are combined with the result of `git describe`. # version string, else they are combined with the result of `git describe`.
set(NVIM_VERSION_MAJOR 0) set(NVIM_VERSION_MAJOR 0)
set(NVIM_VERSION_MINOR 10) set(NVIM_VERSION_MINOR 10)
set(NVIM_VERSION_PATCH 4) set(NVIM_VERSION_PATCH 5)
set(NVIM_VERSION_PRERELEASE "") # for package maintainers set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers
# API level # API level
set(NVIM_API_LEVEL 12) # Bump this after any API change. set(NVIM_API_LEVEL 12) # Bump this after any API change.

View File

@@ -343,6 +343,10 @@ function STHighlighter:process_response(response, client, version)
return return
end end
if not api.nvim_buf_is_valid(self.bufnr) then
return
end
-- if we have a response to a delta request, update the state of our tokens -- if we have a response to a delta request, update the state of our tokens
-- appropriately. if it's a full response, just use that -- appropriately. if it's a full response, just use that
local tokens ---@type integer[] local tokens ---@type integer[]
@@ -382,8 +386,10 @@ function STHighlighter:process_response(response, client, version)
current_result.highlights = highlights current_result.highlights = highlights
current_result.namespace_cleared = false current_result.namespace_cleared = false
-- redraw all windows displaying buffer -- redraw all windows displaying buffer (if still valid)
if api.nvim_buf_is_valid(self.bufnr) then
api.nvim__redraw({ buf = self.bufnr, valid = true }) api.nvim__redraw({ buf = self.bufnr, valid = true })
end
end end
--- on_win handler for the decoration provider (see |nvim_set_decoration_provider|) --- on_win handler for the decoration provider (see |nvim_set_decoration_provider|)

View File

@@ -212,7 +212,8 @@ end
---@param lastline integer ---@param lastline integer
---@param new_lastline integer ---@param new_lastline integer
---@param offset_encoding string ---@param offset_encoding string
---@return vim.lsp.sync.Range, vim.lsp.sync.Range ---@return vim.lsp.sync.Range prev_end_range
---@return vim.lsp.sync.Range curr_end_range
local function compute_end_range( local function compute_end_range(
prev_lines, prev_lines,
curr_lines, curr_lines,
@@ -222,6 +223,16 @@ local function compute_end_range(
new_lastline, new_lastline,
offset_encoding offset_encoding
) )
-- A special case for the following `firstline == new_lastline` case where lines are deleted.
-- Even if the buffer has become empty, nvim behaves as if it has an empty line with eol.
if #curr_lines == 1 and curr_lines[1] == '' then
local prev_line = prev_lines[lastline - 1]
return {
line_idx = lastline - 1,
byte_idx = #prev_line + 1,
char_idx = compute_line_length(prev_line, offset_encoding) + 1,
}, { line_idx = 1, byte_idx = 1, char_idx = 1 }
end
-- If firstline == new_lastline, the first change occurred on a line that was deleted. -- If firstline == new_lastline, the first change occurred on a line that was deleted.
-- In this case, the last_byte... -- In this case, the last_byte...
if firstline == new_lastline then if firstline == new_lastline then

View File

@@ -240,7 +240,12 @@ end
---@return vim.treesitter.highlighter.Query ---@return vim.treesitter.highlighter.Query
function TSHighlighter:get_query(lang) function TSHighlighter:get_query(lang)
if not self._queries[lang] then if not self._queries[lang] then
self._queries[lang] = TSHighlighterQuery.new(lang) local success, result = pcall(TSHighlighterQuery.new, lang)
if not success then
self:destroy()
error(result)
end
self._queries[lang] = result
end end
return self._queries[lang] return self._queries[lang]

4
runtime/windows_icon.rc Normal file
View File

@@ -0,0 +1,4 @@
// NOTE: this resource file *must* be in the same folder as the icon.
// Otherwise, absolute paths would need to be used.
// see https://learn.microsoft.com/en-us/windows/win32/menurc/icon-resource
NEOVIM_ICON ICON "neovim.ico"

View File

@@ -708,6 +708,12 @@ target_sources(main_lib INTERFACE
${EXTERNAL_SOURCES} ${EXTERNAL_SOURCES}
${EXTERNAL_HEADERS}) ${EXTERNAL_HEADERS})
if(WIN32)
# add windows resource file pointing to the neovim icon
# this makes the icon appear for the neovim exe and associated filetypes
target_sources(nvim_bin PRIVATE ${NVIM_RUNTIME_DIR}/windows_icon.rc)
endif()
target_sources(nlua0 PUBLIC ${NLUA0_SOURCES}) target_sources(nlua0 PUBLIC ${NLUA0_SOURCES})
target_link_libraries(nvim_bin PRIVATE main_lib PUBLIC libuv) target_link_libraries(nvim_bin PRIVATE main_lib PUBLIC libuv)

View File

@@ -342,8 +342,8 @@ static void changed_common(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnum
&& (last < wp->w_topline && (last < wp->w_topline
|| (wp->w_topline >= lnum || (wp->w_topline >= lnum
&& wp->w_topline < lnume && wp->w_topline < lnume
&& win_linetabsize(wp, wp->w_topline, ml_get_buf(buf, wp->w_topline), MAXCOL) && (linetabsize_eol(wp, wp->w_topline)
<= (wp->w_skipcol + sms_marker_overlap(wp, -1))))) { <= wp->w_skipcol + sms_marker_overlap(wp, -1))))) {
wp->w_skipcol = 0; wp->w_skipcol = 0;
} }

View File

@@ -129,7 +129,7 @@ static int coladvance2(win_T *wp, pos_T *pos, bool addspaces, bool finetune, col
&& wp->w_width_inner != 0 && wp->w_width_inner != 0
&& wcol >= (colnr_T)width && wcol >= (colnr_T)width
&& width > 0) { && width > 0) {
csize = linetabsize(wp, pos->lnum); csize = linetabsize_eol(wp, pos->lnum);
if (csize > 0) { if (csize > 0) {
csize--; csize--;
} }

View File

@@ -1880,6 +1880,11 @@ static char *ml_get_buf_impl(buf_T *buf, linenr_T lnum, bool will_change)
static int recursive = 0; static int recursive = 0;
static char questions[4]; static char questions[4];
if (buf->b_ml.ml_mfp == NULL) { // there are no lines
buf->b_ml.ml_line_len = 1;
return "";
}
if (lnum > buf->b_ml.ml_line_count) { // invalid line number if (lnum > buf->b_ml.ml_line_count) { // invalid line number
if (recursive == 0) { if (recursive == 0) {
// Avoid giving this message for a recursive call, may happen when // Avoid giving this message for a recursive call, may happen when
@@ -1899,11 +1904,6 @@ errorret:
lnum = 1; lnum = 1;
} }
if (buf->b_ml.ml_mfp == NULL) { // there are no lines
buf->b_ml.ml_line_len = 1;
return "";
}
// See if it is the same line as requested last time. // See if it is the same line as requested last time.
// Otherwise may need to flush last used line. // Otherwise may need to flush last used line.
// Don't use the last used line when 'swapfile' is reset, need to load all // Don't use the last used line when 'swapfile' is reset, need to load all

View File

@@ -1576,7 +1576,7 @@ int msg_outtrans_len(const char *msgstr, int len, int attr)
// When drawing over the command line no need to clear it later or remove // When drawing over the command line no need to clear it later or remove
// the mode message. // the mode message.
if (msg_row >= cmdline_row && msg_col == 0) { if (msg_silent == 0 && len > 0 && msg_row >= cmdline_row && msg_col == 0) {
clear_cmdline = false; clear_cmdline = false;
mode_displayed = false; mode_displayed = false;
} }

View File

@@ -1208,9 +1208,7 @@ static void cursor_correct_sms(win_T *wp)
int width2 = width1 + win_col_off2(wp); int width2 = width1 + win_col_off2(wp);
int so_cols = so == 0 ? 0 : width1 + (so - 1) * width2; int so_cols = so == 0 ? 0 : width1 + (so - 1) * width2;
int space_cols = (wp->w_height_inner - 1) * width2; int space_cols = (wp->w_height_inner - 1) * width2;
int size = so == 0 ? 0 : win_linetabsize(wp, wp->w_topline, int size = so == 0 ? 0 : linetabsize_eol(wp, wp->w_topline);
ml_get_buf(wp->w_buffer, wp->w_topline),
(colnr_T)MAXCOL);
if (wp->w_topline == 1 && wp->w_skipcol == 0) { if (wp->w_topline == 1 && wp->w_skipcol == 0) {
so_cols = 0; // Ignore 'scrolloff' at top of buffer. so_cols = 0; // Ignore 'scrolloff' at top of buffer.
@@ -1226,9 +1224,10 @@ static void cursor_correct_sms(win_T *wp)
so_cols -= width1; so_cols -= width1;
} }
// If there is no marker or we have non-zero scrolloff, just ignore it. int overlap = wp->w_skipcol == 0
int overlap = (wp->w_skipcol == 0 || so_cols != 0) ? 0 : sms_marker_overlap(wp, -1); ? 0 : sms_marker_overlap(wp, wp->w_width_inner - width2);
int top = wp->w_skipcol + overlap + so_cols; // If we have non-zero scrolloff, ignore marker overlap.
int top = wp->w_skipcol + (so_cols != 0 ? so_cols : overlap);
int bot = wp->w_skipcol + width1 + (wp->w_height_inner - 1) * width2 - so_cols; int bot = wp->w_skipcol + width1 + (wp->w_height_inner - 1) * width2 - so_cols;
validate_virtcol(wp); validate_virtcol(wp);
@@ -1249,10 +1248,22 @@ static void cursor_correct_sms(win_T *wp)
if (col != wp->w_virtcol) { if (col != wp->w_virtcol) {
wp->w_curswant = col; wp->w_curswant = col;
coladvance(wp, wp->w_curswant); int rc = coladvance(wp, wp->w_curswant);
// validate_virtcol() marked various things as valid, but after // validate_virtcol() marked various things as valid, but after
// moving the cursor they need to be recomputed // moving the cursor they need to be recomputed
wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL); wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL);
if (rc == FAIL && wp->w_skipcol > 0
&& wp->w_cursor.lnum < wp->w_buffer->b_ml.ml_line_count) {
validate_virtcol(wp);
if (wp->w_virtcol < wp->w_skipcol + overlap) {
// Cursor still not visible: move it to the next line instead.
wp->w_cursor.lnum++;
wp->w_cursor.col = 0;
wp->w_cursor.coladd = 0;
wp->w_curswant = 0;
wp->w_valid &= ~VALID_VIRTCOL;
}
}
} }
} }
@@ -1365,8 +1376,7 @@ bool scrolldown(win_T *wp, linenr_T line_count, int byfold)
wp->w_topline = first; wp->w_topline = first;
} else { } else {
if (do_sms) { if (do_sms) {
int size = win_linetabsize(wp, wp->w_topline, int size = linetabsize_eol(wp, wp->w_topline);
ml_get_buf(wp->w_buffer, wp->w_topline), MAXCOL);
if (size > width1) { if (size > width1) {
wp->w_skipcol = width1; wp->w_skipcol = width1;
size -= width1; size -= width1;
@@ -1449,7 +1459,7 @@ bool scrollup(win_T *wp, linenr_T line_count, bool byfold)
const colnr_T prev_skipcol = wp->w_skipcol; const colnr_T prev_skipcol = wp->w_skipcol;
if (do_sms) { if (do_sms) {
size = linetabsize(wp, wp->w_topline); size = linetabsize_eol(wp, wp->w_topline);
} }
// diff mode: first consume "topfill" // diff mode: first consume "topfill"
@@ -1492,7 +1502,7 @@ bool scrollup(win_T *wp, linenr_T line_count, bool byfold)
wp->w_topfill = win_get_fill(wp, lnum); wp->w_topfill = win_get_fill(wp, lnum);
wp->w_skipcol = 0; wp->w_skipcol = 0;
if (todo > 1 && do_sms) { if (todo > 1 && do_sms) {
size = linetabsize(wp, wp->w_topline); size = linetabsize_eol(wp, wp->w_topline);
} }
} }
} }
@@ -1563,7 +1573,7 @@ void adjust_skipcol(void)
} }
validate_virtcol(curwin); validate_virtcol(curwin);
int overlap = sms_marker_overlap(curwin, -1); int overlap = sms_marker_overlap(curwin, curwin->w_width_inner - width2);
while (curwin->w_skipcol > 0 while (curwin->w_skipcol > 0
&& curwin->w_virtcol < curwin->w_skipcol + overlap + scrolloff_cols) { && curwin->w_virtcol < curwin->w_skipcol + overlap + scrolloff_cols) {
// scroll a screen line down // scroll a screen line down
@@ -1584,8 +1594,7 @@ void adjust_skipcol(void)
// Avoid adjusting for 'scrolloff' beyond the text line height. // Avoid adjusting for 'scrolloff' beyond the text line height.
if (scrolloff_cols > 0) { if (scrolloff_cols > 0) {
int size = win_linetabsize(curwin, curwin->w_topline, int size = linetabsize_eol(curwin, curwin->w_topline);
ml_get(curwin->w_topline), (colnr_T)MAXCOL);
size = width1 + width2 * ((size - width1 + width2 - 1) / width2); size = width1 + width2 * ((size - width1 + width2 - 1) / width2);
while (col > size) { while (col > size) {
col -= width2; col -= width2;

View File

@@ -5197,7 +5197,7 @@ void nv_g_home_m_cmd(cmdarg_T *cap)
// When ending up below 'smoothscroll' marker, move just beyond it so // When ending up below 'smoothscroll' marker, move just beyond it so
// that skipcol is not adjusted later. // that skipcol is not adjusted later.
if (curwin->w_skipcol > 0 && curwin->w_cursor.lnum == curwin->w_topline) { if (curwin->w_skipcol > 0 && curwin->w_cursor.lnum == curwin->w_topline) {
int overlap = sms_marker_overlap(curwin, -1); int overlap = sms_marker_overlap(curwin, curwin->w_width_inner - width2);
if (overlap > 0 && i == curwin->w_skipcol) { if (overlap > 0 && i == curwin->w_skipcol) {
i += overlap; i += overlap;
} }

View File

@@ -619,7 +619,6 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_ALL
{ {
int start_len = gap->ga_len; int start_len = gap->ga_len;
size_t len;
bool starstar = false; bool starstar = false;
static int stardepth = 0; // depth for "**" expansion static int stardepth = 0; // depth for "**" expansion
@@ -631,9 +630,9 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in
} }
} }
// Make room for file name. When doing encoding conversion the actual // Make room for file name (a bit too much to stay on the safe side).
// length may be quite a bit longer, thus use the maximum possible length. const size_t buflen = strlen(path) + MAXPATHL;
char *buf = xmalloc(MAXPATHL); char *buf = xmalloc(buflen);
// Find the first part in the path name that contains a wildcard. // Find the first part in the path name that contains a wildcard.
// When EW_ICASE is set every letter is considered to be a wildcard. // When EW_ICASE is set every letter is considered to be a wildcard.
@@ -662,10 +661,10 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in
) { ) {
e = p; e = p;
} }
len = (size_t)(utfc_ptr2len(path_end)); int charlen = utfc_ptr2len(path_end);
memcpy(p, path_end, len); memcpy(p, path_end, (size_t)charlen);
p += len; p += charlen;
path_end += len; path_end += charlen;
} }
e = p; e = p;
*e = NUL; *e = NUL;
@@ -719,13 +718,14 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in
return 0; return 0;
} }
size_t len = (size_t)(s - buf);
// If "**" is by itself, this is the first time we encounter it and more // If "**" is by itself, this is the first time we encounter it and more
// is following then find matches without any directory. // is following then find matches without any directory.
if (!didstar && stardepth < 100 && starstar && e - s == 2 if (!didstar && stardepth < 100 && starstar && e - s == 2
&& *path_end == '/') { && *path_end == '/') {
STRCPY(s, path_end + 1); vim_snprintf(s, buflen - len, "%s", path_end + 1);
stardepth++; stardepth++;
do_path_expand(gap, buf, (size_t)(s - buf), flags, true); do_path_expand(gap, buf, len, flags, true);
stardepth--; stardepth--;
} }
*s = NUL; *s = NUL;
@@ -737,6 +737,7 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in
const char *name; const char *name;
scandir_next_with_dots(NULL); // initialize scandir_next_with_dots(NULL); // initialize
while (!got_int && (name = scandir_next_with_dots(&dir)) != NULL) { while (!got_int && (name = scandir_next_with_dots(&dir)) != NULL) {
len = (size_t)(s - buf);
if ((name[0] != '.' if ((name[0] != '.'
|| starts_with_dot || starts_with_dot
|| ((flags & EW_DODOT) || ((flags & EW_DODOT)
@@ -744,21 +745,22 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in
&& (name[1] != '.' || name[2] != NUL))) && (name[1] != '.' || name[2] != NUL)))
&& ((regmatch.regprog != NULL && vim_regexec(&regmatch, name, 0)) && ((regmatch.regprog != NULL && vim_regexec(&regmatch, name, 0))
|| ((flags & EW_NOTWILD) || ((flags & EW_NOTWILD)
&& path_fnamencmp(path + (s - buf), name, (size_t)(e - s)) == 0))) { && path_fnamencmp(path + len, name, (size_t)(e - s)) == 0))) {
STRCPY(s, name); len += (size_t)vim_snprintf(s, buflen - len, "%s", name);
len = strlen(buf); if (len + 1 >= buflen) {
continue;
}
if (starstar && stardepth < 100) { if (starstar && stardepth < 100) {
// For "**" in the pattern first go deeper in the tree to // For "**" in the pattern first go deeper in the tree to
// find matches. // find matches.
STRCPY(buf + len, "/**"); // NOLINT vim_snprintf(buf + len, buflen - len, "/**%s", path_end); // NOLINT
STRCPY(buf + len + 3, path_end);
stardepth++; stardepth++;
do_path_expand(gap, buf, len + 1, flags, true); do_path_expand(gap, buf, len + 1, flags, true);
stardepth--; stardepth--;
} }
STRCPY(buf + len, path_end); vim_snprintf(buf + len, buflen - len, "%s", path_end);
if (path_has_exp_wildcard(path_end)) { // handle more wildcards if (path_has_exp_wildcard(path_end)) { // handle more wildcards
// need to expand another component of the path // need to expand another component of the path
// remove backslashes for the remaining components only // remove backslashes for the remaining components only

View File

@@ -74,11 +74,19 @@ int linetabsize_col(int startvcol, char *s)
/// Return the number of cells line "lnum" of window "wp" will take on the /// Return the number of cells line "lnum" of window "wp" will take on the
/// screen, taking into account the size of a tab and inline virtual text. /// screen, taking into account the size of a tab and inline virtual text.
/// Doesn't count the size of 'listchars' "eol".
int linetabsize(win_T *wp, linenr_T lnum) int linetabsize(win_T *wp, linenr_T lnum)
{ {
return win_linetabsize(wp, lnum, ml_get_buf(wp->w_buffer, lnum), MAXCOL); return win_linetabsize(wp, lnum, ml_get_buf(wp->w_buffer, lnum), MAXCOL);
} }
/// Like linetabsize(), but counts the size of 'listchars' "eol".
int linetabsize_eol(win_T *wp, linenr_T lnum)
{
return linetabsize(wp, lnum)
+ ((wp->w_p_list && wp->w_p_lcs_chars.eol != NUL) ? 1 : 0);
}
static const uint32_t inline_filter[4] = {[kMTMetaInline] = kMTFilterSelect }; static const uint32_t inline_filter[4] = {[kMTMetaInline] = kMTFilterSelect };
/// Prepare the structure passed to charsize functions. /// Prepare the structure passed to charsize functions.

View File

@@ -81,6 +81,7 @@ static inline int win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T
REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE; REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE;
/// Like linetabsize_str(), but for a given window instead of the current one. /// Like linetabsize_str(), but for a given window instead of the current one.
/// Doesn't count the size of 'listchars' "eol".
/// ///
/// @param wp /// @param wp
/// @param line /// @param line

View File

@@ -1677,8 +1677,6 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
} }
switch (fmt_spec) { switch (fmt_spec) {
case 'b':
case 'B':
case 'd': case 'd':
case 'u': case 'u':
case 'o': case 'o':
@@ -1802,6 +1800,13 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
if (ptr_arg) { if (ptr_arg) {
arg_sign = 1; arg_sign = 1;
} }
} else if (fmt_spec == 'b' || fmt_spec == 'B') {
uarg = (tvs
? (unsigned long long)tv_nr(tvs, &arg_idx) // NOLINT(runtime/int)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
&arg_cur, fmt),
va_arg(ap, unsigned long long))); // NOLINT(runtime/int)
arg_sign = (uarg != 0);
} else if (fmt_spec == 'd') { } else if (fmt_spec == 'd') {
// signed // signed
switch (length_modifier) { switch (length_modifier) {

View File

@@ -1649,7 +1649,7 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con
? !(spp->sp_flags & HL_CONTAINED) ? !(spp->sp_flags & HL_CONTAINED)
: in_id_list(cur_si, : in_id_list(cur_si,
cur_si->si_cont_list, &spp->sp_syn, cur_si->si_cont_list, &spp->sp_syn,
spp->sp_flags & HL_CONTAINED)))) { spp->sp_flags)))) {
// If we already tried matching in this line, and // If we already tried matching in this line, and
// there isn't a match before next_match_col, skip // there isn't a match before next_match_col, skip
// this item. // this item.
@@ -2774,7 +2774,7 @@ static keyentry_T *match_keyword(char *keyword, hashtab_T *ht, stateitem_T *cur_
: (cur_si == NULL : (cur_si == NULL
? !(kp->flags & HL_CONTAINED) ? !(kp->flags & HL_CONTAINED)
: in_id_list(cur_si, cur_si->si_cont_list, : in_id_list(cur_si, cur_si->si_cont_list,
&kp->k_syn, kp->flags & HL_CONTAINED))) { &kp->k_syn, kp->flags))) {
return kp; return kp;
} }
} }
@@ -3927,7 +3927,7 @@ static void syn_incl_toplevel(int id, int *flagsp)
if ((*flagsp & HL_CONTAINED) || curwin->w_s->b_syn_topgrp == 0) { if ((*flagsp & HL_CONTAINED) || curwin->w_s->b_syn_topgrp == 0) {
return; return;
} }
*flagsp |= HL_CONTAINED; *flagsp |= HL_CONTAINED | HL_INCLUDED_TOPLEVEL;
if (curwin->w_s->b_syn_topgrp >= SYNID_CLUSTER) { if (curwin->w_s->b_syn_topgrp >= SYNID_CLUSTER) {
// We have to alloc this, because syn_combine_list() will free it. // We have to alloc this, because syn_combine_list() will free it.
int16_t *grp_list = xmalloc(2 * sizeof(*grp_list)); int16_t *grp_list = xmalloc(2 * sizeof(*grp_list));
@@ -4968,16 +4968,13 @@ static int get_id_list(char **const arg, const int keylen, int16_t **const list,
break; break;
} }
if (name[1] == 'A') { if (name[1] == 'A') {
id = SYNID_ALLBUT + current_syn_inc_tag; id = SYNID_ALLBUT;
} else if (name[1] == 'T') { } else if (name[1] == 'T') {
if (curwin->w_s->b_syn_topgrp >= SYNID_CLUSTER) { id = SYNID_TOP;
id = curwin->w_s->b_syn_topgrp;
} else { } else {
id = SYNID_TOP + current_syn_inc_tag; id = SYNID_CONTAINED;
}
} else {
id = SYNID_CONTAINED + current_syn_inc_tag;
} }
id += current_syn_inc_tag;
} else if (name[1] == '@') { } else if (name[1] == '@') {
if (skip) { if (skip) {
id = -1; id = -1;
@@ -5095,8 +5092,8 @@ static int16_t *copy_id_list(const int16_t *const list)
/// @param cur_si current item or NULL /// @param cur_si current item or NULL
/// @param list id list /// @param list id list
/// @param ssp group id and ":syn include" tag of group /// @param ssp group id and ":syn include" tag of group
/// @param contained group id is contained /// @param flags group flags
static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, int contained) static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, int flags)
{ {
int retval; int retval;
int16_t id = ssp->id; int16_t id = ssp->id;
@@ -5114,8 +5111,7 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in
// cur_si->si_idx is -1 for keywords, these never contain anything. // cur_si->si_idx is -1 for keywords, these never contain anything.
if (cur_si->si_idx >= 0 && in_id_list(NULL, ssp->cont_in_list, if (cur_si->si_idx >= 0 && in_id_list(NULL, ssp->cont_in_list,
&(SYN_ITEMS(syn_block)[cur_si->si_idx].sp_syn), &(SYN_ITEMS(syn_block)[cur_si->si_idx].sp_syn),
SYN_ITEMS(syn_block)[cur_si->si_idx].sp_flags & SYN_ITEMS(syn_block)[cur_si->si_idx].sp_flags)) {
HL_CONTAINED)) {
return true; return true;
} }
} }
@@ -5127,9 +5123,14 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in
// If list is ID_LIST_ALL, we are in a transparent item that isn't // If list is ID_LIST_ALL, we are in a transparent item that isn't
// inside anything. Only allow not-contained groups. // inside anything. Only allow not-contained groups.
if (list == ID_LIST_ALL) { if (list == ID_LIST_ALL) {
return !contained; return !(flags & HL_CONTAINED);
} }
// Is this top-level (i.e. not 'contained') in the file it was declared in?
// For included files, this is different from HL_CONTAINED, which is set
// unconditionally.
bool toplevel = !(flags & HL_CONTAINED) || (flags & HL_INCLUDED_TOPLEVEL);
// If the first item is "ALLBUT", return true if "id" is NOT in the // If the first item is "ALLBUT", return true if "id" is NOT in the
// contains list. We also require that "id" is at the same ":syn include" // contains list. We also require that "id" is at the same ":syn include"
// level as the list. // level as the list.
@@ -5142,12 +5143,12 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in
} }
} else if (item < SYNID_CONTAINED) { } else if (item < SYNID_CONTAINED) {
// TOP: accept all not-contained groups in the same file // TOP: accept all not-contained groups in the same file
if (item - SYNID_TOP != ssp->inc_tag || contained) { if (item - SYNID_TOP != ssp->inc_tag || !toplevel) {
return false; return false;
} }
} else { } else {
// CONTAINED: accept all contained groups in the same file // CONTAINED: accept all contained groups in the same file
if (item - SYNID_CONTAINED != ssp->inc_tag || !contained) { if (item - SYNID_CONTAINED != ssp->inc_tag || toplevel) {
return false; return false;
} }
} }
@@ -5168,7 +5169,7 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in
// cluster that includes itself (indirectly) // cluster that includes itself (indirectly)
if (scl_list != NULL && depth < 30) { if (scl_list != NULL && depth < 30) {
depth++; depth++;
int r = in_id_list(NULL, scl_list, ssp, contained); int r = in_id_list(NULL, scl_list, ssp, flags);
depth--; depth--;
if (r) { if (r) {
return retval; return retval;

View File

@@ -26,6 +26,7 @@ enum {
HL_TRANS_CONT = 0x10000, ///< transparent item without contains arg HL_TRANS_CONT = 0x10000, ///< transparent item without contains arg
HL_CONCEAL = 0x20000, ///< can be concealed HL_CONCEAL = 0x20000, ///< can be concealed
HL_CONCEALENDS = 0x40000, ///< can be concealed HL_CONCEALENDS = 0x40000, ///< can be concealed
HL_INCLUDED_TOPLEVEL = 0x80000, ///< toplevel item in included syntax, allowed by contains=TOP
}; };
#define SYN_GROUP_STATIC(s) syn_check_group(S_LEN(s)) #define SYN_GROUP_STATIC(s) syn_check_group(S_LEN(s))

View File

@@ -3014,6 +3014,8 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, bool keep_help)
secure = 1; secure = 1;
sandbox++; sandbox++;
curwin->w_cursor.lnum = 1; // start command in line 1 curwin->w_cursor.lnum = 1; // start command in line 1
curwin->w_cursor.col = 0;
curwin->w_cursor.coladd = 0;
do_cmdline_cmd(pbuf); do_cmdline_cmd(pbuf);
retval = OK; retval = OK;

View File

@@ -89,6 +89,34 @@ describe('messages', function()
]]) ]])
end) end)
-- oldtest: Test_mode_cleared_after_silent_message()
it('mode is cleared properly after slient message', function()
screen = Screen.new(60, 10)
screen:attach()
exec([[
edit XsilentMessageMode.txt
call setline(1, 'foobar')
autocmd TextChanged * silent update
]])
finally(function()
os.remove('XsilentMessageMode.txt')
end)
feed('v')
screen:expect([[
^foobar |
{1:~ }|*8
{5:-- VISUAL --} |
]])
feed('d')
screen:expect([[
^oobar |
{1:~ }|*8
|
]])
end)
describe('more prompt', function() describe('more prompt', function()
before_each(function() before_each(function()
command('set more') command('set more')

View File

@@ -300,6 +300,24 @@ describe('lua buffer event callbacks: on_lines', function()
n.assert_alive() n.assert_alive()
end) end)
it('no invalid lnum error for closed memline in on_detach #31251', function()
eq(vim.NIL, exec_lua('return _G.did_detach'))
exec_lua([[
vim.api.nvim_buf_set_lines(0, 0, -1, false, { '' })
local bufname = 'buf2'
local buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(buf, bufname)
vim.bo[buf].bufhidden = 'wipe'
vim.cmd('vertical diffsplit '..bufname)
vim.api.nvim_buf_attach(0, false, { on_detach = function()
vim.cmd("redraw")
_G.did_detach = true
end})
vim.cmd.bdelete()
]])
eq(true, exec_lua('return _G.did_detach'))
end)
it('#12718 lnume', function() it('#12718 lnume', function()
api.nvim_buf_set_lines(0, 0, -1, true, { '1', '2', '3' }) api.nvim_buf_set_lines(0, 0, -1, true, { '1', '2', '3' })
exec_lua([[ exec_lua([[

View File

@@ -170,7 +170,7 @@ describe('incremental synchronization', function()
} }
test_edit({ 'a' }, { 'rb' }, expected_text_changes, 'utf-16', '\n') test_edit({ 'a' }, { 'rb' }, expected_text_changes, 'utf-16', '\n')
end) end)
it('deleting a line', function() it('deleting the first line', function()
local expected_text_changes = { local expected_text_changes = {
{ {
range = { range = {
@@ -183,11 +183,49 @@ describe('incremental synchronization', function()
line = 1, line = 1,
}, },
}, },
rangeLength = 12, rangeLength = 6,
text = '', text = '',
}, },
} }
test_edit({ 'hello world' }, { 'dd' }, expected_text_changes, 'utf-16', '\n') test_edit({ 'hello', 'world' }, { 'ggdd' }, expected_text_changes, 'utf-16', '\n')
end)
it('deleting the last line', function()
local expected_text_changes = {
{
range = {
['start'] = {
character = 0,
line = 1,
},
['end'] = {
character = 0,
line = 2,
},
},
rangeLength = 6,
text = '',
},
}
test_edit({ 'hello', 'world' }, { '2ggdd' }, expected_text_changes, 'utf-16', '\n')
end)
it('deleting all lines', function()
local expected_text_changes = {
{
range = {
['start'] = {
character = 0,
line = 0,
},
['end'] = {
character = 5,
line = 1,
},
},
rangeLength = 11,
text = '',
},
}
test_edit({ 'hello', 'world' }, { 'ggdG' }, expected_text_changes, 'utf-16', '\n')
end) end)
it('deleting an empty line', function() it('deleting an empty line', function()
local expected_text_changes = { local expected_text_changes = {

View File

@@ -13,6 +13,9 @@
" For csh: " For csh:
" setenv TEST_FILTER Test_channel " setenv TEST_FILTER Test_channel
" "
" If the environment variable $TEST_SKIP_PAT is set then test functions
" matching this pattern will be skipped. It's the opposite of $TEST_FILTER.
"
" While working on a test you can make $TEST_NO_RETRY non-empty to not retry: " While working on a test you can make $TEST_NO_RETRY non-empty to not retry:
" export TEST_NO_RETRY=yes " export TEST_NO_RETRY=yes
" "
@@ -92,7 +95,12 @@ set encoding=utf-8
" REDIR_TEST_TO_NULL has a very permissive SwapExists autocommand which is for " REDIR_TEST_TO_NULL has a very permissive SwapExists autocommand which is for
" the test_name.vim file itself. Replace it here with a more restrictive one, " the test_name.vim file itself. Replace it here with a more restrictive one,
" so we still catch mistakes. " so we still catch mistakes.
let s:test_script_fname = expand('%') if has("win32")
" replace any '/' directory separators by '\\'
let s:test_script_fname = substitute(expand('%'), '/', '\\', 'g')
else
let s:test_script_fname = expand('%')
endif
au! SwapExists * call HandleSwapExists() au! SwapExists * call HandleSwapExists()
func HandleSwapExists() func HandleSwapExists()
if exists('g:ignoreSwapExists') if exists('g:ignoreSwapExists')
@@ -431,13 +439,17 @@ func FinishTesting()
if s:done == 0 if s:done == 0
if s:filtered > 0 if s:filtered > 0
if $TEST_FILTER != ''
let message = "NO tests match $TEST_FILTER: '" .. $TEST_FILTER .. "'" let message = "NO tests match $TEST_FILTER: '" .. $TEST_FILTER .. "'"
else
let message = "ALL tests match $TEST_SKIP_PAT: '" .. $TEST_SKIP_PAT .. "'"
endif
else else
let message = 'NO tests executed' let message = 'NO tests executed'
endif endif
else else
if s:filtered > 0 if s:filtered > 0
call add(s:messages, "Filtered " .. s:filtered .. " tests with $TEST_FILTER") call add(s:messages, "Filtered " .. s:filtered .. " tests with $TEST_FILTER and $TEST_SKIP_PAT")
endif endif
let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test') let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test')
endif endif
@@ -530,6 +542,12 @@ endif
" Execute the tests in alphabetical order. " Execute the tests in alphabetical order.
for g:testfunc in sort(s:tests) for g:testfunc in sort(s:tests)
if $TEST_SKIP_PAT != '' && g:testfunc =~ $TEST_SKIP_PAT
call add(s:messages, g:testfunc .. ' matches $TEST_SKIP_PAT')
let s:filtered += 1
continue
endif
" Silence, please! " Silence, please!
set belloff=all set belloff=all
let prev_error = '' let prev_error = ''

View File

@@ -143,4 +143,11 @@ func Test_expand_wildignore()
set wildignore& set wildignore&
endfunc endfunc
" Passing a long string to expand with 'wildignorecase' should not crash Vim.
func Test_expand_long_str()
set wildignorecase
call expand('a'->repeat(99999))
set wildignorecase&
endfunc
" vim: shiftwidth=2 sts=2 expandtab " vim: shiftwidth=2 sts=2 expandtab

View File

@@ -386,6 +386,29 @@ func Test_message_not_cleared_after_mode()
call StopVimInTerminal(buf) call StopVimInTerminal(buf)
endfunc endfunc
func Test_mode_cleared_after_silent_message()
CheckRunVimInTerminal
let lines =<< trim END
edit XsilentMessageMode.txt
call setline(1, 'foobar')
autocmd TextChanged * silent update
END
call writefile(lines, 'XsilentMessageMode', 'D')
let buf = RunVimInTerminal('-S XsilentMessageMode', {'rows': 10})
call term_sendkeys(buf, 'v')
call TermWait(buf)
call VerifyScreenDump(buf, 'Test_mode_cleared_after_silent_message_1', {})
call term_sendkeys(buf, 'd')
call TermWait(buf)
call VerifyScreenDump(buf, 'Test_mode_cleared_after_silent_message_2', {})
call StopVimInTerminal(buf)
call delete('XsilentMessageMode.txt')
endfunc
" Test verbose message before echo command " Test verbose message before echo command
func Test_echo_verbose_system() func Test_echo_verbose_system()
CheckRunVimInTerminal CheckRunVimInTerminal

View File

@@ -1219,4 +1219,59 @@ func Test_smooth_long_scrolloff()
call StopVimInTerminal(buf) call StopVimInTerminal(buf)
endfunc endfunc
func Test_smoothscroll_listchars_eol()
call NewWindow(10, 40)
setlocal list listchars=eol:$ scrolloff=0 smoothscroll
call setline(1, repeat('-', 40))
call append(1, repeat(['foobar'], 10))
normal! G
call assert_equal(2, line('w0'))
call assert_equal(0, winsaveview().skipcol)
exe "normal! \<C-Y>"
call assert_equal(1, line('w0'))
call assert_equal(40, winsaveview().skipcol)
exe "normal! \<C-Y>"
call assert_equal(1, line('w0'))
call assert_equal(0, winsaveview().skipcol)
exe "normal! \<C-Y>"
call assert_equal(1, line('w0'))
call assert_equal(0, winsaveview().skipcol)
exe "normal! \<C-E>"
call assert_equal(1, line('w0'))
call assert_equal(40, winsaveview().skipcol)
exe "normal! \<C-E>"
call assert_equal(2, line('w0'))
call assert_equal(0, winsaveview().skipcol)
for ve in ['', 'all', 'onemore']
let &virtualedit = ve
normal! gg
call assert_equal(1, line('w0'))
call assert_equal(0, winsaveview().skipcol)
exe "normal! \<C-E>"
redraw " redrawing should not cause another scroll
call assert_equal(1, line('w0'))
call assert_equal(40, winsaveview().skipcol)
exe "normal! \<C-E>"
redraw
call assert_equal(2, line('w0'))
call assert_equal(0, winsaveview().skipcol)
if ve != 'all'
call assert_equal([0, 2, 1, 0], getpos('.'))
endif
endfor
set virtualedit&
bwipe!
endfunc
" vim: shiftwidth=2 sts=2 expandtab " vim: shiftwidth=2 sts=2 expandtab

View File

@@ -949,7 +949,7 @@ func Test_syn_contained_transparent()
endfunc endfunc
func Test_syn_include_contains_TOP() func Test_syn_include_contains_TOP()
let l:case = "TOP in included syntax means its group list name" let l:case = "TOP in included syntax refers to top level of that included syntax"
new new
syntax include @INCLUDED syntax/c.vim syntax include @INCLUDED syntax/c.vim
syntax region FencedCodeBlockC start=/```c/ end=/```/ contains=@INCLUDED syntax region FencedCodeBlockC start=/```c/ end=/```/ contains=@INCLUDED
@@ -964,6 +964,18 @@ func Test_syn_include_contains_TOP()
bw! bw!
endfunc endfunc
func Test_syn_include_contains_TOP_excluding()
new
syntax include @INCLUDED syntax/c.vim
syntax region FencedCodeBlockC start=/```c/ end=/```/ contains=@INCLUDED
call setline(1, ['```c', '#if 0', 'int', '#else', 'int', '#if', '#endif', '```' ])
let l:expected = ["cCppOutElse", "cConditional"]
eval AssertHighlightGroups(6, 1, l:expected, 1)
syntax clear
bw!
endfunc
" This was using freed memory " This was using freed memory
func Test_WinEnter_synstack_synID() func Test_WinEnter_synstack_synID()
autocmd WinEnter * call synstack(line("."), col(".")) autocmd WinEnter * call synstack(line("."), col("."))

View File

@@ -1623,4 +1623,39 @@ func Test_tagbsearch()
set tags& tagbsearch& set tags& tagbsearch&
endfunc endfunc
" Test tag guessing with very short names
func Test_tag_guess_short()
call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
\ "y\tXf\t/^y()/"],
\ 'Xt', 'D')
set tags=Xt cpoptions+=t
call writefile(['', 'int * y () {}', ''], 'Xf', 'D')
let v:statusmsg = ''
let @/ = ''
ta y
call assert_match('E435:', v:statusmsg)
call assert_equal(2, line('.'))
call assert_match('<y', @/)
set tags& cpoptions-=t
endfunc
func Test_tag_excmd_with_nostartofline()
call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
\ "f\tXfile\tascii"],
\ 'Xtags', 'D')
call writefile(['f', 'foobar'], 'Xfile', 'D')
set nostartofline
new Xfile
setlocal tags=Xtags
normal! G$
" This used to cause heap-buffer-overflow
tag f
bwipe!
set startofline&
endfunc
" vim: shiftwidth=2 sts=2 expandtab " vim: shiftwidth=2 sts=2 expandtab

View File

@@ -1,6 +1,7 @@
local t = require('test.unit.testutil') local t = require('test.unit.testutil')
local itp = t.gen_itp(it) local itp = t.gen_itp(it)
local child_call_once = t.child_call_once
local cimport = t.cimport local cimport = t.cimport
local eq = t.eq local eq = t.eq
local ffi = t.ffi local ffi = t.ffi
@@ -8,6 +9,12 @@ local to_cstr = t.to_cstr
local strings = cimport('stdlib.h', './src/nvim/strings.h', './src/nvim/memory.h') local strings = cimport('stdlib.h', './src/nvim/strings.h', './src/nvim/memory.h')
local UVARNUM_TYPE
child_call_once(function()
UVARNUM_TYPE = ffi.typeof('uvarnumber_T')
end)
describe('vim_strsave_escaped()', function() describe('vim_strsave_escaped()', function()
local vim_strsave_escaped = function(s, chars) local vim_strsave_escaped = function(s, chars)
local res = strings.vim_strsave_escaped(to_cstr(s), to_cstr(chars)) local res = strings.vim_strsave_escaped(to_cstr(s), to_cstr(chars))
@@ -140,13 +147,22 @@ end)
describe('vim_snprintf()', function() describe('vim_snprintf()', function()
local function a(expected, buf, bsize, fmt, ...) local function a(expected, buf, bsize, fmt, ...)
eq(#expected, strings.vim_snprintf(buf, bsize, fmt, ...)) local args = { ... }
local ctx = string.format('snprintf(buf, %d, "%s"', bsize, fmt)
for _, x in ipairs(args) do
ctx = ctx .. ', ' .. tostring(x)
end
ctx = ctx .. string.format(') = %s', expected)
eq(#expected, strings.vim_snprintf(buf, bsize, fmt, ...), ctx)
if bsize > 0 then if bsize > 0 then
local actual = ffi.string(buf, math.min(#expected + 1, bsize)) local actual = ffi.string(buf, math.min(#expected + 1, bsize))
eq(expected:sub(1, bsize - 1) .. '\0', actual) eq(expected:sub(1, bsize - 1) .. '\0', actual, ctx)
end end
end end
local function uv(n)
return ffi.cast(UVARNUM_TYPE, n)
end
local function i(n) local function i(n)
return ffi.cast('int', n) return ffi.cast('int', n)
end end
@@ -181,7 +197,7 @@ describe('vim_snprintf()', function()
a(' 1234567', buf, bsize, '%9ld', l(1234567)) a(' 1234567', buf, bsize, '%9ld', l(1234567))
a('1234567 ', buf, bsize, '%-9ld', l(1234567)) a('1234567 ', buf, bsize, '%-9ld', l(1234567))
a('deadbeef', buf, bsize, '%x', u(0xdeadbeef)) a('deadbeef', buf, bsize, '%x', u(0xdeadbeef))
a('001100', buf, bsize, '%06b', u(12)) a('001100', buf, bsize, '%06b', uv(12))
a('one two', buf, bsize, '%s %s', 'one', 'two') a('one two', buf, bsize, '%s %s', 'one', 'two')
a('1.234000', buf, bsize, '%f', 1.234) a('1.234000', buf, bsize, '%f', 1.234)
a('1.234000e+00', buf, bsize, '%e', 1.234) a('1.234000e+00', buf, bsize, '%e', 1.234)
@@ -223,10 +239,10 @@ describe('vim_snprintf()', function()
a('three one two', buf, bsize, '%3$s %1$s %2$s', 'one', 'two', 'three') a('three one two', buf, bsize, '%3$s %1$s %2$s', 'one', 'two', 'three')
a('1234567', buf, bsize, '%1$d', i(1234567)) a('1234567', buf, bsize, '%1$d', i(1234567))
a('deadbeef', buf, bsize, '%1$x', u(0xdeadbeef)) a('deadbeef', buf, bsize, '%1$x', u(0xdeadbeef))
a('001100', buf, bsize, '%2$0*1$b', i(6), u(12)) a('001100', buf, bsize, '%2$0*1$b', i(6), uv(12))
a('001100', buf, bsize, '%1$0.*2$b', u(12), i(6)) a('001100', buf, bsize, '%1$0.*2$b', uv(12), i(6))
a('one two', buf, bsize, '%1$s %2$s', 'one', 'two') a('one two', buf, bsize, '%1$s %2$s', 'one', 'two')
a('001100', buf, bsize, '%06b', u(12)) a('001100', buf, bsize, '%06b', uv(12))
a('two one', buf, bsize, '%2$s %1$s', 'one', 'two') a('two one', buf, bsize, '%2$s %1$s', 'one', 'two')
a('1.234000', buf, bsize, '%1$f', 1.234) a('1.234000', buf, bsize, '%1$f', 1.234)
a('1.234000e+00', buf, bsize, '%1$e', 1.234) a('1.234000e+00', buf, bsize, '%1$e', 1.234)

View File

@@ -146,11 +146,16 @@ local function filter_complex_blocks(body)
or string.find(line, 'value_init_') or string.find(line, 'value_init_')
or string.find(line, 'UUID_NULL') -- static const uuid_t UUID_NULL = {...} or string.find(line, 'UUID_NULL') -- static const uuid_t UUID_NULL = {...}
or string.find(line, 'inline _Bool') or string.find(line, 'inline _Bool')
-- used by musl libc headers on 32-bit arches via __REDIR marco
or string.find(line, '__typeof__')
-- used by macOS headers -- used by macOS headers
or string.find(line, 'typedef enum : ') or string.find(line, 'typedef enum : ')
or string.find(line, 'mach_vm_range_recipe') or string.find(line, 'mach_vm_range_recipe')
) )
then then
-- Remove GCC's extension keyword which is just used to disable warnings.
line = string.gsub(line, '__extension__', '')
result[#result + 1] = line result[#result + 1] = line
end end
end end