diff options
94 files changed, 3108 insertions, 788 deletions
diff --git a/.gitignore b/.gitignore index f4037c718a..699d493b59 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,8 @@ local.mk /runtime/doc/*.html /runtime/doc/tags.ref /runtime/doc/errors.log +# Don't include the mpack files. +/runtime/doc/*.mpack # CLion /.idea/ diff --git a/runtime/autoload/health/nvim.vim b/runtime/autoload/health/nvim.vim index c25f5ee64f..f18801ea69 100644 --- a/runtime/autoload/health/nvim.vim +++ b/runtime/autoload/health/nvim.vim @@ -129,6 +129,25 @@ function! s:check_performance() abort endif endfunction +function! s:get_tmux_option(option) abort + let cmd = 'tmux show-option -qvg '.a:option " try global scope + let out = system(cmd) + let val = substitute(out, '\v(\s|\r|\n)', '', 'g') + if v:shell_error + call health#report_error('command failed: '.cmd."\n".out) + return 'error' + elseif empty(val) + let cmd = 'tmux show-option -qvgs '.a:option " try session scope + let out = system(cmd) + let val = substitute(out, '\v(\s|\r|\n)', '', 'g') + if v:shell_error + call health#report_error('command failed: '.cmd."\n".out) + return 'error' + endif + endif + return val +endfunction + function! s:check_tmux() abort if empty($TMUX) || !executable('tmux') return @@ -136,20 +155,31 @@ function! s:check_tmux() abort call health#report_start('tmux') " check escape-time - let suggestions = ["Set escape-time in ~/.tmux.conf:\nset-option -sg escape-time 10", + let suggestions = ["set escape-time in ~/.tmux.conf:\nset-option -sg escape-time 10", \ s:suggest_faq] - let cmd = 'tmux show-option -qvgs escape-time' - let out = system(cmd) - let tmux_esc_time = substitute(out, '\v(\s|\r|\n)', '', 'g') - if v:shell_error - call health#report_error('command failed: '.cmd."\n".out) - elseif empty(tmux_esc_time) - call health#report_error('escape-time is not set', suggestions) - elseif tmux_esc_time > 300 - call health#report_error( - \ 'escape-time ('.tmux_esc_time.') is higher than 300ms', suggestions) - else - call health#report_ok('escape-time: '.tmux_esc_time.'ms') + let tmux_esc_time = s:get_tmux_option('escape-time') + if tmux_esc_time !=# 'error' + if empty(tmux_esc_time) + call health#report_error('`escape-time` is not set', suggestions) + elseif tmux_esc_time > 300 + call health#report_error( + \ '`escape-time` ('.tmux_esc_time.') is higher than 300ms', suggestions) + else + call health#report_ok('escape-time: '.tmux_esc_time) + endif + endif + + " check focus-events + let suggestions = ["(tmux 1.9+ only) Set `focus-events` in ~/.tmux.conf:\nset-option -g focus-events on"] + let tmux_focus_events = s:get_tmux_option('focus-events') + call health#report_info('Checking stuff') + if tmux_focus_events !=# 'error' + if empty(tmux_focus_events) || tmux_focus_events !=# 'on' + call health#report_warn( + \ "`focus-events` is not enabled. |'autoread'| may not work.", suggestions) + else + call health#report_ok('focus-events: '.tmux_focus_events) + endif endif " check default-terminal and $TERM @@ -203,9 +233,9 @@ function! s:check_terminal() abort call health#report_error('command failed: '.cmd."\n".out) else call health#report_info('key_backspace (kbs) terminfo entry: ' - \ .(empty(kbs_entry) ? '? (not found)' : kbs_entry)) + \ .(empty(kbs_entry) ? '? (not found)' : kbs_entry)) call health#report_info('key_dc (kdch1) terminfo entry: ' - \ .(empty(kbs_entry) ? '? (not found)' : kdch1_entry)) + \ .(empty(kbs_entry) ? '? (not found)' : kdch1_entry)) endif for env_var in ['XTERM_VERSION', 'VTE_VERSION', 'TERM_PROGRAM', 'COLORTERM', 'SSH_TTY'] if exists('$'.env_var) diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index a2e0c56f85..ea3a8242ae 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -475,6 +475,9 @@ created for extmark changes. ============================================================================== Global Functions *api-global* +nvim__get_lib_dir() *nvim__get_lib_dir()* + TODO: Documentation + nvim__id({obj}) *nvim__id()* Returns object given as argument. @@ -526,7 +529,8 @@ nvim__id_float({flt}) *nvim__id_float()* nvim__inspect_cell({grid}, {row}, {col}) *nvim__inspect_cell()* TODO: Documentation -nvim__put_attr({id}, {c0}, {c1}) *nvim__put_attr()* + *nvim__put_attr()* +nvim__put_attr({id}, {start_row}, {start_col}, {end_row}, {end_col}) Set attrs in nvim__buf_set_lua_hl callbacks TODO(bfredl): This is rather pedestrian. The final interface @@ -612,7 +616,8 @@ nvim_create_buf({listed}, {scratch}) *nvim_create_buf()* Parameters: ~ {listed} Sets 'buflisted' {scratch} Creates a "throwaway" |scratch-buffer| for - temporary work (always 'nomodified') + temporary work (always 'nomodified'). Also sets + 'nomodeline' on the buffer. Return: ~ Buffer handle, or 0 on error @@ -729,6 +734,15 @@ nvim_feedkeys({keys}, {mode}, {escape_csi}) *nvim_feedkeys()* On execution error: does not fail, but updates v:errmsg. + If you need to input sequences like <C-o> use nvim_replace_termcodes + to replace the termcodes and then pass the resulting string to + nvim_feedkeys. You'll also want to enable escape_csi. + + Example: > + :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true) + :call nvim_feedkeys(key, 'n', v:true) +< + Parameters: ~ {keys} to be typed {mode} behavior flags, see |feedkeys()| @@ -929,6 +943,23 @@ nvim_get_proc_children({pid}) *nvim_get_proc_children()* Return: ~ Array of child process ids, empty if process not found. +nvim_get_runtime_file({name}, {all}) *nvim_get_runtime_file()* + Find files in runtime directories + + 'name' can contain wildcards. For example + nvim_get_runtime_file("colors/*.vim", true) will return all + color scheme files. + + It is not an error to not find any files. An empty array is + returned then. + + Parameters: ~ + {name} pattern of files to search for + {all} whether to return all matches or only the first + + Return: ~ + list of absolute paths to the found files + nvim_get_var({name}) *nvim_get_var()* Gets a global (g:) variable. @@ -1517,6 +1548,9 @@ nvim_unsubscribe({event}) *nvim_unsubscribe()* ============================================================================== Buffer Functions *api-buffer* + +For more information on buffers, see |buffers|. + Unloaded Buffers:~ Buffers may be unloaded by the |:bunload| command or the @@ -1530,6 +1564,12 @@ affected. You can use |nvim_buf_is_loaded()| or |nvim_buf_line_count()| to check whether a buffer is loaded. + *nvim__buf_add_decoration()* +nvim__buf_add_decoration({buffer}, {ns_id}, {hl_group}, {start_row}, + {start_col}, {end_row}, {end_col}, + {virt_text}) + TODO: Documentation + *nvim__buf_redraw_range()* nvim__buf_redraw_range({buffer}, {first}, {last}) TODO: Documentation @@ -1550,7 +1590,7 @@ nvim__buf_stats({buffer}) *nvim__buf_stats()* TODO: Documentation *nvim_buf_add_highlight()* -nvim_buf_add_highlight({buffer}, {ns_id}, {hl_group}, {line}, +nvim_buf_add_highlight({buffer}, {src_id}, {hl_group}, {line}, {col_start}, {col_end}) Adds a highlight to buffer. @@ -1612,6 +1652,7 @@ nvim_buf_attach({buffer}, {send_buffer}, {opts}) *nvim_buf_attach()* {opts} Optional parameters. • on_lines: Lua callback invoked on change. Return`true`to detach. Args: + • the string "lines" • buffer handle • b:changedtick • first line that changed (zero-indexed) @@ -1626,11 +1667,13 @@ nvim_buf_attach({buffer}, {send_buffer}, {opts}) *nvim_buf_attach()* • on_changedtick: Lua callback invoked on changedtick increment without text change. Args: + • the string "changedtick" • buffer handle • b:changedtick • on_detach: Lua callback invoked on detach. Args: + • the string "detach" • buffer handle • utf_sizes: include UTF-32 and UTF-16 size @@ -1744,8 +1787,8 @@ nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {opts}) as (0,0) and (-1,-1) respectively, thus the following are equivalent: > - nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) - nvim_buf_get_extmarks(0, my_ns, [0,0], [-1,-1], {}) + nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) + nvim_buf_get_extmarks(0, my_ns, [0,0], [-1,-1], {}) < If `end` is less than `start` , traversal works backwards. @@ -1754,18 +1797,18 @@ nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {opts}) Example: > - local a = vim.api - local pos = a.nvim_win_get_cursor(0) - local ns = a.nvim_create_namespace('my-plugin') - -- Create new extmark at line 1, column 1. - local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, 0, {}) - -- Create new extmark at line 3, column 1. - local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, 0, {}) - -- Get extmarks only from line 3. - local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {}) - -- Get all marks in this buffer + namespace. - local all = a.nvim_buf_get_extmarks(0, ns, 0, -1, {}) - print(vim.inspect(ms)) + local a = vim.api + local pos = a.nvim_win_get_cursor(0) + local ns = a.nvim_create_namespace('my-plugin') + -- Create new extmark at line 1, column 1. + local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, 0, {}) + -- Create new extmark at line 3, column 1. + local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, 0, {}) + -- Get extmarks only from line 3. + local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {}) + -- Get all marks in this buffer + namespace. + local all = a.nvim_buf_get_extmarks(0, ns, 0, -1, {}) + print(vim.inspect(ms)) < Parameters: ~ @@ -1876,7 +1919,7 @@ nvim_buf_get_var({buffer}, {name}) *nvim_buf_get_var()* Variable value *nvim_buf_get_virtual_text()* -nvim_buf_get_virtual_text({buffer}, {lnum}) +nvim_buf_get_virtual_text({buffer}, {line}) Get the virtual text (annotation) for a buffer line. The virtual text is returned as list of lists, whereas the @@ -2010,7 +2053,8 @@ nvim_buf_set_var({buffer}, {name}, {value}) *nvim_buf_set_var()* {value} Variable value *nvim_buf_set_virtual_text()* -nvim_buf_set_virtual_text({buffer}, {ns_id}, {line}, {chunks}, {opts}) +nvim_buf_set_virtual_text({buffer}, {src_id}, {line}, {chunks}, + {opts}) Set the virtual text (annotation) for a buffer line. By default (and currently the only option) the text will be @@ -2334,28 +2378,31 @@ nvim_ui_detach() *nvim_ui_detach()* Removes the client from the list of UIs. |nvim_list_uis()| -nvim_ui_pum_set_height({height}) *nvim_ui_pum_set_height()* - Tells Nvim the number of elements displaying in the popumenu, - to decide <PageUp> and <PageDown> movement. - - Parameters: ~ - {height} Popupmenu height, must be greater than zero. - *nvim_ui_pum_set_bounds()* nvim_ui_pum_set_bounds({width}, {height}, {row}, {col}) + Tells Nvim the geometry of the popumenu, to align floating + windows with an external popup menu. - Tells Nvim the geometry of the popumenu, to align floating - windows with an external popup menu. Note that this method - is not to be confused with |nvim_ui_pum_set_height()|, which - sets the number of visible items in the popup menu, while - this function sets the bounding box of the popup menu, - including visual decorations such as boarders and sliders. + Note that this method is not to be confused with + |nvim_ui_pum_set_height()|, which sets the number of visible + items in the popup menu, while this function sets the bounding + box of the popup menu, including visual decorations such as + boarders and sliders. Floats need not use the same font size, + nor be anchored to exact grid corners, so one can set + floating-point numbers to the popup menu geometry. Parameters: ~ {width} Popupmenu width. {height} Popupmenu height. {row} Popupmenu row. - {height} Popupmenu height. + {col} Popupmenu height. + +nvim_ui_pum_set_height({height}) *nvim_ui_pum_set_height()* + Tells Nvim the number of elements displaying in the popumenu, + to decide <PageUp> and <PageDown> movement. + + Parameters: ~ + {height} Popupmenu height, must be greater than zero. nvim_ui_set_option({name}, {value}) *nvim_ui_set_option()* TODO: Documentation diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 92f703a8a3..f9a5d36205 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2019,11 +2019,12 @@ argidx() Number current index in the argument list arglistid([{winnr} [, {tabnr}]]) Number argument list id argv({nr} [, {winid}]) String {nr} entry of the argument list argv([-1, {winid}]) List the argument list +asin({expr}) Float arc sine of {expr} assert_beeps({cmd}) Number assert {cmd} causes a beep assert_equal({exp}, {act} [, {msg}]) Number assert {exp} is equal to {act} -assert_equalfile({fname-one}, {fname-two}) - Number assert file contents is equal +assert_equalfile({fname-one}, {fname-two} [, {msg}]) + Number assert file contents are equal assert_exception({error} [, {msg}]) Number assert {error} is in v:exception assert_fails({cmd} [, {error}]) Number assert {cmd} fails @@ -2039,7 +2040,6 @@ assert_notmatch({pat}, {text} [, {msg}]) Number assert {pat} not matches {text} assert_report({msg}) Number report a test failure assert_true({actual} [, {msg}]) Number assert {actual} is true -asin({expr}) Float arc sine of {expr} atan({expr}) Float arc tangent of {expr} atan2({expr}, {expr}) Float arc tangent of {expr1} / {expr2} browse({save}, {title}, {initdir}, {default}) @@ -2630,7 +2630,7 @@ assert_equal({expected}, {actual}, [, {msg}]) test.vim line 12: Expected 'foo' but got 'bar' ~ *assert_equalfile()* -assert_equalfile({fname-one}, {fname-two}) +assert_equalfile({fname-one}, {fname-two} [, {msg}]) When the files {fname-one} and {fname-two} do not contain exactly the same text an error message is added to |v:errors|. Also see |assert-return|. @@ -9367,7 +9367,7 @@ wordcount() *wordcount()* (only in Visual mode) visual_chars Number of chars visually selected (only in Visual mode) - visual_words Number of chars visually selected + visual_words Number of words visually selected (only in Visual mode) diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 9deaf26983..9f878ad8c7 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -58,6 +58,11 @@ Nvim provides the |vim.lsp.omnifunc| 'omnifunc' handler which allows " Use LSP omni-completion in Python files. autocmd Filetype python setlocal omnifunc=v:lua.vim.lsp.omnifunc +If a function has a `*_sync` variant, it's primarily intended for being run +automatically on file save. E.g. code formatting: > + + " Auto-format *.rs files prior to saving them + autocmd BufWritePre *.rs lua vim.lsp.buf.formatting_sync(nil, 1000) ================================================================================ FAQ *lsp-faq* @@ -164,21 +169,36 @@ name: > LSP HIGHLIGHT *lsp-highlight* *hl-LspDiagnosticsError* -LspDiagnosticsError used for "Error" diagnostic virtual text +LspDiagnosticsError used for "Error" diagnostic virtual text *hl-LspDiagnosticsErrorSign* -LspDiagnosticsErrorSign used for "Error" diagnostic signs in sign column +LspDiagnosticsErrorSign used for "Error" diagnostic signs in sign + column + *hl-LspDiagnosticsErrorFloating* +LspDiagnosticsErrorFloating used for "Error" diagnostic messages in the + diagnostics float *hl-LspDiagnosticsWarning* -LspDiagnosticsWarning used for "Warning" diagnostic virtual text +LspDiagnosticsWarning used for "Warning" diagnostic virtual text *hl-LspDiagnosticsWarningSign* -LspDiagnosticsWarningSign used for "Warning" diagnostic signs in sign column +LspDiagnosticsWarningSign used for "Warning" diagnostic signs in sign + column + *hl-LspDiagnosticsWarningFloating* +LspDiagnosticsWarningFloating used for "Warning" diagnostic messages in the + diagnostics float *hl-LspDiagnosticsInformation* -LspDiagnosticInformation used for "Information" diagnostic virtual text +LspDiagnosticsInformation used for "Information" diagnostic virtual text *hl-LspDiagnosticsInformationSign* -LspDiagnosticInformationSign used for "Information" signs in sign column +LspDiagnosticsInformationSign used for "Information" signs in sign column + *hl-LspDiagnosticsInformationFloating* +LspDiagnosticsInformationFloating used for "Information" diagnostic messages in + the diagnostics float *hl-LspDiagnosticsHint* -LspDiagnosticHint used for "Hint" diagnostic virtual text +LspDiagnosticsHint used for "Hint" diagnostic virtual text *hl-LspDiagnosticsHintSign* -LspDiagnosticHintSign used for "Hint" diagnostic signs in sign column +LspDiagnosticsHintSign used for "Hint" diagnostic signs in sign + column + *hl-LspDiagnosticsHintFloating* +LspDiagnosticsHintFloating used for "Hint" diagnostic messages in the + diagnostics float *hl-LspReferenceText* LspReferenceText used for highlighting "text" references *hl-LspReferenceRead* @@ -344,13 +364,12 @@ buf_is_attached({bufnr}, {client_id}) *vim.lsp.buf_is_attached()* {client_id} (number) the client id buf_notify({bufnr}, {method}, {params}) *vim.lsp.buf_notify()* - Sends a notification to all servers attached to the buffer. + Send a notification to a server Parameters: ~ - {bufnr} (optional, number) Buffer handle, or 0 for - current - {method} (string) LSP method name - {params} (string) Parameters to send to the server + {bufnr} [number] (optional): The number of the buffer + {method} [string]: Name of the request method + {params} [string]: Arguments to send to the server Return: ~ true if any client returns true; false otherwise @@ -452,6 +471,10 @@ client() *vim.lsp.client* client_is_stopped({client_id}) *vim.lsp.client_is_stopped()* TODO: Documentation + *vim.lsp.define_default_sign()* +define_default_sign({name}, {properties}) + TODO: Documentation + err_message({...}) *vim.lsp.err_message()* TODO: Documentation @@ -527,7 +550,7 @@ once({fn}) *vim.lsp.once()* optional_validator({fn}) *vim.lsp.optional_validator()* TODO: Documentation -request({method}, {params}, {callback}) *vim.lsp.request()* +request({method}, {params}, {callback}, {bufnr}) *vim.lsp.request()* TODO: Documentation resolve_bufnr({bufnr}) *vim.lsp.resolve_bufnr()* @@ -563,7 +586,7 @@ start_client({config}) *vim.lsp.start_client()* {root_dir} (required, string) Directory where the LSP server will base its rootUri on initialization. - {cmd} (required, list treated like + {cmd} (required, string or list treated like |jobstart()|) Base command that initiates the LSP client. {cmd_cwd} (string, default=|getcwd()|) Directory @@ -667,7 +690,7 @@ stop_client({client_id}, {force}) *vim.lsp.stop_client()* object. To stop all clients: > - vim.lsp.stop_client(lsp.get_active_clients()) + vim.lsp.stop_client(lsp.get_active_clients()) < By default asks the server to shutdown, unless stop was @@ -689,9 +712,6 @@ unsupported_method({method}) *vim.lsp.unsupported_method()* validate_client_config({config}) *vim.lsp.validate_client_config()* TODO: Documentation -validate_command({input}) *vim.lsp.validate_command()* - TODO: Documentation - validate_encoding({encoding}) *vim.lsp.validate_encoding()* TODO: Documentation @@ -731,6 +751,9 @@ transform_schema_to_table() ============================================================================== Lua module: vim.lsp.buf *lsp-buf* +clear_references() *vim.lsp.buf.clear_references()* + TODO: Documentation + code_action({context}) *vim.lsp.buf.code_action()* TODO: Documentation @@ -744,14 +767,32 @@ definition() *vim.lsp.buf.definition()* TODO: Documentation document_highlight() *vim.lsp.buf.document_highlight()* - TODO: Documentation + 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: +> + 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()]] +< document_symbol() *vim.lsp.buf.document_symbol()* TODO: Documentation +execute_command({command}) *vim.lsp.buf.execute_command()* + TODO: Documentation + formatting({options}) *vim.lsp.buf.formatting()* TODO: Documentation + *vim.lsp.buf.formatting_sync()* +formatting_sync({options}, {timeout_ms}) + Perform |vim.lsp.buf.formatting()| synchronously. + + Useful for running on save, to make sure buffer is formatted + prior to being saved. {timeout_ms} is passed on to + |vim.lsp.buf_request_sync()|. + hover() *vim.lsp.buf.hover()* TODO: Documentation @@ -778,8 +819,8 @@ 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. + Return: ~ + `true` if server responds. signature_help() *vim.lsp.buf.signature_help()* TODO: Documentation @@ -789,10 +830,22 @@ type_definition() *vim.lsp.buf.type_definition()* workspace_symbol({query}) *vim.lsp.buf.workspace_symbol()* Lists all symbols in the current workspace in the quickfix - window. The list is filtered against the optional argument - {query}; if the argument is omitted from the call, the user - is prompted to enter a string on the command line. An empty - string means no filtering is done. + window. + + The list is filtered against the optional argument {query}; if + the argument is omitted from the call, the user is prompted to + enter a string on the command line. An empty string means no + filtering is done. + +incoming_calls() *vim.lsp.buf.incoming_calls()* + Lists all the call sites of the symbol under the cursor in the + |quickfix| window. If the symbol can resolve to multiple + items, the user can pick one in the |inputlist|. + +outgoing_calls() *vim.lsp.buf.outgoing_calls()* + Lists all the items that are called by the symbol under the + cursor in the |quickfix| window. If the symbol can resolve to + multiple items, the user can pick one in the |inputlist|. ============================================================================== @@ -805,14 +858,6 @@ err_message({...}) *vim.lsp.callbacks.err_message()* location_callback({_}, {method}, {result}) TODO: Documentation - *vim.lsp.callbacks.log_message()* -log_message({_}, {_}, {result}, {client_id}) - TODO: Documentation - - *vim.lsp.callbacks.signature_help_to_preview_contents()* -signature_help_to_preview_contents({input}) - TODO: Documentation - ============================================================================== Lua module: vim.lsp.log *lsp-log* @@ -844,8 +889,17 @@ create_and_start_client({cmd}, {cmd_args}, {handlers}, encode_and_send({payload}) *vim.lsp.rpc.encode_and_send()* TODO: Documentation -force_env_list({final_env}) *vim.lsp.rpc.force_env_list()* - TODO: Documentation +env_merge({env}) *vim.lsp.rpc.env_merge()* + Merges current process env with the given env and returns the + result as a list of "k=v" strings. +> + + Example: +< + + > in: { PRODUCTION="false", PATH="/usr/bin/", PORT=123, HOST="0.0.0.0", } + out: { "PRODUCTION=false", "PATH=/usr/bin/", "PORT=123", "HOST=0.0.0.0", } +< *vim.lsp.rpc.format_message_with_content_length()* format_message_with_content_length({encoded_message}) @@ -919,6 +973,10 @@ try_call({errkind}, {fn}, {...}) *vim.lsp.rpc.try_call()* ============================================================================== Lua module: vim.lsp.util *lsp-util* + *vim.lsp.util.apply_syntax_to_region()* +apply_syntax_to_region({ft}, {start}, {finish}) + TODO: Documentation + *vim.lsp.util.apply_text_document_edit()* apply_text_document_edit({text_document_edit}) TODO: Documentation @@ -931,59 +989,61 @@ apply_text_edits({text_edits}, {bufnr}) apply_workspace_edit({workspace_edit}) TODO: Documentation - *vim.lsp.util.diagnostics_by_buf* -diagnostics_by_buf - A table containing diagnostics grouped by buf. - - {<bufnr>: {diagnostics}} - - {diagnostics} is an array of diagnostics. - - By default this is populated by the - `textDocument/publishDiagnostics` callback via - |vim.lsp.util.buf_diagnostics_save_positions|. - - It contains entries for active buffers. Once a buffer is - detached the entries for it are discarded. - 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: +buf_clear_references({bufnr}) *vim.lsp.util.buf_clear_references()* + TODO: Documentation + +buf_diagnostics_count({kind}) *vim.lsp.util.buf_diagnostics_count()* + Returns the number of diagnostics of given kind for current + buffer. + + Useful for showing diagnostic counts in statusline. eg: > + function! LspStatus() abort let sl = '' - if luaeval('vim.lsp.buf.server_ready()') + if luaeval('not vim.tbl_isempty(vim.lsp.buf_get_clients(0))') let sl.='%#MyStatuslineLSP#E:' - let sl.='%#MyStatuslineLSPErrors#%{luaeval("vim.lsp.util.buf_diagnostics_count(\"Error\")")}' + 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\")")}' + 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() + let &l:statusline = '%#MyStatuslineLSP#LSP '.LspStatus() < Parameters: ~ - {kind} Diagnostic severity kind: Error, Warning, Information or Hint. + {kind} Diagnostic severity kind: See + |vim.lsp.protocol.DiagnosticSeverity| -buf_clear_references({bufnr}) *vim.lsp.util.buf_clear_references()* - TODO: Documentation + Return: ~ + Count of diagnostics - *vim.lsp.util.buf_diagnostics_save()* + *vim.lsp.util.buf_diagnostics_save_positions()* buf_diagnostics_save_positions({bufnr}, {diagnostics}) - Stores the diagnostics into |vim.lsp.util.diagnostics_by_buf| + Saves the diagnostics (Diagnostic[]) into diagnostics_by_buf Parameters: ~ - {bufr} bufnr for which the diagnostics are for. - {diagnostics} Diagnostics[] received from the - langauge server. + {bufnr} bufnr for which the diagnostics are for. + {diagnostics} Diagnostics[] received from the language + server. + + *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 commands: +> + sign define LspDiagnosticsErrorSign text=E texthl=LspDiagnosticsError linehl= numhl= + sign define LspDiagnosticsWarningSign text=W texthl=LspDiagnosticsWarning linehl= numhl= + sign define LspDiagnosticsInformationSign text=I texthl=LspDiagnosticsInformation linehl= numhl= + sign define LspDiagnosticsHintSign text=H texthl=LspDiagnosticsHint linehl= numhl= +< *vim.lsp.util.buf_diagnostics_underline()* buf_diagnostics_underline({bufnr}, {diagnostics}) @@ -993,17 +1053,9 @@ 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 commands: -> -sign define LspDiagnosticsErrorSign text=E texthl=LspDiagnosticsError linehl= numhl= -sign define LspDiagnosticsWarningSign text=W texthl=LspDiagnosticsWarning linehl= numhl= -sign define LspDiagnosticsInformationSign text=I texthl=LspDiagnosticsInformation linehl= numhl= -sign define LspDiagnosticsHintSign text=H texthl=LspDiagnosticsHint linehl= numhl= -< - + *vim.lsp.util.buf_highlight_references()* +buf_highlight_references({bufnr}, {references}) + TODO: Documentation character_offset({buf}, {row}, {col}) *vim.lsp.util.character_offset()* TODO: Documentation @@ -1016,13 +1068,33 @@ close_preview_autocmd({events}, {winnr}) convert_input_to_markdown_lines({input}, {contents}) TODO: Documentation + *vim.lsp.util.convert_signature_help_to_markdown_lines()* +convert_signature_help_to_markdown_lines({signature_help}) + TODO: Documentation + + *vim.lsp.util.diagnostics_group_by_line()* +diagnostics_group_by_line({diagnostics}) + TODO: Documentation + *vim.lsp.util.extract_completion_items()* extract_completion_items({result}) TODO: Documentation *vim.lsp.util.fancy_floating_markdown()* fancy_floating_markdown({contents}, {opts}) - TODO: Documentation + Convert markdown into syntax highlighted regions by stripping + the code blocks and converting them into highlighted code. + This will by default insert a blank line separator after those + code block regions to improve readability. The result is shown + in a floating preview TODO: refactor to separate + stripping/converting and make use of open_floating_preview + + Parameters: ~ + {contents} table of lines to show in window + {opts} dictionary with optional fields + + Return: ~ + width,height size of float find_window_by_var({name}, {value}) *vim.lsp.util.find_window_by_var()* TODO: Documentation @@ -1034,10 +1106,33 @@ focusable_float({unique_name}, {fn}) *vim.lsp.util.focusable_float()* focusable_preview({unique_name}, {fn}) TODO: Documentation +get_completion_word({item}) *vim.lsp.util.get_completion_word()* + TODO: Documentation + *vim.lsp.util.get_current_line_to_cursor()* get_current_line_to_cursor() TODO: Documentation +get_effective_tabstop({bufnr}) *vim.lsp.util.get_effective_tabstop()* + Get visual width of tabstop. + + Parameters: ~ + {bufnr} (optional, number): Buffer handle, defaults to + current + + Return: ~ + (number) tabstop visual width + + See also: ~ + |softtabstop| + + *vim.lsp.util.get_line_byte_from_position()* +get_line_byte_from_position({bufnr}, {position}) + TODO: Documentation + +get_line_diagnostics() *vim.lsp.util.get_line_diagnostics()* + TODO: Documentation + *vim.lsp.util.get_severity_highlight_name()* get_severity_highlight_name({severity}) TODO: Documentation @@ -1045,9 +1140,6 @@ get_severity_highlight_name({severity}) jump_to_location({location}) *vim.lsp.util.jump_to_location()* TODO: Documentation -preview_location({location}) *vim.lsp.util.preview_location()* - TODO: Documentation - locations_to_items({locations}) *vim.lsp.util.locations_to_items()* TODO: Documentation @@ -1055,9 +1147,22 @@ locations_to_items({locations}) *vim.lsp.util.locations_to_items()* make_floating_popup_options({width}, {height}, {opts}) TODO: Documentation + *vim.lsp.util.make_formatting_params()* +make_formatting_params({options}) + TODO: Documentation + +make_position_param() *vim.lsp.util.make_position_param()* + TODO: Documentation + make_position_params() *vim.lsp.util.make_position_params()* TODO: Documentation +make_range_params() *vim.lsp.util.make_range_params()* + TODO: Documentation + +make_text_document_params() *vim.lsp.util.make_text_document_params()* + TODO: Documentation + npcall({fn}, {...}) *vim.lsp.util.npcall()* TODO: Documentation @@ -1066,15 +1171,49 @@ ok_or_nil({status}, {...}) *vim.lsp.util.ok_or_nil()* *vim.lsp.util.open_floating_preview()* open_floating_preview({contents}, {filetype}, {opts}) + Show contents in a floating window + + Parameters: ~ + {contents} table of lines to show in window + {filetype} string of filetype to set for opened buffer + {opts} dictionary with optional fields + + Return: ~ + bufnr,winnr buffer and window number of floating window or + nil + +parse_snippet({input}) *vim.lsp.util.parse_snippet()* + TODO: Documentation + +parse_snippet_rec({input}, {inner}) *vim.lsp.util.parse_snippet_rec()* + TODO: Documentation + +preview_location({location}) *vim.lsp.util.preview_location()* + Preview a location in a floating windows + + behavior depends on type of location: + • for Location, range is shown (e.g., function definition) + • for LocationLink, targetRange is shown (e.g., body of + function definition) + + Parameters: ~ + {location} a single Location or LocationLink + + Return: ~ + bufnr,winnr buffer and window number of floating window or + nil + + *vim.lsp.util.remove_unmatch_completion_items()* +remove_unmatch_completion_items({items}, {prefix}) TODO: Documentation set_lines({lines}, {A}, {B}, {new_lines}) *vim.lsp.util.set_lines()* TODO: Documentation -set_loclist({locations}) *vim.lsp.util.set_loclist()* +set_loclist({items}) *vim.lsp.util.set_loclist()* TODO: Documentation -set_qflist({locations}) *vim.lsp.util.set_qflist()* +set_qflist({items}) *vim.lsp.util.set_qflist()* TODO: Documentation show_line_diagnostics() *vim.lsp.util.show_line_diagnostics()* @@ -1083,11 +1222,20 @@ show_line_diagnostics() *vim.lsp.util.show_line_diagnostics()* sort_by_key({fn}) *vim.lsp.util.sort_by_key()* TODO: Documentation +sort_completion_items({items}) *vim.lsp.util.sort_completion_items()* + TODO: Documentation + split_lines({value}) *vim.lsp.util.split_lines()* TODO: Documentation +symbols_to_items({symbols}, {bufnr}) *vim.lsp.util.symbols_to_items()* + Convert symbols to quickfix list items + + Parameters: ~ + {symbols} DocumentSymbol[] or SymbolInformation[] + *vim.lsp.util.text_document_completion_list_to_complete_items()* -text_document_completion_list_to_complete_items({result}) +text_document_completion_list_to_complete_items({result}, {prefix}) TODO: Documentation trim_empty_lines({lines}) *vim.lsp.util.trim_empty_lines()* @@ -1097,7 +1245,4 @@ trim_empty_lines({lines}) *vim.lsp.util.trim_empty_lines()* try_trim_markdown_code_blocks({lines}) TODO: Documentation -validate_lsp_position({pos}) *vim.lsp.util.validate_lsp_position()* - TODO: Documentation - vim:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 5a49d36503..60c7a60d25 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -95,6 +95,66 @@ Note: plugins using shell which will not work with paths containing semicolons it is better to not have them in 'runtimepath' at all. +============================================================================== +Lua Syntax Information *lua-syntax-help* + +While Lua has a simple syntax, there are a few things to understand, +particularly when looking at the documentation above. + + *lua-syntax-call-function* + +Lua functions can be called in multiple ways. Consider the function: > + + local example_func = function(a, b) + print("A is: ", a) + print("B is: ", b) + end + + +The first way to call a function is: > + + example_func(1, 2) + -- ==== Result ==== + -- A is: 1 + -- B is: 2 +< + This way of calling a function is familiar to most scripting languages. + In Lua, it's important to understand that any function arguments that are + not supplied are automatically set to `nil`. For example: > + + example_func(1) + -- ==== Result ==== + -- A is: 1 + -- B is: nil +< + + Additionally, if any extra parameters are passed, they are discarded + completely. + +In Lua, it is also possible (when only one argument is passed) to call the +function without any parentheses. This is most often used to approximate +"keyword"-style arguments with a single dictionary. For example: > + + local func_with_opts = function(opts) + local will_do_foo = opts.foo + local filename = opts.filename + + ... + end + + func_with_opts { foo = true, filename = "hello.world" } +< + + In this style, each "parameter" is passed via keyword. It is still valid + to call the function in this style: > + + func_with_opts({ foo = true, filename = "hello.world" }) +< + + But often in the documentation, you will see the former rather than the + latter style, due to its brevity (this is vim after all!). + + ------------------------------------------------------------------------------ LUA PLUGIN EXAMPLE *lua-require-example* @@ -415,6 +475,8 @@ To avoid the error use |vim.schedule_wrap()| to defer the callback: > vim.api.nvim_command('echomsg "test"') end)) +(For one-shot timers, see |vim.defer_fn()|, which automatically adds the wrapping.) + Example: repeating timer 1. Save this code to a file. 2. Execute it with ":luafile %". > @@ -512,6 +574,9 @@ retained for the lifetime of a buffer but this is subject to change. A plugin should keep a reference to the parser object as long as it wants incremental updates. +Parser methods *lua-treesitter-parser* + +tsparser:parse() *tsparser:parse()* Whenever you need to access the current syntax tree, parse the buffer: > tstree = parser:parse() @@ -528,6 +593,16 @@ shouldn't be done directly in the change callback anyway as they will be very frequent. Rather a plugin that does any kind of analysis on a tree should use a timer to throttle too frequent updates. +tsparser:set_included_ranges(ranges) *tsparser:set_included_ranges()* + Changes the ranges the parser should consider. This is used for + language injection. `ranges` should be of the form (all zero-based): > + { + {start_node, end_node}, + ... + } +< + NOTE: `start_node` and `end_node` are both inclusive. + Tree methods *lua-treesitter-tree* tstree:root() *tstree:root()* @@ -698,25 +773,26 @@ VIM.HIGHLIGHT *lua-highlight* Nvim includes a function for highlighting a selection on yank (see for example https://github.com/machakann/vim-highlightedyank). To enable it, add > - au TextYankPost * silent! lua require'vim.highlight'.on_yank() + au TextYankPost * silent! lua vim.highlight.on_yank() < to your `init.vim`. You can customize the highlight group and the duration of the highlight via > - au TextYankPost * silent! lua require'vim.highlight'.on_yank("IncSearch", 500) + au TextYankPost * silent! lua vim.highlight.on_yank {higroup="IncSearch", timeout=150} < If you want to exclude visual selections from highlighting on yank, use > -au TextYankPost * silent! lua return (not vim.v.event.visual) and require'vim.highlight'.on_yank() + au TextYankPost * silent! lua vim.highlight.on_yank {on_visual=false} < -vim.highlight.on_yank([{higroup}, {timeout}, {event}]) - *vim.highlight.on_yank()* - Highlights the yanked text. Optional arguments are the highlight group - to use ({higroup}, default `"IncSearch"`), the duration of highlighting - in milliseconds ({timeout}, default `500`), and the event structure - that is fired ({event}, default `vim.v.event`). - +vim.highlight.on_yank({opts}) *vim.highlight.on_yank()* + Highlights the yanked text. The fields of the optional dict {opts} + control the highlight: + - {higroup} highlight group for yanked region (default `"IncSearch"`) + - {timeout} time in ms before highlight is cleared (default `150`) + - {on_macro} highlight when executing macro (default `false`) + - {on_visual} highlight when yanking visual selection (default `true`) + - {event} event structure (default `vim.v.event`) vim.highlight.range({bufnr}, {ns}, {higroup}, {start}, {finish}, {rtype}, {inclusive}) *vim.highlight.range()* @@ -726,7 +802,6 @@ vim.highlight.range({bufnr}, {ns}, {higroup}, {start}, {finish}, {rtype}, {inclu or blockwise, see |setreg|; default to characterwise) and whether the range is inclusive (default false). - ------------------------------------------------------------------------------ VIM.REGEX *lua-regex* @@ -846,6 +921,9 @@ vim.defer_fn({fn}, {timeout}) *vim.defer_fn* Defers calling {fn} until {timeout} ms passes. Use to do a one-shot timer that calls {fn}. + Note: The {fn} is |schedule_wrap|ped automatically, so API functions are + safe to call. + Parameters: ~ {fn} Callback to call once {timeout} expires {timeout} Time in ms to wait before calling {fn} @@ -1018,6 +1096,9 @@ inspect({object}, {options}) *vim.inspect()* https://github.com/kikito/inspect.lua https://github.com/mpeterv/vinspect +make_meta_accessor({get}, {set}, {del}) *vim.make_meta_accessor()* + TODO: Documentation + paste({lines}, {phase}) *vim.paste()* Paste handler, invoked by |nvim_paste()| when a conforming UI (such as the |TUI|) pastes text into the editor. @@ -1133,7 +1214,7 @@ list_extend({dst}, {src}, {start}, {finish}) *vim.list_extend()* |vim.tbl_extend()| pesc({s}) *vim.pesc()* - Escapes magic chars in a Lua pattern string. + Escapes magic chars in a Lua pattern. Parameters: ~ {s} String to escape @@ -1177,8 +1258,7 @@ startswith({s}, {prefix}) *vim.startswith()* tbl_add_reverse_lookup({o}) *vim.tbl_add_reverse_lookup()* Add the reverse lookup values to an existing table. For - example: `tbl_add_reverse_lookup { A = 1 } == { [1] = 'A', A = - 1 }` + example: tbl_add_reverse_lookup { A = 1 } == { [1] = 'A , A = 1 }` Parameters: ~ {o} table The table to add the reverse to. @@ -1193,6 +1273,37 @@ tbl_contains({t}, {value}) *vim.tbl_contains()* Return: ~ true if `t` contains `value` +tbl_count({t}) *vim.tbl_count()* + Counts the number of non-nil values in table `t` . +> + + vim.tbl_count({ a=1, b=2 }) => 2 + vim.tbl_count({ 1, 2 }) => 2 +< + + Parameters: ~ + {t} Table + + Return: ~ + Number that is the number of the value in table + + See also: ~ + https://github.com/Tieske/Penlight/blob/master/lua/pl/tablex.lua + +tbl_deep_extend({behavior}, {...}) *vim.tbl_deep_extend()* + Merges recursively two or more map-like tables. + + Parameters: ~ + {behavior} Decides what to do if a key is found in more + than one map: + • "error": raise an error + • "keep": use value from the leftmost map + • "force": use value from the rightmost map + {...} Two or more map-like tables. + + See also: ~ + |tbl_extend()| + tbl_extend({behavior}, {...}) *vim.tbl_extend()* Merges two or more map-like tables. @@ -1207,6 +1318,13 @@ tbl_extend({behavior}, {...}) *vim.tbl_extend()* See also: ~ |extend()| +tbl_filter({func}, {t}) *vim.tbl_filter()* + Filter a table using a predicate function + + Parameters: ~ + {func} function or callable table + {t} table + tbl_flatten({t}) *vim.tbl_flatten()* Creates a copy of a list-like table such that any nested tables are "unrolled" and appended to the result. @@ -1225,11 +1343,19 @@ tbl_isempty({t}) *vim.tbl_isempty()* Fromhttps://github.com/premake/premake-core/blob/master/src/base/table.lua@paramt Table to check tbl_islist({t}) *vim.tbl_islist()* - Table + Determine whether a Lua table can be treated as an array. + + An empty table `{}` will default to being treated as an array. + Use `vim.emtpy_dict()` to create a table treated as an empty + dict. Empty tables returned by `rpcrequest()` and `vim.fn` + functions can be checked using this function whether they + represent empty API arrays and vimL lists. + + Parameters: ~ + {t} Table Return: ~ - true: A non-empty array, false: A non-empty table, nil: An - empty table + `true` if array-like table, else `false` . tbl_keys({t}) *vim.tbl_keys()* Return a list of all keys used in a table. However, the order @@ -1244,6 +1370,13 @@ tbl_keys({t}) *vim.tbl_keys()* See also: ~ Fromhttps://github.com/premake/premake-core/blob/master/src/base/table.lua +tbl_map({func}, {t}) *vim.tbl_map()* + Apply a function to all values of a table. + + Parameters: ~ + {func} function or callable table + {t} table + tbl_values({t}) *vim.tbl_values()* Return a list of all values used in a table. However, the order of the return table of values is not guaranteed. @@ -1288,12 +1421,12 @@ validate({opt}) *vim.validate()* => NOP (success) < > - vim.validate{arg1={1, 'table'}} - => error('arg1: expected table, got number') + vim.validate{arg1={1, 'table'}} + => error('arg1: expected table, got number') < > - vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}} - => error('arg1: expected even number, got 3') + vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}} + => error('arg1: expected even number, got 3') < Parameters: ~ diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 8d4f76d3dd..e1beea0fed 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -3947,6 +3947,8 @@ A jump table for the options with a short description can be found at |Q_op|. When on allow some options that are an expression to be set in the modeline. Check the option for whether it is affected by 'modelineexpr'. Also see |modeline|. + This option cannot be set from a |modeline| or in the |sandbox|, for + security reasons. *'modelines'* *'mls'* 'modelines' 'mls' number (default 5) @@ -5807,7 +5809,9 @@ A jump table for the options with a short description can be found at |Q_op|. When the option starts with "%!" then it is used as an expression, evaluated and the result is used as the option value. Example: > :set statusline=%!MyStatusLine() -< The result can contain %{} items that will be evaluated too. +< The *g:statusline_winid* variable will be set to the |window-ID| of the + window that the status line belongs to. + The result can contain %{} items that will be evaluated too. Note that the "%!" expression is evaluated in the context of the current window and buffer, while %{} items are evaluated in the context of the window that the statusline belongs to. @@ -5936,13 +5940,15 @@ A jump table for the options with a short description can be found at |Q_op|. become empty. This will make a group like the following disappear completely from the statusline when none of the flags are set. > :set statusline=...%(\ [%M%R%H]%)... -< *g:actual_curbuf* - Beware that an expression is evaluated each and every time the status - line is displayed. The current buffer and current window will be set - temporarily to that of the window (and buffer) whose statusline is - currently being drawn. The expression will evaluate in this context. - The variable "g:actual_curbuf" is set to the `bufnr()` number of the - real current buffer. +< Beware that an expression is evaluated each and every time the status + line is displayed. + *g:actual_curbuf* *g:actual_curwin* + The current buffer and current window will be set temporarily to that + of the window (and buffer) whose statusline is currently being drawn. + The expression will evaluate in this context. The variable + "g:actual_curbuf" is set to the `bufnr()` number of the real current + buffer and "g:actual_curwin" to the |window-ID| of the real current + window. These values are strings. The 'statusline' option will be evaluated in the |sandbox| if set from a modeline, see |sandbox-option|. diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index af7d233619..0ded6a9060 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -90,6 +90,7 @@ argument. --clean Equivalent to "-u NONE -i NONE": - Skips initializations from files and environment variables. - No 'shada' file is read or written. + - Excludes user directories from 'runtimepath' *--noplugin* --noplugin Skip loading plugins. Resets the 'loadplugins' option. diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index 57337aeac2..7da886dabd 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -3522,6 +3522,24 @@ DEFINING CASE *:syn-case* *E390* :sy[ntax] case Show either "syntax case match" or "syntax case ignore" (translated). + +DEFINING FOLDLEVEL *:syn-foldlevel* + +:sy[ntax] foldlevel [start | minimum] + This defines how the foldlevel of a line is computed when using + foldmethod=syntax (see |fold-syntax| and |:syn-fold|): + + start: Use level of item containing start of line. + minimum: Use lowest local-minimum level of items on line. + + The default is 'start'. Use 'minimum' to search a line horizontally + for the lowest level contained on the line that is followed by a + higher level. This produces more natural folds when syntax items + may close and open horizontally within a line. + +:sy[ntax] foldlevel + Show either "syntax foldlevel start" or "syntax foldlevel minimum". + SPELL CHECKING *:syn-spell* :sy[ntax] spell [toplevel | notoplevel | default] @@ -3985,6 +4003,8 @@ This will make each {} block form one fold. The fold will start on the line where the item starts, and end where the item ends. If the start and end are within the same line, there is no fold. The 'foldnestmax' option limits the nesting of syntax folds. +See |:syn-foldlevel| to control how the foldlevel of a line is computed +from its syntax items. *:syn-contains* *E405* *E406* *E407* *E408* *E409* diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 383a45b9d3..6807bef3eb 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -458,7 +458,7 @@ au BufNewFile,BufRead *.desc setf desc au BufNewFile,BufRead *.d call dist#ft#DtraceCheck() " Desktop files -au BufNewFile,BufRead *.desktop,.directory setf desktop +au BufNewFile,BufRead *.desktop,*.directory setf desktop " Dict config au BufNewFile,BufRead dict.conf,.dictrc setf dictconf @@ -538,7 +538,7 @@ au BufNewFile,BufRead *.ecd setf ecd au BufNewFile,BufRead *.e,*.E call dist#ft#FTe() " Elinks configuration -au BufNewFile,BufRead */etc/elinks.conf,*/.elinks/elinks.conf setf elinks +au BufNewFile,BufRead elinks.conf setf elinks " ERicsson LANGuage; Yaws is erlang too au BufNewFile,BufRead *.erl,*.hrl,*.yaws setf erlang @@ -1130,8 +1130,17 @@ au BufNewFile,BufRead *.ora setf ora " Packet filter conf au BufNewFile,BufRead pf.conf setf pf +" Pacman Config (close enough to dosini) +au BufNewFile,BufRead */etc/pacman.conf setf dosini + +" Pacman hooks +au BufNewFile,BufRead *.hook + \ if getline(1) == '[Trigger]' | + \ setf dosini | + \ endif + " Pam conf -au BufNewFile,BufRead */etc/pam.conf setf pamconf +au BufNewFile,BufRead */etc/pam.conf setf pamconf " Pam environment au BufNewFile,BufRead pam_env.conf,.pam_environment setf pamenv @@ -1162,6 +1171,7 @@ else endif au BufNewFile,BufRead *.plx,*.al,*.psgi setf perl au BufNewFile,BufRead *.p6,*.pm6,*.pl6 setf perl6 +au BufNewFile,BufRead *.raku,*.rakumod setf perl6 " Perl, XPM or XPM2 au BufNewFile,BufRead *.pm @@ -1291,7 +1301,8 @@ au BufNewFile,BufRead *.pyx,*.pxd setf pyrex " Python, Python Shell Startup and Python Stub Files " Quixote (Python-based web framework) -au BufNewFile,BufRead *.py,*.pyw,.pythonstartup,.pythonrc,*.ptl,*.pyi setf python +au BufNewFile,BufRead *.py,*.pyw,.pythonstartup,.pythonrc setf python +au BufNewFile,BufRead *.ptl,*.pyi,SConstruct setf python " Radiance au BufNewFile,BufRead *.rad,*.mat setf radiance @@ -1613,10 +1624,12 @@ au BufNewFile,BufRead *.sqlj setf sqlj au BufNewFile,BufRead *.sqr,*.sqi setf sqr " OpenSSH configuration -au BufNewFile,BufRead ssh_config,*/.ssh/config setf sshconfig +au BufNewFile,BufRead ssh_config,*/.ssh/config setf sshconfig +au BufNewFile,BufRead */etc/ssh/ssh_config.d/*.conf setf sshconfig " OpenSSH server configuration -au BufNewFile,BufRead sshd_config setf sshdconfig +au BufNewFile,BufRead sshd_config setf sshdconfig +au BufNewFile,BufRead */etc/ssh/sshd_config.d/*.conf setf sshdconfig " Stata au BufNewFile,BufRead *.ado,*.do,*.imata,*.mata setf stata @@ -1647,8 +1660,9 @@ au BufNewFile,BufRead *.sil setf sil au BufNewFile,BufRead */etc/sysctl.conf,*/etc/sysctl.d/*.conf setf sysctl " Systemd unit files -au BufNewFile,BufRead */systemd/*.{automount,mount,path,service,socket,swap,target,timer} setf systemd +au BufNewFile,BufRead */systemd/*.{automount,dnssd,link,mount,netdev,network,nspawn,path,service,slice,socket,swap,target,timer} setf systemd " Systemd overrides +au BufNewFile,BufRead */etc/systemd/*.conf.d/*.conf setf systemd au BufNewFile,BufRead */etc/systemd/system/*.d/*.conf setf systemd au BufNewFile,BufRead */.config/systemd/user/*.d/*.conf setf systemd " Systemd temp files diff --git a/runtime/indent/tex.vim b/runtime/indent/tex.vim index a748cfbb40..8a44ade1ac 100644 --- a/runtime/indent/tex.vim +++ b/runtime/indent/tex.vim @@ -64,14 +64,17 @@ " style) is supported. Thanks Miles Wheeler for reporting. " 2018/02/07 by Yichao Zhou <broken.zhou AT gmail.com> " (*) Make indentation more smart in the normal mode +" 2020/04/26 by Yichao Zhou <broken.zhou AT gmail.com> +" (*) Fix a bug related to \[ & \]. Thanks Manuel Boni for +" reporting. " " }}} " Document: {{{ " -" To set the following options (ok, currently it's just one), add a line like -" let g:tex_indent_items = 1 -" to your ~/.vimrc. +" For proper latex experience, please put +" let g:tex_flavor = "latex" +" into your vimrc. " " * g:tex_indent_brace " @@ -184,13 +187,18 @@ function! GetTeXIndent() " {{{ let line = substitute(getline(lnum), '\s*%.*', '','g') " last line let cline = substitute(getline(v:lnum), '\s*%.*', '', 'g') " current line + let ccol = 1 + while cline[ccol] =~ '\s' + let ccol += 1 + endwhile + " We are in verbatim, so do what our user what. - if synIDattr(synID(v:lnum, indent(v:lnum), 1), "name") == "texZone" + if synIDattr(synID(v:lnum, ccol, 1), "name") == "texZone" if empty(cline) return indent(lnum) else return indent(v:lnum) - end + endif endif if lnum == 0 @@ -253,13 +261,13 @@ function! GetTeXIndent() " {{{ let stay = 0 endif - if cline =~ '^\s*\\\?[\]}]' && s:CheckPairedIsLastCharacter(v:lnum, indent(v:lnum)) + if cline =~ '^\s*\\\?[\]}]' && s:CheckPairedIsLastCharacter(v:lnum, ccol) let ind -= shiftwidth() let stay = 0 endif if line !~ '^\s*\\\?[\]}]' - for i in range(indent(lnum)+1, strlen(line)-1) + for i in range(1, strlen(line)-1) let char = line[i] if char == ']' || char == '}' if s:CheckPairedIsLastCharacter(lnum, i) diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index 69c3c8a4dc..ce0a3de520 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -23,24 +23,41 @@ function highlight.range(bufnr, ns, higroup, start, finish, rtype, inclusive) end +local yank_ns = api.nvim_create_namespace('hlyank') --- Highlight the yanked region --- --- use from init.vim via ---- au TextYankPost * lua require'vim.highlight'.on_yank() +--- au TextYankPost * lua vim.highlight.on_yank() --- customize highlight group and timeout via ---- au TextYankPost * lua require'vim.highlight'.on_yank("IncSearch", 500) +--- au TextYankPost * lua vim.highlight.on_yank {higroup="IncSearch", timeout=150} +--- customize conditions (here: do not highlight a visual selection) via +--- au TextYankPost * lua vim.highlight.on_yank {on_visual=false} --- --- @param higroup highlight group for yanked region --- @param timeout time in ms before highlight is cleared --- @param event event structure -function highlight.on_yank(higroup, timeout, event) - event = event or vim.v.event +-- @param opts dictionary with options controlling the highlight: +-- - higroup highlight group for yanked region (default "IncSearch") +-- - timeout time in ms before highlight is cleared (default 150) +-- - on_macro highlight when executing macro (default false) +-- - on_visual highlight when yanking visual selection (default true) +-- - event event structure (default vim.v.event) +function highlight.on_yank(opts) + vim.validate { + opts = { opts, + function(t) if t == nil then return true else return type(t) == 'table' end end, + 'a table or nil to configure options (see `:h highlight.on_yank`)', + }} + opts = opts or {} + local event = opts.event or vim.v.event + local on_macro = opts.on_macro or false + local on_visual = (opts.on_visual ~= false) + + if (not on_macro) and vim.fn.reg_executing() ~= '' then return end if event.operator ~= 'y' or event.regtype == '' then return end - higroup = higroup or "IncSearch" - timeout = timeout or 500 + if (not on_visual) and event.visual then return end + + local higroup = opts.higroup or "IncSearch" + local timeout = opts.timeout or 150 local bufnr = api.nvim_get_current_buf() - local yank_ns = api.nvim_create_namespace('') api.nvim_buf_clear_namespace(bufnr, yank_ns, 0, -1) local pos1 = vim.fn.getpos("'[") diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 2fbc51481f..6fe1d15b7e 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -511,6 +511,7 @@ function lsp.start_client(config) or (not client.resolved_capabilities.type_definition and method == 'textDocument/typeDefinition') or (not client.resolved_capabilities.document_symbol and method == 'textDocument/documentSymbol') or (not client.resolved_capabilities.workspace_symbol and method == 'textDocument/workspaceSymbol') + or (not client.resolved_capabilities.call_hierarchy and method == 'textDocument/prepareCallHierarchy') then callback(unsupported_method(method), method, nil, client_id, bufnr) return @@ -586,9 +587,7 @@ do old_utf16_size) local _ = log.debug() and log.debug("on_lines", bufnr, changedtick, firstline, lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size, nvim_buf_get_lines(bufnr, firstline, new_lastline, true)) - if old_byte_size == 0 then - return - end + -- Don't do anything if there are no clients attached. if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then return @@ -879,11 +878,11 @@ function lsp.buf_request_sync(bufnr, method, params, timeout_ms) end --- 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 +--@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 }; diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 7a819f3c3d..476bb3ba6f 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -23,6 +23,9 @@ local function request(method, params, callback) return vim.lsp.buf_request(0, method, params, callback) end +--- Sends a notification through all clients associated with current buffer. +-- +--@return `true` if server responds. function M.server_ready() return not not vim.lsp.buf_notify(0, "window/progress", {}) end @@ -65,19 +68,22 @@ function M.completion(context) end function M.formatting(options) - validate { options = {options, 't', true} } - local sts = vim.bo.softtabstop; - options = vim.tbl_extend('keep', options or {}, { - tabSize = (sts > 0 and sts) or (sts < 0 and vim.bo.shiftwidth) or vim.bo.tabstop; - insertSpaces = vim.bo.expandtab; - }) - local params = { - textDocument = { uri = vim.uri_from_bufnr(0) }; - options = options; - } + local params = util.make_formatting_params(options) return request('textDocument/formatting', params) end +--- Perform |vim.lsp.buf.formatting()| synchronously. +--- +--- Useful for running on save, to make sure buffer is formatted prior to being +--- saved. {timeout_ms} is passed on to |vim.lsp.buf_request_sync()|. +function M.formatting_sync(options, timeout_ms) + local params = util.make_formatting_params(options) + local result = vim.lsp.buf_request_sync(0, "textDocument/formatting", params, timeout_ms) + if not result then return end + result = result[1].result + vim.lsp.util.apply_text_edits(result) +end + function M.range_formatting(options, start_pos, end_pos) validate { options = {options, 't', true}; @@ -116,7 +122,7 @@ function M.rename(new_name) -- TODO(ashkan) use prepareRename -- * result: [`Range`](#range) \| `{ range: Range, placeholder: string }` \| `null` describing the range of the string to rename and optionally a placeholder text of the string content to be renamed. If `null` is returned then it is deemed that a 'textDocument/rename' request is not valid at the given position. local params = util.make_position_params() - new_name = new_name or npcall(vfn.input, "New Name: ") + new_name = new_name or npcall(vfn.input, "New Name: ", vfn.expand('<cword>')) if not (new_name and #new_name > 0) then return end params.newName = new_name request('textDocument/rename', params) @@ -137,6 +143,44 @@ function M.document_symbol() request('textDocument/documentSymbol', params) end +local function pick_call_hierarchy_item(call_hierarchy_items) + if not call_hierarchy_items then return end + if #call_hierarchy_items == 1 then + return call_hierarchy_items[1] + end + local items = {} + for i, item in ipairs(call_hierarchy_items) do + local entry = item.detail or item.name + table.insert(items, string.format("%d. %s", i, entry)) + end + local choice = vim.fn.inputlist(items) + if choice < 1 or choice > #items then + return + end + return choice +end + +function M.incoming_calls() + local params = util.make_position_params() + request('textDocument/prepareCallHierarchy', params, function(_, _, result) + local call_hierarchy_item = pick_call_hierarchy_item(result) + vim.lsp.buf_request(0, 'callHierarchy/incomingCalls', { item = call_hierarchy_item }) + end) +end + +function M.outgoing_calls() + local params = util.make_position_params() + request('textDocument/prepareCallHierarchy', params, function(_, _, result) + local call_hierarchy_item = pick_call_hierarchy_item(result) + vim.lsp.buf_request(0, 'callHierarchy/outgoingCalls', { item = call_hierarchy_item }) + end) +end + +--- Lists all symbols in the current workspace in the quickfix window. +--- +--- The list is filtered against the optional argument {query}; +--- if the argument is omitted from the call, the user is prompted to enter a string on the command line. +--- An empty string means no filtering is done. function M.workspace_symbol(query) query = query or npcall(vfn.input, "Query: ") local params = {query = query} diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua index 4b14f0132d..1ed58995d0 100644 --- a/runtime/lua/vim/lsp/callbacks.lua +++ b/runtime/lua/vim/lsp/callbacks.lua @@ -214,6 +214,33 @@ M['textDocument/documentHighlight'] = function(_, _, result, _) util.buf_highlight_references(bufnr, result) end +-- direction is "from" for incoming calls and "to" for outgoing calls +local make_call_hierarchy_callback = function(direction) + -- result is a CallHierarchy{Incoming,Outgoing}Call[] + return function(_, _, result) + if not result then return end + local items = {} + for _, call_hierarchy_call in pairs(result) do + local call_hierarchy_item = call_hierarchy_call[direction] + for _, range in pairs(call_hierarchy_call.fromRanges) do + table.insert(items, { + filename = assert(vim.uri_to_fname(call_hierarchy_item.uri)), + text = call_hierarchy_item.name, + lnum = range.start.line + 1, + col = range.start.character + 1, + }) + end + end + util.set_qflist(items) + api.nvim_command("copen") + api.nvim_command("wincmd p") + end +end + +M['callHierarchy/incomingCalls'] = make_call_hierarchy_callback('from') + +M['callHierarchy/outgoingCalls'] = make_call_hierarchy_callback('to') + M['window/logMessage'] = function(_, _, result, client_id) local message_type = result.type local message = result.message diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 4fded1961d..ef5e08680e 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -713,6 +713,9 @@ function protocol.make_client_capabilities() }; applyEdit = true; }; + callHierarchy = { + dynamicRegistration = false; + }; experimental = nil; } end @@ -912,6 +915,7 @@ function protocol.resolve_capabilities(server_capabilities) general_properties.workspace_symbol = server_capabilities.workspaceSymbolProvider or false general_properties.document_formatting = server_capabilities.documentFormattingProvider or false general_properties.document_range_formatting = server_capabilities.documentRangeFormattingProvider or false + general_properties.call_hierarchy = server_capabilities.callHierarchyProvider or false if server_capabilities.codeActionProvider == nil then general_properties.code_action = false diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index dad1dc11f1..81c92bfe05 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -36,10 +36,12 @@ end --- Merges current process env with the given env and returns the result as --- a list of "k=v" strings. --- +--- <pre> --- Example: --- ---- { PRODUCTION="false", PATH="/usr/bin/", PORT=123, HOST="0.0.0.0", } ---- => { "PRODUCTION=false", "PATH=/usr/bin/", "PORT=123", "HOST=0.0.0.0", } +--- in: { PRODUCTION="false", PATH="/usr/bin/", PORT=123, HOST="0.0.0.0", } +--- out: { "PRODUCTION=false", "PATH=/usr/bin/", "PORT=123", "HOST=0.0.0.0", } +--- </pre> local function env_merge(env) if env == nil then return env diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index a4ceeb34e6..52a6fe89f3 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -92,7 +92,7 @@ local function sort_by_key(fn) end end local edit_sort_key = sort_by_key(function(e) - return {e.A[1], e.A[2], e.i} + return {e.A[1], e.A[2], -e.i} end) --- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position @@ -119,6 +119,7 @@ function M.apply_text_edits(text_edits, bufnr) if not api.nvim_buf_is_loaded(bufnr) then vim.fn.bufload(bufnr) end + api.nvim_buf_set_option(bufnr, 'buflisted', true) local start_line, finish_line = math.huge, -1 local cleaned = {} for i, e in ipairs(text_edits) do @@ -618,8 +619,10 @@ end --- --@param contents table of lines to trim and pad --@param opts dictionary with optional fields --- - pad_left amount of columns to pad contents at left (default 1) --- - pad_right amount of columns to pad contents at right (default 1) +-- - pad_left number of columns to pad contents at left (default 1) +-- - pad_right number of columns to pad contents at right (default 1) +-- - pad_top number of lines to pad contents at top (default 0) +-- - pad_bottom number of lines to pad contents at bottom (default 0) --@return contents table of trimmed and padded lines function M._trim_and_pad(contents, opts) validate { @@ -633,6 +636,16 @@ function M._trim_and_pad(contents, opts) for i, line in ipairs(contents) do contents[i] = string.format('%s%s%s', left_padding, line:gsub("\r", ""), right_padding) end + if opts.pad_top then + for _ = 1, opts.pad_top do + table.insert(contents, 1, "") + end + end + if opts.pad_bottom then + for _ = 1, opts.pad_bottom do + table.insert(contents, "") + end + end return contents end @@ -650,8 +663,12 @@ end -- - height of floating window -- - width of floating window -- - wrap_at character to wrap at for computing height --- - pad_left amount of columns to pad contents at left --- - pad_right amount of columns to pad contents at right +-- - max_width maximal width of floating window +-- - max_height maximal height of floating window +-- - pad_left number of columns to pad contents at left +-- - pad_right number of columns to pad contents at right +-- - pad_top number of lines to pad contents at top +-- - pad_bottom number of lines to pad contents at bottom -- - separator insert separator after code block --@return width,height size of float function M.fancy_floating_markdown(contents, opts) @@ -762,6 +779,8 @@ end -- - height of floating window -- - width of floating window -- - wrap_at character to wrap at for computing height +-- - max_width maximal width of floating window +-- - max_height maximal height of floating window --@return width,height size of float function M._make_floating_popup_size(contents, opts) validate { @@ -772,6 +791,9 @@ function M._make_floating_popup_size(contents, opts) local width = opts.width local height = opts.height + local wrap_at = opts.wrap_at + local max_width = opts.max_width + local max_height = opts.max_height local line_widths = {} if not width then @@ -782,11 +804,14 @@ function M._make_floating_popup_size(contents, opts) width = math.max(line_widths[i], width) end end + if max_width then + width = math.min(width, max_width) + wrap_at = math.min(wrap_at or max_width, max_width) + end if not height then height = #contents - local wrap_at = opts.wrap_at - if wrap_at and width > wrap_at then + if wrap_at and width >= wrap_at then height = 0 if vim.tbl_isempty(line_widths) then for _, line in ipairs(contents) do @@ -795,11 +820,14 @@ function M._make_floating_popup_size(contents, opts) end else for i = 1, #contents do - height = height + math.ceil(line_widths[i]/wrap_at) + height = height + math.max(1, math.ceil(line_widths[i]/wrap_at)) end end end end + if max_height then + height = math.min(height, max_height) + end return width, height end @@ -812,8 +840,12 @@ end -- - height of floating window -- - width of floating window -- - wrap_at character to wrap at for computing height --- - pad_left amount of columns to pad contents at left --- - pad_right amount of columns to pad contents at right +-- - max_width maximal width of floating window +-- - max_height maximal height of floating window +-- - pad_left number of columns to pad contents at left +-- - pad_right number of columns to pad contents at right +-- - pad_top number of lines to pad contents at top +-- - pad_bottom number of lines to pad contents at bottom --@return bufnr,winnr buffer and window number of floating window or nil function M.open_floating_preview(contents, filetype, opts) validate { @@ -859,6 +891,8 @@ do local severity_highlights = {} + local severity_floating_highlights = {} + local default_severity_highlight = { [protocol.DiagnosticSeverity.Error] = { guifg = "Red" }; [protocol.DiagnosticSeverity.Warning] = { guifg = "Orange" }; @@ -870,6 +904,7 @@ do for severity, hi_info in pairs(default_severity_highlight) do local severity_name = protocol.DiagnosticSeverity[severity] local highlight_name = "LspDiagnostics"..severity_name + local floating_highlight_name = highlight_name.."Floating" -- Try to fill in the foreground color with a sane default. local cmd_parts = {"highlight", "default", highlight_name} for k, v in pairs(hi_info) do @@ -877,7 +912,9 @@ do end api.nvim_command(table.concat(cmd_parts, ' ')) api.nvim_command('highlight link ' .. highlight_name .. 'Sign ' .. highlight_name) + api.nvim_command('highlight link ' .. highlight_name .. 'Floating ' .. highlight_name) severity_highlights[severity] = highlight_name + severity_floating_highlights[severity] = floating_highlight_name end function M.buf_clear_diagnostics(bufnr) @@ -926,7 +963,7 @@ do -- TODO(ashkan) make format configurable? local prefix = string.format("%d. ", i) - local hiname = severity_highlights[diagnostic.severity] + local hiname = severity_floating_highlights[diagnostic.severity] assert(hiname, 'unknown severity: ' .. tostring(diagnostic.severity)) local message_lines = split_lines(diagnostic.message) table.insert(lines, prefix..message_lines[1]) @@ -946,7 +983,9 @@ do end --- Saves the diagnostics (Diagnostic[]) into diagnostics_by_buf - -- + --- + --@param bufnr bufnr for which the diagnostics are for. + --@param diagnostics Diagnostics[] received from the language server. function M.buf_diagnostics_save_positions(bufnr, diagnostics) validate { bufnr = {bufnr, 'n', true}; @@ -1038,6 +1077,29 @@ do end end + --- Returns the number of diagnostics of given kind for current buffer. + --- + --- Useful for showing diagnostic counts in statusline. eg: + --- + --- <pre> + --- function! LspStatus() abort + --- let sl = '' + --- if luaeval('not vim.tbl_isempty(vim.lsp.buf_get_clients(0))') + --- 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() + --- </pre> + --- + --@param kind Diagnostic severity kind: See |vim.lsp.protocol.DiagnosticSeverity| + --- + --@return Count of diagnostics function M.buf_diagnostics_count(kind) local bufnr = vim.api.nvim_get_current_buf() local diagnostics = M.diagnostics_by_buf[bufnr] @@ -1058,6 +1120,16 @@ do [protocol.DiagnosticSeverity.Hint] = "LspDiagnosticsHintSign"; } + --- Place signs for each diagnostic in the sign column. + --- + --- Sign characters can be customized with the following commands: + --- + --- <pre> + --- sign define LspDiagnosticsErrorSign text=E texthl=LspDiagnosticsError linehl= numhl= + --- sign define LspDiagnosticsWarningSign text=W texthl=LspDiagnosticsWarning linehl= numhl= + --- sign define LspDiagnosticsInformationSign text=I texthl=LspDiagnosticsInformation linehl= numhl= + --- sign define LspDiagnosticsHintSign text=H texthl=LspDiagnosticsHint linehl= numhl= + --- </pre> function M.buf_diagnostics_signs(bufnr, diagnostics) for _, diagnostic in ipairs(diagnostics) do vim.fn.sign_place(0, sign_ns, diagnostic_severity_map[diagnostic.severity], bufnr, {lnum=(diagnostic.range.start.line+1)}) @@ -1083,40 +1155,31 @@ function M.locations_to_items(locations) for _, d in ipairs(locations) do -- locations may be Location or LocationLink local uri = d.uri or d.targetUri - local fname = assert(vim.uri_to_fname(uri)) local range = d.range or d.targetSelectionRange - table.insert(grouped[fname], {start = range.start}) + table.insert(grouped[uri], {start = range.start}) end local keys = vim.tbl_keys(grouped) table.sort(keys) -- TODO(ashkan) I wish we could do this lazily. - for _, fname in ipairs(keys) do - local rows = grouped[fname] - + for _, uri in ipairs(keys) do + local rows = grouped[uri] table.sort(rows, position_sort) - local i = 0 - for line in io.lines(fname) do - for _, temp in ipairs(rows) do - local pos = temp.start - local row = pos.line - if i == row then - local col - if pos.character > #line then - col = #line - else - col = vim.str_byteindex(line, pos.character) - end - table.insert(items, { - filename = fname, - lnum = row + 1, - col = col + 1; - text = line; - }) - end - end - i = i + 1 + local bufnr = vim.uri_to_bufnr(uri) + vim.fn.bufload(bufnr) + local filename = vim.uri_to_fname(uri) + for _, temp in ipairs(rows) do + local pos = temp.start + local row = pos.line + local line = (api.nvim_buf_get_lines(bufnr, row, row + 1, false) or {""})[1] + local col = M.character_offset(bufnr, row, pos.character) + table.insert(items, { + filename = filename, + lnum = row + 1, + col = col + 1; + text = line; + }) end end return items @@ -1145,7 +1208,7 @@ end --- Convert symbols to quickfix list items --- ---@symbols DocumentSymbol[] or SymbolInformation[] +--@param 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 @@ -1254,6 +1317,30 @@ function M.make_text_document_params() return { uri = vim.uri_from_bufnr(0) } end +--- Get visual width of tabstop. +--- +--@see |softtabstop| +--@param bufnr (optional, number): Buffer handle, defaults to current +--@returns (number) tabstop visual width +function M.get_effective_tabstop(bufnr) + validate { bufnr = {bufnr, 'n', true} } + local bo = bufnr and vim.bo[bufnr] or vim.bo + local sts = bo.softtabstop + return (sts > 0 and sts) or (sts < 0 and bo.shiftwidth) or bo.tabstop +end + +function M.make_formatting_params(options) + validate { options = {options, 't', true} } + options = vim.tbl_extend('keep', options or {}, { + tabSize = M.get_effective_tabstop(); + insertSpaces = vim.bo.expandtab; + }) + return { + textDocument = { uri = vim.uri_from_bufnr(0) }; + options = options; + } +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 384d22cb89..6e427665f2 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -347,16 +347,16 @@ function vim.tbl_flatten(t) return result end --- Determine whether a Lua table can be treated as an array. --- --- An empty table `{}` will default to being treated as an array. --- Use `vim.emtpy_dict()` to create a table treated as an --- empty dict. Empty tables returned by `rpcrequest()` and --- `vim.fn` functions can be checked using this function --- whether they represent empty API arrays and vimL lists. ---- ---@params Table ---@returns true: An array-like table, false: A dict-like or mixed table +--- Determine whether a Lua table can be treated as an array. +--- +--- An empty table `{}` will default to being treated as an array. +--- Use `vim.emtpy_dict()` to create a table treated as an +--- empty dict. Empty tables returned by `rpcrequest()` and +--- `vim.fn` functions can be checked using this function +--- whether they represent empty API arrays and vimL lists. +--- +--@param t Table +--@returns `true` if array-like table, else `false`. function vim.tbl_islist(t) if type(t) ~= 'table' then return false @@ -392,7 +392,7 @@ end --- </pre> --- --@see https://github.com/Tieske/Penlight/blob/master/lua/pl/tablex.lua ---@param Table +--@param t Table --@returns Number that is the number of the value in table function vim.tbl_count(t) vim.validate{t={t,'t'}} diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index c502e45bd0..927456708c 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -15,19 +15,33 @@ function Parser:parse() local changes self.tree, changes = self._parser:parse_buf(self.bufnr) self.valid = true - for _, cb in ipairs(self.change_cbs) do - cb(changes) + + if not vim.tbl_isempty(changes) then + for _, cb in ipairs(self.changedtree_cbs) do + cb(changes) + end end + return self.tree, changes end -function Parser:_on_lines(bufnr, _, start_row, old_stop_row, stop_row, old_byte_size) +function Parser:_on_lines(bufnr, changed_tick, start_row, old_stop_row, stop_row, old_byte_size) local start_byte = a.nvim_buf_get_offset(bufnr,start_row) local stop_byte = a.nvim_buf_get_offset(bufnr,stop_row) local old_stop_byte = start_byte + old_byte_size self._parser:edit(start_byte,old_stop_byte,stop_byte, start_row,0,old_stop_row,0,stop_row,0) self.valid = false + + for _, cb in ipairs(self.lines_cbs) do + cb(bufnr, changed_tick, start_row, old_stop_row, stop_row, old_byte_size) + end +end + +function Parser:set_included_ranges(ranges) + self._parser:set_included_ranges(ranges) + -- The buffer will need to be parsed again later + self.valid = false end local M = { @@ -69,9 +83,13 @@ function M.create_parser(bufnr, lang, id) if bufnr == 0 then bufnr = a.nvim_get_current_buf() end + + vim.fn.bufload(bufnr) + local self = setmetatable({bufnr=bufnr, lang=lang, valid=false}, Parser) self._parser = vim._create_ts_parser(lang) - self.change_cbs = {} + self.changedtree_cbs = {} + self.lines_cbs = {} self:parse() -- TODO(bfredl): use weakref to self, so that the parser is free'd is no plugin is -- using it. @@ -90,7 +108,7 @@ function M.create_parser(bufnr, lang, id) return self end -function M.get_parser(bufnr, ft, cb) +function M.get_parser(bufnr, ft, buf_attach_cbs) if bufnr == nil or bufnr == 0 then bufnr = a.nvim_get_current_buf() end @@ -102,9 +120,15 @@ function M.get_parser(bufnr, ft, cb) if parsers[id] == nil then parsers[id] = M.create_parser(bufnr, ft, id) end - if cb ~= nil then - table.insert(parsers[id].change_cbs, cb) + + if buf_attach_cbs and buf_attach_cbs.on_changedtree then + table.insert(parsers[id].changedtree_cbs, buf_attach_cbs.on_changedtree) end + + if buf_attach_cbs and buf_attach_cbs.on_lines then + table.insert(parsers[id].lines_cbs, buf_attach_cbs.on_lines) + end + return parsers[id] end diff --git a/runtime/lua/vim/tshighlighter.lua b/runtime/lua/vim/tshighlighter.lua index 1440acf0d0..6465751ae8 100644 --- a/runtime/lua/vim/tshighlighter.lua +++ b/runtime/lua/vim/tshighlighter.lua @@ -3,6 +3,7 @@ local a = vim.api -- support reload for quick experimentation local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {} TSHighlighter.__index = TSHighlighter +local ts_hs_ns = a.nvim_create_namespace("treesitter_hl") -- These are conventions defined by tree-sitter, though it -- needs to be user extensible also. @@ -24,9 +25,17 @@ TSHighlighter.hl_map = { function TSHighlighter.new(query, bufnr, ft) local self = setmetatable({}, TSHighlighter) - self.parser = vim.treesitter.get_parser(bufnr, ft, function(...) self:on_change(...) end) + self.parser = vim.treesitter.get_parser( + bufnr, + ft, + { + on_changedtree = function(...) self:on_changedtree(...) end, + on_lines = function() self.root = self.parser:parse():root() end + } + ) + self.buf = self.parser.bufnr - -- TODO(bfredl): perhaps on_start should be called uncondionally, instead for only on mod? + local tree = self.parser:parse() self.root = tree:root() self:set_query(query) @@ -34,11 +43,6 @@ function TSHighlighter.new(query, bufnr, ft) self.redraw_count = 0 self.line_count = {} a.nvim_buf_set_option(self.buf, "syntax", "") - a.nvim__buf_set_luahl(self.buf, { - on_start=function(...) return self:on_start(...) end, - on_window=function(...) return self:on_window(...) end, - on_line=function(...) return self:on_line(...) end, - }) -- Tricky: if syntax hasn't been enabled, we need to reload color scheme -- but use synload.vim rather than syntax.vim to not enable @@ -50,73 +54,63 @@ function TSHighlighter.new(query, bufnr, ft) return self end +local function is_highlight_name(capture_name) + local firstc = string.sub(capture_name, 1, 1) + return firstc ~= string.lower(firstc) +end + +function TSHighlighter:get_hl_from_capture(capture) + + local name = self.query.captures[capture] + + if is_highlight_name(name) then + -- From "Normal.left" only keep "Normal" + return vim.split(name, '.', true)[1] + else + -- Default to false to avoid recomputing + return TSHighlighter.hl_map[name] + end +end + function TSHighlighter:set_query(query) if type(query) == "string" then query = vim.treesitter.parse_query(self.parser.lang, query) end self.query = query - self.id_map = {} - for i, capture in ipairs(self.query.captures) do - local hl = 0 - local firstc = string.sub(capture, 1, 1) - local hl_group = self.hl_map[capture] - if firstc ~= string.lower(firstc) then - hl_group = vim.split(capture, '.', true)[1] - end - if hl_group then - hl = a.nvim_get_hl_id_by_name(hl_group) + self.hl_cache = setmetatable({}, { + __index = function(table, capture) + local hl = self:get_hl_from_capture(capture) + rawset(table, capture, hl) + + return hl end - self.id_map[i] = hl - end + }) - a.nvim__buf_redraw_range(self.buf, 0, a.nvim_buf_line_count(self.buf)) + self:on_changedtree({{self.root:range()}}) end -function TSHighlighter:on_change(changes) - for _, ch in ipairs(changes or {}) do - a.nvim__buf_redraw_range(self.buf, ch[1], ch[3]+1) - end - self.edit_count = self.edit_count + 1 -end +function TSHighlighter:on_changedtree(changes) + -- Get a fresh root + self.root = self.parser.tree:root() -function TSHighlighter:on_start(_, _buf, _tick) - local tree = self.parser:parse() - self.root = tree:root() -end + for _, ch in ipairs(changes or {}) do + -- Try to be as exact as possible + local changed_node = self.root:descendant_for_range(ch[1], ch[2], ch[3], ch[4]) -function TSHighlighter:on_window(_, _win, _buf, _topline, botline) - self.iter = nil - self.nextrow = 0 - self.botline = botline - self.redraw_count = self.redraw_count + 1 -end + a.nvim_buf_clear_namespace(self.buf, ts_hs_ns, ch[1], ch[3]) -function TSHighlighter:on_line(_, _win, buf, line) - if self.iter == nil then - self.iter = self.query:iter_captures(self.root,buf,line,self.botline) - end - while line >= self.nextrow do - local capture, node, match = self.iter() - local active = true - if capture == nil then - break - end - if match ~= nil then - active = self:run_pred(match) - match.active = active - end - local start_row, start_col, end_row, end_col = node:range() - local hl = self.id_map[capture] - if hl > 0 and active and end_row >= line then - a.nvim__put_attr(hl, start_row, start_col, end_row, end_col) - end - if start_row > line then - self.nextrow = start_row + for capture, node in self.query:iter_captures(changed_node, self.buf, ch[1], ch[3] + 1) do + local start_row, start_col, end_row, end_col = node:range() + local hl = self.hl_cache[capture] + if hl then + a.nvim__buf_add_decoration(self.buf, ts_hs_ns, hl, + start_row, start_col, + end_row, end_col, + {}) + end end end - self.line_count[line] = (self.line_count[line] or 0) + 1 - --return tostring(self.line_count[line]) end return TSHighlighter diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim index 28dc3256c7..6870bcec75 100644 --- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim +++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim @@ -247,10 +247,12 @@ func s:StartDebug_term(dict) endif let response = '' - for lnum in range(1,200) - if len(getbufline(s:gdbbuf, lnum)) > 0 && getbufline(s:gdbbuf, lnum)[0] =~ 'new-ui mi ' + for lnum in range(1, 200) + let line1 = get(getbufline(s:gdbbuf, lnum), 0, '') + let line2 = get(getbufline(s:gdbbuf, lnum + 1), 0, '') + if line1 =~ 'new-ui mi ' " response can be in the same line or the next line - let response = getbufline(s:gdbbuf, lnum)[0] . getbufline(s:gdbbuf, lnum + 1)[0] + let response = line1 . line2 if response =~ 'Undefined command' echoerr 'Sorry, your gdb is too old, gdb 7.12 is required' call s:CloseBuffers() @@ -260,10 +262,9 @@ func s:StartDebug_term(dict) " Success! break endif - if response =~ 'Reading symbols from' && response !~ 'new-ui' - " Reading symbols might take a while - let try_count -= 1 - endif + elseif line1 =~ 'Reading symbols from' && line2 !~ 'new-ui mi ' + " Reading symbols might take a while, try more times + let try_count -= 1 endif endfor if response =~ 'New UI allocated' @@ -465,12 +466,17 @@ endfunc " Function called when pressing CTRL-C in the prompt buffer and when placing a " breakpoint. func s:PromptInterrupt() - if s:pid == 0 - echoerr 'Cannot interrupt gdb, did not find a process ID' + " call ch_log('Interrupting gdb') + if has('win32') + " Using job_stop() does not work on MS-Windows, need to send SIGTRAP to + " the debugger program so that gdb responds again. + if s:pid == 0 + echoerr 'Cannot interrupt gdb, did not find a process ID' + else + call debugbreak(s:pid) + endif else - "call ch_log('Interrupting gdb') - " Using job_stop(s:gdbjob, 'int') does not work. - call debugbreak(s:pid) + call jobstop(s:gdbjob) endif endfunc diff --git a/runtime/scripts.vim b/runtime/scripts.vim index c552f0202f..6aae2b1ec3 100644 --- a/runtime/scripts.vim +++ b/runtime/scripts.vim @@ -1,7 +1,7 @@ " Vim support file to detect file types in scripts " " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last change: 2019 Jun 25 +" Last change: 2020 Jun 07 " This file is called by an autocommand for every file that has just been " loaded into a buffer. It checks if the type of file can be recognized by @@ -35,10 +35,12 @@ let s:line1 = getline(1) if s:line1 =~# "^#!" " A script that starts with "#!". - " Check for a line like "#!/usr/bin/env VAR=val bash". Turn it into + " Check for a line like "#!/usr/bin/env {options} bash". Turn it into " "#!/usr/bin/bash" to make matching easier. + " Recognize only a few {options} that are commonly used. if s:line1 =~# '^#!\s*\S*\<env\s' let s:line1 = substitute(s:line1, '\S\+=\S\+', '', 'g') + let s:line1 = substitute(s:line1, '\(-[iS]\|--ignore-environment\|--split-string\)', '', '') let s:line1 = substitute(s:line1, '\<env\s\+', '', '') endif diff --git a/runtime/syntax/tex.vim b/runtime/syntax/tex.vim index 363c781d0f..b3a8f96b6b 100644 --- a/runtime/syntax/tex.vim +++ b/runtime/syntax/tex.vim @@ -1,8 +1,8 @@ " Vim syntax file " Language: TeX -" Maintainer: Charles E. Campbell <NdrchipO@ScampbellPfamily.AbizM> -" Last Change: May 14, 2019 -" Version: 114 +" Maintainer: Charles E. Campbell <NcampObell@SdrPchip.AorgM-NOSPAM> +" Last Change: Jun 07, 2020 +" Version: 118 " URL: http://www.drchip.org/astronaut/vim/index.html#SYNTAX_TEX " " Notes: {{{1 @@ -147,6 +147,11 @@ if exists("g:tex_nospell") && g:tex_nospell else let s:tex_nospell = 0 endif +if exists("g:tex_excludematcher") + let s:tex_excludematcher= g:tex_excludematcher +else + let s:tex_excludematcher= 0 +endif " Clusters: {{{1 " -------- @@ -156,8 +161,12 @@ if !s:tex_no_error endif syn cluster texEnvGroup contains=texMatcher,texMathDelim,texSpecialChar,texStatement syn cluster texFoldGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texInput,texInputFile,texLength,texLigature,texMatcher,texMathZoneV,texMathZoneW,texMathZoneX,texMathZoneY,texMathZoneZ,texNewCmd,texNewEnv,texOnlyMath,texOption,texParen,texRefZone,texSection,texBeginEnd,texSectionZone,texSpaceCode,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texZone,@texMathZones,texTitle,texAbstract,texBoldStyle,texItalStyle,texEmphStyle,texNoSpell -syn cluster texBoldGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texInput,texInputFile,texLength,texLigature,texMatcher,texMathZoneV,texMathZoneW,texMathZoneX,texMathZoneY,texMathZoneZ,texNewCmd,texNewEnv,texOnlyMath,texOption,texParen,texRefZone,texSection,texBeginEnd,texSectionZone,texSpaceCode,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texZone,@texMathZones,texTitle,texAbstract,texBoldStyle,texBoldItalStyle,texNoSpell -syn cluster texItalGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texInput,texInputFile,texLength,texLigature,texMatcher,texMathZoneV,texMathZoneW,texMathZoneX,texMathZoneY,texMathZoneZ,texNewCmd,texNewEnv,texOnlyMath,texOption,texParen,texRefZone,texSection,texBeginEnd,texSectionZone,texSpaceCode,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texZone,@texMathZones,texTitle,texAbstract,texItalStyle,texEmphStyle,texItalBoldStyle,texNoSpell +syn cluster texBoldGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texInput,texInputFile,texLength,texLigature,texMathZoneV,texMathZoneW,texMathZoneX,texMathZoneY,texMathZoneZ,texNewCmd,texNewEnv,texOnlyMath,texOption,texParen,texRefZone,texSection,texBeginEnd,texSectionZone,texSpaceCode,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texZone,@texMathZones,texTitle,texAbstract,texBoldStyle,texBoldItalStyle,texNoSpell +syn cluster texItalGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texInput,texInputFile,texLength,texLigature,texMathZoneV,texMathZoneW,texMathZoneX,texMathZoneY,texMathZoneZ,texNewCmd,texNewEnv,texOnlyMath,texOption,texParen,texRefZone,texSection,texBeginEnd,texSectionZone,texSpaceCode,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texZone,@texMathZones,texTitle,texAbstract,texItalStyle,texEmphStyle,texItalBoldStyle,texNoSpell +if !s:tex_excludematcher + syn cluster texBoldGroup add=texMatcher + syn cluster texItalGroup add=texMatcher +endif if !s:tex_nospell syn cluster texMatchGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texInput,texLength,texLigature,texMatcher,texNewCmd,texNewEnv,texOnlyMath,texParen,texRefZone,texSection,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texBoldStyle,texBoldItalStyle,texItalStyle,texItalBoldStyle,texZone,texInputFile,texOption,@Spell syn cluster texMatchNMGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texInput,texLength,texLigature,texMatcherNM,texNewCmd,texNewEnv,texOnlyMath,texParen,texRefZone,texSection,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texBoldStyle,texBoldItalStyle,texItalStyle,texItalBoldStyle,texZone,texInputFile,texOption,@Spell @@ -305,11 +314,6 @@ if s:tex_conceal !~# 'b' endif syn match texTypeStyle "\\textmd\>" syn match texTypeStyle "\\textrm\>" -syn match texTypeStyle "\\textsc\>" -syn match texTypeStyle "\\textsf\>" -syn match texTypeStyle "\\textsl\>" -syn match texTypeStyle "\\texttt\>" -syn match texTypeStyle "\\textup\>" syn match texTypeStyle "\\mathbb\>" syn match texTypeStyle "\\mathbf\>" @@ -386,12 +390,18 @@ if s:tex_fast =~# 'b' syn region texItalStyle matchgroup=texTypeStyle start="\\textit\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texItalGroup,@Spell syn region texItalBoldStyle matchgroup=texTypeStyle start="\\textbf\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texBoldGroup,@Spell syn region texEmphStyle matchgroup=texTypeStyle start="\\emph\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texItalGroup,@Spell + syn region texEmphStyle matchgroup=texTypeStyle start="\\texts[cfl]\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texBoldGroup,@Spell + syn region texEmphStyle matchgroup=texTypeStyle start="\\textup\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texBoldGroup,@Spell + syn region texEmphStyle matchgroup=texTypeStyle start="\\texttt\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texBoldGroup,@Spell else syn region texBoldStyle matchgroup=texTypeStyle start="\\textbf\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texBoldGroup syn region texBoldItalStyle matchgroup=texTypeStyle start="\\textit\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texItalGroup syn region texItalStyle matchgroup=texTypeStyle start="\\textit\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texItalGroup syn region texItalBoldStyle matchgroup=texTypeStyle start="\\textbf\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texBoldGroup syn region texEmphStyle matchgroup=texTypeStyle start="\\emph\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texItalGroup + syn region texEmphStyle matchgroup=texTypeStyle start="\\texts[cfl]\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texEmphGroup + syn region texEmphStyle matchgroup=texTypeStyle start="\\textup\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texEmphGroup + syn region texEmphStyle matchgroup=texTypeStyle start="\\texttt\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texEmphGroup endif endif endif @@ -624,7 +634,7 @@ if s:tex_fast =~# 'r' syn region texRefOption contained matchgroup=Delimiter start='\[' end=']' contains=@texRefGroup,texRefZone nextgroup=texRefOption,texCite syn region texCite contained matchgroup=Delimiter start='{' end='}' contains=@texRefGroup,texRefZone,texCite endif -syn match texRefZone '\\cite\%([tp]\*\=\)\=' nextgroup=texRefOption,texCite +syn match texRefZone '\\cite\%([tp]\*\=\)\=\>' nextgroup=texRefOption,texCite " Handle newcommand, newenvironment : {{{1 syn match texNewCmd "\\newcommand\>" nextgroup=texCmdName skipwhite skipnl @@ -745,6 +755,8 @@ if has("conceal") && &enc == 'utf-8' \ ['lceil' , '⌈'], \ ['ldots' , '…'], \ ['le' , '≤'], + \ ['left|' , '|'], + \ ['left\|' , '‖'], \ ['left(' , '('], \ ['left\[' , '['], \ ['left\\{' , '{'], @@ -795,6 +807,8 @@ if has("conceal") && &enc == 'utf-8' \ ['quad' , ' '], \ ['qquad' , ' '], \ ['rfloor' , '⌋'], + \ ['right|' , '|'], + \ ['right\\|' , '‖'], \ ['right)' , ')'], \ ['right]' , ']'], \ ['right\\}' , '}'], @@ -1047,6 +1061,7 @@ if has("conceal") && &enc == 'utf-8' call s:SuperSub('texSuperscript','\^','R','ᴿ') call s:SuperSub('texSuperscript','\^','T','ᵀ') call s:SuperSub('texSuperscript','\^','U','ᵁ') + call s:SuperSub('texSuperscript','\^','V','ⱽ') call s:SuperSub('texSuperscript','\^','W','ᵂ') call s:SuperSub('texSuperscript','\^',',','︐') call s:SuperSub('texSuperscript','\^',':','︓') diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py index 3c51b2aa81..328a903b46 100755 --- a/scripts/gen_vimdoc.py +++ b/scripts/gen_vimdoc.py @@ -374,6 +374,7 @@ def update_params_map(parent, ret_map, width=62): def render_node(n, text, prefix='', indent='', width=62): """Renders a node as Vim help text, recursively traversing all descendants.""" global fmt_vimhelp + global has_seen_preformatted def ind(s): return s if fmt_vimhelp else '' @@ -386,6 +387,7 @@ def render_node(n, text, prefix='', indent='', width=62): o = get_text(n, preformatted=True) ensure_nl = '' if o[-1] == '\n' else '\n' text += '>{}{}\n<'.format(ensure_nl, o) + elif is_inline(n): text = doc_wrap(get_text(n), indent=indent, width=width) elif n.nodeName == 'verbatim': @@ -394,11 +396,17 @@ def render_node(n, text, prefix='', indent='', width=62): text += ' [verbatim] {}'.format(get_text(n)) elif n.nodeName == 'listitem': for c in n.childNodes: - text += ( - indent - + prefix - + render_node(c, text, indent=indent + (' ' * len(prefix)), width=width) + result = render_node( + c, + text, + indent=indent + (' ' * len(prefix)), + width=width ) + + if is_blank(result): + continue + + text += indent + prefix + result elif n.nodeName in ('para', 'heading'): for c in n.childNodes: text += render_node(c, text, indent=indent, width=width) @@ -433,6 +441,7 @@ def render_node(n, text, prefix='', indent='', width=62): else: raise RuntimeError('unhandled node type: {}\n{}'.format( n.nodeName, n.toprettyxml(indent=' ', newl='\n'))) + return text @@ -496,6 +505,7 @@ def para_as_map(parent, indent='', width=62): and '' != get_text(self_or_child(child)).strip() and ' ' != text[-1]): text += ' ' + text += render_node(child, text, indent=indent, width=width) prev = child @@ -566,6 +576,7 @@ def fmt_node_as_vimhelp(parent, width=62, indent=''): rendered_blocks.append(clean_lines('\n'.join(chunks).strip())) rendered_blocks.append('') + return clean_lines('\n'.join(rendered_blocks).strip()) @@ -678,6 +689,11 @@ def extract_from_xml(filename, target, width): signature += vimtag.rjust(width - len(signature)) paras = [] + brief_desc = find_first(member, 'briefdescription') + if brief_desc: + for child in brief_desc.childNodes: + paras.append(para_as_map(child)) + desc = find_first(member, 'detaileddescription') if desc: for child in desc.childNodes: @@ -763,8 +779,36 @@ def fmt_doxygen_xml_as_vimhelp(filename, target): func_doc = fn['signature'] + '\n' func_doc += textwrap.indent(clean_lines(doc), ' ' * 16) + + # Verbatim handling. func_doc = re.sub(r'^\s+([<>])$', r'\1', func_doc, flags=re.M) + split_lines = func_doc.split('\n') + start = 0 + while True: + try: + start = split_lines.index('>', start) + except ValueError: + break + + try: + end = split_lines.index('<', start) + except ValueError: + break + + split_lines[start + 1:end] = [ + (' ' + x).rstrip() + for x in textwrap.dedent( + "\n".join( + split_lines[start+1:end] + ) + ).split("\n") + ] + + start = end + + func_doc = "\n".join(split_lines) + if 'Deprecated' in xrefs: deprecated_fns_txt[name] = func_doc elif name.startswith(CONFIG[target]['fn_name_prefix']): @@ -847,11 +891,21 @@ def main(config): groupxml = os.path.join(base, '%s.xml' % compound.getAttribute('refid')) - desc = find_first(minidom.parse(groupxml), 'detaileddescription') + group_parsed = minidom.parse(groupxml) + doc_list = [] + brief_desc = find_first(group_parsed, 'briefdescription') + if brief_desc: + for child in brief_desc.childNodes: + doc_list.append(fmt_node_as_vimhelp(child)) + + desc = find_first(group_parsed, 'detaileddescription') if desc: doc = fmt_node_as_vimhelp(desc) + if doc: - intros[groupname] = doc + doc_list.append(doc) + + intros[groupname] = "\n".join(doc_list) for compound in dom.getElementsByTagName('compound'): if compound.getAttribute('kind') != 'file': diff --git a/scripts/lua2dox_filter b/scripts/lua2dox_filter index 6cb16ef060..61577527c4 100755 --- a/scripts/lua2dox_filter +++ b/scripts/lua2dox_filter @@ -36,7 +36,16 @@ test_executable(){ ##! \brief sets the lua interpreter set_lua(){ - test_executable 'texlua' + if test -z "${EXE}" + then + test_executable 'luajit' + fi + + if test -z "${EXE}" + then + test_executable 'texlua' + fi + if test -z "${EXE}" then test_executable 'lua' diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 982fab173f..c7258dde12 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -570,10 +570,16 @@ add_library( ) set_property(TARGET libnvim APPEND PROPERTY INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS}) +if(MSVC) + set(LIBNVIM_NAME libnvim) +else() + set(LIBNVIM_NAME nvim) +endif() set_target_properties( libnvim PROPERTIES POSITION_INDEPENDENT_CODE ON + OUTPUT_NAME ${LIBNVIM_NAME} ) set_property( TARGET libnvim diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index b345dcaccd..8e61976c4b 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -42,6 +42,8 @@ /// \defgroup api-buffer /// +/// \brief For more information on buffers, see |buffers| +/// /// Unloaded Buffers:~ /// /// Buffers may be unloaded by the |:bunload| command or the buffer's @@ -126,6 +128,7 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) /// @param opts Optional parameters. /// - on_lines: Lua callback invoked on change. /// Return `true` to detach. Args: +/// - the string "lines" /// - buffer handle /// - b:changedtick /// - first line that changed (zero-indexed) @@ -136,9 +139,11 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) /// - deleted_codeunits (if `utf_sizes` is true) /// - on_changedtick: Lua callback invoked on changedtick /// increment without text change. Args: +/// - the string "changedtick" /// - buffer handle /// - b:changedtick /// - on_detach: Lua callback invoked on detach. Args: +/// - the string "detach" /// - buffer handle /// - utf_sizes: include UTF-32 and UTF-16 size of the replaced /// region, as args to `on_lines`. diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 40cef87cf0..d6f95c7a5f 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -203,6 +203,16 @@ Integer nvim_get_hl_id_by_name(String name) /// flags. This is a blocking call, unlike |nvim_input()|. /// /// On execution error: does not fail, but updates v:errmsg. +// +// If you need to input sequences like <C-o> use nvim_replace_termcodes +// to replace the termcodes and then pass the resulting string to +// nvim_feedkeys. You'll also want to enable escape_csi. +/// +/// Example: +/// <pre> +/// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true) +/// :call nvim_feedkeys(key, 'n', v:true) +/// </pre> /// /// @param keys to be typed /// @param mode behavior flags, see |feedkeys()| diff --git a/src/nvim/aucmd.c b/src/nvim/aucmd.c index a9a7d834a4..32c77fa288 100644 --- a/src/nvim/aucmd.c +++ b/src/nvim/aucmd.c @@ -8,6 +8,8 @@ #include "nvim/ui.h" #include "nvim/aucmd.h" #include "nvim/eval.h" +#include "nvim/ex_getln.h" +#include "nvim/buffer.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "aucmd.c.generated.h" @@ -50,12 +52,47 @@ void aucmd_schedule_focusgained(bool gained) static void do_autocmd_focusgained(bool gained) { static bool recursive = false; + static Timestamp last_time = (time_t)0; + bool need_redraw = false; if (recursive) { return; // disallow recursion } recursive = true; - apply_autocmds((gained ? EVENT_FOCUSGAINED : EVENT_FOCUSLOST), - NULL, NULL, false, curbuf); + need_redraw |= apply_autocmds((gained ? EVENT_FOCUSGAINED : EVENT_FOCUSLOST), + NULL, NULL, false, curbuf); + + // When activated: Check if any file was modified outside of Vim. + // Only do this when not done within the last two seconds as: + // 1. Some filesystems have modification time granularity in seconds. Fat32 + // has a granularity of 2 seconds. + // 2. We could get multiple notifications in a row. + if (gained && last_time + (Timestamp)2000 < os_now()) { + need_redraw = check_timestamps(true); + last_time = os_now(); + } + + if (need_redraw) { + // Something was executed, make sure the cursor is put back where it + // belongs. + need_wait_return = false; + + if (State & CMDLINE) { + redrawcmdline(); + } else if ((State & NORMAL) || (State & INSERT)) { + if (must_redraw != 0) { + update_screen(0); + } + + setcursor(); + } + + ui_flush(); + } + + if (need_maketitle) { + maketitle(); + } + recursive = false; } diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 3ce39feda5..86067aceac 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -3084,28 +3084,29 @@ fileinfo( } vim_snprintf_add((char *)buffer, IOSIZE, "\"%s%s%s%s%s%s", - curbufIsChanged() ? (shortmess(SHM_MOD) - ? " [+]" : _(" [Modified]")) : " ", - (curbuf->b_flags & BF_NOTEDITED) - && !bt_dontwrite(curbuf) - ? _("[Not edited]") : "", - (curbuf->b_flags & BF_NEW) - && !bt_dontwrite(curbuf) - ? _("[New file]") : "", - (curbuf->b_flags & BF_READERR) ? _("[Read errors]") : "", - curbuf->b_p_ro ? (shortmess(SHM_RO) ? _("[RO]") - : _("[readonly]")) : "", - (curbufIsChanged() || (curbuf->b_flags & BF_WRITE_MASK) - || curbuf->b_p_ro) ? - " " : ""); - /* With 32 bit longs and more than 21,474,836 lines multiplying by 100 - * causes an overflow, thus for large numbers divide instead. */ - if (curwin->w_cursor.lnum > 1000000L) + curbufIsChanged() + ? (shortmess(SHM_MOD) ? " [+]" : _(" [Modified]")) : " ", + (curbuf->b_flags & BF_NOTEDITED) && !bt_dontwrite(curbuf) + ? _("[Not edited]") : "", + (curbuf->b_flags & BF_NEW) && !bt_dontwrite(curbuf) + ? new_file_message() : "", + (curbuf->b_flags & BF_READERR) + ? _("[Read errors]") : "", + curbuf->b_p_ro + ? (shortmess(SHM_RO) ? _("[RO]") : _("[readonly]")) : "", + (curbufIsChanged() + || (curbuf->b_flags & BF_WRITE_MASK) + || curbuf->b_p_ro) + ? " " : ""); + // With 32 bit longs and more than 21,474,836 lines multiplying by 100 + // causes an overflow, thus for large numbers divide instead. + if (curwin->w_cursor.lnum > 1000000L) { n = (int)(((long)curwin->w_cursor.lnum) / ((long)curbuf->b_ml.ml_line_count / 100L)); - else + } else { n = (int)(((long)curwin->w_cursor.lnum * 100L) / (long)curbuf->b_ml.ml_line_count); + } if (curbuf->b_ml.ml_flags & ML_EMPTY) { vim_snprintf_add((char *)buffer, IOSIZE, "%s", _(no_lines_msg)); } else if (p_ru) { @@ -3463,7 +3464,8 @@ int build_stl_str_hl( } type; } items[STL_MAX_ITEM]; #define TMPLEN 70 - char_u tmp[TMPLEN]; + char_u buf_tmp[TMPLEN]; + char_u win_tmp[TMPLEN]; char_u *usefmt = fmt; const int save_must_redraw = must_redraw; const int save_redr_type = curwin->w_redr_type; @@ -3471,10 +3473,18 @@ int build_stl_str_hl( // When the format starts with "%!" then evaluate it as an expression and // use the result as the actual format string. if (fmt[0] == '%' && fmt[1] == '!') { + typval_T tv = { + .v_type = VAR_NUMBER, + .vval.v_number = wp->handle, + }; + set_var(S_LEN("g:statusline_winid"), &tv, false); + usefmt = eval_to_string_safe(fmt + 2, NULL, use_sandbox); if (usefmt == NULL) { usefmt = fmt; } + + do_unlet(S_LEN("g:statusline_winid"), true); } if (fillchar == 0) { @@ -3903,8 +3913,10 @@ int build_stl_str_hl( // { Evaluate the expression // Store the current buffer number as a string variable - vim_snprintf((char *)tmp, sizeof(tmp), "%d", curbuf->b_fnum); - set_internal_string_var((char_u *)"g:actual_curbuf", tmp); + vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), "%d", curbuf->b_fnum); + set_internal_string_var((char_u *)"g:actual_curbuf", buf_tmp); + vim_snprintf((char *)win_tmp, sizeof(win_tmp), "%d", curwin->handle); + set_internal_string_var((char_u *)"g:actual_curwin", win_tmp); buf_T *const save_curbuf = curbuf; win_T *const save_curwin = curwin; @@ -3925,6 +3937,7 @@ int build_stl_str_hl( // Remove the variable we just stored do_unlet(S_LEN("g:actual_curbuf"), true); + do_unlet(S_LEN("g:actual_curwin"), true); // } @@ -3983,8 +3996,8 @@ int build_stl_str_hl( // Store the position percentage in our temporary buffer. // Note: We cannot store the value in `num` because // `get_rel_pos` can return a named position. Ex: "Top" - get_rel_pos(wp, tmp, TMPLEN); - str = tmp; + get_rel_pos(wp, buf_tmp, TMPLEN); + str = buf_tmp; break; case STL_ARGLISTSTAT: @@ -3994,19 +4007,19 @@ int build_stl_str_hl( // at the end of the null-terminated string. // Setting the first byte to null means it will place the argument // number string at the beginning of the buffer. - tmp[0] = 0; + buf_tmp[0] = 0; // Note: The call will only return true if it actually - // appended data to the `tmp` buffer. - if (append_arg_number(wp, tmp, (int)sizeof(tmp), false)) { - str = tmp; + // appended data to the `buf_tmp` buffer. + if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp), false)) { + str = buf_tmp; } break; case STL_KEYMAP: fillable = false; - if (get_keymap_str(wp, (char_u *)"<%s>", tmp, TMPLEN)) { - str = tmp; + if (get_keymap_str(wp, (char_u *)"<%s>", buf_tmp, TMPLEN)) { + str = buf_tmp; } break; case STL_PAGENUM: @@ -4063,9 +4076,9 @@ int build_stl_str_hl( // (including the brackets and null terminating character) if (*wp->w_buffer->b_p_ft != NUL && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 3) { - vim_snprintf((char *)tmp, sizeof(tmp), "[%s]", - wp->w_buffer->b_p_ft); - str = tmp; + vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), "[%s]", + wp->w_buffer->b_p_ft); + str = buf_tmp; } break; @@ -4077,13 +4090,13 @@ int build_stl_str_hl( // (including the comma and null terminating character) if (*wp->w_buffer->b_p_ft != NUL && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 2) { - vim_snprintf((char *)tmp, sizeof(tmp), ",%s", - wp->w_buffer->b_p_ft); + vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), ",%s", + wp->w_buffer->b_p_ft); // Uppercase the file extension - for (char_u *t = tmp; *t != 0; t++) { + for (char_u *t = buf_tmp; *t != 0; t++) { *t = (char_u)TOUPPER_LOC(*t); } - str = tmp; + str = buf_tmp; } break; } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index d696eedbb7..550f8a5e40 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -373,6 +373,10 @@ struct stl_hlrec { #define SYNSPL_TOP 1 // spell check toplevel text #define SYNSPL_NOTOP 2 // don't spell check toplevel text +// values for b_syn_foldlevel: how to compute foldlevel on a line +#define SYNFLD_START 0 // use level of item at start of line +#define SYNFLD_MINIMUM 1 // use lowest local minimum level on line + // avoid #ifdefs for when b_spell is not available # define B_SPELL(buf) ((buf)->b_spell) @@ -398,6 +402,7 @@ typedef struct { int b_syn_error; // TRUE when error occurred in HL bool b_syn_slow; // true when 'redrawtime' reached int b_syn_ic; // ignore case for :syn cmds + int b_syn_foldlevel; // how to compute foldlevel on a line int b_syn_spell; // SYNSPL_ values garray_T b_syn_patterns; // table for syntax patterns garray_T b_syn_clusters; // table for syntax clusters diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 66cd0e09c6..0a83c3a586 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2818,7 +2818,7 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) /// @param[in] fonceit If true, do not complain if variable doesn’t exist. /// /// @return OK if it existed, FAIL otherwise. -int do_unlet(const char *const name, const size_t name_len, const int forceit) +int do_unlet(const char *const name, const size_t name_len, const bool forceit) FUNC_ATTR_NONNULL_ALL { const char *varname; @@ -5552,19 +5552,18 @@ void prepare_assert_error(garray_T *gap) } } -// Append "str" to "gap", escaping unprintable characters. +// Append "p[clen]" to "gap", escaping unprintable characters. // Changes NL to \n, CR to \r, etc. -static void ga_concat_esc(garray_T *gap, char_u *str) +static void ga_concat_esc(garray_T *gap, const char_u *p, int clen) + FUNC_ATTR_NONNULL_ALL { - char_u *p; char_u buf[NUMBUFLEN]; - if (str == NULL) { - ga_concat(gap, (char_u *)"NULL"); - return; - } - - for (p = str; *p != NUL; p++) { + if (clen > 1) { + memmove(buf, p, clen); + buf[clen] = NUL; + ga_concat(gap, buf); + } else { switch (*p) { case BS: ga_concat(gap, (char_u *)"\\b"); break; case ESC: ga_concat(gap, (char_u *)"\\e"); break; @@ -5585,6 +5584,41 @@ static void ga_concat_esc(garray_T *gap, char_u *str) } } +// Append "str" to "gap", escaping unprintable characters. +// Changes NL to \n, CR to \r, etc. +static void ga_concat_shorten_esc(garray_T *gap, const char_u *str) + FUNC_ATTR_NONNULL_ARG(1) +{ + char_u buf[NUMBUFLEN]; + + if (str == NULL) { + ga_concat(gap, (char_u *)"NULL"); + return; + } + + for (const char_u *p = str; *p != NUL; p++) { + int same_len = 1; + const char_u *s = p; + const int c = mb_ptr2char_adv(&s); + const int clen = s - p; + while (*s != NUL && c == utf_ptr2char(s)) { + same_len++; + s += clen; + } + if (same_len > 20) { + ga_concat(gap, (char_u *)"\\["); + ga_concat_esc(gap, p, clen); + ga_concat(gap, (char_u *)" occurs "); + vim_snprintf((char *)buf, NUMBUFLEN, "%d", same_len); + ga_concat(gap, buf); + ga_concat(gap, (char_u *)" times]"); + p = s - 1; + } else { + ga_concat_esc(gap, p, clen); + } + } +} + // Fill "gap" with information about an assert error. void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_str, typval_T *exp_tv, @@ -5609,10 +5643,10 @@ void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, if (exp_str == NULL) { tofree = (char_u *)encode_tv2string(exp_tv, NULL); - ga_concat_esc(gap, tofree); + ga_concat_shorten_esc(gap, tofree); xfree(tofree); } else { - ga_concat_esc(gap, exp_str); + ga_concat_shorten_esc(gap, exp_str); } if (atype != ASSERT_NOTEQUAL) { @@ -5624,7 +5658,7 @@ void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, ga_concat(gap, (char_u *)" but got "); } tofree = (char_u *)encode_tv2string(got_tv, NULL); - ga_concat_esc(gap, tofree); + ga_concat_shorten_esc(gap, tofree); xfree(tofree); } } @@ -5674,6 +5708,9 @@ int assert_equalfile(typval_T *argvars) IObuff[0] = NUL; FILE *const fd1 = os_fopen(fname1, READBIN); + char line1[200]; + char line2[200]; + ptrdiff_t lineidx = 0; if (fd1 == NULL) { snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname1); } else { @@ -5682,6 +5719,7 @@ int assert_equalfile(typval_T *argvars) fclose(fd1); snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname2); } else { + int64_t linecount = 1; for (int64_t count = 0; ; count++) { const int c1 = fgetc(fd1); const int c2 = fgetc(fd2); @@ -5693,10 +5731,24 @@ int assert_equalfile(typval_T *argvars) } else if (c2 == EOF) { STRCPY(IObuff, "second file is shorter"); break; - } else if (c1 != c2) { - snprintf((char *)IObuff, IOSIZE, - "difference at byte %" PRId64, count); - break; + } else { + line1[lineidx] = c1; + line2[lineidx] = c2; + lineidx++; + if (c1 != c2) { + snprintf((char *)IObuff, IOSIZE, + "difference at byte %" PRId64 ", line %" PRId64, + count, linecount); + break; + } + } + if (c1 == NL) { + linecount++; + lineidx = 0; + } else if (lineidx + 2 == (ptrdiff_t)sizeof(line1)) { + memmove(line1, line1 + 100, lineidx - 100); + memmove(line2, line2 + 100, lineidx - 100); + lineidx -= 100; } } fclose(fd1); @@ -5705,7 +5757,24 @@ int assert_equalfile(typval_T *argvars) } if (IObuff[0] != NUL) { prepare_assert_error(&ga); + if (argvars[2].v_type != VAR_UNKNOWN) { + char *const tofree = encode_tv2echo(&argvars[2], NULL); + ga_concat(&ga, (char_u *)tofree); + xfree(tofree); + ga_concat(&ga, (char_u *)": "); + } ga_concat(&ga, IObuff); + if (lineidx > 0) { + line1[lineidx] = NUL; + line2[lineidx] = NUL; + ga_concat(&ga, (char_u *)" after \""); + ga_concat(&ga, (char_u *)line1); + if (STRCMP(line1, line2) != 0) { + ga_concat(&ga, (char_u *)"\" vs \""); + ga_concat(&ga, (char_u *)line2); + } + ga_concat(&ga, (char_u *)"\""); + } assert_error(&ga); ga_clear(&ga); return 1; @@ -7143,6 +7212,16 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg) func_ref(name); callback->data.funcref = vim_strsave(name); callback->type = kCallbackFuncref; + } else if (nlua_is_table_from_lua(arg)) { + char_u *name = nlua_register_table_as_callable(arg); + + if (name != NULL) { + func_ref(name); + callback->data.funcref = vim_strsave(name); + callback->type = kCallbackFuncref; + } else { + r = FAIL; + } } else if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0) { callback->type = kCallbackNone; } else { @@ -8137,9 +8216,6 @@ void set_argv_var(char **argv, int argc) list_T *l = tv_list_alloc(argc); int i; - if (l == NULL) { - getout(1); - } tv_list_set_lock(l, VAR_FIXED); for (i = 0; i < argc; i++) { tv_list_append_string(l, (const char *const)argv[i], -1); diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 51872a7ba8..d20e381481 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -28,7 +28,7 @@ return { asin={args=1, func="float_op_wrapper", data="&asin"}, -- WJMc assert_beeps={args={1, 2}}, assert_equal={args={2, 3}}, - assert_equalfile={args=2}, + assert_equalfile={args={2, 3}}, assert_exception={args={1, 2}}, assert_fails={args={1, 3}}, assert_false={args={1, 2}}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 1071e75c06..831167a489 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -415,7 +415,7 @@ static void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL); } -// "assert_equalfile(fname-one, fname-two)" function +// "assert_equalfile(fname-one, fname-two[, msg])" function static void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = assert_equalfile(argvars); @@ -824,9 +824,12 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else if (argvars[0].v_type == VAR_PARTIAL) { partial = argvars[0].vval.v_partial; func = partial_name(partial); + } else if (nlua_is_table_from_lua(&argvars[0])) { + func = nlua_register_table_as_callable(&argvars[0]); } else { func = (char_u *)tv_get_string(&argvars[0]); } + if (*func == NUL) { return; // type error or empty name } diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 0daaf6c878..2394eb8099 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -16,6 +16,7 @@ #include "nvim/eval/typval_encode.h" #include "nvim/eval.h" #include "nvim/eval/userfunc.h" +#include "nvim/lua/executor.h" #include "nvim/types.h" #include "nvim/assert.h" #include "nvim/memory.h" @@ -301,6 +302,7 @@ void tv_list_free_list(list_T *const l) } list_log(l, NULL, NULL, "freelist"); + nlua_free_typval_list(l); xfree(l); } @@ -797,10 +799,14 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, if (l1 == l2) { return true; } - if (l1 == NULL || l2 == NULL) { + if (tv_list_len(l1) != tv_list_len(l2)) { return false; } - if (tv_list_len(l1) != tv_list_len(l2)) { + if (tv_list_len(l1) == 0) { + // empty and NULL list are considered equal + return true; + } + if (l1 == NULL || l2 == NULL) { return false; } @@ -1374,6 +1380,7 @@ void tv_dict_free_dict(dict_T *const d) d->dv_used_next->dv_used_prev = d->dv_used_prev; } + nlua_free_typval_dict(d); xfree(d); } diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 343dd205ff..503a32a81e 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -180,6 +180,8 @@ struct listvar_S { int lv_idx; ///< Index of a cached item, used for optimising repeated l[idx]. int lv_copyID; ///< ID used by deepcopy(). VarLockStatus lv_lock; ///< Zero, VAR_LOCKED, VAR_FIXED. + + LuaRef lua_table_ref; }; // Static list with 10 items. Use tv_list_init_static10() to initialize. @@ -245,6 +247,8 @@ struct dictvar_S { dict_T *dv_used_next; ///< Next dictionary in used dictionaries list. dict_T *dv_used_prev; ///< Previous dictionary in used dictionaries list. QUEUE watchers; ///< Dictionary key watchers set by user code. + + LuaRef lua_table_ref; }; /// Type used for script ID @@ -271,6 +275,12 @@ typedef struct { /// Number of fixed variables used for arguments #define FIXVAR_CNT 12 +/// Callback interface for C function reference> +/// Used for managing functions that were registered with |register_cfunc| +typedef int (*cfunc_T)(int argcount, typval_T *argvars, typval_T *rettv, void *state); // NOLINT +/// Callback to clear cfunc_T and any associated state. +typedef void (*cfunc_free_T)(void *state); + // Structure to hold info for a function that is currently being executed. typedef struct funccall_S funccall_T; @@ -307,6 +317,10 @@ struct ufunc { garray_T uf_lines; ///< function lines int uf_profiling; ///< true when func is being profiled int uf_prof_initialized; + // Managing cfuncs + cfunc_T uf_cb; ///< C function extension callback + cfunc_free_T uf_cb_free; ///< C function extesion free callback + void *uf_cb_state; ///< State of C function extension. // Profiling the function as a whole. int uf_tm_count; ///< nr of calls proftime_T uf_tm_total; ///< time spent in function + children diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 0aa64b1d5f..91c948ce7e 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -364,7 +364,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID, kMPConvList); TYPVAL_ENCODE_CONV_LIST_START(tv, tv_list_len(tv->vval.v_list)); - assert(saved_copyID != copyID && saved_copyID != copyID - 1); + assert(saved_copyID != copyID); _mp_push(*mpstack, ((MPConvStackVal) { .type = kMPConvList, .tv = tv, diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 4d658498c1..229f0e8dde 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -32,6 +32,7 @@ #define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0 #define FC_REMOVED 0x20 // function redefined while uf_refcount > 0 #define FC_SANDBOX 0x40 // function defined in the sandbox +#define FC_CFUNC 0x80 // C function extension #ifdef INCLUDE_GENERATED_DECLARATIONS #include "eval/userfunc.c.generated.h" @@ -162,6 +163,17 @@ static void register_closure(ufunc_T *fp) [current_funccal->fc_funcs.ga_len++] = fp; } + +/// Get a name for a lambda. Returned in static memory. +char_u * get_lambda_name(void) +{ + static char_u name[30]; + static int lambda_no = 0; + + snprintf((char *)name, sizeof(name), "<lambda>%d", ++lambda_no); + return name; +} + /// Parse a lambda expression and get a Funcref from "*arg". /// /// @return OK or FAIL. Returns NOTDONE for dict or {expr}. @@ -175,7 +187,6 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) int ret; char_u *start = skipwhite(*arg + 1); char_u *s, *e; - static int lambda_no = 0; bool *old_eval_lavars = eval_lavars_used; bool eval_lavars = false; @@ -219,11 +230,9 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) if (evaluate) { int len, flags = 0; char_u *p; - char_u name[20]; garray_T newlines; - lambda_no++; - snprintf((char *)name, sizeof(name), "<lambda>%d", lambda_no); + char_u *name = get_lambda_name(); fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); pt = xcalloc(1, sizeof(partial_T)); @@ -700,6 +709,11 @@ static void func_clear_items(ufunc_T *fp) ga_clear_strings(&(fp->uf_args)); ga_clear_strings(&(fp->uf_lines)); + if (fp->uf_cb_free != NULL) { + fp->uf_cb_free(fp->uf_cb_state); + fp->uf_cb_free = NULL; + } + XFREE_CLEAR(fp->uf_tml_count); XFREE_CLEAR(fp->uf_tml_total); XFREE_CLEAR(fp->uf_tml_self); @@ -1408,6 +1422,9 @@ call_func( if (fp != NULL && (fp->uf_flags & FC_DELETED)) { error = ERROR_DELETED; + } else if (fp != NULL && (fp->uf_flags & FC_CFUNC)) { + cfunc_T cb = fp->uf_cb; + error = (*cb)(argcount, argvars, rettv, fp->uf_cb_state); } else if (fp != NULL) { if (argv_func != NULL) { // postponed filling in the arguments, do it now @@ -3435,3 +3452,29 @@ bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) xfree(tofree); return abort; } + +/// Registers a C extension user function. +char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state) +{ + char_u *name = get_lambda_name(); + ufunc_T *fp = NULL; + + fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); + if (fp == NULL) { + return NULL; + } + + fp->uf_refcount = 1; + fp->uf_varargs = true; + fp->uf_flags = FC_CFUNC; + fp->uf_calls = 0; + fp->uf_script_ctx = current_sctx; + fp->uf_cb = cb; + fp->uf_cb_free = cb_free; + fp->uf_cb_state = state; + + STRCPY(fp->uf_name, name); + hash_add(&func_hashtab, UF2HIKEY(fp)); + + return fp->uf_name; +} diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 8a0f2e634a..519978f4fb 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -109,6 +109,8 @@ typedef struct { # include "ex_cmds.c.generated.h" #endif +static int preview_bufnr = 0; + /// ":ascii" and "ga" implementation void do_ascii(const exarg_T *const eap) { @@ -3244,7 +3246,7 @@ static char_u *sub_parse_flags(char_u *cmd, subflags_T *subflags, /// @param do_buf_event If `true`, send buffer updates. /// @return buffer used for 'inccommand' preview static buf_T *do_sub(exarg_T *eap, proftime_T timeout, - bool do_buf_event) + bool do_buf_event, handle_T bufnr) { long i = 0; regmmatch_T regmatch; @@ -4198,7 +4200,7 @@ skip: } curbuf->b_changed = save_b_changed; // preserve 'modified' during preview preview_buf = show_sub(eap, old_cursor, &preview_lines, - pre_hl_id, pre_src_id); + pre_hl_id, pre_src_id, bufnr); if (subsize > 0) { extmark_clear(orig_buf, pre_src_id, eap->line1-1, 0, kv_last(preview_lines.subresults).end.lnum-1, MAXCOL); @@ -5575,14 +5577,31 @@ void ex_helpclose(exarg_T *eap) } } +/// Tries to enter to an existing window of given buffer. If no existing buffer +/// is found, creates a new split. +/// +/// Returns OK/FAIL. +int sub_preview_win(buf_T *preview_buf) +{ + if (preview_buf != NULL) { + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == preview_buf) { + win_enter(wp, false); + + return OK; + } + } + } + return win_split((int)p_cwh, WSP_BOT); +} + /// Shows the effects of the :substitute command being typed ('inccommand'). /// If inccommand=split, shows a preview window and later restores the layout. static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, - PreviewLines *preview_lines, int hl_id, int src_id) + PreviewLines *preview_lines, int hl_id, int src_id, + handle_T bufnr) FUNC_ATTR_NONNULL_ALL { - static handle_T bufnr = 0; // special buffer, re-used on each visit - win_T *save_curwin = curwin; cmdmod_T save_cmdmod = cmdmod; char_u *save_shm_p = vim_strsave(p_shm); @@ -5600,9 +5619,9 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, bool outside_curline = (eap->line1 != old_cusr.lnum || eap->line2 != old_cusr.lnum); - bool split = outside_curline && (*p_icm != 'n'); + bool preview = outside_curline && (*p_icm != 'n'); if (preview_buf == curbuf) { // Preview buffer cannot preview itself! - split = false; + preview = false; preview_buf = NULL; } @@ -5620,11 +5639,10 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, linenr_T highest_num_line = 0; int col_width = 0; - if (split && win_split((int)p_cwh, WSP_BOT) != FAIL) { + if (preview && sub_preview_win(preview_buf) != FAIL) { buf_open_scratch(preview_buf ? bufnr : 0, "[Preview]"); buf_clear(); preview_buf = curbuf; - bufnr = preview_buf->handle; curbuf->b_p_bl = false; curbuf->b_p_ma = true; curbuf->b_p_ul = -1; @@ -5713,7 +5731,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, win_enter(save_curwin, false); // Return to original window update_topline(); - // Update screen now. Must do this _before_ close_windows(). + // Update screen now. int save_rd = RedrawingDisabled; RedrawingDisabled = 0; update_screen(SOME_VALID); @@ -5727,6 +5745,17 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, return preview_buf; } +/// Closes any open windows for inccommand preview buffer. +void close_preview_windows(void) +{ + block_autocmds(); + buf_T *buf = preview_bufnr ? buflist_findnr(preview_bufnr) : NULL; + if (buf != NULL) { + close_windows(buf, false); + } + unblock_autocmds(); +} + /// :substitute command /// /// If 'inccommand' is empty: calls do_sub(). @@ -5736,7 +5765,9 @@ void ex_substitute(exarg_T *eap) { bool preview = (State & CMDPREVIEW); if (*p_icm == NUL || !preview) { // 'inccommand' is disabled - (void)do_sub(eap, profile_zero(), true); + close_preview_windows(); + (void)do_sub(eap, profile_zero(), true, preview_bufnr); + return; } @@ -5760,9 +5791,14 @@ void ex_substitute(exarg_T *eap) // Don't show search highlighting during live substitution bool save_hls = p_hls; p_hls = false; - buf_T *preview_buf = do_sub(eap, profile_setlimit(p_rdt), false); + buf_T *preview_buf = do_sub(eap, profile_setlimit(p_rdt), false, + preview_bufnr); p_hls = save_hls; + if (preview_buf != NULL) { + preview_bufnr = preview_buf->handle; + } + if (save_changedtick != buf_get_changedtick(curbuf)) { // Undo invisibly. This also moves the cursor! if (!u_undo_and_forget(1)) { abort(); } @@ -5772,10 +5808,7 @@ void ex_substitute(exarg_T *eap) curbuf->b_u_time_cur = save_b_u_time_cur; buf_set_changedtick(curbuf, save_changedtick); } - if (buf_valid(preview_buf)) { - // XXX: Must do this *after* u_undo_and_forget(), why? - close_windows(preview_buf, false); - } + curbuf->b_p_ul = save_b_p_ul; curwin->w_p_cul = save_w_p_cul; // Restore 'cursorline' curwin->w_p_cuc = save_w_p_cuc; // Restore 'cursorcolumn' diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 1a836b7a98..93684e8606 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -450,6 +450,11 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) ExpandCleanup(&s->xpc); ccline.xpc = NULL; + if (s->gotesc) { + // There might be a preview window open for inccommand. Close it. + close_preview_windows(); + } + if (s->did_incsearch) { if (s->gotesc) { curwin->w_cursor = s->save_cursor; @@ -1958,8 +1963,10 @@ static int command_line_changed(CommandLineState *s) update_topline(); redrawcmdline(); + } else if (State & CMDPREVIEW) { State = (State & ~CMDPREVIEW); + close_preview_windows(); update_screen(SOME_VALID); // Clear 'inccommand' preview. } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index f29304867a..20f0cdccc3 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -569,20 +569,21 @@ readfile( return FAIL; } } - if (dir_of_file_exists(fname)) - filemess(curbuf, sfname, (char_u *)_("[New File]"), 0); - else - filemess(curbuf, sfname, - (char_u *)_("[New DIRECTORY]"), 0); - /* Even though this is a new file, it might have been - * edited before and deleted. Get the old marks. */ + if (dir_of_file_exists(fname)) { + filemess(curbuf, sfname, (char_u *)new_file_message(), 0); + } else { + filemess(curbuf, sfname, (char_u *)_("[New DIRECTORY]"), 0); + } + // Even though this is a new file, it might have been + // edited before and deleted. Get the old marks. check_marks_read(); - /* Set forced 'fileencoding'. */ - if (eap != NULL) + // Set forced 'fileencoding'. + if (eap != NULL) { set_forced_fenc(eap); + } apply_autocmds_exarg(EVENT_BUFNEWFILE, sfname, sfname, - FALSE, curbuf, eap); - /* remember the current fileformat */ + false, curbuf, eap); + // remember the current fileformat save_file_ff(curbuf); if (aborting()) /* autocmds may abort script processing */ @@ -2203,6 +2204,11 @@ static void check_marks_read(void) curbuf->b_marks_read = true; } +char *new_file_message(void) +{ + return shortmess(SHM_NEW) ? _("[New]") : _("[New File]"); +} + /* * buf_write() - write to file "fname" lines "start" through "end" * @@ -3513,8 +3519,8 @@ restore_backup: STRCAT(IObuff, _("[Device]")); c = TRUE; } else if (newfile) { - STRCAT(IObuff, shortmess(SHM_NEW) ? _("[New]") : _("[New File]")); - c = TRUE; + STRCAT(IObuff, new_file_message()); + c = true; } if (no_eol) { msg_add_eol(); diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c index f1f84e63be..4a64cc31b1 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -210,10 +210,25 @@ struct prt_ps_mbfont_S { char *defcs; }; +// Types of PS resource file currently used +typedef enum { + PRT_RESOURCE_TYPE_PROCSET = 0, + PRT_RESOURCE_TYPE_ENCODING = 1, + PRT_RESOURCE_TYPE_CMAP = 2, +} PrtResourceType; + +// String versions of PS resource types +static const char *const prt_resource_types[] = +{ + [PRT_RESOURCE_TYPE_PROCSET] = "procset", + [PRT_RESOURCE_TYPE_ENCODING] = "encoding", + [PRT_RESOURCE_TYPE_CMAP] = "cmap", +}; + struct prt_ps_resource_S { char_u name[64]; char_u filename[MAXPATHL + 1]; - int type; + PrtResourceType type; char_u title[256]; char_u version[256]; }; @@ -1171,11 +1186,6 @@ static struct prt_ps_mbfont_S prt_ps_mbfonts[] = } }; -// Types of PS resource file currently used -#define PRT_RESOURCE_TYPE_PROCSET (0) -#define PRT_RESOURCE_TYPE_ENCODING (1) -#define PRT_RESOURCE_TYPE_CMAP (2) - /* The PS prolog file version number has to match - if the prolog file is * updated, increment the number in the file and here. Version checking was * added as of VIM 6.2. @@ -1189,16 +1199,6 @@ static struct prt_ps_mbfont_S prt_ps_mbfonts[] = #define PRT_PROLOG_VERSION ((char_u *)"1.4") #define PRT_CID_PROLOG_VERSION ((char_u *)"1.0") -/* String versions of PS resource types - indexed by constants above so don't - * re-order! - */ -static char *prt_resource_types[] = -{ - "procset", - "encoding", - "cmap" -}; - // Strings to look for in a PS resource file #define PRT_RESOURCE_HEADER "%!PS-Adobe-" #define PRT_RESOURCE_RESOURCE "Resource-" @@ -1845,10 +1845,11 @@ static void prt_dsc_ints(char *comment, int count, int *ints) } static void prt_dsc_resources( - char *comment, // if NULL add to previous - char *type, - char *string + const char *comment, // if NULL add to previous + const char *type, + const char *string ) + FUNC_ATTR_NONNULL_ARG(2, 3) { if (comment != NULL) vim_snprintf((char *)prt_line_buffer, sizeof(prt_line_buffer), diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 69114c967d..32e804d213 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -19,6 +19,7 @@ #include "nvim/globals.h" #include "nvim/message.h" #include "nvim/eval/typval.h" +#include "nvim/eval/userfunc.h" #include "nvim/ascii.h" #include "nvim/macros.h" @@ -50,6 +51,7 @@ typedef struct { #define LUA_PUSH_STATIC_STRING(lstate, s) \ lua_pushlstring(lstate, s, sizeof(s) - 1) + static LuaTableProps nlua_traverse_table(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { @@ -314,6 +316,13 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) break; } case LUA_TTABLE: { + // Only need to track table refs if we have a metatable associated. + LuaRef table_ref = LUA_NOREF; + if (lua_getmetatable(lstate, -1)) { + lua_pop(lstate, 1); + table_ref = nlua_ref(lstate, -1); + } + const LuaTableProps table_props = nlua_traverse_table(lstate); for (size_t i = 0; i < kv_size(stack); i++) { @@ -329,6 +338,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) case kObjectTypeArray: { cur.tv->v_type = VAR_LIST; cur.tv->vval.v_list = tv_list_alloc((ptrdiff_t)table_props.maxidx); + cur.tv->vval.v_list->lua_table_ref = table_ref; tv_list_ref(cur.tv->vval.v_list); if (table_props.maxidx != 0) { cur.container = true; @@ -342,6 +352,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) cur.tv->v_type = VAR_DICT; cur.tv->vval.v_dict = tv_dict_alloc(); cur.tv->vval.v_dict->dv_refcount++; + cur.tv->vval.v_dict->lua_table_ref = table_ref; } else { cur.special = table_props.has_string_with_nul; if (table_props.has_string_with_nul) { @@ -352,11 +363,13 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) S_LEN("_VAL")); assert(val_di != NULL); cur.tv = &val_di->di_tv; + cur.tv->vval.v_list->lua_table_ref = table_ref; assert(cur.tv->v_type == VAR_LIST); } else { cur.tv->v_type = VAR_DICT; cur.tv->vval.v_dict = tv_dict_alloc(); cur.tv->vval.v_dict->dv_refcount++; + cur.tv->vval.v_dict->lua_table_ref = table_ref; } cur.container = true; cur.idx = lua_gettop(lstate); @@ -384,6 +397,20 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) nlua_pop_typval_table_processing_end: break; } + case LUA_TFUNCTION: { + LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); + state->lua_callable.func_ref = nlua_ref(lstate, -1); + state->lua_callable.table_ref = LUA_NOREF; + + char_u *name = register_cfunc( + &nlua_CFunction_func_call, + &nlua_CFunction_func_free, + state); + + cur.tv->v_type = VAR_FUNC; + cur.tv->vval.v_string = vim_strsave(name); + break; + } case LUA_TUSERDATA: { nlua_pushref(lstate, nlua_nil_ref); bool is_nil = lua_rawequal(lstate, -2, -1); diff --git a/src/nvim/lua/converter.h b/src/nvim/lua/converter.h index 542c56ea3e..8601a32418 100644 --- a/src/nvim/lua/converter.h +++ b/src/nvim/lua/converter.h @@ -9,6 +9,15 @@ #include "nvim/func_attr.h" #include "nvim/eval.h" +typedef struct { + LuaRef func_ref; + LuaRef table_ref; +} LuaCallable; + +typedef struct { + LuaCallable lua_callable; +} LuaCFunctionState; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/converter.h.generated.h" #endif diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 327ed6d6b7..9f30609d66 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -35,8 +35,8 @@ #include "nvim/os/os.h" #endif -#include "nvim/lua/executor.h" #include "nvim/lua/converter.h" +#include "nvim/lua/executor.h" #include "nvim/lua/treesitter.h" #include "luv/luv.h" @@ -53,6 +53,15 @@ typedef struct { # include "lua/executor.c.generated.h" #endif +#define PUSH_ALL_TYPVALS(lstate, args, argcount, special) \ + for (int i = 0; i < argcount; i++) { \ + if (args[i].v_type == VAR_UNKNOWN) { \ + lua_pushnil(lstate); \ + } else { \ + nlua_push_typval(lstate, &args[i], special); \ + } \ + } + /// Convert lua error into a Vim error message /// /// @param lstate Lua interpreter state. @@ -700,24 +709,25 @@ int nlua_call(lua_State *lstate) } TRY_WRAP({ - // TODO(bfredl): this should be simplified in error handling refactor - force_abort = false; - suppress_errthrow = false; - current_exception = NULL; - did_emsg = false; - - try_start(); - typval_T rettv; - int dummy; - // call_func() retval is deceptive, ignore it. Instead we set `msg_list` - // (TRY_WRAP) to capture abort-causing non-exception errors. - (void)call_func(name, (int)name_len, &rettv, nargs, - vim_args, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &dummy, true, NULL, NULL); - if (!try_end(&err)) { - nlua_push_typval(lstate, &rettv, false); - } - tv_clear(&rettv); + // TODO(bfredl): this should be simplified in error handling refactor + force_abort = false; + suppress_errthrow = false; + current_exception = NULL; + did_emsg = false; + + try_start(); + typval_T rettv; + int dummy; + // call_func() retval is deceptive, ignore it. Instead we set `msg_list` + // (TRY_WRAP) to capture abort-causing non-exception errors. + (void)call_func(name, (int)name_len, &rettv, nargs, + vim_args, NULL, + curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &dummy, true, NULL, NULL); + if (!try_end(&err)) { + nlua_push_typval(lstate, &rettv, false); + } + tv_clear(&rettv); }); free_vim_args: @@ -833,12 +843,25 @@ void executor_free_luaref(LuaRef ref) nlua_unref(lstate, ref); } -/// push a value referenced in the regirstry +/// push a value referenced in the registry void nlua_pushref(lua_State *lstate, LuaRef ref) { lua_rawgeti(lstate, LUA_REGISTRYINDEX, ref); } +/// Gets a new reference to an object stored at original_ref +/// +/// NOTE: It does not copy the value, it creates a new ref to the lua object. +/// Leaves the stack unchanged. +LuaRef nlua_newref(lua_State *lstate, LuaRef original_ref) +{ + nlua_pushref(lstate, original_ref); + LuaRef new_ref = nlua_ref(lstate, -1); + lua_pop(lstate, 1); + + return new_ref; +} + /// Evaluate lua string /// /// Used for luaeval(). @@ -916,13 +939,8 @@ static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name, return; } - for (int i = 0; i < argcount; i++) { - if (args[i].v_type == VAR_UNKNOWN) { - lua_pushnil(lstate); - } else { - nlua_push_typval(lstate, &args[i], special); - } - } + PUSH_ALL_TYPVALS(lstate, args, argcount, special); + if (lua_pcall(lstate, argcount, ret_tv ? 1 : 0, 0)) { nlua_error(lstate, _("E5108: Error executing lua %.*s")); return; @@ -933,6 +951,51 @@ static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name, } } +/// Call a LuaCallable given some typvals +/// +/// Used to call any lua callable passed from Lua into VimL +/// +/// @param[in] lstate Lua State +/// @param[in] lua_cb Lua Callable +/// @param[in] argcount Count of typval arguments +/// @param[in] argvars Typval Arguments +/// @param[out] rettv The return value from the called function. +int typval_exec_lua_callable( + lua_State *lstate, + LuaCallable lua_cb, + int argcount, + typval_T *argvars, + typval_T *rettv +) +{ + int offset = 0; + LuaRef cb = lua_cb.func_ref; + + if (cb == LUA_NOREF) { + // This shouldn't happen. + luaL_error(lstate, "Invalid function passed to VimL"); + return ERROR_OTHER; + } + + nlua_pushref(lstate, cb); + + if (lua_cb.table_ref != LUA_NOREF) { + offset += 1; + nlua_pushref(lstate, lua_cb.table_ref); + } + + PUSH_ALL_TYPVALS(lstate, argvars, argcount, false); + + if (lua_pcall(lstate, argcount + offset, 1, 0)) { + luaL_error(lstate, "nlua_CFunction_func_call failed."); + return ERROR_OTHER; + } + + nlua_pop_typval(lstate, rettv); + + return ERROR_NONE; +} + /// Execute Lua string /// /// Used for nvim_exec_lua(). @@ -1128,21 +1191,11 @@ void ex_luafile(exarg_T *const eap) } } -static int create_tslua_parser(lua_State *L) -{ - if (lua_gettop(L) < 1 || !lua_isstring(L, 1)) { - return luaL_error(L, "string expected"); - } - - const char *lang_name = lua_tostring(L, 1); - return tslua_push_parser(L, lang_name); -} - static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { tslua_init(lstate); - lua_pushcfunction(lstate, create_tslua_parser); + lua_pushcfunction(lstate, tslua_push_parser); lua_setfield(lstate, -2, "_create_ts_parser"); lua_pushcfunction(lstate, tslua_add_language); @@ -1290,3 +1343,115 @@ static int regex_match_line(lua_State *lstate) return nret; } + +int nlua_CFunction_func_call( + int argcount, + typval_T *argvars, + typval_T *rettv, + void *state) +{ + lua_State *const lstate = nlua_enter(); + LuaCFunctionState *funcstate = (LuaCFunctionState *)state; + + return typval_exec_lua_callable( + lstate, + funcstate->lua_callable, + argcount, + argvars, + rettv); +} +/// Required functions for lua c functions as VimL callbacks +void nlua_CFunction_func_free(void *state) +{ + lua_State *const lstate = nlua_enter(); + LuaCFunctionState *funcstate = (LuaCFunctionState *)state; + + nlua_unref(lstate, funcstate->lua_callable.func_ref); + nlua_unref(lstate, funcstate->lua_callable.table_ref); + xfree(funcstate); +} + +bool nlua_is_table_from_lua(typval_T *const arg) +{ + if (arg->v_type != VAR_DICT && arg->v_type != VAR_LIST) { + return false; + } + + if (arg->v_type == VAR_DICT) { + return arg->vval.v_dict->lua_table_ref > 0 + && arg->vval.v_dict->lua_table_ref != LUA_NOREF; + } else if (arg->v_type == VAR_LIST) { + return arg->vval.v_list->lua_table_ref > 0 + && arg->vval.v_list->lua_table_ref != LUA_NOREF; + } + + return false; +} + +char_u *nlua_register_table_as_callable(typval_T *const arg) +{ + if (!nlua_is_table_from_lua(arg)) { + return NULL; + } + + LuaRef table_ref; + if (arg->v_type == VAR_DICT) { + table_ref = arg->vval.v_dict->lua_table_ref; + } else if (arg->v_type == VAR_LIST) { + table_ref = arg->vval.v_list->lua_table_ref; + } else { + return NULL; + } + + lua_State *const lstate = nlua_enter(); + + int top = lua_gettop(lstate); + + nlua_pushref(lstate, table_ref); + if (!lua_getmetatable(lstate, -1)) { + return NULL; + } + + lua_getfield(lstate, -1, "__call"); + if (!lua_isfunction(lstate, -1)) { + return NULL; + } + + LuaRef new_table_ref = nlua_newref(lstate, table_ref); + + LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); + state->lua_callable.func_ref = nlua_ref(lstate, -1); + state->lua_callable.table_ref = new_table_ref; + + char_u *name = register_cfunc( + &nlua_CFunction_func_call, + &nlua_CFunction_func_free, + state); + + + lua_pop(lstate, 3); + assert(top == lua_gettop(lstate)); + + return name; +} + +/// Helper function to free a list_T +void nlua_free_typval_list(list_T *const l) +{ + if (l->lua_table_ref != LUA_NOREF && l->lua_table_ref > 0) { + lua_State *const lstate = nlua_enter(); + nlua_unref(lstate, l->lua_table_ref); + l->lua_table_ref = LUA_NOREF; + } +} + + +/// Helper function to free a dict_T +void nlua_free_typval_dict(dict_T *const d) +{ + if (d->lua_table_ref != LUA_NOREF && d->lua_table_ref > 0) { + lua_State *const lstate = nlua_enter(); + nlua_unref(lstate, d->lua_table_ref); + d->lua_table_ref = LUA_NOREF; + } +} diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index 3259fc0fa1..6599b44584 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -8,6 +8,7 @@ #include "nvim/func_attr.h" #include "nvim/eval/typval.h" #include "nvim/ex_cmds_defs.h" +#include "nvim/lua/converter.h" // Generated by msgpack-gen.lua void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL; diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 51d9549033..138031237e 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -20,6 +20,7 @@ #include "nvim/lua/treesitter.h" #include "nvim/api/private/handle.h" #include "nvim/memline.h" +#include "nvim/buffer.h" typedef struct { TSParser *parser; @@ -41,6 +42,7 @@ static struct luaL_Reg parser_meta[] = { { "parse_buf", parser_parse_buf }, { "edit", parser_edit }, { "tree", parser_tree }, + { "set_included_ranges", parser_set_ranges }, { NULL, NULL } }; @@ -214,8 +216,13 @@ int tslua_inspect_lang(lua_State *L) return 1; } -int tslua_push_parser(lua_State *L, const char *lang_name) +int tslua_push_parser(lua_State *L) { + // Gather language + if (lua_gettop(L) < 1 || !lua_isstring(L, 1)) { + return luaL_error(L, "string expected"); + } + const char *lang_name = lua_tostring(L, 1); TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name); if (!lang) { return luaL_error(L, "no such language: %s", lang_name); @@ -300,11 +307,13 @@ static int parser_parse_buf(lua_State *L) } long bufnr = lua_tointeger(L, 2); - void *payload = handle_get_buffer(bufnr); - if (!payload) { + buf_T *buf = handle_get_buffer(bufnr); + + if (!buf) { return luaL_error(L, "invalid buffer handle: %d", bufnr); } - TSInput input = { payload, input_cb, TSInputEncodingUTF8 }; + + TSInput input = { (void *)buf, input_cb, TSInputEncodingUTF8 }; TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input); uint32_t n_ranges = 0; @@ -377,6 +386,57 @@ static int parser_edit(lua_State *L) return 0; } +static int parser_set_ranges(lua_State *L) +{ + if (lua_gettop(L) < 2) { + return luaL_error( + L, + "not enough args to parser:set_included_ranges()"); + } + + TSLua_parser *p = parser_check(L); + if (!p || !p->tree) { + return 0; + } + + if (!lua_istable(L, 2)) { + return luaL_error( + L, + "argument for parser:set_included_ranges() should be a table."); + } + + size_t tbl_len = lua_objlen(L, 2); + TSRange *ranges = xmalloc(sizeof(TSRange) * tbl_len); + + + // [ parser, ranges ] + for (size_t index = 0; index < tbl_len; index++) { + lua_rawgeti(L, 2, index + 1); // [ parser, ranges, range ] + + TSNode node; + if (!node_check(L, -1, &node)) { + xfree(ranges); + return luaL_error( + L, + "ranges should be tables of nodes."); + } + lua_pop(L, 1); // [ parser, ranges ] + + ranges[index] = (TSRange) { + .start_point = ts_node_start_point(node), + .end_point = ts_node_end_point(node), + .start_byte = ts_node_start_byte(node), + .end_byte = ts_node_end_byte(node) + }; + } + + // This memcpies ranges, thus we can free it afterwards + ts_parser_set_included_ranges(p->parser, ranges, tbl_len); + xfree(ranges); + + return 0; +} + // Tree methods @@ -459,9 +519,9 @@ static void push_node(lua_State *L, TSNode node, int uindex) lua_setfenv(L, -2); // [udata] } -static bool node_check(lua_State *L, TSNode *res) +static bool node_check(lua_State *L, int index, TSNode *res) { - TSNode *ud = luaL_checkudata(L, 1, "treesitter_node"); + TSNode *ud = luaL_checkudata(L, index, "treesitter_node"); if (ud) { *res = *ud; return true; @@ -473,7 +533,7 @@ static bool node_check(lua_State *L, TSNode *res) static int node_tostring(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } lua_pushstring(L, "<node "); @@ -486,7 +546,7 @@ static int node_tostring(lua_State *L) static int node_eq(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } // This should only be called if both x and y in "x == y" has the @@ -503,7 +563,7 @@ static int node_eq(lua_State *L) static int node_range(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } TSPoint start = ts_node_start_point(node); @@ -518,7 +578,7 @@ static int node_range(lua_State *L) static int node_start(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } TSPoint start = ts_node_start_point(node); @@ -532,7 +592,7 @@ static int node_start(lua_State *L) static int node_end(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } TSPoint end = ts_node_end_point(node); @@ -546,7 +606,7 @@ static int node_end(lua_State *L) static int node_child_count(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } uint32_t count = ts_node_child_count(node); @@ -557,7 +617,7 @@ static int node_child_count(lua_State *L) static int node_named_child_count(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } uint32_t count = ts_node_named_child_count(node); @@ -568,7 +628,7 @@ static int node_named_child_count(lua_State *L) static int node_type(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } lua_pushstring(L, ts_node_type(node)); @@ -578,7 +638,7 @@ static int node_type(lua_State *L) static int node_symbol(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } TSSymbol symbol = ts_node_symbol(node); @@ -589,7 +649,7 @@ static int node_symbol(lua_State *L) static int node_named(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } lua_pushboolean(L, ts_node_is_named(node)); @@ -599,7 +659,7 @@ static int node_named(lua_State *L) static int node_sexpr(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } char *allocated = ts_node_string(node); @@ -611,7 +671,7 @@ static int node_sexpr(lua_State *L) static int node_missing(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } lua_pushboolean(L, ts_node_is_missing(node)); @@ -621,7 +681,7 @@ static int node_missing(lua_State *L) static int node_has_error(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } lua_pushboolean(L, ts_node_has_error(node)); @@ -631,7 +691,7 @@ static int node_has_error(lua_State *L) static int node_child(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } long num = lua_tointeger(L, 2); @@ -644,7 +704,7 @@ static int node_child(lua_State *L) static int node_named_child(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } long num = lua_tointeger(L, 2); @@ -657,7 +717,7 @@ static int node_named_child(lua_State *L) static int node_descendant_for_range(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } TSPoint start = { (uint32_t)lua_tointeger(L, 2), @@ -673,7 +733,7 @@ static int node_descendant_for_range(lua_State *L) static int node_named_descendant_for_range(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } TSPoint start = { (uint32_t)lua_tointeger(L, 2), @@ -689,7 +749,7 @@ static int node_named_descendant_for_range(lua_State *L) static int node_parent(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } TSNode parent = ts_node_parent(node); @@ -771,7 +831,7 @@ static int query_next_capture(lua_State *L) static int node_rawquery(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } TSQuery *query = query_check(L, 2); diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 523d23ec12..820b237c4f 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -255,6 +255,8 @@ function vim.schedule_wrap(cb) end) end +--- <Docs described in |vim.empty_dict()| > +--@private function vim.empty_dict() return setmetatable({}, vim._empty_dict_mt) end @@ -270,6 +272,10 @@ vim.fn = setmetatable({}, { end }) +vim.funcref = function(viml_func_name) + return vim.fn[viml_func_name] +end + -- These are for loading runtime modules lazily since they aren't available in -- the nvim binary as specified in executor.c local function __index(t, key) @@ -286,6 +292,9 @@ local function __index(t, key) elseif key == 'lsp' then t.lsp = require('vim.lsp') return t.lsp + elseif key == 'highlight' then + t.highlight = require('vim.highlight') + return t.highlight end end @@ -462,6 +471,8 @@ end --- Defers calling `fn` until `timeout` ms passes. --- --- Use to do a one-shot timer that calls `fn` +--- Note: The {fn} is |schedule_wrap|ped automatically, so API functions are +--- safe to call. --@param fn Callback to call once `timeout` expires --@param timeout Number of milliseconds to wait before calling `fn` --@return timer luv timer object diff --git a/src/nvim/main.c b/src/nvim/main.c index b3a903cbe3..f79fb57eae 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -84,44 +84,11 @@ #endif #include "nvim/api/vim.h" -// Maximum number of commands from + or -c arguments. -#define MAX_ARG_CMDS 10 - // values for "window_layout" #define WIN_HOR 1 // "-o" horizontally split windows #define WIN_VER 2 // "-O" vertically split windows #define WIN_TABS 3 // "-p" windows on tab pages -// Struct for various parameters passed between main() and other functions. -typedef struct { - int argc; - char **argv; - - char *use_vimrc; // vimrc from -u argument - - int n_commands; // no. of commands from + or -c - char *commands[MAX_ARG_CMDS]; // commands from + or -c arg - char_u cmds_tofree[MAX_ARG_CMDS]; // commands that need free() - int n_pre_commands; // no. of commands from --cmd - char *pre_commands[MAX_ARG_CMDS]; // commands from --cmd argument - - int edit_type; // type of editing to do - char_u *tagname; // tag from -t argument - char_u *use_ef; // 'errorfile' from -q argument - - bool input_isatty; // stdin is a terminal - bool output_isatty; // stdout is a terminal - bool err_isatty; // stderr is a terminal - int no_swap_file; // "-n" argument used - int use_debug_break_level; - int window_count; // number of windows to use - int window_layout; // 0, WIN_HOR, WIN_VER or WIN_TABS - - int diff_mode; // start with 'diff' set - - char *listen_addr; // --listen {address} -} mparm_T; - // Values for edit_type. #define EDIT_NONE 0 // no edit type yet #define EDIT_FILE 1 // file name argument[s] given, use argument list @@ -188,7 +155,7 @@ bool event_teardown(void) /// Performs early initialization. /// /// Needed for unit tests. Must be called after `time_init()`. -void early_init(void) +void early_init(mparm_T *paramp) { env_init(); fs_init(); @@ -222,7 +189,7 @@ void early_init(void) // msg_outtrans_len_attr(). // First find out the home directory, needed to expand "~" in options. init_homedir(); // find real value of $HOME - set_init_1(); + set_init_1(paramp != NULL ? paramp->clean : false); log_init(); TIME_MSG("inits 1"); @@ -265,9 +232,17 @@ int main(int argc, char **argv) init_startuptime(¶ms); + // Need to find "--clean" before actually parsing arguments. + for (int i = 1; i < params.argc; i++) { + if (STRICMP(params.argv[i], "--clean") == 0) { + params.clean = true; + break; + } + } + event_init(); - early_init(); + early_init(¶ms); set_argv_var(argv, argc); // set v:argv @@ -291,6 +266,12 @@ int main(int argc, char **argv) fname = get_fname(¶ms, cwd); } + // Recovery mode without a file name: List swap files. + // In this case, no UI is needed. + if (recoverymode && fname == NULL) { + headless_mode = true; + } + TIME_MSG("expanding arguments"); if (params.diff_mode && params.window_count == -1) @@ -332,6 +313,26 @@ int main(int argc, char **argv) input_start(STDIN_FILENO); } + // Wait for UIs to set up Nvim or show early messages + // and prompts (--cmd, swapfile dialog, …). + bool use_remote_ui = (embedded_mode && !headless_mode); + bool use_builtin_ui = (!headless_mode && !embedded_mode && !silent_mode); + if (use_remote_ui || use_builtin_ui) { + TIME_MSG("waiting for UI"); + if (use_remote_ui) { + remote_ui_wait_for_attach(); + } else { + ui_builtin_start(); + } + TIME_MSG("done waiting for UI"); + + // prepare screen now, so external UIs can display messages + starting = NO_BUFFERS; + screenclear(); + TIME_MSG("initialized screen early for UI"); + } + + // open terminals when opening files that start with term:// #define PROTO "term://" do_cmdline_cmd("augroup nvim_terminal"); @@ -354,25 +355,6 @@ int main(int argc, char **argv) p_lpl = false; } - // Wait for UIs to set up Nvim or show early messages - // and prompts (--cmd, swapfile dialog, …). - bool use_remote_ui = (embedded_mode && !headless_mode); - bool use_builtin_ui = (!headless_mode && !embedded_mode && !silent_mode); - if (use_remote_ui || use_builtin_ui) { - TIME_MSG("waiting for UI"); - if (use_remote_ui) { - remote_ui_wait_for_attach(); - } else { - ui_builtin_start(); - } - TIME_MSG("done waiting for UI"); - - // prepare screen now, so external UIs can display messages - starting = NO_BUFFERS; - screenclear(); - TIME_MSG("initialized screen early for UI"); - } - // Execute --cmd arguments. exe_pre_commands(¶ms); @@ -862,6 +844,7 @@ static void command_line_scan(mparm_T *parmp) argv_idx += 11; } else if (STRNICMP(argv[0] + argv_idx, "clean", 5) == 0) { parmp->use_vimrc = "NONE"; + parmp->clean = true; set_option_value("shadafile", 0L, "NONE", 0); } else { if (argv[0][argv_idx]) @@ -978,7 +961,6 @@ static void command_line_scan(mparm_T *parmp) case 'r': // "-r" recovery mode case 'L': { // "-L" recovery mode recoverymode = 1; - headless_mode = true; break; } case 's': { @@ -1277,9 +1259,8 @@ static void init_params(mparm_T *paramp, int argc, char **argv) /// Initialize global startuptime file if "--startuptime" passed as an argument. static void init_startuptime(mparm_T *paramp) { - for (int i = 1; i < paramp->argc; i++) { - if (STRICMP(paramp->argv[i], "--startuptime") == 0 - && i + 1 < paramp->argc) { + for (int i = 1; i < paramp->argc - 1; i++) { + if (STRICMP(paramp->argv[i], "--startuptime") == 0) { time_fd = os_fopen(paramp->argv[i + 1], "a"); time_start("--- NVIM STARTING ---"); break; diff --git a/src/nvim/main.h b/src/nvim/main.h index 86d25fe657..61252f2bce 100644 --- a/src/nvim/main.h +++ b/src/nvim/main.h @@ -4,8 +4,42 @@ #include "nvim/normal.h" #include "nvim/event/loop.h" +// Maximum number of commands from + or -c arguments. +#define MAX_ARG_CMDS 10 + extern Loop main_loop; +// Struct for various parameters passed between main() and other functions. +typedef struct { + int argc; + char **argv; + + char *use_vimrc; // vimrc from -u argument + bool clean; // --clean argument + + int n_commands; // no. of commands from + or -c + char *commands[MAX_ARG_CMDS]; // commands from + or -c arg + char_u cmds_tofree[MAX_ARG_CMDS]; // commands that need free() + int n_pre_commands; // no. of commands from --cmd + char *pre_commands[MAX_ARG_CMDS]; // commands from --cmd argument + + int edit_type; // type of editing to do + char_u *tagname; // tag from -t argument + char_u *use_ef; // 'errorfile' from -q argument + + bool input_isatty; // stdin is a terminal + bool output_isatty; // stdout is a terminal + bool err_isatty; // stderr is a terminal + int no_swap_file; // "-n" argument used + int use_debug_break_level; + int window_count; // number of windows to use + int window_layout; // 0, WIN_HOR, WIN_VER or WIN_TABS + + int diff_mode; // start with 'diff' set + + char *listen_addr; // --listen {address} +} mparm_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "main.h.generated.h" #endif diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index e10770b6bd..6dafbafb3e 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -1029,6 +1029,15 @@ void fast_breakcheck(void) } } +// Like line_breakcheck() but check 100 times less often. +void veryfast_breakcheck(void) +{ + if (++breakcheck_count >= BREAKCHECK_SKIP * 100) { + breakcheck_count = 0; + os_breakcheck(); + } +} + /// os_call_shell() wrapper. Handles 'verbose', :profile, and v:shell_error. /// Invalidates cached tags. /// diff --git a/src/nvim/ops.c b/src/nvim/ops.c index eb32a1dd9b..595a699563 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -3080,10 +3080,12 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (ve_flags == VE_ALL && (curwin->w_cursor.coladd > 0 || endcol2 == curwin->w_cursor.col)) { - if (dir == FORWARD && c == NUL) - ++col; - if (dir != FORWARD && c != NUL) - ++curwin->w_cursor.col; + if (dir == FORWARD && c == NUL) { + col++; + } + if (dir != FORWARD && c != NUL && curwin->w_cursor.coladd > 0) { + curwin->w_cursor.col++; + } if (c == TAB) { if (dir == BACKWARD && curwin->w_cursor.col) curwin->w_cursor.col--; @@ -4671,17 +4673,23 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) int maxlen = 0; pos_T startpos; pos_T endpos; + colnr_T save_coladd = 0; dohex = (vim_strchr(curbuf->b_p_nf, 'x') != NULL); // "heX" dooct = (vim_strchr(curbuf->b_p_nf, 'o') != NULL); // "Octal" dobin = (vim_strchr(curbuf->b_p_nf, 'b') != NULL); // "Bin" doalp = (vim_strchr(curbuf->b_p_nf, 'p') != NULL); // "alPha" + if (virtual_active()) { + save_coladd = pos->coladd; + pos->coladd = 0; + } + curwin->w_cursor = *pos; ptr = ml_get(pos->lnum); col = pos->col; - if (*ptr == NUL) { + if (*ptr == NUL || col + !!save_coladd >= (int)STRLEN(ptr)) { goto theend; } @@ -4976,6 +4984,8 @@ theend: curwin->w_cursor = save_cursor; } else if (did_change) { curwin->w_set_curswant = true; + } else if (virtual_active()) { + curwin->w_cursor.coladd = save_coladd; } return did_change; diff --git a/src/nvim/option.c b/src/nvim/option.c index 96f8e1529a..d789ad3587 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -524,11 +524,17 @@ char *get_lib_dir(void) /// /// Windows: Uses "…/nvim-data" for kXDGDataHome to avoid storing /// configuration and data files in the same path. #4403 -static void set_runtimepath_default(void) +/// +/// If "clean_arg" is true, Nvim was started with --clean. +static void set_runtimepath_default(bool clean_arg) { size_t rtp_size = 0; - char *const data_home = stdpaths_get_xdg_var(kXDGDataHome); - char *const config_home = stdpaths_get_xdg_var(kXDGConfigHome); + char *const data_home = clean_arg + ? NULL + : stdpaths_get_xdg_var(kXDGDataHome); + char *const config_home = clean_arg + ? NULL + : stdpaths_get_xdg_var(kXDGConfigHome); char *const vimruntime = vim_getenv("VIMRUNTIME"); char *const libdir = get_lib_dir(); char *const data_dirs = stdpaths_get_xdg_var(kXDGDataDirs); @@ -622,7 +628,8 @@ static void set_runtimepath_default(void) /// Initialize the options, first part. /// /// Called only once from main(), just after creating the first buffer. -void set_init_1(void) +/// If "clean_arg" is true, Nvim was started with --clean. +void set_init_1(bool clean_arg) { int opt_idx; @@ -765,7 +772,7 @@ void set_init_1(void) true); // Set default for &runtimepath. All necessary expansions are performed in // this function. - set_runtimepath_default(); + set_runtimepath_default(clean_arg); /* * Set all the options (except the terminal options) to their default @@ -2538,8 +2545,8 @@ static bool valid_filetype(const char_u *val) return valid_name(val, ".-_"); } -/// Return true if "val" is a valid 'spellang' value. -bool valid_spellang(const char_u *val) +/// Return true if "val" is a valid 'spelllang' value. +bool valid_spelllang(const char_u *val) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return valid_name(val, ".-_,@"); @@ -3071,7 +3078,7 @@ ambw_end: const bool is_spellfile = varp == &(curwin->w_s->b_p_spf); if ((is_spellfile && !valid_spellfile(*varp)) - || (!is_spellfile && !valid_spellang(*varp))) { + || (!is_spellfile && !valid_spelllang(*varp))) { errmsg = e_invarg; } else { errmsg = did_set_spell_option(is_spellfile); @@ -5469,9 +5476,6 @@ static int put_setstring(FILE *fd, char *cmd, char *name, // replace home directory in the whole option value into "buf" buf = xmalloc(size); - if (buf == NULL) { - goto fail; - } home_replace(NULL, *valuep, buf, size, false); // If the option value is longer than MAXPATHL, we need to append @@ -5479,10 +5483,7 @@ static int put_setstring(FILE *fd, char *cmd, char *name, // can be expanded when read back. if (size >= MAXPATHL && (flags & P_COMMA) != 0 && vim_strchr(*valuep, ',') != NULL) { - part = xmalloc(size); - if (part == NULL) { - goto fail; - } + part = xmalloc(size); // write line break to clear the option, e.g. ':set rtp=' if (put_eol(fd) == FAIL) { diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 2ca5f42e51..387732fdee 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -4960,7 +4960,7 @@ static long find_match_text(colnr_T startcol, int regstart, char_u *match_text) int c2_len = PTR2LEN(s2); int c2 = PTR2CHAR(s2); - if ((c1 != c2 && (!rex.reg_ic || mb_tolower(c1) != mb_tolower(c2))) + if ((c1 != c2 && (!rex.reg_ic || utf_fold(c1) != utf_fold(c2))) || c1_len != c2_len) { match = false; break; @@ -5682,11 +5682,11 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, break; } if (rex.reg_ic) { - int curc_low = mb_tolower(curc); + int curc_low = utf_fold(curc); int done = false; for (; c1 <= c2; c1++) { - if (mb_tolower(c1) == curc_low) { + if (utf_fold(c1) == curc_low) { result = result_if_matched; done = TRUE; break; @@ -5698,8 +5698,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, } } else if (state->c < 0 ? check_char_class(state->c, curc) : (curc == state->c - || (rex.reg_ic && mb_tolower(curc) - == mb_tolower(state->c)))) { + || (rex.reg_ic + && utf_fold(curc) == utf_fold(state->c)))) { result = result_if_matched; break; } @@ -6106,7 +6106,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, result = (c == curc); if (!result && rex.reg_ic) { - result = mb_tolower(c) == mb_tolower(curc); + result = utf_fold(c) == utf_fold(curc); } // If rex.reg_icombine is not set only skip over the character @@ -6260,8 +6260,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, // Checking if the required start character matches is // cheaper than adding a state that won't match. c = PTR2CHAR(reginput + clen); - if (c != prog->regstart && (!rex.reg_ic || mb_tolower(c) - != mb_tolower(prog->regstart))) { + if (c != prog->regstart + && (!rex.reg_ic + || utf_fold(c) != utf_fold(prog->regstart))) { #ifdef REGEXP_DEBUG fprintf(log_fd, " Skipping start state, regstart does not match\n"); diff --git a/src/nvim/search.c b/src/nvim/search.c index 23086c629b..b105d99d7c 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -2326,9 +2326,9 @@ int findsent(Direction dir, long count) func = decl; while (count--) { - /* - * if on an empty line, skip up to a non-empty line - */ + const pos_T prev_pos = pos; + + // if on an empty line, skip up to a non-empty line if (gchar_pos(&pos) == NUL) { do { if ((*func)(&pos) == -1) { @@ -2411,6 +2411,17 @@ found: while (!noskip && ((c = gchar_pos(&pos)) == ' ' || c == '\t')) if (incl(&pos) == -1) break; + + if (equalpos(prev_pos, pos)) { + // didn't actually move, advance one character and try again + if ((*func)(&pos) == -1) { + if (count) { + return FAIL; + } + break; + } + count++; + } } setpcmark(); diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 19a14f340b..95257fe945 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -2676,6 +2676,36 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, if (name == NULL) { break; } + switch (vartv.v_type) { + case VAR_FUNC: + case VAR_PARTIAL: + tv_clear(&vartv); + continue; + case VAR_DICT: + { + dict_T *di = vartv.vval.v_dict; + int copyID = get_copyID(); + if (!set_ref_in_ht(&di->dv_hashtab, copyID, NULL) + && copyID == di->dv_copyID) { + tv_clear(&vartv); + continue; + } + break; + } + case VAR_LIST: + { + list_T *l = vartv.vval.v_list; + int copyID = get_copyID(); + if (!set_ref_in_list(l, copyID, NULL) + && copyID == l->lv_copyID) { + tv_clear(&vartv); + continue; + } + break; + } + default: + break; + } typval_T tgttv; tv_copy(&vartv, &tgttv); ShaDaWriteResult spe_ret; diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 180073ade1..95948dac78 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -2007,7 +2007,7 @@ char_u *did_set_spelllang(win_T *wp) region = NULL; len = (int)STRLEN(lang); - if (!valid_spellang(lang)) { + if (!valid_spelllang(lang)) { continue; } @@ -2930,8 +2930,6 @@ void spell_suggest(int count) memmove(p, line, c); STRCPY(p + c, stp->st_word); STRCAT(p, sug.su_badptr + stp->st_orglen); - ml_replace(curwin->w_cursor.lnum, p, false); - curwin->w_cursor.col = c; // For redo we use a change-word command. ResetRedobuff(); @@ -2940,7 +2938,10 @@ void spell_suggest(int count) stp->st_wordlen + sug.su_badlen - stp->st_orglen); AppendCharToRedobuff(ESC); - // After this "p" may be invalid. + // "p" may be freed here + ml_replace(curwin->w_cursor.lnum, p, false); + curwin->w_cursor.col = c; + changed_bytes(curwin->w_cursor.lnum, c); } else curwin->w_cursor = prev_cursor; @@ -3761,7 +3762,8 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so tword[sp->ts_twordlen] = NUL; if (sp->ts_prefixdepth <= PFD_NOTSPECIAL - && (sp->ts_flags & TSF_PREFIXOK) == 0) { + && (sp->ts_flags & TSF_PREFIXOK) == 0 + && pbyts != NULL) { // There was a prefix before the word. Check that the prefix // can be used with this word. // Count the length of the NULs in the prefix. If there are diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index f8c10d0258..6b9348e55d 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -1134,7 +1134,6 @@ static int read_sal_section(FILE *fd, slang_T *slang) salitem_T *smp; int ccnt; char_u *p; - int c = NUL; slang->sl_sofo = false; @@ -1158,7 +1157,9 @@ static int read_sal_section(FILE *fd, slang_T *slang) ga_grow(gap, cnt + 1); // <sal> : <salfromlen> <salfrom> <saltolen> <salto> - for (; gap->ga_len < cnt; ++gap->ga_len) { + for (; gap->ga_len < cnt; gap->ga_len++) { + int c = NUL; + smp = &((salitem_T *)gap->ga_data)[gap->ga_len]; ccnt = getc(fd); // <salfromlen> if (ccnt < 0) @@ -1810,7 +1811,8 @@ spell_reload_one ( #define CONDIT_SUF 4 // add a suffix for matching flags #define CONDIT_AFF 8 // word already has an affix -// Tunable parameters for when the tree is compressed. See 'mkspellmem'. +// Tunable parameters for when the tree is compressed. Filled from the +// 'mkspellmem' option. static long compress_start = 30000; // memory / SBLOCKSIZE static long compress_inc = 100; // memory / SBLOCKSIZE static long compress_added = 500000; // word count @@ -3015,6 +3017,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) char_u message[MAXLINELEN + MAXWLEN]; int flags; int duplicate = 0; + Timestamp last_msg_time = 0; // Open the file. fd = os_fopen((char *)fname, "r"); @@ -3090,18 +3093,22 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) continue; } - // This takes time, print a message every 10000 words. + // This takes time, print a message every 10000 words, but not more + // often than once per second. if (spin->si_verbose && spin->si_msg_count > 10000) { spin->si_msg_count = 0; - vim_snprintf((char *)message, sizeof(message), - _("line %6d, word %6ld - %s"), - lnum, spin->si_foldwcount + spin->si_keepwcount, w); - msg_start(); - msg_puts_long_attr(message, 0); - msg_clr_eos(); - msg_didout = FALSE; - msg_col = 0; - ui_flush(); + if (os_time() > last_msg_time) { + last_msg_time = os_time(); + vim_snprintf((char *)message, sizeof(message), + _("line %6d, word %6ld - %s"), + lnum, spin->si_foldwcount + spin->si_keepwcount, w); + msg_start(); + msg_puts_long_attr(message, 0); + msg_clr_eos(); + msg_didout = false; + msg_col = 0; + ui_flush(); + } } // Store the word in the hashtable to be able to find duplicates. @@ -3914,9 +3921,10 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int ++spin->si_msg_count; if (spin->si_compress_cnt > 1) { - if (--spin->si_compress_cnt == 1) + if (--spin->si_compress_cnt == 1) { // Did enough words to lower the block count limit. spin->si_blocks_cnt += compress_inc; + } } // When we have allocated lots of memory we need to compress the word tree @@ -3955,9 +3963,10 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int // compression useful, or one of them is small, which means // compression goes fast. But when filling the soundfold word tree // there is no keep-case tree. - wordtree_compress(spin, spin->si_foldroot); - if (affixID >= 0) - wordtree_compress(spin, spin->si_keeproot); + wordtree_compress(spin, spin->si_foldroot, "case-folded"); + if (affixID >= 0) { + wordtree_compress(spin, spin->si_keeproot, "keep-case"); + } } return OK; @@ -3990,6 +3999,7 @@ static wordnode_T *get_wordnode(spellinfo_T *spin) // siblings. // Returns the number of nodes actually freed. static int deref_wordnode(spellinfo_T *spin, wordnode_T *node) + FUNC_ATTR_NONNULL_ALL { wordnode_T *np; int cnt = 0; @@ -4009,6 +4019,7 @@ static int deref_wordnode(spellinfo_T *spin, wordnode_T *node) // Free a wordnode_T for re-use later. // Only the "wn_child" field becomes invalid. static void free_wordnode(spellinfo_T *spin, wordnode_T *n) + FUNC_ATTR_NONNULL_ALL { n->wn_child = spin->si_first_free; spin->si_first_free = n; @@ -4016,18 +4027,19 @@ static void free_wordnode(spellinfo_T *spin, wordnode_T *n) } // Compress a tree: find tails that are identical and can be shared. -static void wordtree_compress(spellinfo_T *spin, wordnode_T *root) +static void wordtree_compress(spellinfo_T *spin, wordnode_T *root, + const char *name) + FUNC_ATTR_NONNULL_ALL { hashtab_T ht; - int n; - int tot = 0; - int perc; + long tot = 0; + long perc; // Skip the root itself, it's not actually used. The first sibling is the // start of the tree. if (root->wn_sibling != NULL) { hash_init(&ht); - n = node_compress(spin, root->wn_sibling, &ht, &tot); + const long n = node_compress(spin, root->wn_sibling, &ht, &tot); #ifndef SPELL_PRINTTREE if (spin->si_verbose || p_verbose > 2) @@ -4040,8 +4052,8 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root) else perc = (tot - n) * 100 / tot; vim_snprintf((char *)IObuff, IOSIZE, - _("Compressed %d of %d nodes; %d (%d%%) remaining"), - n, tot, tot - n, perc); + _("Compressed %s of %ld nodes; %ld (%ld%%) remaining"), + name, tot, tot - n, perc); spell_message(spin, IObuff); } #ifdef SPELL_PRINTTREE @@ -4053,23 +4065,23 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root) // Compress a node, its siblings and its children, depth first. // Returns the number of compressed nodes. -static int -node_compress ( +static long node_compress( spellinfo_T *spin, wordnode_T *node, hashtab_T *ht, - int *tot // total count of nodes before compressing, + long *tot // total count of nodes before compressing, // incremented while going through the tree ) + FUNC_ATTR_NONNULL_ALL { wordnode_T *np; wordnode_T *tp; wordnode_T *child; hash_T hash; hashitem_T *hi; - int len = 0; + long len = 0; unsigned nr, n; - int compressed = 0; + long compressed = 0; // Go through the list of siblings. Compress each child and then try // finding an identical child to replace it. @@ -4142,7 +4154,7 @@ node_compress ( node->wn_u1.hashkey[5] = NUL; // Check for CTRL-C pressed now and then. - fast_breakcheck(); + veryfast_breakcheck(); return compressed; } @@ -4749,7 +4761,7 @@ static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname) // Compress the soundfold trie. spell_message(spin, (char_u *)_(msg_compressing)); - wordtree_compress(spin, spin->si_foldroot); + wordtree_compress(spin, spin->si_foldroot, "case-folded"); // Write the .sug file. // Make the file name by changing ".spl" to ".sug". @@ -5095,7 +5107,8 @@ mkspell ( spin.si_newcompID = 127; // start compound ID at first maximum // default: fnames[0] is output file, following are input files - innames = &fnames[1]; + // When "fcount" is 1 there is only one file. + innames = &fnames[fcount == 1 ? 0 : 1]; incount = fcount - 1; wfname = xmalloc(MAXPATHL); @@ -5105,12 +5118,10 @@ mkspell ( if (fcount == 1 && len > 4 && STRCMP(fnames[0] + len - 4, ".add") == 0) { // For ":mkspell path/en.latin1.add" output file is // "path/en.latin1.add.spl". - innames = &fnames[0]; incount = 1; vim_snprintf((char *)wfname, MAXPATHL, "%s.spl", fnames[0]); } else if (fcount == 1) { // For ":mkspell path/vim" output file is "path/vim.latin1.spl". - innames = &fnames[0]; incount = 1; vim_snprintf((char *)wfname, MAXPATHL, SPL_FNAME_TMPL, fnames[0], spin.si_ascii ? (char_u *)"ascii" : spell_enc()); @@ -5220,9 +5231,9 @@ mkspell ( if (!error && !got_int) { // Combine tails in the tree. spell_message(&spin, (char_u *)_(msg_compressing)); - wordtree_compress(&spin, spin.si_foldroot); - wordtree_compress(&spin, spin.si_keeproot); - wordtree_compress(&spin, spin.si_prefroot); + wordtree_compress(&spin, spin.si_foldroot, "case-folded"); + wordtree_compress(&spin, spin.si_keeproot, "keep-case"); + wordtree_compress(&spin, spin.si_prefroot, "prefixes"); } if (!error && !got_int) { @@ -5274,7 +5285,8 @@ theend: // Display a message for spell file processing when 'verbose' is set or using // ":mkspell". "str" can be IObuff. -static void spell_message(spellinfo_T *spin, char_u *str) +static void spell_message(const spellinfo_T *spin, char_u *str) + FUNC_ATTR_NONNULL_ALL { if (spin->si_verbose || p_verbose > 2) { if (!spin->si_verbose) diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index f3b05c3961..4aa7c21ce4 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -121,6 +121,8 @@ static int hl_attr_table[] = { HL_BOLD, HL_STANDOUT, HL_UNDERLINE, HL_UNDERCURL, HL_ITALIC, HL_INVERSE, HL_INVERSE, HL_STRIKETHROUGH, HL_NOCOMBINE, 0 }; +static char e_illegal_arg[] = N_("E390: Illegal argument: %s"); + // The patterns that are being searched for are stored in a syn_pattern. // A match item consists of one pattern. // A start/end item consists of n start patterns and m end patterns. @@ -3045,7 +3047,7 @@ static void syn_cmd_conceal(exarg_T *eap, int syncing) } else if (STRNICMP(arg, "off", 3) == 0 && next - arg == 3) { curwin->w_s->b_syn_conceal = false; } else { - EMSG2(_("E390: Illegal argument: %s"), arg); + EMSG2(_(e_illegal_arg), arg); } } @@ -3073,7 +3075,42 @@ static void syn_cmd_case(exarg_T *eap, int syncing) } else if (STRNICMP(arg, "ignore", 6) == 0 && next - arg == 6) { curwin->w_s->b_syn_ic = true; } else { - EMSG2(_("E390: Illegal argument: %s"), arg); + EMSG2(_(e_illegal_arg), arg); + } +} + +/// Handle ":syntax foldlevel" command. +static void syn_cmd_foldlevel(exarg_T *eap, int syncing) +{ + char_u *arg = eap->arg; + char_u *arg_end; + + eap->nextcmd = find_nextcmd(arg); + if (eap->skip) + return; + + if (*arg == NUL) { + switch (curwin->w_s->b_syn_foldlevel) { + case SYNFLD_START: MSG(_("syntax foldlevel start")); break; + case SYNFLD_MINIMUM: MSG(_("syntax foldlevel minimum")); break; + default: break; + } + return; + } + + arg_end = skiptowhite(arg); + if (STRNICMP(arg, "start", 5) == 0 && arg_end - arg == 5) { + curwin->w_s->b_syn_foldlevel = SYNFLD_START; + } else if (STRNICMP(arg, "minimum", 7) == 0 && arg_end - arg == 7) { + curwin->w_s->b_syn_foldlevel = SYNFLD_MINIMUM; + } else { + EMSG2(_(e_illegal_arg), arg); + return; + } + + arg = skipwhite(arg_end); + if (*arg != NUL) { + EMSG2(_(e_illegal_arg), arg); } } @@ -3105,7 +3142,7 @@ static void syn_cmd_spell(exarg_T *eap, int syncing) } else if (STRNICMP(arg, "default", 7) == 0 && next - arg == 7) { curwin->w_s->b_syn_spell = SYNSPL_DEFAULT; } else { - EMSG2(_("E390: Illegal argument: %s"), arg); + EMSG2(_(e_illegal_arg), arg); return; } @@ -3161,6 +3198,7 @@ void syntax_clear(synblock_T *block) block->b_syn_error = false; // clear previous error block->b_syn_slow = false; // clear previous timeout block->b_syn_ic = false; // Use case, by default + block->b_syn_foldlevel = SYNFLD_START; block->b_syn_spell = SYNSPL_DEFAULT; // default spell checking block->b_syn_containedin = false; block->b_syn_conceal = false; @@ -5485,6 +5523,7 @@ static struct subcommand subcommands[] = { "cluster", syn_cmd_cluster }, { "conceal", syn_cmd_conceal }, { "enable", syn_cmd_enable }, + { "foldlevel", syn_cmd_foldlevel }, { "include", syn_cmd_include }, { "iskeyword", syn_cmd_iskeyword }, { "keyword", syn_cmd_keyword }, @@ -5763,6 +5802,17 @@ int syn_get_stack_item(int i) return CUR_STATE(i).si_id; } +static int syn_cur_foldlevel(void) +{ + int level = 0; + for (int i = 0; i < current_state.ga_len; i++) { + if (CUR_STATE(i).si_flags & HL_FOLD) { + level++; + } + } + return level; +} + /* * Function called to get folding level for line "lnum" in window "wp". */ @@ -5776,9 +5826,22 @@ int syn_get_foldlevel(win_T *wp, long lnum) && !wp->w_s->b_syn_slow) { syntax_start(wp, lnum); - for (int i = 0; i < current_state.ga_len; ++i) { - if (CUR_STATE(i).si_flags & HL_FOLD) { - ++level; + // Start with the fold level at the start of the line. + level = syn_cur_foldlevel(); + + if (wp->w_s->b_syn_foldlevel == SYNFLD_MINIMUM) { + // Find the lowest fold level that is followed by a higher one. + int cur_level = level; + int low_level = cur_level; + while (!current_finished) { + (void)syn_current_attr(false, false, NULL, false); + cur_level = syn_cur_foldlevel(); + if (cur_level < low_level) { + low_level = cur_level; + } else if (cur_level > low_level) { + level = low_level; + } + current_col++; } } } diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index a096b77ac6..6a13341a89 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -489,7 +489,17 @@ static int terminal_execute(VimState *state, int key) terminal_send_key(s->term, key); } - return curbuf->handle == s->term->buf_handle; + if (curbuf->terminal == NULL) { + return 0; + } + if (s->term != curbuf->terminal) { + invalidate_terminal(s->term, s->term->cursor.row, s->term->cursor.row + 1); + invalidate_terminal(curbuf->terminal, + curbuf->terminal->cursor.row, + curbuf->terminal->cursor.row + 1); + s->term = curbuf->terminal; + } + return 1; } void terminal_destroy(Terminal *term) diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index b041fdedb1..41ff9b2bd6 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -275,7 +275,7 @@ func GetVimCommand(...) " If using valgrind, make sure every run uses a different log file. if cmd =~ 'valgrind.*--log-file=' - let cmd = substitute(cmd, '--log-file=\(^\s*\)', '--log-file=\1.' . g:valgrind_cnt, '') + let cmd = substitute(cmd, '--log-file=\(\S*\)', '--log-file=\1.' . g:valgrind_cnt, '') let g:valgrind_cnt += 1 endif diff --git a/src/nvim/testdir/summarize.vim b/src/nvim/testdir/summarize.vim index 4e4135287b..7f8f758a71 100644 --- a/src/nvim/testdir/summarize.vim +++ b/src/nvim/testdir/summarize.vim @@ -28,7 +28,7 @@ if 1 " This uses the :s command to just fetch and process the output of the " tests, it doesn't actually replace anything. " And it uses "silent" to avoid reporting the number of matches. - silent %s/^Executed\s\+\zs\d\+\ze\s\+tests\?/\=Count(submatch(0),'executed')/egn + silent %s/Executed\s\+\zs\d\+\ze\s\+tests\?/\=Count(submatch(0),'executed')/egn silent %s/^SKIPPED \zs.*/\=Count(submatch(0), 'skipped')/egn silent %s/^\(\d\+\)\s\+FAILED:/\=Count(submatch(1), 'failed')/egn diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim index 4cc90eca7a..b4f7478807 100644 --- a/src/nvim/testdir/test_assert.vim +++ b/src/nvim/testdir/test_assert.vim @@ -28,7 +28,18 @@ func Test_assert_equalfile() call writefile(['1234X89'], 'Xone') call writefile(['1234Y89'], 'Xtwo') call assert_equal(1, assert_equalfile('Xone', 'Xtwo')) - call assert_match("difference at byte 4", v:errors[0]) + call assert_match('difference at byte 4, line 1 after "1234X" vs "1234Y"', v:errors[0]) + call remove(v:errors, 0) + + call writefile([repeat('x', 234) .. 'X'], 'Xone') + call writefile([repeat('x', 234) .. 'Y'], 'Xtwo') + call assert_equal(1, assert_equalfile('Xone', 'Xtwo')) + let xes = repeat('x', 134) + call assert_match('difference at byte 234, line 1 after "' .. xes .. 'X" vs "' .. xes .. 'Y"', v:errors[0]) + call remove(v:errors, 0) + + call assert_equal(1, assert_equalfile('Xone', 'Xtwo', 'a message')) + call assert_match("a message: difference at byte 234, line 1 after", v:errors[0]) call remove(v:errors, 0) call delete('Xone') diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 5217aa7339..d116246ef3 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -1252,6 +1252,10 @@ func Test_TextYankPost() call assert_equal( \{'regcontents': ['foo'], 'inclusive': v:false, 'regname': '', 'operator': 'y', 'visual': v:false, 'regtype': 'V'}, \g:event) + norm Vy + call assert_equal( + \{'regcontents': ['foo'], 'inclusive': v:true, 'regname': '', 'operator': 'y', 'visual': v:true, 'regtype': 'V'}, + \g:event) call feedkeys("\<C-V>y", 'x') call assert_equal( \{'regcontents': ['f'], 'inclusive': v:true, 'regname': '', 'operator': 'y', 'visual': v:true, 'regtype': "\x161"}, diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 42e18ed027..49bbe84869 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -800,3 +800,25 @@ func Test_diff_closeoff() diffoff! enew! endfunc + +func Test_diff_and_scroll() + " this was causing an ml_get error + set ls=2 + for i in range(winheight(0) * 2) + call setline(i, i < winheight(0) - 10 ? i : i + 10) + endfor + vnew + for i in range(winheight(0)*2 + 10) + call setline(i, i < winheight(0) - 10 ? 0 : i) + endfor + diffthis + wincmd p + diffthis + execute 'normal ' . winheight(0) . "\<C-d>" + + bwipe! + bwipe! + set ls& +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index ffd2cee80f..d440bdcb1e 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -131,7 +131,7 @@ let s:filename_checks = { \ 'def': ['file.def'], \ 'denyhosts': ['denyhosts.conf'], \ 'desc': ['file.desc'], - \ 'desktop': ['file.desktop', '.directory'], + \ 'desktop': ['file.desktop', '.directory', 'file.directory'], \ 'dictconf': ['dict.conf', '.dictrc'], \ 'dictdconf': ['dictd.conf'], \ 'diff': ['file.diff', 'file.rej'], @@ -139,7 +139,7 @@ let s:filename_checks = { \ 'dnsmasq': ['/etc/dnsmasq.conf'], \ 'dockerfile': ['Dockerfile', 'file.Dockerfile'], \ 'dosbatch': ['file.bat', 'file.sys'], - \ 'dosini': ['.editorconfig', '/etc/yum.conf', 'file.ini'], + \ 'dosini': ['.editorconfig', '/etc/pacman.conf', '/etc/yum.conf', 'file.ini'], \ 'dot': ['file.dot', 'file.gv'], \ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe'], \ 'dsl': ['file.dsl'], @@ -150,7 +150,7 @@ let s:filename_checks = { \ 'dylanlid': ['file.lid'], \ 'ecd': ['file.ecd'], \ 'edif': ['file.edf', 'file.edif', 'file.edo'], - \ 'elinks': ['/etc/elinks.conf', '/.elinks/elinks.conf'], + \ 'elinks': ['elinks.conf'], \ 'elm': ['file.elm'], \ 'elmfilt': ['filter-rules'], \ 'erlang': ['file.erl', 'file.hrl', 'file.yaws'], @@ -329,7 +329,7 @@ let s:filename_checks = { \ 'pccts': ['file.g'], \ 'pdf': ['file.pdf'], \ 'perl': ['file.plx', 'file.al', 'file.psgi', 'gitolite.rc', '.gitolite.rc', 'example.gitolite.rc'], - \ 'perl6': ['file.p6', 'file.pm6', 'file.pl6'], + \ 'perl6': ['file.p6', 'file.pm6', 'file.pl6', 'file.raku', 'file.rakumod'], \ 'pf': ['pf.conf'], \ 'pfmain': ['main.cf'], \ 'php': ['file.php', 'file.php9', 'file.phtml', 'file.ctp'], @@ -360,7 +360,7 @@ let s:filename_checks = { \ 'protocols': ['/etc/protocols'], \ 'psf': ['file.psf'], \ 'pyrex': ['file.pyx', 'file.pxd'], - \ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi'], + \ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi', 'SConstruct'], \ 'quake': ['anybaseq2/file.cfg', 'anyid1/file.cfg', 'quake3/file.cfg'], \ 'radiance': ['file.rad', 'file.mat'], \ 'ratpoison': ['.ratpoisonrc', 'ratpoisonrc'], @@ -427,8 +427,8 @@ let s:filename_checks = { \ 'sqr': ['file.sqr', 'file.sqi'], \ 'squid': ['squid.conf'], \ 'srec': ['file.s19', 'file.s28', 'file.s37', 'file.mot', 'file.srec'], - \ 'sshconfig': ['ssh_config', '/.ssh/config'], - \ 'sshdconfig': ['sshd_config'], + \ 'sshconfig': ['ssh_config', '/.ssh/config', '/etc/ssh/ssh_config.d/file.conf', 'any/etc/ssh/ssh_config.d/file.conf'], + \ 'sshdconfig': ['sshd_config', '/etc/ssh/sshd_config.d/file.conf', 'any/etc/ssh/sshd_config.d/file.conf'], \ 'st': ['file.st'], \ 'stata': ['file.ado', 'file.do', 'file.imata', 'file.mata'], \ 'stp': ['file.stp'], @@ -439,7 +439,7 @@ let s:filename_checks = { \ 'swiftgyb': ['file.swift.gyb'], \ 'sil': ['file.sil'], \ 'sysctl': ['/etc/sysctl.conf', '/etc/sysctl.d/file.conf'], - \ 'systemd': ['any/systemd/file.automount', 'any/systemd/file.mount', 'any/systemd/file.path', 'any/systemd/file.service', 'any/systemd/file.socket', 'any/systemd/file.swap', 'any/systemd/file.target', 'any/systemd/file.timer', '/etc/systemd/system/some.d/file.conf', '/etc/systemd/system/some.d/.#file', '/etc/systemd/system/.#otherfile', '/home/user/.config/systemd/user/some.d/mine.conf', '/home/user/.config/systemd/user/some.d/.#file', '/home/user/.config/systemd/user/.#otherfile'], + \ 'systemd': ['any/systemd/file.automount', 'any/systemd/file.dnssd', 'any/systemd/file.link', 'any/systemd/file.mount', 'any/systemd/file.netdev', 'any/systemd/file.network', 'any/systemd/file.nspawn', 'any/systemd/file.path', 'any/systemd/file.service', 'any/systemd/file.slice', 'any/systemd/file.socket', 'any/systemd/file.swap', 'any/systemd/file.target', 'any/systemd/file.timer', '/etc/systemd/some.conf.d/file.conf', '/etc/systemd/system/some.d/file.conf', '/etc/systemd/system/some.d/.#file', '/etc/systemd/system/.#otherfile', '/home/user/.config/systemd/user/some.d/mine.conf', '/home/user/.config/systemd/user/some.d/.#file', '/home/user/.config/systemd/user/.#otherfile'], \ 'systemverilog': ['file.sv', 'file.svh'], \ 'tags': ['tags'], \ 'tak': ['file.tak'], @@ -606,9 +606,19 @@ let s:script_checks = { \ 'yaml': [['%YAML 1.2']], \ } -func Test_script_detection() +" Various forms of "env" optional arguments. +let s:script_env_checks = { + \ 'perl': [['#!/usr/bin/env VAR=val perl']], + \ 'scala': [['#!/usr/bin/env VAR=val VVAR=vval scala']], + \ 'awk': [['#!/usr/bin/env VAR=val -i awk']], + \ 'scheme': [['#!/usr/bin/env VAR=val --ignore-environment scheme']], + \ 'python': [['#!/usr/bin/env VAR=val -S python -w -T']], + \ 'wml': [['#!/usr/bin/env VAR=val --split-string wml']], + \ } + +func Run_script_detection(test_dict) filetype on - for [ft, files] in items(s:script_checks) + for [ft, files] in items(a:test_dict) for file in files call writefile(file, 'Xtest') split Xtest @@ -620,7 +630,32 @@ func Test_script_detection() filetype off endfunc +func Test_script_detection() + call Run_script_detection(s:script_checks) + call Run_script_detection(s:script_env_checks) +endfunc + func Test_setfiletype_completion() call feedkeys(":setfiletype java\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"setfiletype java javacc javascript javascriptreact', @:) endfunc + +func Test_hook_file() + filetype on + + call writefile(['[Trigger]', 'this is pacman config'], 'Xfile.hook') + split Xfile.hook + call assert_equal('dosini', &filetype) + bwipe! + + call writefile(['not pacman'], 'Xfile.hook') + split Xfile.hook + call assert_notequal('dosini', &filetype) + bwipe! + + call delete('Xfile.hook') + filetype off +endfunc + + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_increment.vim b/src/nvim/testdir/test_increment.vim index ab11d943d9..f81f8edbde 100644 --- a/src/nvim/testdir/test_increment.vim +++ b/src/nvim/testdir/test_increment.vim @@ -779,4 +779,40 @@ func Test_increment_empty_line() bwipe! endfunc +func Test_normal_increment_with_virtualedit() + set virtualedit=all + + call setline(1, ["\<TAB>1"]) + exec "norm! 0\<C-A>" + call assert_equal("\<TAB>2", getline(1)) + call assert_equal([0, 1, 2, 0], getpos('.')) + + call setline(1, ["\<TAB>1"]) + exec "norm! 0l\<C-A>" + call assert_equal("\<TAB>2", getline(1)) + call assert_equal([0, 1, 2, 0], getpos('.')) + + call setline(1, ["\<TAB>1"]) + exec "norm! 07l\<C-A>" + call assert_equal("\<TAB>2", getline(1)) + call assert_equal([0, 1, 2, 0], getpos('.')) + + call setline(1, ["\<TAB>1"]) + exec "norm! 0w\<C-A>" + call assert_equal("\<TAB>2", getline(1)) + call assert_equal([0, 1, 2, 0], getpos('.')) + + call setline(1, ["\<TAB>1"]) + exec "norm! 0wl\<C-A>" + call assert_equal("\<TAB>1", getline(1)) + call assert_equal([0, 1, 3, 0], getpos('.')) + + call setline(1, ["\<TAB>1"]) + exec "norm! 0w30l\<C-A>" + call assert_equal("\<TAB>1", getline(1)) + call assert_equal([0, 1, 3, 29], getpos('.')) + + set virtualedit& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim index ecd0e8d56b..f48458566b 100644 --- a/src/nvim/testdir/test_regexp_utf8.vim +++ b/src/nvim/testdir/test_regexp_utf8.vim @@ -332,4 +332,23 @@ func Test_ambiwidth() set regexpengine& ambiwidth& endfunc +func Run_regexp_ignore_case() + call assert_equal('iIİ', substitute('iIİ', '\([iIİ]\)', '\1', 'g')) + + call assert_equal('iIx', substitute('iIİ', '\c\([İ]\)', 'x', 'g')) + call assert_equal('xxİ', substitute('iIİ', '\(i\c\)', 'x', 'g')) + call assert_equal('iIx', substitute('iIİ', '\(İ\c\)', 'x', 'g')) + call assert_equal('iIx', substitute('iIİ', '\c\(\%u0130\)', 'x', 'g')) + call assert_equal('iIx', substitute('iIİ', '\c\([\u0130]\)', 'x', 'g')) + call assert_equal('iIx', substitute('iIİ', '\c\([\u012f-\u0131]\)', 'x', 'g')) +endfunc + +func Test_regexp_ignore_case() + set regexpengine=1 + call Run_regexp_ignore_case() + set regexpengine=2 + call Run_regexp_ignore_case() + set regexpengine& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index d4f58af10a..d20f8d1eef 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -167,4 +167,22 @@ func Test_set_register() enew! endfunc +func Test_ve_blockpaste() + new + set ve=all + 0put =['QWERTZ','ASDFGH'] + call cursor(1,1) + exe ":norm! \<C-V>3ljdP" + call assert_equal(1, col('.')) + call assert_equal(getline(1, 2), ['QWERTZ', 'ASDFGH']) + call cursor(1,1) + exe ":norm! \<C-V>3ljd" + call cursor(1,1) + norm! $3lP + call assert_equal(5, col('.')) + call assert_equal(getline(1, 2), ['TZ QWER', 'GH ASDF']) + set ve&vim + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index 66b6e6c05c..8c81ec3431 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -7,6 +7,7 @@ " %X source view_util.vim +source term_util.vim func s:get_statusline() return ScreenLines(&lines - 1, &columns)[0] @@ -29,7 +30,9 @@ endfunc " Function used to display syntax group. func SyntaxItem() - return synIDattr(synID(line("."),col("."),1),"name") + call assert_equal(s:expected_curbuf, g:actual_curbuf) + call assert_equal(s:expected_curwin, g:actual_curwin) + return synIDattr(synID(line("."), col("."),1), "name") endfunc func Test_caught_error_in_statusline() @@ -218,6 +221,8 @@ func Test_statusline() "%{: Evaluate expression between '%{' and '}' and substitute result. syntax on + let s:expected_curbuf = string(bufnr('')) + let s:expected_curwin = string(win_getid()) set statusline=%{SyntaxItem()} call assert_match('^vimNumber\s*$', s:get_statusline()) s/^/"/ @@ -332,6 +337,23 @@ func Test_statusline() set statusline=%!2*3+1 call assert_match('7\s*$', s:get_statusline()) + func GetNested() + call assert_equal(string(win_getid()), g:actual_curwin) + call assert_equal(string(bufnr('')), g:actual_curbuf) + return 'nested' + endfunc + func GetStatusLine() + call assert_equal(win_getid(), g:statusline_winid) + return 'the %{GetNested()} line' + endfunc + set statusline=%!GetStatusLine() + call assert_match('the nested line', s:get_statusline()) + call assert_false(exists('g:actual_curwin')) + call assert_false(exists('g:actual_curbuf')) + call assert_false(exists('g:statusline_winid')) + delfunc GetNested + delfunc GetStatusLine + " Check statusline in current and non-current window " with the 'fillchars' option. set fillchars=stl:^,stlnc:=,vert:\|,fold:-,diff:- diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 6cada1503f..85ee42420e 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -153,7 +153,7 @@ endfunc func Test_syntax_completion() call feedkeys(":syn \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"syn case clear cluster conceal enable include iskeyword keyword list manual match off on region reset spell sync', @:) + call assert_equal('"syn case clear cluster conceal enable foldlevel include iskeyword keyword list manual match off on region reset spell sync', @:) call feedkeys(":syn case \<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"syn case ignore match', @:) @@ -579,3 +579,86 @@ func Test_syntax_hangs() set redrawtime& bwipe! endfunc + +func Test_syntax_foldlevel() + new + call setline(1, [ + \ 'void f(int a)', + \ '{', + \ ' if (a == 1) {', + \ ' a = 0;', + \ ' } else if (a == 2) {', + \ ' a = 1;', + \ ' } else {', + \ ' a = 2;', + \ ' }', + \ ' if (a > 0) {', + \ ' if (a == 1) {', + \ ' a = 0;', + \ ' } /* missing newline */ } /* end of outer if */ else {', + \ ' a = 1;', + \ ' }', + \ ' if (a == 1)', + \ ' {', + \ ' a = 0;', + \ ' }', + \ ' else if (a == 2)', + \ ' {', + \ ' a = 1;', + \ ' }', + \ ' else', + \ ' {', + \ ' a = 2;', + \ ' }', + \ '}', + \ ]) + setfiletype c + syntax on + set foldmethod=syntax + + call assert_fails('syn foldlevel start start', 'E390') + call assert_fails('syn foldlevel not_an_option', 'E390') + + set foldlevel=1 + + syn foldlevel start + redir @c + syn foldlevel + redir END + call assert_equal("\nsyntax foldlevel start", @c) + syn sync fromstart + let a = map(range(3,9), 'foldclosed(v:val)') + call assert_equal([3,3,3,3,3,3,3], a) " attached cascade folds together + let a = map(range(10,15), 'foldclosed(v:val)') + call assert_equal([10,10,10,10,10,10], a) " over-attached 'else' hidden + let a = map(range(16,27), 'foldclosed(v:val)') + let unattached_results = [-1,17,17,17,-1,21,21,21,-1,25,25,25] + call assert_equal(unattached_results, a) " unattached cascade folds separately + + syn foldlevel minimum + redir @c + syn foldlevel + redir END + call assert_equal("\nsyntax foldlevel minimum", @c) + syn sync fromstart + let a = map(range(3,9), 'foldclosed(v:val)') + call assert_equal([3,3,5,5,7,7,7], a) " attached cascade folds separately + let a = map(range(10,15), 'foldclosed(v:val)') + call assert_equal([10,10,10,13,13,13], a) " over-attached 'else' visible + let a = map(range(16,27), 'foldclosed(v:val)') + call assert_equal(unattached_results, a) " unattached cascade folds separately + + set foldlevel=2 + + syn foldlevel start + syn sync fromstart + let a = map(range(11,14), 'foldclosed(v:val)') + call assert_equal([11,11,11,-1], a) " over-attached 'else' hidden + + syn foldlevel minimum + syn sync fromstart + let a = map(range(11,14), 'foldclosed(v:val)') + call assert_equal([11,11,-1,-1], a) " over-attached 'else' visible + + quit! +endfunc diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index 5fd71d8bfc..6abe5b7c89 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -255,8 +255,52 @@ func Test_tagjump_etags() ta foo call assert_equal('void foo() {}', getline('.')) + " Test for including another tags file + call writefile([ + \ "\x0c", + \ "Xmain.c,64", + \ "void foo() {}\x7ffoo\x011,0", + \ "\x0c", + \ "Xnonexisting,include", + \ "\x0c", + \ "Xtags2,include" + \ ], 'Xtags') + call writefile([ + \ "\x0c", + \ "Xmain.c,64", + \ "int main(int argc, char **argv)\x7fmain\x012,14", + \ ], 'Xtags2') + tag main + call assert_equal(2, line('.')) + + " corrupted tag line + call writefile([ + \ "\x0c", + \ "Xmain.c,8", + \ "int main" + \ ], 'Xtags', 'b') + call assert_fails('tag foo', 'E426:') + + " invalid line number + call writefile([ + \ "\x0c", + \ "Xmain.c,64", + \ "void foo() {}\x7ffoo\x0abc,0", + \ ], 'Xtags') + call assert_fails('tag foo', 'E426:') + + " invalid tag name + call writefile([ + \ "\x0c", + \ "Xmain.c,64", + \ ";;;;\x7f1,0", + \ ], 'Xtags') + call assert_fails('tag foo', 'E426:') + call delete('Xtags') + call delete('Xtags2') call delete('Xmain.c') + set tags& bwipe! endfunc @@ -531,4 +575,29 @@ func Test_tagline() set tags& endfunc +" Test for the 'taglength' option +func Test_tag_length() + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "tame\tXfile1\t1;", + \ "tape\tXfile2\t1;"], 'Xtags') + call writefile(['tame'], 'Xfile1') + call writefile(['tape'], 'Xfile2') + + " Jumping to the tag 'tape', should instead jump to 'tame' + new + set taglength=2 + tag tape + call assert_equal('Xfile1', @%) + " Tag search should jump to the right tag + enew + tag /^tape$ + call assert_equal('Xfile2', @%) + + call delete('Xtags') + call delete('Xfile1') + call delete('Xfile2') + set tags& taglength& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim index b20c4df311..7863317eb0 100644 --- a/src/nvim/testdir/test_textobjects.vim +++ b/src/nvim/testdir/test_textobjects.vim @@ -290,5 +290,16 @@ func! Test_sentence_with_cursor_on_delimiter() normal! 17|yas call assert_equal("A '([sentence.])' ", @") + " don't get stuck on a quote at the start of a sentence + %delete _ + call setline(1, ['A sentence.', '"A sentence"?', 'A sentence!']) + normal gg)) + call assert_equal(3, getcurpos()[1]) + + %delete _ + call setline(1, ['A sentence.', "'A sentence'?", 'A sentence!']) + normal gg)) + call assert_equal(3, getcurpos()[1]) + %delete _ endfunc diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index b4d91a01fc..bfd9435c49 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -274,7 +274,7 @@ static void terminfo_start(UI *ui) : (konsole ? 1 : 0); patch_terminfo_bugs(data, term, colorterm, vtev, konsolev, iterm_env, nsterm); - augment_terminfo(data, term, colorterm, vtev, konsolev, iterm_env, nsterm); + augment_terminfo(data, term, vtev, konsolev, iterm_env, nsterm); data->can_change_scroll_region = !!unibi_get_str(data->ut, unibi_change_scroll_region); data->can_set_lr_margin = @@ -1907,7 +1907,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, /// This adds stuff that is not in standard terminfo as extended unibilium /// capabilities. static void augment_terminfo(TUIData *data, const char *term, - const char *colorterm, long vte_version, + long vte_version, long konsolev, bool iterm_env, bool nsterm) { unibi_term *ut = data->ut; diff --git a/src/nvim/version.c b/src/nvim/version.c index e9f2b37e6b..190f13e74b 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -250,7 +250,7 @@ static const int included_patches[] = { 1671, 1670, 1669, - // 1668, + 1668, 1667, 1666, 1665, @@ -364,7 +364,7 @@ static const int included_patches[] = { 1557, 1556, 1555, - // 1554, + 1554, 1553, 1552, 1551, @@ -375,7 +375,7 @@ static const int included_patches[] = { 1546, 1545, // 1544, - // 1543, + 1543, 1542, 1541, 1540, diff --git a/test/functional/autoread/focus_spec.lua b/test/functional/autoread/focus_spec.lua new file mode 100644 index 0000000000..1d52e9948f --- /dev/null +++ b/test/functional/autoread/focus_spec.lua @@ -0,0 +1,58 @@ +local helpers = require('test.functional.helpers')(after_each) +local thelpers = require('test.functional.terminal.helpers') +local lfs = require('lfs') +local clear = helpers.clear +local nvim_prog = helpers.nvim_prog +local feed_command = helpers.feed_command +local feed_data = thelpers.feed_data + +if helpers.pending_win32(pending) then return end + +describe('autoread TUI FocusGained/FocusLost', function() + local screen + + before_each(function() + clear() + screen = thelpers.screen_setup(0, '["'..nvim_prog + ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile noshowcmd noruler"]') + end) + + it('external file change', function() + local path = 'xtest-foo' + local expected_addition = [[ + line 1 + line 2 + line 3 + line 4 + ]] + + helpers.write_file(path, '') + lfs.touch(path, os.time() - 10) + feed_command('edit '..path) + feed_data('\027[O') + + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:xtest-foo }| + :edit xtest-foo | + {3:-- TERMINAL --} | + ]]} + + helpers.write_file(path, expected_addition) + + feed_data('\027[I') + + screen:expect{grid=[[ + {1:l}ine 1 | + line 2 | + line 3 | + line 4 | + {5:xtest-foo }| + "xtest-foo" 4L, 28C | + {3:-- TERMINAL --} | + ]]} + end) +end) diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index 3269fbc68d..9b0668f9e6 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen') local clear = helpers.clear local command = helpers.command +local ok = helpers.ok local eq = helpers.eq local matches = helpers.matches local eval = helpers.eval @@ -17,6 +18,7 @@ local rmdir = helpers.rmdir local sleep = helpers.sleep local iswin = helpers.iswin local write_file = helpers.write_file +local meths = helpers.meths describe('startup', function() before_each(function() @@ -356,4 +358,36 @@ describe('sysinit', function() eq('loaded 1 xdg 0 vim 1', eval('printf("loaded %d xdg %d vim %d", g:loaded, get(g:, "xdg", 0), get(g:, "vim", 0))')) end) + + it('fixed hang issue with -D (#12647)', function() + local screen + screen = Screen.new(60, 6) + screen:attach() + command([[let g:id = termopen('"]]..nvim_prog.. + [[" -u NONE -i NONE --cmd "set noruler" -D')]]) + screen:expect([[ + ^ | + Entering Debug mode. Type "cont" to continue. | + cmd: augroup nvim_terminal | + > | + <" -u NONE -i NONE --cmd "set noruler" -D 1,0-1 All| + | + ]]) + command([[call chansend(g:id, "cont\n")]]) + screen:expect([[ + ^ | + ~ | + [No Name] | + | + <" -u NONE -i NONE --cmd "set noruler" -D 1,0-1 All| + | + ]]) + end) +end) + +describe('clean', function() + clear() + ok(string.match(meths.get_option('runtimepath'), funcs.stdpath('config')) ~= nil) + clear('--clean') + ok(string.match(meths.get_option('runtimepath'), funcs.stdpath('config')) == nil) end) diff --git a/test/functional/eval/input_spec.lua b/test/functional/eval/input_spec.lua index e774b939f7..14c02f9eb2 100644 --- a/test/functional/eval/input_spec.lua +++ b/test/functional/eval/input_spec.lua @@ -462,7 +462,7 @@ describe('confirm()', function() -- With shortmess-=F command('set shortmess-=F') feed(':edit foo<cr>') - check_and_clear('"foo" [New File] |\n') + check_and_clear('"foo" [New] |\n') -- With shortmess+=F command('set shortmess+=F') diff --git a/test/functional/eval/let_spec.lua b/test/functional/eval/let_spec.lua index f8fcdfd41f..5bc703b567 100644 --- a/test/functional/eval/let_spec.lua +++ b/test/functional/eval/let_spec.lua @@ -75,4 +75,19 @@ describe(':let', function() command(cmd_get_child_env) eq(eval('$NVIM_TEST'), eval('g:env_from_child')) end) + + it("release of list assigned to l: variable does not trigger assertion #12387, #12430", function() + source([[ + func! s:f() + let l:x = [1] + let g:x = l: + endfunc + for _ in range(2) + call s:f() + endfor + call garbagecollect() + call feedkeys('i', 't') + ]]) + eq(1, eval('1')) + end) end) diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua index afe999e1fa..db0a706319 100644 --- a/test/functional/eval/null_spec.lua +++ b/test/functional/eval/null_spec.lua @@ -47,10 +47,8 @@ describe('NULL', function() -- Subjectable behaviour - -- FIXME Should return 1 - null_expr_test('is equal to empty list', 'L == []', 0, 0) - -- FIXME Should return 1 - null_expr_test('is equal to empty list (reverse order)', '[] == L', 0, 0) + null_expr_test('is equal to empty list', 'L == []', 0, 1) + null_expr_test('is equal to empty list (reverse order)', '[] == L', 0, 1) -- Correct behaviour null_expr_test('can be indexed with error message for empty list', 'L[0]', diff --git a/test/functional/ex_cmds/drop_spec.lua b/test/functional/ex_cmds/drop_spec.lua index 762ea3d166..ef53fe75e3 100644 --- a/test/functional/ex_cmds/drop_spec.lua +++ b/test/functional/ex_cmds/drop_spec.lua @@ -31,7 +31,7 @@ describe(":drop", function() {0:~ }| {0:~ }| {1:tmp1.vim }| - "tmp1.vim" [New File] | + "tmp1.vim" [New] | ]]) end) @@ -70,7 +70,7 @@ describe(":drop", function() {0:~ }{2:│}{0:~ }| {0:~ }{2:│}{0:~ }| {2:tmp2 [+] tmp1 }| - "tmp3" [New File] | + "tmp3" [New] | ]]) end) diff --git a/test/functional/legacy/assert_spec.lua b/test/functional/legacy/assert_spec.lua index 3cb5d97869..d48b8882af 100644 --- a/test/functional/legacy/assert_spec.lua +++ b/test/functional/legacy/assert_spec.lua @@ -38,6 +38,9 @@ describe('assert function:', function() call assert_equal(4, n) let l = [1, 2, 3] call assert_equal([1, 2, 3], l) + call assert_equal(v:_null_list, v:_null_list) + call assert_equal(v:_null_list, []) + call assert_equal([], v:_null_list) fu Func() endfu let F1 = function('Func') @@ -92,6 +95,11 @@ describe('assert function:', function() call('assert_equal', 'foo', 'bar', 'testing') expected_errors({"testing: Expected 'foo' but got 'bar'"}) end) + + it('should shorten a long message', function() + call ('assert_equal', 'XxxxxxxxxxxxxxxxxxxxxxX', 'XyyyyyyyyyyyyyyyyyyyyyyyyyX') + expected_errors({"Expected 'X\\[x occurs 21 times]X' but got 'X\\[y occurs 25 times]X'"}) + end) end) -- assert_notequal({expected}, {actual}[, {msg}]) diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua index 61c8e5c02e..964ea4561e 100644 --- a/test/functional/lua/luaeval_spec.lua +++ b/test/functional/lua/luaeval_spec.lua @@ -2,7 +2,6 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') -local redir_exec = helpers.redir_exec local pcall_err = helpers.pcall_err local exc_exec = helpers.exc_exec local exec_lua = helpers.exec_lua @@ -188,23 +187,198 @@ describe('luaeval()', function() it('issues an error in some cases', function() eq("Vim(call):E5100: Cannot convert given lua table: table should either have a sequence of positive integer keys or contain only string keys", exc_exec('call luaeval("{1, foo=2}")')) - eq("Vim(call):E5101: Cannot convert given lua type", - exc_exec('call luaeval("vim.api.nvim_buf_get_lines")')) + startswith("Vim(call):E5107: Error loading lua [string \"luaeval()\"]:", exc_exec('call luaeval("1, 2, 3")')) startswith("Vim(call):E5108: Error executing lua [string \"luaeval()\"]:", exc_exec('call luaeval("(nil)()")')) - eq("Vim(call):E5101: Cannot convert given lua type", - exc_exec('call luaeval("{42, vim.api}")')) - eq("Vim(call):E5101: Cannot convert given lua type", - exc_exec('call luaeval("{foo=42, baz=vim.api}")')) - - -- The following should not crash: conversion error happens inside - eq("Vim(call):E5101: Cannot convert given lua type", - exc_exec('call luaeval("vim.api")')) - -- The following should not show internal error - eq("\nE5101: Cannot convert given lua type\n0", - redir_exec('echo luaeval("vim.api")')) + + end) + + it('should handle sending lua functions to viml', function() + eq(true, exec_lua [[ + can_pass_lua_callback_to_vim_from_lua_result = nil + + vim.fn.call(function() + can_pass_lua_callback_to_vim_from_lua_result = true + end, {}) + + return can_pass_lua_callback_to_vim_from_lua_result + ]]) + end) + + it('run functions even in timers', function() + eq(true, exec_lua [[ + can_pass_lua_callback_to_vim_from_lua_result = nil + + vim.fn.timer_start(50, function() + can_pass_lua_callback_to_vim_from_lua_result = true + end) + + vim.wait(1000, function() + return can_pass_lua_callback_to_vim_from_lua_result + end) + + return can_pass_lua_callback_to_vim_from_lua_result + ]]) + end) + + it('can run named functions more than once', function() + eq(5, exec_lua [[ + count_of_vals = 0 + + vim.fn.timer_start(5, function() + count_of_vals = count_of_vals + 1 + end, {['repeat'] = 5}) + + vim.fn.wait(1000, function() + return count_of_vals >= 5 + end) + + return count_of_vals + ]]) + end) + + it('can handle clashing names', function() + eq(1, exec_lua [[ + local f_loc = function() return 1 end + + local result = nil + vim.fn.timer_start(100, function() + result = f_loc() + end) + + local f_loc = function() return 2 end + vim.wait(1000, function() return result ~= nil end) + + return result + ]]) + end) + + it('should handle passing functions around', function() + command [[ + function VimCanCallLuaCallbacks(Concat, Cb) + let message = a:Concat("Hello Vim", "I'm Lua") + call a:Cb(message) + endfunction + ]] + + eq("Hello Vim I'm Lua", exec_lua [[ + can_pass_lua_callback_to_vim_from_lua_result = "" + + vim.fn.VimCanCallLuaCallbacks( + function(greeting, message) return greeting .. " " .. message end, + function(message) can_pass_lua_callback_to_vim_from_lua_result = message end + ) + + return can_pass_lua_callback_to_vim_from_lua_result + ]]) + end) + + it('should handle funcrefs', function() + command [[ + function VimCanCallLuaCallbacks(Concat, Cb) + let message = a:Concat("Hello Vim", "I'm Lua") + call a:Cb(message) + endfunction + ]] + + eq("Hello Vim I'm Lua", exec_lua [[ + can_pass_lua_callback_to_vim_from_lua_result = "" + + vim.funcref('VimCanCallLuaCallbacks')( + function(greeting, message) return greeting .. " " .. message end, + function(message) can_pass_lua_callback_to_vim_from_lua_result = message end + ) + + return can_pass_lua_callback_to_vim_from_lua_result + ]]) + end) + + it('should work with metatables using __call', function() + eq(1, exec_lua [[ + local this_is_local_variable = false + local callable_table = setmetatable({x = 1}, { + __call = function(t, ...) + this_is_local_variable = t.x + end + }) + + vim.fn.timer_start(5, callable_table) + + vim.wait(1000, function() + return this_is_local_variable + end) + + return this_is_local_variable + ]]) + end) + + it('should handle being called from a timer once.', function() + eq(3, exec_lua [[ + local this_is_local_variable = false + local callable_table = setmetatable({5, 4, 3, 2, 1}, { + __call = function(t, ...) this_is_local_variable = t[3] end + }) + + vim.fn.timer_start(5, callable_table) + vim.wait(1000, function() + return this_is_local_variable + end) + + return this_is_local_variable + ]]) + end) + + it('should call functions once with __call metamethod', function() + eq(true, exec_lua [[ + local this_is_local_variable = false + local callable_table = setmetatable({a = true, b = false}, { + __call = function(t, ...) this_is_local_variable = t.a end + }) + + assert(getmetatable(callable_table).__call) + vim.fn.call(callable_table, {}) + + return this_is_local_variable + ]]) + end) + + it('should work with lists using __call', function() + eq(3, exec_lua [[ + local this_is_local_variable = false + local mt = { + __call = function(t, ...) + this_is_local_variable = t[3] + end + } + local callable_table = setmetatable({5, 4, 3, 2, 1}, mt) + + -- Call it once... + vim.fn.timer_start(5, callable_table) + vim.wait(1000, function() + return this_is_local_variable + end) + + assert(this_is_local_variable) + this_is_local_variable = false + + vim.fn.timer_start(5, callable_table) + vim.wait(1000, function() + return this_is_local_variable + end) + + return this_is_local_variable + ]]) + end) + + it('should not work with tables not using __call', function() + eq({false, 'Vim:E921: Invalid callback argument'}, exec_lua [[ + local this_is_local_variable = false + local callable_table = setmetatable({x = 1}, {}) + + return {pcall(function() vim.fn.timer_start(5, callable_table) end)} + ]]) end) it('correctly converts containers with type_idx', function() diff --git a/test/functional/lua/treesitter_spec.lua b/test/functional/lua/treesitter_spec.lua index ecee471386..6ba4220849 100644 --- a/test/functional/lua/treesitter_spec.lua +++ b/test/functional/lua/treesitter_spec.lua @@ -237,9 +237,7 @@ static int nlua_schedule(lua_State *const lstate) (number_literal) @number (char_literal) @string -; TODO(bfredl): overlapping matches are unreliable, -; we need a proper priority mechanism -;(type_identifier) @type +(type_identifier) @type ((type_identifier) @Special (#eq? @Special "LuaRef")) (primitive_type) @type @@ -264,7 +262,7 @@ static int nlua_schedule(lua_State *const lstate) [4] = {bold = true, foreground = Screen.colors.Brown}, [5] = {foreground = Screen.colors.Magenta}, [6] = {foreground = Screen.colors.Red}, - [7] = {foreground = Screen.colors.SlateBlue}, + [7] = {bold = true, foreground = Screen.colors.SlateBlue}, [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}, @@ -300,7 +298,7 @@ static int nlua_schedule(lua_State *const lstate) ]], hl_query) screen:expect{grid=[[ {2:/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} nlua_schedule(lua_State *{3:const} lstate) | + {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) | { | {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | || {6:lstate} != {6:lstate}) { | @@ -311,7 +309,7 @@ static int nlua_schedule(lua_State *const lstate) {7:LuaRef} cb = nlua_ref(lstate, {5:1}); | | multiqueue_put(main_loop.events, nlua_schedule_event, | - {5:1}, ({3:void} *)(ptrdiff_t)cb); | + {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | {4:return} {5:0}; | ^} | {1:~ }| @@ -322,7 +320,7 @@ static int nlua_schedule(lua_State *const lstate) feed('7Go*/<esc>') screen:expect{grid=[[ {2:/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} nlua_schedule(lua_State *{3:const} lstate) | + {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) | { | {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | || {6:lstate} != {6:lstate}) { | @@ -334,7 +332,7 @@ static int nlua_schedule(lua_State *const lstate) {7:LuaRef} cb = nlua_ref(lstate, {5:1}); | | multiqueue_put(main_loop.events, nlua_schedule_event, | - {5:1}, ({3:void} *)(ptrdiff_t)cb); | + {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | {4:return} {5:0}; | } | {1:~ }| @@ -344,7 +342,7 @@ static int nlua_schedule(lua_State *const lstate) feed('3Go/*<esc>') screen:expect{grid=[[ {2:/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} nlua_schedule(lua_State *{3:const} lstate) | + {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) | { | {2:/^*} | {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} | @@ -357,7 +355,7 @@ static int nlua_schedule(lua_State *const lstate) {7:LuaRef} cb = nlua_ref(lstate, {5:1}); | | multiqueue_put(main_loop.events, nlua_schedule_event, | - {5:1}, ({3:void} *)(ptrdiff_t)cb); | + {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | {4:return} {5:0}; | {8:}} | | @@ -404,4 +402,65 @@ static int nlua_schedule(lua_State *const lstate) end eq({true,true}, {has_named,has_anonymous}) end) + it('allows to set simple ranges', function() + if not check_parser() then return end + + insert(test_text) + + local res = exec_lua([[ + parser = vim.treesitter.get_parser(0, "c") + return { parser:parse():root():range() } + ]]) + + eq({0, 0, 19, 0}, res) + + -- The following sets the included ranges for the current parser + -- As stated here, this only includes the function (thus the whole buffer, without the last line) + local res2 = exec_lua([[ + local root = parser:parse():root() + parser:set_included_ranges({root:child(0)}) + parser.valid = false + return { parser:parse():root():range() } + ]]) + + eq({0, 0, 18, 1}, res2) + end) + it("allows to set complex ranges", function() + if not check_parser() then return end + + insert(test_text) + + + local res = exec_lua([[ + parser = vim.treesitter.get_parser(0, "c") + query = vim.treesitter.parse_query("c", "(declaration) @decl") + + local nodes = {} + for _, node in query:iter_captures(parser:parse():root(), 0, 0, 19) do + table.insert(nodes, node) + end + + parser:set_included_ranges(nodes) + + local root = parser:parse():root() + + local res = {} + for i=0,(root:named_child_count() - 1) do + table.insert(res, { root:named_child(i):range() }) + end + return res + ]]) + + eq({ + { 2, 2, 2, 40 }, + { 3, 3, 3, 32 }, + { 4, 7, 4, 8 }, + { 4, 8, 4, 25 }, + { 8, 2, 8, 6 }, + { 8, 7, 8, 33 }, + { 9, 8, 9, 20 }, + { 10, 4, 10, 5 }, + { 10, 5, 10, 20 }, + { 14, 9, 14, 27 } }, res) + end) end) diff --git a/test/functional/options/shortmess_spec.lua b/test/functional/options/shortmess_spec.lua index 8ea9a19464..a56e9c09b4 100644 --- a/test/functional/options/shortmess_spec.lua +++ b/test/functional/options/shortmess_spec.lua @@ -25,7 +25,7 @@ describe("'shortmess'", function() ~ | ~ | ~ | - "foo" [New File] | + "foo" [New] | ]]) eq(1, eval('bufnr("%")')) @@ -50,7 +50,7 @@ describe("'shortmess'", function() ~ | ~ | ~ | - "foo" [New File] | + "foo" [New] | ]]) eq(1, eval('bufnr("%")')) feed(':edit bar<CR>') @@ -59,7 +59,7 @@ describe("'shortmess'", function() ~ | ~ | ~ | - "bar" [New File] | + "bar" [New] | ]]) eq(2, eval('bufnr("%")')) feed(':bprevious<CR>') @@ -68,7 +68,7 @@ describe("'shortmess'", function() ~ | ~ | ~ | - "foo" [New file] --No lines in buffer-- | + "foo" [New] --No lines in buffer-- | ]]) eq(1, eval('bufnr("%")')) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 5a8a9106a5..aaa28390ea 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -770,10 +770,13 @@ describe('LSP', function() it('highlight groups', function() eq({'LspDiagnosticsError', + 'LspDiagnosticsErrorFloating', 'LspDiagnosticsErrorSign', 'LspDiagnosticsHint', + 'LspDiagnosticsHintFloating', 'LspDiagnosticsHintSign', 'LspDiagnosticsInformation', + 'LspDiagnosticsInformationFloating', 'LspDiagnosticsInformationSign', 'LspDiagnosticsUnderline', 'LspDiagnosticsUnderlineError', @@ -781,6 +784,7 @@ describe('LSP', function() 'LspDiagnosticsUnderlineInformation', 'LspDiagnosticsUnderlineWarning', 'LspDiagnosticsWarning', + 'LspDiagnosticsWarningFloating', 'LspDiagnosticsWarningSign', }, exec_lua([[require'vim.lsp'; return vim.fn.getcompletion('Lsp', 'highlight')]])) @@ -811,10 +815,33 @@ describe('LSP', function() 'å å ɧ 汉语 ↥ 🤦 🦄'; }, buf_lines(1)) end) + it('handles edits with the same start position, applying changes in the order in the array', function() + local edits = { + make_edit(0, 6, 0, 10, {""}); + make_edit(0, 6, 0, 6, {"REPLACE"}); + make_edit(1, 0, 1, 3, {""}); + make_edit(1, 0, 1, 0, {"123"}); + make_edit(2, 16, 2, 18, {""}); + make_edit(2, 16, 2, 16, {"XYZ"}); + make_edit(3, 7, 3, 11, {"this"}); + make_edit(3, 7, 3, 11, {"will"}); + make_edit(3, 7, 3, 11, {"not "}); + make_edit(3, 7, 3, 11, {"show"}); + make_edit(3, 7, 3, 11, {"(but this will)"}); + } + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + eq({ + 'First REPLACE of text'; + '123ond line of text'; + 'Third line of teXYZ'; + 'Fourth (but this will) of text'; + 'å å ɧ 汉语 ↥ 🤦 🦄'; + }, buf_lines(1)) + end) it('applies complex edits', function() local edits = { - make_edit(0, 0, 0, 0, {"", "12"}); make_edit(0, 0, 0, 0, {"3", "foo"}); + make_edit(0, 0, 0, 0, {"", "12"}); make_edit(0, 1, 0, 1, {"bar", "123"}); make_edit(0, #"First ", 0, #"First line of text", {"guy"}); make_edit(1, 0, 1, #'Second', {"baz"}); @@ -1043,6 +1070,64 @@ describe('LSP', function() ]]) end) end) + describe('lsp.util.locations_to_items', function() + it('Convert Location[] to items', function() + local expected = { + { + filename = 'fake/uri', + lnum = 1, + col = 3, + text = 'testing' + }, + } + local actual = exec_lua [[ + local bufnr = vim.uri_to_bufnr("file://fake/uri") + local lines = {"testing", "123"} + vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines) + local locations = { + { + uri = 'file://fake/uri', + range = { + start = { line = 0, character = 2 }, + ['end'] = { line = 0, character = 3 }, + } + }, + } + return vim.lsp.util.locations_to_items(locations) + ]] + eq(expected, actual) + end) + it('Convert LocationLink[] to items', function() + local expected = { + { + filename = 'fake/uri', + lnum = 1, + col = 3, + text = 'testing' + }, + } + local actual = exec_lua [[ + local bufnr = vim.uri_to_bufnr("file://fake/uri") + local lines = {"testing", "123"} + vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines) + local locations = { + { + targetUri = vim.uri_from_bufnr(bufnr), + targetRange = { + start = { line = 0, character = 2 }, + ['end'] = { line = 0, character = 3 }, + }, + targetSelectionRange = { + start = { line = 0, character = 2 }, + ['end'] = { line = 0, character = 3 }, + } + }, + } + return vim.lsp.util.locations_to_items(locations) + ]] + eq(expected, actual) + end) + end) describe('lsp.util.symbols_to_items', function() describe('convert DocumentSymbol[] to items', function() it('DocumentSymbol has children', function() @@ -1397,4 +1482,162 @@ describe('LSP', function() eq({15,5}, exec_lua[[ return {vim.lsp.util._make_floating_popup_size(contents,{width = 15, wrap_at = 14})} ]]) end) end) + + describe('lsp.util.get_effective_tabstop', function() + local function test_tabstop(tabsize, softtabstop) + exec_lua(string.format([[ + vim.api.nvim_buf_set_option(0, 'softtabstop', %d) + vim.api.nvim_buf_set_option(0, 'tabstop', 2) + vim.api.nvim_buf_set_option(0, 'shiftwidth', 3) + ]], softtabstop)) + eq(tabsize, exec_lua('return vim.lsp.util.get_effective_tabstop()')) + end + + it('with softtabstop = 1', function() test_tabstop(1, 1) end) + it('with softtabstop = 0', function() test_tabstop(2, 0) end) + it('with softtabstop = -1', function() test_tabstop(3, -1) end) + end) + + describe('vim.lsp.buf.outgoing_calls', function() + it('does nothing for an empty response', function() + local qflist_count = exec_lua([=[ + require'vim.lsp.callbacks'['callHierarchy/outgoingCalls']() + return #vim.fn.getqflist() + ]=]) + eq(0, qflist_count) + end) + + it('opens the quickfix list with the right caller', function() + local qflist = exec_lua([=[ + local rust_analyzer_response = { { + fromRanges = { { + ['end'] = { + character = 7, + line = 3 + }, + start = { + character = 4, + line = 3 + } + } }, + to = { + detail = "fn foo()", + kind = 12, + name = "foo", + range = { + ['end'] = { + character = 11, + line = 0 + }, + start = { + character = 0, + line = 0 + } + }, + selectionRange = { + ['end'] = { + character = 6, + line = 0 + }, + start = { + character = 3, + line = 0 + } + }, + uri = "file:///src/main.rs" + } + } } + local callback = require'vim.lsp.callbacks'['callHierarchy/outgoingCalls'] + callback(nil, nil, rust_analyzer_response) + return vim.fn.getqflist() + ]=]) + + local expected = { { + bufnr = 2, + col = 5, + lnum = 4, + module = "", + nr = 0, + pattern = "", + text = "foo", + type = "", + valid = 1, + vcol = 0 + } } + + eq(expected, qflist) + end) + end) + + describe('vim.lsp.buf.incoming_calls', function() + it('does nothing for an empty response', function() + local qflist_count = exec_lua([=[ + require'vim.lsp.callbacks'['callHierarchy/incomingCalls']() + return #vim.fn.getqflist() + ]=]) + eq(0, qflist_count) + end) + + it('opens the quickfix list with the right callee', function() + local qflist = exec_lua([=[ + local rust_analyzer_response = { { + from = { + detail = "fn main()", + kind = 12, + name = "main", + range = { + ['end'] = { + character = 1, + line = 4 + }, + start = { + character = 0, + line = 2 + } + }, + selectionRange = { + ['end'] = { + character = 7, + line = 2 + }, + start = { + character = 3, + line = 2 + } + }, + uri = "file:///src/main.rs" + }, + fromRanges = { { + ['end'] = { + character = 7, + line = 3 + }, + start = { + character = 4, + line = 3 + } + } } + } } + + local callback = require'vim.lsp.callbacks'['callHierarchy/incomingCalls'] + callback(nil, nil, rust_analyzer_response) + return vim.fn.getqflist() + ]=]) + + local expected = { { + bufnr = 2, + col = 5, + lnum = 4, + module = "", + nr = 0, + pattern = "", + text = "main", + type = "", + valid = 1, + vcol = 0 + } } + + eq(expected, qflist) + end) + end) end) diff --git a/test/functional/shada/errors_spec.lua b/test/functional/shada/errors_spec.lua index 66c8c4ad2f..77a41caec7 100644 --- a/test/functional/shada/errors_spec.lua +++ b/test/functional/shada/errors_spec.lua @@ -1,7 +1,7 @@ -- ShaDa errors handling support local helpers = require('test.functional.helpers')(after_each) -local nvim_command, eq, exc_exec, redir_exec = - helpers.command, helpers.eq, helpers.exc_exec, helpers.redir_exec +local nvim_command, eq, exc_exec = + helpers.command, helpers.eq, helpers.exc_exec local shada_helpers = require('test.functional.shada.helpers') local reset, clear, get_shada_rw = @@ -494,23 +494,6 @@ $ eq(0, exc_exec('wshada! ' .. shada_fname)) end) - it('errors when a funcref is stored in a variable', function() - nvim_command('let F = function("tr")') - nvim_command('set shada+=!') - eq('\nE5004: Error while dumping variable g:F, itself: attempt to dump function reference' - .. '\nE574: Failed to write variable F', - redir_exec('wshada')) - end) - - it('errors when a self-referencing list is stored in a variable', function() - nvim_command('let L = []') - nvim_command('call add(L, L)') - nvim_command('set shada+=!') - eq('\nE5005: Unable to dump variable g:L: container references itself in index 0' - .. '\nE574: Failed to write variable L', - redir_exec('wshada')) - end) - it('errors with too large items', function() wshada({ 1, 206, 70, 90, 31, 179, 86, 133, 169, 103, 101, 110, 101, 114, 97, diff --git a/test/functional/shada/variables_spec.lua b/test/functional/shada/variables_spec.lua index 74bbceddcc..cc0e7fa537 100644 --- a/test/functional/shada/variables_spec.lua +++ b/test/functional/shada/variables_spec.lua @@ -1,7 +1,7 @@ -- ShaDa variables saving/reading support local helpers = require('test.functional.helpers')(after_each) -local meths, funcs, nvim_command, eq, exc_exec = - helpers.meths, helpers.funcs, helpers.command, helpers.eq, helpers.exc_exec +local meths, funcs, nvim_command, eq = + helpers.meths, helpers.funcs, helpers.command, helpers.eq local shada_helpers = require('test.functional.shada.helpers') local reset, clear = shada_helpers.reset, shada_helpers.clear @@ -121,28 +121,39 @@ describe('ShaDa support code', function() meths.get_var('NESTEDVAR')) end) - it('errors and writes when a funcref is stored in a variable', + it('ignore when a funcref is stored in a variable', function() nvim_command('let F = function("tr")') meths.set_var('U', '10') nvim_command('set shada+=!') - eq('Vim(wshada):E5004: Error while dumping variable g:F, itself: attempt to dump function reference', - exc_exec('wshada')) - meths.set_option('shada', '') - reset('set shada+=!') + nvim_command('wshada') + reset() + nvim_command('set shada+=!') + nvim_command('rshada') eq('10', meths.get_var('U')) end) - it('errors and writes when a self-referencing list is stored in a variable', + it('ignore when a partial is stored in a variable', + function() + nvim_command('let P = { -> 1 }') + meths.set_var('U', '10') + nvim_command('set shada+=!') + nvim_command('wshada') + reset() + nvim_command('set shada+=!') + nvim_command('rshada') + eq('10', meths.get_var('U')) + end) + + it('ignore when a self-referencing list is stored in a variable', function() meths.set_var('L', {}) nvim_command('call add(L, L)') meths.set_var('U', '10') nvim_command('set shada+=!') - eq('Vim(wshada):E5005: Unable to dump variable g:L: container references itself in index 0', - exc_exec('wshada')) - meths.set_option('shada', '') - reset('set shada+=!') + nvim_command('wshada') + reset() + nvim_command('rshada') eq('10', meths.get_var('U')) end) end) diff --git a/test/functional/terminal/window_split_tab_spec.lua b/test/functional/terminal/window_split_tab_spec.lua index 7b49a38e77..03bd336aec 100644 --- a/test/functional/terminal/window_split_tab_spec.lua +++ b/test/functional/terminal/window_split_tab_spec.lua @@ -103,4 +103,14 @@ describe(':terminal', function() | ]]) end) + + it('stays in terminal mode with <Cmd>wincmd', function() + command('terminal') + command('split') + command('terminal') + feed('a<Cmd>wincmd j<CR>') + eq(2, eval("winnr()")) + eq('t', eval('mode()')) + end) + end) diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 639e311ae6..11fe861de8 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -2054,10 +2054,10 @@ describe('floatwin', function() screen:expect{grid=[[ ## grid 1 [2:----------------------------------------]| - [2:----------------------------------------]| - [2:----------------------------------------]| - [2:----------------------------------------]| - [2:----------------------------------------]| + {5:[No Name] }| + [5:----------------------------------------]| + [5:----------------------------------------]| + [5:----------------------------------------]| {5:[Preview] }| [3:----------------------------------------]| ## grid 2 @@ -2068,6 +2068,10 @@ describe('floatwin', function() {17:f}{1:oo }| {17:b}{1:ar }| {1: }| + ## grid 5 + |1| {17:f}oo | + |2| {17:b}ar | + {0:~ }| ]], float_pos=expected_pos} else screen:expect([[ diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index 06465071c5..7c03005529 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -1234,13 +1234,13 @@ describe('typval.c', function() local l = list() local l2 = list() - -- NULL lists are not equal to empty lists - eq(false, lib.tv_list_equal(l, nil, true, false)) - eq(false, lib.tv_list_equal(nil, l, false, false)) - eq(false, lib.tv_list_equal(nil, l, false, true)) - eq(false, lib.tv_list_equal(l, nil, true, true)) + -- NULL lists are equal to empty lists + eq(true, lib.tv_list_equal(l, nil, true, false)) + eq(true, lib.tv_list_equal(nil, l, false, false)) + eq(true, lib.tv_list_equal(nil, l, false, true)) + eq(true, lib.tv_list_equal(l, nil, true, true)) - -- Yet NULL lists are equal themselves + -- NULL lists are equal themselves eq(true, lib.tv_list_equal(nil, nil, true, false)) eq(true, lib.tv_list_equal(nil, nil, false, false)) eq(true, lib.tv_list_equal(nil, nil, false, true)) @@ -2648,13 +2648,13 @@ describe('typval.c', function() local l2 = lua2typvalt(empty_list) local nl = lua2typvalt(null_list) - -- NULL lists are not equal to empty lists - eq(false, lib.tv_equal(l, nl, true, false)) - eq(false, lib.tv_equal(nl, l, false, false)) - eq(false, lib.tv_equal(nl, l, false, true)) - eq(false, lib.tv_equal(l, nl, true, true)) + -- NULL lists are equal to empty lists + eq(true, lib.tv_equal(l, nl, true, false)) + eq(true, lib.tv_equal(nl, l, false, false)) + eq(true, lib.tv_equal(nl, l, false, true)) + eq(true, lib.tv_equal(l, nl, true, true)) - -- Yet NULL lists are equal themselves + -- NULL lists are equal themselves eq(true, lib.tv_equal(nl, nl, true, false)) eq(true, lib.tv_equal(nl, nl, false, false)) eq(true, lib.tv_equal(nl, nl, false, true)) diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index a77a089763..465b553693 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -96,8 +96,8 @@ local init = only_separate(function() c.func(unpack(c.args)) end libnvim.time_init() - libnvim.early_init() libnvim.event_init() + libnvim.early_init(nil) if child_calls_mod then for _, c in ipairs(child_calls_mod) do c.func(unpack(c.args)) diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index 5d22332fae..477e25a882 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -178,8 +178,8 @@ set(GPERF_SHA256 588546b945bba4b70b6a3a616e80b4ab466e3f33024a352fc2198112cdbb3ae set(WINTOOLS_URL https://github.com/neovim/deps/raw/2f9acbecf06365c10baa3c0087f34a54c9c6f949/opt/win32tools.zip) set(WINTOOLS_SHA256 8bfce7e3a365721a027ce842f2ec1cf878f1726233c215c05964aac07300798c) -set(WINGUI_URL https://github.com/equalsraf/neovim-qt/releases/download/v0.2.15/neovim-qt.zip) -set(WINGUI_SHA256 b519ecb80b60522d25043f2d076a55656f5fbe5adf7f7e2943e5d8b161043987) +set(WINGUI_URL https://github.com/equalsraf/neovim-qt/releases/download/v0.2.16/neovim-qt.zip) +set(WINGUI_SHA256 aad95a1f8413a9ebf36fc0298d0dfd7d786abf88cb0f4ae9f7ec895b70c7b312) set(WIN32YANK_X86_URL https://github.com/equalsraf/win32yank/releases/download/v0.0.4/win32yank-x86.zip) set(WIN32YANK_X86_SHA256 62f34e5a46c5d4a7b3f3b512e1ff7b77fedd432f42581cbe825233a996eed62c) |