diff options
116 files changed, 2104 insertions, 1389 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d07b9fdac7..6a81ee4238 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,6 @@ jobs: # Workaround brew issues rm -f /usr/local/bin/2to3 brew update >/dev/null - brew upgrade brew install automake ccache perl cpanminus ninja - name: Setup interpreter packages diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f1ed05e6cb..5839be2944 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -82,7 +82,6 @@ jobs: run: | rm -f /usr/local/bin/2to3 brew update >/dev/null - brew upgrade brew install automake ninja - if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly') run: printf 'NVIM_BUILD_TYPE=Release\n' >> $GITHUB_ENV diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt index 613475b00d..30f08c5297 100644 --- a/config/CMakeLists.txt +++ b/config/CMakeLists.txt @@ -45,6 +45,7 @@ check_function_exists(readlink HAVE_READLINK) check_function_exists(setpgid HAVE_SETPGID) check_function_exists(setsid HAVE_SETSID) check_function_exists(sigaction HAVE_SIGACTION) +check_function_exists(strnlen HAVE_STRNLEN) check_function_exists(strcasecmp HAVE_STRCASECMP) check_function_exists(strncasecmp HAVE_STRNCASECMP) check_function_exists(strptime HAVE_STRPTIME) diff --git a/config/config.h.in b/config/config.h.in index 27a28116af..b0635fb52b 100644 --- a/config/config.h.in +++ b/config/config.h.in @@ -30,6 +30,7 @@ #cmakedefine HAVE_SETPGID #cmakedefine HAVE_SETSID #cmakedefine HAVE_SIGACTION +#cmakedefine HAVE_STRNLEN #cmakedefine HAVE_STRCASECMP #cmakedefine HAVE_STRINGS_H #cmakedefine HAVE_STRNCASECMP diff --git a/runtime/autoload/remote/define.vim b/runtime/autoload/remote/define.vim index 2aec96e365..82e5164d85 100644 --- a/runtime/autoload/remote/define.vim +++ b/runtime/autoload/remote/define.vim @@ -240,7 +240,11 @@ function! s:GetAutocmdPrefix(name, opts) endif if has_key(a:opts, 'nested') && a:opts.nested - call add(rv, 'nested') + call add(rv, '++nested') + endif + + if has_key(a:opts, 'once') && a:opts.once + call add(rv, '++once') endif return join(rv, ' ') diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index d2d010882e..8fb6290e50 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -1018,7 +1018,7 @@ nvim_get_mode() *nvim_get_mode()* {fast} nvim_get_option({name}) *nvim_get_option()* - Gets an option value string. + Gets the global value of an option. Parameters: ~ {name} Option name @@ -1049,6 +1049,24 @@ nvim_get_option_info({name}) *nvim_get_option_info()* Return: ~ Option Information +nvim_get_option_value({name}, {*opts}) *nvim_get_option_value()* + Gets the value of an option. The behavior of this function + matches that of |:set|: the local value of an option is + returned if it exists; otherwise, the global value is + returned. Local values always correspond to the current buffer + or window. To get a buffer-local or window-local option for a + specific buffer or window, use |nvim_buf_get_option()| or + |nvim_win_get_option()|. + + Parameters: ~ + {name} Option name + {opts} Optional parameters + • scope: One of 'global' or 'local'. Analagous to + |:setglobal| and |:setlocal|, respectively. + + Return: ~ + Option value + nvim_get_proc({pid}) *nvim_get_proc()* Gets info describing process `pid` . @@ -1510,11 +1528,25 @@ nvim_set_keymap({mode}, {lhs}, {rhs}, {*opts}) *nvim_set_keymap()* key is an error. nvim_set_option({name}, {value}) *nvim_set_option()* - Sets an option value. + Sets the global value of an option. + + Parameters: ~ + {name} Option name + {value} New option value + + *nvim_set_option_value()* +nvim_set_option_value({name}, {value}, {*opts}) + Sets the value of an option. The behavior of this function + matches that of |:set|: for global-local options, both the + global and local value are set unless otherwise specified with + {scope}. Parameters: ~ {name} Option name {value} New option value + {opts} Optional parameters + • scope: One of 'global' or 'local'. Analagous to + |:setglobal| and |:setlocal|, respectively. nvim_set_var({name}, {value}) *nvim_set_var()* Sets a global (g:) variable. diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 879a34bcaa..242631d98c 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -824,6 +824,10 @@ RemoteReply When a reply from a Vim that functions as Note that even if an autocommand is defined, the reply should be read with |remote_read()| to consume it. + *SearchWrapped* +SearchWrapped After making a search with |n| or |N| if the + search wraps around the document back to + the start/finish respectively. *SessionLoadPost* SessionLoadPost After loading the session file created using the |:mksession| command. diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index d02510a829..0893f1f343 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -41,6 +41,7 @@ requires a namespace. *diagnostic-structure* A diagnostic is a Lua table with the following keys: + bufnr: Buffer number lnum: The starting line of the diagnostic end_lnum: The final line of the diagnostic col: The starting column of the diagnostic @@ -48,6 +49,7 @@ A diagnostic is a Lua table with the following keys: severity: The severity of the diagnostic |vim.diagnostic.severity| message: The diagnostic text source: The source of the diagnostic + user_data: Arbitrary data plugins or users can add Diagnostics use the same indexing as the rest of the Nvim API (i.e. 0-based rows and columns). |api-indexing| diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 11f9d62e69..3793a21f36 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -630,6 +630,12 @@ client() *vim.lsp.client* server. • {handlers} (table): The handlers used by the client as described in |lsp-handler|. + • {requests} (table): The current pending requests in flight + to the server. Entries are key-value pairs with the key + being the request ID while the value is a table with + `type` , `bufnr` , and `method` key-value pairs. `type` is + either "pending" for an active request, or "cancel" for a + cancel request. • {config} (table): copy of the table that was passed by the user to |vim.lsp.start_client()|. • {server_capabilities} (table): Response from the server @@ -1326,12 +1332,14 @@ apply_text_document_edit({text_document_edit}, {index}) https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit *vim.lsp.util.apply_text_edits()* -apply_text_edits({text_edits}, {bufnr}) +apply_text_edits({text_edits}, {bufnr}, {offset_encoding}) Applies a list of text edits to a buffer. Parameters: ~ - {text_edits} table list of `TextEdit` objects - {bufnr} number Buffer id + {text_edits} table list of `TextEdit` objects + {bufnr} number Buffer id + {offset_encoding} string utf-8|utf-16|utf-32|nil defaults + to encoding of first client of `bufnr` See also: ~ https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit @@ -1358,37 +1366,30 @@ buf_highlight_references({bufnr}, {references}, {offset_encoding}) {references} table List of `DocumentHighlight` objects to highlight {offset_encoding} string One of "utf-8", "utf-16", - "utf-32", or nil. Defaults to utf-16 + "utf-32", or nil. Defaults to + `offset_encoding` of first client of + `bufnr` See also: ~ https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#documentHighlight *vim.lsp.util.character_offset()* -character_offset({bufnr}, {row}, {col}) +character_offset({buf}, {row}, {col}, {offset_encoding}) Returns the UTF-32 and UTF-16 offsets for a position in a certain buffer. Parameters: ~ - {buf} buffer id (0 for current) - {row} 0-indexed line - {col} 0-indexed byte offset in line + {buf} buffer id (0 for current) + {row} 0-indexed line + {col} 0-indexed byte offset in line + {offset_encoding} string utf-8|utf-16|utf-32|nil defaults + to `offset_encoding` of first client of + `buf` Return: ~ - (number, number) UTF-32 and UTF-16 index of the character + (number, number) `offset_encoding` index of the character in line {row} column {col} in buffer {buf} - *vim.lsp.util.close_preview_autocmd()* -close_preview_autocmd({events}, {winnr}) - Creates autocommands to close a preview window when events - happen. - - Parameters: ~ - {events} (table) list of events - {winnr} (number) window id of preview window - - See also: ~ - |autocmd-events| - *vim.lsp.util.convert_input_to_markdown_lines()* convert_input_to_markdown_lines({input}, {contents}) Converts any of `MarkedString` | `MarkedString[]` | @@ -1528,47 +1529,73 @@ make_formatting_params({options}) https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting *vim.lsp.util.make_given_range_params()* -make_given_range_params({start_pos}, {end_pos}) +make_given_range_params({start_pos}, {end_pos}, {bufnr}, {offset_encoding}) Using the given range in the current buffer, creates an object that is similar to |vim.lsp.util.make_range_params()|. Parameters: ~ - {start_pos} ({number, number}, optional) mark-indexed - position. Defaults to the start of the last - visual selection. - {end_pos} ({number, number}, optional) mark-indexed - position. Defaults to the end of the last - visual selection. + {start_pos} ({number, number}, optional) + mark-indexed position. Defaults to the + start of the last visual selection. + {end_pos} ({number, number}, optional) + mark-indexed position. Defaults to the + end of the last visual selection. + {bufnr} (optional, number): buffer handle or 0 + for current, defaults to current + {offset_encoding} string utf-8|utf-16|utf-32|nil defaults + to `offset_encoding` of first client of + `bufnr` Return: ~ { textDocument = { uri = `current_file_uri` }, range = { start = `start_position` , end = `end_position` } } -make_position_params() *vim.lsp.util.make_position_params()* + *vim.lsp.util.make_position_params()* +make_position_params({window}, {offset_encoding}) Creates a `TextDocumentPositionParams` object for the current buffer and cursor position. + Parameters: ~ + {window} (optional, number): window handle or 0 + for current, defaults to current + {offset_encoding} string utf-8|utf-16|utf-32|nil defaults + to `offset_encoding` of first client of + buffer of `window` + Return: ~ `TextDocumentPositionParams` object See also: ~ https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams -make_range_params() *vim.lsp.util.make_range_params()* + *vim.lsp.util.make_range_params()* +make_range_params({window}, {offset_encoding}) Using the current position in the current buffer, creates an object that can be used as a building block for several LSP requests, such as `textDocument/codeAction` , `textDocument/colorPresentation` , `textDocument/rangeFormatting` . + Parameters: ~ + {window} (optional, number): window handle or 0 + for current, defaults to current + {offset_encoding} string utf-8|utf-16|utf-32|nil defaults + to `offset_encoding` of first client of + buffer of `window` + Return: ~ { textDocument = { uri = `current_file_uri` }, range = { start = `current_position` , end = `current_position` } } -make_text_document_params() *vim.lsp.util.make_text_document_params()* + *vim.lsp.util.make_text_document_params()* +make_text_document_params({bufnr}) Creates a `TextDocumentIdentifier` object for the current buffer. + Parameters: ~ + {bufnr} (optional, number): Buffer handle, defaults to + current + Return: ~ `TextDocumentIdentifier` diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 47633c750c..097cd38400 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -3229,10 +3229,14 @@ A jump table for the options with a short description can be found at |Q_op|. 'inccommand' 'icm' string (default "nosplit") global - "nosplit": Shows the effects of a command incrementally, as you type. - "split" : Also shows partial off-screen results in a preview window. + When nonempty, shows the effects of |:substitute|, |:smagic|, and + |:snomagic| as you type. - Works for |:substitute|, |:smagic|, |:snomagic|. |hl-Substitute| + Possible values: + nosplit Shows the effects of a command incrementally in the + buffer. + split Like "nosplit", but also shows partial off-screen + results in a preview window. If the preview is too slow (exceeds 'redrawtime') then 'inccommand' is automatically disabled until |Command-line-mode| is done. diff --git a/runtime/doc/sign.txt b/runtime/doc/sign.txt index 895ee5ebef..68165f3d3d 100644 --- a/runtime/doc/sign.txt +++ b/runtime/doc/sign.txt @@ -47,6 +47,8 @@ The color of the column is set with the SignColumn highlight group :highlight SignColumn guibg=darkgrey < +If 'cursorline' is enabled, then the CursorLineSign highlight group is used +|hl-CursorLineSign|. *sign-identifier* Each placed sign is identified by a number called the sign identifier. This identifier is used to jump to the sign or to remove the sign. The identifier @@ -131,6 +133,10 @@ See |sign_define()| for the equivalent Vim script function. texthl={group} Highlighting group used for the text item. + culhl={group} + Highlighting group used for the text item when the cursor is + on the same line as the sign and 'cursorline' is enabled. + Example: > :sign define MySign text=>> texthl=Search linehl=DiffText < @@ -377,6 +383,9 @@ sign_define({list}) text text that is displayed when there is no icon or the GUI is not being used. texthl highlight group used for the text item + culhl highlight group used for the text item when + the cursor is on the same line as the sign and + 'cursorline' is enabled. numhl highlight group used for 'number' column at the associated line. Overrides |hl-LineNr|, |hl-CursorLineNr|. @@ -425,9 +434,13 @@ sign_getdefined([{name}]) *sign_getdefined()* or the GUI is not being used. texthl highlight group used for the text item; not present if not set. + culhl highlight group used for the text item when + the cursor is on the same line as the sign and + 'cursorline' is enabled; not present if not + set. numhl highlight group used for 'number' column at the associated line. Overrides |hl-LineNr|, - |hl-CursorLineNr|. + |hl-CursorLineNr|; not present if not set. Returns an empty List if there are no signs and when {name} is not found. diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index b57b8bfd5c..d49809599d 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -5127,6 +5127,10 @@ LineNrBelow Line number for when the 'relativenumber' *hl-CursorLineNr* CursorLineNr Like LineNr when 'cursorline' is set and 'cursorlineopt' contains "number" or is "both", for the cursor line. + *hl-CursorLineSign* +CursorLineSign Like SignColumn when 'cursorline' is set for the cursor line. + *hl-CursorLineFold* +CursorLineFold Like FoldColumn when 'cursorline' is set for the cursor line. *hl-MatchParen* MatchParen The character under the cursor or just before it, if it is a paired bracket, and its match. |pi_paren.txt| diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 08dc0583ac..8f7241dd46 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -421,9 +421,9 @@ get_node_text({node}, {source}) *get_node_text()* Gets the text corresponding to a given node Parameters: ~ - {node} the node - {bsource} The buffer or string from which the node is - extracted + {node} the node + {source} The buffer or string from which the node is + extracted get_query({lang}, {query_name}) *get_query()* Returns the runtime query {query_name} for {lang}. @@ -530,11 +530,9 @@ Query:iter_matches({self}, {node}, {source}, {start}, {stop}) for id, node in pairs(match) do local name = query.captures[id] -- `node` was captured by the `name` capture in the match -< -> - local node_data = metadata[id] -- Node level metadata -< -> + + local node_data = metadata[id] -- Node level metadata + ... use the info here ... end end diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 956cb3e624..bc59ea785e 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -180,6 +180,7 @@ Commands: |:match| can be invoked before highlight group is defined Events: + |SearchWrapped| |Signal| |TabNewEntered| |TermClose| diff --git a/runtime/filetype.vim b/runtime/filetype.vim index e1c917ac64..806748b7cb 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -256,7 +256,7 @@ au BufNewFile,BufRead *.lpc,*.ulpc setf lpc au BufNewFile,BufRead calendar setf calendar " C# -au BufNewFile,BufRead *.cs setf cs +au BufNewFile,BufRead *.cs,*.csx setf cs " CSDL au BufNewFile,BufRead *.csdl setf csdl @@ -786,6 +786,10 @@ au BufNewFile,BufRead *.hb setf hb " Httest au BufNewFile,BufRead *.htt,*.htb setf httest +" i3 (and sway) +au BufNewFile,BufRead */i3/config,*/sway/config setf i3config +au BufNewFile,BufRead */.i3/config,*/.sway/config setf i3config + " Icon au BufNewFile,BufRead *.icn setf icon @@ -1646,6 +1650,9 @@ au BufNewFile,BufRead .tcshrc,*.tcsh,tcsh.tcshrc,tcsh.login call dist#ft#SetFile " (patterns ending in a start further below) au BufNewFile,BufRead .login,.cshrc,csh.cshrc,csh.login,csh.logout,*.csh,.alias call dist#ft#CSH() +" Zig +au BufNewFile,BufRead *.zig setf zig + " Z-Shell script (patterns ending in a star further below) au BufNewFile,BufRead .zprofile,*/etc/zprofile,.zfbfmarks setf zsh au BufNewFile,BufRead .zshrc,.zshenv,.zlogin,.zlogout,.zcompdump setf zsh @@ -1732,6 +1739,9 @@ au BufNewFile,BufRead *.speedup,*.spdata,*.spd setf spup " Slice au BufNewFile,BufRead *.ice setf slice +" Microsoft Visual Studio Solution +au BufNewFile,BufRead *.sln setf solution + " Spice au BufNewFile,BufRead *.sp,*.spice setf spice diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 95bc4635b0..72a84dcc53 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1493,11 +1493,7 @@ local function adjust_start_col(lnum, line, items, encoding) end end if min_start_char then - if encoding == 'utf-8' then - return min_start_char - else - return vim.str_byteindex(line, min_start_char, encoding == 'utf-16') - end + return util._str_byteindex_enc(line, min_start_char, encoding) else return nil end diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 22089aaaa6..c974d1a6b4 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -285,7 +285,7 @@ local function location_handler(_, result, ctx, _) util.jump_to_location(result[1]) if #result > 1 then - util.set_qflist(util.locations_to_items(result)) + vim.fn.setqflist({}, ' ', {title = 'LSP locations', items = util.locations_to_items(result)}) api.nvim_command("copen") end else @@ -379,7 +379,7 @@ local make_call_hierarchy_handler = function(direction) }) end end - util.set_qflist(items) + vim.fn.setqflist({}, ' ', {title = 'LSP call hierarchy', items = items}) api.nvim_command("copen") end end diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index a3a91a34f7..68a030d50b 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -90,6 +90,42 @@ local function split_lines(value) return split(value, '\n', true) end +--- Convert byte index to `encoding` index. +--- Convenience wrapper around vim.str_utfindex +---@param line string line to be indexed +---@param index number byte index (utf-8), or `nil` for length +---@param encoding string utf-8|utf-16|utf-32|nil defaults to utf-16 +---@return number `encoding` index of `index` in `line` +function M._str_utfindex_enc(line, index, encoding) + if encoding ~= 'utf-8' then + local col32, col16 = vim.str_utfindex(line, index) + if encoding == 'utf-32' then + return col32 + else + return col16 + end + else + return index + end +end + +--- Convert UTF index to `encoding` index. +--- Convenience wrapper around vim.str_byteindex +---Alternative to vim.str_byteindex that takes an encoding. +---@param line string line to be indexed +---@param index number UTF index +---@param encoding string utf-8|utf-16|utf-32|nil defaults to utf-16 +---@return number byte (utf-8) index of `encoding` index `index` in `line` +function M._str_byteindex_enc(line, index, encoding) + if encoding ~= 'utf-8' then + return vim.str_byteindex(line, index, not encoding or encoding ~= 'utf-32') + else + return index + end +end + +local _str_utfindex_enc = M._str_utfindex_enc +local _str_byteindex_enc = M._str_byteindex_enc --- Replaces text in a range with new text. --- --- CAUTION: Changes in-place! @@ -237,6 +273,7 @@ end ---@private --- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position --- Returns a zero-indexed column, since set_lines() does the conversion to +---@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to utf-16 --- 1-indexed local function get_line_byte_from_position(bufnr, position, offset_encoding) -- LSP's line and characters are 0-indexed @@ -247,13 +284,7 @@ local function get_line_byte_from_position(bufnr, position, offset_encoding) if col > 0 then local line = get_line(bufnr, position.line) local ok, result - - if offset_encoding == "utf-16" or not offset_encoding then - ok, result = pcall(vim.str_byteindex, line, col, true) - elseif offset_encoding == "utf-32" then - ok, result = pcall(vim.str_byteindex, line, col, false) - end - + ok, result = pcall(_str_byteindex_enc, line, col, offset_encoding) if ok then return result end @@ -325,12 +356,15 @@ end --- Applies a list of text edits to a buffer. ---@param text_edits table list of `TextEdit` objects ---@param bufnr number Buffer id +---@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to encoding of first client of `bufnr` ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit -function M.apply_text_edits(text_edits, bufnr) +function M.apply_text_edits(text_edits, bufnr, offset_encoding) validate { text_edits = { text_edits, 't', false }; bufnr = { bufnr, 'number', false }; + offset_encoding = { offset_encoding, 'string', true }; } + offset_encoding = offset_encoding or M._get_offset_encoding(bufnr) if not next(text_edits) then return end if not api.nvim_buf_is_loaded(bufnr) then vim.fn.bufload(bufnr) @@ -367,8 +401,7 @@ function M.apply_text_edits(text_edits, bufnr) -- Some LSP servers may return +1 range of the buffer content but nvim_buf_set_text can't accept it so we should fix it here. local has_eol_text_edit = false local max = vim.api.nvim_buf_line_count(bufnr) - -- TODO handle offset_encoding - local _, len = vim.str_utfindex(vim.api.nvim_buf_get_lines(bufnr, -2, -1, false)[1] or '') + local len = _str_utfindex_enc(vim.api.nvim_buf_get_lines(bufnr, -2, -1, false)[1] or '', nil, offset_encoding) text_edits = vim.tbl_map(function(text_edit) if max <= text_edit.range.start.line then text_edit.range.start.line = max - 1 @@ -1220,17 +1253,57 @@ function M.stylize_markdown(bufnr, contents, opts) return stripped end +---@private --- Creates autocommands to close a preview window when events happen. --- ----@param events (table) list of events ----@param winnr (number) window id of preview window +---@param events table list of events +---@param winnr number window id of preview window +---@param bufnrs table list of buffers where the preview window will remain visible ---@see |autocmd-events| -function M.close_preview_autocmd(events, winnr) +local function close_preview_autocmd(events, winnr, bufnrs) + local augroup = 'preview_window_'..winnr + + -- close the preview window when entered a buffer that is not + -- the floating window buffer or the buffer that spawned it + vim.cmd(string.format([[ + augroup %s + autocmd! + autocmd BufEnter * lua vim.lsp.util._close_preview_window(%d, {%s}) + augroup end + ]], augroup, winnr, table.concat(bufnrs, ','))) + if #events > 0 then - api.nvim_command("autocmd "..table.concat(events, ',').." <buffer> ++once lua pcall(vim.api.nvim_win_close, "..winnr..", true)") + vim.cmd(string.format([[ + augroup %s + autocmd %s <buffer> lua vim.lsp.util._close_preview_window(%d) + augroup end + ]], augroup, table.concat(events, ','), winnr)) end end +---@private +--- Closes the preview window +--- +---@param winnr number window id of preview window +---@param bufnrs table|nil optional list of ignored buffers +function M._close_preview_window(winnr, bufnrs) + vim.schedule(function() + -- exit if we are in one of ignored buffers + if bufnrs and vim.tbl_contains(bufnrs, api.nvim_get_current_buf()) then + return + end + + local augroup = 'preview_window_'..winnr + vim.cmd(string.format([[ + augroup %s + autocmd! + augroup end + augroup! %s + ]], augroup, augroup)) + pcall(vim.api.nvim_win_close, winnr, true) + end) +end + ---@internal --- Computes size of float needed to show contents (with optional wrapping) --- @@ -1337,7 +1410,7 @@ function M.open_floating_preview(contents, syntax, opts) opts.wrap = opts.wrap ~= false -- wrapping by default opts.stylize_markdown = opts.stylize_markdown ~= false opts.focus = opts.focus ~= false - opts.close_events = opts.close_events or {"CursorMoved", "CursorMovedI", "BufHidden", "InsertCharPre"} + opts.close_events = opts.close_events or {"CursorMoved", "CursorMovedI", "InsertCharPre"} local bufnr = api.nvim_get_current_buf() @@ -1406,7 +1479,7 @@ function M.open_floating_preview(contents, syntax, opts) api.nvim_buf_set_option(floating_bufnr, 'modifiable', false) api.nvim_buf_set_option(floating_bufnr, 'bufhidden', 'wipe') api.nvim_buf_set_keymap(floating_bufnr, "n", "q", "<cmd>bdelete<cr>", {silent = true, noremap = true, nowait = true}) - M.close_preview_autocmd(opts.close_events, floating_winnr) + close_preview_autocmd(opts.close_events, floating_winnr, {floating_bufnr, bufnr}) -- save focus_id if opts.focus_id then @@ -1432,11 +1505,11 @@ do --[[ References ]] --- ---@param bufnr number Buffer id ---@param references table List of `DocumentHighlight` objects to highlight - ---@param offset_encoding string One of "utf-8", "utf-16", "utf-32", or nil. Defaults to utf-16 + ---@param offset_encoding string One of "utf-8", "utf-16", "utf-32", or nil. Defaults to `offset_encoding` of first client of `bufnr` ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#documentHighlight function M.buf_highlight_references(bufnr, references, offset_encoding) validate { bufnr = {bufnr, 'n', true} } - offset_encoding = offset_encoding or 'utf-16' + offset_encoding = offset_encoding or M._get_offset_encoding(bufnr) for _, reference in ipairs(references) do local start_line, start_char = reference["range"]["start"]["line"], reference["range"]["start"]["character"] local end_line, end_char = reference["range"]["end"]["line"], reference["range"]["end"]["character"] @@ -1649,43 +1722,78 @@ function M.try_trim_markdown_code_blocks(lines) return 'markdown' end -local str_utfindex = vim.str_utfindex ---@private -local function make_position_param() - local row, col = unpack(api.nvim_win_get_cursor(0)) +---@param window (optional, number): window handle or 0 for current, defaults to current +---@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window` +local function make_position_param(window, offset_encoding) + window = window or 0 + local buf = vim.api.nvim_win_get_buf(window) + local row, col = unpack(api.nvim_win_get_cursor(window)) + offset_encoding = offset_encoding or M._get_offset_encoding(buf) row = row - 1 - local line = api.nvim_buf_get_lines(0, row, row+1, true)[1] + local line = api.nvim_buf_get_lines(buf, row, row+1, true)[1] if not line then return { line = 0; character = 0; } end - -- TODO handle offset_encoding - local _ - _, col = str_utfindex(line, col) + + col = _str_utfindex_enc(line, col, offset_encoding) + return { line = row; character = col; } end --- Creates a `TextDocumentPositionParams` object for the current buffer and cursor position. --- +---@param window (optional, number): window handle or 0 for current, defaults to current +---@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window` ---@returns `TextDocumentPositionParams` object ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams -function M.make_position_params() +function M.make_position_params(window, offset_encoding) + window = window or 0 + local buf = vim.api.nvim_win_get_buf(window) + offset_encoding = offset_encoding or M._get_offset_encoding(buf) return { - textDocument = M.make_text_document_params(); - position = make_position_param() + textDocument = M.make_text_document_params(buf); + position = make_position_param(window, offset_encoding) } end +--- Utility function for getting the encoding of the first LSP client on the given buffer. +---@param bufnr (number) buffer handle or 0 for current, defaults to current +---@returns (string) encoding first client if there is one, nil otherwise +function M._get_offset_encoding(bufnr) + validate { + bufnr = {bufnr, 'n', true}; + } + + local offset_encoding + + for _, client in pairs(vim.lsp.buf_get_clients(bufnr)) do + local this_offset_encoding = client.offset_encoding or "utf-16" + if not offset_encoding then + offset_encoding = this_offset_encoding + elseif offset_encoding ~= this_offset_encoding then + vim.notify("warning: multiple different client offset_encodings detected for buffer, this is not supported yet", vim.log.levels.WARN) + end + end + + return offset_encoding +end + --- Using the current position in the current buffer, creates an object that --- can be used as a building block for several LSP requests, such as --- `textDocument/codeAction`, `textDocument/colorPresentation`, --- `textDocument/rangeFormatting`. --- +---@param window (optional, number): window handle or 0 for current, defaults to current +---@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window` ---@returns { textDocument = { uri = `current_file_uri` }, range = { start = ---`current_position`, end = `current_position` } } -function M.make_range_params() - local position = make_position_param() +function M.make_range_params(window, offset_encoding) + local buf = vim.api.nvim_win_get_buf(window) + offset_encoding = offset_encoding or M._get_offset_encoding(buf) + local position = make_position_param(window, offset_encoding) return { - textDocument = M.make_text_document_params(), + textDocument = M.make_text_document_params(buf), range = { start = position; ["end"] = position; } } end @@ -1697,27 +1805,29 @@ end ---Defaults to the start of the last visual selection. ---@param end_pos ({number, number}, optional) mark-indexed position. ---Defaults to the end of the last visual selection. +---@param bufnr (optional, number): buffer handle or 0 for current, defaults to current +---@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of `bufnr` ---@returns { textDocument = { uri = `current_file_uri` }, range = { start = ---`start_position`, end = `end_position` } } -function M.make_given_range_params(start_pos, end_pos) +function M.make_given_range_params(start_pos, end_pos, bufnr, offset_encoding) validate { start_pos = {start_pos, 't', true}; end_pos = {end_pos, 't', true}; + offset_encoding = {offset_encoding, 's', true}; } - local A = list_extend({}, start_pos or api.nvim_buf_get_mark(0, '<')) - local B = list_extend({}, end_pos or api.nvim_buf_get_mark(0, '>')) + bufnr = bufnr or 0 + offset_encoding = offset_encoding or M._get_offset_encoding(bufnr) + local A = list_extend({}, start_pos or api.nvim_buf_get_mark(bufnr, '<')) + local B = list_extend({}, end_pos or api.nvim_buf_get_mark(bufnr, '>')) -- convert to 0-index A[1] = A[1] - 1 B[1] = B[1] - 1 - -- account for encoding. - -- TODO handle offset_encoding + -- account for offset_encoding. if A[2] > 0 then - local _, char = M.character_offset(0, A[1], A[2]) - A = {A[1], char} + A = {A[1], M.character_offset(bufnr, A[1], A[2], offset_encoding)} end if B[2] > 0 then - local _, char = M.character_offset(0, B[1], B[2]) - B = {B[1], char} + B = {B[1], M.character_offset(bufnr, B[1], B[2], offset_encoding)} end -- we need to offset the end character position otherwise we loose the last -- character of the selection, as LSP end position is exclusive @@ -1726,7 +1836,7 @@ function M.make_given_range_params(start_pos, end_pos) B[2] = B[2] + 1 end return { - textDocument = M.make_text_document_params(), + textDocument = M.make_text_document_params(bufnr), range = { start = {line = A[1], character = A[2]}, ['end'] = {line = B[1], character = B[2]} @@ -1736,10 +1846,11 @@ end --- Creates a `TextDocumentIdentifier` object for the current buffer. --- +---@param bufnr (optional, number): Buffer handle, defaults to current ---@returns `TextDocumentIdentifier` ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentIdentifier -function M.make_text_document_params() - return { uri = vim.uri_from_bufnr(0) } +function M.make_text_document_params(bufnr) + return { uri = vim.uri_from_bufnr(bufnr or 0) } end --- Create the workspace params @@ -1782,14 +1893,16 @@ end ---@param buf buffer id (0 for current) ---@param row 0-indexed line ---@param col 0-indexed byte offset in line ----@returns (number, number) UTF-32 and UTF-16 index of the character in line {row} column {col} in buffer {buf} -function M.character_offset(bufnr, row, col) - local line = get_line(bufnr, row) +---@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of `buf` +---@returns (number, number) `offset_encoding` index of the character in line {row} column {col} in buffer {buf} +function M.character_offset(buf, row, col, offset_encoding) + local line = get_line(buf, row) + offset_encoding = offset_encoding or M._get_offset_encoding(buf) -- If the col is past the EOL, use the line length. if col > #line then - return str_utfindex(line) + return _str_utfindex_enc(line, nil, offset_encoding) end - return str_utfindex(line, col) + return _str_utfindex_enc(line, col, offset_encoding) end --- Helper function to return nested values in language server settings diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index b8255e61ed..ebed502c92 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -164,22 +164,36 @@ function M.parse_query(lang, query) return self end --- TODO(vigoux): support multiline nodes too - --- Gets the text corresponding to a given node --- ---@param node the node ----@param bsource The buffer or string from which the node is extracted +---@param source The buffer or string from which the node is extracted function M.get_node_text(node, source) local start_row, start_col, start_byte = node:start() local end_row, end_col, end_byte = node:end_() if type(source) == "number" then - if start_row ~= end_row then + local lines + local eof_row = a.nvim_buf_line_count(source) + if start_row >= eof_row then return nil end - local line = a.nvim_buf_get_lines(source, start_row, start_row+1, true)[1] - return string.sub(line, start_col+1, end_col) + + if end_col == 0 then + lines = a.nvim_buf_get_lines(source, start_row, end_row, true) + end_col = -1 + else + lines = a.nvim_buf_get_lines(source, start_row, end_row + 1, true) + end + + if #lines == 1 then + lines[1] = string.sub(lines[1], start_col+1, end_col) + else + lines[1] = string.sub(lines[1], start_col+1) + lines[#lines] = string.sub(lines[#lines], 1, end_col) + end + + return table.concat(lines, "\n") elseif type(source) == "string" then return source:sub(start_byte+1, end_byte) end @@ -211,11 +225,6 @@ local predicate_handlers = { ["lua-match?"] = function(match, _, source, predicate) local node = match[predicate[2]] local regex = predicate[3] - local start_row, _, end_row, _ = node:range() - if start_row ~= end_row then - return false - end - return string.find(M.get_node_text(node, source), regex) end, @@ -239,13 +248,8 @@ local predicate_handlers = { return function(match, _, source, pred) local node = match[pred[2]] - local start_row, start_col, end_row, end_col = node:range() - if start_row ~= end_row then - return false - end - local regex = compiled_vim_regexes[pred[3]] - return regex:match_line(source, start_row, start_col, end_col) + return regex:match_str(M.get_node_text(node, source)) end end)(), diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 185d55daed..bb16459a7f 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -468,9 +468,11 @@ list(APPEND NVIM_LINK_LIBRARIES if(UNIX) list(APPEND NVIM_LINK_LIBRARIES - m - util - ) + m) + if (NOT CMAKE_SYSTEM_NAME STREQUAL "SunOS") + list(APPEND NVIM_LINK_LIBRARIES + util) + endif() endif() set(NVIM_EXEC_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES} ${LUA_PREFERRED_LIBRARIES}) diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 718743ed9c..7988bff25a 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -29,7 +29,6 @@ #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/ops.h" #include "nvim/undo.h" diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index d470def277..9b407eab8b 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -513,7 +513,7 @@ String cbuf_to_string(const char *buf, size_t size) String cstrn_to_string(const char *str, size_t maxsize) FUNC_ATTR_NONNULL_ALL { - return cbuf_to_string(str, strnlen(str, maxsize)); + return cbuf_to_string(str, STRNLEN(str, maxsize)); } /// Creates a String using the given C string. Unlike diff --git a/src/nvim/aucmd.c b/src/nvim/aucmd.c index a236b47027..d7f73fa4a1 100644 --- a/src/nvim/aucmd.c +++ b/src/nvim/aucmd.c @@ -8,7 +8,6 @@ #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/main.h" -#include "nvim/misc1.h" #include "nvim/os/os.h" #include "nvim/ui.h" #include "nvim/vim.h" diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 1daae85c5e..6227b08b26 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -76,6 +76,7 @@ return { 'QuickFixCmdPre', -- before :make, :grep etc. 'QuitPre', -- before :quit 'RemoteReply', -- upon string reception from a remote vim + 'SearchWrapped', -- after the search wrapped around 'SessionLoadPost', -- after loading a session file 'ShellCmdPost', -- after ":!cmd" 'ShellFilterPost', -- after ":1,2!cmd", ":w !cmd", ":r !cmd". diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 9044657358..3780cad1d6 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -15,8 +15,8 @@ #include "nvim/ex_docmd.h" #include "nvim/fileio.h" #include "nvim/getchar.h" -#include "nvim/misc1.h" #include "nvim/option.h" +#include "nvim/os/input.h" #include "nvim/regexp.h" #include "nvim/search.h" #include "nvim/state.h" diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index f2f4950e58..89baea83f8 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -57,7 +57,6 @@ #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/os/input.h" @@ -2719,7 +2718,7 @@ void buflist_list(exarg_T *eap) IObuff[len++] = ' '; } while (--i > 0 && len < IOSIZE - 18); if (vim_strchr(eap->arg, 't') && buf->b_last_used) { - add_time(IObuff + len, (size_t)(IOSIZE - len), buf->b_last_used); + undo_fmt_time(IObuff + len, (size_t)(IOSIZE - len), buf->b_last_used); } else { vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), _("line %" PRId64), diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index e53b2d1dfa..49e527e98b 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -1116,12 +1116,6 @@ typedef struct { pos_T w_cursor_corr; // corrected cursor position } pos_save_T; -// Struct passed to get_v_event() and restore_v_event(). -typedef struct { - bool sve_did_save; - hashtab_T sve_hashtab; -} save_v_event_T; - /// Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode /// \addtogroup MENU_INDEX /// @{ diff --git a/src/nvim/change.c b/src/nvim/change.c index ef771125f1..1dbbfff024 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -19,7 +19,6 @@ #include "nvim/indent_c.h" #include "nvim/mark.h" #include "nvim/memline.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" @@ -131,7 +130,7 @@ void changed_internal(void) curbuf->b_changed = true; curbuf->b_changed_invalid = true; ml_setflags(curbuf); - check_status(curbuf); + redraw_buf_status_later(curbuf); redraw_tabline = true; need_maketitle = true; // set window title later } @@ -517,7 +516,7 @@ void unchanged(buf_T *buf, int ff, bool always_inc_changedtick) if (ff) { save_file_ff(buf); } - check_status(buf); + redraw_buf_status_later(buf); redraw_tabline = true; need_maketitle = true; // set window title later buf_inc_changedtick(buf); @@ -1863,3 +1862,289 @@ void del_lines(long nlines, bool undo) // adjust marks, mark the buffer as changed and prepare for displaying deleted_lines_mark(first, n); } + +/// Returns the length in bytes of the prefix of the given string which introduces a comment. +/// +/// If this string is not a comment then 0 is returned. +/// When "flags" is not NULL, it is set to point to the flags of the recognized comment leader. +/// "backward" must be true for the "O" command. +/// If "include_space" is set, include trailing whitespace while calculating the length. +int get_leader_len(char_u *line, char_u **flags, bool backward, bool include_space) +{ + int i, j; + int result; + int got_com = false; + int found_one; + char_u part_buf[COM_MAX_LEN]; // buffer for one option part + char_u *string; // pointer to comment string + char_u *list; + int middle_match_len = 0; + char_u *prev_list; + char_u *saved_flags = NULL; + + result = i = 0; + while (ascii_iswhite(line[i])) { // leading white space is ignored + i++; + } + + // Repeat to match several nested comment strings. + while (line[i] != NUL) { + // scan through the 'comments' option for a match + found_one = false; + for (list = curbuf->b_p_com; *list;) { + // Get one option part into part_buf[]. Advance "list" to next + // one. Put "string" at start of string. + if (!got_com && flags != NULL) { + *flags = list; // remember where flags started + } + prev_list = list; + (void)copy_option_part(&list, part_buf, COM_MAX_LEN, ","); + string = vim_strchr(part_buf, ':'); + if (string == NULL) { // missing ':', ignore this part + continue; + } + *string++ = NUL; // isolate flags from string + + // If we found a middle match previously, use that match when this + // is not a middle or end. + if (middle_match_len != 0 + && vim_strchr(part_buf, COM_MIDDLE) == NULL + && vim_strchr(part_buf, COM_END) == NULL) { + break; + } + + // When we already found a nested comment, only accept further + // nested comments. + if (got_com && vim_strchr(part_buf, COM_NEST) == NULL) { + continue; + } + + // When 'O' flag present and using "O" command skip this one. + if (backward && vim_strchr(part_buf, COM_NOBACK) != NULL) { + continue; + } + + // Line contents and string must match. + // When string starts with white space, must have some white space + // (but the amount does not need to match, there might be a mix of + // TABs and spaces). + if (ascii_iswhite(string[0])) { + if (i == 0 || !ascii_iswhite(line[i - 1])) { + continue; // missing white space + } + while (ascii_iswhite(string[0])) { + string++; + } + } + for (j = 0; string[j] != NUL && string[j] == line[i + j]; j++) { + } + if (string[j] != NUL) { + continue; // string doesn't match + } + // When 'b' flag used, there must be white space or an + // end-of-line after the string in the line. + if (vim_strchr(part_buf, COM_BLANK) != NULL + && !ascii_iswhite(line[i + j]) && line[i + j] != NUL) { + continue; + } + + // We have found a match, stop searching unless this is a middle + // comment. The middle comment can be a substring of the end + // comment in which case it's better to return the length of the + // end comment and its flags. Thus we keep searching with middle + // and end matches and use an end match if it matches better. + if (vim_strchr(part_buf, COM_MIDDLE) != NULL) { + if (middle_match_len == 0) { + middle_match_len = j; + saved_flags = prev_list; + } + continue; + } + if (middle_match_len != 0 && j > middle_match_len) { + // Use this match instead of the middle match, since it's a + // longer thus better match. + middle_match_len = 0; + } + + if (middle_match_len == 0) { + i += j; + } + found_one = true; + break; + } + + if (middle_match_len != 0) { + // Use the previously found middle match after failing to find a + // match with an end. + if (!got_com && flags != NULL) { + *flags = saved_flags; + } + i += middle_match_len; + found_one = true; + } + + // No match found, stop scanning. + if (!found_one) { + break; + } + + result = i; + + // Include any trailing white space. + while (ascii_iswhite(line[i])) { + i++; + } + + if (include_space) { + result = i; + } + + // If this comment doesn't nest, stop here. + got_com = true; + if (vim_strchr(part_buf, COM_NEST) == NULL) { + break; + } + } + return result; +} + +/// Return the offset at which the last comment in line starts. If there is no +/// comment in the whole line, -1 is returned. +/// +/// When "flags" is not null, it is set to point to the flags describing the +/// recognized comment leader. +int get_last_leader_offset(char_u *line, char_u **flags) +{ + int result = -1; + int i, j; + int lower_check_bound = 0; + char_u *string; + char_u *com_leader; + char_u *com_flags; + char_u *list; + int found_one; + char_u part_buf[COM_MAX_LEN]; // buffer for one option part + + // Repeat to match several nested comment strings. + i = (int)STRLEN(line); + while (--i >= lower_check_bound) { + // scan through the 'comments' option for a match + found_one = false; + for (list = curbuf->b_p_com; *list;) { + char_u *flags_save = list; + + // Get one option part into part_buf[]. Advance list to next one. + // put string at start of string. + (void)copy_option_part(&list, part_buf, COM_MAX_LEN, ","); + string = vim_strchr(part_buf, ':'); + if (string == NULL) { // If everything is fine, this cannot actually + // happen. + continue; + } + *string++ = NUL; // Isolate flags from string. + com_leader = string; + + // Line contents and string must match. + // When string starts with white space, must have some white space + // (but the amount does not need to match, there might be a mix of + // TABs and spaces). + if (ascii_iswhite(string[0])) { + if (i == 0 || !ascii_iswhite(line[i - 1])) { + continue; + } + while (ascii_iswhite(*string)) { + string++; + } + } + for (j = 0; string[j] != NUL && string[j] == line[i + j]; j++) { + // do nothing + } + if (string[j] != NUL) { + continue; + } + + // When 'b' flag used, there must be white space or an + // end-of-line after the string in the line. + if (vim_strchr(part_buf, COM_BLANK) != NULL + && !ascii_iswhite(line[i + j]) && line[i + j] != NUL) { + continue; + } + + if (vim_strchr(part_buf, COM_MIDDLE) != NULL) { + // For a middlepart comment, only consider it to match if + // everything before the current position in the line is + // whitespace. Otherwise we would think we are inside a + // comment if the middle part appears somewhere in the middle + // of the line. E.g. for C the "*" appears often. + for (j = 0; j <= i && ascii_iswhite(line[j]); j++) { + } + if (j < i) { + continue; + } + } + + // We have found a match, stop searching. + found_one = true; + + if (flags) { + *flags = flags_save; + } + com_flags = flags_save; + + break; + } + + if (found_one) { + char_u part_buf2[COM_MAX_LEN]; // buffer for one option part + int len1, len2, off; + + result = i; + // If this comment nests, continue searching. + if (vim_strchr(part_buf, COM_NEST) != NULL) { + continue; + } + + lower_check_bound = i; + + // Let's verify whether the comment leader found is a substring + // of other comment leaders. If it is, let's adjust the + // lower_check_bound so that we make sure that we have determined + // the comment leader correctly. + + while (ascii_iswhite(*com_leader)) { + com_leader++; + } + len1 = (int)STRLEN(com_leader); + + for (list = curbuf->b_p_com; *list;) { + char_u *flags_save = list; + + (void)copy_option_part(&list, part_buf2, COM_MAX_LEN, ","); + if (flags_save == com_flags) { + continue; + } + string = vim_strchr(part_buf2, ':'); + string++; + while (ascii_iswhite(*string)) { + string++; + } + len2 = (int)STRLEN(string); + if (len2 == 0) { + continue; + } + + // Now we have to verify whether string ends with a substring + // beginning the com_leader. + for (off = (len2 > i ? i : len2); off > 0 && off + len1 > len2;) { + off--; + if (!STRNCMP(string + off, com_leader, len2 - off)) { + if (i - off < lower_check_bound) { + lower_check_bound = i - off; + } + } + } + } + } + } + return result; +} diff --git a/src/nvim/change.h b/src/nvim/change.h index e1a1bfba17..e7c8a2b031 100644 --- a/src/nvim/change.h +++ b/src/nvim/change.h @@ -4,6 +4,13 @@ #include "nvim/buffer_defs.h" // for buf_T #include "nvim/pos.h" // for linenr_T +// flags for open_line() +#define OPENLINE_DELSPACES 1 // delete spaces after cursor +#define OPENLINE_DO_COM 2 // format comments +#define OPENLINE_KEEPTRAIL 4 // keep trailing spaces +#define OPENLINE_MARKFIX 8 // fix mark positions +#define OPENLINE_COM_LIST 16 // format comments with list/2nd line indent + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "change.h.generated.h" #endif diff --git a/src/nvim/channel.c b/src/nvim/channel.c index a662f3a951..cd5134fe5f 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -10,7 +10,6 @@ #include "nvim/event/socket.h" #include "nvim/fileio.h" #include "nvim/lua/executor.h" -#include "nvim/misc1.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/server.h" #include "nvim/os/shell.h" diff --git a/src/nvim/charset.c b/src/nvim/charset.c index eb0903b594..599d662993 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -21,7 +21,6 @@ #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/os_unix.h" diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index 4e1d7f9d78..6e2c6232d7 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -14,7 +14,6 @@ #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/plines.h" #include "nvim/screen.h" diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 1f8acd8c79..0233b3a5ab 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -29,7 +29,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" @@ -39,6 +38,7 @@ #include "nvim/screen.h" #include "nvim/strings.h" #include "nvim/undo.h" +#include "nvim/ui.h" #include "nvim/vim.h" #include "nvim/window.h" #include "xdiff/xdiff.h" diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index d1dd9b8309..8eda173cac 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -20,7 +20,6 @@ #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/normal.h" #include "nvim/os/input.h" #include "nvim/screen.h" diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 9bfb8a9d4a..2135d0bcd2 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -35,7 +35,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" @@ -832,6 +831,16 @@ static int insert_execute(VimState *state, int key) return insert_handle_key(s); } + +/// Return true when need to go to Insert mode because of 'insertmode'. +/// +/// Don't do this when still processing a command or a mapping. +/// Don't do this when inside a ":normal" command. +bool goto_im(void) +{ + return p_im && stuff_empty() && typebuf_typed(); +} + static int insert_handle_key(InsertState *s) { // The big switch to handle a character in insert mode. diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 85e81ee975..86384bc5b2 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -35,7 +35,6 @@ #include "nvim/lua/executor.h" #include "nvim/mark.h" #include "nvim/memline.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/ops.h" #include "nvim/option.h" @@ -303,6 +302,31 @@ const list_T *eval_msgpack_type_lists[] = { [kMPExt] = NULL, }; +dict_T *get_v_event(save_v_event_T *sve) +{ + dict_T *v_event = get_vim_var_dict(VV_EVENT); + + if (v_event->dv_hashtab.ht_used > 0) { + // recursive use of v:event, save, make empty and restore later + sve->sve_did_save = true; + sve->sve_hashtab = v_event->dv_hashtab; + hash_init(&v_event->dv_hashtab); + } else { + sve->sve_did_save = false; + } + return v_event; +} + +void restore_v_event(dict_T *v_event, save_v_event_T *sve) +{ + tv_dict_free_contents(v_event); + if (sve->sve_did_save) { + v_event->dv_hashtab = sve->sve_hashtab; + } else { + hash_init(&v_event->dv_hashtab); + } +} + // Return "n1" divided by "n2", taking care of dividing by zero. varnumber_T num_divide(varnumber_T n1, varnumber_T n2) FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 3b3a68bd29..a9ec5d47a6 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -193,6 +193,13 @@ extern const list_T *eval_msgpack_type_lists[LAST_MSGPACK_TYPE + 1]; #undef LAST_MSGPACK_TYPE +// Struct passed to get_v_event() and restore_v_event(). +typedef struct { + bool sve_did_save; + hashtab_T sve_hashtab; +} save_v_event_T; + + /// trans_function_name() flags typedef enum { TFN_INT = 1, ///< May use internal function name diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 33ca4016cf..5252c940f7 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -33,12 +33,12 @@ #include "nvim/if_cscope.h" #include "nvim/indent.h" #include "nvim/indent_c.h" +#include "nvim/input.h" #include "nvim/lua/executor.h" #include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/math.h" #include "nvim/memline.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/msgpack_rpc/channel.h" diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 3e37e8cbb6..11bbaaed9c 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -28,11 +28,10 @@ #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/os/input.h" #include "nvim/pos.h" #include "nvim/types.h" #include "nvim/vim.h" -// TODO(ZyX-I): Move line_breakcheck out of misc1 -#include "nvim/misc1.h" // For line_breakcheck #include "nvim/os/fileio.h" #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 9478a8441b..eb241eb8ae 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -17,7 +17,6 @@ #include "nvim/getchar.h" #include "nvim/globals.h" #include "nvim/lua/executor.h" -#include "nvim/misc1.h" #include "nvim/os/input.h" #include "nvim/regexp.h" #include "nvim/search.h" diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c index f070c8179f..3c43d1f98d 100644 --- a/src/nvim/event/rstream.c +++ b/src/nvim/event/rstream.c @@ -12,7 +12,7 @@ #include "nvim/event/rstream.h" #include "nvim/log.h" #include "nvim/memory.h" -#include "nvim/misc1.h" +#include "nvim/main.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index f95fe84f69..c0cb17fa61 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -39,6 +39,7 @@ #include "nvim/getchar.h" #include "nvim/highlight.h" #include "nvim/indent.h" +#include "nvim/input.h" #include "nvim/log.h" #include "nvim/main.h" #include "nvim/mark.h" @@ -46,7 +47,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 449e6f7bf5..33f9477608 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -35,12 +35,12 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/os/fs_defs.h" +#include "nvim/os/input.h" #include "nvim/os/shell.h" #include "nvim/os_unix.h" #include "nvim/path.h" diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 77cd50ecb7..9f0f8d93a3 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -39,6 +39,7 @@ #include "nvim/globals.h" #include "nvim/hardcopy.h" #include "nvim/if_cscope.h" +#include "nvim/input.h" #include "nvim/keymap.h" #include "nvim/lua/executor.h" #include "nvim/main.h" @@ -48,7 +49,6 @@ #include "nvim/memory.h" #include "nvim/menu.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" @@ -68,6 +68,7 @@ #include "nvim/spell.h" #include "nvim/spellfile.h" #include "nvim/strings.h" +#include "nvim/state.h" #include "nvim/syntax.h" #include "nvim/tag.h" #include "nvim/terminal.h" diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 475f22d061..ba2238ace2 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -48,7 +48,6 @@ #include "nvim/memory.h" #include "nvim/menu.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/ops.h" diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index f80a63560c..a37cad9f2d 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -28,7 +28,6 @@ #include "nvim/getchar.h" #include "nvim/globals.h" #include "nvim/keymap.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/os/input.h" diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index 5953a574f3..d31021b3ef 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -57,7 +57,6 @@ #include "nvim/globals.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option.h" #include "nvim/os/fs_defs.h" #include "nvim/os/input.h" diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 24428c2d9a..f8cf341836 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -30,12 +30,12 @@ #include "nvim/getchar.h" #include "nvim/hashtab.h" #include "nvim/iconv.h" +#include "nvim/input.h" #include "nvim/mbyte.h" #include "nvim/memfile.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 4a8be7a31b..b1d4321d4c 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -28,10 +28,10 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/os/input.h" #include "nvim/plines.h" #include "nvim/screen.h" #include "nvim/strings.h" diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 27e8cb36af..05c38a5233 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -29,6 +29,7 @@ #include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/input.h" #include "nvim/keymap.h" #include "nvim/lua/executor.h" #include "nvim/main.h" @@ -36,7 +37,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/ops.h" @@ -458,6 +458,15 @@ void flush_buffers(flush_buffers_T flush_typeahead) } } +/// flush map and typeahead buffers and give a warning for an error +void beep_flush(void) +{ + if (emsg_silent == 0) { + flush_buffers(FLUSH_MINIMAL); + vim_beep(BO_ERROR); + } +} + /* * The previous contents of the redo buffer is kept in old_redobuffer. * This is used for the CTRL-O <.> command in insert mode. diff --git a/src/nvim/globals.h b/src/nvim/globals.h index b2422fd531..dfbc80066e 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -202,7 +202,6 @@ EXTERN bool msg_did_scroll INIT(= false); EXTERN char_u *keep_msg INIT(= NULL); // msg to be shown after redraw EXTERN int keep_msg_attr INIT(= 0); // highlight attr for keep_msg -EXTERN bool keep_msg_more INIT(= false); // keep_msg was set by msgmore() EXTERN bool need_fileinfo INIT(= false); // do fileinfo() after redraw EXTERN int msg_scroll INIT(= false); // msg_start() will scroll EXTERN bool msg_didout INIT(= false); // msg_outstr() was used in line @@ -997,6 +996,8 @@ EXTERN char e_non_empty_string_required[] INIT(= N_("E1142: Non-empty string req EXTERN char e_cannot_define_autocommands_for_all_events[] INIT(= N_("E1155: Cannot define autocommands for ALL events")); +EXTERN char e_highlight_group_name_too_long[] INIT(= N_("E1249: Highlight group name too long")); + EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM")); EXTERN char bot_top_msg[] INIT(= N_("search hit BOTTOM, continuing at TOP")); diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index d4d53c4126..50a03e0c02 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -65,6 +65,8 @@ typedef enum { HLF_LNA, // LineNrAbove HLF_LNB, // LineNrBelow HLF_CLN, // current line number when 'cursorline' is set + HLF_CLS, // current line sign column + HLF_CLF, // current line fold HLF_R, // return to continue message and yes/no questions HLF_S, // status lines HLF_SNC, // status lines of not-current windows @@ -122,6 +124,8 @@ EXTERN const char *hlf_names[] INIT(= { [HLF_LNA] = "LineNrAbove", [HLF_LNB] = "LineNrBelow", [HLF_CLN] = "CursorLineNr", + [HLF_CLS] = "CursorLineSign", + [HLF_CLF] = "CursorLineFold", [HLF_R] = "Question", [HLF_S] = "StatusLine", [HLF_SNC] = "StatusLineNC", diff --git a/src/nvim/indent.c b/src/nvim/indent.c index f49aff6643..8cc5bc2436 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -17,7 +17,6 @@ #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index 3e3e07e9d6..faa9b38cf7 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -8,7 +8,6 @@ #include "nvim/vim.h" #include "nvim/ascii.h" -#include "nvim/misc1.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/edit.h" diff --git a/src/nvim/input.c b/src/nvim/input.c new file mode 100644 index 0000000000..2f7c5c2c16 --- /dev/null +++ b/src/nvim/input.c @@ -0,0 +1,255 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// input.c: high level functions for prompting the user or input +// like yes/no or number prompts. + +#include <inttypes.h> +#include <stdbool.h> + +#include "nvim/func_attr.h" +#include "nvim/getchar.h" +#include "nvim/mbyte.h" +#include "nvim/memory.h" +#include "nvim/input.h" +#include "nvim/mouse.h" +#include "nvim/os/input.h" +#include "nvim/ui.h" +#include "nvim/vim.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "input.c.generated.h" +#endif + +/// Ask for a reply from the user, 'y' or 'n' +/// +/// No other characters are accepted, the message is repeated until a valid +/// reply is entered or <C-c> is hit. +/// +/// @param[in] str Prompt: question to ask user. Is always followed by +/// " (y/n)?". +/// @param[in] direct Determines what function to use to get user input. If +/// true then ui_inchar() will be used, otherwise vgetc(). +/// I.e. when direct is true then characters are obtained +/// directly from the user without buffers involved. +/// +/// @return 'y' or 'n'. Last is also what will be returned in case of interrupt. +int ask_yesno(const char *const str, const bool direct) +{ + const int save_State = State; + + no_wait_return++; + State = CONFIRM; // Mouse behaves like with :confirm. + setmouse(); // Disable mouse in xterm. + no_mapping++; + + int r = ' '; + while (r != 'y' && r != 'n') { + // Same highlighting as for wait_return. + smsg_attr(HL_ATTR(HLF_R), "%s (y/n)?", str); + if (direct) { + r = get_keystroke(NULL); + } else { + r = plain_vgetc(); + } + if (r == Ctrl_C || r == ESC) { + r = 'n'; + } + msg_putchar(r); // Show what you typed. + ui_flush(); + } + no_wait_return--; + State = save_State; + setmouse(); + no_mapping--; + + return r; +} + +/// Get a key stroke directly from the user. +/// +/// Ignores mouse clicks and scrollbar events, except a click for the left +/// button (used at the more prompt). +/// Doesn't use vgetc(), because it syncs undo and eats mapped characters. +/// Disadvantage: typeahead is ignored. +/// Translates the interrupt character for unix to ESC. +int get_keystroke(MultiQueue *events) +{ + char_u *buf = NULL; + int buflen = 150; + int maxlen; + int len = 0; + int n; + int save_mapped_ctrl_c = mapped_ctrl_c; + int waited = 0; + + mapped_ctrl_c = 0; // mappings are not used here + for (;;) { + // flush output before waiting + ui_flush(); + // Leave some room for check_termcode() to insert a key code into (max + // 5 chars plus NUL). And fix_input_buffer() can triple the number of + // bytes. + maxlen = (buflen - 6 - len) / 3; + if (buf == NULL) { + buf = xmalloc((size_t)buflen); + } else if (maxlen < 10) { + // Need some more space. This might happen when receiving a long + // escape sequence. + buflen += 100; + buf = xrealloc(buf, (size_t)buflen); + maxlen = (buflen - 6 - len) / 3; + } + + // First time: blocking wait. Second time: wait up to 100ms for a + // terminal code to complete. + n = os_inchar(buf + len, maxlen, len == 0 ? -1L : 100L, 0, events); + if (n > 0) { + // Replace zero and CSI by a special key code. + n = fix_input_buffer(buf + len, n); + len += n; + waited = 0; + } else if (len > 0) { + waited++; // keep track of the waiting time + } + if (n > 0) { // found a termcode: adjust length + len = n; + } + if (len == 0) { // nothing typed yet + continue; + } + + // Handle modifier and/or special key code. + n = buf[0]; + if (n == K_SPECIAL) { + n = TO_SPECIAL(buf[1], buf[2]); + if (buf[1] == KS_MODIFIER + || n == K_IGNORE + || (is_mouse_key(n) && n != K_LEFTMOUSE)) { + if (buf[1] == KS_MODIFIER) { + mod_mask = buf[2]; + } + len -= 3; + if (len > 0) { + memmove(buf, buf + 3, (size_t)len); + } + continue; + } + break; + } + if (MB_BYTE2LEN(n) > len) { + // more bytes to get. + continue; + } + buf[len >= buflen ? buflen - 1 : len] = NUL; + n = utf_ptr2char(buf); + break; + } + xfree(buf); + + mapped_ctrl_c = save_mapped_ctrl_c; + return n; +} + +/// Get a number from the user. +/// When "mouse_used" is not NULL allow using the mouse. +/// +/// @param colon allow colon to abort +int get_number(int colon, int *mouse_used) +{ + int n = 0; + int c; + int typed = 0; + + if (mouse_used != NULL) { + *mouse_used = false; + } + + // When not printing messages, the user won't know what to type, return a + // zero (as if CR was hit). + if (msg_silent != 0) { + return 0; + } + + no_mapping++; + for (;;) { + ui_cursor_goto(msg_row, msg_col); + c = safe_vgetc(); + if (ascii_isdigit(c)) { + n = n * 10 + c - '0'; + msg_putchar(c); + typed++; + } else if (c == K_DEL || c == K_KDEL || c == K_BS || c == Ctrl_H) { + if (typed > 0) { + msg_puts("\b \b"); + typed--; + } + n /= 10; + } else if (mouse_used != NULL && c == K_LEFTMOUSE) { + *mouse_used = true; + n = mouse_row + 1; + break; + } else if (n == 0 && c == ':' && colon) { + stuffcharReadbuff(':'); + if (!exmode_active) { + cmdline_row = msg_row; + } + skip_redraw = true; // skip redraw once + do_redraw = false; + break; + } else if (c == Ctrl_C || c == ESC || c == 'q') { + n = 0; + break; + } else if (c == CAR || c == NL) { + break; + } + } + no_mapping--; + return n; +} + +/// Ask the user to enter a number. +/// +/// When "mouse_used" is not NULL allow using the mouse and in that case return +/// the line number. +int prompt_for_number(int *mouse_used) +{ + int i; + int save_cmdline_row; + int save_State; + + // When using ":silent" assume that <CR> was entered. + if (mouse_used != NULL) { + msg_puts(_("Type number and <Enter> or click with the mouse " + "(q or empty cancels): ")); + } else { + msg_puts(_("Type number and <Enter> (q or empty cancels): ")); + } + + // Set the state such that text can be selected/copied/pasted and we still + // get mouse events. + save_cmdline_row = cmdline_row; + cmdline_row = 0; + save_State = State; + State = ASKMORE; // prevents a screen update when using a timer + // May show different mouse shape. + setmouse(); + + i = get_number(true, mouse_used); + if (KeyTyped) { + // don't call wait_return() now + if (msg_row > 0) { + cmdline_row = msg_row - 1; + } + need_wait_return = false; + msg_didany = false; + msg_didout = false; + } else { + cmdline_row = save_cmdline_row; + } + State = save_State; + // May need to restore mouse shape. + setmouse(); + + return i; +} diff --git a/src/nvim/input.h b/src/nvim/input.h new file mode 100644 index 0000000000..7975f21215 --- /dev/null +++ b/src/nvim/input.h @@ -0,0 +1,9 @@ +#ifndef NVIM_INPUT_H +#define NVIM_INPUT_H + +#include "nvim/vim.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "input.h.generated.h" +#endif +#endif // NVIM_INPUT_H diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index a899ca63ac..b09d133495 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -31,7 +31,6 @@ #include "nvim/map.h" #include "nvim/memline.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/os/os.h" #include "nvim/screen.h" diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c index c9f82a2df1..0d6789317c 100644 --- a/src/nvim/lua/stdlib.c +++ b/src/nvim/lua/stdlib.c @@ -34,7 +34,6 @@ #include "nvim/map.h" #include "nvim/memline.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/os/os.h" #include "nvim/regexp.h" diff --git a/src/nvim/main.c b/src/nvim/main.c index 921bc883cf..cbd1f53727 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -39,7 +39,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" @@ -696,6 +695,50 @@ void getout(int exitval) os_exit(exitval); } +/// Preserve files and exit. +/// @note IObuff must contain a message. +/// @note This may be called from deadly_signal() in a signal handler, avoid +/// unsafe functions, such as allocating memory. +void preserve_exit(void) + FUNC_ATTR_NORETURN +{ + // 'true' when we are sure to exit, e.g., after a deadly signal + static bool really_exiting = false; + + // Prevent repeated calls into this method. + if (really_exiting) { + if (input_global_fd() >= 0) { + // normalize stream (#2598) + stream_set_blocking(input_global_fd(), true); + } + exit(2); + } + + really_exiting = true; + // Ignore SIGHUP while we are already exiting. #9274 + signal_reject_deadly(); + mch_errmsg(IObuff); + mch_errmsg("\n"); + ui_flush(); + + ml_close_notmod(); // close all not-modified buffers + + FOR_ALL_BUFFERS(buf) { + if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) { + mch_errmsg("Vim: preserving files...\r\n"); + ui_flush(); + ml_sync_all(false, false, true); // preserve all swap files + break; + } + } + + ml_close_all(false); // close all memfiles, without deleting + + mch_errmsg("Vim: Finished.\r\n"); + + getout(1); +} + /// Gets the integer value of a numeric command line argument if given, /// such as '-o10'. /// diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 42117bc762..ce44f6c619 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -42,13 +42,13 @@ #include "nvim/eval.h" #include "nvim/fileio.h" #include "nvim/func_attr.h" +#include "nvim/getchar.h" #include "nvim/iconv.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option.h" #include "nvim/os/os.h" #include "nvim/path.h" diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 08202a6d5c..08521c0dc3 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -51,6 +51,7 @@ #include "nvim/fileio.h" #include "nvim/func_attr.h" #include "nvim/getchar.h" +#include "nvim/input.h" #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -58,7 +59,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/os/os.h" @@ -1841,6 +1841,17 @@ char_u *ml_get_pos(const pos_T *pos) return ml_get_buf(curbuf, pos->lnum, false) + pos->col; } +/// get codepoint at pos. pos must be either valid or have col set to MAXCOL! +int gchar_pos(pos_T *pos) + FUNC_ATTR_NONNULL_ARG(1) +{ + // When searching columns is sometimes put at the end of a line. + if (pos->col == MAXCOL) { + return NUL; + } + return utf_ptr2char(ml_get_pos(pos)); +} + /// Return a pointer to a line in a specific buffer /// /// @param will_change true mark the buffer dirty (chars in the line will be changed) diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 3d621ebbb7..677ff8f522 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -17,7 +17,6 @@ #include "nvim/memfile.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/sign.h" #include "nvim/ui.h" #include "nvim/vim.h" diff --git a/src/nvim/menu.c b/src/nvim/menu.c index d596b31062..ac4d52c392 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -22,7 +22,6 @@ #include "nvim/memory.h" #include "nvim/menu.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/screen.h" #include "nvim/state.h" #include "nvim/strings.h" diff --git a/src/nvim/message.c b/src/nvim/message.c index 8a6ac2decc..befca8c76b 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -23,12 +23,12 @@ #include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/highlight.h" +#include "nvim/input.h" #include "nvim/keymap.h" #include "nvim/main.h" #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/normal.h" #include "nvim/ops.h" @@ -76,6 +76,8 @@ static int msg_hist_len = 0; static FILE *verbose_fd = NULL; static int verbose_did_open = FALSE; +bool keep_msg_more = false; // keep_msg was set by msgmore() + /* * When writing messages to the screen, there are many different situations. * A number of variables is used to remember the current state: @@ -1298,6 +1300,49 @@ void set_keep_msg(char *s, int attr) keep_msg_attr = attr; } +void msgmore(long n) +{ + long pn; + + if (global_busy // no messages now, wait until global is finished + || !messaging()) { // 'lazyredraw' set, don't do messages now + return; + } + + // We don't want to overwrite another important message, but do overwrite + // a previous "more lines" or "fewer lines" message, so that "5dd" and + // then "put" reports the last action. + if (keep_msg != NULL && !keep_msg_more) { + return; + } + + if (n > 0) { + pn = n; + } else { + pn = -n; + } + + if (pn > p_report) { + if (n > 0) { + vim_snprintf(msg_buf, MSG_BUF_LEN, + NGETTEXT("%ld more line", "%ld more lines", pn), + pn); + } else { + vim_snprintf(msg_buf, MSG_BUF_LEN, + NGETTEXT("%ld line less", "%ld fewer lines", pn), + pn); + } + if (got_int) { + xstrlcat(msg_buf, _(" (Interrupted)"), MSG_BUF_LEN); + } + if (msg(msg_buf)) { + set_keep_msg(msg_buf, 0); + keep_msg_more = true; + } + } +} + + void msg_ext_set_kind(const char *msg_kind) { // Don't change the label of an existing batch: @@ -2054,7 +2099,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs msg_ext_last_attr = attr; } // Concat pieces with the same highlight - size_t len = strnlen((char *)str, maxlen); // -V781 + size_t len = STRNLEN(str, maxlen); // -V781 ga_concat_len(&msg_ext_last_chunk, (char *)str, len); msg_ext_cur_len += len; return; diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c deleted file mode 100644 index 872a2c58e3..0000000000 --- a/src/nvim/misc1.c +++ /dev/null @@ -1,1116 +0,0 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - -/* - * misc1.c: functions that didn't seem to fit elsewhere - */ - -#include <assert.h> -#include <inttypes.h> -#include <limits.h> -#include <stdbool.h> -#include <string.h> - -#include "nvim/ascii.h" -#include "nvim/buffer.h" -#include "nvim/buffer_updates.h" -#include "nvim/charset.h" -#include "nvim/cursor.h" -#include "nvim/diff.h" -#include "nvim/edit.h" -#include "nvim/eval.h" -#include "nvim/event/stream.h" -#include "nvim/ex_cmds.h" -#include "nvim/ex_docmd.h" -#include "nvim/ex_getln.h" -#include "nvim/fileio.h" -#include "nvim/fold.h" -#include "nvim/func_attr.h" -#include "nvim/garray.h" -#include "nvim/getchar.h" -#include "nvim/indent.h" -#include "nvim/indent_c.h" -#include "nvim/main.h" -#include "nvim/mbyte.h" -#include "nvim/memline.h" -#include "nvim/memory.h" -#include "nvim/message.h" -#include "nvim/misc1.h" -#include "nvim/mouse.h" -#include "nvim/move.h" -#include "nvim/option.h" -#include "nvim/os/input.h" -#include "nvim/os/os.h" -#include "nvim/os/shell.h" -#include "nvim/os/signal.h" -#include "nvim/os/time.h" -#include "nvim/os_unix.h" -#include "nvim/quickfix.h" -#include "nvim/regexp.h" -#include "nvim/screen.h" -#include "nvim/search.h" -#include "nvim/state.h" -#include "nvim/strings.h" -#include "nvim/tag.h" -#include "nvim/ui.h" -#include "nvim/undo.h" -#include "nvim/vim.h" -#include "nvim/window.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "misc1.c.generated.h" -#endif -// All user names (for ~user completion as done by shell). -static garray_T ga_users = GA_EMPTY_INIT_VALUE; - -/* - * get_leader_len() returns the length in bytes of the prefix of the given - * string which introduces a comment. If this string is not a comment then - * 0 is returned. - * When "flags" is not NULL, it is set to point to the flags of the recognized - * comment leader. - * "backward" must be true for the "O" command. - * If "include_space" is set, include trailing whitespace while calculating the - * length. - */ -int get_leader_len(char_u *line, char_u **flags, bool backward, bool include_space) -{ - int i, j; - int result; - int got_com = FALSE; - int found_one; - char_u part_buf[COM_MAX_LEN]; // buffer for one option part - char_u *string; // pointer to comment string - char_u *list; - int middle_match_len = 0; - char_u *prev_list; - char_u *saved_flags = NULL; - - result = i = 0; - while (ascii_iswhite(line[i])) { // leading white space is ignored - ++i; - } - - /* - * Repeat to match several nested comment strings. - */ - while (line[i] != NUL) { - /* - * scan through the 'comments' option for a match - */ - found_one = FALSE; - for (list = curbuf->b_p_com; *list;) { - // Get one option part into part_buf[]. Advance "list" to next - // one. Put "string" at start of string. - if (!got_com && flags != NULL) { - *flags = list; // remember where flags started - } - prev_list = list; - (void)copy_option_part(&list, part_buf, COM_MAX_LEN, ","); - string = vim_strchr(part_buf, ':'); - if (string == NULL) { // missing ':', ignore this part - continue; - } - *string++ = NUL; // isolate flags from string - - // If we found a middle match previously, use that match when this - // is not a middle or end. - if (middle_match_len != 0 - && vim_strchr(part_buf, COM_MIDDLE) == NULL - && vim_strchr(part_buf, COM_END) == NULL) { - break; - } - - // When we already found a nested comment, only accept further - // nested comments. - if (got_com && vim_strchr(part_buf, COM_NEST) == NULL) { - continue; - } - - // When 'O' flag present and using "O" command skip this one. - if (backward && vim_strchr(part_buf, COM_NOBACK) != NULL) { - continue; - } - - // Line contents and string must match. - // When string starts with white space, must have some white space - // (but the amount does not need to match, there might be a mix of - // TABs and spaces). - if (ascii_iswhite(string[0])) { - if (i == 0 || !ascii_iswhite(line[i - 1])) { - continue; // missing white space - } - while (ascii_iswhite(string[0])) { - ++string; - } - } - for (j = 0; string[j] != NUL && string[j] == line[i + j]; ++j) { - } - if (string[j] != NUL) { - continue; // string doesn't match - } - // When 'b' flag used, there must be white space or an - // end-of-line after the string in the line. - if (vim_strchr(part_buf, COM_BLANK) != NULL - && !ascii_iswhite(line[i + j]) && line[i + j] != NUL) { - continue; - } - - // We have found a match, stop searching unless this is a middle - // comment. The middle comment can be a substring of the end - // comment in which case it's better to return the length of the - // end comment and its flags. Thus we keep searching with middle - // and end matches and use an end match if it matches better. - if (vim_strchr(part_buf, COM_MIDDLE) != NULL) { - if (middle_match_len == 0) { - middle_match_len = j; - saved_flags = prev_list; - } - continue; - } - if (middle_match_len != 0 && j > middle_match_len) { - // Use this match instead of the middle match, since it's a - // longer thus better match. - middle_match_len = 0; - } - - if (middle_match_len == 0) { - i += j; - } - found_one = TRUE; - break; - } - - if (middle_match_len != 0) { - // Use the previously found middle match after failing to find a - // match with an end. - if (!got_com && flags != NULL) { - *flags = saved_flags; - } - i += middle_match_len; - found_one = TRUE; - } - - // No match found, stop scanning. - if (!found_one) { - break; - } - - result = i; - - // Include any trailing white space. - while (ascii_iswhite(line[i])) { - ++i; - } - - if (include_space) { - result = i; - } - - // If this comment doesn't nest, stop here. - got_com = TRUE; - if (vim_strchr(part_buf, COM_NEST) == NULL) { - break; - } - } - return result; -} - -/* - * Return the offset at which the last comment in line starts. If there is no - * comment in the whole line, -1 is returned. - * - * When "flags" is not null, it is set to point to the flags describing the - * recognized comment leader. - */ -int get_last_leader_offset(char_u *line, char_u **flags) -{ - int result = -1; - int i, j; - int lower_check_bound = 0; - char_u *string; - char_u *com_leader; - char_u *com_flags; - char_u *list; - int found_one; - char_u part_buf[COM_MAX_LEN]; // buffer for one option part - - /* - * Repeat to match several nested comment strings. - */ - i = (int)STRLEN(line); - while (--i >= lower_check_bound) { - /* - * scan through the 'comments' option for a match - */ - found_one = FALSE; - for (list = curbuf->b_p_com; *list;) { - char_u *flags_save = list; - - /* - * Get one option part into part_buf[]. Advance list to next one. - * put string at start of string. - */ - (void)copy_option_part(&list, part_buf, COM_MAX_LEN, ","); - string = vim_strchr(part_buf, ':'); - if (string == NULL) { // If everything is fine, this cannot actually - // happen. - continue; - } - *string++ = NUL; // Isolate flags from string. - com_leader = string; - - /* - * Line contents and string must match. - * When string starts with white space, must have some white space - * (but the amount does not need to match, there might be a mix of - * TABs and spaces). - */ - if (ascii_iswhite(string[0])) { - if (i == 0 || !ascii_iswhite(line[i - 1])) { - continue; - } - while (ascii_iswhite(*string)) { - string++; - } - } - for (j = 0; string[j] != NUL && string[j] == line[i + j]; ++j) { - // do nothing - } - if (string[j] != NUL) { - continue; - } - - /* - * When 'b' flag used, there must be white space or an - * end-of-line after the string in the line. - */ - if (vim_strchr(part_buf, COM_BLANK) != NULL - && !ascii_iswhite(line[i + j]) && line[i + j] != NUL) { - continue; - } - - if (vim_strchr(part_buf, COM_MIDDLE) != NULL) { - // For a middlepart comment, only consider it to match if - // everything before the current position in the line is - // whitespace. Otherwise we would think we are inside a - // comment if the middle part appears somewhere in the middle - // of the line. E.g. for C the "*" appears often. - for (j = 0; j <= i && ascii_iswhite(line[j]); j++) { - } - if (j < i) { - continue; - } - } - - /* - * We have found a match, stop searching. - */ - found_one = TRUE; - - if (flags) { - *flags = flags_save; - } - com_flags = flags_save; - - break; - } - - if (found_one) { - char_u part_buf2[COM_MAX_LEN]; // buffer for one option part - int len1, len2, off; - - result = i; - /* - * If this comment nests, continue searching. - */ - if (vim_strchr(part_buf, COM_NEST) != NULL) { - continue; - } - - lower_check_bound = i; - - // Let's verify whether the comment leader found is a substring - // of other comment leaders. If it is, let's adjust the - // lower_check_bound so that we make sure that we have determined - // the comment leader correctly. - - while (ascii_iswhite(*com_leader)) { - ++com_leader; - } - len1 = (int)STRLEN(com_leader); - - for (list = curbuf->b_p_com; *list;) { - char_u *flags_save = list; - - (void)copy_option_part(&list, part_buf2, COM_MAX_LEN, ","); - if (flags_save == com_flags) { - continue; - } - string = vim_strchr(part_buf2, ':'); - ++string; - while (ascii_iswhite(*string)) { - ++string; - } - len2 = (int)STRLEN(string); - if (len2 == 0) { - continue; - } - - // Now we have to verify whether string ends with a substring - // beginning the com_leader. - for (off = (len2 > i ? i : len2); off > 0 && off + len1 > len2;) { - --off; - if (!STRNCMP(string + off, com_leader, len2 - off)) { - if (i - off < lower_check_bound) { - lower_check_bound = i - off; - } - } - } - } - } - } - return result; -} - -int gchar_pos(pos_T *pos) - FUNC_ATTR_NONNULL_ARG(1) -{ - // When searching columns is sometimes put at the end of a line. - if (pos->col == MAXCOL) { - return NUL; - } - return utf_ptr2char(ml_get_pos(pos)); -} - -/* - * check_status: called when the status bars for the buffer 'buf' - * need to be updated - */ -void check_status(buf_T *buf) -{ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == buf && wp->w_status_height) { - wp->w_redr_status = TRUE; - if (must_redraw < VALID) { - must_redraw = VALID; - } - } - } -} - -/// Ask for a reply from the user, 'y' or 'n' -/// -/// No other characters are accepted, the message is repeated until a valid -/// reply is entered or <C-c> is hit. -/// -/// @param[in] str Prompt: question to ask user. Is always followed by -/// " (y/n)?". -/// @param[in] direct Determines what function to use to get user input. If -/// true then ui_inchar() will be used, otherwise vgetc(). -/// I.e. when direct is true then characters are obtained -/// directly from the user without buffers involved. -/// -/// @return 'y' or 'n'. Last is also what will be returned in case of interrupt. -int ask_yesno(const char *const str, const bool direct) -{ - const int save_State = State; - - no_wait_return++; - State = CONFIRM; // Mouse behaves like with :confirm. - setmouse(); // Disable mouse in xterm. - no_mapping++; - - int r = ' '; - while (r != 'y' && r != 'n') { - // Same highlighting as for wait_return. - smsg_attr(HL_ATTR(HLF_R), "%s (y/n)?", str); - if (direct) { - r = get_keystroke(NULL); - } else { - r = plain_vgetc(); - } - if (r == Ctrl_C || r == ESC) { - r = 'n'; - } - msg_putchar(r); // Show what you typed. - ui_flush(); - } - no_wait_return--; - State = save_State; - setmouse(); - no_mapping--; - - return r; -} - -/* - * Return TRUE if "c" is a mouse key. - */ -int is_mouse_key(int c) -{ - return c == K_LEFTMOUSE - || c == K_LEFTMOUSE_NM - || c == K_LEFTDRAG - || c == K_LEFTRELEASE - || c == K_LEFTRELEASE_NM - || c == K_MOUSEMOVE - || c == K_MIDDLEMOUSE - || c == K_MIDDLEDRAG - || c == K_MIDDLERELEASE - || c == K_RIGHTMOUSE - || c == K_RIGHTDRAG - || c == K_RIGHTRELEASE - || c == K_MOUSEDOWN - || c == K_MOUSEUP - || c == K_MOUSELEFT - || c == K_MOUSERIGHT - || c == K_X1MOUSE - || c == K_X1DRAG - || c == K_X1RELEASE - || c == K_X2MOUSE - || c == K_X2DRAG - || c == K_X2RELEASE; -} - -/* - * Get a key stroke directly from the user. - * Ignores mouse clicks and scrollbar events, except a click for the left - * button (used at the more prompt). - * Doesn't use vgetc(), because it syncs undo and eats mapped characters. - * Disadvantage: typeahead is ignored. - * Translates the interrupt character for unix to ESC. - */ -int get_keystroke(MultiQueue *events) -{ - char_u *buf = NULL; - int buflen = 150; - int maxlen; - int len = 0; - int n; - int save_mapped_ctrl_c = mapped_ctrl_c; - int waited = 0; - - mapped_ctrl_c = 0; // mappings are not used here - for (;;) { - // flush output before waiting - ui_flush(); - // Leave some room for check_termcode() to insert a key code into (max - // 5 chars plus NUL). And fix_input_buffer() can triple the number of - // bytes. - maxlen = (buflen - 6 - len) / 3; - if (buf == NULL) { - buf = xmalloc((size_t)buflen); - } else if (maxlen < 10) { - // Need some more space. This might happen when receiving a long - // escape sequence. - buflen += 100; - buf = xrealloc(buf, (size_t)buflen); - maxlen = (buflen - 6 - len) / 3; - } - - // First time: blocking wait. Second time: wait up to 100ms for a - // terminal code to complete. - n = os_inchar(buf + len, maxlen, len == 0 ? -1L : 100L, 0, events); - if (n > 0) { - // Replace zero and CSI by a special key code. - n = fix_input_buffer(buf + len, n); - len += n; - waited = 0; - } else if (len > 0) { - ++waited; // keep track of the waiting time - } - if (n > 0) { // found a termcode: adjust length - len = n; - } - if (len == 0) { // nothing typed yet - continue; - } - - // Handle modifier and/or special key code. - n = buf[0]; - if (n == K_SPECIAL) { - n = TO_SPECIAL(buf[1], buf[2]); - if (buf[1] == KS_MODIFIER - || n == K_IGNORE - || (is_mouse_key(n) && n != K_LEFTMOUSE)) { - if (buf[1] == KS_MODIFIER) { - mod_mask = buf[2]; - } - len -= 3; - if (len > 0) { - memmove(buf, buf + 3, (size_t)len); - } - continue; - } - break; - } - if (MB_BYTE2LEN(n) > len) { - // more bytes to get. - continue; - } - buf[len >= buflen ? buflen - 1 : len] = NUL; - n = utf_ptr2char(buf); - break; - } - xfree(buf); - - mapped_ctrl_c = save_mapped_ctrl_c; - return n; -} - -/// Get a number from the user. -/// When "mouse_used" is not NULL allow using the mouse. -/// -/// @param colon allow colon to abort -int get_number(int colon, int *mouse_used) -{ - int n = 0; - int c; - int typed = 0; - - if (mouse_used != NULL) { - *mouse_used = FALSE; - } - - // When not printing messages, the user won't know what to type, return a - // zero (as if CR was hit). - if (msg_silent != 0) { - return 0; - } - - no_mapping++; - for (;;) { - ui_cursor_goto(msg_row, msg_col); - c = safe_vgetc(); - if (ascii_isdigit(c)) { - n = n * 10 + c - '0'; - msg_putchar(c); - ++typed; - } else if (c == K_DEL || c == K_KDEL || c == K_BS || c == Ctrl_H) { - if (typed > 0) { - msg_puts("\b \b"); - --typed; - } - n /= 10; - } else if (mouse_used != NULL && c == K_LEFTMOUSE) { - *mouse_used = TRUE; - n = mouse_row + 1; - break; - } else if (n == 0 && c == ':' && colon) { - stuffcharReadbuff(':'); - if (!exmode_active) { - cmdline_row = msg_row; - } - skip_redraw = true; // skip redraw once - do_redraw = false; - break; - } else if (c == Ctrl_C || c == ESC || c == 'q') { - n = 0; - break; - } else if (c == CAR || c == NL) { - break; - } - } - no_mapping--; - return n; -} - -/* - * Ask the user to enter a number. - * When "mouse_used" is not NULL allow using the mouse and in that case return - * the line number. - */ -int prompt_for_number(int *mouse_used) -{ - int i; - int save_cmdline_row; - int save_State; - - // When using ":silent" assume that <CR> was entered. - if (mouse_used != NULL) { - msg_puts(_("Type number and <Enter> or click with the mouse " - "(q or empty cancels): ")); - } else { - msg_puts(_("Type number and <Enter> (q or empty cancels): ")); - } - - /* Set the state such that text can be selected/copied/pasted and we still - * get mouse events. */ - save_cmdline_row = cmdline_row; - cmdline_row = 0; - save_State = State; - State = ASKMORE; // prevents a screen update when using a timer - // May show different mouse shape. - setmouse(); - - i = get_number(TRUE, mouse_used); - if (KeyTyped) { - // don't call wait_return() now - if (msg_row > 0) { - cmdline_row = msg_row - 1; - } - need_wait_return = false; - msg_didany = false; - msg_didout = false; - } else { - cmdline_row = save_cmdline_row; - } - State = save_State; - // May need to restore mouse shape. - setmouse(); - - return i; -} - -void msgmore(long n) -{ - long pn; - - if (global_busy // no messages now, wait until global is finished - || !messaging()) { // 'lazyredraw' set, don't do messages now - return; - } - - // We don't want to overwrite another important message, but do overwrite - // a previous "more lines" or "fewer lines" message, so that "5dd" and - // then "put" reports the last action. - if (keep_msg != NULL && !keep_msg_more) { - return; - } - - if (n > 0) { - pn = n; - } else { - pn = -n; - } - - if (pn > p_report) { - if (n > 0) { - vim_snprintf(msg_buf, MSG_BUF_LEN, - NGETTEXT("%ld more line", "%ld more lines", pn), - pn); - } else { - vim_snprintf(msg_buf, MSG_BUF_LEN, - NGETTEXT("%ld line less", "%ld fewer lines", pn), - pn); - } - if (got_int) { - xstrlcat(msg_buf, _(" (Interrupted)"), MSG_BUF_LEN); - } - if (msg(msg_buf)) { - set_keep_msg(msg_buf, 0); - keep_msg_more = true; - } - } -} - -/* - * flush map and typeahead buffers and give a warning for an error - */ -void beep_flush(void) -{ - if (emsg_silent == 0) { - flush_buffers(FLUSH_MINIMAL); - vim_beep(BO_ERROR); - } -} - -// Give a warning for an error -// val is one of the BO_ values, e.g., BO_OPER -void vim_beep(unsigned val) -{ - called_vim_beep = true; - - if (emsg_silent == 0) { - if (!((bo_flags & val) || (bo_flags & BO_ALL))) { - static int beeps = 0; - static uint64_t start_time = 0; - - // Only beep up to three times per half a second, - // otherwise a sequence of beeps would freeze Vim. - if (start_time == 0 || os_hrtime() - start_time > 500000000u) { - beeps = 0; - start_time = os_hrtime(); - } - beeps++; - if (beeps <= 3) { - if (p_vb) { - ui_call_visual_bell(); - } else { - ui_call_bell(); - } - } - } - - // When 'debug' contains "beep" produce a message. If we are sourcing - // a script or executing a function give the user a hint where the beep - // comes from. - if (vim_strchr(p_debug, 'e') != NULL) { - msg_source(HL_ATTR(HLF_W)); - msg_attr(_("Beep!"), HL_ATTR(HLF_W)); - } - } -} - -#if defined(EXITFREE) - -void free_users(void) -{ - ga_clear_strings(&ga_users); -} - -#endif - -/* - * Find all user names for user completion. - * Done only once and then cached. - */ -static void init_users(void) -{ - static int lazy_init_done = FALSE; - - if (lazy_init_done) { - return; - } - - lazy_init_done = TRUE; - - os_get_usernames(&ga_users); -} - -/* - * Function given to ExpandGeneric() to obtain an user names. - */ -char_u *get_users(expand_T *xp, int idx) -{ - init_users(); - if (idx < ga_users.ga_len) { - return ((char_u **)ga_users.ga_data)[idx]; - } - return NULL; -} - -/* - * Check whether name matches a user name. Return: - * 0 if name does not match any user name. - * 1 if name partially matches the beginning of a user name. - * 2 is name fully matches a user name. - */ -int match_user(char_u *name) -{ - int n = (int)STRLEN(name); - int result = 0; - - init_users(); - for (int i = 0; i < ga_users.ga_len; i++) { - if (STRCMP(((char_u **)ga_users.ga_data)[i], name) == 0) { - return 2; // full match - } - if (STRNCMP(((char_u **)ga_users.ga_data)[i], name, n) == 0) { - result = 1; // partial match - } - } - return result; -} - -/// Preserve files and exit. -/// @note IObuff must contain a message. -/// @note This may be called from deadly_signal() in a signal handler, avoid -/// unsafe functions, such as allocating memory. -void preserve_exit(void) - FUNC_ATTR_NORETURN -{ - // 'true' when we are sure to exit, e.g., after a deadly signal - static bool really_exiting = false; - - // Prevent repeated calls into this method. - if (really_exiting) { - if (input_global_fd() >= 0) { - // normalize stream (#2598) - stream_set_blocking(input_global_fd(), true); - } - exit(2); - } - - really_exiting = true; - // Ignore SIGHUP while we are already exiting. #9274 - signal_reject_deadly(); - mch_errmsg(IObuff); - mch_errmsg("\n"); - ui_flush(); - - ml_close_notmod(); // close all not-modified buffers - - FOR_ALL_BUFFERS(buf) { - if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) { - mch_errmsg("Vim: preserving files...\r\n"); - ui_flush(); - ml_sync_all(false, false, true); // preserve all swap files - break; - } - } - - ml_close_all(false); // close all memfiles, without deleting - - mch_errmsg("Vim: Finished.\r\n"); - - getout(1); -} - -/* - * Check for CTRL-C pressed, but only once in a while. - * Should be used instead of os_breakcheck() for functions that check for - * each line in the file. Calling os_breakcheck() each time takes too much - * time, because it can be a system call. - */ - -#ifndef BREAKCHECK_SKIP -# define BREAKCHECK_SKIP 1000 -#endif - -static int breakcheck_count = 0; - -void line_breakcheck(void) -{ - if (++breakcheck_count >= BREAKCHECK_SKIP) { - breakcheck_count = 0; - os_breakcheck(); - } -} - -/* - * Like line_breakcheck() but check 10 times less often. - */ -void fast_breakcheck(void) -{ - if (++breakcheck_count >= BREAKCHECK_SKIP * 10) { - breakcheck_count = 0; - os_breakcheck(); - } -} - -// Like line_breakcheck() but check 100 times less often. -void veryfast_breakcheck(void) -{ - if (++breakcheck_count >= BREAKCHECK_SKIP * 100) { - breakcheck_count = 0; - os_breakcheck(); - } -} - -/// os_call_shell() wrapper. Handles 'verbose', :profile, and v:shell_error. -/// Invalidates cached tags. -/// -/// @return shell command exit code -int call_shell(char_u *cmd, ShellOpts opts, char_u *extra_shell_arg) -{ - int retval; - proftime_T wait_time; - - if (p_verbose > 3) { - verbose_enter(); - smsg(_("Executing command: \"%s\""), cmd == NULL ? p_sh : cmd); - msg_putchar('\n'); - verbose_leave(); - } - - if (do_profiling == PROF_YES) { - prof_child_enter(&wait_time); - } - - if (*p_sh == NUL) { - emsg(_(e_shellempty)); - retval = -1; - } else { - // The external command may update a tags file, clear cached tags. - tag_freematch(); - - retval = os_call_shell(cmd, opts, extra_shell_arg); - } - - set_vim_var_nr(VV_SHELL_ERROR, (varnumber_T)retval); - if (do_profiling == PROF_YES) { - prof_child_exit(&wait_time); - } - - return retval; -} - -/// Get the stdout of an external command. -/// If "ret_len" is NULL replace NUL characters with NL. When "ret_len" is not -/// NULL store the length there. -/// -/// @param cmd command to execute -/// @param infile optional input file name -/// @param flags can be kShellOptSilent or 0 -/// @param ret_len length of the stdout -/// -/// @return an allocated string, or NULL for error. -char_u *get_cmd_output(char_u *cmd, char_u *infile, ShellOpts flags, size_t *ret_len) -{ - char_u *buffer = NULL; - - if (check_secure()) { - return NULL; - } - - // get a name for the temp file - char_u *tempname = vim_tempname(); - if (tempname == NULL) { - emsg(_(e_notmp)); - return NULL; - } - - // Add the redirection stuff - char_u *command = make_filter_cmd(cmd, infile, tempname); - - /* - * Call the shell to execute the command (errors are ignored). - * Don't check timestamps here. - */ - ++no_check_timestamps; - call_shell(command, kShellOptDoOut | kShellOptExpand | flags, NULL); - --no_check_timestamps; - - xfree(command); - - // read the names from the file into memory - FILE *fd = os_fopen((char *)tempname, READBIN); - - if (fd == NULL) { - semsg(_(e_notopen), tempname); - goto done; - } - - fseek(fd, 0L, SEEK_END); - size_t len = (size_t)ftell(fd); // get size of temp file - fseek(fd, 0L, SEEK_SET); - - buffer = xmalloc(len + 1); - size_t i = fread((char *)buffer, 1, len, fd); - fclose(fd); - os_remove((char *)tempname); - if (i != len) { - semsg(_(e_notread), tempname); - XFREE_CLEAR(buffer); - } else if (ret_len == NULL) { - // Change NUL into SOH, otherwise the string is truncated. - for (i = 0; i < len; ++i) { - if (buffer[i] == NUL) { - buffer[i] = 1; - } - } - - buffer[len] = NUL; // make sure the buffer is terminated - } else { - *ret_len = len; - } - -done: - xfree(tempname); - return buffer; -} - -/* - * Free the list of files returned by expand_wildcards() or other expansion - * functions. - */ -void FreeWild(int count, char_u **files) -{ - if (count <= 0 || files == NULL) { - return; - } - while (count--) { - xfree(files[count]); - } - xfree(files); -} - -/* - * Return TRUE when need to go to Insert mode because of 'insertmode'. - * Don't do this when still processing a command or a mapping. - * Don't do this when inside a ":normal" command. - */ -int goto_im(void) -{ - return p_im && stuff_empty() && typebuf_typed(); -} - -/// Put the timestamp of an undo header in "buf[buflen]" in a nice format. -void add_time(char_u *buf, size_t buflen, time_t tt) -{ - struct tm curtime; - - if (time(NULL) - tt >= 100) { - os_localtime_r(&tt, &curtime); - if (time(NULL) - tt < (60L * 60L * 12L)) { - // within 12 hours - (void)strftime((char *)buf, buflen, "%H:%M:%S", &curtime); - } else { - // longer ago - (void)strftime((char *)buf, buflen, "%Y/%m/%d %H:%M:%S", &curtime); - } - } else { - int64_t seconds = time(NULL) - tt; - vim_snprintf((char *)buf, buflen, - NGETTEXT("%" PRId64 " second ago", - "%" PRId64 " seconds ago", (uint32_t)seconds), - seconds); - } -} - -dict_T *get_v_event(save_v_event_T *sve) -{ - dict_T *v_event = get_vim_var_dict(VV_EVENT); - - if (v_event->dv_hashtab.ht_used > 0) { - // recursive use of v:event, save, make empty and restore later - sve->sve_did_save = true; - sve->sve_hashtab = v_event->dv_hashtab; - hash_init(&v_event->dv_hashtab); - } else { - sve->sve_did_save = false; - } - return v_event; -} - -void restore_v_event(dict_T *v_event, save_v_event_T *sve) -{ - tv_dict_free_contents(v_event); - if (sve->sve_did_save) { - v_event->dv_hashtab = sve->sve_hashtab; - } else { - hash_init(&v_event->dv_hashtab); - } -} - -/// Fires a ModeChanged autocmd. -void trigger_modechanged(void) -{ - if (!has_event(EVENT_MODECHANGED)) { - return; - } - - char *mode = get_mode(); - if (STRCMP(mode, last_mode) == 0) { - xfree(mode); - return; - } - - save_v_event_T save_v_event; - dict_T *v_event = get_v_event(&save_v_event); - tv_dict_add_str(v_event, S_LEN("new_mode"), mode); - tv_dict_add_str(v_event, S_LEN("old_mode"), last_mode); - - char_u *pat_pre = concat_str((char_u *)last_mode, (char_u *)":"); - char_u *pat = concat_str(pat_pre, (char_u *)mode); - xfree(pat_pre); - - apply_autocmds(EVENT_MODECHANGED, pat, NULL, false, curbuf); - xfree(last_mode); - last_mode = mode; - - xfree(pat); - restore_v_event(v_event, &save_v_event); -} diff --git a/src/nvim/misc1.h b/src/nvim/misc1.h deleted file mode 100644 index 4ce142c4c5..0000000000 --- a/src/nvim/misc1.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef NVIM_MISC1_H -#define NVIM_MISC1_H - -#include "nvim/os/shell.h" -#include "nvim/vim.h" - -// flags for open_line() -#define OPENLINE_DELSPACES 1 // delete spaces after cursor -#define OPENLINE_DO_COM 2 // format comments -#define OPENLINE_KEEPTRAIL 4 // keep trailing spaces -#define OPENLINE_MARKFIX 8 // fix mark positions -#define OPENLINE_COM_LIST 16 // format comments with list/2nd line indent - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "misc1.h.generated.h" -#endif -#endif // NVIM_MISC1_H diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index 386094e509..5d007fb173 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -10,7 +10,6 @@ #include "nvim/diff.h" #include "nvim/fold.h" #include "nvim/memline.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/os_unix.h" @@ -31,6 +30,32 @@ static linenr_T orig_topline = 0; static int orig_topfill = 0; +/// Return true if "c" is a mouse key. +bool is_mouse_key(int c) +{ + return c == K_LEFTMOUSE + || c == K_LEFTMOUSE_NM + || c == K_LEFTDRAG + || c == K_LEFTRELEASE + || c == K_LEFTRELEASE_NM + || c == K_MOUSEMOVE + || c == K_MIDDLEMOUSE + || c == K_MIDDLEDRAG + || c == K_MIDDLERELEASE + || c == K_RIGHTMOUSE + || c == K_RIGHTDRAG + || c == K_RIGHTRELEASE + || c == K_MOUSEDOWN + || c == K_MOUSEUP + || c == K_MOUSELEFT + || c == K_MOUSERIGHT + || c == K_X1MOUSE + || c == K_X1DRAG + || c == K_X1RELEASE + || c == K_X2MOUSE + || c == K_X2DRAG + || c == K_X2RELEASE; +} /// Move the cursor to the specified row and column on the screen. /// Change current window if necessary. Returns an integer with the /// CURSOR_MOVED bit set if the cursor has moved or unset otherwise. diff --git a/src/nvim/move.c b/src/nvim/move.c index d80e63e79d..0a672000e4 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -23,9 +23,9 @@ #include "nvim/diff.h" #include "nvim/edit.h" #include "nvim/fold.h" +#include "nvim/getchar.h" #include "nvim/mbyte.h" #include "nvim/memline.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index a1a1f0f8c0..299651ee97 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -24,7 +24,6 @@ #include "nvim/map.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/os/input.h" diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 9332c55b5f..95a521c0d8 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -40,7 +40,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 52c382028e..9bc63477e9 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -38,7 +38,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" diff --git a/src/nvim/option.c b/src/nvim/option.c index 1fe2e1d04c..2ceb1bd992 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -55,7 +55,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" @@ -262,6 +261,7 @@ typedef struct vimoption { #define HIGHLIGHT_INIT \ "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText,d:Directory,e:ErrorMsg," \ "i:IncSearch,l:Search,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow,N:CursorLineNr," \ + "G:CursorLineSign,O:CursorLineFold" \ "r:Question,s:StatusLine,S:StatusLineNC,c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg," \ "W:WildMenu,f:Folded,F:FoldColumn,A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,>:SignColumn," \ "-:Conceal,B:SpellBad,P:SpellCap,R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel,x:PmenuSbar," \ @@ -7898,7 +7898,7 @@ void set_fileformat(int eol_style, int opt_flags) } // This may cause the buffer to become (un)modified. - check_status(curbuf); + redraw_buf_status_later(curbuf); redraw_tabline = true; need_maketitle = true; // Set window title later. } diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index e9963516fc..24c7678633 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -21,7 +21,6 @@ #include "nvim/assert.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option.h" #include "nvim/os/os.h" #include "nvim/os/os_defs.h" diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 5b231f205b..3790eba212 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -17,7 +17,6 @@ #include "nvim/main.h" #include "nvim/mbyte.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/os/input.h" #include "nvim/state.h" @@ -183,6 +182,40 @@ void os_breakcheck(void) updating_screen = save_us; } +#define BREAKCHECK_SKIP 1000 +static int breakcheck_count = 0; + +/// Check for CTRL-C pressed, but only once in a while. +/// +/// Should be used instead of os_breakcheck() for functions that check for +/// each line in the file. Calling os_breakcheck() each time takes too much +/// time, because it will use system calls to check for input. +void line_breakcheck(void) +{ + if (++breakcheck_count >= BREAKCHECK_SKIP) { + breakcheck_count = 0; + os_breakcheck(); + } +} + +/// Like line_breakcheck() but check 10 times less often. +void fast_breakcheck(void) +{ + if (++breakcheck_count >= BREAKCHECK_SKIP * 10) { + breakcheck_count = 0; + os_breakcheck(); + } +} + +/// Like line_breakcheck() but check 100 times less often. +void veryfast_breakcheck(void) +{ + if (++breakcheck_count >= BREAKCHECK_SKIP * 100) { + breakcheck_count = 0; + os_breakcheck(); + } +} + /// Test whether a file descriptor refers to a terminal. /// diff --git a/src/nvim/os/os_defs.h b/src/nvim/os/os_defs.h index 8049b3b80e..dce4b0c187 100644 --- a/src/nvim/os/os_defs.h +++ b/src/nvim/os/os_defs.h @@ -13,6 +13,10 @@ # include "nvim/os/unix_defs.h" #endif +#if !defined(NAME_MAX) && defined(_XOPEN_NAME_MAX) +#define NAME_MAX _XOPEN_NAME_MAX +#endif + #define BASENAMELEN (NAME_MAX - 5) // Use the system path length if it makes sense. diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index 24ecf5c24f..450bc75ffb 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -15,7 +15,7 @@ # include <libutil.h> #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) # include <util.h> -#else +#elif !defined(__sun) # include <pty.h> #endif @@ -198,7 +198,9 @@ static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL termios->c_cflag = CS8|CREAD; termios->c_lflag = ISIG|ICANON|IEXTEN|ECHO|ECHOE|ECHOK; - cfsetspeed(termios, 38400); + // not using cfsetspeed, not available on all platforms + cfsetispeed(termios, 38400); + cfsetospeed(termios, 38400); #ifdef IUTF8 termios->c_iflag |= IUTF8; diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 6ef0aa1091..e618b2788b 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -12,6 +12,7 @@ #include "nvim/event/libuv_process.h" #include "nvim/event/loop.h" #include "nvim/event/rstream.h" +#include "nvim/eval.h" #include "nvim/ex_cmds.h" #include "nvim/fileio.h" #include "nvim/lib/kvec.h" @@ -20,7 +21,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option_defs.h" #include "nvim/os/shell.h" #include "nvim/os/signal.h" @@ -28,6 +28,7 @@ #include "nvim/screen.h" #include "nvim/strings.h" #include "nvim/types.h" +#include "nvim/tag.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -681,6 +682,116 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args) return exitcode; } +/// os_call_shell() wrapper. Handles 'verbose', :profile, and v:shell_error. +/// Invalidates cached tags. +/// +/// @return shell command exit code +int call_shell(char_u *cmd, ShellOpts opts, char_u *extra_shell_arg) +{ + int retval; + proftime_T wait_time; + + if (p_verbose > 3) { + verbose_enter(); + smsg(_("Executing command: \"%s\""), cmd == NULL ? p_sh : cmd); + msg_putchar('\n'); + verbose_leave(); + } + + if (do_profiling == PROF_YES) { + prof_child_enter(&wait_time); + } + + if (*p_sh == NUL) { + emsg(_(e_shellempty)); + retval = -1; + } else { + // The external command may update a tags file, clear cached tags. + tag_freematch(); + + retval = os_call_shell(cmd, opts, extra_shell_arg); + } + + set_vim_var_nr(VV_SHELL_ERROR, (varnumber_T)retval); + if (do_profiling == PROF_YES) { + prof_child_exit(&wait_time); + } + + return retval; +} + +/// Get the stdout of an external command. +/// If "ret_len" is NULL replace NUL characters with NL. When "ret_len" is not +/// NULL store the length there. +/// +/// @param cmd command to execute +/// @param infile optional input file name +/// @param flags can be kShellOptSilent or 0 +/// @param ret_len length of the stdout +/// +/// @return an allocated string, or NULL for error. +char_u *get_cmd_output(char_u *cmd, char_u *infile, ShellOpts flags, size_t *ret_len) +{ + char_u *buffer = NULL; + + if (check_secure()) { + return NULL; + } + + // get a name for the temp file + char_u *tempname = vim_tempname(); + if (tempname == NULL) { + emsg(_(e_notmp)); + return NULL; + } + + // Add the redirection stuff + char_u *command = make_filter_cmd(cmd, infile, tempname); + + // Call the shell to execute the command (errors are ignored). + // Don't check timestamps here. + no_check_timestamps++; + call_shell(command, kShellOptDoOut | kShellOptExpand | flags, NULL); + no_check_timestamps--; + + xfree(command); + + // read the names from the file into memory + FILE *fd = os_fopen((char *)tempname, READBIN); + + if (fd == NULL) { + semsg(_(e_notopen), tempname); + goto done; + } + + fseek(fd, 0L, SEEK_END); + size_t len = (size_t)ftell(fd); // get size of temp file + fseek(fd, 0L, SEEK_SET); + + buffer = xmalloc(len + 1); + size_t i = fread((char *)buffer, 1, len, fd); + fclose(fd); + os_remove((char *)tempname); + if (i != len) { + semsg(_(e_notread), tempname); + XFREE_CLEAR(buffer); + } else if (ret_len == NULL) { + // Change NUL into SOH, otherwise the string is truncated. + for (i = 0; i < len; i++) { + if (buffer[i] == NUL) { + buffer[i] = 1; + } + } + + buffer[len] = NUL; // make sure the buffer is terminated + } else { + *ret_len = len; + } + +done: + xfree(tempname); + return buffer; +} /// os_system - synchronously execute a command in the shell /// /// example: diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index 0d125ec964..a8bf68a1a2 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -18,7 +18,6 @@ #include "nvim/main.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/os/signal.h" #include "nvim/vim.h" diff --git a/src/nvim/os/users.c b/src/nvim/os/users.c index 9952e2b387..e0ce3fec31 100644 --- a/src/nvim/os/users.c +++ b/src/nvim/os/users.c @@ -18,6 +18,9 @@ # include <lm.h> #endif +// All user names (for ~user completion as done by shell). +static garray_T ga_users = GA_EMPTY_INIT_VALUE; + // Add a user name to the list of users in garray_T *users. // Do nothing if user name is NULL or empty. static void add_user(garray_T *users, char *user, bool need_copy) @@ -157,3 +160,60 @@ char *os_get_user_directory(const char *name) return NULL; } + +#if defined(EXITFREE) + +void free_users(void) +{ + ga_clear_strings(&ga_users); +} + +#endif + +/// Find all user names for user completion. +/// +/// Done only once and then cached. +static void init_users(void) +{ + static int lazy_init_done = false; + + if (lazy_init_done) { + return; + } + + lazy_init_done = true; + + os_get_usernames(&ga_users); +} + +/// Given to ExpandGeneric() to obtain an user names. +char_u *get_users(expand_T *xp, int idx) +{ + init_users(); + if (idx < ga_users.ga_len) { + return ((char_u **)ga_users.ga_data)[idx]; + } + return NULL; +} + +/// Check whether name matches a user name. +/// +/// @return 0 if name does not match any user name. +/// 1 if name partially matches the beginning of a user name. +/// 2 is name fully matches a user name. +int match_user(char_u *name) +{ + int n = (int)STRLEN(name); + int result = 0; + + init_users(); + for (int i = 0; i < ga_users.ga_len; i++) { + if (STRCMP(((char_u **)ga_users.ga_data)[i], name) == 0) { + return 2; // full match + } + if (STRNCMP(((char_u **)ga_users.ga_data)[i], name, n) == 0) { + result = 1; // partial match + } + } + return result; +} diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index 9396a5896a..1398dba0e4 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -20,7 +20,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/os/input.h" diff --git a/src/nvim/path.c b/src/nvim/path.c index 7b9081eafa..674d67e21a 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -18,7 +18,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/os/os.h" @@ -1337,6 +1336,17 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file, char_u ***fil return ((flags & EW_EMPTYOK) || ga.ga_data != NULL) ? OK : FAIL; } +/// Free the list of files returned by expand_wildcards() or other expansion functions. +void FreeWild(int count, char_u **files) +{ + if (count <= 0 || files == NULL) { + return; + } + while (count--) { + xfree(files[count]); + } + xfree(files); +} /* * Return TRUE if we can expand this backtick thing here. diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 6a192d148f..32d0ebe8eb 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -27,7 +27,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" @@ -658,7 +657,8 @@ static int qf_get_next_str_line(qfstate_T *state) state->linebuf = IObuff; state->linelen = len; } - STRLCPY(state->linebuf, p_str, state->linelen + 1); + memcpy(state->linebuf, p_str, state->linelen); + state->linebuf[state->linelen] = '\0'; // Increment using len in order to discard the rest of the line if it // exceeds LINE_MAXLEN. diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index e67efb8ea0..45e580dbee 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -64,7 +64,7 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" +#include "nvim/os/input.h" #include "nvim/plines.h" #include "nvim/garray.h" #include "nvim/strings.h" diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 5df5cc5975..3e7306bad3 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -16,6 +16,7 @@ #include "nvim/ascii.h" #include "nvim/garray.h" +#include "nvim/os/input.h" /* * Logging of NFA engine. diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index be365d4ab8..1c04cb16b3 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -12,7 +12,6 @@ #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/lua/executor.h" -#include "nvim/misc1.h" #include "nvim/option.h" #include "nvim/os/os.h" #include "nvim/runtime.h" diff --git a/src/nvim/screen.c b/src/nvim/screen.c index a666b9c8b0..a938a3b062 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -98,7 +98,6 @@ #include "nvim/memory.h" #include "nvim/menu.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" @@ -314,6 +313,19 @@ void update_curbuf(int type) update_screen(type); } +/// called when the status bars for the buffer 'buf' need to be updated +void redraw_buf_status_later(buf_T *buf) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf && wp->w_status_height) { + wp->w_redr_status = true; + if (must_redraw < VALID) { + must_redraw = VALID; + } + } + } +} + /// Redraw the parts of the screen that is marked for redraw. /// /// Most code shouldn't call this directly, rather use redraw_later() and @@ -2746,7 +2758,11 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc p_extra = p_extra_free; c_extra = NUL; c_final = NUL; - char_attr = win_hl_attr(wp, HLF_FC); + if (use_cursor_line_sign(wp, lnum)) { + char_attr = win_hl_attr(wp, HLF_CLF); + } else { + char_attr = win_hl_attr(wp, HLF_FC); + } } } @@ -2757,7 +2773,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc * buffer or when using Netbeans. */ int count = win_signcol_count(wp); if (count > 0) { - get_sign_display_info(false, wp, sattrs, row, + get_sign_display_info(false, wp, lnum, sattrs, row, startrow, filler_lines, filler_todo, count, &c_extra, &c_final, extra, sizeof(extra), &p_extra, &n_extra, @@ -2778,7 +2794,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u' && num_signs > 0) { int count = win_signcol_count(wp); - get_sign_display_info(true, wp, sattrs, row, + get_sign_display_info(true, wp, lnum, sattrs, row, startrow, filler_lines, filler_todo, count, &c_extra, &c_final, extra, sizeof(extra), &p_extra, &n_extra, @@ -4617,6 +4633,14 @@ void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off) } } +// Return true if CursorLineSign highlight is to be used. +static bool use_cursor_line_sign(win_T *wp, linenr_T lnum) +{ + return wp->w_p_cul + && lnum == wp->w_cursor.lnum + && (wp->w_p_culopt_flags & CULOPT_NBR); +} + // Get information needed to display the sign in line 'lnum' in window 'wp'. // If 'nrcol' is TRUE, the sign is going to be displayed in the number column. // Otherwise the sign is going to be displayed in the sign column. @@ -4624,11 +4648,11 @@ void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off) // @param count max number of signs // @param[out] n_extrap number of characters from pp_extra to display // @param[in, out] sign_idxp Index of the displayed sign -static void get_sign_display_info(bool nrcol, win_T *wp, sign_attrs_T sattrs[], int row, - int startrow, int filler_lines, int filler_todo, int count, - int *c_extrap, int *c_finalp, char_u *extra, size_t extra_size, - char_u **pp_extra, int *n_extrap, int *char_attrp, - int *draw_statep, int *sign_idxp) +static void get_sign_display_info(bool nrcol, win_T *wp, linenr_T lnum, sign_attrs_T sattrs[], + int row, int startrow, int filler_lines, int filler_todo, + int count, int *c_extrap, int *c_finalp, char_u *extra, + size_t extra_size, char_u **pp_extra, int *n_extrap, + int *char_attrp, int *draw_statep, int *sign_idxp) { // Draw cells with the sign value or blank. *c_extrap = ' '; @@ -4636,7 +4660,11 @@ static void get_sign_display_info(bool nrcol, win_T *wp, sign_attrs_T sattrs[], if (nrcol) { *n_extrap = number_width(wp) + 1; } else { - *char_attrp = win_hl_attr(wp, HLF_SC); + if (use_cursor_line_sign(wp, lnum)) { + *char_attrp = win_hl_attr(wp, HLF_CLS); + } else { + *char_attrp = win_hl_attr(wp, HLF_SC); + } *n_extrap = win_signcol_width(wp); } @@ -4676,7 +4704,12 @@ static void get_sign_display_info(bool nrcol, win_T *wp, sign_attrs_T sattrs[], (*pp_extra)[*n_extrap] = NUL; } } - *char_attrp = sattr->sat_texthl; + + if (use_cursor_line_sign(wp, lnum) && sattr->sat_culhl > 0) { + *char_attrp = sattr->sat_culhl; + } else { + *char_attrp = sattr->sat_texthl; + } } } diff --git a/src/nvim/search.c b/src/nvim/search.c index f47315705c..906c9a6f47 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -14,6 +14,7 @@ #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" +#include "nvim/change.h" #include "nvim/cursor.h" #include "nvim/edit.h" #include "nvim/eval.h" @@ -31,11 +32,11 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" +#include "nvim/os/input.h" #include "nvim/os/time.h" #include "nvim/path.h" #include "nvim/regexp.h" @@ -1353,6 +1354,10 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, } retval = 1; // pattern found + if (sia && sia->sa_wrapped) { + apply_autocmds(EVENT_SEARCHWRAPPED, NULL, NULL, false, NULL); + } + /* * Add character and/or line offset */ diff --git a/src/nvim/sign.c b/src/nvim/sign.c index fca73dc9f2..32be714184 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -31,6 +31,7 @@ struct sign { char_u *sn_text; // text used instead of pixmap int sn_line_hl; // highlight ID for line int sn_text_hl; // highlight ID for text + int sn_cul_hl; // highlight ID for text on current line when 'cursorline' is set int sn_num_hl; // highlight ID for line number }; @@ -499,6 +500,9 @@ int buf_get_signattrs(buf_T *buf, linenr_T lnum, sign_attrs_T sattrs[]) if (sp->sn_line_hl != 0) { sattr.sat_linehl = syn_id2attr(sp->sn_line_hl); } + if (sp->sn_cul_hl != 0) { + sattr.sat_culhl = syn_id2attr(sp->sn_cul_hl); + } if (sp->sn_num_hl != 0) { sattr.sat_numhl = syn_id2attr(sp->sn_num_hl); } @@ -901,7 +905,7 @@ static int sign_define_init_text(sign_T *sp, char_u *text) /// Define a new sign or update an existing sign int sign_define_by_name(char_u *name, char_u *icon, char_u *linehl, char_u *text, char_u *texthl, - char *numhl) + char_u *culhl, char *numhl) { sign_T *sp_prev; sign_T *sp; @@ -939,15 +943,35 @@ int sign_define_by_name(char_u *name, char_u *icon, char_u *linehl, char_u *text } if (linehl != NULL) { - sp->sn_line_hl = syn_check_group((char *)linehl, (int)STRLEN(linehl)); + if (*linehl == NUL) { + sp->sn_line_hl = 0; + } else { + sp->sn_line_hl = syn_check_group((char *)linehl, (int)STRLEN(linehl)); + } } if (texthl != NULL) { - sp->sn_text_hl = syn_check_group((char *)texthl, (int)STRLEN(texthl)); + if (*texthl == NUL) { + sp->sn_text_hl = 0; + } else { + sp->sn_text_hl = syn_check_group((char *)texthl, (int)STRLEN(texthl)); + } + } + + if (culhl != NULL) { + if (*culhl == NUL) { + sp->sn_cul_hl = 0; + } else { + sp->sn_cul_hl = syn_check_group((char *)culhl, (int)STRLEN(culhl)); + } } if (numhl != NULL) { - sp->sn_num_hl = syn_check_group(numhl, (int)STRLEN(numhl)); + if (*numhl == NUL) { + sp->sn_num_hl = 0; + } else { + sp->sn_num_hl = syn_check_group(numhl, (int)STRLEN(numhl)); + } } return OK; @@ -1133,6 +1157,7 @@ static void sign_define_cmd(char_u *sign_name, char_u *cmdline) char_u *text = NULL; char_u *linehl = NULL; char_u *texthl = NULL; + char_u *culhl = NULL; char_u *numhl = NULL; int failed = false; @@ -1155,6 +1180,9 @@ static void sign_define_cmd(char_u *sign_name, char_u *cmdline) } else if (STRNCMP(arg, "texthl=", 7) == 0) { arg += 7; texthl = vim_strnsave(arg, (size_t)(p - arg)); + } else if (STRNCMP(arg, "culhl=", 6) == 0) { + arg += 6; + culhl = vim_strnsave(arg, (size_t)(p - arg)); } else if (STRNCMP(arg, "numhl=", 6) == 0) { arg += 6; numhl = vim_strnsave(arg, (size_t)(p - arg)); @@ -1166,13 +1194,14 @@ static void sign_define_cmd(char_u *sign_name, char_u *cmdline) } if (!failed) { - sign_define_by_name(sign_name, icon, linehl, text, texthl, (char *)numhl); + sign_define_by_name(sign_name, icon, linehl, text, texthl, culhl, (char *)numhl); } xfree(icon); xfree(text); xfree(linehl); xfree(texthl); + xfree(culhl); xfree(numhl); } @@ -1481,6 +1510,13 @@ static void sign_getinfo(sign_T *sp, dict_T *retdict) } tv_dict_add_str(retdict, S_LEN("texthl"), (char *)p); } + if (sp->sn_cul_hl > 0) { + p = get_highlight_name_ext(NULL, sp->sn_cul_hl - 1, false); + if (p == NULL) { + p = "NONE"; + } + tv_dict_add_str(retdict, S_LEN("culhl"), (char *)p); + } if (sp->sn_num_hl > 0) { p = get_highlight_name_ext(NULL, sp->sn_num_hl - 1, false); if (p == NULL) { @@ -1609,6 +1645,16 @@ static void sign_list_defined(sign_T *sp) msg_puts(p); } } + if (sp->sn_cul_hl > 0) { + msg_puts(" culhl="); + const char *const p = get_highlight_name_ext(NULL, + sp->sn_cul_hl - 1, false); + if (p == NULL) { + msg_puts("NONE"); + } else { + msg_puts(p); + } + } if (sp->sn_num_hl > 0) { msg_puts(" numhl="); const char *const p = get_highlight_name_ext(NULL, @@ -1847,6 +1893,7 @@ int sign_define_from_dict(const char *name_arg, dict_T *dict) char *linehl = NULL; char *text = NULL; char *texthl = NULL; + char *culhl = NULL; char *numhl = NULL; int retval = -1; @@ -1866,11 +1913,12 @@ int sign_define_from_dict(const char *name_arg, dict_T *dict) linehl = tv_dict_get_string(dict, "linehl", true); text = tv_dict_get_string(dict, "text", true); texthl = tv_dict_get_string(dict, "texthl", true); + culhl = tv_dict_get_string(dict, "culhl", true); numhl = tv_dict_get_string(dict, "numhl", true); } if (sign_define_by_name((char_u *)name, (char_u *)icon, (char_u *)linehl, - (char_u *)text, (char_u *)texthl, numhl) + (char_u *)text, (char_u *)texthl, (char_u *)culhl, numhl) == OK) { retval = 0; } @@ -1881,6 +1929,7 @@ cleanup: xfree(linehl); xfree(text); xfree(texthl); + xfree(culhl); xfree(numhl); return retval; diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h index 46436b2c8e..c734502878 100644 --- a/src/nvim/sign_defs.h +++ b/src/nvim/sign_defs.h @@ -38,6 +38,7 @@ typedef struct sign_attrs_S { char_u *sat_text; int sat_texthl; int sat_linehl; + int sat_culhl; int sat_numhl; } sign_attrs_T; diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 85d1e139bf..bd31e98faa 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -94,12 +94,12 @@ #include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/hashtab.h" +#include "nvim/input.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/normal.h" #include "nvim/option.h" #include "nvim/os/input.h" diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index ae4514dd30..42bb3c61a5 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -237,8 +237,8 @@ #include "nvim/fileio.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/option.h" +#include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/path.h" #include "nvim/regexp.h" @@ -4446,10 +4446,10 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) putc(SN_PREFCOND, fd); // <sectionID> putc(SNF_REQUIRED, fd); // <sectionflags> - size_t l = (size_t)write_spell_prefcond(NULL, &spin->si_prefcond); + size_t l = (size_t)write_spell_prefcond(NULL, &spin->si_prefcond, &fwv); put_bytes(fd, l, 4); // <sectionlen> - write_spell_prefcond(fd, &spin->si_prefcond); + write_spell_prefcond(fd, &spin->si_prefcond, &fwv); } // SN_REP: <repcount> <rep> ... @@ -5793,7 +5793,7 @@ static int set_spell_finish(spelltab_T *new_st) // Write the table with prefix conditions to the .spl file. // When "fd" is NULL only count the length of what is written. -static int write_spell_prefcond(FILE *fd, garray_T *gap) +static int write_spell_prefcond(FILE *fd, garray_T *gap, size_t *fwv) { assert(gap->ga_len >= 0); @@ -5801,8 +5801,7 @@ static int write_spell_prefcond(FILE *fd, garray_T *gap) put_bytes(fd, (uintmax_t)gap->ga_len, 2); // <prefcondcnt> } size_t totlen = 2 + (size_t)gap->ga_len; // <prefcondcnt> and <condlen> bytes - size_t x = 1; // collect return value of fwrite() - for (int i = 0; i < gap->ga_len; ++i) { + for (int i = 0; i < gap->ga_len; i++) { // <prefcond> : <condlen> <condstr> char_u *p = ((char_u **)gap->ga_data)[i]; if (p != NULL) { @@ -5810,7 +5809,7 @@ static int write_spell_prefcond(FILE *fd, garray_T *gap) if (fd != NULL) { assert(len <= INT_MAX); fputc((int)len, fd); - x &= fwrite(p, len, 1, fd); + *fwv &= fwrite(p, len, 1, fd); } totlen += len; } else if (fd != NULL) { diff --git a/src/nvim/state.c b/src/nvim/state.c index 71db25664f..68bc76660d 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -4,7 +4,9 @@ #include <assert.h> #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/edit.h" +#include "nvim/eval.h" #include "nvim/ex_docmd.h" #include "nvim/getchar.h" #include "nvim/lib/kvec.h" @@ -202,3 +204,33 @@ char *get_mode(void) return buf; } + +/// Fires a ModeChanged autocmd. +void trigger_modechanged(void) +{ + if (!has_event(EVENT_MODECHANGED)) { + return; + } + + char *mode = get_mode(); + if (STRCMP(mode, last_mode) == 0) { + xfree(mode); + return; + } + + save_v_event_T save_v_event; + dict_T *v_event = get_v_event(&save_v_event); + tv_dict_add_str(v_event, S_LEN("new_mode"), mode); + tv_dict_add_str(v_event, S_LEN("old_mode"), last_mode); + + char_u *pat_pre = concat_str((char_u *)last_mode, (char_u *)":"); + char_u *pat = concat_str(pat_pre, (char_u *)mode); + xfree(pat_pre); + + apply_autocmds(EVENT_MODECHANGED, pat, NULL, false, curbuf); + xfree(last_mode); + last_mode = mode; + + xfree(pat); + restore_v_event(v_event, &save_v_event); +} diff --git a/src/nvim/strings.c b/src/nvim/strings.c index c58e052ae9..e2a8108c45 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -31,7 +31,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/ops.h" #include "nvim/option.h" @@ -394,6 +393,18 @@ void del_trailing_spaces(char_u *ptr) } } +#if !defined(HAVE_STRNLEN) +size_t xstrnlen(const char *s, size_t n) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE +{ + const char *end = memchr(s, '\0', n); + if (end == NULL) { + return n; + } + return end - s; +} +#endif + #if (!defined(HAVE_STRCASECMP) && !defined(HAVE_STRICMP)) /* * Compare two strings, ignoring case, using current locale. diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index cb243668ce..a9447165c2 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -32,8 +32,8 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option.h" +#include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/os_unix.h" @@ -115,6 +115,8 @@ static int include_none = 0; // when 1 include "nvim/None" static int include_default = 0; // when 1 include "nvim/default" static int include_link = 0; // when 2 include "nvim/link" and "clear" +#define MAX_SYN_NAME 200 + /// The "term", "cterm" and "gui" arguments can be any combination of the /// following names, separated by commas (but no spaces!). static char *(hl_name_table[]) = @@ -6175,6 +6177,8 @@ static const char *highlight_init_both[] = { "default link LineNrAbove LineNr", "default link LineNrBelow LineNr", "default link QuickFixLine Search", + "default link CursorLineSign SignColumn", + "default link CursorLineFold FoldColumn", "default link Substitute Search", "default link Whitespace NonText", "default link MsgSeparator StatusLine", @@ -7623,10 +7627,9 @@ int syn_name2id(const char *name) int syn_name2id_len(const char_u *name, size_t len) FUNC_ATTR_NONNULL_ALL { - char name_u[201]; + char name_u[MAX_SYN_NAME + 1]; - if (len == 0 || len > 200) { - // ID names over 200 chars don't deserve to be found! + if (len == 0 || len > MAX_SYN_NAME) { return 0; } @@ -7684,6 +7687,10 @@ char_u *syn_id2name(int id) /// @return 0 for failure else the id of the group int syn_check_group(const char *name, int len) { + if (len > MAX_SYN_NAME) { + emsg(_(e_highlight_group_name_too_long)); + return 0; + } int id = syn_name2id_len((char_u *)name, len); if (id == 0) { // doesn't exist yet return syn_add_group(vim_strnsave((char_u *)name, len)); diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 483d2df778..a10a2a0c32 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -25,11 +25,11 @@ #include "nvim/fold.h" #include "nvim/garray.h" #include "nvim/if_cscope.h" +#include "nvim/input.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/os/input.h" diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index afebda4948..d97c24dcf7 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -64,7 +64,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/option.h" @@ -1467,6 +1466,17 @@ static void refresh_scrollback(Terminal *term, buf_T *buf) int width, height; vterm_get_size(term->vt, &height, &width); + // May still have pending scrollback after increase in terminal height if the + // scrollback wasn't refreshed in time; append these to the top of the buffer. + int row_offset = term->sb_pending; + while (term->sb_pending > 0 && buf->b_ml.ml_line_count < height) { + fetch_row(term, term->sb_pending - row_offset - 1, width); + ml_append(0, (uint8_t *)term->textbuf, 0, false); + appended_lines(0, 1); + term->sb_pending--; + } + + row_offset -= term->sb_pending; while (term->sb_pending > 0) { // This means that either the window height has decreased or the screen // became full and libvterm had to push all rows up. Convert the first @@ -1477,7 +1487,7 @@ static void refresh_scrollback(Terminal *term, buf_T *buf) ml_delete(1, false); deleted_lines(1, 1); } - fetch_row(term, -term->sb_pending, width); + fetch_row(term, -term->sb_pending - row_offset, width); int buf_index = (int)buf->b_ml.ml_line_count - height; ml_append(buf_index, (uint8_t *)term->textbuf, 0, false); appended_lines(buf_index, 1); @@ -1507,6 +1517,13 @@ static void refresh_screen(Terminal *term, buf_T *buf) // Terminal height may have decreased before `invalid_end` reflects it. term->invalid_end = MIN(term->invalid_end, height); + // There are no invalid rows. + if (term->invalid_start >= term->invalid_end) { + term->invalid_start = INT_MAX; + term->invalid_end = -1; + return; + } + for (int r = term->invalid_start, linenr = row_to_linenr(term, r); r < term->invalid_end; r++, linenr++) { fetch_row(term, r, width); diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 69edbc227d..7ed9c68c96 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -117,7 +117,7 @@ let s:filename_checks = { \ 'cpp': ['file.cxx', 'file.c++', 'file.hh', 'file.hxx', 'file.hpp', 'file.ipp', 'file.moc', 'file.tcc', 'file.inl', 'file.tlh'], \ 'crm': ['file.crm'], \ 'crontab': ['crontab', 'crontab.file', '/etc/cron.d/file', 'any/etc/cron.d/file'], - \ 'cs': ['file.cs'], + \ 'cs': ['file.cs', 'file.csx'], \ 'csc': ['file.csc'], \ 'csdl': ['file.csdl'], \ 'csp': ['file.csp', 'file.fdr'], @@ -226,6 +226,7 @@ let s:filename_checks = { \ 'hollywood': ['file.hws'], \ 'hostconf': ['/etc/host.conf', 'any/etc/host.conf'], \ 'hostsaccess': ['/etc/hosts.allow', '/etc/hosts.deny', 'any/etc/hosts.allow', 'any/etc/hosts.deny'], + \ 'i3config': ['/home/user/.i3/config', '/home/user/.config/i3/config', '/etc/i3/config', '/etc/xdg/i3/config'], \ 'logcheck': ['/etc/logcheck/file.d-some/file', '/etc/logcheck/file.d/file', 'any/etc/logcheck/file.d-some/file', 'any/etc/logcheck/file.d/file'], \ 'modula3': ['file.m3', 'file.mg', 'file.i3', 'file.ig'], \ 'natural': ['file.NSA', 'file.NSC', 'file.NSG', 'file.NSL', 'file.NSM', 'file.NSN', 'file.NSP', 'file.NSS'], @@ -455,6 +456,7 @@ let s:filename_checks = { \ 'skill': ['file.il', 'file.ils', 'file.cdf'], \ 'slang': ['file.sl'], \ 'slice': ['file.ice'], + \ 'solution': ['file.sln'], \ 'slpconf': ['/etc/slp.conf', 'any/etc/slp.conf'], \ 'slpreg': ['/etc/slp.reg', 'any/etc/slp.reg'], \ 'slpspi': ['/etc/slp.spi', 'any/etc/slp.spi'], @@ -502,7 +504,7 @@ let s:filename_checks = { \ 'tex': ['file.latex', 'file.sty', 'file.dtx', 'file.ltx', 'file.bbl'], \ 'texinfo': ['file.texinfo', 'file.texi', 'file.txi'], \ 'texmf': ['texmf.cnf'], - \ 'text': ['file.text', 'README', 'LICENSE', 'COPYING', 'AUTHORS', '/usr/share/doc/bash-completion/AUTHORS', '/etc/apt/apt.conf.d/README', '/etc/Muttrc.d/README'], + \ 'text': ['file.text', 'file.txt', 'README', 'LICENSE', 'COPYING', 'AUTHORS', '/usr/share/doc/bash-completion/AUTHORS', '/etc/apt/apt.conf.d/README', '/etc/Muttrc.d/README'], \ 'tf': ['file.tf', '.tfrc', 'tfrc'], \ 'tidy': ['.tidyrc', 'tidyrc', 'tidy.conf'], \ 'tilde': ['file.t.html'], @@ -568,6 +570,7 @@ let s:filename_checks = { \ 'yaml': ['file.yaml', 'file.yml'], \ 'raml': ['file.raml'], \ 'z8a': ['file.z8a'], + \ 'zig': ['file.zig'], \ 'zimbu': ['file.zu'], \ 'zimbutempl': ['file.zut'], \ 'zsh': ['.zprofile', '/etc/zprofile', '.zfbfmarks', 'file.zsh', '.zcompdump', '.zlogin', '.zlogout', '.zshenv', '.zshrc', '.zcompdump-file', '.zlog', '.zlog-file', '.zsh', '.zsh-file', 'any/etc/zprofile', 'zlog', 'zlog-file', 'zsh', 'zsh-file'], diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim index c38bfa5677..899eb530ec 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -661,6 +661,13 @@ function Test_no_space_before_xxx() let &columns = l:org_columns endfunction +" Test for :highlight command errors +func Test_highlight_cmd_errors() + if has('gui_running') || has('nvim') + call assert_fails('hi ' .. repeat('a', 201) .. ' ctermfg=black', 'E1249:') + endif +endfunc + " Test for using RGB color values in a highlight group func Test_xxlast_highlight_RGB_color() CheckCanRunGui diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim index f287256396..799e6cb57b 100644 --- a/src/nvim/testdir/test_signs.vim +++ b/src/nvim/testdir/test_signs.vim @@ -15,13 +15,13 @@ func Test_sign() " the icon name when listing signs. sign define Sign1 text=x - call Sign_command_ignore_error('sign define Sign2 text=xy texthl=Title linehl=Error icon=../../pixmaps/stock_vim_find_help.png') + call Sign_command_ignore_error('sign define Sign2 text=xy texthl=Title linehl=Error culhl=Search icon=../../pixmaps/stock_vim_find_help.png') " Test listing signs. let a=execute('sign list') call assert_match('^\nsign Sign1 text=x \nsign Sign2 ' . \ 'icon=../../pixmaps/stock_vim_find_help.png .*text=xy ' . - \ 'linehl=Error texthl=Title$', a) + \ 'linehl=Error texthl=Title culhl=Search$', a) let a=execute('sign list Sign1') call assert_equal("\nsign Sign1 text=x ", a) @@ -126,6 +126,30 @@ func Test_sign() " call assert_fails("sign define Sign4 text= linehl=Comment", 'E239:') call assert_fails("sign define Sign4 text=\\ ab linehl=Comment", 'E239:') + " an empty highlight argument for an existing sign clears it + sign define SignY texthl=TextHl culhl=CulHl linehl=LineHl + let sl = sign_getdefined('SignY')[0] + call assert_equal('TextHl', sl.texthl) + call assert_equal('CulHl', sl.culhl) + call assert_equal('LineHl', sl.linehl) + + sign define SignY texthl= culhl=CulHl linehl=LineHl + let sl = sign_getdefined('SignY')[0] + call assert_false(has_key(sl, 'texthl')) + call assert_equal('CulHl', sl.culhl) + call assert_equal('LineHl', sl.linehl) + + sign define SignY linehl= + let sl = sign_getdefined('SignY')[0] + call assert_false(has_key(sl, 'linehl')) + call assert_equal('CulHl', sl.culhl) + + sign define SignY culhl= + let sl = sign_getdefined('SignY')[0] + call assert_false(has_key(sl, 'culhl')) + + sign undefine SignY + " define sign with whitespace sign define Sign4 text=\ X linehl=Comment sign undefine Sign4 @@ -392,19 +416,21 @@ func Test_sign_funcs() call sign_undefine() " Tests for sign_define() - let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'} + let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error', + \ 'culhl': 'Visual'} call assert_equal(0, "sign1"->sign_define(attr)) call assert_equal([{'name' : 'sign1', 'texthl' : 'Error', - \ 'linehl' : 'Search', 'text' : '=>'}], sign_getdefined()) + \ 'linehl' : 'Search', 'culhl': 'Visual', 'text' : '=>'}], + \ sign_getdefined()) " Define a new sign without attributes and then update it call sign_define("sign2") let attr = {'text' : '!!', 'linehl' : 'DiffAdd', 'texthl' : 'DiffChange', - \ 'icon' : 'sign2.ico'} + \ 'culhl': 'DiffDelete', 'icon' : 'sign2.ico'} call Sign_define_ignore_error("sign2", attr) call assert_equal([{'name' : 'sign2', 'texthl' : 'DiffChange', - \ 'linehl' : 'DiffAdd', 'text' : '!!', 'icon' : 'sign2.ico'}], - \ "sign2"->sign_getdefined()) + \ 'linehl' : 'DiffAdd', 'culhl' : 'DiffDelete', 'text' : '!!', + \ 'icon' : 'sign2.ico'}], "sign2"->sign_getdefined()) " Test for a sign name with digits call assert_equal(0, sign_define(0002, {'linehl' : 'StatusLine'})) diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 5bb6059fa7..5fec41f9a5 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -114,6 +114,12 @@ static void tinput_done_event(void **argv) static void tinput_wait_enqueue(void **argv) { TermInput *input = argv[0]; + if (rbuffer_size(input->key_buffer) == 0 && input->paste == 3) { + const String keys = { .data = "", .size = 0 }; + String copy = copy_string(keys); + multiqueue_put(main_loop.events, tinput_paste_event, 3, + copy.data, copy.size, (intptr_t)input->paste); + } RBUFFER_UNTIL_EMPTY(input->key_buffer, buf, len) { const String keys = { .data = buf, .size = len }; if (input->paste) { diff --git a/src/nvim/ui.c b/src/nvim/ui.c index aad72af025..1aadaf5c9d 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -23,7 +23,6 @@ #include "nvim/main.h" #include "nvim/mbyte.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" @@ -300,6 +299,44 @@ void ui_busy_stop(void) } } +/// Emit a bell or visualbell as a warning +/// +/// val is one of the BO_ values, e.g., BO_OPER +void vim_beep(unsigned val) +{ + called_vim_beep = true; + + if (emsg_silent == 0) { + if (!((bo_flags & val) || (bo_flags & BO_ALL))) { + static int beeps = 0; + static uint64_t start_time = 0; + + // Only beep up to three times per half a second, + // otherwise a sequence of beeps would freeze Vim. + if (start_time == 0 || os_hrtime() - start_time > 500000000u) { + beeps = 0; + start_time = os_hrtime(); + } + beeps++; + if (beeps <= 3) { + if (p_vb) { + ui_call_visual_bell(); + } else { + ui_call_bell(); + } + } + } + + // When 'debug' contains "beep" produce a message. If we are sourcing + // a script or executing a function give the user a hint where the beep + // comes from. + if (vim_strchr(p_debug, 'e') != NULL) { + msg_source(HL_ATTR(HLF_W)); + msg_attr(_("Beep!"), HL_ATTR(HLF_W)); + } + } +} + void ui_attach_impl(UI *ui, uint64_t chanid) { if (ui_count == MAX_UI_COUNT) { diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 8161fce9f4..d18f35a43a 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -93,13 +93,14 @@ #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/garray.h" +#include "nvim/getchar.h" #include "nvim/lib/kvec.h" #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option.h" +#include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/os_unix.h" @@ -2621,7 +2622,7 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) if (uhp == NULL) { *msgbuf = NUL; } else { - add_time(msgbuf, sizeof(msgbuf), uhp->uh_time); + undo_fmt_time(msgbuf, sizeof(msgbuf), uhp->uh_time); } { @@ -2641,6 +2642,29 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) msgbuf); } +/// Put the timestamp of an undo header in "buf[buflen]" in a nice format. +void undo_fmt_time(char_u *buf, size_t buflen, time_t tt) +{ + struct tm curtime; + + if (time(NULL) - tt >= 100) { + os_localtime_r(&tt, &curtime); + if (time(NULL) - tt < (60L * 60L * 12L)) { + // within 12 hours + (void)strftime((char *)buf, buflen, "%H:%M:%S", &curtime); + } else { + // longer ago + (void)strftime((char *)buf, buflen, "%Y/%m/%d %H:%M:%S", &curtime); + } + } else { + int64_t seconds = time(NULL) - tt; + vim_snprintf((char *)buf, buflen, + NGETTEXT("%" PRId64 " second ago", + "%" PRId64 " seconds ago", (uint32_t)seconds), + seconds); + } +} + /// u_sync: stop adding to the current entry list /// /// @param force if true, also sync when no_u_sync is set. @@ -2683,16 +2707,13 @@ void ex_undolist(exarg_T *eap) while (uhp != NULL) { if (uhp->uh_prev.ptr == NULL && uhp->uh_walk != nomark && uhp->uh_walk != mark) { - vim_snprintf((char *)IObuff, IOSIZE, "%6ld %7d ", - uhp->uh_seq, changes); - add_time(IObuff + STRLEN(IObuff), IOSIZE - STRLEN(IObuff), - uhp->uh_time); + vim_snprintf((char *)IObuff, IOSIZE, "%6ld %7d ", uhp->uh_seq, changes); + undo_fmt_time(IObuff + STRLEN(IObuff), IOSIZE - STRLEN(IObuff), uhp->uh_time); if (uhp->uh_save_nr > 0) { while (STRLEN(IObuff) < 33) { STRCAT(IObuff, " "); } - vim_snprintf_add((char *)IObuff, IOSIZE, - " %3ld", uhp->uh_save_nr); + vim_snprintf_add((char *)IObuff, IOSIZE, " %3ld", uhp->uh_save_nr); } GA_APPEND(char_u *, &ga, vim_strsave(IObuff)); } diff --git a/src/nvim/vim.h b/src/nvim/vim.h index e3539c1a57..2f8ddd1e88 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -215,6 +215,11 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() // (vim_strchr() is now in strings.c) #define STRLEN(s) strlen((char *)(s)) +#ifdef HAVE_STRNLEN +# define STRNLEN(s, n) strnlen((char *)(s), (size_t)(n)) +#else +# define STRNLEN(s, n) xstrnlen((char *)(s), (size_t)(n)) +#endif #define STRCPY(d, s) strcpy((char *)(d), (char *)(s)) #define STRNCPY(d, s, n) strncpy((char *)(d), (char *)(s), (size_t)(n)) #define STRLCPY(d, s, n) xstrlcpy((char *)(d), (char *)(s), (size_t)(n)) diff --git a/src/nvim/window.c b/src/nvim/window.c index be963d8374..c711f462d1 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -30,7 +30,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index 45a01be620..a8f538b951 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -420,7 +420,7 @@ describe('API/extmarks', function() end) it('marks move with open line', function() - -- open_line in misc1.c + -- open_line in change.c -- testing marks below are also moved feed("yyP") set_extmark(ns, marks[1], 0, 4) @@ -489,7 +489,7 @@ describe('API/extmarks', function() end) it('marks move with line splits (using enter)', function() - -- open_line in misc1.c + -- open_line in change.c -- testing marks below are also moved feed("yyP") set_extmark(ns, marks[1], 0, 4) @@ -500,7 +500,7 @@ describe('API/extmarks', function() end) it('marks at last line move on insert new line', function() - -- open_line in misc1.c + -- open_line in change.c set_extmark(ns, marks[1], 0, 4) feed('0i<cr><esc>') check_undo_redo(ns, marks[1], 0, 4, 1, 4) diff --git a/test/functional/autocmd/searchwrapped_spec.lua b/test/functional/autocmd/searchwrapped_spec.lua new file mode 100644 index 0000000000..46c2c99b3d --- /dev/null +++ b/test/functional/autocmd/searchwrapped_spec.lua @@ -0,0 +1,53 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local command = helpers.command +local curbufmeths = helpers.curbufmeths +local eq = helpers.eq +local eval = helpers.eval +local feed = helpers.feed + +describe('autocmd SearchWrapped', function() + before_each(function() + clear() + command('set ignorecase') + command('let g:test = 0') + command('autocmd! SearchWrapped * let g:test += 1') + curbufmeths.set_lines(0, 1, false, { + 'The quick brown fox', + 'jumps over the lazy dog'}) + end) + + it('gets triggered when search wraps the end', function() + feed('/the<Return>') + eq(0, eval('g:test')) + + feed('n') + eq(1, eval('g:test')) + + feed('nn') + eq(2, eval('g:test')) + end) + + it('gets triggered when search wraps in reverse order', function() + feed('/the<Return>') + eq(0, eval('g:test')) + + feed('NN') + eq(1, eval('g:test')) + + feed('NN') + eq(2, eval('g:test')) + end) + + it('does not get triggered on failed searches', function() + feed('/blargh<Return>') + eq(0, eval('g:test')) + + feed('NN') + eq(0, eval('g:test')) + + feed('NN') + eq(0, eval('g:test')) + end) +end) diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 7dcca231ee..f25cfa2039 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -6,9 +6,11 @@ local poke_eventloop = helpers.poke_eventloop local eval, feed_command, source = helpers.eval, helpers.feed_command, helpers.source local eq, neq = helpers.eq, helpers.neq local write_file = helpers.write_file -local command= helpers.command +local command = helpers.command local exc_exec = helpers.exc_exec local matches = helpers.matches +local exec_lua = helpers.exec_lua +local sleep = helpers.sleep describe(':terminal buffer', function() local screen @@ -328,3 +330,37 @@ describe('No heap-buffer-overflow when', function() assert_alive() end) end) + +describe('on_lines does not emit out-of-bounds line indexes when', function() + before_each(function() + clear() + exec_lua([[ + function _G.register_callback(bufnr) + _G.cb_error = '' + vim.api.nvim_buf_attach(bufnr, false, { + on_lines = function(_, bufnr, _, firstline, _, _) + local status, msg = pcall(vim.api.nvim_buf_get_offset, bufnr, firstline) + if not status then + _G.cb_error = msg + end + end + }) + end + ]]) + end) + + it('creating a terminal buffer #16394', function() + feed_command([[autocmd TermOpen * ++once call v:lua.register_callback(expand("<abuf>"))]]) + feed_command('terminal') + sleep(500) + eq('', exec_lua([[return _G.cb_error]])) + end) + + it('deleting a terminal buffer #16394', function() + feed_command('terminal') + sleep(500) + feed_command('lua _G.register_callback(0)') + feed_command('bdelete!') + eq('', exec_lua([[return _G.cb_error]])) + end) +end) diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index b932c58430..11bdc73a47 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -12,6 +12,8 @@ local curbufmeths = helpers.curbufmeths local nvim = helpers.nvim local feed_data = thelpers.feed_data local pcall_err = helpers.pcall_err +local exec_lua = helpers.exec_lua +local assert_alive = helpers.assert_alive describe(':terminal scrollback', function() local screen @@ -527,3 +529,71 @@ describe("'scrollback' option", function() end) end) + +describe("pending scrollback line handling", function() + local screen + + before_each(function() + clear() + screen = Screen.new(30, 7) + screen:attach() + screen:set_default_attr_ids { + [1] = {foreground = Screen.colors.Brown}, + [2] = {reverse = true}, + [3] = {bold = true}, + } + end) + + it("does not crash after setting 'number' #14891", function() + exec_lua [[ + local a = vim.api + local buf = a.nvim_create_buf(true, true) + local chan = a.nvim_open_term(buf, {}) + a.nvim_win_set_option(0, "number", true) + a.nvim_chan_send(chan, ("a\n"):rep(11) .. "a") + a.nvim_win_set_buf(0, buf) + ]] + screen:expect [[ + {1: 1 }^a | + {1: 2 } a | + {1: 3 } a | + {1: 4 } a | + {1: 5 } a | + {1: 6 } a | + | + ]] + feed('G') + screen:expect [[ + {1: 7 } a | + {1: 8 } a | + {1: 9 } a | + {1: 10 } a | + {1: 11 } a | + {1: 12 } ^a | + | + ]] + assert_alive() + end) + + it("does not crash after nvim_buf_call #14891", function() + exec_lua [[ + local a = vim.api + local bufnr = a.nvim_create_buf(false, true) + a.nvim_buf_call(bufnr, function() + vim.fn.termopen({"echo", ("hi\n"):rep(11)}) + end) + a.nvim_win_set_buf(0, bufnr) + vim.cmd("startinsert") + ]] + screen:expect [[ + hi | + hi | + hi | + | + | + [Process exited 0]{2: } | + {3:-- TERMINAL --} | + ]] + assert_alive() + end) +end) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 6b9586b4de..7113cc1b49 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -677,8 +677,8 @@ describe('TUI', function() item 2997 | item 2998 | item 2999 | - item 3000 en{1:d} | - {5:[No Name] [+] 3000,13 Bot}| + item 3000 en{1:d}d | + {5:[No Name] [+] 5999,13 Bot}| | {3:-- TERMINAL --} | ]]) @@ -765,6 +765,44 @@ describe('TUI', function() ]]) end) + it('paste: streamed paste with isolated "stop paste" code', function() + child_session:request('nvim_exec_lua', [[ + _G.paste_phases = {} + vim.paste = (function(overridden) + return function(lines, phase) + table.insert(_G.paste_phases, phase) + overridden(lines, phase) + end + end)(vim.paste) + ]], {}) + feed_data('i') + feed_data('\027[200~pasted') -- phase 1 + screen:expect([[ + pasted{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]) + feed_data(' from terminal') -- phase 2 + screen:expect([[ + pasted from terminal{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]) + -- Send isolated "stop paste" sequence. + feed_data('\027[201~') -- phase 3 + screen:expect_unchanged() + local _, rv = child_session:request('nvim_exec_lua', [[return _G.paste_phases]], {}) + eq({1, 2, 3}, rv) + end) + it('allows termguicolors to be set at runtime', function() screen:set_option('rgb', true) screen:set_default_attr_ids({ diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index 1138cfbf4c..911fa017ab 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -227,6 +227,43 @@ void ui_refresh(void) }, res) end) + it('supports getting text of multiline node', function() + if pending_c_parser(pending) then return end + insert(test_text) + local res = exec_lua([[ + local parser = vim.treesitter.get_parser(0, "c") + local tree = parser:parse()[1] + return vim.treesitter.get_node_text(tree:root(), 0) + ]]) + eq(test_text, res) + + local res2 = exec_lua([[ + local parser = vim.treesitter.get_parser(0, "c") + local root = parser:parse()[1]:root() + return vim.treesitter.get_node_text(root:child(0):child(0), 0) + ]]) + eq('void', res2) + end) + + it('support getting text where start of node is past EOF', function() + local text = [[ +def run + a = <<~E +end]] + insert(text) + local result = exec_lua([[ + local fake_node = {} + function fake_node:start() + return 3, 0, 23 + end + function fake_node:end_() + return 3, 0, 23 + end + return vim.treesitter.get_node_text(fake_node, 0) == nil + ]]) + eq(true, result) + end) + it('can match special regex characters like \\ * + ( with `vim-match?`', function() insert('char* astring = "\\n"; (1 + 1) * 2 != 2;') diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua index 9c035c728b..03cd4bfd06 100644 --- a/test/functional/ui/cursor_spec.lua +++ b/test/functional/ui/cursor_spec.lua @@ -212,10 +212,10 @@ describe('ui/cursor', function() if m.blinkwait then m.blinkwait = 700 end end if m.hl_id then - m.hl_id = 58 + m.hl_id = 60 m.attr = {background = Screen.colors.DarkGray} end - if m.id_lm then m.id_lm = 59 end + if m.id_lm then m.id_lm = 61 end end -- Assert the new expectation. diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua index 249686234c..3e0e15c2b7 100644 --- a/test/functional/ui/fold_spec.lua +++ b/test/functional/ui/fold_spec.lua @@ -41,6 +41,7 @@ describe("folded lines", function() [9] = {bold = true, foreground = Screen.colors.Brown}, [10] = {background = Screen.colors.LightGrey, underline = true}, [11] = {bold=true}, + [12] = {background = Screen.colors.Grey90}, }) end) @@ -84,6 +85,117 @@ describe("folded lines", function() end end) + it("highlights with CursorLineFold when 'cursorline' is set", function() + command("set cursorline foldcolumn=2 foldmethod=marker") + command("hi link CursorLineFold Search") + insert(content1) + feed("zf3j") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7: }This is a | + {7: }valid English | + {7: }sentence composed by | + {7: }an exhausted developer | + {7: }in his cave. | + {6: }{12:^ }| + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + {7: }This is a | + {7: }valid English | + {7: }sentence composed by | + {7: }an exhausted developer | + {7: }in his cave. | + {6: }{12:^ }| + {1:~ }| + | + ]]) + end + feed("k") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7: }This is a | + {7: }valid English | + {7: }sentence composed by | + {7: }an exhausted developer | + {6: }{12:^in his cave. }| + {7: } | + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + {7: }This is a | + {7: }valid English | + {7: }sentence composed by | + {7: }an exhausted developer | + {6: }{12:^in his cave. }| + {7: } | + {1:~ }| + | + ]]) + end + command("set cursorlineopt=line") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7: }This is a | + {7: }valid English | + {7: }sentence composed by | + {7: }an exhausted developer | + {7: }{12:^in his cave. }| + {7: } | + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + {7: }This is a | + {7: }valid English | + {7: }sentence composed by | + {7: }an exhausted developer | + {7: }{12:^in his cave. }| + {7: } | + {1:~ }| + | + ]]) + end + end) + it("highlighting with relative line numbers", function() command("set relativenumber cursorline cursorlineopt=number foldmethod=marker") feed_command("set foldcolumn=2") diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index 741b93043d..dcd31cfdb7 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -157,6 +157,99 @@ describe('Signs', function() ]]) end) + it('higlights the cursorline sign with culhl', function() + feed('ia<cr>b<cr>c<esc>') + command('sign define piet text=>> texthl=Search culhl=ErrorMsg') + command('sign place 1 line=1 name=piet buffer=1') + command('sign place 2 line=2 name=piet buffer=1') + command('sign place 3 line=3 name=piet buffer=1') + command('set cursorline') + screen:expect([[ + {1:>>}a | + {1:>>}b | + {8:>>}{3:^c }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + feed('k') + screen:expect([[ + {1:>>}a | + {8:>>}{3:^b }| + {1:>>}c | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + command('set nocursorline') + screen:expect([[ + {1:>>}a | + {1:>>}^b | + {1:>>}c | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + command('set cursorline cursorlineopt=line') + screen:expect([[ + {1:>>}a | + {1:>>}{3:^b }| + {1:>>}c | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + command('set cursorlineopt=number') + screen:expect([[ + {1:>>}a | + {8:>>}^b | + {1:>>}c | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) + it('multiple signs #9295', function() feed('ia<cr>b<cr>c<cr><esc>') command('set number') diff --git a/test/helpers.lua b/test/helpers.lua index 09b113c01d..87431e4342 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -741,9 +741,20 @@ function module.read_file_list(filename, start) if not file then return nil end + + -- There is no need to read more than the last 2MB of the log file, so seek + -- to that. + local file_size = file:seek("end") + local offset = file_size - 2000000 + if offset < 0 then + offset = 0 + end + file:seek("set", offset) + local lines = {} local i = 1 - for line in file:lines() do + local line = file:read("*l") + while line ~= nil do if i >= start then table.insert(lines, line) if #lines > maxlines then @@ -751,6 +762,7 @@ function module.read_file_list(filename, start) end end i = i + 1 + line = file:read("*l") end file:close() return lines diff --git a/test/unit/os/shell_spec.lua b/test/unit/os/shell_spec.lua index a73fc8e47e..29a2b78491 100644 --- a/test/unit/os/shell_spec.lua +++ b/test/unit/os/shell_spec.lua @@ -4,7 +4,6 @@ local cimported = helpers.cimport( './src/nvim/os/shell.h', './src/nvim/option_defs.h', './src/nvim/main.h', - './src/nvim/misc1.h', './src/nvim/memory.h' ) local ffi, eq = helpers.ffi, helpers.eq diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index 52839d8efa..c5adf36908 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -203,8 +203,8 @@ set(LIBICONV_SHA256 ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc891 set(TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/v0.20.1.tar.gz) set(TREESITTER_C_SHA256 ffcc2ef0eded59ad1acec9aec4f9b0c7dd209fc1a85d85f8b0e81298e3dddcc2) -set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.20.1.tar.gz) -set(TREESITTER_SHA256 12a3f7206af3028dbe8a0de50d8ebd6d7010bf762db918acae76fc7585f1258d) +set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/25f64e1eb66bb1ab3eccd4f0b7da543005f3ba79.tar.gz) +set(TREESITTER_SHA256 4f43bad474df494d00a779157f1c437638987d0e7896f9c73492cfeef35366b5) if(USE_BUNDLED_UNIBILIUM) include(BuildUnibilium) |