diff options
93 files changed, 2454 insertions, 969 deletions
diff --git a/.travis.yml b/.travis.yml index 53faf29cc5..85eca601a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -86,7 +86,7 @@ addons: - valgrind - xclip homebrew: - update: false + update: true casks: - powershell packages: diff --git a/CMakeLists.txt b/CMakeLists.txt index de530bb4f7..74e161d989 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -308,6 +308,11 @@ if(UNIX) endif() endif() +check_c_compiler_flag(-fno-common HAVE_FNO_COMMON) +if (HAVE_FNO_COMMON) + add_compile_options(-fno-common) +endif() + check_c_compiler_flag(-fdiagnostics-color=auto HAS_DIAG_COLOR_FLAG) if(HAS_DIAG_COLOR_FLAG) if(CMAKE_GENERATOR MATCHES "Ninja") diff --git a/ci/build.ps1 b/ci/build.ps1 index 01cf20874e..36570be7ae 100644 --- a/ci/build.ps1 +++ b/ci/build.ps1 @@ -36,8 +36,7 @@ $scoop = (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh' Invoke-Expression $scoop } -scoop install diffutils perl -diff3 --version +scoop install perl perl --version cpanm.bat --version @@ -81,7 +80,7 @@ if ($compiler -eq 'MINGW') { # in MSYS2, but we cannot build inside the MSYS2 shell. $cmakeGenerator = 'Ninja' $cmakeGeneratorArgs = '-v' - $mingwPackages = @('ninja', 'cmake').ForEach({ + $mingwPackages = @('ninja', 'cmake', 'diffutils').ForEach({ "mingw-w64-$arch-$_" }) diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index 5feab0ce70..122ae357bc 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -328,18 +328,24 @@ function! man#complete(arg_lead, cmd_line, cursor_pos) abort return s:complete(sect, sect, name) endfunction -function! s:get_paths(sect, name) abort +function! s:get_paths(sect, name, do_fallback) abort + " callers must try-catch this, as some `man` implementations don't support `s:find_arg` try let mandirs = join(split(s:system(['man', s:find_arg]), ':\|\n'), ',') + return globpath(mandirs,'man?/'.a:name.'*.'.a:sect.'*', 0, 1) catch - call s:error(v:exception) - return + if !a:do_fallback + throw v:exception + endif + + " fallback to a single path, with the page we're trying to find + let [l:sect, l:name, l:path] = s:verify_exists(a:sect, a:name) + return [l:path] endtry - return globpath(mandirs,'man?/'.a:name.'*.'.a:sect.'*', 0, 1) endfunction function! s:complete(sect, psect, name) abort - let pages = s:get_paths(a:sect, a:name) + let pages = s:get_paths(a:sect, a:name, v:false) " We remove duplicates in case the same manpage in different languages was found. return uniq(sort(map(pages, 's:format_candidate(v:val, a:psect)'), 'i')) endfunction @@ -387,7 +393,7 @@ endfunction function! man#goto_tag(pattern, flags, info) abort let [l:sect, l:name] = man#extract_sect_and_name_ref(a:pattern) - let l:paths = s:get_paths(l:sect, l:name) + let l:paths = s:get_paths(l:sect, l:name, v:true) let l:structured = [] for l:path in l:paths diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 113a92d5e4..29c5c37bcd 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2110,6 +2110,7 @@ extend({expr1}, {expr2} [, {expr3}]) exp({expr}) Float exponential of {expr} expand({expr} [, {nosuf} [, {list}]]) any expand special keywords in {expr} +expandcmd({expr}) String expand {expr} like with `:edit` feedkeys({string} [, {mode}]) Number add key sequence to typeahead buffer filereadable({file}) Number |TRUE| if {file} is a readable file filewritable({file}) Number |TRUE| if {file} is a writable file @@ -3733,6 +3734,14 @@ expand({expr} [, {nosuf} [, {list}]]) *expand()* See |glob()| for finding existing files. See |system()| for getting the raw output of an external command. +expandcmd({expr}) *expandcmd()* + Expand special items in {expr} like what is done for an Ex + command such as `:edit`. This expands special keywords, like + with |expand()|, and environment variables, anywhere in + {expr}. Returns the expanded string. + Example: > + :echo expandcmd('make %<.o') +< extend({expr1}, {expr2} [, {expr3}]) *extend()* {expr1} and {expr2} must be both |Lists| or both |Dictionaries|. @@ -7809,7 +7818,7 @@ sign_getplaced([{expr} [, {dict}]]) *sign_getplaced()* priority sign priority The returned signs in a buffer are ordered by their line - number. + number and priority. Returns an empty list on failure or if there are no placed signs. @@ -10370,8 +10379,8 @@ text... The parsing works slightly different from |:echo|, more like |:execute|. All the expressions are first evaluated and concatenated before echoing anything. - The expressions must evaluate to a Number or String, a - Dictionary or List causes an error. + If expressions does not evaluate to a Number or + String, string() is used to turn it into a string. Uses the highlighting set by the |:echohl| command. Example: > :echomsg "It's a Zizzer Zazzer Zuzz, as you can plainly see." @@ -10382,7 +10391,7 @@ text... message in the |message-history|. When used in a script or function the line number will be added. Spaces are placed between the arguments as with the - :echo command. When used inside a try conditional, + |:echomsg| command. When used inside a try conditional, the message is raised as an error exception instead (see |try-echoerr|). Example: > diff --git a/runtime/doc/fold.txt b/runtime/doc/fold.txt index f2f6c70b0c..8e2cb2f728 100644 --- a/runtime/doc/fold.txt +++ b/runtime/doc/fold.txt @@ -527,8 +527,7 @@ FOLDCOLUMN *fold-foldcolumn* 'foldcolumn' is a number, which sets the width for a column on the side of the window to indicate folds. When it is zero, there is no foldcolumn. A normal -value is 4 or 5. The minimal useful value is 2, although 1 still provides -some information. The maximum is 12. +value is auto:9. The maximum is 9. An open fold is indicated with a column that has a '-' at the top and '|' characters below it. This column stops where the open fold stops. When folds diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 9de2aaf592..2f5427f6fc 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -48,6 +48,7 @@ go-to-definition, hover, etc. Example config: > nnoremap <silent> <c-k> <cmd>lua vim.lsp.buf.signature_help()<CR> nnoremap <silent> 1gD <cmd>lua vim.lsp.buf.type_definition()<CR> nnoremap <silent> gr <cmd>lua vim.lsp.buf.references()<CR> + nnoremap <silent> g0 <cmd>lua vim.lsp.buf.document_symbol()<CR> Nvim provides the |vim.lsp.omnifunc| 'omnifunc' handler which allows |i_CTRL-X_CTRL-O| to consume LSP completion. Example config (note the use of @@ -77,21 +78,6 @@ FAQ *lsp-faq* "after/ftplugin/python.vim". ================================================================================ -LSP HIGHLIGHT *lsp-highlight* - -When LSP is activated these highlight groups are defined: - - LspDiagnosticsError - LspDiagnosticsHint - LspDiagnosticsInformation - LspDiagnosticsUnderline - LspDiagnosticsUnderlineError - LspDiagnosticsUnderlineHint - LspDiagnosticsUnderlineInformation - LspDiagnosticsUnderlineWarning - LspDiagnosticsWarning - -================================================================================ LSP API *lsp-api* The `vim.lsp` Lua module is a framework for building LSP plugins. @@ -174,6 +160,25 @@ name: > vim.lsp.protocol.TextDocumentSyncKind[1] == "Full" ================================================================================ +LSP HIGHLIGHT *lsp-highlight* + + *hl-LspDiagnosticsError* +LspDiagnosticsError used for "Error" diagnostic virtual text + *hl-LspDiagnosticsWarning* +LspDiagnosticsWarning used for "Warning" diagnostic virtual text + *hl-LspDiagnosticsInformation* +LspDiagnosticInformation used for "Information" diagnostic virtual text + *hl-LspDiagnosticsHint* +LspDiagnosticHint used for "Hint" diagnostic virtual text + *hl-LspReferenceText* +LspReferenceText used for highlighting "text" references + *hl-LspReferenceRead* +LspReferenceRead used for highlighting "read" references + *hl-LspReferenceWrite* +LspReferenceWrite used for highlighting "write" references + + +================================================================================ LSP EXAMPLE *lsp-extension-example* This example is for plugin authors or users who want a lot of control. If you @@ -290,6 +295,12 @@ The example will: < +============================================================================== +AUTOCOMMANDS *lsp-autocommands* + + *LspDiagnosticsChanged* +LspDiagnosticsChanged After receiving publishDiagnostics server response + ============================================================================== Lua module: vim.lsp *lsp-core* @@ -333,7 +344,7 @@ buf_notify({bufnr}, {method}, {params}) *vim.lsp.buf_notify()* {params} (string) Parameters to send to the server Return: ~ - nil + true if any client returns true; false otherwise *vim.lsp.buf_request()* buf_request({bufnr}, {method}, {params}, {callback}) @@ -720,6 +731,12 @@ declaration() *vim.lsp.buf.declaration()* definition() *vim.lsp.buf.definition()* TODO: Documentation +document_highlight() *vim.lsp.buf.document_highlight()* + TODO: Documentation + +document_symbol() *vim.lsp.buf.document_symbol()* + TODO: Documentation + formatting({options}) *vim.lsp.buf.formatting()* TODO: Documentation @@ -751,6 +768,10 @@ rename({new_name}) *vim.lsp.buf.rename()* request({method}, {params}, {callback}) *vim.lsp.buf.request()* TODO: Documentation +server_ready() *vim.lsp.buf.server_ready()* + Sends a notification through all clients associated with current + buffer and returns `true` if server responds. + signature_help() *vim.lsp.buf.signature_help()* TODO: Documentation @@ -896,6 +917,33 @@ apply_workspace_edit({workspace_edit}) buf_clear_diagnostics({bufnr}) *vim.lsp.util.buf_clear_diagnostics()* TODO: Documentation + + *vim.lsp.util.buf_diagnostics_count()* +buf_diagnostics_count({kind}) + Returns the number of diagnostics of given kind for current buffer. + Useful for showing diagnostics counts in statusline. eg: + +> + function! LspStatus() abort + let sl = '' + if luaeval('vim.lsp.buf.server_ready()') + let sl.='%#MyStatuslineLSP#E:' + let sl.='%#MyStatuslineLSPErrors#%{luaeval("vim.lsp.util.buf_diagnostics_count(\"Error\")")}' + let sl.='%#MyStatuslineLSP# W:' + let sl.='%#MyStatuslineLSPWarnings#%{luaeval("vim.lsp.util.buf_diagnostics_count(\"Warning\")")}' + else + let sl.='%#MyStatuslineLSPErrors#off' + endif + return sl + endfunction + let &l:statusline = '%#MyStatuslineLSP#LSP '.LspStatus() +< + + Parameters: ~ + {kind} Diagnostic severity kind: Error, Warning, Information or Hint. + +buf_clear_references({bufnr}) *vim.lsp.util.buf_clear_references()* + TODO: Documentation *vim.lsp.util.buf_diagnostics_save_positions()* buf_diagnostics_save_positions({bufnr}, {diagnostics}) @@ -909,6 +957,18 @@ buf_diagnostics_underline({bufnr}, {diagnostics}) buf_diagnostics_virtual_text({bufnr}, {diagnostics}) TODO: Documentation + *vim.lsp.util.buf_diagnostics_signs()* +buf_diagnostics_signs({bufnr}, {diagnostics}) + Place signs for each diagnostic in the sign column. + Sign characters can be customized with the following options: +> +let g:LspDiagnosticsErrorSign = 'E' +let g:LspDiagnosticsWarningSign = 'W' +let g:LspDiagnosticsInformationSign = 'I' +let g:LspDiagnosticsHintSign = 'H' +< + + character_offset({buf}, {row}, {col}) *vim.lsp.util.character_offset()* TODO: Documentation diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index c113a70027..800f24b5c9 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -693,6 +693,35 @@ identical identifiers, highlighting both as |hl-WarningMsg|: > (eq? @WarningMsg.left @WarningMsg.right)) ------------------------------------------------------------------------------ +VIM.REGEX *lua-regex* + +Vim regexes can be used directly from lua. Currently they only allow +matching within a single line. + +vim.regex({re}) *vim.regex()* + + Parse the regex {re} and return a regex object. 'magic' and + 'ignorecase' options are ignored, lua regexes always defaults to magic + and ignoring case. The behavior can be changed with flags in + the beginning of the string |/magic|. + +Regex objects support the following methods: + +regex:match_str({str}) *regex:match_str()* + Match the string against the regex. If the string should match the + regex precisely, surround the regex with `^` and `$`. + If the was a match, the byte indices for the beginning and end of + the match is returned. When there is no match, `nil` is returned. + As any integer is truth-y, `regex:match()` can be directly used + as a condition in an if-statement. + +regex:match_line({bufnr}, {line_idx}[, {start}, {end}]) *regex:match_line()* + Match line {line_idx} (zero-based) in buffer {bufnr}. If {start} and + {end} are supplied, match only this byte index range. Otherwise see + |regex:match_str()|. If {start} is used, then the returned byte + indices will be relative {start}. + +------------------------------------------------------------------------------ VIM *lua-builtin* vim.api.{func}({...}) *vim.api* diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 7107a0135d..283b2c3f12 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -2421,11 +2421,14 @@ A jump table for the options with a short description can be found at |Q_op|. automatically close when moving out of them. *'foldcolumn'* *'fdc'* -'foldcolumn' 'fdc' number (default 0) +'foldcolumn' 'fdc' string (default "0") local to window - When non-zero, a column with the specified width is shown at the side - of the window which indicates open and closed folds. The maximum - value is 12. + When and how to draw the foldcolumn. Valid values are: + "auto": resize to the maximum amount of folds to display. + "auto:[1-9]": resize to accommodate multiple folds up to the + selected level + 0: to disable foldcolumn + "[1-9]": to display a fixed number of columns See |folding|. *'foldenable'* *'fen'* *'nofoldenable'* *'nofen'* diff --git a/runtime/doc/sign.txt b/runtime/doc/sign.txt index 4e0d91dae0..e3ba4ba181 100644 --- a/runtime/doc/sign.txt +++ b/runtime/doc/sign.txt @@ -176,9 +176,9 @@ See |sign_place()| for the equivalent Vim script function. By default, the sign is assigned a default priority of 10. To assign a different priority value, use "priority={prio}" to - specify a value. The priority is used to determine the - highlight group used when multiple signs are placed on the - same line. + specify a value. The priority is used to determine the sign + that is displayed when multiple signs are placed on the same + line. Examples: > :sign place 5 line=3 name=sign1 file=a.py @@ -198,7 +198,9 @@ See |sign_place()| for the equivalent Vim script function. it (e.g., when the debugger has stopped at a breakpoint). The optional "group={group}" attribute can be used before - "file=" to select a sign in a particular group. + "file=" to select a sign in a particular group. The optional + "priority={prio}" attribute can be used to change the priority + of an existing sign. :sign place {id} name={name} [buffer={nr}] Same, but use buffer {nr}. If the buffer argument is not diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index e3f0d593a7..af7d233619 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -184,12 +184,17 @@ argument. the 'modifiable' and 'write' options can be set to enable changes and writing. - *-Z* *restricted-mode* *E145* + *-Z* *restricted-mode* *E145* *E981* -Z Restricted mode. All commands that make use of an external shell are disabled. This includes suspending with CTRL-Z, - ":sh", filtering, the system() function, backtick expansion, - delete(), rename(), mkdir(), writefile(), libcall(), - jobstart(), etc. + ":sh", filtering, the system() function, backtick expansion + and libcall(). + Also disallowed are delete(), rename(), mkdir(), jobstart(), + etc. + Interfaces, such as Python, Ruby and Lua, are also disabled, + since they could be used to execute shell commands. + Note that the user may still find a loophole to execute a + shell command, it has only been made difficult. -e *-e* *-E* -E Start Nvim in Ex mode |gQ|. diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 5f9253cbd0..234f7801ab 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -609,6 +609,7 @@ String manipulation: *string-functions* strcharpart() get part of a string using char index strgetchar() get character from a string using char index expand() expand special keywords + expandcmd() expand a command like done for `:edit` iconv() convert text from one encoding to another byteidx() byte index of a character in a string byteidxcomp() like byteidx() but count composing characters diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 5835c7f314..376375e4ef 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -201,6 +201,7 @@ Options: 'guicursor' works in the terminal 'fillchars' flags: "msgsep" (see 'display'), "eob" for |hl-EndOfBuffer| marker, "foldopen", "foldsep", "foldclose" + 'foldcolumn' supports up to 9 dynamic/fixed columns 'inccommand' shows interactive results for |:substitute|-like commands 'listchars' local to window 'pumblend' pseudo-transparent popupmenu diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 8190542955..afff4d9900 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -101,7 +101,7 @@ local function for_each_buffer_client(bufnr, callback) for client_id in pairs(client_ids) do local client = active_clients[client_id] if client then - callback(client, client_id) + callback(client, client_id, bufnr) end end end @@ -520,23 +520,24 @@ function lsp.start_client(config) end --- Checks capabilities before rpc.request-ing. - function client.request(method, params, callback) + function client.request(method, params, callback, bufnr) if not callback then callback = resolve_callback(method) or error("not found: request callback for client "..client.name) end - local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, callback) + local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, callback, bufnr) -- TODO keep these checks or just let it go anyway? if (not client.resolved_capabilities.hover and method == 'textDocument/hover') or (not client.resolved_capabilities.signature_help and method == 'textDocument/signatureHelp') or (not client.resolved_capabilities.goto_definition and method == 'textDocument/definition') or (not client.resolved_capabilities.implementation and method == 'textDocument/implementation') + or (not client.resolved_capabilities.document_symbol and method == 'textDocument/documentSymbol') then - callback(unsupported_method(method), method, nil, client_id) + callback(unsupported_method(method), method, nil, client_id, bufnr) return end return rpc.request(method, params, function(err, result) - callback(err, method, result, client_id) + callback(err, method, result, client_id, bufnr) end) end @@ -838,8 +839,8 @@ function lsp.buf_request(bufnr, method, params, callback) callback = { callback, 'f', true }; } local client_request_ids = {} - for_each_buffer_client(bufnr, function(client, client_id) - local request_success, request_id = client.request(method, params, callback) + for_each_buffer_client(bufnr, function(client, client_id, resolved_bufnr) + local request_success, request_id = client.request(method, params, callback, resolved_bufnr) -- This could only fail if the client shut down in the time since we looked -- it up and we did the request, which should be rare. @@ -895,21 +896,22 @@ function lsp.buf_request_sync(bufnr, method, params, timeout_ms) return request_results end ---- Sends a notification to all servers attached to the buffer. ---- ---@param bufnr (optional, number) Buffer handle, or 0 for current ---@param method (string) LSP method name ---@param params (string) Parameters to send to the server ---- ---@returns nil +--- Send a notification to a server +-- @param bufnr [number] (optional): The number of the buffer +-- @param method [string]: Name of the request method +-- @param params [string]: Arguments to send to the server +-- +-- @returns true if any client returns true; false otherwise function lsp.buf_notify(bufnr, method, params) validate { bufnr = { bufnr, 'n', true }; method = { method, 's' }; } - for_each_buffer_client(bufnr, function(client, _client_id) - client.rpc.notify(method, params) + local resp = false + for_each_buffer_client(bufnr, function(client, _client_id, _resolved_bufnr) + if client.rpc.notify(method, params) then resp = true end end) + return resp end --- Implements 'omnifunc' compatible LSP completion. diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 19deb5df45..fc9e10cb73 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -23,6 +23,10 @@ local function request(method, params, callback) return vim.lsp.buf_request(0, method, params, callback) end +function M.server_ready() + return not not vim.lsp.buf_notify(0, "window/progress", {}) +end + function M.hover() local params = util.make_position_params() request('textDocument/hover', params) @@ -134,5 +138,28 @@ function M.references(context) request('textDocument/references', params) end +function M.document_symbol() + local params = { textDocument = util.make_text_document_params() } + request('textDocument/documentSymbol', params) +end + +--- Send request to server to resolve document highlights for the +--- current text document position. This request can be associated +--- to key mapping or to events such as `CursorHold`, eg: +--- +--- <pre> +--- vim.api.nvim_command [[autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()]] +--- vim.api.nvim_command [[autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight()]] +--- vim.api.nvim_command [[autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()]] +--- </pre> +function M.document_highlight() + local params = util.make_position_params() + request('textDocument/documentHighlight', params) +end + +function M.clear_references() + util.buf_clear_references() +end + return M -- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua index e76e07ca96..644c12f98c 100644 --- a/runtime/lua/vim/lsp/callbacks.lua +++ b/runtime/lua/vim/lsp/callbacks.lua @@ -32,12 +32,19 @@ M['textDocument/publishDiagnostics'] = function(_, _, result) util.buf_diagnostics_save_positions(bufnr, result.diagnostics) util.buf_diagnostics_underline(bufnr, result.diagnostics) util.buf_diagnostics_virtual_text(bufnr, result.diagnostics) - -- util.set_loclist(result.diagnostics) + util.buf_diagnostics_signs(bufnr, result.diagnostics) + vim.api.nvim_command("doautocmd User LspDiagnosticsChanged") end M['textDocument/references'] = function(_, _, result) if not result then return end - util.set_qflist(result) + util.set_qflist(util.locations_to_items(result)) +end + +M['textDocument/documentSymbol'] = function(_, _, result, _, bufnr) + if not result or vim.tbl_isempty(result) then return end + + util.set_qflist(util.symbols_to_items(result, bufnr)) api.nvim_command("copen") api.nvim_command("wincmd p") end @@ -96,7 +103,7 @@ local function location_callback(_, method, result) end util.jump_to_location(result[1]) if #result > 1 then - util.set_qflist(result) + util.set_qflist(util.locations_to_items(result)) api.nvim_command("copen") api.nvim_command("wincmd p") end @@ -196,7 +203,33 @@ M['textDocument/peekDefinition'] = function(_, _, result, _) api.nvim_buf_add_highlight(headbuf, -1, 'Keyword', 0, -1) end -local function log_message(_, _, result, client_id) +M['textDocument/documentHighlight'] = function(_, _, result, _) + if not result then return end + local bufnr = api.nvim_get_current_buf() + util.buf_highlight_references(bufnr, result) +end + +M['window/logMessage'] = function(_, _, result, client_id) + local message_type = result.type + local message = result.message + local client = vim.lsp.get_client_by_id(client_id) + local client_name = client and client.name or string.format("id=%d", client_id) + if not client then + err_message("LSP[", client_name, "] client has shut down after sending the message") + end + if message_type == protocol.MessageType.Error then + log.error(message) + elseif message_type == protocol.MessageType.Warning then + log.warn(message) + elseif message_type == protocol.MessageType.Info then + log.info(message) + else + log.debug(message) + end + return result +end + +M['window/showMessage'] = function(_, _, result, client_id) local message_type = result.type local message = result.message local client = vim.lsp.get_client_by_id(client_id) @@ -213,9 +246,6 @@ local function log_message(_, _, result, client_id) return result end -M['window/showMessage'] = log_message -M['window/logMessage'] = log_message - -- Add boilerplate error validation and logging for all of these. for k, fn in pairs(M) do M[k] = function(err, method, params, client_id) diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index 974eaae38c..c0db5e5485 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -13,7 +13,6 @@ log.levels = { INFO = 2; WARN = 3; ERROR = 4; - -- FATAL = 4; } -- Default log level is warn. diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index f64b0b50e7..41e8119c8c 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -663,19 +663,19 @@ function protocol.make_client_capabilities() documentHighlight = { dynamicRegistration = false }; - -- documentSymbol = { - -- dynamicRegistration = false; - -- symbolKind = { - -- valueSet = (function() - -- local res = {} - -- for k in pairs(protocol.SymbolKind) do - -- if type(k) == 'string' then table.insert(res, k) end - -- end - -- return res - -- end)(); - -- }; - -- hierarchicalDocumentSymbolSupport = false; - -- }; + documentSymbol = { + dynamicRegistration = false; + symbolKind = { + valueSet = (function() + local res = {} + for k in pairs(protocol.SymbolKind) do + if type(k) == 'number' then table.insert(res, k) end + end + return res + end)(); + }; + hierarchicalDocumentSymbolSupport = true; + }; }; workspace = nil; experimental = nil; diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 3dfe4d7d02..5dd010f2a4 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -269,7 +269,7 @@ function M.convert_input_to_markdown_lines(input, contents) end end end - if contents[1] == '' or contents[1] == nil then + if (contents[1] == '' or contents[1] == nil) and #contents == 1 then return {} end return contents @@ -569,7 +569,8 @@ do local all_buffer_diagnostics = {} local diagnostic_ns = api.nvim_create_namespace("vim_lsp_diagnostics") - + local reference_ns = api.nvim_create_namespace("vim_lsp_references") + local sign_ns = 'vim_lsp_signs' local underline_highlight_name = "LspDiagnosticsUnderline" vim.cmd(string.format("highlight default %s gui=underline cterm=underline", underline_highlight_name)) for kind, _ in pairs(protocol.DiagnosticSeverity) do @@ -603,6 +604,11 @@ do function M.buf_clear_diagnostics(bufnr) validate { bufnr = {bufnr, 'n', true} } bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr + + -- clear sign group + vim.fn.sign_unplace(sign_ns, {buffer=bufnr}) + + -- clear virtual text namespace api.nvim_buf_clear_namespace(bufnr, diagnostic_ns, 0, -1) end @@ -683,7 +689,6 @@ do end end - function M.buf_diagnostics_underline(bufnr, diagnostics) for _, diagnostic in ipairs(diagnostics) do local start = diagnostic.range["start"] @@ -705,6 +710,25 @@ do end end + function M.buf_clear_references(bufnr) + validate { bufnr = {bufnr, 'n', true} } + api.nvim_buf_clear_namespace(bufnr, reference_ns, 0, -1) + end + + function M.buf_highlight_references(bufnr, references) + validate { bufnr = {bufnr, 'n', true} } + for _, reference in ipairs(references) do + local start_pos = {reference["range"]["start"]["line"], reference["range"]["start"]["character"]} + local end_pos = {reference["range"]["end"]["line"], reference["range"]["end"]["character"]} + local document_highlight_kind = { + [protocol.DocumentHighlightKind.Text] = "LspReferenceText"; + [protocol.DocumentHighlightKind.Read] = "LspReferenceRead"; + [protocol.DocumentHighlightKind.Write] = "LspReferenceWrite"; + } + highlight_range(bufnr, reference_ns, document_highlight_kind[reference["kind"]], start_pos, end_pos) + end + end + function M.buf_diagnostics_virtual_text(bufnr, diagnostics) local buffer_line_diagnostics = all_buffer_diagnostics[bufnr] if not buffer_line_diagnostics then @@ -725,6 +749,34 @@ do api.nvim_buf_set_virtual_text(bufnr, diagnostic_ns, line, virt_texts, {}) end end + function M.buf_diagnostics_count(kind) + local bufnr = vim.api.nvim_get_current_buf() + local buffer_line_diagnostics = all_buffer_diagnostics[bufnr] + if not buffer_line_diagnostics then return end + local count = 0 + for _, line_diags in pairs(buffer_line_diagnostics) do + for _, diag in ipairs(line_diags) do + if protocol.DiagnosticSeverity[kind] == diag.severity then count = count + 1 end + end + end + return count + end + function M.buf_diagnostics_signs(bufnr, diagnostics) + vim.fn.sign_define('LspDiagnosticsErrorSign', {text=vim.g['LspDiagnosticsErrorSign'] or 'E', texthl='LspDiagnosticsError', linehl='', numhl=''}) + vim.fn.sign_define('LspDiagnosticsWarningSign', {text=vim.g['LspDiagnosticsWarningSign'] or 'W', texthl='LspDiagnosticsWarning', linehl='', numhl=''}) + vim.fn.sign_define('LspDiagnosticsInformationSign', {text=vim.g['LspDiagnosticsInformationSign'] or 'I', texthl='LspDiagnosticsInformation', linehl='', numhl=''}) + vim.fn.sign_define('LspDiagnosticsHintSign', {text=vim.g['LspDiagnosticsHintSign'] or 'H', texthl='LspDiagnosticsHint', linehl='', numhl=''}) + + for _, diagnostic in ipairs(diagnostics) do + local diagnostic_severity_map = { + [protocol.DiagnosticSeverity.Error] = "LspDiagnosticsErrorSign"; + [protocol.DiagnosticSeverity.Warning] = "LspDiagnosticsWarningSign"; + [protocol.DiagnosticSeverity.Information] = "LspDiagnosticsInformationSign"; + [protocol.DiagnosticSeverity.Hint] = "LspDiagnosticsHintSign"; + } + vim.fn.sign_place(0, sign_ns, diagnostic_severity_map[diagnostic.severity], bufnr, {lnum=(diagnostic.range.start.line+1)}) + end + end end local position_sort = sort_by_key(function(v) @@ -745,7 +797,7 @@ function M.locations_to_items(locations) for _, d in ipairs(locations) do local start = d.range.start local fname = assert(vim.uri_to_fname(d.uri)) - table.insert(grouped[fname], {start = start, msg= d.message }) + table.insert(grouped[fname], {start = start}) end @@ -772,7 +824,7 @@ function M.locations_to_items(locations) filename = fname, lnum = row + 1, col = col + 1; - text = temp.msg; + text = line; }) end end @@ -782,23 +834,60 @@ function M.locations_to_items(locations) return items end --- locations is Location[] --- Only sets for the current window. -function M.set_loclist(locations) +function M.set_loclist(items) vim.fn.setloclist(0, {}, ' ', { title = 'Language Server'; - items = M.locations_to_items(locations); + items = items; }) end --- locations is Location[] -function M.set_qflist(locations) +function M.set_qflist(items) vim.fn.setqflist({}, ' ', { title = 'Language Server'; - items = M.locations_to_items(locations); + items = items; }) end +--- Convert symbols to quickfix list items +--- +--@symbols DocumentSymbol[] or SymbolInformation[] +function M.symbols_to_items(symbols, bufnr) + local function _symbols_to_items(_symbols, _items, _bufnr) + for _, symbol in ipairs(_symbols) do + if symbol.location then -- SymbolInformation type + local range = symbol.location.range + local kind = protocol.SymbolKind[symbol.kind] + table.insert(_items, { + filename = vim.uri_to_fname(symbol.location.uri), + lnum = range.start.line + 1, + col = range.start.character + 1, + kind = kind, + text = '['..kind..'] '..symbol.name, + }) + elseif symbol.range then -- DocumentSymbole type + local kind = protocol.SymbolKind[symbol.kind] + table.insert(_items, { + -- bufnr = _bufnr, + filename = vim.api.nvim_buf_get_name(_bufnr), + lnum = symbol.range.start.line + 1, + col = symbol.range.start.character + 1, + kind = kind, + text = '['..kind..'] '..symbol.name + }) + if symbol.children then + for _, child in ipairs(symbol) do + for _, v in ipairs(_symbols_to_items(child, _items, _bufnr)) do + vim.list_extend(_items, v) + end + end + end + end + end + return _items + end + return _symbols_to_items(symbols, {}, bufnr) +end + -- Remove empty lines from the beginning and end. function M.trim_empty_lines(lines) local start = 1 @@ -851,11 +940,15 @@ function M.make_position_params() local line = api.nvim_buf_get_lines(0, row, row+1, true)[1] col = str_utfindex(line, col) return { - textDocument = { uri = vim.uri_from_bufnr(0) }; + textDocument = M.make_text_document_params(); position = { line = row; character = col; } } end +function M.make_text_document_params() + return { uri = vim.uri_from_bufnr(0) } +end + -- @param buf buffer handle or 0 for current. -- @param row 0-indexed line -- @param col 0-indexed byte offset in line diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 498992aa2e..1bf1c63fd7 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -356,6 +356,24 @@ function vim.tbl_islist(t) end end +--- Counts the number of non-nil values in table `t`. +--- +--- <pre> +--- vim.tbl_count({ a=1, b=2 }) => 2 +--- vim.tbl_count({ 1, 2 }) => 2 +--- </pre> +--- +--@see https://github.com/Tieske/Penlight/blob/master/lua/pl/tablex.lua +--@param Table +--@returns Number that is the number of the value in table +function vim.tbl_count(t) + vim.validate{t={t,'t'}} + + local count = 0 + for _ in pairs(t) do count = count + 1 end + return count +end + --- Trim whitespace (Lua pattern "%s") from both sides of a string. --- --@see https://www.lua.org/pil/20.2.html diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 0d0e22adb3..8dacfa11cf 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -113,12 +113,33 @@ end local Query = {} Query.__index = Query +local magic_prefixes = {['\\v']=true, ['\\m']=true, ['\\M']=true, ['\\V']=true} +local function check_magic(str) + if string.len(str) < 2 or magic_prefixes[string.sub(str,1,2)] then + return str + end + return '\\v'..str +end + function M.parse_query(lang, query) M.require_language(lang) local self = setmetatable({}, Query) self.query = vim._ts_parse_query(lang, query) self.info = self.query:inspect() self.captures = self.info.captures + self.regexes = {} + for id,preds in pairs(self.info.patterns) do + local regexes = {} + for i, pred in ipairs(preds) do + if (pred[1] == "match?" and type(pred[2]) == "number" + and type(pred[3]) == "string") then + regexes[i] = vim.regex(check_magic(pred[3])) + end + end + if next(regexes) then + self.regexes[id] = regexes + end + end return self end @@ -131,8 +152,13 @@ local function get_node_text(node, bufnr) return string.sub(line, start_col+1, end_col) end -local function match_preds(match, preds, bufnr) - for _, pred in pairs(preds) do +function Query:match_preds(match, pattern, bufnr) + local preds = self.info.patterns[pattern] + if not preds then + return true + end + local regexes = self.regexes[pattern] + for i, pred in pairs(preds) do if pred[1] == "eq?" then local node = match[pred[2]] local node_text = get_node_text(node, bufnr) @@ -149,6 +175,16 @@ local function match_preds(match, preds, bufnr) if node_text ~= str or str == nil then return false end + elseif pred[1] == "match?" then + if not regexes or not regexes[i] then + return false + end + 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 + return regexes[i]:match_line(bufnr, start_row, start_col, end_col) else return false end @@ -164,8 +200,7 @@ function Query:iter_captures(node, bufnr, start, stop) local function iter() local capture, captured_node, match = raw_iter() if match ~= nil then - local preds = self.info.patterns[match.pattern] - local active = match_preds(match, preds, bufnr) + local active = self:match_preds(match, match.pattern, bufnr) match.active = active if not active then return iter() -- tail call: try next match @@ -184,8 +219,7 @@ function Query:iter_matches(node, bufnr, start, stop) local function iter() local pattern, match = raw_iter() if match ~= nil then - local preds = self.info.patterns[pattern] - local active = (not preds) or match_preds(match, preds, bufnr) + local active = self:match_preds(match, pattern, bufnr) if not active then return iter() -- tail call: try next match end diff --git a/runtime/lua/vim/tshighlighter.lua b/runtime/lua/vim/tshighlighter.lua index 9d094f0f9a..1440acf0d0 100644 --- a/runtime/lua/vim/tshighlighter.lua +++ b/runtime/lua/vim/tshighlighter.lua @@ -69,6 +69,8 @@ function TSHighlighter:set_query(query) end self.id_map[i] = hl end + + a.nvim__buf_redraw_range(self.buf, 0, a.nvim_buf_line_count(self.buf)) end function TSHighlighter:on_change(changes) diff --git a/runtime/scripts.vim b/runtime/scripts.vim index a690431014..c552f0202f 100644 --- a/runtime/scripts.vim +++ b/runtime/scripts.vim @@ -376,6 +376,10 @@ else elseif s:line1 =~? '-\*-.*erlang.*-\*-' set ft=erlang + " YAML + elseif s:line1 =~# '^%YAML' + set ft=yaml + " CVS diff else let s:lnum = 1 diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 645fb653e5..7578f0fbc5 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1123,7 +1123,7 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, } if (!ns_initialized((uint64_t)ns_id)) { - api_set_error(err, kErrorTypeValidation, _("Invalid ns_id")); + api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); return rv; } @@ -1190,7 +1190,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, } if (!ns_initialized((uint64_t)ns_id)) { - api_set_error(err, kErrorTypeValidation, _("Invalid ns_id")); + api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); return rv; } Integer limit = -1; @@ -1280,7 +1280,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, } if (!ns_initialized((uint64_t)ns_id)) { - api_set_error(err, kErrorTypeValidation, _("Invalid ns_id")); + api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); return 0; } @@ -1308,7 +1308,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, if (id >= 0) { id_num = (uint64_t)id; } else { - api_set_error(err, kErrorTypeValidation, _("Invalid mark id")); + api_set_error(err, kErrorTypeValidation, "Invalid mark id"); return 0; } @@ -1337,7 +1337,7 @@ Boolean nvim_buf_del_extmark(Buffer buffer, return false; } if (!ns_initialized((uint64_t)ns_id)) { - api_set_error(err, kErrorTypeValidation, _("Invalid ns_id")); + api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); return false; } @@ -1655,7 +1655,7 @@ Integer nvim__buf_add_decoration(Buffer buffer, Integer ns_id, String hl_group, } if (!ns_initialized((uint64_t)ns_id)) { - api_set_error(err, kErrorTypeValidation, _("Invalid ns_id")); + api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); return 0; } diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index a1745ef777..a458762cc6 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1544,7 +1544,7 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int *col = MAXCOL; return true; } else if (id < 0) { - api_set_error(err, kErrorTypeValidation, _("Mark id must be positive")); + api_set_error(err, kErrorTypeValidation, "Mark id must be positive"); return false; } @@ -1554,7 +1554,7 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int *col = extmark.col; return true; } else { - api_set_error(err, kErrorTypeValidation, _("No mark with requested id")); + api_set_error(err, kErrorTypeValidation, "No mark with requested id"); return false; } @@ -1565,7 +1565,7 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int || pos.items[0].type != kObjectTypeInteger || pos.items[1].type != kObjectTypeInteger) { api_set_error(err, kErrorTypeValidation, - _("Position must have 2 integer elements")); + "Position must have 2 integer elements"); return false; } Integer pos_row = pos.items[0].data.integer; @@ -1575,7 +1575,7 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int return true; } else { api_set_error(err, kErrorTypeValidation, - _("Position must be a mark id Integer or position Array")); + "Position must be a mark id Integer or position Array"); return false; } } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 9cdf36e4ed..1f943b25b6 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -166,11 +166,11 @@ typedef struct { char_u *wo_briopt; # define w_p_briopt w_onebuf_opt.wo_briopt /* 'breakindentopt' */ int wo_diff; -# define w_p_diff w_onebuf_opt.wo_diff /* 'diff' */ - long wo_fdc; -# define w_p_fdc w_onebuf_opt.wo_fdc /* 'foldcolumn' */ - int wo_fdc_save; -# define w_p_fdc_save w_onebuf_opt.wo_fdc_save /* 'foldenable' saved for diff mode */ +# define w_p_diff w_onebuf_opt.wo_diff // 'diff' + char_u *wo_fdc; +# define w_p_fdc w_onebuf_opt.wo_fdc // 'foldcolumn' + char_u *wo_fdc_save; +# define w_p_fdc_save w_onebuf_opt.wo_fdc_save // 'fdc' saved for diff mode int wo_fen; # define w_p_fen w_onebuf_opt.wo_fen /* 'foldenable' */ int wo_fen_save; diff --git a/src/nvim/channel.c b/src/nvim/channel.c index c66a0682e3..5eb29a7290 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -19,7 +19,6 @@ #include "nvim/ascii.h" static bool did_stdio = false; -PMap(uint64_t) *channels = NULL; /// next free id for a job or rpc channel /// 1 is reserved for stdio channel diff --git a/src/nvim/channel.h b/src/nvim/channel.h index c733e276be..9d26852ce5 100644 --- a/src/nvim/channel.h +++ b/src/nvim/channel.h @@ -85,7 +85,7 @@ struct Channel { bool callback_scheduled; }; -EXTERN PMap(uint64_t) *channels; +EXTERN PMap(uint64_t) *channels INIT(= NULL); #ifdef INCLUDE_GENERATED_DECLARATIONS # include "channel.h.generated.h" diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 0f9e2e23c0..f9d5adbc12 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1570,6 +1570,7 @@ char_u* skiptohex(char_u *q) /// /// @return Pointer to the next whitespace or NUL character. char_u *skiptowhite(const char_u *p) + FUNC_ATTR_NONNULL_ALL { while (*p != ' ' && *p != '\t' && *p != NUL) { p++; diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 04309444d9..f8b7592d0b 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -1385,11 +1385,18 @@ void diff_win_options(win_T *wp, int addbuf) curbuf = curwin->w_buffer; if (!wp->w_p_diff) { - wp->w_p_fdc_save = wp->w_p_fdc; wp->w_p_fen_save = wp->w_p_fen; wp->w_p_fdl_save = wp->w_p_fdl; + + if (wp->w_p_diff_saved) { + free_string_option(wp->w_p_fdc_save); + } + wp->w_p_fdc_save = vim_strsave(wp->w_p_fdc); } - wp->w_p_fdc = diff_foldcolumn; + xfree(wp->w_p_fdc); + wp->w_p_fdc = (char_u *)xstrdup("2"); + assert(diff_foldcolumn >= 0 && diff_foldcolumn <= 9); + snprintf((char *)wp->w_p_fdc, STRLEN(wp->w_p_fdc) + 1, "%d", diff_foldcolumn); wp->w_p_fen = true; wp->w_p_fdl = 0; foldUpdateAll(wp); @@ -1443,9 +1450,9 @@ void ex_diffoff(exarg_T *eap) wp->w_p_fdm = vim_strsave(*wp->w_p_fdm_save ? wp->w_p_fdm_save : (char_u *)"manual"); - if (wp->w_p_fdc == diff_foldcolumn) { - wp->w_p_fdc = wp->w_p_fdc_save; - } + free_string_option(wp->w_p_fdc); + wp->w_p_fdc = vim_strsave(wp->w_p_fdc_save); + if (wp->w_p_fdl == 0) { wp->w_p_fdl = wp->w_p_fdl_save; } diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 3e57bc8599..f938607f17 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -4083,7 +4083,7 @@ static int ins_compl_get_exp(pos_T *ini) type = CTRL_X_PATH_DEFINES; else if (*e_cpt == ']' || *e_cpt == 't') { type = CTRL_X_TAGS; - vim_snprintf((char *)IObuff, IOSIZE, _("Scanning tags.")); + vim_snprintf((char *)IObuff, IOSIZE, "%s", _("Scanning tags.")); (void)msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R)); } else { type = -1; @@ -6215,7 +6215,7 @@ comp_textwidth ( if (cmdwin_type != 0) { textwidth -= 1; } - textwidth -= curwin->w_p_fdc; + textwidth -= win_fdccol_count(curwin); textwidth -= win_signcol_count(curwin); if (curwin->w_p_nu || curwin->w_p_rnu) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 74a5edc0df..ca0e078e4a 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -8114,10 +8114,16 @@ void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, bool callback_from_typval(Callback *const callback, typval_T *const arg) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { + int r = OK; + if (arg->v_type == VAR_PARTIAL && arg->vval.v_partial != NULL) { callback->data.partial = arg->vval.v_partial; callback->data.partial->pt_refcount++; callback->type = kCallbackPartial; + } else if (arg->v_type == VAR_STRING + && arg->vval.v_string != NULL + && ascii_isdigit(*arg->vval.v_string)) { + r = FAIL; } else if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING) { char_u *name = arg->vval.v_string; func_ref(name); @@ -8126,6 +8132,10 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg) } else if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0) { callback->type = kCallbackNone; } else { + r = FAIL; + } + + if (r == FAIL) { EMSG(_("E921: Invalid callback argument")); return false; } @@ -9459,6 +9469,27 @@ void set_selfdict(typval_T *rettv, dict_T *selfdict) } } +// Turn a typeval into a string. Similar to tv_get_string_buf() but uses +// string() on Dict, List, etc. +static const char *tv_stringify(typval_T *varp, char *buf) + FUNC_ATTR_NONNULL_ALL +{ + if (varp->v_type == VAR_LIST + || varp->v_type == VAR_DICT + || varp->v_type == VAR_FUNC + || varp->v_type == VAR_PARTIAL + || varp->v_type == VAR_FLOAT) { + typval_T tmp; + + f_string(varp, &tmp, NULL); + const char *const res = tv_get_string_buf(&tmp, buf); + tv_clear(varp); + *varp = tmp; + return res; + } + return tv_get_string_buf(varp, buf); +} + // Find variable "name" in the list of variables. // Return a pointer to it if found, NULL if not found. // Careful: "a:0" variables don't have a name. @@ -10349,7 +10380,10 @@ void ex_execute(exarg_T *eap) } if (!eap->skip) { - const char *const argstr = tv_get_string(&rettv); + char buf[NUMBUFLEN]; + const char *const argstr = eap->cmdidx == CMD_execute + ? tv_get_string_buf(&rettv, buf) + : tv_stringify(&rettv, buf); const size_t len = strlen(argstr); ga_grow(&ga, len + 2); if (!GA_EMPTY(&ga)) { diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 4654f8cef6..d3e769a7ef 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -102,6 +102,7 @@ return { exists={args=1}, exp={args=1, func="float_op_wrapper", data="&exp"}, expand={args={1, 3}}, + expandcmd={args=1}, extend={args={2, 3}}, feedkeys={args={1, 2}}, file_readable={args=1, func='f_filereadable'}, -- obsolete diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index bd57cec794..6d67279e64 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -2103,6 +2103,31 @@ static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) menu_get((char_u *)tv_get_string(&argvars[0]), modes, rettv->vval.v_list); } +// "expandcmd()" function +// Expand all the special characters in a command string. +static void f_expandcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char_u *errormsg = NULL; + + rettv->v_type = VAR_STRING; + char_u *cmdstr = (char_u *)xstrdup(tv_get_string(&argvars[0])); + + exarg_T eap = { + .cmd = cmdstr, + .arg = cmdstr, + .usefilter = false, + .nextcmd = NULL, + .cmdidx = CMD_USER, + }; + eap.argt |= NOSPC; + + expand_filename(&eap, &cmdstr, &errormsg); + if (errormsg != NULL && *errormsg != NUL) { + EMSG(errormsg); + } + rettv->vval.v_string = cmdstr; +} + /* * "extend(list, list [, idx])" function * "extend(dict, dict [, action])" function @@ -4292,7 +4317,7 @@ static void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) HistoryType histype; rettv->vval.v_number = false; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } const char *str = tv_get_string_chk(&argvars[0]); // NULL on type error @@ -5272,7 +5297,6 @@ static void f_list2str(typval_T *argvars, typval_T *rettv, FunPtr fptr) }); ga_append(&ga, NUL); - rettv->v_type = VAR_STRING; rettv->vval.v_string = ga.ga_data; } @@ -7755,8 +7779,7 @@ static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - if (check_restricted() - || check_secure() + if (check_secure() || !tv_check_str_or_nr(&argvars[0])) { return; } @@ -8260,7 +8283,7 @@ static void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -9558,7 +9581,7 @@ static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) /* * "string()" function */ -static void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = (char_u *)encode_tv2string(&argvars[0], NULL); @@ -10981,7 +11004,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = -1; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 008453b87f..5afdedff75 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -33,7 +33,7 @@ typedef double float_T; enum { DO_NOT_FREE_CNT = (INT_MAX / 2) }; /// Additional values for tv_list_alloc() len argument -enum { +enum ListLenSpecials { /// List length is not known in advance /// /// To be used when there is neither a way to know how many elements will be @@ -49,7 +49,7 @@ enum { /// /// To be used when it looks impractical to determine list length. kListLenMayKnow = -3, -} ListLenSpecials; +}; /// Maximal possible value of varnumber_T variable #define VARNUMBER_MAX INT64_MAX diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index bc6821f60f..a2c4435014 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -3008,18 +3008,18 @@ void ex_z(exarg_T *eap) ex_no_reprint = true; } -/* - * Check if the restricted flag is set. - * If so, give an error message and return TRUE. - * Otherwise, return FALSE. - */ -int check_restricted(void) +// Check if the restricted flag is set. +// If so, give an error message and return true. +// Otherwise, return false. +bool check_restricted(void) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { if (restricted) { - EMSG(_("E145: Shell commands not allowed in restricted mode")); - return TRUE; + EMSG(_("E145: Shell commands and some functionality not allowed" + " in restricted mode")); + return true; } - return FALSE; + return false; } /* @@ -4482,8 +4482,9 @@ prepare_tagpreview ( curwin->w_p_wfh = TRUE; RESET_BINDING(curwin); /* don't take over 'scrollbind' and 'cursorbind' */ - curwin->w_p_diff = FALSE; /* no 'diff' */ - curwin->w_p_fdc = 0; /* no 'foldcolumn' */ + curwin->w_p_diff = false; // no 'diff' + set_string_option_direct((char_u *)"fdc", -1, // no 'foldcolumn' + (char_u *)"0", OPT_FREE, SID_NONE); return true; } } diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index f7aa8a994a..252af409c0 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -24,6 +24,7 @@ local SBOXOK = 0x80000 local CMDWIN = 0x100000 local MODIFY = 0x200000 local EXFLAGS = 0x400000 +local RESTRICT = 0x800000 local FILES = bit.bor(XFILE, EXTRA) local WORD1 = bit.bor(EXTRA, NOSPC) local FILE1 = bit.bor(FILES, NOSPC) @@ -1582,19 +1583,19 @@ return { }, { command='lua', - flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_lua', }, { command='luado', - flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_luado', }, { command='luafile', - flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_luafile', }, @@ -1924,13 +1925,13 @@ return { }, { command='perl', - flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, SBOXOK, CMDWIN), + flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, SBOXOK, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_script_ni', }, { command='perldo', - flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_ni', }, @@ -2056,67 +2057,67 @@ return { }, { command='python', - flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_python', }, { command='pydo', - flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_pydo', }, { command='pyfile', - flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_pyfile', }, { command='py3', - flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_python3', }, { command='py3do', - flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_pydo3', }, { command='python3', - flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_python3', }, { command='py3file', - flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_py3file', }, { command='pyx', - flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_pyx', }, { command='pyxdo', - flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_pyxdo', }, { command='pythonx', - flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_pyx', }, { command='pyxfile', - flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_pyxfile', }, @@ -2242,19 +2243,19 @@ return { }, { command='ruby', - flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_ruby', }, { command='rubydo', - flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_rubydo', }, { command='rubyfile', - flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_rubyfile', }, diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index d25d81658c..1f0560ae48 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -36,35 +36,36 @@ // 4. Add documentation in ../doc/xxx.txt. Add a tag for both the short and // long name of the command. -#define RANGE 0x001 /* allow a linespecs */ -#define BANG 0x002 /* allow a ! after the command name */ -#define EXTRA 0x004 /* allow extra args after command name */ -#define XFILE 0x008 /* expand wildcards in extra part */ -#define NOSPC 0x010 /* no spaces allowed in the extra part */ -#define DFLALL 0x020 /* default file range is 1,$ */ -#define WHOLEFOLD 0x040 /* extend range to include whole fold also - when less than two numbers given */ -#define NEEDARG 0x080 /* argument required */ -#define TRLBAR 0x100 /* check for trailing vertical bar */ -#define REGSTR 0x200 /* allow "x for register designation */ -#define COUNT 0x400 /* allow count in argument, after command */ -#define NOTRLCOM 0x800 /* no trailing comment allowed */ -#define ZEROR 0x1000 /* zero line number allowed */ -#define USECTRLV 0x2000 /* do not remove CTRL-V from argument */ -#define NOTADR 0x4000 /* number before command is not an address */ -#define EDITCMD 0x8000 /* allow "+command" argument */ -#define BUFNAME 0x10000 /* accepts buffer name */ -#define BUFUNL 0x20000 /* accepts unlisted buffer too */ -#define ARGOPT 0x40000 /* allow "++opt=val" argument */ -#define SBOXOK 0x80000 /* allowed in the sandbox */ -#define CMDWIN 0x100000 /* allowed in cmdline window; when missing - * disallows editing another buffer when - * curbuf_lock is set */ -#define MODIFY 0x200000 /* forbidden in non-'modifiable' buffer */ -#define EXFLAGS 0x400000 /* allow flags after count in argument */ -#define FILES (XFILE | EXTRA) /* multiple extra files allowed */ -#define WORD1 (EXTRA | NOSPC) /* one extra word allowed */ -#define FILE1 (FILES | NOSPC) /* 1 file allowed, defaults to current file */ +#define RANGE 0x001 // allow a linespecs +#define BANG 0x002 // allow a ! after the command name +#define EXTRA 0x004 // allow extra args after command name +#define XFILE 0x008 // expand wildcards in extra part +#define NOSPC 0x010 // no spaces allowed in the extra part +#define DFLALL 0x020 // default file range is 1,$ +#define WHOLEFOLD 0x040 // extend range to include whole fold also + // when less than two numbers given +#define NEEDARG 0x080 // argument required +#define TRLBAR 0x100 // check for trailing vertical bar +#define REGSTR 0x200 // allow "x for register designation +#define COUNT 0x400 // allow count in argument, after command +#define NOTRLCOM 0x800 // no trailing comment allowed +#define ZEROR 0x1000 // zero line number allowed +#define USECTRLV 0x2000 // do not remove CTRL-V from argument +#define NOTADR 0x4000 // number before command is not an address +#define EDITCMD 0x8000 // allow "+command" argument +#define BUFNAME 0x10000 // accepts buffer name +#define BUFUNL 0x20000 // accepts unlisted buffer too +#define ARGOPT 0x40000 // allow "++opt=val" argument +#define SBOXOK 0x80000 // allowed in the sandbox +#define CMDWIN 0x100000 // allowed in cmdline window; when missing + // disallows editing another buffer when + // curbuf_lock is set +#define MODIFY 0x200000 // forbidden in non-'modifiable' buffer +#define EXFLAGS 0x400000 // allow flags after count in argument +#define RESTRICT 0x800000L // forbidden in restricted mode +#define FILES (XFILE | EXTRA) // multiple extra files allowed +#define WORD1 (EXTRA | NOSPC) // one extra word allowed +#define FILE1 (FILES | NOSPC) // 1 file allowed, defaults to current file // values for cmd_addr_type #define ADDR_LINES 0 diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 28ecaf684a..abe394dc3a 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1783,10 +1783,14 @@ static char_u * do_one_cmd(char_u **cmdlinep, if (!ea.skip) { if (sandbox != 0 && !(ea.argt & SBOXOK)) { - /* Command not allowed in sandbox. */ + // Command not allowed in sandbox. errormsg = (char_u *)_(e_sandbox); goto doend; } + if (restricted != 0 && (ea.argt & RESTRICT)) { + errormsg = (char_u *)_("E981: Command not allowed in restricted mode"); + goto doend; + } if (!MODIFIABLE(curbuf) && (ea.argt & MODIFY) // allow :put in terminals && (!curbuf->terminal || ea.cmdidx != CMD_put)) { diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 1c3c212aef..c9f36ccd61 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2701,7 +2701,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) goto color_cmdline_error; } if (tv.v_type != VAR_LIST) { - PRINT_ERRMSG(_("E5400: Callback should return list")); + PRINT_ERRMSG("%s", _("E5400: Callback should return list")); goto color_cmdline_error; } if (tv.vval.v_list == NULL) { diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index d00943c1be..e77b556024 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -86,7 +86,7 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id, extmark_del(buf, ns_id, id); } else { // TODO(bfredl): we need to do more if "revising" a decoration mark. - MarkTreeIter itr[1]; + MarkTreeIter itr[1] = { 0 }; old_pos = marktree_lookup(buf->b_marktree, old_mark, itr); assert(itr->node); if (old_pos.row == row && old_pos.col == col) { @@ -119,7 +119,7 @@ revised: static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col) { - MarkTreeIter itr[1]; + MarkTreeIter itr[1] = { 0 }; mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr); if (pos.row == -1) { return false; @@ -147,7 +147,7 @@ bool extmark_del(buf_T *buf, uint64_t ns_id, uint64_t id) return false; } - MarkTreeIter itr[1]; + MarkTreeIter itr[1] = { 0 }; mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr); assert(pos.row >= 0); marktree_del_itr(buf->b_marktree, itr, false); @@ -207,7 +207,7 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, delete_set = map_new(uint64_t, uint64_t)(); } - MarkTreeIter itr[1]; + MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, l_row, l_col, itr); while (true) { mtmark_t mark = marktree_itr_current(itr); @@ -276,7 +276,7 @@ ExtmarkArray extmark_get(buf_T *buf, uint64_t ns_id, int64_t amount, bool reverse) { ExtmarkArray array = KV_INITIAL_VALUE; - MarkTreeIter itr[1]; + MarkTreeIter itr[1] = { 0 }; // Find all the marks marktree_itr_get_ext(buf->b_marktree, (mtpos_t){ l_row, l_col }, itr, reverse, false, NULL); @@ -396,7 +396,7 @@ void u_extmark_copy(buf_T *buf, ExtmarkUndoObject undo; - MarkTreeIter itr[1]; + MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, l_row, l_col, itr); while (true) { mtmark_t mark = marktree_itr_current(itr); @@ -738,7 +738,7 @@ void clear_virttext(VirtText *text) VirtText *extmark_find_virttext(buf_T *buf, int row, uint64_t ns_id) { - MarkTreeIter itr[1]; + MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, row, 0, itr); while (true) { mtmark_t mark = marktree_itr_current(itr); diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 331d7f9e29..c14a64fa38 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -1465,10 +1465,10 @@ static void foldMarkAdjustRecurse(garray_T *gap, linenr_T line1, linenr_T line2, * Get the lowest 'foldlevel' value that makes the deepest nested fold in the * current window open. */ -int getDeepestNesting(void) +int getDeepestNesting(win_T *wp) { - checkupdate(curwin); - return getDeepestNestingRecurse(&curwin->w_folds); + checkupdate(wp); + return getDeepestNestingRecurse(&wp->w_folds); } static int getDeepestNestingRecurse(garray_T *gap) diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h index 01f60ccf49..f0b52079aa 100644 --- a/src/nvim/getchar.h +++ b/src/nvim/getchar.h @@ -10,12 +10,12 @@ /// Values for "noremap" argument of ins_typebuf() /// /// Also used for map->m_noremap and menu->noremap[]. -enum { +enum RemapValues { REMAP_YES = 0, ///< Allow remapping. REMAP_NONE = -1, ///< No remapping. REMAP_SCRIPT = -2, ///< Remap script-local mappings only. REMAP_SKIP = -3, ///< No remapping for first char. -} RemapValues; +}; // Argument for flush_buffers(). typedef enum { diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 323c6cf9c3..ccf704fe76 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -120,24 +120,20 @@ typedef off_t off_T; # endif #endif -/* - * When vgetc() is called, it sets mod_mask to the set of modifiers that are - * held down based on the MOD_MASK_* symbols that are read first. - */ -EXTERN int mod_mask INIT(= 0x0); /* current key modifiers */ +// When vgetc() is called, it sets mod_mask to the set of modifiers that are +// held down based on the MOD_MASK_* symbols that are read first. +EXTERN int mod_mask INIT(= 0x0); // current key modifiers EXTERN bool lua_attr_active INIT(= false); -/* - * Cmdline_row is the row where the command line starts, just below the - * last window. - * When the cmdline gets longer than the available space the screen gets - * scrolled up. After a CTRL-D (show matches), after hitting ':' after - * "hit return", and for the :global command, the command line is - * temporarily moved. The old position is restored with the next call to - * update_screen(). - */ +// Cmdline_row is the row where the command line starts, just below the +// last window. +// When the cmdline gets longer than the available space the screen gets +// scrolled up. After a CTRL-D (show matches), after hitting ':' after +// "hit return", and for the :global command, the command line is +// temporarily moved. The old position is restored with the next call to +// update_screen(). EXTERN int cmdline_row; EXTERN int redraw_cmdline INIT(= false); // cmdline must be redrawn @@ -149,42 +145,38 @@ EXTERN int cmdline_was_last_drawn INIT(= false); // cmdline was last drawn EXTERN int exec_from_reg INIT(= false); // executing register -/* - * When '$' is included in 'cpoptions' option set: - * When a change command is given that deletes only part of a line, a dollar - * is put at the end of the changed text. dollar_vcol is set to the virtual - * column of this '$'. -1 is used to indicate no $ is being displayed. - */ +// When '$' is included in 'cpoptions' option set: +// When a change command is given that deletes only part of a line, a dollar +// is put at the end of the changed text. dollar_vcol is set to the virtual +// column of this '$'. -1 is used to indicate no $ is being displayed. EXTERN colnr_T dollar_vcol INIT(= -1); -/* - * Variables for Insert mode completion. - */ +// Variables for Insert mode completion. -/* Length in bytes of the text being completed (this is deleted to be replaced - * by the match.) */ +// Length in bytes of the text being completed (this is deleted to be replaced +// by the match.) EXTERN int compl_length INIT(= 0); -/* Set when character typed while looking for matches and it means we should - * stop looking for matches. */ -EXTERN int compl_interrupted INIT(= FALSE); +// Set when character typed while looking for matches and it means we should +// stop looking for matches. +EXTERN int compl_interrupted INIT(= false); // Set when doing something for completion that may call edit() recursively, // which is not allowed. Also used to disable folding during completion EXTERN int compl_busy INIT(= false); -/* List of flags for method of completion. */ +// List of flags for method of completion. EXTERN int compl_cont_status INIT(= 0); -# define CONT_ADDING 1 /* "normal" or "adding" expansion */ -# define CONT_INTRPT (2 + 4) /* a ^X interrupted the current expansion */ - /* it's set only iff N_ADDS is set */ -# define CONT_N_ADDS 4 /* next ^X<> will add-new or expand-current */ -# define CONT_S_IPOS 8 /* next ^X<> will set initial_pos? - * if so, word-wise-expansion will set SOL */ -# define CONT_SOL 16 /* pattern includes start of line, just for - * word-wise expansion, not set for ^X^L */ -# define CONT_LOCAL 32 /* for ctrl_x_mode 0, ^X^P/^X^N do a local - * expansion, (eg use complete=.) */ +# define CONT_ADDING 1 // "normal" or "adding" expansion +# define CONT_INTRPT (2 + 4) // a ^X interrupted the current expansion + // it's set only iff N_ADDS is set +# define CONT_N_ADDS 4 // next ^X<> will add-new or expand-current +# define CONT_S_IPOS 8 // next ^X<> will set initial_pos? + // if so, word-wise-expansion will set SOL +# define CONT_SOL 16 // pattern includes start of line, just for + // word-wise expansion, not set for ^X^L +# define CONT_LOCAL 32 // for ctrl_x_mode 0, ^X^P/^X^N do a local + // expansion, (eg use complete=.) // state for putting characters in the message area EXTERN int cmdmsg_rl INIT(= false); // cmdline is drawn right to left @@ -200,49 +192,49 @@ EXTERN bool msg_scrolled_ign INIT(= false); 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 int keep_msg_more INIT(= FALSE); /* keep_msg was set by msgmore() */ -EXTERN int need_fileinfo INIT(= FALSE); /* do fileinfo() after redraw */ -EXTERN int msg_scroll INIT(= FALSE); /* msg_start() will scroll */ -EXTERN int msg_didout INIT(= FALSE); /* msg_outstr() was used in line */ -EXTERN int msg_didany INIT(= FALSE); /* msg_outstr() was used at all */ -EXTERN int msg_nowait INIT(= FALSE); /* don't wait for this msg */ -EXTERN int emsg_off INIT(= 0); /* don't display errors for now, - unless 'debug' is set. */ -EXTERN int info_message INIT(= FALSE); /* printing informative message */ -EXTERN int msg_hist_off INIT(= FALSE); /* don't add messages to history */ -EXTERN int need_clr_eos INIT(= FALSE); /* need to clear text before - displaying a message. */ -EXTERN int emsg_skip INIT(= 0); /* don't display errors for - expression that is skipped */ -EXTERN int emsg_severe INIT(= FALSE); /* use message of next of several - emsg() calls for throw */ -EXTERN int did_endif INIT(= FALSE); /* just had ":endif" */ -EXTERN dict_T vimvardict; /* Dictionary with v: variables */ -EXTERN dict_T globvardict; /* Dictionary with g: variables */ -EXTERN int did_emsg; /* set by emsg() when the message - is displayed or thrown */ +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 int keep_msg_more INIT(= false); // keep_msg was set by msgmore() +EXTERN int need_fileinfo INIT(= false); // do fileinfo() after redraw +EXTERN int msg_scroll INIT(= false); // msg_start() will scroll +EXTERN int msg_didout INIT(= false); // msg_outstr() was used in line +EXTERN int msg_didany INIT(= false); // msg_outstr() was used at all +EXTERN int msg_nowait INIT(= false); // don't wait for this msg +EXTERN int emsg_off INIT(= 0); // don't display errors for now, + // unless 'debug' is set. +EXTERN int info_message INIT(= false); // printing informative message +EXTERN int msg_hist_off INIT(= false); // don't add messages to history +EXTERN int need_clr_eos INIT(= false); // need to clear text before + // displaying a message. +EXTERN int emsg_skip INIT(= 0); // don't display errors for + // expression that is skipped +EXTERN int emsg_severe INIT(= false); // use message of next of several + // emsg() calls for throw +EXTERN int did_endif INIT(= false); // just had ":endif" +EXTERN dict_T vimvardict; // Dictionary with v: variables +EXTERN dict_T globvardict; // Dictionary with g: variables +EXTERN int did_emsg; // set by emsg() when the message + // is displayed or thrown EXTERN bool called_vim_beep; // set if vim_beep() is called -EXTERN int did_emsg_syntax; /* did_emsg set because of a - syntax error */ -EXTERN int called_emsg; /* always set by emsg() */ -EXTERN int ex_exitval INIT(= 0); /* exit value for ex mode */ -EXTERN int emsg_on_display INIT(= FALSE); /* there is an error message */ -EXTERN int rc_did_emsg INIT(= FALSE); /* vim_regcomp() called emsg() */ - -EXTERN int no_wait_return INIT(= 0); /* don't wait for return for now */ -EXTERN int need_wait_return INIT(= 0); /* need to wait for return later */ -EXTERN int did_wait_return INIT(= FALSE); /* wait_return() was used and - nothing written since then */ -EXTERN int need_maketitle INIT(= TRUE); /* call maketitle() soon */ +EXTERN int did_emsg_syntax; // did_emsg set because of a + // syntax error +EXTERN int called_emsg; // always set by emsg() +EXTERN int ex_exitval INIT(= 0); // exit value for ex mode +EXTERN int emsg_on_display INIT(= false); // there is an error message +EXTERN int rc_did_emsg INIT(= false); // vim_regcomp() called emsg() + +EXTERN int no_wait_return INIT(= 0); // don't wait for return for now +EXTERN int need_wait_return INIT(= 0); // need to wait for return later +EXTERN int did_wait_return INIT(= false); // wait_return() was used and + // nothing written since then +EXTERN int need_maketitle INIT(= true); // call maketitle() soon EXTERN int quit_more INIT(= false); // 'q' hit at "--more--" msg EXTERN int ex_keep_indent INIT(= false); // getexmodeline(): keep indent EXTERN int vgetc_busy INIT(= 0); // when inside vgetc() then > 0 -EXTERN int didset_vim INIT(= FALSE); /* did set $VIM ourselves */ -EXTERN int didset_vimruntime INIT(= FALSE); /* idem for $VIMRUNTIME */ +EXTERN int didset_vim INIT(= false); // did set $VIM ourselves +EXTERN int didset_vimruntime INIT(= false); // idem for $VIMRUNTIME /// Lines left before a "more" message. Ex mode needs to be able to reset this /// after you type something. @@ -250,8 +242,8 @@ EXTERN int lines_left INIT(= -1); // lines left for listing EXTERN int msg_no_more INIT(= false); // don't use more prompt, truncate // messages -EXTERN char_u *sourcing_name INIT( = NULL); /* name of error message source */ -EXTERN linenr_T sourcing_lnum INIT(= 0); /* line number of the source file */ +EXTERN char_u *sourcing_name INIT(= NULL); // name of error message source +EXTERN linenr_T sourcing_lnum INIT(= 0); // line number of the source file EXTERN int ex_nesting_level INIT(= 0); // nesting level EXTERN int debug_break_level INIT(= -1); // break below this level @@ -282,11 +274,11 @@ EXTERN int check_cstack INIT(= false); /// commands). EXTERN int trylevel INIT(= 0); -/// When "force_abort" is TRUE, always skip commands after an error message, +/// When "force_abort" is true, always skip commands after an error message, /// even after the outermost ":endif", ":endwhile" or ":endfor" or for a -/// function without the "abort" flag. It is set to TRUE when "trylevel" is +/// function without the "abort" flag. It is set to true when "trylevel" is /// non-zero (and ":silent!" was not used) or an exception is being thrown at -/// the time an error is detected. It is set to FALSE when "trylevel" gets +/// the time an error is detected. It is set to false when "trylevel" gets /// zero again and there was no error or interrupt or throw. EXTERN int force_abort INIT(= false); @@ -357,41 +349,38 @@ EXTERN int provider_call_nesting INIT(= 0); EXTERN int t_colors INIT(= 256); // int value of T_CCO -/* - * When highlight_match is TRUE, highlight a match, starting at the cursor - * position. Search_match_lines is the number of lines after the match (0 for - * a match within one line), search_match_endcol the column number of the - * character just after the match in the last line. - */ -EXTERN int highlight_match INIT(= FALSE); /* show search match pos */ -EXTERN linenr_T search_match_lines; /* lines of of matched string */ -EXTERN colnr_T search_match_endcol; /* col nr of match end */ - -EXTERN int no_smartcase INIT(= FALSE); /* don't use 'smartcase' once */ - -EXTERN int need_check_timestamps INIT(= FALSE); /* need to check file - timestamps asap */ -EXTERN int did_check_timestamps INIT(= FALSE); /* did check timestamps - recently */ -EXTERN int no_check_timestamps INIT(= 0); /* Don't check timestamps */ - -EXTERN int autocmd_busy INIT(= FALSE); /* Is apply_autocmds() busy? */ -EXTERN int autocmd_no_enter INIT(= FALSE); /* *Enter autocmds disabled */ -EXTERN int autocmd_no_leave INIT(= FALSE); /* *Leave autocmds disabled */ -EXTERN int modified_was_set; /* did ":set modified" */ -EXTERN int did_filetype INIT(= FALSE); /* FileType event found */ -EXTERN int keep_filetype INIT(= FALSE); /* value for did_filetype when - starting to execute - autocommands */ +// When highlight_match is true, highlight a match, starting at the cursor +// position. Search_match_lines is the number of lines after the match (0 for +// a match within one line), search_match_endcol the column number of the +// character just after the match in the last line. +EXTERN int highlight_match INIT(= false); // show search match pos +EXTERN linenr_T search_match_lines; // lines of of matched string +EXTERN colnr_T search_match_endcol; // col nr of match end + +EXTERN int no_smartcase INIT(= false); // don't use 'smartcase' once + +EXTERN int need_check_timestamps INIT(= false); // need to check file + // timestamps asap +EXTERN int did_check_timestamps INIT(= false); // did check timestamps + // recently +EXTERN int no_check_timestamps INIT(= 0); // Don't check timestamps + +EXTERN int autocmd_busy INIT(= false); // Is apply_autocmds() busy? +EXTERN int autocmd_no_enter INIT(= false); // *Enter autocmds disabled +EXTERN int autocmd_no_leave INIT(= false); // *Leave autocmds disabled +EXTERN int modified_was_set; // did ":set modified" +EXTERN int did_filetype INIT(= false); // FileType event found +// value for did_filetype when starting to execute autocommands +EXTERN int keep_filetype INIT(= false); // When deleting the current buffer, another one must be loaded. // If we know which one is preferred, au_new_curbuf is set to it. EXTERN bufref_T au_new_curbuf INIT(= { NULL, 0, 0 }); -// When deleting a buffer/window and autocmd_busy is TRUE, do not free the +// When deleting a buffer/window and autocmd_busy is true, do not free the // buffer/window. but link it in the list starting with // au_pending_free_buf/ap_pending_free_win, using b_next/w_next. -// Free the buffer/window when autocmd_busy is being set to FALSE. +// Free the buffer/window when autocmd_busy is being set to false. EXTERN buf_T *au_pending_free_buf INIT(= NULL); EXTERN win_T *au_pending_free_win INIT(= NULL); @@ -399,31 +388,27 @@ EXTERN win_T *au_pending_free_win INIT(= NULL); EXTERN int mouse_grid; EXTERN int mouse_row; EXTERN int mouse_col; -EXTERN bool mouse_past_bottom INIT(= false); /* mouse below last line */ -EXTERN bool mouse_past_eol INIT(= false); /* mouse right of line */ -EXTERN int mouse_dragging INIT(= 0); /* extending Visual area with - mouse dragging */ +EXTERN bool mouse_past_bottom INIT(= false); // mouse below last line +EXTERN bool mouse_past_eol INIT(= false); // mouse right of line +EXTERN int mouse_dragging INIT(= 0); // extending Visual area with + // mouse dragging -/* The root of the menu hierarchy. */ +// The root of the menu hierarchy. EXTERN vimmenu_T *root_menu INIT(= NULL); -/* - * While defining the system menu, sys_menu is TRUE. This avoids - * overruling of menus that the user already defined. - */ -EXTERN int sys_menu INIT(= FALSE); - -/* While redrawing the screen this flag is set. It means the screen size - * ('lines' and 'rows') must not be changed. */ -EXTERN int updating_screen INIT(= FALSE); - -/* - * All windows are linked in a list. firstwin points to the first entry, - * lastwin to the last entry (can be the same as firstwin) and curwin to the - * currently active window. - */ -EXTERN win_T *firstwin; /* first window */ -EXTERN win_T *lastwin; /* last window */ -EXTERN win_T *prevwin INIT(= NULL); /* previous window */ +// While defining the system menu, sys_menu is true. This avoids +// overruling of menus that the user already defined. +EXTERN int sys_menu INIT(= false); + +// While redrawing the screen this flag is set. It means the screen size +// ('lines' and 'rows') must not be changed. +EXTERN int updating_screen INIT(= 0); + +// All windows are linked in a list. firstwin points to the first entry, +// lastwin to the last entry (can be the same as firstwin) and curwin to the +// currently active window. +EXTERN win_T *firstwin; // first window +EXTERN win_T *lastwin; // last window +EXTERN win_T *prevwin INIT(= NULL); // previous window # define ONE_WINDOW (firstwin == lastwin) # define FOR_ALL_FRAMES(frp, first_frame) \ for (frp = first_frame; frp != NULL; frp = frp->fr_next) // NOLINT @@ -439,33 +424,27 @@ EXTERN win_T *prevwin INIT(= NULL); /* previous window */ for (win_T *wp = ((tp) == curtab) \ ? firstwin : (tp)->tp_firstwin; wp != NULL; wp = wp->w_next) -EXTERN win_T *curwin; /* currently active window */ +EXTERN win_T *curwin; // currently active window -EXTERN win_T *aucmd_win; /* window used in aucmd_prepbuf() */ -EXTERN int aucmd_win_used INIT(= FALSE); /* aucmd_win is being used */ +EXTERN win_T *aucmd_win; // window used in aucmd_prepbuf() +EXTERN int aucmd_win_used INIT(= false); // aucmd_win is being used -/* - * The window layout is kept in a tree of frames. topframe points to the top - * of the tree. - */ -EXTERN frame_T *topframe; /* top of the window frame tree */ +// The window layout is kept in a tree of frames. topframe points to the top +// of the tree. +EXTERN frame_T *topframe; // top of the window frame tree -/* - * Tab pages are alternative topframes. "first_tabpage" points to the first - * one in the list, "curtab" is the current one. - */ +// Tab pages are alternative topframes. "first_tabpage" points to the first +// one in the list, "curtab" is the current one. EXTERN tabpage_T *first_tabpage; EXTERN tabpage_T *lastused_tabpage; EXTERN tabpage_T *curtab; -EXTERN int redraw_tabline INIT(= FALSE); /* need to redraw tabline */ +EXTERN int redraw_tabline INIT(= false); // need to redraw tabline // Iterates over all tabs in the tab list # define FOR_ALL_TABS(tp) for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) -/* - * All buffers are linked in a list. 'firstbuf' points to the first entry, - * 'lastbuf' to the last entry and 'curbuf' to the currently active buffer. - */ +// All buffers are linked in a list. 'firstbuf' points to the first entry, +// 'lastbuf' to the last entry and 'curbuf' to the currently active buffer. EXTERN buf_T *firstbuf INIT(= NULL); // first buffer EXTERN buf_T *lastbuf INIT(= NULL); // last buffer EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer @@ -481,23 +460,19 @@ EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer for (sign = buf->b_signlist; sign != NULL; sign = sign->next) // NOLINT -/* - * List of files being edited (global argument list). curwin->w_alist points - * to this when the window is using the global argument list. - */ -EXTERN alist_T global_alist; /* global argument list */ +// List of files being edited (global argument list). curwin->w_alist points +// to this when the window is using the global argument list. +EXTERN alist_T global_alist; // global argument list EXTERN int max_alist_id INIT(= 0); ///< the previous argument list id EXTERN bool arg_had_last INIT(= false); // accessed last file in // global_alist -EXTERN int ru_col; /* column for ruler */ -EXTERN int ru_wid; /* 'rulerfmt' width of ruler when non-zero */ -EXTERN int sc_col; /* column for shown command */ +EXTERN int ru_col; // column for ruler +EXTERN int ru_wid; // 'rulerfmt' width of ruler when non-zero +EXTERN int sc_col; // column for shown command -// // When starting or exiting some things are done differently (e.g. screen // updating). -// // First NO_SCREEN, then NO_BUFFERS, then 0 when startup finished. EXTERN int starting INIT(= NO_SCREEN); @@ -546,98 +521,78 @@ EXTERN int VIsual_select INIT(= false); EXTERN int VIsual_reselect; /// Type of Visual mode. EXTERN int VIsual_mode INIT(= 'v'); -/// TRUE when redoing Visual. +/// true when redoing Visual. EXTERN int redo_VIsual_busy INIT(= false); /// When pasting text with the middle mouse button in visual mode with /// restart_edit set, remember where it started so we can set Insstart. EXTERN pos_T where_paste_started; -/* - * This flag is used to make auto-indent work right on lines where only a - * <RETURN> or <ESC> is typed. It is set when an auto-indent is done, and - * reset when any other editing is done on the line. If an <ESC> or <RETURN> - * is received, and did_ai is TRUE, the line is truncated. - */ +// This flag is used to make auto-indent work right on lines where only a +// <RETURN> or <ESC> is typed. It is set when an auto-indent is done, and +// reset when any other editing is done on the line. If an <ESC> or <RETURN> +// is received, and did_ai is true, the line is truncated. EXTERN bool did_ai INIT(= false); -/* - * Column of first char after autoindent. 0 when no autoindent done. Used - * when 'backspace' is 0, to avoid backspacing over autoindent. - */ +// Column of first char after autoindent. 0 when no autoindent done. Used +// when 'backspace' is 0, to avoid backspacing over autoindent. EXTERN colnr_T ai_col INIT(= 0); -/* - * This is a character which will end a start-middle-end comment when typed as - * the first character on a new line. It is taken from the last character of - * the "end" comment leader when the COM_AUTO_END flag is given for that - * comment end in 'comments'. It is only valid when did_ai is TRUE. - */ +// This is a character which will end a start-middle-end comment when typed as +// the first character on a new line. It is taken from the last character of +// the "end" comment leader when the COM_AUTO_END flag is given for that +// comment end in 'comments'. It is only valid when did_ai is true. EXTERN int end_comment_pending INIT(= NUL); -/* - * This flag is set after a ":syncbind" to let the check_scrollbind() function - * know that it should not attempt to perform scrollbinding due to the scroll - * that was a result of the ":syncbind." (Otherwise, check_scrollbind() will - * undo some of the work done by ":syncbind.") -ralston - */ -EXTERN int did_syncbind INIT(= FALSE); - -/* - * This flag is set when a smart indent has been performed. When the next typed - * character is a '{' the inserted tab will be deleted again. - */ +// This flag is set after a ":syncbind" to let the check_scrollbind() function +// know that it should not attempt to perform scrollbinding due to the scroll +// that was a result of the ":syncbind." (Otherwise, check_scrollbind() will +// undo some of the work done by ":syncbind.") -ralston +EXTERN int did_syncbind INIT(= false); + +// This flag is set when a smart indent has been performed. When the next typed +// character is a '{' the inserted tab will be deleted again. EXTERN bool did_si INIT(= false); -/* - * This flag is set after an auto indent. If the next typed character is a '}' - * one indent will be removed. - */ +// This flag is set after an auto indent. If the next typed character is a '}' +// one indent will be removed. EXTERN bool can_si INIT(= false); -/* - * This flag is set after an "O" command. If the next typed character is a '{' - * one indent will be removed. - */ +// This flag is set after an "O" command. If the next typed character is a '{' +// one indent will be removed. EXTERN bool can_si_back INIT(= false); // w_cursor before formatting text. EXTERN pos_T saved_cursor INIT(= { 0, 0, 0 }); -/* - * Stuff for insert mode. - */ -EXTERN pos_T Insstart; /* This is where the latest - * insert/append mode started. */ +// Stuff for insert mode. +EXTERN pos_T Insstart; // This is where the latest + // insert/append mode started. // This is where the latest insert/append mode started. In contrast to // Insstart, this won't be reset by certain keys and is needed for // op_insert(), to detect correctly where inserting by the user started. EXTERN pos_T Insstart_orig; -/* - * Stuff for VREPLACE mode. - */ -EXTERN int orig_line_count INIT(= 0); /* Line count when "gR" started */ -EXTERN int vr_lines_changed INIT(= 0); /* #Lines changed by "gR" so far */ +// Stuff for VREPLACE mode. +EXTERN int orig_line_count INIT(= 0); // Line count when "gR" started +EXTERN int vr_lines_changed INIT(= 0); // #Lines changed by "gR" so far // increase around internal delete/replace EXTERN int inhibit_delete_count INIT(= 0); -/* - * These flags are set based upon 'fileencoding'. - * Note that "enc_utf8" is also set for "unicode", because the characters are - * internally stored as UTF-8 (to avoid trouble with NUL bytes). - */ -# define DBCS_JPN 932 /* japan */ -# define DBCS_JPNU 9932 /* euc-jp */ -# define DBCS_KOR 949 /* korea */ -# define DBCS_KORU 9949 /* euc-kr */ -# define DBCS_CHS 936 /* chinese */ -# define DBCS_CHSU 9936 /* euc-cn */ -# define DBCS_CHT 950 /* taiwan */ -# define DBCS_CHTU 9950 /* euc-tw */ -# define DBCS_2BYTE 1 /* 2byte- */ +// These flags are set based upon 'fileencoding'. +// Note that "enc_utf8" is also set for "unicode", because the characters are +// internally stored as UTF-8 (to avoid trouble with NUL bytes). +# define DBCS_JPN 932 // japan +# define DBCS_JPNU 9932 // euc-jp +# define DBCS_KOR 949 // korea +# define DBCS_KORU 9949 // euc-kr +# define DBCS_CHS 936 // chinese +# define DBCS_CHSU 9936 // euc-cn +# define DBCS_CHT 950 // taiwan +# define DBCS_CHTU 9950 // euc-tw +# define DBCS_2BYTE 1 // 2byte- # define DBCS_DEBUG -1 // mbyte flags that used to depend on 'encoding'. These are now deprecated, as @@ -678,40 +633,40 @@ EXTERN int u_sync_once INIT(= 0); // Call u_sync() once when evaluating EXTERN bool force_restart_edit INIT(= false); // force restart_edit after // ex_normal returns -EXTERN int restart_edit INIT(= 0); /* call edit when next cmd finished */ -EXTERN int arrow_used; /* Normally FALSE, set to TRUE after - * hitting cursor key in insert mode. - * Used by vgetorpeek() to decide when - * to call u_sync() */ -EXTERN int ins_at_eol INIT(= FALSE); /* put cursor after eol when - restarting edit after CTRL-O */ +EXTERN int restart_edit INIT(= 0); // call edit when next cmd finished +EXTERN int arrow_used; // Normally false, set to true after + // hitting cursor key in insert mode. + // Used by vgetorpeek() to decide when + // to call u_sync() +EXTERN int ins_at_eol INIT(= false); // put cursor after eol when + // restarting edit after CTRL-O EXTERN char_u *edit_submode INIT(= NULL); // msg for CTRL-X submode EXTERN char_u *edit_submode_pre INIT(= NULL); // prepended to edit_submode EXTERN char_u *edit_submode_extra INIT(= NULL); // appended to edit_submode EXTERN hlf_T edit_submode_highl; // highl. method for extra info -EXTERN int no_abbr INIT(= TRUE); /* TRUE when no abbreviations loaded */ +EXTERN int no_abbr INIT(= true); // true when no abbreviations loaded EXTERN int mapped_ctrl_c INIT(= 0); // Modes where CTRL-C is mapped. -EXTERN cmdmod_T cmdmod; /* Ex command modifiers */ +EXTERN cmdmod_T cmdmod; // Ex command modifiers EXTERN int msg_silent INIT(= 0); // don't print messages EXTERN int emsg_silent INIT(= 0); // don't print error messages EXTERN bool emsg_noredir INIT(= false); // don't redirect error messages EXTERN bool cmd_silent INIT(= false); // don't echo the command line -/* Values for swap_exists_action: what to do when swap file already exists */ -#define SEA_NONE 0 /* don't use dialog */ -#define SEA_DIALOG 1 /* use dialog when possible */ -#define SEA_QUIT 2 /* quit editing the file */ -#define SEA_RECOVER 3 /* recover the file */ +// Values for swap_exists_action: what to do when swap file already exists +#define SEA_NONE 0 // don't use dialog +#define SEA_DIALOG 1 // use dialog when possible +#define SEA_QUIT 2 // quit editing the file +#define SEA_RECOVER 3 // recover the file EXTERN int swap_exists_action INIT(= SEA_NONE); -/* For dialog when swap file already - * exists. */ -EXTERN int swap_exists_did_quit INIT(= FALSE); -/* Selected "quit" at the dialog. */ +// For dialog when swap file already +// exists. +EXTERN int swap_exists_did_quit INIT(= false); +// Selected "quit" at the dialog. EXTERN char_u IObuff[IOSIZE]; ///< Buffer for sprintf, I/O, etc. EXTERN char_u NameBuff[MAXPATHL]; ///< Buffer for expanding file names @@ -724,11 +679,11 @@ IOSIZE #endif ]; -/* When non-zero, postpone redrawing. */ +// When non-zero, postpone redrawing. EXTERN int RedrawingDisabled INIT(= 0); -EXTERN int readonlymode INIT(= FALSE); /* Set to TRUE for "view" */ -EXTERN int recoverymode INIT(= FALSE); /* Set to TRUE for "-r" option */ +EXTERN int readonlymode INIT(= false); // Set to true for "view" +EXTERN int recoverymode INIT(= false); // Set to true for "-r" option // typeahead buffer EXTERN typebuf_T typebuf INIT(= { NULL, NULL, 0, 0, 0, 0, 0, 0, 0 }); @@ -738,7 +693,7 @@ EXTERN int ex_normal_lock INIT(= 0); // forbid use of ex_normal() EXTERN int ignore_script INIT(= false); // ignore script input EXTERN int stop_insert_mode; // for ":stopinsert" and 'insertmode' EXTERN bool KeyTyped; // true if user typed current char -EXTERN int KeyStuffed; // TRUE if current char from stuffbuf +EXTERN int KeyStuffed; // true if current char from stuffbuf EXTERN int maptick INIT(= 0); // tick for each non-mapped char EXTERN int must_redraw INIT(= 0); // type of redraw necessary @@ -754,15 +709,15 @@ EXTERN FILE *scriptout INIT(= NULL); ///< Stream to write script to. // volatile because it is used in a signal handler. EXTERN volatile int got_int INIT(= false); // set to true when interrupt // signal occurred -EXTERN int bangredo INIT(= FALSE); /* set to TRUE with ! command */ -EXTERN int searchcmdlen; /* length of previous search cmd */ -EXTERN int reg_do_extmatch INIT(= 0); /* Used when compiling regexp: - * REX_SET to allow \z\(...\), - * REX_USE to allow \z\1 et al. */ -EXTERN reg_extmatch_T *re_extmatch_in INIT(= NULL); /* Used by vim_regexec(): - * strings for \z\1...\z\9 */ -EXTERN reg_extmatch_T *re_extmatch_out INIT(= NULL); /* Set by vim_regexec() - * to store \z\(...\) matches */ +EXTERN int bangredo INIT(= false); // set to true with ! command +EXTERN int searchcmdlen; // length of previous search cmd +EXTERN int reg_do_extmatch INIT(= 0); // Used when compiling regexp: + // REX_SET to allow \z\(...\), + // REX_USE to allow \z\1 et al. +// Used by vim_regexec(): strings for \z\1...\z\9 +EXTERN reg_extmatch_T *re_extmatch_in INIT(= NULL); +// Set by vim_regexec() to store \z\(...\) matches +EXTERN reg_extmatch_T *re_extmatch_out INIT(= NULL); EXTERN int did_outofmem_msg INIT(= false); // set after out of memory msg @@ -781,11 +736,11 @@ EXTERN int autocmd_bufnr INIT(= 0); // fnum for <abuf> on cmdline EXTERN char_u *autocmd_match INIT(= NULL); // name for <amatch> on cmdline EXTERN int did_cursorhold INIT(= false); // set when CursorHold t'gerd -EXTERN int postponed_split INIT(= 0); /* for CTRL-W CTRL-] command */ -EXTERN int postponed_split_flags INIT(= 0); /* args for win_split() */ -EXTERN int postponed_split_tab INIT(= 0); /* cmdmod.tab */ -EXTERN int g_do_tagpreview INIT(= 0); /* for tag preview commands: - height of preview window */ +EXTERN int postponed_split INIT(= 0); // for CTRL-W CTRL-] command +EXTERN int postponed_split_flags INIT(= 0); // args for win_split() +EXTERN int postponed_split_tab INIT(= 0); // cmdmod.tab +EXTERN int g_do_tagpreview INIT(= 0); // for tag preview commands: + // height of preview window EXTERN int g_tag_at_cursor INIT(= false); // whether the tag command comes // from the command line (0) or was // invoked as a normal command (1) @@ -793,15 +748,13 @@ EXTERN int g_tag_at_cursor INIT(= false); // whether the tag command comes EXTERN int replace_offset INIT(= 0); // offset for replace_push() EXTERN char_u *escape_chars INIT(= (char_u *)" \t\\\"|"); -/* need backslash in cmd line */ +// need backslash in cmd line -EXTERN int keep_help_flag INIT(= FALSE); /* doing :ta from help file */ +EXTERN int keep_help_flag INIT(= false); // doing :ta from help file -/* - * When a string option is NULL (which only happens in out-of-memory - * situations), it is set to empty_option, to avoid having to check for NULL - * everywhere. - */ +// When a string option is NULL (which only happens in out-of-memory +// situations), it is set to empty_option, to avoid having to check for NULL +// everywhere. EXTERN char_u *empty_option INIT(= (char_u *)""); EXTERN int redir_off INIT(= false); // no redirection for a moment @@ -810,10 +763,10 @@ EXTERN int redir_reg INIT(= 0); // message redirection register EXTERN int redir_vname INIT(= 0); // message redirection variable EXTERN garray_T *capture_ga INIT(= NULL); // captured output for execute() -EXTERN char_u langmap_mapchar[256]; /* mapping for language keys */ +EXTERN char_u langmap_mapchar[256]; // mapping for language keys -EXTERN int save_p_ls INIT(= -1); /* Save 'laststatus' setting */ -EXTERN int save_p_wmh INIT(= -1); /* Save 'winminheight' setting */ +EXTERN int save_p_ls INIT(= -1); // Save 'laststatus' setting +EXTERN int save_p_wmh INIT(= -1); // Save 'winminheight' setting EXTERN int wild_menu_showing INIT(= 0); enum { WM_SHOWN = 1, ///< wildmenu showing @@ -822,10 +775,8 @@ enum { }; -/* - * Some file names are stored in pathdef.c, which is generated from the - * Makefile to make their value depend on the Makefile. - */ +// Some file names are stored in pathdef.c, which is generated from the +// Makefile to make their value depend on the Makefile. #ifdef HAVE_PATHDEF extern char *default_vim_dir; extern char *default_vimruntime_dir; @@ -834,14 +785,14 @@ extern char_u *compiled_user; extern char_u *compiled_sys; #endif -/* When a window has a local directory, the absolute path of the global - * current directory is stored here (in allocated memory). If the current - * directory is not a local directory, globaldir is NULL. */ +// When a window has a local directory, the absolute path of the global +// current directory is stored here (in allocated memory). If the current +// directory is not a local directory, globaldir is NULL. EXTERN char_u *globaldir INIT(= NULL); -/* Whether 'keymodel' contains "stopsel" and "startsel". */ -EXTERN int km_stopsel INIT(= FALSE); -EXTERN int km_startsel INIT(= FALSE); +// Whether 'keymodel' contains "stopsel" and "startsel". +EXTERN int km_stopsel INIT(= false); +EXTERN int km_startsel INIT(= false); EXTERN int cedit_key INIT(= -1); ///< key value of 'cedit' option EXTERN int cmdwin_type INIT(= 0); ///< type of cmdline window or 0 @@ -850,18 +801,16 @@ EXTERN int cmdwin_level INIT(= 0); ///< cmdline recursion level EXTERN char_u no_lines_msg[] INIT(= N_("--No lines in buffer--")); -/* - * When ":global" is used to number of substitutions and changed lines is - * accumulated until it's finished. - * Also used for ":spellrepall". - */ -EXTERN long sub_nsubs; /* total number of substitutions */ -EXTERN linenr_T sub_nlines; /* total number of lines changed */ +// When ":global" is used to number of substitutions and changed lines is +// accumulated until it's finished. +// Also used for ":spellrepall". +EXTERN long sub_nsubs; // total number of substitutions +EXTERN linenr_T sub_nlines; // total number of lines changed -/* table to store parsed 'wildmode' */ +// table to store parsed 'wildmode' EXTERN char_u wim_flags[4]; -/* whether titlestring and iconstring contains statusline syntax */ +// whether titlestring and iconstring contains statusline syntax # define STL_IN_ICON 1 # define STL_IN_TITLE 2 EXTERN int stl_syntax INIT(= 0); @@ -869,7 +818,7 @@ EXTERN int stl_syntax INIT(= 0); // don't use 'hlsearch' temporarily EXTERN bool no_hlsearch INIT(= false); -/* Page number used for %N in 'pageheader' and 'guitablabel'. */ +// Page number used for %N in 'pageheader' and 'guitablabel'. EXTERN linenr_T printer_page_num; @@ -887,18 +836,16 @@ EXTERN char pseps[2] INIT(= { '\\', 0 }); // normal path separator string // kNone when no operator is being executed, kFalse otherwise. EXTERN TriState virtual_op INIT(= kNone); -/* Display tick, incremented for each call to update_screen() */ +// Display tick, incremented for each call to update_screen() EXTERN disptick_T display_tick INIT(= 0); -/* Line in which spell checking wasn't highlighted because it touched the - * cursor position in Insert mode. */ +// Line in which spell checking wasn't highlighted because it touched the +// cursor position in Insert mode. EXTERN linenr_T spell_redraw_lnum INIT(= 0); -/* - * The error messages that can be shared are included here. - * Excluded are errors that are only used once and debugging messages. - */ +// The error messages that can be shared are included here. +// Excluded are errors that are only used once and debugging messages. EXTERN char_u e_abort[] INIT(= N_("E470: Command aborted")); EXTERN char_u e_afterinit[] INIT(= N_( "E905: Cannot set this option after startup")); @@ -1070,7 +1017,7 @@ EXTERN char line_msg[] INIT(= N_(" line ")); // For undo we need to know the lowest time possible. EXTERN time_t starttime; -EXTERN FILE *time_fd INIT(= NULL); /* where to write startup timing */ +EXTERN FILE *time_fd INIT(= NULL); // where to write startup timing // Some compilers warn for not using a return value, but in some situations we // can't do anything useful with the value. Assign to this variable to avoid @@ -1106,4 +1053,4 @@ typedef enum { #define MIN_CD_SCOPE kCdScopeWindow #define MAX_CD_SCOPE kCdScopeGlobal -#endif /* NVIM_GLOBALS_H */ +#endif // NVIM_GLOBALS_H diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index 67a7e58ed7..bb443161ef 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -3372,11 +3372,9 @@ term_again: continue; } - /* - * Are we at the start of a cpp base class declaration or - * constructor initialization? - */ /* XXX */ - n = false; + // Are we at the start of a cpp base class declaration or + // constructor initialization? XXX + n = 0; if (curbuf->b_ind_cpp_baseclass != 0 && theline[0] != '{') { n = cin_is_cpp_baseclass(&cache_cpp_baseclass); l = get_cursor_line_ptr(); @@ -3409,7 +3407,6 @@ term_again: * } foo, * bar; */ - n = 0; if (cin_ends_in(l, (char_u *)",", NULL) || (*l != NUL && (n = l[STRLEN(l) - 1]) == '\\')) { /* take us back to opening paren */ diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 9e0063ebaa..9167ec5e54 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -11,6 +11,7 @@ #include "nvim/func_attr.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/private/handle.h" #include "nvim/api/vim.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/vim.h" @@ -19,6 +20,7 @@ #include "nvim/message.h" #include "nvim/memline.h" #include "nvim/buffer_defs.h" +#include "nvim/regexp.h" #include "nvim/macros.h" #include "nvim/screen.h" #include "nvim/cursor.h" @@ -244,6 +246,14 @@ static int nlua_schedule(lua_State *const lstate) return 0; } +static struct luaL_Reg regex_meta[] = { + { "__gc", regex_gc }, + { "__tostring", regex_tostring }, + { "match_str", regex_match_str }, + { "match_line", regex_match_line }, + { NULL, NULL } +}; + /// Initialize lua interpreter state /// /// Called by lua interpreter itself to initialize state. @@ -291,6 +301,15 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL // call lua_pushcfunction(lstate, &nlua_call); lua_setfield(lstate, -2, "call"); + // regex + lua_pushcfunction(lstate, &nlua_regex); + lua_setfield(lstate, -2, "regex"); + + luaL_newmetatable(lstate, "nvim_regex"); + luaL_register(lstate, NULL, regex_meta); + lua_pushvalue(lstate, -1); // [meta, meta] + lua_setfield(lstate, -2, "__index"); // [meta] + lua_pop(lstate, 1); // don't use metatable now // rpcrequest lua_pushcfunction(lstate, &nlua_rpcrequest); @@ -1037,3 +1056,136 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, ts_lua_parse_query); lua_setfield(lstate, -2, "_ts_parse_query"); } + +static int nlua_regex(lua_State *lstate) +{ + Error err = ERROR_INIT; + const char *text = luaL_checkstring(lstate, 1); + regprog_T *prog = NULL; + + TRY_WRAP({ + try_start(); + prog = vim_regcomp((char_u *)text, RE_AUTO | RE_MAGIC | RE_STRICT); + try_end(&err); + }); + + if (ERROR_SET(&err)) { + return luaL_error(lstate, "couldn't parse regex: %s", err.msg); + } + assert(prog); + + regprog_T **p = lua_newuserdata(lstate, sizeof(regprog_T *)); + *p = prog; + + lua_getfield(lstate, LUA_REGISTRYINDEX, "nvim_regex"); // [udata, meta] + lua_setmetatable(lstate, -2); // [udata] + return 1; +} + +static regprog_T **regex_check(lua_State *L) +{ + return luaL_checkudata(L, 1, "nvim_regex"); +} + + +static int regex_gc(lua_State *lstate) +{ + regprog_T **prog = regex_check(lstate); + vim_regfree(*prog); + return 0; +} + +static int regex_tostring(lua_State *lstate) +{ + lua_pushstring(lstate, "<regex>"); + return 1; +} + +static int regex_match(lua_State *lstate, regprog_T **prog, char_u *str) +{ + regmatch_T rm; + rm.regprog = *prog; + rm.rm_ic = false; + bool match = vim_regexec(&rm, str, 0); + *prog = rm.regprog; + + if (match) { + lua_pushinteger(lstate, (lua_Integer)(rm.startp[0]-str)); + lua_pushinteger(lstate, (lua_Integer)(rm.endp[0]-str)); + return 2; + } + return 0; +} + +static int regex_match_str(lua_State *lstate) +{ + regprog_T **prog = regex_check(lstate); + const char *str = luaL_checkstring(lstate, 2); + int nret = regex_match(lstate, prog, (char_u *)str); + + if (!*prog) { + return luaL_error(lstate, "regex: internal error"); + } + + return nret; +} + +static int regex_match_line(lua_State *lstate) +{ + regprog_T **prog = regex_check(lstate); + + int narg = lua_gettop(lstate); + if (narg < 3) { + return luaL_error(lstate, "not enough args"); + } + + long bufnr = luaL_checkinteger(lstate, 2); + long rownr = luaL_checkinteger(lstate, 3); + long start = 0, end = -1; + if (narg >= 4) { + start = luaL_checkinteger(lstate, 4); + } + if (narg >= 5) { + end = luaL_checkinteger(lstate, 5); + if (end < 0) { + return luaL_error(lstate, "invalid end"); + } + } + + buf_T *buf = bufnr ? handle_get_buffer((int)bufnr) : curbuf; + if (!buf || buf->b_ml.ml_mfp == NULL) { + return luaL_error(lstate, "invalid buffer"); + } + + if (rownr >= buf->b_ml.ml_line_count) { + return luaL_error(lstate, "invalid row"); + } + + char_u *line = ml_get_buf(buf, rownr+1, false); + size_t len = STRLEN(line); + + if (start < 0 || (size_t)start > len) { + return luaL_error(lstate, "invalid start"); + } + + char_u save = NUL; + if (end >= 0) { + if ((size_t)end > len || end < start) { + return luaL_error(lstate, "invalid end"); + } + save = line[end]; + line[end] = NUL; + } + + int nret = regex_match(lstate, prog, line+start); + + if (end >= 0) { + line[end] = save; + } + + if (!*prog) { + return luaL_error(lstate, "regex: internal error"); + } + + return nret; +} diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index a420f79ffd..4753df7b87 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -93,10 +93,7 @@ static PMap(cstr_t) *langs; static void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta) { if (luaL_newmetatable(L, tname)) { // [meta] - for (size_t i = 0; meta[i].name != NULL; i++) { - lua_pushcfunction(L, meta[i].func); // [meta, func] - lua_setfield(L, -2, meta[i].name); // [meta] - } + luaL_register(L, NULL, meta); lua_pushvalue(L, -1); // [meta, meta] lua_setfield(L, -2, "__index"); // [meta] diff --git a/src/nvim/main.c b/src/nvim/main.c index 56d9030a7f..4a9f2371a2 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -10,6 +10,7 @@ #include <msgpack.h> #include "nvim/ascii.h" +#include "nvim/channel.h" #include "nvim/vim.h" #include "nvim/main.h" #include "nvim/aucmd.h" diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c index 25b07366d7..6dd452b5af 100644 --- a/src/nvim/marktree.c +++ b/src/nvim/marktree.c @@ -849,7 +849,8 @@ bool marktree_splice(MarkTree *b, bool same_line = old_extent.row == 0 && new_extent.row == 0; unrelative(start, &old_extent); unrelative(start, &new_extent); - MarkTreeIter itr[1], enditr[1]; + MarkTreeIter itr[1] = { 0 }; + MarkTreeIter enditr[1] = { 0 }; mtpos_t oldbase[MT_MAX_DEPTH]; @@ -905,6 +906,7 @@ continue_same_node: refkey(b, itr->node, itr->i); refkey(b, enditr->node, enditr->i); } else { + past_right = true; // NOLINT break; } } @@ -1002,7 +1004,7 @@ void marktree_move_region(MarkTree *b, mtpos_t start = { start_row, start_col }, size = { extent_row, extent_col }; mtpos_t end = size; unrelative(start, &end); - MarkTreeIter itr[1]; + MarkTreeIter itr[1] = { 0 }; marktree_itr_get_ext(b, start, itr, false, true, NULL); kvec_t(mtkey_t) saved = KV_INITIAL_VALUE; while (itr->node) { diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 85e6697bfb..e67be60aa6 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -265,68 +265,70 @@ static struct { const char *name; int canon; } enc_alias_table[] = { - {"ansi", IDX_LATIN_1}, - {"iso-8859-1", IDX_LATIN_1}, - {"latin2", IDX_ISO_2}, - {"latin3", IDX_ISO_3}, - {"latin4", IDX_ISO_4}, - {"cyrillic", IDX_ISO_5}, - {"arabic", IDX_ISO_6}, - {"greek", IDX_ISO_7}, - {"hebrew", IDX_ISO_8}, - {"latin5", IDX_ISO_9}, - {"turkish", IDX_ISO_9}, /* ? */ - {"latin6", IDX_ISO_10}, - {"nordic", IDX_ISO_10}, /* ? */ - {"thai", IDX_ISO_11}, /* ? */ - {"latin7", IDX_ISO_13}, - {"latin8", IDX_ISO_14}, - {"latin9", IDX_ISO_15}, - {"utf8", IDX_UTF8}, - {"unicode", IDX_UCS2}, - {"ucs2", IDX_UCS2}, - {"ucs2be", IDX_UCS2}, - {"ucs-2be", IDX_UCS2}, - {"ucs2le", IDX_UCS2LE}, - {"utf16", IDX_UTF16}, - {"utf16be", IDX_UTF16}, - {"utf-16be", IDX_UTF16}, - {"utf16le", IDX_UTF16LE}, - {"ucs4", IDX_UCS4}, - {"ucs4be", IDX_UCS4}, - {"ucs-4be", IDX_UCS4}, - {"ucs4le", IDX_UCS4LE}, - {"utf32", IDX_UCS4}, - {"utf-32", IDX_UCS4}, - {"utf32be", IDX_UCS4}, - {"utf-32be", IDX_UCS4}, - {"utf32le", IDX_UCS4LE}, - {"utf-32le", IDX_UCS4LE}, - {"932", IDX_CP932}, - {"949", IDX_CP949}, - {"936", IDX_CP936}, - {"gbk", IDX_CP936}, - {"950", IDX_CP950}, - {"eucjp", IDX_EUC_JP}, - {"unix-jis", IDX_EUC_JP}, - {"ujis", IDX_EUC_JP}, - {"shift-jis", IDX_SJIS}, - {"pck", IDX_SJIS}, /* Sun: PCK */ - {"euckr", IDX_EUC_KR}, - {"5601", IDX_EUC_KR}, /* Sun: KS C 5601 */ - {"euccn", IDX_EUC_CN}, - {"gb2312", IDX_EUC_CN}, - {"euctw", IDX_EUC_TW}, - {"japan", IDX_EUC_JP}, - {"korea", IDX_EUC_KR}, - {"prc", IDX_EUC_CN}, - {"chinese", IDX_EUC_CN}, - {"taiwan", IDX_EUC_TW}, - {"cp950", IDX_BIG5}, - {"950", IDX_BIG5}, - {"mac", IDX_MACROMAN}, - {"mac-roman", IDX_MACROMAN}, - {NULL, 0} + { "ansi", IDX_LATIN_1 }, + { "iso-8859-1", IDX_LATIN_1 }, + { "latin2", IDX_ISO_2 }, + { "latin3", IDX_ISO_3 }, + { "latin4", IDX_ISO_4 }, + { "cyrillic", IDX_ISO_5 }, + { "arabic", IDX_ISO_6 }, + { "greek", IDX_ISO_7 }, + { "hebrew", IDX_ISO_8 }, + { "latin5", IDX_ISO_9 }, + { "turkish", IDX_ISO_9 }, // ? + { "latin6", IDX_ISO_10 }, + { "nordic", IDX_ISO_10 }, // ? + { "thai", IDX_ISO_11 }, // ? + { "latin7", IDX_ISO_13 }, + { "latin8", IDX_ISO_14 }, + { "latin9", IDX_ISO_15 }, + { "utf8", IDX_UTF8 }, + { "unicode", IDX_UCS2 }, + { "ucs2", IDX_UCS2 }, + { "ucs2be", IDX_UCS2 }, + { "ucs-2be", IDX_UCS2 }, + { "ucs2le", IDX_UCS2LE }, + { "utf16", IDX_UTF16 }, + { "utf16be", IDX_UTF16 }, + { "utf-16be", IDX_UTF16 }, + { "utf16le", IDX_UTF16LE }, + { "ucs4", IDX_UCS4 }, + { "ucs4be", IDX_UCS4 }, + { "ucs-4be", IDX_UCS4 }, + { "ucs4le", IDX_UCS4LE }, + { "utf32", IDX_UCS4 }, + { "utf-32", IDX_UCS4 }, + { "utf32be", IDX_UCS4 }, + { "utf-32be", IDX_UCS4 }, + { "utf32le", IDX_UCS4LE }, + { "utf-32le", IDX_UCS4LE }, + { "932", IDX_CP932 }, + { "949", IDX_CP949 }, + { "936", IDX_CP936 }, + { "gbk", IDX_CP936 }, + { "950", IDX_CP950 }, + { "eucjp", IDX_EUC_JP }, + { "unix-jis", IDX_EUC_JP }, + { "ujis", IDX_EUC_JP }, + { "shift-jis", IDX_SJIS }, + { "pck", IDX_SJIS }, // Sun: PCK + { "euckr", IDX_EUC_KR }, + { "5601", IDX_EUC_KR }, // Sun: KS C 5601 + { "euccn", IDX_EUC_CN }, + { "gb2312", IDX_EUC_CN }, + { "euctw", IDX_EUC_TW }, + { "japan", IDX_EUC_JP }, + { "korea", IDX_EUC_KR }, + { "prc", IDX_EUC_CN }, + { "zh-cn", IDX_EUC_CN }, + { "chinese", IDX_EUC_CN }, + { "zh-tw", IDX_EUC_TW }, + { "taiwan", IDX_EUC_TW }, + { "cp950", IDX_BIG5 }, + { "950", IDX_BIG5 }, + { "mac", IDX_MACROMAN }, + { "mac-roman", IDX_MACROMAN }, + { NULL, 0 } }; /* diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index e395654e04..32d8352d9b 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -73,6 +73,7 @@ int jump_to_mouse(int flags, int col = mouse_col; int grid = mouse_grid; int mouse_char; + int fdc = 0; mouse_past_bottom = false; mouse_past_eol = false; @@ -131,6 +132,7 @@ retnomove: if (wp == NULL) { return IN_UNKNOWN; } + fdc = win_fdccol_count(wp); dragwin = NULL; // winpos and height may change in win_enter()! if (grid == DEFAULT_GRID_HANDLE && row >= wp->w_height) { @@ -165,9 +167,8 @@ retnomove: || (!on_status_line && !on_sep_line && (wp->w_p_rl - ? col < wp->w_width_inner - wp->w_p_fdc - : col >= wp->w_p_fdc + (cmdwin_type == 0 && wp == curwin - ? 0 : 1)) + ? col < wp->w_width_inner - fdc + : col >= fdc + (cmdwin_type == 0 && wp == curwin ? 0 : 1)) && (flags & MOUSE_MAY_STOP_VIS)))) { end_visual_mode(); redraw_curbuf_later(INVERTED); // delete the inversion @@ -305,8 +306,8 @@ retnomove: } // Check for position outside of the fold column. - if (curwin->w_p_rl ? col < curwin->w_width_inner - curwin->w_p_fdc : - col >= curwin->w_p_fdc + (cmdwin_type == 0 ? 0 : 1)) { + if (curwin->w_p_rl ? col < curwin->w_width_inner - fdc : + col >= fdc + (cmdwin_type == 0 ? 0 : 1)) { mouse_char = ' '; } diff --git a/src/nvim/move.c b/src/nvim/move.c index 3ae4f32a83..f47853e9cb 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -674,7 +674,7 @@ int win_col_off(win_T *wp) { return ((wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) + 1 : 0) + (cmdwin_type == 0 || wp != curwin ? 0 : 1) - + (int)wp->w_p_fdc + + win_fdccol_count(wp) + (win_signcol_count(wp) * win_signcol_width(wp)); } diff --git a/src/nvim/msgpack_rpc/channel.h b/src/nvim/msgpack_rpc/channel.h index 9ff5abdc5f..90e1c7d48b 100644 --- a/src/nvim/msgpack_rpc/channel.h +++ b/src/nvim/msgpack_rpc/channel.h @@ -15,7 +15,7 @@ /// HACK: os/input.c drains this queue immediately before blocking for input. /// Events on this queue are async-safe, but they need the resolved state /// of os_inchar(), so they are processed "just-in-time". -MultiQueue *ch_before_blocking_events; +EXTERN MultiQueue *ch_before_blocking_events INIT(= NULL); #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/normal.c b/src/nvim/normal.c index c69b10f99a..6434bd00d8 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -4458,16 +4458,16 @@ dozet: case 'r': curwin->w_p_fdl += cap->count1; { - int d = getDeepestNesting(); + int d = getDeepestNesting(curwin); if (curwin->w_p_fdl >= d) { curwin->w_p_fdl = d; } } break; - /* "zR": open all folds */ - case 'R': curwin->w_p_fdl = getDeepestNesting(); - old_fdl = -1; /* force an update */ + case 'R': // "zR": open all folds + curwin->w_p_fdl = getDeepestNesting(curwin); + old_fdl = -1; // force an update break; case 'j': /* "zj" move to next fold downwards */ diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 735a33ca97..5457400b76 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -842,6 +842,15 @@ static bool is_append_register(int regname) return ASCII_ISUPPER(regname); } +/// @see get_yank_register +/// @returns true when register should be inserted literally +/// (selection or clipboard) +static inline bool is_literal_register(int regname) + FUNC_ATTR_CONST +{ + return regname == '*' || regname == '+'; +} + /// Returns a copy of contents in register `name` /// for use in do_put. Should be freed by caller. yankreg_T *copy_register(int name) @@ -1152,11 +1161,12 @@ static int put_in_typebuf( */ int insert_reg( int regname, - int literally /* insert literally, not as if typed */ + bool literally_arg // insert literally, not as if typed ) { int retval = OK; bool allocated; + const bool literally = literally_arg || is_literal_register(regname); /* * It is possible to get into an endless loop by having CTRL-R a in @@ -1326,12 +1336,14 @@ bool get_spec_reg( /// register contents will be interpreted as commands. /// /// @param regname Register name. -/// @param literally Insert text literally instead of "as typed". +/// @param literally_arg Insert text literally instead of "as typed". /// @param remcr When true, don't add CR characters. /// /// @returns FAIL for failure, OK otherwise -bool cmdline_paste_reg(int regname, bool literally, bool remcr) +bool cmdline_paste_reg(int regname, bool literally_arg, bool remcr) { + const bool literally = literally_arg || is_literal_register(regname); + yankreg_T *reg = get_yank_register(regname, YREG_PASTE); if (reg->y_array == NULL) return FAIL; @@ -2534,7 +2546,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) case kMTCharWise: { colnr_T startcol = 0, endcol = MAXCOL; - int is_oneChar = FALSE; + int is_oneChar = false; colnr_T cs, ce; p = ml_get(lnum); bd.startspaces = 0; @@ -2565,8 +2577,8 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) && utf_head_off(p, p + endcol) == 0)) { if (oap->start.lnum == oap->end.lnum && oap->start.col == oap->end.col) { - /* Special case: inside a single char */ - is_oneChar = TRUE; + // Special case: inside a single char + is_oneChar = true; bd.startspaces = oap->end.coladd - oap->start.coladd + oap->inclusive; endcol = startcol; @@ -4425,8 +4437,8 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bdp->textlen = 0; bdp->start_vcol = 0; bdp->end_vcol = 0; - bdp->is_short = FALSE; - bdp->is_oneChar = FALSE; + bdp->is_short = false; + bdp->is_oneChar = false; bdp->pre_whitesp = 0; bdp->pre_whitesp_c = 0; bdp->end_char_vcols = 0; @@ -4452,9 +4464,10 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bdp->start_char_vcols = incr; if (bdp->start_vcol < oap->start_vcol) { /* line too short */ bdp->end_vcol = bdp->start_vcol; - bdp->is_short = TRUE; - if (!is_del || oap->op_type == OP_APPEND) + bdp->is_short = true; + if (!is_del || oap->op_type == OP_APPEND) { bdp->endspaces = oap->end_vcol - oap->start_vcol + 1; + } } else { /* notice: this converts partly selected Multibyte characters to * spaces, too. */ @@ -4463,11 +4476,11 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bdp->startspaces = bdp->start_char_vcols - bdp->startspaces; pend = pstart; bdp->end_vcol = bdp->start_vcol; - if (bdp->end_vcol > oap->end_vcol) { /* it's all in one character */ - bdp->is_oneChar = TRUE; - if (oap->op_type == OP_INSERT) + if (bdp->end_vcol > oap->end_vcol) { // it's all in one character + bdp->is_oneChar = true; + if (oap->op_type == OP_INSERT) { bdp->endspaces = bdp->start_char_vcols - bdp->startspaces; - else if (oap->op_type == OP_APPEND) { + } else if (oap->op_type == OP_APPEND) { bdp->startspaces += oap->end_vcol - oap->start_vcol + 1; bdp->endspaces = bdp->start_char_vcols - bdp->startspaces; } else { @@ -4492,17 +4505,16 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, if (bdp->end_vcol <= oap->end_vcol && (!is_del || oap->op_type == OP_APPEND - || oap->op_type == OP_REPLACE)) { /* line too short */ - bdp->is_short = TRUE; - /* Alternative: include spaces to fill up the block. - * Disadvantage: can lead to trailing spaces when the line is - * short where the text is put */ - /* if (!is_del || oap->op_type == OP_APPEND) */ - if (oap->op_type == OP_APPEND || virtual_op) + || oap->op_type == OP_REPLACE)) { // line too short + bdp->is_short = true; + // Alternative: include spaces to fill up the block. + // Disadvantage: can lead to trailing spaces when the line is + // short where the text is put + // if (!is_del || oap->op_type == OP_APPEND) + if (oap->op_type == OP_APPEND || virtual_op) { bdp->endspaces = oap->end_vcol - bdp->end_vcol + oap->inclusive; - else - bdp->endspaces = 0; /* replace doesn't add characters */ + } } else if (bdp->end_vcol > oap->end_vcol) { bdp->endspaces = bdp->end_vcol - oap->end_vcol - 1; if (!is_del && bdp->endspaces) { diff --git a/src/nvim/option.c b/src/nvim/option.c index 478c9a0ff8..5a27ff21cc 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -315,6 +315,9 @@ static char *(p_scl_values[]) = { "yes", "no", "auto", "auto:1", "auto:2", "auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", "auto:9", "yes:1", "yes:2", "yes:3", "yes:4", "yes:5", "yes:6", "yes:7", "yes:8", "yes:9", NULL }; +static char *(p_fdc_values[]) = { "auto:1", "auto:2", + "auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", "auto:9", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", NULL }; /// All possible flags for 'shm'. static char_u SHM_ALL[] = { @@ -2565,7 +2568,7 @@ static bool valid_filetype(const char_u *val) bool valid_spellang(const char_u *val) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - return valid_name(val, ".-_,"); + return valid_name(val, ".-_,@"); } /// Return true if "val" is a valid 'spellfile' value. @@ -3199,6 +3202,11 @@ ambw_end: if (check_opt_strings(*varp, p_scl_values, false) != OK) { errmsg = e_invarg; } + } else if (varp == &curwin->w_p_fdc || varp == &curwin->w_allbuf_opt.wo_fdc) { + // 'foldcolumn' + if (check_opt_strings(*varp, p_fdc_values, false) != OK) { + errmsg = e_invarg; + } } else if (varp == &p_pt) { // 'pastetoggle': translate key codes like in a mapping if (*p_pt) { @@ -4363,12 +4371,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (value < 0) { errmsg = e_positive; } - } else if (pp == &curwin->w_p_fdc || pp == &curwin->w_allbuf_opt.wo_fdc) { - if (value < 0) { - errmsg = e_positive; - } else if (value > 12) { - errmsg = e_invarg; - } } else if (pp == &curwin->w_p_cole || pp == &curwin->w_allbuf_opt.wo_cole) { if (value < 0) { errmsg = e_positive; @@ -5936,8 +5938,9 @@ void copy_winopt(winopt_T *from, winopt_T *to) to->wo_diff_saved = from->wo_diff_saved; to->wo_cocu = vim_strsave(from->wo_cocu); to->wo_cole = from->wo_cole; - to->wo_fdc = from->wo_fdc; - to->wo_fdc_save = from->wo_fdc_save; + to->wo_fdc = vim_strsave(from->wo_fdc); + to->wo_fdc_save = from->wo_diff_saved + ? vim_strsave(from->wo_fdc_save) : empty_option; to->wo_fen = from->wo_fen; to->wo_fen_save = from->wo_fen_save; to->wo_fdi = vim_strsave(from->wo_fdi); @@ -5973,6 +5976,8 @@ void check_win_options(win_T *win) */ static void check_winopt(winopt_T *wop) { + check_string_option(&wop->wo_fdc); + check_string_option(&wop->wo_fdc_save); check_string_option(&wop->wo_fdi); check_string_option(&wop->wo_fdm); check_string_option(&wop->wo_fdm_save); @@ -5995,6 +6000,8 @@ static void check_winopt(winopt_T *wop) */ void clear_winopt(winopt_T *wop) { + clear_string_option(&wop->wo_fdc); + clear_string_option(&wop->wo_fdc_save); clear_string_option(&wop->wo_fdi); clear_string_option(&wop->wo_fdm); clear_string_option(&wop->wo_fdm_save); @@ -6073,10 +6080,8 @@ void buf_copy_options(buf_T *buf, int flags) save_p_isk = buf->b_p_isk; buf->b_p_isk = NULL; } - /* - * Always free the allocated strings. - * If not already initialized, set 'readonly' and copy 'fileformat'. - */ + // Always free the allocated strings. If not already initialized, + // reset 'readonly' and copy 'fileformat'. if (!buf->b_p_initialized) { free_buf_options(buf, true); buf->b_p_ro = false; // don't copy readonly diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 7d080b8d56..c18b9e0697 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -831,10 +831,11 @@ return { }, { full_name='foldcolumn', abbreviation='fdc', - type='number', scope={'window'}, + type='string', scope={'window'}, vi_def=true, + alloced=true, redraw={'current_window'}, - defaults={if_true={vi=false}} + defaults={if_true={vi="0"}} }, { full_name='foldenable', abbreviation='fen', diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 3b8470182a..6294d5e4e2 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -448,7 +448,6 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, } else { buffer[len] = NUL; } - i = 0; for (p = buffer; p < buffer + len; p++) { if (*p == NUL || (*p == ' ' && check_spaces)) { // count entry i++; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index b6da02d9c3..2e2c6ca737 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -334,10 +334,10 @@ int update_screen(int type) } return FAIL; } + updating_screen = 1; - updating_screen = TRUE; - ++display_tick; /* let syntax code know we're in a next round of - * display updating */ + display_tick++; // let syntax code know we're in a next round of + // display updating // Tricky: vim code can reset msg_scrolled behind our back, so need // separate bookkeeping for now. @@ -565,7 +565,7 @@ int update_screen(int type) wp->w_buffer->b_mod_set = false; } - updating_screen = FALSE; + updating_screen = 0; /* Clear or redraw the command line. Done last, because scrolling may * mess up the command line. */ @@ -1744,7 +1744,7 @@ static int advance_color_col(int vcol, int **color_cols) // space is available for window "wp", minus "col". static int compute_foldcolumn(win_T *wp, int col) { - int fdc = wp->w_p_fdc; + int fdc = win_fdccol_count(wp); int wmw = wp == curwin && p_wmw == 0 ? 1 : p_wmw; int wwidth = wp->w_grid.Columns; @@ -2215,9 +2215,10 @@ win_line ( int n_skip = 0; /* nr of chars to skip for 'nowrap' */ - int fromcol = 0, tocol = 0; // start/end of inverting + int fromcol = -10; // start of inverting + int tocol = MAXCOL; // end of inverting int fromcol_prev = -2; // start of inverting after cursor - int noinvcur = false; // don't invert the cursor + bool noinvcur = false; // don't invert the cursor pos_T *top, *bot; int lnum_in_visual_area = false; pos_T pos; @@ -2416,27 +2417,26 @@ win_line ( capcol_lnum = 0; } - // - // handle visual active in this window - // - fromcol = -10; - tocol = MAXCOL; + // handle Visual active in this window if (VIsual_active && wp->w_buffer == curwin->w_buffer) { - // Visual is after curwin->w_cursor if (ltoreq(curwin->w_cursor, VIsual)) { + // Visual is after curwin->w_cursor top = &curwin->w_cursor; bot = &VIsual; - } else { // Visual is before curwin->w_cursor + } else { + // Visual is before curwin->w_cursor top = &VIsual; bot = &curwin->w_cursor; } lnum_in_visual_area = (lnum >= top->lnum && lnum <= bot->lnum); - if (VIsual_mode == Ctrl_V) { // block mode + if (VIsual_mode == Ctrl_V) { + // block mode if (lnum_in_visual_area) { fromcol = wp->w_old_cursor_fcol; tocol = wp->w_old_cursor_lcol; } - } else { // non-block mode + } else { + // non-block mode if (lnum > top->lnum && lnum <= bot->lnum) { fromcol = 0; } else if (lnum == top->lnum) { diff --git a/src/nvim/sign.c b/src/nvim/sign.c index 23dd447744..ab5d04d39b 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -198,7 +198,7 @@ static void insert_sign( // column for signs. if (buf->b_signlist == NULL) { redraw_buf_later(buf, NOT_VALID); - changed_cline_bef_curs(); + changed_line_abv_curs(); } // first sign in signlist @@ -265,6 +265,81 @@ dict_T * sign_get_info(signlist_T *sign) return d; } +// Sort the signs placed on the same line as "sign" by priority. Invoked after +// changing the priority of an already placed sign. Assumes the signs in the +// buffer are sorted by line number and priority. +static void sign_sort_by_prio_on_line(buf_T *buf, signlist_T *sign) + FUNC_ATTR_NONNULL_ALL +{ + // If there is only one sign in the buffer or only one sign on the line or + // the sign is already sorted by priority, then return. + if ((sign->prev == NULL + || sign->prev->lnum != sign->lnum + || sign->prev->priority > sign->priority) + && (sign->next == NULL + || sign->next->lnum != sign->lnum + || sign->next->priority < sign->priority)) { + return; + } + + // One or more signs on the same line as 'sign' + // Find a sign after which 'sign' should be inserted + + // First search backward for a sign with higher priority on the same line + signlist_T *p = sign; + while (p->prev != NULL + && p->prev->lnum == sign->lnum + && p->prev->priority <= sign->priority) { + p = p->prev; + } + if (p == sign) { + // Sign not found. Search forward for a sign with priority just before + // 'sign'. + p = sign->next; + while (p->next != NULL + && p->next->lnum == sign->lnum + && p->next->priority > sign->priority) { + p = p->next; + } + } + + // Remove 'sign' from the list + if (buf->b_signlist == sign) { + buf->b_signlist = sign->next; + } + if (sign->prev != NULL) { + sign->prev->next = sign->next; + } + if (sign->next != NULL) { + sign->next->prev = sign->prev; + } + sign->prev = NULL; + sign->next = NULL; + + // Re-insert 'sign' at the right place + if (p->priority <= sign->priority) { + // 'sign' has a higher priority and should be inserted before 'p' + sign->prev = p->prev; + sign->next = p; + p->prev = sign; + if (sign->prev != NULL) { + sign->prev->next = sign; + } + if (buf->b_signlist == p) { + buf->b_signlist = sign; + } + } else { + // 'sign' has a lower priority and should be inserted after 'p' + sign->prev = p; + sign->next = p->next; + p->next = sign; + if (sign->next != NULL) { + sign->next->prev = sign; + } + } +} + + /// Add the sign into the signlist. Find the right spot to do it though. void buf_addsign( buf_T *buf, // buffer to store sign in @@ -284,6 +359,8 @@ void buf_addsign( && sign_in_group(sign, groupname)) { // Update an existing sign sign->typenr = typenr; + sign->priority = prio; + sign_sort_by_prio_on_line(buf, sign); return; } else if (lnum < sign->lnum) { insert_sign_by_lnum_prio(buf, prev, id, groupname, prio, lnum, typenr); @@ -418,11 +495,11 @@ linenr_T buf_delsign( } } - // When deleted the last sign needs to redraw the windows to remove the - // sign column. + // When deleting the last sign the cursor position may change, because the + // sign columns no longer shows. And the 'signcolumn' may be hidden. if (buf->b_signlist == NULL) { redraw_buf_later(buf, NOT_VALID); - changed_cline_bef_curs(); + changed_line_abv_curs(); } return lnum; @@ -495,7 +572,7 @@ void buf_delete_signs(buf_T *buf, char_u *group) // When deleting the last sign need to redraw the windows to remove the // sign column. Not when curwin is NULL (this means we're exiting). if (buf->b_signlist != NULL && curwin != NULL) { - changed_cline_bef_curs(); + changed_line_abv_curs(); } lastp = &buf->b_signlist; @@ -754,6 +831,14 @@ int sign_define_by_name( } else { sp_prev->sn_next = sp; } + } else { + // Signs may already exist, a redraw is needed in windows with a + // non-empty sign list. + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer->b_signlist != NULL) { + redraw_buf_later(wp->w_buffer, NOT_VALID); + } + } } // set values for a defined sign. @@ -1531,10 +1616,44 @@ static enum EXP_SUBCMD, // expand :sign sub-commands EXP_DEFINE, // expand :sign define {name} args EXP_PLACE, // expand :sign place {id} args + EXP_LIST, // expand :sign place args EXP_UNPLACE, // expand :sign unplace" - EXP_SIGN_NAMES // expand with name of placed signs + EXP_SIGN_NAMES, // expand with name of placed signs + EXP_SIGN_GROUPS, // expand with name of placed sign groups } expand_what; +// Return the n'th sign name (used for command line completion) +static char_u *get_nth_sign_name(int idx) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + // Complete with name of signs already defined + int current_idx = 0; + for (sign_T *sp = first_sign; sp != NULL; sp = sp->sn_next) { + if (current_idx++ == idx) { + return sp->sn_name; + } + } + return NULL; +} + +// Return the n'th sign group name (used for command line completion) +static char_u *get_nth_sign_group_name(int idx) +{ + // Complete with name of sign groups already defined + int current_idx = 0; + int todo = (int)sg_table.ht_used; + for (hashitem_T *hi = sg_table.ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + todo--; + if (current_idx++ == idx) { + signgroup_T *const group = HI2SG(hi); + return group->sg_name; + } + } + } + return NULL; +} + /// Function given to ExpandGeneric() to obtain the sign command /// expansion. char_u * get_sign_name(expand_T *xp, int idx) @@ -1552,20 +1671,18 @@ char_u * get_sign_name(expand_T *xp, int idx) "buffer=", NULL }; return (char_u *)place_arg[idx]; } + case EXP_LIST: { + char *list_arg[] = { "group=", "file=", "buffer=", NULL }; + return (char_u *)list_arg[idx]; + } case EXP_UNPLACE: { char *unplace_arg[] = { "group=", "file=", "buffer=", NULL }; return (char_u *)unplace_arg[idx]; } - case EXP_SIGN_NAMES: { - // Complete with name of signs already defined - int current_idx = 0; - for (sign_T *sp = first_sign; sp != NULL; sp = sp->sn_next) { - if (current_idx++ == idx) { - return sp->sn_name; - } - } - } - return NULL; + case EXP_SIGN_NAMES: + return get_nth_sign_name(idx); + case EXP_SIGN_GROUPS: + return get_nth_sign_group_name(idx); default: return NULL; } @@ -1574,7 +1691,6 @@ char_u * get_sign_name(expand_T *xp, int idx) /// Handle command line completion for :sign command. void set_context_in_sign_cmd(expand_T *xp, char_u *arg) { - char_u *p; char_u *end_subcmd; char_u *last; int cmd_idx; @@ -1598,26 +1714,6 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) // | // begin_subcmd_args begin_subcmd_args = skipwhite(end_subcmd); - p = skiptowhite(begin_subcmd_args); - if (*p == NUL) { - // - // Expand first argument of subcmd when possible. - // For ":jump {id}" and ":unplace {id}", we could - // possibly expand the ids of all signs already placed. - // - xp->xp_pattern = begin_subcmd_args; - switch (cmd_idx) { - case SIGNCMD_LIST: - case SIGNCMD_UNDEFINE: - // :sign list <CTRL-D> - // :sign undefine <CTRL-D> - expand_what = EXP_SIGN_NAMES; - break; - default: - xp->xp_context = EXPAND_NOTHING; - } - return; - } // Expand last argument of subcmd. // @@ -1626,6 +1722,7 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) // p // Loop until reaching last argument. + char_u *p = begin_subcmd_args; do { p = skipwhite(p); last = p; @@ -1645,7 +1742,20 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) expand_what = EXP_DEFINE; break; case SIGNCMD_PLACE: - expand_what = EXP_PLACE; + // List placed signs + if (ascii_isdigit(*begin_subcmd_args)) { + // :sign place {id} {args}... + expand_what = EXP_PLACE; + } else { + // :sign place {args}... + expand_what = EXP_LIST; + } + break; + case SIGNCMD_LIST: + case SIGNCMD_UNDEFINE: + // :sign list <CTRL-D> + // :sign undefine <CTRL-D> + expand_what = EXP_SIGN_NAMES; break; case SIGNCMD_JUMP: case SIGNCMD_UNPLACE: @@ -1659,19 +1769,33 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) xp->xp_pattern = p + 1; switch (cmd_idx) { case SIGNCMD_DEFINE: - if (STRNCMP(last, "texthl", p - last) == 0 - || STRNCMP(last, "linehl", p - last) == 0 - || STRNCMP(last, "numhl", p - last) == 0) { + if (STRNCMP(last, "texthl", 6) == 0 + || STRNCMP(last, "linehl", 6) == 0 + || STRNCMP(last, "numhl", 5) == 0) { xp->xp_context = EXPAND_HIGHLIGHT; - } else if (STRNCMP(last, "icon", p - last) == 0) { + } else if (STRNCMP(last, "icon", 4) == 0) { xp->xp_context = EXPAND_FILES; } else { xp->xp_context = EXPAND_NOTHING; } break; case SIGNCMD_PLACE: - if (STRNCMP(last, "name", p - last) == 0) { + if (STRNCMP(last, "name", 4) == 0) { expand_what = EXP_SIGN_NAMES; + } else if (STRNCMP(last, "group", 5) == 0) { + expand_what = EXP_SIGN_GROUPS; + } else if (STRNCMP(last, "file", 4) == 0) { + xp->xp_context = EXPAND_BUFFERS; + } else { + xp->xp_context = EXPAND_NOTHING; + } + break; + case SIGNCMD_UNPLACE: + case SIGNCMD_JUMP: + if (STRNCMP(last, "group", 5) == 0) { + expand_what = EXP_SIGN_GROUPS; + } else if (STRNCMP(last, "file", 4) == 0) { + xp->xp_context = EXPAND_BUFFERS; } else { xp->xp_context = EXPAND_NOTHING; } diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 21e0271bda..fed642e34b 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -67,7 +67,7 @@ func Common_vert_split() set foldmethod=marker foldcolumn=4 call assert_equal(0, &diff) call assert_equal('marker', &foldmethod) - call assert_equal(4, &foldcolumn) + call assert_equal('4', &foldcolumn) call assert_equal(0, &scrollbind) call assert_equal(0, &cursorbind) call assert_equal(1, &wrap) @@ -76,7 +76,7 @@ func Common_vert_split() vert diffsplit Xtest2 call assert_equal(1, &diff) call assert_equal('diff', &foldmethod) - call assert_equal(2, &foldcolumn) + call assert_equal('2', &foldcolumn) call assert_equal(1, &scrollbind) call assert_equal(1, &cursorbind) call assert_equal(0, &wrap) @@ -142,7 +142,7 @@ func Common_vert_split() 1wincmd w call assert_equal(0, &diff) call assert_equal('marker', &foldmethod) - call assert_equal(4, &foldcolumn) + call assert_equal('4', &foldcolumn) call assert_equal(0, &scrollbind) call assert_equal(0, &cursorbind) call assert_equal(1, &wrap) @@ -150,7 +150,7 @@ func Common_vert_split() wincmd w call assert_equal(0, &diff) call assert_equal('marker', &foldmethod) - call assert_equal(4, &foldcolumn) + call assert_equal('4', &foldcolumn) call assert_equal(0, &scrollbind) call assert_equal(0, &cursorbind) call assert_equal(1, &wrap) @@ -158,7 +158,7 @@ func Common_vert_split() wincmd w call assert_equal(0, &diff) call assert_equal('marker', &foldmethod) - call assert_equal(4, &foldcolumn) + call assert_equal('4', &foldcolumn) call assert_equal(0, &scrollbind) call assert_equal(0, &cursorbind) call assert_equal(1, &wrap) diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 7290cceb0b..9605348389 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -600,6 +600,7 @@ let s:script_checks = { \ 'haskell': [['#!/path/haskell']], \ 'cpp': [['// Standard iostream objects -*- C++ -*-'], \ ['// -*- C++ -*-']], + \ 'yaml': [['%YAML 1.2']], \ } func Test_script_detection() diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim index 265dee66ce..aad21c002f 100644 --- a/src/nvim/testdir/test_messages.vim +++ b/src/nvim/testdir/test_messages.vim @@ -1,4 +1,4 @@ -" Tests for :messages +" Tests for :messages, :echomsg, :echoerr function Test_messages() let oldmore = &more @@ -65,6 +65,35 @@ func Test_message_completion() call assert_equal('"message clear', @:) endfunc +func Test_echomsg() + call assert_equal("\nhello", execute(':echomsg "hello"')) + call assert_equal("\n", execute(':echomsg ""')) + call assert_equal("\n12345", execute(':echomsg 12345')) + call assert_equal("\n[]", execute(':echomsg []')) + call assert_equal("\n[1, 2, 3]", execute(':echomsg [1, 2, 3]')) + call assert_equal("\n{}", execute(':echomsg {}')) + call assert_equal("\n{'a': 1, 'b': 2}", execute(':echomsg {"a": 1, "b": 2}')) + if has('float') + call assert_equal("\n1.23", execute(':echomsg 1.23')) + endif + call assert_match("function('<lambda>\\d*')", execute(':echomsg {-> 1234}')) +endfunc + +func Test_echoerr() + throw 'skipped: Nvim does not support test_ignore_error()' + call test_ignore_error('IgNoRe') + call assert_equal("\nIgNoRe hello", execute(':echoerr "IgNoRe hello"')) + call assert_equal("\n12345 IgNoRe", execute(':echoerr 12345 "IgNoRe"')) + call assert_equal("\n[1, 2, 'IgNoRe']", execute(':echoerr [1, 2, "IgNoRe"]')) + call assert_equal("\n{'IgNoRe': 2, 'a': 1}", execute(':echoerr {"a": 1, "IgNoRe": 2}')) + if has('float') + call assert_equal("\n1.23 IgNoRe", execute(':echoerr 1.23 "IgNoRe"')) + endif + call test_ignore_error('<lambda>') + call assert_match("function('<lambda>\\d*')", execute(':echoerr {-> 1234}')) + call test_ignore_error('RESET') +endfunc + func Test_echospace() set noruler noshowcmd laststatus=1 call assert_equal(&columns - 1, v:echospace) diff --git a/src/nvim/testdir/test_number.vim b/src/nvim/testdir/test_number.vim index 59debcea0d..3c9afc41d5 100644 --- a/src/nvim/testdir/test_number.vim +++ b/src/nvim/testdir/test_number.vim @@ -252,3 +252,14 @@ func Test_numberwidth_adjusted() call s:compare_lines(expect, lines) call s:close_windows() endfunc + +" This was causing a memcheck error +func Test_relativenumber_uninitialised() + new + set rnu + call setline(1, ["a", "b"]) + redraw + call feedkeys("j", 'xt') + redraw + bwipe! +endfunc diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 29d391c232..c2f710358b 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -511,3 +511,11 @@ func Test_shortmess_F2() bwipe bwipe endfunc + +func Test_visualbell() + set belloff= + set visualbell + call assert_beeps('normal 0h') + set novisualbell + set belloff=all +endfunc diff --git a/src/nvim/testdir/test_restricted.vim b/src/nvim/testdir/test_restricted.vim new file mode 100644 index 0000000000..a29f7c33d3 --- /dev/null +++ b/src/nvim/testdir/test_restricted.vim @@ -0,0 +1,103 @@ +" Test for "rvim" or "vim -Z" + +source shared.vim + +"if has('win32') && has('gui') +" " Win32 GUI shows a dialog instead of displaying the error in the last line. +" finish +"endif + +func Test_restricted() + call Run_restricted_test('!ls', 'E145:') +endfunc + +func Run_restricted_test(ex_cmd, error) + let cmd = GetVimCommand('Xrestricted') + if cmd == '' + return + endif + + " Use a VimEnter autocommand to avoid that the error message is displayed in + " a dialog with an OK button. + call writefile([ + \ "func Init()", + \ " silent! " . a:ex_cmd, + \ " call writefile([v:errmsg], 'Xrestrout')", + \ " qa!", + \ "endfunc", + \ "au VimEnter * call Init()", + \ ], 'Xrestricted') + call system(cmd . ' -Z') + call assert_match(a:error, join(readfile('Xrestrout'))) + + call delete('Xrestricted') + call delete('Xrestrout') +endfunc + +func Test_restricted_lua() + if !has('lua') + throw 'Skipped: Lua is not supported' + endif + call Run_restricted_test('lua print("Hello, Vim!")', 'E981:') + call Run_restricted_test('luado return "hello"', 'E981:') + call Run_restricted_test('luafile somefile', 'E981:') + call Run_restricted_test('call luaeval("expression")', 'E145:') +endfunc + +func Test_restricted_mzscheme() + if !has('mzscheme') + throw 'Skipped: MzScheme is not supported' + endif + call Run_restricted_test('mzscheme statement', 'E981:') + call Run_restricted_test('mzfile somefile', 'E981:') + call Run_restricted_test('call mzeval("expression")', 'E145:') +endfunc + +func Test_restricted_perl() + if !has('perl') + throw 'Skipped: Perl is not supported' + endif + " TODO: how to make Safe mode fail? + " call Run_restricted_test('perl system("ls")', 'E981:') + " call Run_restricted_test('perldo system("hello")', 'E981:') + " call Run_restricted_test('perlfile somefile', 'E981:') + " call Run_restricted_test('call perleval("system(\"ls\")")', 'E145:') +endfunc + +func Test_restricted_python() + if !has('python') + throw 'Skipped: Python is not supported' + endif + call Run_restricted_test('python print "hello"', 'E981:') + call Run_restricted_test('pydo return "hello"', 'E981:') + call Run_restricted_test('pyfile somefile', 'E981:') + call Run_restricted_test('call pyeval("expression")', 'E145:') +endfunc + +func Test_restricted_python3() + if !has('python3') + throw 'Skipped: Python3 is not supported' + endif + call Run_restricted_test('py3 print "hello"', 'E981:') + call Run_restricted_test('py3do return "hello"', 'E981:') + call Run_restricted_test('py3file somefile', 'E981:') + call Run_restricted_test('call py3eval("expression")', 'E145:') +endfunc + +func Test_restricted_ruby() + if !has('ruby') + throw 'Skipped: Ruby is not supported' + endif + call Run_restricted_test('ruby print "Hello"', 'E981:') + call Run_restricted_test('rubydo print "Hello"', 'E981:') + call Run_restricted_test('rubyfile somefile', 'E981:') +endfunc + +func Test_restricted_tcl() + if !has('tcl') + throw 'Skipped: Tcl is not supported' + endif + call Run_restricted_test('tcl puts "Hello"', 'E981:') + call Run_restricted_test('tcldo puts "Hello"', 'E981:') + call Run_restricted_test('tclfile somefile', 'E981:') +endfunc diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index 5d99027ca5..8036dea29f 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -698,3 +698,9 @@ func Test_search_display_pattern() set norl endif endfunc + +func Test_search_special() + " this was causing illegal memory access and an endless loop + set t_PE= + exe "norm /\x80PS" +endfunc diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim index ef4b227215..8b1927e4f0 100644 --- a/src/nvim/testdir/test_signs.vim +++ b/src/nvim/testdir/test_signs.vim @@ -4,6 +4,8 @@ if !has('signs') finish endif +source screendump.vim + func Test_sign() new call setline(1, ['a', 'b', 'c', 'd']) @@ -210,13 +212,16 @@ func Test_sign_completion() call assert_equal('"sign define Sign linehl=SpellBad SpellCap ' . \ 'SpellLocal SpellRare', @:) - call writefile(['foo'], 'XsignOne') - call writefile(['bar'], 'XsignTwo') + call feedkeys(":sign define Sign texthl=Spell\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign define Sign texthl=SpellBad SpellCap ' . + \ 'SpellLocal SpellRare', @:) + + call writefile(repeat(["Sun is shining"], 30), "XsignOne") + call writefile(repeat(["Sky is blue"], 30), "XsignTwo") call feedkeys(":sign define Sign icon=Xsig\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"sign define Sign icon=XsignOne XsignTwo', @:) - call delete('XsignOne') - call delete('XsignTwo') + " Test for completion of arguments to ':sign undefine' call feedkeys(":sign undefine \<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"sign undefine Sign1 Sign2', @:) @@ -227,17 +232,70 @@ func Test_sign_completion() call feedkeys(":sign place 1 name=\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"sign place 1 name=Sign1 Sign2', @:) + edit XsignOne + sign place 1 name=Sign1 line=5 + sign place 1 name=Sign1 group=g1 line=10 + edit XsignTwo + sign place 1 name=Sign2 group=g2 line=15 + + " Test for completion of group= and file= arguments to ':sign place' + call feedkeys(":sign place 1 name=Sign1 file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign place 1 name=Sign1 file=XsignOne XsignTwo', @:) + call feedkeys(":sign place 1 name=Sign1 group=\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign place 1 name=Sign1 group=g1 g2', @:) + + " Test for completion of arguments to 'sign place' without sign identifier + call feedkeys(":sign place \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign place buffer= file= group=', @:) + call feedkeys(":sign place file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign place file=XsignOne XsignTwo', @:) + call feedkeys(":sign place group=\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign place group=g1 g2', @:) + call feedkeys(":sign place group=g1 file=\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign place group=g1 file=XsignOne XsignTwo', @:) + + " Test for completion of arguments to ':sign unplace' call feedkeys(":sign unplace 1 \<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"sign unplace 1 buffer= file= group=', @:) - + call feedkeys(":sign unplace 1 file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign unplace 1 file=XsignOne XsignTwo', @:) + call feedkeys(":sign unplace 1 group=\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign unplace 1 group=g1 g2', @:) + call feedkeys(":sign unplace 1 group=g2 file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign unplace 1 group=g2 file=XsignOne XsignTwo', @:) + + " Test for completion of arguments to ':sign list' call feedkeys(":sign list \<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"sign list Sign1 Sign2', @:) + " Test for completion of arguments to ':sign jump' call feedkeys(":sign jump 1 \<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"sign jump 1 buffer= file= group=', @:) + call feedkeys(":sign jump 1 file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign jump 1 file=XsignOne XsignTwo', @:) + call feedkeys(":sign jump 1 group=\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign jump 1 group=g1 g2', @:) + + " Error cases + call feedkeys(":sign here\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign here', @:) + call feedkeys(":sign define Sign here=\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"sign define Sign here=\<C-A>", @:) + call feedkeys(":sign place 1 here=\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"sign place 1 here=\<C-A>", @:) + call feedkeys(":sign jump 1 here=\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"sign jump 1 here=\<C-A>", @:) + call feedkeys(":sign here there\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"sign here there\<C-A>", @:) + call feedkeys(":sign here there=\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"sign here there=\<C-A>", @:) + sign unplace * group=* sign undefine Sign1 sign undefine Sign2 + enew + call delete('XsignOne') + call delete('XsignTwo') endfunc func Test_sign_invalid_commands() @@ -1127,6 +1185,319 @@ func Test_sign_priority() \ 'priority' : 10}], \ s[0].signs) + call sign_unplace('*') + + " Three signs on different lines with changing priorities + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 11, 'priority' : 50}) + call sign_place(2, '', 'sign2', 'Xsign', + \ {'lnum' : 12, 'priority' : 60}) + call sign_place(3, '', 'sign3', 'Xsign', + \ {'lnum' : 13, 'priority' : 70}) + call sign_place(2, '', 'sign2', 'Xsign', + \ {'lnum' : 12, 'priority' : 40}) + call sign_place(3, '', 'sign3', 'Xsign', + \ {'lnum' : 13, 'priority' : 30}) + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 11, 'priority' : 50}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11, 'group' : '', + \ 'priority' : 50}, + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 12, 'group' : '', + \ 'priority' : 40}, + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 13, 'group' : '', + \ 'priority' : 30}], + \ s[0].signs) + + call sign_unplace('*') + + " Two signs on the same line with changing priorities + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 20}) + call sign_place(2, '', 'sign2', 'Xsign', + \ {'lnum' : 4, 'priority' : 30}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 30}, + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}], + \ s[0].signs) + " Change the priority of the last sign to highest + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 40}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 40}, + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 30}], + \ s[0].signs) + " Change the priority of the first sign to lowest + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 25}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 30}, + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 25}], + \ s[0].signs) + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 45}) + call sign_place(2, '', 'sign2', 'Xsign', + \ {'lnum' : 4, 'priority' : 55}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 55}, + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 45}], + \ s[0].signs) + + call sign_unplace('*') + + " Three signs on the same line with changing priorities + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 40}) + call sign_place(2, '', 'sign2', 'Xsign', + \ {'lnum' : 4, 'priority' : 30}) + call sign_place(3, '', 'sign3', 'Xsign', + \ {'lnum' : 4, 'priority' : 20}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 40}, + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 30}, + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}], + \ s[0].signs) + + " Change the priority of the middle sign to the highest + call sign_place(2, '', 'sign2', 'Xsign', + \ {'lnum' : 4, 'priority' : 50}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 50}, + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 40}, + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}], + \ s[0].signs) + + " Change the priority of the middle sign to the lowest + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 15}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 50}, + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 15}], + \ s[0].signs) + + " Change the priority of the last sign to the highest + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 55}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 55}, + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 50}, + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}], + \ s[0].signs) + + " Change the priority of the first sign to the lowest + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 15}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 50}, + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 15}], + \ s[0].signs) + + call sign_unplace('*') + + " Three signs on the same line with changing priorities along with other + " signs + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 2, 'priority' : 10}) + call sign_place(2, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 30}) + call sign_place(3, '', 'sign2', 'Xsign', + \ {'lnum' : 4, 'priority' : 20}) + call sign_place(4, '', 'sign3', 'Xsign', + \ {'lnum' : 4, 'priority' : 25}) + call sign_place(5, '', 'sign2', 'Xsign', + \ {'lnum' : 6, 'priority' : 80}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '', + \ 'priority' : 10}, + \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 30}, + \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 25}, + \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '', + \ 'priority' : 80}], + \ s[0].signs) + + " Change the priority of the first sign to lowest + call sign_place(2, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 15}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '', + \ 'priority' : 10}, + \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 25}, + \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 15}, + \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '', + \ 'priority' : 80}], + \ s[0].signs) + + " Change the priority of the last sign to highest + call sign_place(2, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 30}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '', + \ 'priority' : 10}, + \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 30}, + \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 25}, + \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '', + \ 'priority' : 80}], + \ s[0].signs) + + " Change the priority of the middle sign to lowest + call sign_place(4, '', 'sign3', 'Xsign', + \ {'lnum' : 4, 'priority' : 15}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '', + \ 'priority' : 10}, + \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 30}, + \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 15}, + \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '', + \ 'priority' : 80}], + \ s[0].signs) + + " Change the priority of the middle sign to highest + call sign_place(3, '', 'sign2', 'Xsign', + \ {'lnum' : 4, 'priority' : 35}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '', + \ 'priority' : 10}, + \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 35}, + \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 30}, + \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 15}, + \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '', + \ 'priority' : 80}], + \ s[0].signs) + + call sign_unplace('*') + + " Multiple signs with the same priority on the same line + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 20}) + call sign_place(2, '', 'sign2', 'Xsign', + \ {'lnum' : 4, 'priority' : 20}) + call sign_place(3, '', 'sign3', 'Xsign', + \ {'lnum' : 4, 'priority' : 20}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}], + \ s[0].signs) + " Place the last sign again with the same priority + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 20}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}], + \ s[0].signs) + " Place the first sign again with the same priority + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 20}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}], + \ s[0].signs) + " Place the middle sign again with the same priority + call sign_place(3, '', 'sign3', 'Xsign', + \ {'lnum' : 4, 'priority' : 20}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}], + \ s[0].signs) + + call sign_unplace('*') + + " Place multiple signs with same id on a line with different priority + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 5, 'priority' : 20}) + call sign_place(1, '', 'sign2', 'Xsign', + \ {'lnum' : 5, 'priority' : 10}) + let s = sign_getplaced('Xsign', {'lnum' : 5}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign2', 'lnum' : 5, 'group' : '', + \ 'priority' : 10}], + \ s[0].signs) + call sign_place(1, '', 'sign2', 'Xsign', + \ {'lnum' : 5, 'priority' : 5}) + let s = sign_getplaced('Xsign', {'lnum' : 5}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign2', 'lnum' : 5, 'group' : '', + \ 'priority' : 5}], + \ s[0].signs) + " Error case call assert_fails("call sign_place(1, 'g1', 'sign1', 'Xsign', \ [])", 'E715:') @@ -1339,3 +1710,35 @@ func Test_sign_jump_func() sign undefine sign1 enew! | only! endfunc + +" Test for correct cursor position after the sign column appears or disappears. +func Test_sign_cursor_position() + if !CanRunVimInTerminal() + throw 'Skipped: cannot make screendumps' + endif + + let lines =<< trim END + call setline(1, [repeat('x', 75), 'mmmm', 'yyyy']) + call cursor(2,1) + sign define s1 texthl=Search text==> + redraw + sign place 10 line=2 name=s1 + END + call writefile(lines, 'XtestSigncolumn') + let buf = RunVimInTerminal('-S XtestSigncolumn', {'rows': 6}) + call VerifyScreenDump(buf, 'Test_sign_cursor_1', {}) + + " Change the sign text + call term_sendkeys(buf, ":sign define s1 text=-)\<CR>") + call VerifyScreenDump(buf, 'Test_sign_cursor_2', {}) + + " update cursor position calculation + call term_sendkeys(buf, "lh") + call term_sendkeys(buf, ":sign unplace 10\<CR>") + call VerifyScreenDump(buf, 'Test_sign_cursor_3', {}) + + + " clean up + call StopVimInTerminal(buf) + call delete('XtestSigncolumn') +endfunc diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index 3043103270..40376a877e 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -339,4 +339,8 @@ func Test_nocatch_garbage_collect() delfunc FeedChar endfunc +func Test_timer_invalid_callback() + call assert_fails('call timer_start(0, "0")', 'E921') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index e9276db484..22f4501be2 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1099,6 +1099,7 @@ static void tui_grid_scroll(UI *ui, Integer g, Integer startrow, Integer endrow, set_scroll_region(ui, top, bot, left, right); } cursor_goto(ui, top, left); + update_attrs(ui, 0); if (rows > 0) { if (rows == 1) { diff --git a/src/nvim/version.c b/src/nvim/version.c index c67fd9175f..6412d4caae 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -129,7 +129,7 @@ static const int included_patches[] = { 1792, 1791, 1790, - // 1789, + 1789, 1788, 1787, 1786, @@ -258,7 +258,7 @@ static const int included_patches[] = { 1663, 1662, 1661, - // 1660, + 1660, 1659, 1658, 1657, diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 23e172da75..838a742271 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -326,7 +326,7 @@ struct expr_ast_node { } data; }; -enum { +enum ExprParserFlags { /// Allow multiple expressions in a row: e.g. for :echo /// /// Parser will still parse only one of them though. @@ -345,7 +345,7 @@ enum { // viml_expressions_parser.c, nvim_parse_expression() flags parsing // alongside with its documentation and flag sets in check_parsing() // function in expressions parser functional and unit tests. -} ExprParserFlags; +}; /// AST error definition typedef struct { diff --git a/src/nvim/window.c b/src/nvim/window.c index 8181883426..f61a46996d 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -618,7 +618,6 @@ void win_set_minimal_style(win_T *wp) wp->w_p_cuc = false; wp->w_p_spell = false; wp->w_p_list = false; - wp->w_p_fdc = 0; // Hide EOB region: use " " fillchar and cleared highlighting if (wp->w_p_fcs_chars.eob != ' ') { @@ -642,6 +641,12 @@ void win_set_minimal_style(win_T *wp) wp->w_p_scl = (char_u *)xstrdup("auto"); } + // foldcolumn: use 'auto' + if (wp->w_p_fdc[0] != '0') { + xfree(wp->w_p_fdc); + wp->w_p_fdc = (char_u *)xstrdup("0"); + } + // colorcolumn: cleared if (wp->w_p_cc != NULL && *wp->w_p_cc != NUL) { xfree(wp->w_p_cc); @@ -689,6 +694,21 @@ void win_check_anchored_floats(win_T *win) } } +/// Return the number of fold columns to display +int win_fdccol_count(win_T *wp) +{ + const char *fdc = (const char *)wp->w_p_fdc; + + // auto:<NUM> + if (strncmp(fdc, "auto:", 5) == 0) { + int needed_fdccols = getDeepestNesting(wp); + return MIN(fdc[5] - '0', needed_fdccols); + } else { + return fdc[0] - '0'; + } +} + + static void ui_ext_win_position(win_T *wp) { if (!wp->w_floating) { diff --git a/src/tree_sitter/api.h b/src/tree_sitter/api.h index 40187e3db0..9d832e6ec4 100644 --- a/src/tree_sitter/api.h +++ b/src/tree_sitter/api.h @@ -174,8 +174,19 @@ const TSLanguage *ts_parser_language(const TSParser *self); * The second and third parameters specify the location and length of an array * of ranges. The parser does *not* take ownership of these ranges; it copies * the data, so it doesn't matter how these ranges are allocated. + * + * If `length` is zero, then the entire document will be parsed. Otherwise, + * the given ranges must be ordered from earliest to latest in the document, + * and they must not overlap. That is, the following must hold for all + * `i` < `length - 1`: + * + * ranges[i].end_byte <= ranges[i + 1].start_byte + * + * If this requirement is not satisfied, the operation will fail, the ranges + * will not be assigned, and this function will return `false`. On success, + * this function returns `true` */ -void ts_parser_set_included_ranges( +bool ts_parser_set_included_ranges( TSParser *self, const TSRange *ranges, uint32_t length @@ -325,14 +336,6 @@ TSLogger ts_parser_logger(const TSParser *self); */ void ts_parser_print_dot_graphs(TSParser *self, int file); -/** - * Set whether or not the parser should halt immediately upon detecting an - * error. This will generally result in a syntax tree with an error at the - * root, and one or more partial syntax trees within the error. This behavior - * may not be supported long-term. - */ -void ts_parser_halt_on_error(TSParser *self, bool halt); - /******************/ /* Section - Tree */ /******************/ @@ -732,13 +735,23 @@ const char *ts_query_string_value_for_id( ); /** - * Disable a certain capture within a query. This prevents the capture - * from being returned in matches, and also avoids any resource usage - * associated with recording the capture. + * Disable a certain capture within a query. + * + * This prevents the capture from being returned in matches, and also avoids + * any resource usage associated with recording the capture. Currently, there + * is no way to undo this. */ void ts_query_disable_capture(TSQuery *, const char *, uint32_t); /** + * Disable a certain pattern within a query. + * + * This prevents the pattern from matching and removes most of the overhead + * associated with the pattern. Currently, there is no way to undo this. + */ +void ts_query_disable_pattern(TSQuery *, uint32_t); + +/** * Create a new cursor for executing a given query. * * The cursor stores the state that is needed to iteratively search diff --git a/src/tree_sitter/array.h b/src/tree_sitter/array.h index bc77e687bf..26cb8448f1 100644 --- a/src/tree_sitter/array.h +++ b/src/tree_sitter/array.h @@ -126,12 +126,28 @@ static inline void array__splice(VoidArray *self, size_t element_size, array__reserve(self, element_size, new_size); char *contents = (char *)self->contents; - if (self->size > old_end) - memmove(contents + new_end * element_size, contents + old_end * element_size, - (self->size - old_end) * element_size); - if (new_count > 0) - memcpy((contents + index * element_size), elements, - new_count * element_size); + if (self->size > old_end) { + memmove( + contents + new_end * element_size, + contents + old_end * element_size, + (self->size - old_end) * element_size + ); + } + if (new_count > 0) { + if (elements) { + memcpy( + (contents + index * element_size), + elements, + new_count * element_size + ); + } else { + memset( + (contents + index * element_size), + 0, + new_count * element_size + ); + } + } self->size += new_count - old_count; } diff --git a/src/tree_sitter/atomic.h b/src/tree_sitter/atomic.h index 301ee36700..7bd0e850a9 100644 --- a/src/tree_sitter/atomic.h +++ b/src/tree_sitter/atomic.h @@ -12,11 +12,11 @@ static inline size_t atomic_load(const volatile size_t *p) { } static inline uint32_t atomic_inc(volatile uint32_t *p) { - return InterlockedIncrement(p); + return InterlockedIncrement((long volatile *)p); } static inline uint32_t atomic_dec(volatile uint32_t *p) { - return InterlockedDecrement(p); + return InterlockedDecrement((long volatile *)p); } #else diff --git a/src/tree_sitter/bits.h b/src/tree_sitter/bits.h index 3bec455dd1..ce7a715567 100644 --- a/src/tree_sitter/bits.h +++ b/src/tree_sitter/bits.h @@ -7,7 +7,7 @@ static inline uint32_t bitmask_for_index(uint16_t id) { return (1u << (31 - id)); } -#ifdef _WIN32 +#if defined _WIN32 && !defined __GNUC__ #include <intrin.h> diff --git a/src/tree_sitter/language.h b/src/tree_sitter/language.h index d7e17c3d70..f908b4593a 100644 --- a/src/tree_sitter/language.h +++ b/src/tree_sitter/language.h @@ -29,10 +29,12 @@ static inline bool ts_language_is_symbol_external(const TSLanguage *self, TSSymb return 0 < symbol && symbol < self->external_token_count + 1; } -static inline const TSParseAction *ts_language_actions(const TSLanguage *self, - TSStateId state, - TSSymbol symbol, - uint32_t *count) { +static inline const TSParseAction *ts_language_actions( + const TSLanguage *self, + TSStateId state, + TSSymbol symbol, + uint32_t *count +) { TableEntry entry; ts_language_table_entry(self, state, symbol, &entry); *count = entry.action_count; @@ -90,8 +92,8 @@ static inline TSStateId ts_language_next_state(const TSLanguage *self, const TSParseAction *actions = ts_language_actions(self, state, symbol, &count); if (count > 0) { TSParseAction action = actions[count - 1]; - if (action.type == TSParseActionTypeShift || action.type == TSParseActionTypeRecover) { - return action.params.state; + if (action.type == TSParseActionTypeShift) { + return action.params.extra ? state : action.params.state; } } return 0; diff --git a/src/tree_sitter/lexer.c b/src/tree_sitter/lexer.c index e2ca851973..3f8a4c0ae8 100644 --- a/src/tree_sitter/lexer.c +++ b/src/tree_sitter/lexer.c @@ -355,7 +355,7 @@ void ts_lexer_mark_end(Lexer *self) { ts_lexer__mark_end(&self->data); } -void ts_lexer_set_included_ranges( +bool ts_lexer_set_included_ranges( Lexer *self, const TSRange *ranges, uint32_t count @@ -363,6 +363,16 @@ void ts_lexer_set_included_ranges( if (count == 0 || !ranges) { ranges = &DEFAULT_RANGE; count = 1; + } else { + uint32_t previous_byte = 0; + for (unsigned i = 0; i < count; i++) { + const TSRange *range = &ranges[i]; + if ( + range->start_byte < previous_byte || + range->end_byte < range->start_byte + ) return false; + previous_byte = range->end_byte; + } } size_t size = count * sizeof(TSRange); @@ -370,6 +380,7 @@ void ts_lexer_set_included_ranges( memcpy(self->included_ranges, ranges, size); self->included_range_count = count; ts_lexer_goto(self, self->current_position); + return true; } TSRange *ts_lexer_included_ranges(const Lexer *self, uint32_t *count) { diff --git a/src/tree_sitter/lexer.h b/src/tree_sitter/lexer.h index 8cd9c26706..5e39294529 100644 --- a/src/tree_sitter/lexer.h +++ b/src/tree_sitter/lexer.h @@ -38,7 +38,7 @@ void ts_lexer_start(Lexer *); void ts_lexer_finish(Lexer *, uint32_t *); void ts_lexer_advance_to_end(Lexer *); void ts_lexer_mark_end(Lexer *); -void ts_lexer_set_included_ranges(Lexer *self, const TSRange *ranges, uint32_t count); +bool ts_lexer_set_included_ranges(Lexer *self, const TSRange *ranges, uint32_t count); TSRange *ts_lexer_included_ranges(const Lexer *self, uint32_t *count); #ifdef __cplusplus diff --git a/src/tree_sitter/parser.c b/src/tree_sitter/parser.c index f381afccab..0fa0c4195b 100644 --- a/src/tree_sitter/parser.c +++ b/src/tree_sitter/parser.c @@ -71,7 +71,6 @@ struct TSParser { unsigned accept_count; unsigned operation_count; const volatile size_t *cancellation_flag; - bool halt_on_error; Subtree old_tree; TSRangeArray included_range_differences; unsigned included_range_difference_index; @@ -1014,7 +1013,9 @@ static void ts_parser__handle_error( TSStateId state_after_missing_symbol = ts_language_next_state( self->language, state, missing_symbol ); - if (state_after_missing_symbol == 0) continue; + if (state_after_missing_symbol == 0 || state_after_missing_symbol == state) { + continue; + } if (ts_language_has_reduce_action( self->language, @@ -1067,46 +1068,6 @@ static void ts_parser__handle_error( LOG_STACK(); } -static void ts_parser__halt_parse(TSParser *self) { - LOG("halting_parse"); - LOG_STACK(); - - ts_lexer_advance_to_end(&self->lexer); - Length remaining_length = length_sub( - self->lexer.current_position, - ts_stack_position(self->stack, 0) - ); - - Subtree filler_node = ts_subtree_new_error( - &self->tree_pool, - 0, - length_zero(), - remaining_length, - remaining_length.bytes, - 0, - self->language - ); - ts_subtree_to_mut_unsafe(filler_node).ptr->visible = false; - ts_stack_push(self->stack, 0, filler_node, false, 0); - - SubtreeArray children = array_new(); - Subtree root_error = ts_subtree_new_error_node(&self->tree_pool, &children, false, self->language); - ts_stack_push(self->stack, 0, root_error, false, 0); - - Subtree eof = ts_subtree_new_leaf( - &self->tree_pool, - ts_builtin_sym_end, - length_zero(), - length_zero(), - 0, - 0, - false, - false, - self->language - ); - ts_parser__accept(self, 0, eof); -} - static bool ts_parser__recover_to_state( TSParser *self, StackVersion version, @@ -1661,7 +1622,6 @@ TSParser *ts_parser_new(void) { self->finished_tree = NULL_SUBTREE; self->reusable_node = reusable_node_new(); self->dot_graph_file = NULL; - self->halt_on_error = false; self->cancellation_flag = NULL; self->timeout_duration = 0; self->end_clock = clock_null(); @@ -1741,10 +1701,6 @@ void ts_parser_print_dot_graphs(TSParser *self, int fd) { } } -void ts_parser_halt_on_error(TSParser *self, bool should_halt_on_error) { - self->halt_on_error = should_halt_on_error; -} - const size_t *ts_parser_cancellation_flag(const TSParser *self) { return (const size_t *)self->cancellation_flag; } @@ -1761,8 +1717,12 @@ void ts_parser_set_timeout_micros(TSParser *self, uint64_t timeout_micros) { self->timeout_duration = duration_from_micros(timeout_micros); } -void ts_parser_set_included_ranges(TSParser *self, const TSRange *ranges, uint32_t count) { - ts_lexer_set_included_ranges(&self->lexer, ranges, count); +bool ts_parser_set_included_ranges( + TSParser *self, + const TSRange *ranges, + uint32_t count +) { + return ts_lexer_set_included_ranges(&self->lexer, ranges, count); } const TSRange *ts_parser_included_ranges(const TSParser *self, uint32_t *count) { @@ -1858,9 +1818,6 @@ TSTree *ts_parser_parse( unsigned min_error_cost = ts_parser__condense_stack(self); if (self->finished_tree.ptr && ts_subtree_error_cost(self->finished_tree) < min_error_cost) { break; - } else if (self->halt_on_error && min_error_cost > 0) { - ts_parser__halt_parse(self); - break; } while (self->included_range_difference_index < self->included_range_differences.size) { diff --git a/src/tree_sitter/query.c b/src/tree_sitter/query.c index 2563325248..053882cf68 100644 --- a/src/tree_sitter/query.c +++ b/src/tree_sitter/query.c @@ -19,6 +19,8 @@ typedef struct { uint8_t next_size; } Stream; +#define MAX_STEP_CAPTURE_COUNT 4 + /* * QueryStep - A step in the process of matching a query. Each node within * a query S-expression maps to one of these steps. An entire pattern is @@ -37,9 +39,11 @@ typedef struct { typedef struct { TSSymbol symbol; TSFieldId field; - uint16_t capture_id; - uint16_t depth: 15; + uint16_t capture_ids[MAX_STEP_CAPTURE_COUNT]; + uint16_t depth: 13; bool contains_captures: 1; + bool is_immediate: 1; + bool is_last: 1; } QueryStep; /* @@ -62,10 +66,12 @@ typedef struct { } SymbolTable; /* - * PatternEntry - The set of steps needed to match a particular pattern, - * represented as a slice of a shared array. These entries are stored in a - * 'pattern map' - a sorted array that makes it possible to efficiently lookup - * patterns based on the symbol for their first step. + * PatternEntry - Information about the starting point for matching a + * particular pattern, consisting of the index of the pattern within the query, + * and the index of the patter's first step in the shared `steps` array. These + * entries are stored in a 'pattern map' - a sorted array that makes it + * possible to efficiently lookup patterns based on the symbol for their first + * step. */ typedef struct { uint16_t step_index; @@ -140,6 +146,7 @@ static const TSQueryError PARENT_DONE = -1; static const uint8_t PATTERN_DONE_MARKER = UINT8_MAX; static const uint16_t NONE = UINT16_MAX; static const TSSymbol WILDCARD_SYMBOL = 0; +static const TSSymbol NAMED_WILDCARD_SYMBOL = UINT16_MAX - 1; static const uint16_t MAX_STATE_COUNT = 32; // #define LOG(...) fprintf(stderr, __VA_ARGS__) @@ -324,6 +331,49 @@ static uint16_t symbol_table_insert_name( return self->slices.size - 1; } +/************ + * QueryStep + ************/ + +static QueryStep query_step__new( + TSSymbol symbol, + uint16_t depth, + bool is_immediate +) { + return (QueryStep) { + .symbol = symbol, + .depth = depth, + .field = 0, + .capture_ids = {NONE, NONE, NONE, NONE}, + .contains_captures = false, + .is_immediate = is_immediate, + }; +} + +static void query_step__add_capture(QueryStep *self, uint16_t capture_id) { + for (unsigned i = 0; i < MAX_STEP_CAPTURE_COUNT; i++) { + if (self->capture_ids[i] == NONE) { + self->capture_ids[i] = capture_id; + break; + } + } +} + +static void query_step__remove_capture(QueryStep *self, uint16_t capture_id) { + for (unsigned i = 0; i < MAX_STEP_CAPTURE_COUNT; i++) { + if (self->capture_ids[i] == capture_id) { + self->capture_ids[i] = NONE; + while (i + 1 < MAX_STEP_CAPTURE_COUNT) { + if (self->capture_ids[i + 1] == NONE) break; + self->capture_ids[i] = self->capture_ids[i + 1]; + self->capture_ids[i + 1] = NONE; + i++; + } + break; + } + } +} + /********* * Query *********/ @@ -333,7 +383,7 @@ static uint16_t symbol_table_insert_name( // to quickly find the starting steps of all of the patterns whose root matches // that node. Each entry has two fields: a `pattern_index`, which identifies one // of the patterns in the query, and a `step_index`, which indicates the start -// offset of that pattern's steps pattern within the `steps` array. +// offset of that pattern's steps within the `steps` array. // // The entries are sorted by the patterns' root symbols, and lookups use a // binary search. This ensures that the cost of this initial lookup step @@ -399,14 +449,14 @@ static void ts_query__finalize_steps(TSQuery *self) { for (unsigned i = 0; i < self->steps.size; i++) { QueryStep *step = &self->steps.contents[i]; uint32_t depth = step->depth; - if (step->capture_id != NONE) { + if (step->capture_ids[0] != NONE) { step->contains_captures = true; } else { step->contains_captures = false; for (unsigned j = i + 1; j < self->steps.size; j++) { QueryStep *s = &self->steps.contents[j]; if (s->depth == PATTERN_DONE_MARKER || s->depth <= depth) break; - if (s->capture_id != NONE) step->contains_captures = true; + if (s->capture_ids[0] != NONE) step->contains_captures = true; } } } @@ -533,7 +583,8 @@ static TSQueryError ts_query__parse_pattern( TSQuery *self, Stream *stream, uint32_t depth, - uint32_t *capture_count + uint32_t *capture_count, + bool is_immediate ) { uint16_t starting_step_index = self->steps.size; @@ -552,7 +603,7 @@ static TSQueryError ts_query__parse_pattern( // Parse a nested list, which represents a pattern followed by // zero-or-more predicates. if (stream->next == '(' && depth == 0) { - TSQueryError e = ts_query__parse_pattern(self, stream, 0, capture_count); + TSQueryError e = ts_query__parse_pattern(self, stream, 0, capture_count, is_immediate); if (e) return e; // Parse the predicates. @@ -573,7 +624,7 @@ static TSQueryError ts_query__parse_pattern( // Parse the wildcard symbol if (stream->next == '*') { - symbol = WILDCARD_SYMBOL; + symbol = NAMED_WILDCARD_SYMBOL; stream_advance(stream); } @@ -597,24 +648,37 @@ static TSQueryError ts_query__parse_pattern( } // Add a step for the node. - array_push(&self->steps, ((QueryStep) { - .depth = depth, - .symbol = symbol, - .field = 0, - .capture_id = NONE, - .contains_captures = false, - })); + array_push(&self->steps, query_step__new(symbol, depth, is_immediate)); // Parse the child patterns stream_skip_whitespace(stream); + bool child_is_immediate = false; + uint16_t child_start_step_index = self->steps.size; for (;;) { - TSQueryError e = ts_query__parse_pattern(self, stream, depth + 1, capture_count); + if (stream->next == '.') { + child_is_immediate = true; + stream_advance(stream); + stream_skip_whitespace(stream); + } + + TSQueryError e = ts_query__parse_pattern( + self, + stream, + depth + 1, + capture_count, + child_is_immediate + ); if (e == PARENT_DONE) { + if (child_is_immediate) { + self->steps.contents[child_start_step_index].is_last = true; + } stream_advance(stream); break; } else if (e) { return e; } + + child_is_immediate = false; } } @@ -643,13 +707,7 @@ static TSQueryError ts_query__parse_pattern( stream_reset(stream, string_content); return TSQueryErrorNodeType; } - array_push(&self->steps, ((QueryStep) { - .depth = depth, - .symbol = symbol, - .field = 0, - .capture_id = NONE, - .contains_captures = false, - })); + array_push(&self->steps, query_step__new(symbol, depth, is_immediate)); if (stream->next != '"') return TSQueryErrorSyntax; stream_advance(stream); @@ -672,7 +730,13 @@ static TSQueryError ts_query__parse_pattern( // Parse the pattern uint32_t step_index = self->steps.size; - TSQueryError e = ts_query__parse_pattern(self, stream, depth, capture_count); + TSQueryError e = ts_query__parse_pattern( + self, + stream, + depth, + capture_count, + is_immediate + ); if (e == PARENT_DONE) return TSQueryErrorSyntax; if (e) return e; @@ -695,12 +759,7 @@ static TSQueryError ts_query__parse_pattern( stream_skip_whitespace(stream); // Add a step that matches any kind of node - array_push(&self->steps, ((QueryStep) { - .depth = depth, - .symbol = WILDCARD_SYMBOL, - .field = 0, - .contains_captures = false, - })); + array_push(&self->steps, query_step__new(WILDCARD_SYMBOL, depth, is_immediate)); } else { @@ -710,7 +769,7 @@ static TSQueryError ts_query__parse_pattern( stream_skip_whitespace(stream); // Parse an '@'-prefixed capture pattern - if (stream->next == '@') { + while (stream->next == '@') { stream_advance(stream); // Parse the capture name @@ -725,7 +784,8 @@ static TSQueryError ts_query__parse_pattern( capture_name, length ); - self->steps.contents[starting_step_index].capture_id = capture_id; + QueryStep *step = &self->steps.contents[starting_step_index]; + query_step__add_capture(step, capture_id); (*capture_count)++; stream_skip_whitespace(stream); @@ -794,8 +854,8 @@ TSQuery *ts_query_new( .offset = self->predicate_steps.size, .length = 0, })); - *error_type = ts_query__parse_pattern(self, &stream, 0, &capture_count); - array_push(&self->steps, ((QueryStep) { .depth = PATTERN_DONE_MARKER })); + *error_type = ts_query__parse_pattern(self, &stream, 0, &capture_count, false); + array_push(&self->steps, query_step__new(0, PATTERN_DONE_MARKER, false)); // If any pattern could not be parsed, then report the error information // and terminate. @@ -891,16 +951,31 @@ void ts_query_disable_capture( const char *name, uint32_t length ) { + // Remove capture information for any pattern step that previously + // captured with the given name. int id = symbol_table_id_for_name(&self->captures, name, length); if (id != -1) { for (unsigned i = 0; i < self->steps.size; i++) { QueryStep *step = &self->steps.contents[i]; - if (step->capture_id == id) { - step->capture_id = NONE; - } + query_step__remove_capture(step, id); + } + ts_query__finalize_steps(self); + } +} + +void ts_query_disable_pattern( + TSQuery *self, + uint32_t pattern_index +) { + // Remove the given pattern from the pattern map. Its steps will still + // be in the `steps` array, but they will never be read. + for (unsigned i = 0; i < self->pattern_map.size; i++) { + PatternEntry *pattern = &self->pattern_map.contents[i]; + if (pattern->pattern_index == pattern_index) { + array_erase(&self->pattern_map, i); + i--; } } - ts_query__finalize_steps(self); } /*************** @@ -1010,7 +1085,7 @@ static bool ts_query_cursor__first_in_progress_capture( static bool ts_query__cursor_add_state( TSQueryCursor *self, - const PatternEntry *slice + const PatternEntry *pattern ) { uint32_t list_id = capture_list_pool_acquire(&self->capture_list_pool); @@ -1037,11 +1112,11 @@ static bool ts_query__cursor_add_state( } } - LOG(" start state. pattern:%u\n", slice->pattern_index); + LOG(" start state. pattern:%u\n", pattern->pattern_index); array_push(&self->states, ((QueryState) { .capture_list_id = list_id, - .step_index = slice->step_index, - .pattern_index = slice->pattern_index, + .step_index = pattern->step_index, + .pattern_index = pattern->pattern_index, .start_depth = self->depth, .capture_count = 0, .consumed_capture_count = 0, @@ -1113,15 +1188,16 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) { return self->finished_states.size > 0; } } else { - bool can_have_later_siblings; + bool has_later_siblings; bool can_have_later_siblings_with_this_field; TSFieldId field_id = ts_tree_cursor_current_status( &self->cursor, - &can_have_later_siblings, + &has_later_siblings, &can_have_later_siblings_with_this_field ); TSNode node = ts_tree_cursor_current_node(&self->cursor); TSSymbol symbol = ts_node_symbol(node); + bool is_named = ts_node_is_named(node); if (symbol != ts_builtin_sym_error && self->query->symbol_map) { symbol = self->query->symbol_map[symbol]; } @@ -1145,43 +1221,46 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) { ) return false; LOG( - "enter node. type:%s, field:%s, row:%u state_count:%u, finished_state_count:%u, can_have_later_siblings:%d, can_have_later_siblings_with_this_field:%d\n", + "enter node. " + "type:%s, field:%s, row:%u state_count:%u, " + "finished_state_count:%u, has_later_siblings:%d, " + "can_have_later_siblings_with_this_field:%d\n", ts_node_type(node), ts_language_field_name_for_id(self->query->language, field_id), ts_node_start_point(node).row, self->states.size, self->finished_states.size, - can_have_later_siblings, + has_later_siblings, can_have_later_siblings_with_this_field ); // Add new states for any patterns whose root node is a wildcard. for (unsigned i = 0; i < self->query->wildcard_root_pattern_count; i++) { - PatternEntry *slice = &self->query->pattern_map.contents[i]; - QueryStep *step = &self->query->steps.contents[slice->step_index]; + PatternEntry *pattern = &self->query->pattern_map.contents[i]; + QueryStep *step = &self->query->steps.contents[pattern->step_index]; // If this node matches the first step of the pattern, then add a new // state at the start of this pattern. if (step->field && field_id != step->field) continue; - if (!ts_query__cursor_add_state(self, slice)) break; + if (!ts_query__cursor_add_state(self, pattern)) break; } // Add new states for any patterns whose root node matches this node. unsigned i; if (ts_query__pattern_map_search(self->query, symbol, &i)) { - PatternEntry *slice = &self->query->pattern_map.contents[i]; - QueryStep *step = &self->query->steps.contents[slice->step_index]; + PatternEntry *pattern = &self->query->pattern_map.contents[i]; + QueryStep *step = &self->query->steps.contents[pattern->step_index]; do { // If this node matches the first step of the pattern, then add a new // state at the start of this pattern. if (step->field && field_id != step->field) continue; - if (!ts_query__cursor_add_state(self, slice)) break; + if (!ts_query__cursor_add_state(self, pattern)) break; // Advance to the next pattern whose root node matches this node. i++; if (i == self->query->pattern_map.size) break; - slice = &self->query->pattern_map.contents[i]; - step = &self->query->steps.contents[slice->step_index]; + pattern = &self->query->pattern_map.contents[i]; + step = &self->query->steps.contents[pattern->step_index]; } while (step->symbol == symbol); } @@ -1191,14 +1270,23 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) { QueryStep *step = &self->query->steps.contents[state->step_index]; // Check that the node matches all of the criteria for the next - // step of the pattern.if ( + // step of the pattern. if ((uint32_t)state->start_depth + (uint32_t)step->depth != self->depth) continue; // Determine if this node matches this step of the pattern, and also // if this node can have later siblings that match this step of the // pattern. - bool node_does_match = !step->symbol || step->symbol == symbol; - bool later_sibling_can_match = can_have_later_siblings; + bool node_does_match = + step->symbol == symbol || + step->symbol == WILDCARD_SYMBOL || + (step->symbol == NAMED_WILDCARD_SYMBOL && is_named); + bool later_sibling_can_match = has_later_siblings; + if (step->is_immediate && is_named) { + later_sibling_can_match = false; + } + if (step->is_last && has_later_siblings) { + node_does_match = false; + } if (step->field) { if (step->field == field_id) { if (!can_have_later_siblings_with_this_field) { @@ -1261,11 +1349,13 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) { // If the current node is captured in this pattern, add it to the // capture list. - if (step->capture_id != NONE) { + for (unsigned j = 0; j < MAX_STEP_CAPTURE_COUNT; j++) { + uint16_t capture_id = step->capture_ids[j]; + if (step->capture_ids[j] == NONE) break; LOG( " capture node. pattern:%u, capture_id:%u\n", next_state->pattern_index, - step->capture_id + capture_id ); TSQueryCapture *capture_list = capture_list_pool_get( &self->capture_list_pool, @@ -1273,7 +1363,7 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) { ); capture_list[next_state->capture_count++] = (TSQueryCapture) { node, - step->capture_id + capture_id }; } diff --git a/src/tree_sitter/stack.c b/src/tree_sitter/stack.c index 3e842c99c3..ade1577566 100644 --- a/src/tree_sitter/stack.c +++ b/src/tree_sitter/stack.c @@ -11,7 +11,7 @@ #define MAX_NODE_POOL_SIZE 50 #define MAX_ITERATOR_COUNT 64 -#ifdef _WIN32 +#if defined _WIN32 && !defined __GNUC__ #define inline __forceinline #else #define inline static inline __attribute__((always_inline)) diff --git a/src/tree_sitter/subtree.c b/src/tree_sitter/subtree.c index 30144fa175..b98f172339 100644 --- a/src/tree_sitter/subtree.c +++ b/src/tree_sitter/subtree.c @@ -322,12 +322,9 @@ void ts_subtree_balance(Subtree self, SubtreePool *pool, const TSLanguage *langu if (tree.ptr->repeat_depth > 0) { Subtree child1 = tree.ptr->children[0]; Subtree child2 = tree.ptr->children[tree.ptr->child_count - 1]; - if ( - ts_subtree_child_count(child1) > 0 && - ts_subtree_child_count(child2) > 0 && - child1.ptr->repeat_depth > child2.ptr->repeat_depth - ) { - unsigned n = child1.ptr->repeat_depth - child2.ptr->repeat_depth; + long repeat_delta = (long)ts_subtree_repeat_depth(child1) - (long)ts_subtree_repeat_depth(child2); + if (repeat_delta > 0) { + unsigned n = repeat_delta; for (unsigned i = n / 2; i > 0; i /= 2) { ts_subtree__compress(tree, i, language, &pool->tree_stack); n -= i; @@ -344,10 +341,6 @@ void ts_subtree_balance(Subtree self, SubtreePool *pool, const TSLanguage *langu } } -static inline uint32_t ts_subtree_repeat_depth(Subtree self) { - return ts_subtree_child_count(self) ? self.ptr->repeat_depth : 0; -} - void ts_subtree_set_children( MutableSubtree self, Subtree *children, uint32_t child_count, const TSLanguage *language ) { diff --git a/src/tree_sitter/subtree.h b/src/tree_sitter/subtree.h index 79ccd92390..18c48dcbd0 100644 --- a/src/tree_sitter/subtree.h +++ b/src/tree_sitter/subtree.h @@ -206,6 +206,10 @@ static inline uint32_t ts_subtree_child_count(Subtree self) { return self.data.is_inline ? 0 : self.ptr->child_count; } +static inline uint32_t ts_subtree_repeat_depth(Subtree self) { + return self.data.is_inline ? 0 : self.ptr->repeat_depth; +} + static inline uint32_t ts_subtree_node_count(Subtree self) { return (self.data.is_inline || self.ptr->child_count == 0) ? 1 : self.ptr->node_count; } diff --git a/test/functional/ex_cmds/echo_spec.lua b/test/functional/ex_cmds/echo_spec.lua index 10c7230896..408ce52b8c 100644 --- a/test/functional/ex_cmds/echo_spec.lua +++ b/test/functional/ex_cmds/echo_spec.lua @@ -11,31 +11,57 @@ local dedent = helpers.dedent local command = helpers.command local exc_exec = helpers.exc_exec local redir_exec = helpers.redir_exec +local matches = helpers.matches + +describe(':echo :echon :echomsg :echoerr', function() + local fn_tbl = {'String', 'StringN', 'StringMsg', 'StringErr'} + local function assert_same_echo_dump(expected, input, use_eval) + for _,v in pairs(fn_tbl) do + eq(expected, use_eval and eval(v..'('..input..')') or funcs[v](input)) + end + end + local function assert_matches_echo_dump(expected, input, use_eval) + for _,v in pairs(fn_tbl) do + matches(expected, use_eval and eval(v..'('..input..')') or funcs[v](input)) + end + end -describe(':echo', function() before_each(function() clear() source([[ function String(s) return execute('echo a:s')[1:] endfunction + function StringMsg(s) + return execute('echomsg a:s')[1:] + endfunction + function StringN(s) + return execute('echon a:s') + endfunction + function StringErr(s) + try + execute 'echoerr a:s' + catch + return substitute(v:exception, '^Vim(echoerr):', '', '') + endtry + endfunction ]]) end) describe('used to represent floating-point values', function() it('dumps NaN values', function() - eq('str2float(\'nan\')', eval('String(str2float(\'nan\'))')) + assert_same_echo_dump("str2float('nan')", "str2float('nan')", true) end) it('dumps infinite values', function() - eq('str2float(\'inf\')', eval('String(str2float(\'inf\'))')) - eq('-str2float(\'inf\')', eval('String(str2float(\'-inf\'))')) + assert_same_echo_dump("str2float('inf')", "str2float('inf')", true) + assert_same_echo_dump("-str2float('inf')", "str2float('-inf')", true) end) it('dumps regular values', function() - eq('1.5', funcs.String(1.5)) - eq('1.56e-20', funcs.String(1.56000e-020)) - eq('0.0', eval('String(0.0)')) + assert_same_echo_dump('1.5', 1.5) + assert_same_echo_dump('1.56e-20', 1.56000e-020) + assert_same_echo_dump('0.0', '0.0', true) end) it('dumps special v: values', function() @@ -45,69 +71,81 @@ describe(':echo', function() eq('v:true', funcs.String(true)) eq('v:false', funcs.String(false)) eq('v:null', funcs.String(NIL)) + eq('true', eval('StringMsg(v:true)')) + eq('false', eval('StringMsg(v:false)')) + eq('null', eval('StringMsg(v:null)')) + eq('true', funcs.StringMsg(true)) + eq('false', funcs.StringMsg(false)) + eq('null', funcs.StringMsg(NIL)) + eq('true', eval('StringErr(v:true)')) + eq('false', eval('StringErr(v:false)')) + eq('null', eval('StringErr(v:null)')) + eq('true', funcs.StringErr(true)) + eq('false', funcs.StringErr(false)) + eq('null', funcs.StringErr(NIL)) end) it('dumps values with at most six digits after the decimal point', function() - eq('1.234568e-20', funcs.String(1.23456789123456789123456789e-020)) - eq('1.234568', funcs.String(1.23456789123456789123456789)) + assert_same_echo_dump('1.234568e-20', 1.23456789123456789123456789e-020) + assert_same_echo_dump('1.234568', 1.23456789123456789123456789) end) it('dumps values with at most seven digits before the decimal point', function() - eq('1234567.891235', funcs.String(1234567.89123456789123456789)) - eq('1.234568e7', funcs.String(12345678.9123456789123456789)) + assert_same_echo_dump('1234567.891235', 1234567.89123456789123456789) + assert_same_echo_dump('1.234568e7', 12345678.9123456789123456789) end) it('dumps negative values', function() - eq('-1.5', funcs.String(-1.5)) - eq('-1.56e-20', funcs.String(-1.56000e-020)) - eq('-1.234568e-20', funcs.String(-1.23456789123456789123456789e-020)) - eq('-1.234568', funcs.String(-1.23456789123456789123456789)) - eq('-1234567.891235', funcs.String(-1234567.89123456789123456789)) - eq('-1.234568e7', funcs.String(-12345678.9123456789123456789)) + assert_same_echo_dump('-1.5', -1.5) + assert_same_echo_dump('-1.56e-20', -1.56000e-020) + assert_same_echo_dump('-1.234568e-20', -1.23456789123456789123456789e-020) + assert_same_echo_dump('-1.234568', -1.23456789123456789123456789) + assert_same_echo_dump('-1234567.891235', -1234567.89123456789123456789) + assert_same_echo_dump('-1.234568e7', -12345678.9123456789123456789) end) end) describe('used to represent numbers', function() it('dumps regular values', function() - eq('0', funcs.String(0)) - eq('-1', funcs.String(-1)) - eq('1', funcs.String(1)) + assert_same_echo_dump('0', 0) + assert_same_echo_dump('-1', -1) + assert_same_echo_dump('1', 1) end) it('dumps large values', function() - eq('2147483647', funcs.String(2^31-1)) - eq('-2147483648', funcs.String(-2^31)) + assert_same_echo_dump('2147483647', 2^31-1) + assert_same_echo_dump('-2147483648', -2^31) end) end) describe('used to represent strings', function() it('dumps regular strings', function() - eq('test', funcs.String('test')) + assert_same_echo_dump('test', 'test') end) it('dumps empty strings', function() - eq('', funcs.String('')) + assert_same_echo_dump('', '') end) - it('dumps strings with \' inside', function() - eq('\'\'\'', funcs.String('\'\'\'')) - eq('a\'b\'\'', funcs.String('a\'b\'\'')) - eq('\'b\'\'d', funcs.String('\'b\'\'d')) - eq('a\'b\'c\'d', funcs.String('a\'b\'c\'d')) + it("dumps strings with ' inside", function() + assert_same_echo_dump("'''", "'''") + assert_same_echo_dump("a'b''", "a'b''") + assert_same_echo_dump("'b''d", "'b''d") + assert_same_echo_dump("a'b'c'd", "a'b'c'd") end) it('dumps NULL strings', function() - eq('', eval('String($XXX_UNEXISTENT_VAR_XXX)')) + assert_same_echo_dump('', '$XXX_UNEXISTENT_VAR_XXX', true) end) it('dumps NULL lists', function() - eq('[]', eval('String(v:_null_list)')) + assert_same_echo_dump('[]', 'v:_null_list', true) end) it('dumps NULL dictionaries', function() - eq('{}', eval('String(v:_null_dict)')) + assert_same_echo_dump('{}', 'v:_null_dict', true) end) end) @@ -129,15 +167,27 @@ describe(':echo', function() it('dumps references to built-in functions', function() eq('function', eval('String(function("function"))')) + eq("function('function')", eval('StringMsg(function("function"))')) + eq("function('function')", eval('StringErr(function("function"))')) end) it('dumps references to user functions', function() eq('Test1', eval('String(function("Test1"))')) eq('g:Test3', eval('String(function("g:Test3"))')) + eq("function('Test1')", eval("StringMsg(function('Test1'))")) + eq("function('g:Test3')", eval("StringMsg(function('g:Test3'))")) + eq("function('Test1')", eval("StringErr(function('Test1'))")) + eq("function('g:Test3')", eval("StringErr(function('g:Test3'))")) end) it('dumps references to script functions', function() eq('<SNR>2_Test2', eval('String(Test2_f)')) + eq("function('<SNR>2_Test2')", eval('StringMsg(Test2_f)')) + eq("function('<SNR>2_Test2')", eval('StringErr(Test2_f)')) + end) + + it('dump references to lambdas', function() + assert_matches_echo_dump("function%('<lambda>%d+'%)", '{-> 1234}', true) end) it('dumps partials with self referencing a partial', function() @@ -156,19 +206,23 @@ describe(':echo', function() end) it('dumps automatically created partials', function() - eq('function(\'<SNR>2_Test2\', {\'f\': function(\'<SNR>2_Test2\')})', - eval('String({"f": Test2_f}.f)')) - eq('function(\'<SNR>2_Test2\', [1], {\'f\': function(\'<SNR>2_Test2\', [1])})', - eval('String({"f": function(Test2_f, [1])}.f)')) + assert_same_echo_dump( + "function('<SNR>2_Test2', {'f': function('<SNR>2_Test2')})", + '{"f": Test2_f}.f', + true) + assert_same_echo_dump( + "function('<SNR>2_Test2', [1], {'f': function('<SNR>2_Test2', [1])})", + '{"f": function(Test2_f, [1])}.f', + true) end) it('dumps manually created partials', function() - eq('function(\'Test3\', [1, 2], {})', - eval('String(function("Test3", [1, 2], {}))')) - eq('function(\'Test3\', {})', - eval('String(function("Test3", {}))')) - eq('function(\'Test3\', [1, 2])', - eval('String(function("Test3", [1, 2]))')) + assert_same_echo_dump("function('Test3', [1, 2], {})", + "function('Test3', [1, 2], {})", true) + assert_same_echo_dump("function('Test3', [1, 2])", + "function('Test3', [1, 2])", true) + assert_same_echo_dump("function('Test3', {})", + "function('Test3', {})", true) end) it('does not crash or halt when dumping partials with reference cycles in self', @@ -225,15 +279,19 @@ describe(':echo', function() describe('used to represent lists', function() it('dumps empty list', function() - eq('[]', funcs.String({})) + assert_same_echo_dump('[]', {}) + end) + + it('dumps non-empty list', function() + assert_same_echo_dump('[1, 2]', {1,2}) end) it('dumps nested lists', function() - eq('[[[[[]]]]]', funcs.String({{{{{}}}}})) + assert_same_echo_dump('[[[[[]]]]]', {{{{{}}}}}) end) it('dumps nested non-empty lists', function() - eq('[1, [[3, [[5], 4]], 2]]', funcs.String({1, {{3, {{5}, 4}}, 2}})) + assert_same_echo_dump('[1, [[3, [[5], 4]], 2]]', {1, {{3, {{5}, 4}}, 2}}) end) it('does not error when dumping recursive lists', function() @@ -252,18 +310,18 @@ describe(':echo', function() describe('used to represent dictionaries', function() it('dumps empty dictionary', function() - eq('{}', eval('String({})')) + assert_same_echo_dump('{}', '{}', true) end) it('dumps list with two same empty dictionaries, also in partials', function() command('let d = {}') - eq('[{}, {}]', eval('String([d, d])')) + assert_same_echo_dump('[{}, {}]', '[d, d]', true) eq('[function(\'tr\', {}), {}]', eval('String([function("tr", d), d])')) eq('[{}, function(\'tr\', {})]', eval('String([d, function("tr", d)])')) end) it('dumps non-empty dictionary', function() - eq('{\'t\'\'est\': 1}', funcs.String({['t\'est']=1})) + assert_same_echo_dump("{'t''est': 1}", {["t'est"]=1}) end) it('does not error when dumping recursive dictionaries', function() @@ -297,11 +355,20 @@ describe(':echo', function() eq('<8e>', funcs.String(chr(0x8e))) eq('<c2>', funcs.String(('«'):sub(1, 1))) eq('«', funcs.String(('«'):sub(1, 2))) + + eq('<80>', funcs.StringMsg(chr(0x80))) + eq('<81>', funcs.StringMsg(chr(0x81))) + eq('<8e>', funcs.StringMsg(chr(0x8e))) + eq('<c2>', funcs.StringMsg(('«'):sub(1, 1))) + eq('«', funcs.StringMsg(('«'):sub(1, 2))) end) it('displays ASCII control characters using ^X notation', function() eq('^C', funcs.String(ctrl('c'))) eq('^A', funcs.String(ctrl('a'))) eq('^F', funcs.String(ctrl('f'))) + eq('^C', funcs.StringMsg(ctrl('c'))) + eq('^A', funcs.StringMsg(ctrl('a'))) + eq('^F', funcs.StringMsg(ctrl('f'))) end) it('prints CR, NL and tab as-is', function() eq('\n', funcs.String('\n')) @@ -311,11 +378,15 @@ describe(':echo', function() it('prints non-printable UTF-8 in <> notation', function() -- SINGLE SHIFT TWO, unicode control eq('<8e>', funcs.String(funcs.nr2char(0x8E))) + eq('<8e>', funcs.StringMsg(funcs.nr2char(0x8E))) -- Surrogate pair: U+1F0A0 PLAYING CARD BACK is represented in UTF-16 as -- 0xD83C 0xDCA0. This is not valid in UTF-8. eq('<d83c>', funcs.String(funcs.nr2char(0xD83C))) eq('<dca0>', funcs.String(funcs.nr2char(0xDCA0))) eq('<d83c><dca0>', funcs.String(funcs.nr2char(0xD83C) .. funcs.nr2char(0xDCA0))) + eq('<d83c>', funcs.StringMsg(funcs.nr2char(0xD83C))) + eq('<dca0>', funcs.StringMsg(funcs.nr2char(0xDCA0))) + eq('<d83c><dca0>', funcs.StringMsg(funcs.nr2char(0xD83C) .. funcs.nr2char(0xDCA0))) end) end) end) diff --git a/test/functional/legacy/expand_spec.lua b/test/functional/legacy/expand_spec.lua index 1b735080f4..f238128b31 100644 --- a/test/functional/legacy/expand_spec.lua +++ b/test/functional/legacy/expand_spec.lua @@ -70,6 +70,40 @@ describe('expand file name', function() call assert_match('\~', expand('%:p')) bwipe! endfunc + + func Test_expandcmd() + let $FOO = 'Test' + call assert_equal('e x/Test/y', expandcmd('e x/$FOO/y')) + unlet $FOO + + new + edit Xfile1 + call assert_equal('e Xfile1', expandcmd('e %')) + edit Xfile2 + edit Xfile1 + call assert_equal('e Xfile2', expandcmd('e #')) + edit Xfile2 + edit Xfile3 + edit Xfile4 + let bnum = bufnr('Xfile2') + call assert_equal('e Xfile2', expandcmd('e #' . bnum)) + call setline('.', 'Vim!@#') + call assert_equal('e Vim', expandcmd('e <cword>')) + call assert_equal('e Vim!@#', expandcmd('e <cWORD>')) + enew! + edit Xfile.java + call assert_equal('e Xfile.py', expandcmd('e %:r.py')) + call assert_equal('make abc.java', expandcmd('make abc.%:e')) + call assert_equal('make Xabc.java', expandcmd('make %:s?file?abc?')) + edit a1a2a3.rb + call assert_equal('make b1b2b3.rb a1a2a3 Xfile.o', expandcmd('make %:gs?a?b? %< #<.o')) + + call assert_fails('call expandcmd("make <afile>")', 'E495:') + call assert_fails('call expandcmd("make <afile>")', 'E495:') + enew + call assert_fails('call expandcmd("make %")', 'E499:') + close + endfunc ]]) end) @@ -87,4 +121,9 @@ describe('expand file name', function() call('Test_expand_tilde_filename') expected_empty() end) + + it('works with expandcmd()', function() + call('Test_expandcmd') + expected_empty() + end) end) diff --git a/test/functional/lua/treesitter_spec.lua b/test/functional/lua/treesitter_spec.lua index 494d6c84bb..f93185d1f6 100644 --- a/test/functional/lua/treesitter_spec.lua +++ b/test/functional/lua/treesitter_spec.lua @@ -245,6 +245,11 @@ static int nlua_schedule(lua_State *const lstate) (primitive_type) @type (sized_type_specifier) @type +; defaults to very magic syntax, for best compatibility +((identifier) @Identifier (match? @Identifier "^l(u)a_")) +; still support \M etc prefixes +((identifier) @Constant (match? @Constant "\M^\[A-Z_]\+$")) + ((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right) (eq? @WarningMsg.left @WarningMsg.right)) (comment) @comment @@ -263,7 +268,7 @@ static int nlua_schedule(lua_State *const lstate) [8] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, [9] = {foreground = Screen.colors.Magenta, background = Screen.colors.Red}, [10] = {foreground = Screen.colors.Red, background = Screen.colors.Red}, - + [11] = {foreground = Screen.colors.Cyan4}, }) insert(hl_text) @@ -297,10 +302,10 @@ static int nlua_schedule(lua_State *const lstate) {2:/// Schedule Lua callback on main loop's event queue} | {3:static} {3:int} nlua_schedule(lua_State *{3:const} lstate) | { | - {4:if} (lua_type(lstate, {5:1}) != LUA_TFUNCTION | + {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | || {6:lstate} != {6:lstate}) { | - lua_pushliteral(lstate, {5:"vim.schedule: expected function"}); | - {4:return} lua_error(lstate); | + {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); | + {4:return} {11:lua_error}(lstate); | } | | {7:LuaRef} cb = nlua_ref(lstate, {5:1}); | @@ -319,10 +324,10 @@ static int nlua_schedule(lua_State *const lstate) {2:/// Schedule Lua callback on main loop's event queue} | {3:static} {3:int} nlua_schedule(lua_State *{3:const} lstate) | { | - {4:if} (lua_type(lstate, {5:1}) != LUA_TFUNCTION | + {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | || {6:lstate} != {6:lstate}) { | - lua_pushliteral(lstate, {5:"vim.schedule: expected function"}); | - {4:return} lua_error(lstate); | + {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); | + {4:return} {11:lua_error}(lstate); | {8:*^/} | } | | diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 2f459145f5..5ce4bf2973 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -315,10 +315,7 @@ describe('lua stdlib', function() local a = { x = { 1, 2 }, y = 5} local b = vim.deepcopy(a) - local count = 0 - for _ in pairs(b) do count = count + 1 end - - return b.x[1] == 1 and b.x[2] == 2 and b.y == 5 and count == 2 + return b.x[1] == 1 and b.x[2] == 2 and b.y == 5 and vim.tbl_count(b) == 2 and tostring(a) ~= tostring(b) ]])) @@ -326,31 +323,22 @@ describe('lua stdlib', function() local a = {} local b = vim.deepcopy(a) - local count = 0 - for _ in pairs(b) do count = count + 1 end - - return vim.tbl_islist(b) and count == 0 and tostring(a) ~= tostring(b) + return vim.tbl_islist(b) and vim.tbl_count(b) == 0 and tostring(a) ~= tostring(b) ]])) ok(exec_lua([[ local a = vim.empty_dict() local b = vim.deepcopy(a) - local count = 0 - for _ in pairs(b) do count = count + 1 end - - return not vim.tbl_islist(b) and count == 0 + return not vim.tbl_islist(b) and vim.tbl_count(b) == 0 ]])) ok(exec_lua([[ local a = {x = vim.empty_dict(), y = {}} local b = vim.deepcopy(a) - local count = 0 - for _ in pairs(b) do count = count + 1 end - return not vim.tbl_islist(b.x) and vim.tbl_islist(b.y) - and count == 2 + and vim.tbl_count(b) == 2 and tostring(a) ~= tostring(b) ]])) end) @@ -430,10 +418,7 @@ describe('lua stdlib', function() local b = {y = 2} local c = vim.tbl_extend("keep", a, b) - local count = 0 - for _ in pairs(c) do count = count + 1 end - - return c.x == 1 and b.y == 2 and count == 2 + return c.x == 1 and b.y == 2 and vim.tbl_count(c) == 2 ]])) ok(exec_lua([[ @@ -442,10 +427,7 @@ describe('lua stdlib', function() local c = {z = 3} local d = vim.tbl_extend("keep", a, b, c) - local count = 0 - for _ in pairs(d) do count = count + 1 end - - return d.x == 1 and d.y == 2 and d.z == 3 and count == 3 + return d.x == 1 and d.y == 2 and d.z == 3 and vim.tbl_count(d) == 3 ]])) ok(exec_lua([[ @@ -453,10 +435,7 @@ describe('lua stdlib', function() local b = {x = 3} local c = vim.tbl_extend("keep", a, b) - local count = 0 - for _ in pairs(c) do count = count + 1 end - - return c.x == 1 and count == 1 + return c.x == 1 and vim.tbl_count(c) == 1 ]])) ok(exec_lua([[ @@ -464,10 +443,7 @@ describe('lua stdlib', function() local b = {x = 3} local c = vim.tbl_extend("force", a, b) - local count = 0 - for _ in pairs(c) do count = count + 1 end - - return c.x == 3 and count == 1 + return c.x == 3 and vim.tbl_count(c) == 1 ]])) ok(exec_lua([[ @@ -475,10 +451,7 @@ describe('lua stdlib', function() local b = {} local c = vim.tbl_extend("keep", a, b) - local count = 0 - for _ in pairs(c) do count = count + 1 end - - return not vim.tbl_islist(c) and count == 0 + return not vim.tbl_islist(c) and vim.tbl_count(c) == 0 ]])) ok(exec_lua([[ @@ -486,10 +459,7 @@ describe('lua stdlib', function() local b = vim.empty_dict() local c = vim.tbl_extend("keep", a, b) - local count = 0 - for _ in pairs(c) do count = count + 1 end - - return vim.tbl_islist(c) and count == 0 + return vim.tbl_islist(c) and vim.tbl_count(c) == 0 ]])) eq('Error executing lua: .../shared.lua: invalid "behavior": nil', @@ -511,6 +481,19 @@ describe('lua stdlib', function() ) end) + it('vim.tbl_count', function() + eq(0, exec_lua [[ return vim.tbl_count({}) ]]) + eq(0, exec_lua [[ return vim.tbl_count(vim.empty_dict()) ]]) + eq(0, exec_lua [[ return vim.tbl_count({nil}) ]]) + eq(0, exec_lua [[ return vim.tbl_count({a=nil}) ]]) + eq(1, exec_lua [[ return vim.tbl_count({1}) ]]) + eq(2, exec_lua [[ return vim.tbl_count({1, 2}) ]]) + eq(2, exec_lua [[ return vim.tbl_count({1, nil, 3}) ]]) + eq(1, exec_lua [[ return vim.tbl_count({a=1}) ]]) + eq(2, exec_lua [[ return vim.tbl_count({a=1, b=2}) ]]) + eq(2, exec_lua [[ return vim.tbl_count({a=1, b=nil, c=3}) ]]) + end) + it('vim.deep_equal', function() eq(true, exec_lua [[ return vim.deep_equal({a=1}, {a=1}) ]]) eq(true, exec_lua [[ return vim.deep_equal({a={b=1}}, {a={b=1}}) ]]) @@ -844,4 +827,22 @@ describe('lua stdlib', function() eq('2', funcs.luaeval "BUF") eq(2, funcs.luaeval "#vim.api.nvim_list_bufs()") end) + + it('vim.regex', function() + exec_lua [[ + re1 = vim.regex"ab\\+c" + vim.cmd "set nomagic ignorecase" + re2 = vim.regex"xYz" + ]] + eq({}, exec_lua[[return {re1:match_str("x ac")}]]) + eq({3,7}, exec_lua[[return {re1:match_str("ac abbc")}]]) + + meths.buf_set_lines(0, 0, -1, true, {"yy", "abc abbc"}) + eq({}, exec_lua[[return {re1:match_line(0, 0)}]]) + eq({0,3}, exec_lua[[return {re1:match_line(0, 1)}]]) + eq({3,7}, exec_lua[[return {re1:match_line(0, 1, 1)}]]) + eq({3,7}, exec_lua[[return {re1:match_line(0, 1, 1, 8)}]]) + eq({}, exec_lua[[return {re1:match_line(0, 1, 1, 7)}]]) + eq({0,3}, exec_lua[[return {re1:match_line(0, 1, 0, 7)}]]) + end) end) diff --git a/test/functional/options/num_options_spec.lua b/test/functional/options/num_options_spec.lua index deda5c9118..abb90b3b7c 100644 --- a/test/functional/options/num_options_spec.lua +++ b/test/functional/options/num_options_spec.lua @@ -72,7 +72,7 @@ describe(':set validation', function() should_fail('updatetime', -1, 'E487') should_fail('foldlevel', -5, 'E487') - should_fail('foldcolumn', 13, 'E474') + should_fail('foldcolumn', '13', 'E474') should_fail('conceallevel', 4, 'E474') should_fail('numberwidth', 21, 'E474') should_fail('numberwidth', 0, 'E487') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index ba1eb2113e..369b826adf 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -241,7 +241,7 @@ describe('LSP', function() it('should succeed with manual shutdown', function() local expected_callbacks = { - {NIL, "shutdown", {}, 1}; + {NIL, "shutdown", {}, 1, NIL}; {NIL, "test", {}, 1}; } test_rpc_server { diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua index f178ed1ac7..6ec45064da 100644 --- a/test/functional/ui/fold_spec.lua +++ b/test/functional/ui/fold_spec.lua @@ -295,4 +295,64 @@ describe("folded lines", function() ]]) end) + + it("work with autoresize", function() + + funcs.setline(1, 'line 1') + funcs.setline(2, 'line 2') + funcs.setline(3, 'line 3') + funcs.setline(4, 'line 4') + + feed("zfj") + command("set foldcolumn=0") + screen:expect{grid=[[ + {5:^+-- 2 lines: line 1·························}| + line 3 | + line 4 | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + -- should adapt to the current nesting of folds (e.g., 1) + command("set foldcolumn=auto:1") + screen:expect{grid=[[ + {7:+}{5:^+-- 2 lines: line 1························}| + {7: }line 3 | + {7: }line 4 | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + -- fdc should not change with a new fold as the maximum is 1 + feed("zf3j") + + screen:expect{grid=[[ + {7:+}{5:^+-- 4 lines: line 1························}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + -- relax the maximum fdc thus fdc should expand to + -- accomodate the current number of folds + command("set foldcolumn=auto:4") + screen:expect{grid=[[ + {7:+ }{5:^+-- 4 lines: line 1·······················}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end) end) diff --git a/third-party/cmake/BuildLuv.cmake b/third-party/cmake/BuildLuv.cmake index e40694e5fa..ab3e2190ab 100644 --- a/third-party/cmake/BuildLuv.cmake +++ b/third-party/cmake/BuildLuv.cmake @@ -65,6 +65,7 @@ set(LUV_PATCH_COMMAND set(LUV_CONFIGURE_COMMAND_COMMON ${CMAKE_COMMAND} ${LUV_SRC_DIR} -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} -DLUA_BUILD_TYPE=System -DWITH_SHARED_LIBUV=ON @@ -87,7 +88,8 @@ endif() if(USE_BUNDLED_LIBUV) set(LUV_CONFIGURE_COMMAND_COMMON ${LUV_CONFIGURE_COMMAND_COMMON} - -DCMAKE_PREFIX_PATH=${DEPS_INSTALL_DIR}) + -DCMAKE_PREFIX_PATH=${DEPS_INSTALL_DIR} + -DLUA_COMPAT53_DIR=${DEPS_BUILD_DIR}/src/lua-compat-5.3) endif() if(MINGW AND CMAKE_CROSSCOMPILING) @@ -107,27 +109,22 @@ elseif(MSVC) "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1} ${LUV_INCLUDE_FLAGS}" # Make sure we use the same generator, otherwise we may # accidentally end up using different MSVC runtimes - -DCMAKE_GENERATOR=${CMAKE_GENERATOR} - # Use static runtime - -DCMAKE_C_FLAGS_DEBUG="-MTd" - -DCMAKE_C_FLAGS_RELEASE="-MT") + -DCMAKE_GENERATOR=${CMAKE_GENERATOR}) else() set(LUV_CONFIGURE_COMMAND ${LUV_CONFIGURE_COMMAND_COMMON} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1} ${LUV_INCLUDE_FLAGS} -fPIC") + if(CMAKE_GENERATOR MATCHES "Unix Makefiles" AND + (CMAKE_SYSTEM_NAME MATCHES ".*BSD" OR CMAKE_SYSTEM_NAME MATCHES "DragonFly")) + set(LUV_CONFIGURE_COMMAND ${LUV_CONFIGURE_COMMAND} -DCMAKE_MAKE_PROGRAM=gmake) + endif() endif() -if(CMAKE_GENERATOR MATCHES "Unix Makefiles" AND - (CMAKE_SYSTEM_NAME MATCHES ".*BSD" OR CMAKE_SYSTEM_NAME MATCHES "DragonFly")) - set(LUV_BUILD_COMMAND ${CMAKE_COMMAND} - "-DLUA_COMPAT53_DIR=${DEPS_BUILD_DIR}/src/lua-compat-5.3" - "-DCMAKE_MAKE_PROGRAM=gmake" --build .) -else() - set(LUV_BUILD_COMMAND ${CMAKE_COMMAND} - "-DLUA_COMPAT53_DIR=${DEPS_BUILD_DIR}/src/lua-compat-5.3" --build .) -endif() -set(LUV_INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install) +set(LUV_BUILD_COMMAND + ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE}) +set(LUV_INSTALL_COMMAND + ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE}) BuildLuv(PATCH_COMMAND ${LUV_PATCH_COMMAND} CONFIGURE_COMMAND ${LUV_CONFIGURE_COMMAND} |