diff options
52 files changed, 2480 insertions, 1163 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 97156067f5..27cc02bd65 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,7 +101,7 @@ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY # version string, else they are combined with the result of `git describe`. set(NVIM_VERSION_MAJOR 0) set(NVIM_VERSION_MINOR 3) -set(NVIM_VERSION_PATCH 1) +set(NVIM_VERSION_PATCH 2) set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers # API level diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 1495e74d8d..21f818cb8d 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -230,27 +230,27 @@ nvim_command({command}) *nvim_command()* On execution error: fails with VimL error, does not update v:errmsg. - Parameters:~ + Parameters: ~ {command} Ex-command string nvim_get_hl_by_name({name}, {rgb}) *nvim_get_hl_by_name()* Gets a highlight definition by name. - Parameters:~ + Parameters: ~ {name} Highlight group name {rgb} Export RGB colors - Return:~ + Return: ~ Highlight definition map nvim_get_hl_by_id({hl_id}, {rgb}) *nvim_get_hl_by_id()* Gets a highlight definition by id. |hlID()| - Parameters:~ + Parameters: ~ {hl_id} Highlight id as returned by |hlID()| {rgb} Export RGB colors - Return:~ + Return: ~ Highlight definition map nvim_feedkeys({keys}, {mode}, {escape_csi}) *nvim_feedkeys()* @@ -260,7 +260,7 @@ nvim_feedkeys({keys}, {mode}, {escape_csi}) *nvim_feedkeys()* On execution error: does not fail, but updates v:errmsg. - Parameters:~ + Parameters: ~ {keys} to be typed {mode} behavior flags, see |feedkeys()| {escape_csi} If true, escape K_SPECIAL/CSI bytes in @@ -277,13 +277,13 @@ nvim_input({keys}) *nvim_input()* |keycodes| like <CR> are translated, so "<" is special. To input a literal "<", send <LT>. - Attributes:~ + Attributes: ~ {async} - Parameters:~ + Parameters: ~ {keys} to be typed - Return:~ + Return: ~ Number of bytes actually written (can be fewer than requested if the buffer becomes full). @@ -292,7 +292,7 @@ nvim_replace_termcodes({str}, {from_part}, {do_lt}, {special}) Replaces terminal codes and |keycodes| (<CR>, <Esc>, ...) in a string with the internal representation. - Parameters:~ + Parameters: ~ {str} String to be converted. {from_part} Legacy Vim parameter. Usually true. {do_lt} Also translate <lt>. Ignored if `special` is @@ -307,7 +307,7 @@ nvim_command_output({command}) *nvim_command_output()* On execution error: fails with VimL error, does not update v:errmsg. - Parameters:~ + Parameters: ~ {command} Ex-command string nvim_eval({expr}) *nvim_eval()* @@ -317,10 +317,10 @@ nvim_eval({expr}) *nvim_eval()* On execution error: fails with VimL error, does not update v:errmsg. - Parameters:~ + Parameters: ~ {expr} VimL expression string - Return:~ + Return: ~ Evaluation result or expanded object nvim_execute_lua({code}, {args}) *nvim_execute_lua()* @@ -330,11 +330,11 @@ nvim_execute_lua({code}, {args}) *nvim_execute_lua()* Only statements are executed. To evaluate an expression, prefix it with `return`: return my_function(...) - Parameters:~ + Parameters: ~ {code} lua code to execute {args} Arguments to the code - Return:~ + Return: ~ Return value of lua code if present or NIL. nvim_call_function({fn}, {args}) *nvim_call_function()* @@ -343,11 +343,11 @@ nvim_call_function({fn}, {args}) *nvim_call_function()* On execution error: fails with VimL error, does not update v:errmsg. - Parameters:~ + Parameters: ~ {fn} Function to call {args} Function arguments packed in an Array - Return:~ + Return: ~ Result of the function call nvim_call_dict_function({dict}, {fn}, {args}) *nvim_call_dict_function()* @@ -356,100 +356,100 @@ nvim_call_dict_function({dict}, {fn}, {args}) *nvim_call_dict_function()* On execution error: fails with VimL error, does not update v:errmsg. - Parameters:~ + Parameters: ~ {dict} Dictionary, or String evaluating to a VimL |self| dict {fn} Name of the function defined on the VimL dict {args} Function arguments packed in an Array - Return:~ + Return: ~ Result of the function call nvim_strwidth({text}) *nvim_strwidth()* Calculates the number of display cells occupied by `text`. <Tab> counts as one cell. - Parameters:~ + Parameters: ~ {text} Some text - Return:~ + Return: ~ Number of cells nvim_list_runtime_paths() *nvim_list_runtime_paths()* Gets the paths contained in 'runtimepath'. - Return:~ + Return: ~ List of paths nvim_set_current_dir({dir}) *nvim_set_current_dir()* Changes the global working directory. - Parameters:~ + Parameters: ~ {dir} Directory path nvim_get_current_line() *nvim_get_current_line()* Gets the current line - Parameters:~ + Parameters: ~ - Return:~ + Return: ~ Current line string nvim_set_current_line({line}) *nvim_set_current_line()* Sets the current line - Parameters:~ + Parameters: ~ {line} Line contents nvim_del_current_line() *nvim_del_current_line()* Deletes the current line - Parameters:~ + Parameters: ~ nvim_get_var({name}) *nvim_get_var()* Gets a global (g:) variable - Parameters:~ + Parameters: ~ {name} Variable name - Return:~ + Return: ~ Variable value nvim_set_var({name}, {value}) *nvim_set_var()* Sets a global (g:) variable - Parameters:~ + Parameters: ~ {name} Variable name {value} Variable value nvim_del_var({name}) *nvim_del_var()* Removes a global (g:) variable - Parameters:~ + Parameters: ~ {name} Variable name nvim_get_vvar({name}) *nvim_get_vvar()* Gets a v: variable - Parameters:~ + Parameters: ~ {name} Variable name - Return:~ + Return: ~ Variable value nvim_get_option({name}) *nvim_get_option()* Gets an option value string - Parameters:~ + Parameters: ~ {name} Option name - Return:~ + Return: ~ Option value (global) nvim_set_option({name}, {value}) *nvim_set_option()* Sets an option value - Parameters:~ + Parameters: ~ {name} Option name {value} New option value @@ -458,7 +458,7 @@ nvim_out_write({str}) *nvim_out_write()* "\n", the message is buffered (won't display) until a linefeed is written. - Parameters:~ + Parameters: ~ {str} Message nvim_err_write({str}) *nvim_err_write()* @@ -466,80 +466,80 @@ nvim_err_write({str}) *nvim_err_write()* "\n", the message is buffered (won't display) until a linefeed is written. - Parameters:~ + Parameters: ~ {str} Message nvim_err_writeln({str}) *nvim_err_writeln()* Writes a message to the Vim error buffer. Appends "\n", so the buffer is flushed (and displayed). - Parameters:~ + Parameters: ~ {str} Message nvim_list_bufs() *nvim_list_bufs()* Gets the current list of buffer handles - Return:~ + Return: ~ List of buffer handles nvim_get_current_buf() *nvim_get_current_buf()* Gets the current buffer - Return:~ + Return: ~ Buffer handle nvim_set_current_buf({buffer}) *nvim_set_current_buf()* Sets the current buffer - Parameters:~ + Parameters: ~ {buffer} Buffer handle nvim_list_wins() *nvim_list_wins()* Gets the current list of window handles - Return:~ + Return: ~ List of window handles nvim_get_current_win() *nvim_get_current_win()* Gets the current window - Return:~ + Return: ~ Window handle nvim_set_current_win({window}) *nvim_set_current_win()* Sets the current window - Parameters:~ + Parameters: ~ {window} Window handle nvim_list_tabpages() *nvim_list_tabpages()* Gets the current list of tabpage handles - Return:~ + Return: ~ List of tabpage handles nvim_get_current_tabpage() *nvim_get_current_tabpage()* Gets the current tabpage - Return:~ + Return: ~ Tabpage handle nvim_set_current_tabpage({tabpage}) *nvim_set_current_tabpage()* Sets the current tabpage - Parameters:~ + Parameters: ~ {tabpage} Tabpage handle nvim_subscribe({event}) *nvim_subscribe()* Subscribes to event broadcasts - Parameters:~ + Parameters: ~ {event} Event type string nvim_unsubscribe({event}) *nvim_unsubscribe()* Unsubscribes to event broadcasts - Parameters:~ + Parameters: ~ {event} Event type string nvim_get_color_by_name({name}) *nvim_get_color_by_name()* @@ -552,20 +552,20 @@ nvim_get_mode() *nvim_get_mode()* Gets the current mode. |mode()| "blocking" is true if Nvim is waiting for input. - Return:~ + Return: ~ Dictionary { "mode": String, "blocking": Boolean } - Attributes:~ + Attributes: ~ {async} nvim_get_keymap({mode}) *nvim_get_keymap()* Gets a list of global (non-buffer-local) |mapping| definitions. - Parameters:~ + Parameters: ~ {mode} Mode short-name ("n", "i", "v", ...) - Return:~ + Return: ~ Array of maparg()-like dictionaries describing mappings. The "buffer" key is always zero. @@ -575,21 +575,21 @@ nvim_get_commands({opts}) *nvim_get_commands()* Currently only |user-commands| are supported, not builtin Ex commands. - Parameters:~ + Parameters: ~ {opts} Optional parameters. Currently only supports {"builtin":false} - Return:~ + Return: ~ Map of maps describing commands. nvim_get_api_info() *nvim_get_api_info()* Returns a 2-tuple (Array), where item 0 is the current channel id and item 1 is the |api-metadata| map (Dictionary). - Return:~ + Return: ~ 2-tuple [{channel-id}, {api-metadata}] - Attributes:~ + Attributes: ~ {async} *nvim_set_client_info()* @@ -601,7 +601,7 @@ nvim_set_client_info({name}, {version}, {type}, {methods}, first identifies the channel, and a plugin using that library later overrides that info) - Parameters:~ + Parameters: ~ {name} short name for the connected client {version} Dictionary describing the version, with the following possible keys (all optional) @@ -648,7 +648,7 @@ nvim_set_client_info({name}, {version}, {type}, {methods}, nvim_get_chan_info({chan}) *nvim_get_chan_info()* Get information about a channel. - Return:~ + Return: ~ a Dictionary, describing a channel with the following keys: "stream" the stream underlying the channel "stdio" stdin and stdout of this Nvim instance "stderr" @@ -660,7 +660,7 @@ nvim_get_chan_info({chan}) *nvim_get_chan_info()* nvim_list_chans() *nvim_list_chans()* Get information about all open channels. - Return:~ + Return: ~ Array of Dictionaries, each describing a channel with the format specified at |nvim_get_chan_info|. @@ -670,12 +670,12 @@ nvim_call_atomic({calls}) *nvim_call_atomic()* This has two main usages: To perform several requests from an async context atomically, i.e. without interleaving redraws, RPC requests from other clients, or user interactions (however API methods may trigger autocommands or event processing which have such side-effects, e.g. |:sleep| may wake timers). To minimize RPC overhead (roundtrips) of a sequence of many requests. - Parameters:~ + Parameters: ~ {calls} an array of calls, where each call is described by an array with two elements: the request name, and an array of arguments. - Return:~ + Return: ~ an array with two elements. The first is an array of return values. The second is NIL if all calls succeeded. If a call resulted in an error, it is a three-element @@ -688,10 +688,10 @@ nvim_call_atomic({calls}) *nvim_call_atomic()* nvim_parse_expression({expr}, {flags}, {highlight}) Parse a VimL expression - Attributes:~ + Attributes: ~ {async} - Parameters:~ + Parameters: ~ {expr} Expression to parse. Is always treated as a single line. {flags} Flags: - "m" if multiple expressions in a @@ -715,7 +715,7 @@ nvim_parse_expression({expr}, {flags}, {highlight}) exclusive: one should highlight region [start_col, end_col)). - Return:~ + Return: ~ AST: top-level dictionary with these keys: "error": Dictionary with error, present only if parser saw some error. Contains the following keys: "message": String, @@ -768,10 +768,10 @@ nvim__id({obj}) *nvim__id()* This API function is used for testing. One should not rely on its presence in plugins. - Parameters:~ + Parameters: ~ {obj} Object to return. - Return:~ + Return: ~ its argument. nvim__id_array({arr}) *nvim__id_array()* @@ -780,10 +780,10 @@ nvim__id_array({arr}) *nvim__id_array()* This API function is used for testing. One should not rely on its presence in plugins. - Parameters:~ + Parameters: ~ {arr} Array to return. - Return:~ + Return: ~ its argument. nvim__id_dictionary({dct}) *nvim__id_dictionary()* @@ -792,10 +792,10 @@ nvim__id_dictionary({dct}) *nvim__id_dictionary()* This API function is used for testing. One should not rely on its presence in plugins. - Parameters:~ + Parameters: ~ {dct} Dictionary to return. - Return:~ + Return: ~ its argument. nvim__id_float({flt}) *nvim__id_float()* @@ -804,22 +804,22 @@ nvim__id_float({flt}) *nvim__id_float()* This API function is used for testing. One should not rely on its presence in plugins. - Parameters:~ + Parameters: ~ {flt} Value to return. - Return:~ + Return: ~ its argument. nvim__stats() *nvim__stats()* Gets internal stats. - Return:~ + Return: ~ Map of various internal stats. nvim_list_uis() *nvim_list_uis()* Gets a list of dictionaries representing attached UIs. - Return:~ + Return: ~ Array of UI dictionaries Each dictionary has the following keys: "height" requested height of the UI "width" requested width of the UI "rgb" whether the UI uses rgb colors (false implies cterm colors) "ext_..." Requested UI extensions, see |ui-options| "chan" Channel id of remote UI (not present for TUI) @@ -827,13 +827,13 @@ nvim_list_uis() *nvim_list_uis()* nvim_get_proc_children({pid}) *nvim_get_proc_children()* Gets the immediate children of process `pid`. - Return:~ + Return: ~ Array of child process ids, empty if process not found. nvim_get_proc({pid}) *nvim_get_proc()* Gets info describing process `pid`. - Return:~ + Return: ~ Map of process properties, or NIL if process not found. @@ -843,16 +843,16 @@ Buffer Functions *api-buffer* nvim_buf_line_count({buffer}) *nvim_buf_line_count()* Gets the buffer line count - Parameters:~ + Parameters: ~ {buffer} Buffer handle - Return:~ + Return: ~ Line count nvim_buf_attach({buffer}, {send_buffer}, {opts}) *nvim_buf_attach()* Activate updates from this buffer to the current channel. - Parameters:~ + Parameters: ~ {buffer} The buffer handle {send_buffer} Set to true if the initial notification should contain the whole buffer. If so, the @@ -862,7 +862,7 @@ nvim_buf_attach({buffer}, {send_buffer}, {opts}) *nvim_buf_attach()* `nvim_buf_changedtick_event` {opts} Optional parameters. Currently not used. - Return:~ + Return: ~ False when updates couldn't be enabled because the buffer isn't loaded or optscontained an invalid key; otherwise True. @@ -870,52 +870,52 @@ nvim_buf_attach({buffer}, {send_buffer}, {opts}) *nvim_buf_attach()* nvim_buf_detach({buffer}) *nvim_buf_detach()* Deactivate updates from this buffer to the current channel. - Parameters:~ + Parameters: ~ {buffer} The buffer handle - Return:~ + Return: ~ False when updates couldn't be disabled because the buffer isn't loaded; otherwise True. *nvim_buf_get_lines()* nvim_buf_get_lines({buffer}, {start}, {end}, {strict_indexing}) - Retrieves a line range from the buffer + Gets a line-range from the buffer. Indexing is zero-based, end-exclusive. Negative indices are - interpreted as length+1+index, i e -1 refers to the index past - the end. So to get the last element set start=-2 and end=-1. + interpreted as length+1+index: -1 refers to the index past the + end. So to get the last element use start=-2 and end=-1. Out-of-bounds indices are clamped to the nearest valid value, unless `strict_indexing` is set. - Parameters:~ + Parameters: ~ {buffer} Buffer handle {start} First line index {end} Last line index (exclusive) {strict_indexing} Whether out-of-bounds should be an error. - Return:~ + Return: ~ Array of lines *nvim_buf_set_lines()* nvim_buf_set_lines({buffer}, {start}, {end}, {strict_indexing}, {replacement}) - Replaces line range on the buffer + Sets (replaces) a line-range in the buffer. Indexing is zero-based, end-exclusive. Negative indices are - interpreted as length+1+index, i e -1 refers to the index past - the end. So to change or delete the last element set start=-2 - and end=-1. + interpreted as length+1+index: -1 refers to the index past the + end. So to change or delete the last element use start=-2 and + end=-1. - To insert lines at a given index, set both start and end to - the same index. To delete a range of lines, set replacement to + To insert lines at a given index, set `start` and `end` to the + same index. To delete a range of lines, set `replacement` to an empty array. Out-of-bounds indices are clamped to the nearest valid value, unless `strict_indexing` is set. - Parameters:~ + Parameters: ~ {buffer} Buffer handle {start} First line index {end} Last line index (exclusive) @@ -926,47 +926,47 @@ nvim_buf_set_lines({buffer}, {start}, {end}, {strict_indexing}, nvim_buf_get_var({buffer}, {name}) *nvim_buf_get_var()* Gets a buffer-scoped (b:) variable. - Parameters:~ + Parameters: ~ {buffer} Buffer handle {name} Variable name - Return:~ + Return: ~ Variable value nvim_buf_get_changedtick({buffer}) *nvim_buf_get_changedtick()* Gets a changed tick of a buffer - Parameters:~ + Parameters: ~ {buffer} Buffer handle. - Return:~ + Return: ~ b:changedtickvalue. nvim_buf_get_keymap({buffer}, {mode}) *nvim_buf_get_keymap()* Gets a list of buffer-local |mapping| definitions. - Parameters:~ + Parameters: ~ {mode} Mode short-name ("n", "i", "v", ...) {buffer} Buffer handle - Return:~ + Return: ~ Array of maparg()-like dictionaries describing mappings. The "buffer" key holds the associated buffer handle. nvim_buf_get_commands({buffer}, {opts}) *nvim_buf_get_commands()* Gets a map of buffer-local |user-commands|. - Parameters:~ + Parameters: ~ {buffer} Buffer handle. {opts} Optional parameters. Currently not used. - Return:~ + Return: ~ Map of maps describing commands. nvim_buf_set_var({buffer}, {name}, {value}) *nvim_buf_set_var()* Sets a buffer-scoped (b:) variable - Parameters:~ + Parameters: ~ {buffer} Buffer handle {name} Variable name {value} Variable value @@ -974,25 +974,25 @@ nvim_buf_set_var({buffer}, {name}, {value}) *nvim_buf_set_var()* nvim_buf_del_var({buffer}, {name}) *nvim_buf_del_var()* Removes a buffer-scoped (b:) variable - Parameters:~ + Parameters: ~ {buffer} Buffer handle {name} Variable name nvim_buf_get_option({buffer}, {name}) *nvim_buf_get_option()* Gets a buffer option value - Parameters:~ + Parameters: ~ {buffer} Buffer handle {name} Option name - Return:~ + Return: ~ Option value nvim_buf_set_option({buffer}, {name}, {value}) *nvim_buf_set_option()* Sets a buffer option value. Passing 'nil' as value deletes the option (only works if there's a global fallback) - Parameters:~ + Parameters: ~ {buffer} Buffer handle {name} Option name {value} Option value @@ -1000,37 +1000,37 @@ nvim_buf_set_option({buffer}, {name}, {value}) *nvim_buf_set_option()* nvim_buf_get_name({buffer}) *nvim_buf_get_name()* Gets the full file name for the buffer - Parameters:~ + Parameters: ~ {buffer} Buffer handle - Return:~ + Return: ~ Buffer name nvim_buf_set_name({buffer}, {name}) *nvim_buf_set_name()* Sets the full file name for a buffer - Parameters:~ + Parameters: ~ {buffer} Buffer handle {name} Buffer name nvim_buf_is_valid({buffer}) *nvim_buf_is_valid()* Checks if a buffer is valid - Parameters:~ + Parameters: ~ {buffer} Buffer handle - Return:~ + Return: ~ true if the buffer is valid, false otherwise nvim_buf_get_mark({buffer}, {name}) *nvim_buf_get_mark()* Return a tuple (row,col) representing the position of the named mark - Parameters:~ + Parameters: ~ {buffer} Buffer handle {name} Mark name - Return:~ + Return: ~ (row, col) tuple *nvim_buf_add_highlight()* @@ -1059,7 +1059,7 @@ nvim_buf_add_highlight({buffer}, {src_id}, {hl_group}, {line}, initialization, and later asynchronously add and clear highlights in response to buffer changes. - Parameters:~ + Parameters: ~ {buffer} Buffer handle {src_id} Source group to use or 0 to use a new group, or -1 for ungrouped highlight @@ -1070,7 +1070,7 @@ nvim_buf_add_highlight({buffer}, {src_id}, {hl_group}, {line}, {col_end} End of (byte-indexed) column range to highlight, or -1 to highlight to end of line - Return:~ + Return: ~ The src_id that was used *nvim_buf_clear_highlight()* @@ -1081,7 +1081,7 @@ nvim_buf_clear_highlight({buffer}, {src_id}, {line_start}, {line_end}) To clear a source group in the entire buffer, pass in 0 and -1 to line_start and line_end respectively. - Parameters:~ + Parameters: ~ {buffer} Buffer handle {src_id} Highlight source group to clear, or -1 to clear all. @@ -1096,76 +1096,76 @@ Window Functions *api-window* nvim_win_get_buf({window}) *nvim_win_get_buf()* Gets the current buffer in a window - Parameters:~ + Parameters: ~ {window} Window handle - Return:~ + Return: ~ Buffer handle nvim_win_get_cursor({window}) *nvim_win_get_cursor()* Gets the cursor position in the window - Parameters:~ + Parameters: ~ {window} Window handle - Return:~ + Return: ~ (row, col) tuple nvim_win_set_cursor({window}, {pos}) *nvim_win_set_cursor()* Sets the cursor position in the window - Parameters:~ + Parameters: ~ {window} Window handle {pos} (row, col) tuple representing the new position nvim_win_get_height({window}) *nvim_win_get_height()* Gets the window height - Parameters:~ + Parameters: ~ {window} Window handle - Return:~ + Return: ~ Height as a count of rows nvim_win_set_height({window}, {height}) *nvim_win_set_height()* Sets the window height. This will only succeed if the screen is split horizontally. - Parameters:~ + Parameters: ~ {window} Window handle {height} Height as a count of rows nvim_win_get_width({window}) *nvim_win_get_width()* Gets the window width - Parameters:~ + Parameters: ~ {window} Window handle - Return:~ + Return: ~ Width as a count of columns nvim_win_set_width({window}, {width}) *nvim_win_set_width()* Sets the window width. This will only succeed if the screen is split vertically. - Parameters:~ + Parameters: ~ {window} Window handle {width} Width as a count of columns nvim_win_get_var({window}, {name}) *nvim_win_get_var()* Gets a window-scoped (w:) variable - Parameters:~ + Parameters: ~ {window} Window handle {name} Variable name - Return:~ + Return: ~ Variable value nvim_win_set_var({window}, {name}, {value}) *nvim_win_set_var()* Sets a window-scoped (w:) variable - Parameters:~ + Parameters: ~ {window} Window handle {name} Variable name {value} Variable value @@ -1173,25 +1173,25 @@ nvim_win_set_var({window}, {name}, {value}) *nvim_win_set_var()* nvim_win_del_var({window}, {name}) *nvim_win_del_var()* Removes a window-scoped (w:) variable - Parameters:~ + Parameters: ~ {window} Window handle {name} Variable name nvim_win_get_option({window}, {name}) *nvim_win_get_option()* Gets a window option value - Parameters:~ + Parameters: ~ {window} Window handle {name} Option name - Return:~ + Return: ~ Option value nvim_win_set_option({window}, {name}, {value}) *nvim_win_set_option()* Sets a window option value. Passing 'nil' as value deletes the option(only works if there's a global fallback) - Parameters:~ + Parameters: ~ {window} Window handle {name} Option name {value} Option value @@ -1200,37 +1200,37 @@ nvim_win_get_position({window}) *nvim_win_get_position()* Gets the window position in display cells. First position is zero. - Parameters:~ + Parameters: ~ {window} Window handle - Return:~ + Return: ~ (row, col) tuple with the window position nvim_win_get_tabpage({window}) *nvim_win_get_tabpage()* Gets the window tabpage - Parameters:~ + Parameters: ~ {window} Window handle - Return:~ + Return: ~ Tabpage that contains the window nvim_win_get_number({window}) *nvim_win_get_number()* Gets the window number - Parameters:~ + Parameters: ~ {window} Window handle - Return:~ + Return: ~ Window number nvim_win_is_valid({window}) *nvim_win_is_valid()* Checks if a window is valid - Parameters:~ + Parameters: ~ {window} Window handle - Return:~ + Return: ~ true if the window is valid, false otherwise @@ -1240,26 +1240,26 @@ Tabpage Functions *api-tabpage* nvim_tabpage_list_wins({tabpage}) *nvim_tabpage_list_wins()* Gets the windows in a tabpage - Parameters:~ + Parameters: ~ {tabpage} Tabpage - Return:~ + Return: ~ List of windows in tabpage nvim_tabpage_get_var({tabpage}, {name}) *nvim_tabpage_get_var()* Gets a tab-scoped (t:) variable - Parameters:~ + Parameters: ~ {tabpage} Tabpage handle {name} Variable name - Return:~ + Return: ~ Variable value nvim_tabpage_set_var({tabpage}, {name}, {value}) *nvim_tabpage_set_var()* Sets a tab-scoped (t:) variable - Parameters:~ + Parameters: ~ {tabpage} Tabpage handle {name} Variable name {value} Variable value @@ -1267,35 +1267,35 @@ nvim_tabpage_set_var({tabpage}, {name}, {value}) *nvim_tabpage_set_var()* nvim_tabpage_del_var({tabpage}, {name}) *nvim_tabpage_del_var()* Removes a tab-scoped (t:) variable - Parameters:~ + Parameters: ~ {tabpage} Tabpage handle {name} Variable name nvim_tabpage_get_win({tabpage}) *nvim_tabpage_get_win()* Gets the current window in a tabpage - Parameters:~ + Parameters: ~ {tabpage} Tabpage handle - Return:~ + Return: ~ Window handle nvim_tabpage_get_number({tabpage}) *nvim_tabpage_get_number()* Gets the tabpage number - Parameters:~ + Parameters: ~ {tabpage} Tabpage handle - Return:~ + Return: ~ Tabpage number nvim_tabpage_is_valid({tabpage}) *nvim_tabpage_is_valid()* Checks if a tabpage is valid - Parameters:~ + Parameters: ~ {tabpage} Tabpage handle - Return:~ + Return: ~ true if the tabpage is valid, false otherwise diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index d71bbf2528..c460e65c64 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1528,8 +1528,8 @@ v:event Dictionary of event data for the current |autocommand|. Valid event, e.g. |DirChanged| or |TextYankPost|. KEY DESCRIPTION ~ abort Whether the event triggered during - an aborting condition, i e |c_Esc| or - |c_CTRL-c|for |CmdlineLeave|. + an aborting condition (e.g. |c_Esc| or + |c_CTRL-c| for |CmdlineLeave|). cmdlevel Level of cmdline. cmdtype Type of cmdline, |cmdline-char|. cwd Current working directory. @@ -4995,6 +4995,9 @@ jobstart({cmd}[, {opts}]) *jobstart()* :call jobstart(split(&shell) + split(&shellcmdflag) + ['{cmd}']) < (See |shell-unquoting| for details.) + Example: > + :call jobstart('nvim -h', {'on_stdout':{j,d,e->append(line('.'),d)}}) +< Returns |job-id| on success, 0 on invalid arguments (or job table is full), -1 if {cmd}[0] or 'shell' is not executable. For communication over the job's stdio, it is represented as a diff --git a/runtime/doc/if_lua.txt b/runtime/doc/if_lua.txt index 968d882a22..17c1a8a329 100644 --- a/runtime/doc/if_lua.txt +++ b/runtime/doc/if_lua.txt @@ -310,14 +310,22 @@ semantically equivalent in Lua to: return chunk(arg) -- return typval end -Note that "_A" receives the argument to "luaeval". Lua nils, numbers, strings, -tables and booleans are converted to their respective VimL types. An error is -thrown if conversion of any of the remaining Lua types is attempted. - -Note 2: lua tables are used as both dictionaries and lists, thus making it -impossible to determine whether empty table is meant to be empty list or empty -dictionary. Additionally lua does not have integer numbers. To distinguish -between these cases there is the following agreement: +Lua nils, numbers, strings, tables and booleans are converted to their +respective VimL types. An error is thrown if conversion of any other Lua types +is attempted. + +The magic global "_A" contains the second argument to luaeval(). + +Example: > + :echo luaeval('_A[1] + _A[2]', [40, 2]) + 42 + :echo luaeval('string.match(_A, "[a-z]+")', 'XYXfoo123') + foo + +Lua tables are used as both dictionaries and lists, so it is impossible to +determine whether empty table is meant to be empty list or empty dictionary. +Additionally lua does not have integer numbers. To distinguish between these +cases there is the following agreement: 0. Empty table is empty list. 1. Table with N incrementally growing integral numbers, starting from 1 and diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index 83030bf713..094b280697 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -4845,9 +4845,10 @@ Cursor character under the cursor *hl-CursorIM* CursorIM like Cursor, but used when in IME mode |CursorIM| *hl-CursorColumn* -CursorColumn screen column at the cursor, when 'cursorcolumn' is set +CursorColumn Screen-column at the cursor, when 'cursorcolumn' is set. *hl-CursorLine* -CursorLine screen line at the cursor, when 'cursorline' is set +CursorLine Screen-line at the cursor, when 'cursorline' is set. + Low-priority if foreground (ctermfg OR guifg) is not set. *hl-Directory* Directory directory names (and other special names in listings) *hl-DiffAdd* diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index 3ce8ac1d89..9d10756e23 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -21,7 +21,7 @@ After connecting to Nvim (usually a spawned, embedded instance) use the |nvim_ui_attach| API method to tell Nvim that your program wants to draw the Nvim screen grid with a size of width × height cells. `options` must be a dictionary with these (optional) keys: - `rgb` Decides the color format. + `rgb` Decides the color format. |ui-rgb| Set true (default) for 24-bit RGB colors. Set false for terminal colors (max of 256). *ui-ext-options* @@ -29,6 +29,8 @@ a dictionary with these (optional) keys: `ext_tabline` Externalize the tabline. |ui-tabline| `ext_cmdline` Externalize the cmdline. |ui-cmdline| `ext_wildmenu` Externalize the wildmenu. |ui-ext-wildmenu| + `ext_newgrid` Use new revision of the grid events. |ui-newgrid| + `ext_hlstate` Use detailed highlight state. |ui-hlstate| Specifying a non-existent option is an error. UIs can check the |api-metadata| `ui_options` key for supported options. Additionally Nvim (currently) requires @@ -49,12 +51,18 @@ Events must be handled in-order. The user should only see the updated screen state after all events in the same "redraw" batch are processed (not any intermediate state after processing only part of the array). -Nvim sends |ui-global| and |ui-grid| events unconditionally; these suffice to -implement a terminal-like layout. +By default, Nvim sends |ui-global| and |ui-grid-old| events; these suffice to +implement a terminal-like interface. However there are two revisions of the +grid part of the protocol. The newer revision |ui-newgrid|, enabled by +`ext_newgrid` option, has some improvements, such as a more efficient +representation of highlighted text, simplified events and room for futher +enhancements that will use multiple grids. The older revision is available and +used by default only for backwards compatibility reasons. New UIs are strongly +recommended to use |ui-newgrid|, as further protocol extensions will require it. Nvim optionally sends screen elements "semantically" as structured events instead of raw grid-lines, controlled by |ui-ext-options|. The UI must present -those elements itself; Nvim will not draw those elements on the |ui-grid|. +those elements itself; Nvim will not draw those elements on the grid. Future versions of Nvim may add new update kinds and may append new parameters to existing update kinds. Clients must be prepared to ignore such extensions, @@ -149,7 +157,147 @@ Global Events *ui-global* Notify the user with an audible or visual bell, respectively. ============================================================================== -Grid Events *ui-grid* +Grid Events (new revision) *ui-newgrid* + +These events are used if `ext_newgrid` option is set (recommended for all new +UIs). + +Most of these events take a `grid` index as first parameter. Grid 1 is the +global grid used by default for the entire editor screen state. Grids other +than that will be defined by future extensions. Just activating the `ext_newgrid` +option by itself will never cause any additional grids to be created. + +Highlight attribute groups are predefined. UIs should maintain a table to map +numerical highlight `id`:s to the actual attributes. + +["grid_resize", grid, width, height] + Resize a `grid`. If `grid` wasn't seen by the client before, a new grid is + being created with this size. + +["default_colors_set", rgb_fg, rgb_bg, rgb_sp, cterm_fg, cterm_bg] + The first three arguments set the default foreground, background and + special colors respectively. `cterm_fg` and `cterm_bg` specifies the + default color codes to use in a 256-color terminal. + + Note: unlike the corresponding events in the first revision, the + screen is not always cleared after sending this event. The GUI has to + repaint the screen with changed background color itself. + + *ui-event-hl_attr_define* +["hl_attr_define", id, rgb_attr, cterm_attr, info] + Add a highlight with `id` to the highlight table, with the + attributes specified by the `rgb_attr` and `cterm_attr` dicts, with the + following (all optional) keys. + + `foreground`: foreground color. + `background`: background color. + `special`: color to use for underline and undercurl, when present. + `reverse`: reverse video. Foreground and background colors are + switched. + `italic`: italic text. + `bold`: bold text. + `underline`: underlined text. The line has `special` color. + `undercurl`: undercurled text. The curl has `special` color. + + For absent color keys the default color should be used. Don't store + the default value in the table, rather a sentinel value, so that + a changed default color will take effect. + All boolean keys default to false, and will only be sent when they + are true. + + Highlights are always transmitted both for both the rgb format and as + terminal 256-color codes, as the `rgb_attr` and `cterm_attr` parameters + respectively. The |ui-rgb| option has no effect effect anymore. + Most external UIs will only need to store and use the `rgb_attr` + attributes. + + `id` 0 will always be used for the default highlight with colors defined + by `default_colors_set` and no styles applied. + + Note: `id`:s can be reused if Nvim's internal highlight table is full. + In this case, Nvim will always issue redraws of screen cells that are + affected by redefined `id`:s, so UIs do not need to keep track of this + themselves. + + `info` is an empty array per default, and will be used by the + |ui-hlstate| extension explaned below. + + *ui-event-grid_line* +["grid_line", grid, row, col_start, cells] + Redraw a continous part of a `row` on a `grid`, starting at the column + `col_start`. `cells` is an array of arrays each with 1 to 3 items: + `[text(, hl_id, repeat)]` . `text` is the UTF-8 text that should be put in + a cell, with the highlight `hl_id` defined by a previous `hl_attr_define` + call. If `hl_id` is not present the most recently seen `hl_id` in + the same call should be used (it is always sent for the first + cell in the event). If `repeat` is present, the cell should be + repeated `repeat` times (including the first time), otherwise just + once. + + The right cell of a double-width char will be represented as the empty + string. Double-width chars never use `repeat`. + + If the array of cell changes doesn't reach to the end of the line, the + rest should remain unchanged. A whitespace char, repeated + enough to cover the remaining line, will be sent when the rest of the + line should be cleared. + +["grid_clear", grid] + Clear a `grid`. + +["grid_destroy", grid] + `grid` will not be used anymore and the UI can free any data associated + with it. + +["grid_cursor_goto", grid, row, column] + Makes `grid` the current grid and `row, column` the cursor position on this + grid. This event will be sent at most once in a `redraw` batch and + indicates the visible cursor position. + +["grid_scroll", grid, top, bot, left, right, rows, cols] + Scroll the text in the a region of `grid`. The diagrams below illustrate + what will happen, depending on the scroll direction. "=" is used to + represent the SR(scroll region) boundaries and "-" the moved rectangles. + Note that dst and src share a common region. + + If `rows` is bigger than 0, move a rectangle in the SR up, this can + happen while scrolling down. +> + +-------------------------+ + | (clipped above SR) | ^ + |=========================| dst_top | + | dst (still in SR) | | + +-------------------------+ src_top | + | src (moved up) and dst | | + |-------------------------| dst_bot | + | src (cleared) | | + +=========================+ src_bot +< + If `rows` is less than zero, move a rectangle in the SR down, this can + happen while scrolling up. +> + +=========================+ src_top + | src (cleared) | | + |------------------------ | dst_top | + | src (moved down) and dst| | + +-------------------------+ src_bot | + | dst (still in SR) | | + |=========================| dst_bot | + | (clipped below SR) | v + +-------------------------+ +< + `cols` is always zero in this version of Nvim, and reserved for future + use. + + Note when updating code from |ui-grid-old| events: ranges are + end-exclusive, which is consistent with API conventions, but different + from `set_scroll_region` which was end-inclusive. + +============================================================================== +Grid Events (first revision) *ui-grid-old* + +This is an older representation of the screen grid, used if `ext_newgrid` +option is not set. ["resize", width, height] The grid is resized to `width` and `height` cells. @@ -173,7 +321,7 @@ Grid Events *ui-grid* Set the default foreground, background and special colors respectively. - *ui-event-highlight_set* + *ui-event-highlight_set* ["highlight_set", attrs] Set the attributes that the next text put on the grid will have. `attrs` is a dict with the keys below. Any absent key is reset @@ -197,6 +345,9 @@ Grid Events *ui-grid* ["set_scroll_region", top, bot, left, right] Define the scroll region used by `scroll` below. + + Note: ranges are end-inclusive, which is inconsistent with API + conventions. ["scroll", count] Scroll the text in the scroll region. The diagrams below illustrate @@ -231,6 +382,42 @@ Grid Events *ui-grid* +-------------------------+ < ============================================================================== +Detailed highlight state Extension *ui-hlstate* + + +Only sent if `ext_hlstate` option is set in |ui-options|. `ext_hlstate` implies +`ext_newgrid`. + +By default, nvim will only describe grid cells using the final calculated +higlight attributes, as described by the dict keys in |ui-event-highlight_set|. +The `ext_hlstate` extension allows to the UI to also receive a semantic +describtion of the higlights active in a cell. In this mode highlights will be +predefined in a table, see |ui-event-hl_attr_define| and |ui-event-grid_line|. +The `info` parameter in `hl_attr_define` will contain a semantic description +of the highlights. As highlight groups can be combined, this will be an array +of items, with the item with highest priority last. Each item is a dictionary +with the following possible keys: + + `kind`: always present. One of the following values: + "ui": A builtin ui highlight. + "syntax": highlight applied to a buffer by a syntax declaration or + other runtime/plugin functionallity such as + |nvim_buf_add_highlight| + "terminal": highlight from a process running in a |terminal-emulator|. + Contains no futher semantic information. + `ui_name`: Name of the builtin highlight. See |highlight-groups| for + possible values. Only present for "ui". + `hi_name`: Name of the final |:highlight| group where the used + attributes are defined. + `id`: Unique numeric id representing this item. + +Note: "ui" items will have both `ui_name` and `hi_name` present. These can +differ, because the builtin group was linked to another group |hi-link| , or +because 'winhighlight' was used. UI items will be transmitted, even if the +highlight group is cleared, so `ui_name` can always be used to reliably identify +screen elements, even if no attributes have been applied. + +============================================================================== Popupmenu Events *ui-popupmenu* Only sent if `ext_popupmenu` option is set in |ui-options| diff --git a/runtime/syntax/man.vim b/runtime/syntax/man.vim index 9ae50d17a5..6afe56a6e3 100644 --- a/runtime/syntax/man.vim +++ b/runtime/syntax/man.vim @@ -34,19 +34,18 @@ if b:man_sect =~# '^[023]' syntax case match syntax include @c $VIMRUNTIME/syntax/c.vim syntax match manCFuncDefinition display '\<\h\w*\>\ze\(\s\|\n\)*(' contained - syntax match manSentence display '\%(^ \{3,7}\u\|\. \u\)\_.\{-} - \\%(-$\|\.$\|:$\)\| - \ \{3,7}\a.*\%(\.\|:\)$' contained contains=manReference + syntax match manLowerSentence /\n\s\{7}\l.\+[()]\=\%(\:\|.\|-\)[()]\=[{};]\@<!\n$/ display keepend contained contains=manReference + syntax region manSentence start=/^\s\{7}\%(\u\|\*\)[^{}=]*/ end=/\n$/ end=/\ze\n\s\{3,7}#/ keepend contained contains=manReference syntax region manSynopsis start='^\%( \SYNOPSIS\| \SYNTAX\| \SINTASSI\| \SKŁADNIA\| \СИНТАКСИС\| - \書式\)$' end='^\%(\S.*\)\=\S$' keepend contains=manSentence,manSectionHeading,@c,manCFuncDefinition + \書式\)$' end='^\%(\S.*\)\=\S$' keepend contains=manLowerSentence,manSentence,manSectionHeading,@c,manCFuncDefinition highlight default link manCFuncDefinition Function - syntax region manExample start='^EXAMPLES\=$' end='^\%(\S.*\)\=\S$' keepend contains=manSentence,manSectionHeading,manSubHeading,@c,manCFuncDefinition + syntax region manExample start='^EXAMPLES\=$' end='^\%(\S.*\)\=\S$' keepend contains=manLowerSentence,manSentence,manSectionHeading,manSubHeading,@c,manCFuncDefinition " XXX: groupthere doesn't seem to work syntax sync minlines=500 diff --git a/scripts/gen_api_vimdoc.py b/scripts/gen_api_vimdoc.py index 4c99686faf..0bbc3706c6 100755 --- a/scripts/gen_api_vimdoc.py +++ b/scripts/gen_api_vimdoc.py @@ -188,7 +188,7 @@ def parse_params(parent, width=62): desc = parse_parblock(desc_node, width=None) items.append((name.strip(), desc.strip())) - out = 'Parameters:~\n' + out = 'Parameters: ~\n' for name, desc in items: name = ' %s' % name.ljust(name_length) out += doc_wrap(desc, prefix=name, width=width) + '\n' @@ -229,7 +229,7 @@ def parse_para(parent, width=62): prefix=' ', width=width)) elif kind == 'return': - lines.append('%s:~' % kind.title()) + lines.append('%s: ~' % kind.title()) lines.append(doc_wrap(parse_para(child), prefix=' ', width=width)) @@ -361,16 +361,16 @@ def parse_source_xml(filename): annotations = '\n'.join(annotations) if annotations: - annotations = ('\n\nAttributes:~\n' + + annotations = ('\n\nAttributes: ~\n' + textwrap.indent(annotations, ' ')) - i = doc.rfind('Parameters:~') + i = doc.rfind('Parameters: ~') if i == -1: doc += annotations else: doc = doc[:i] + annotations + '\n\n' + doc[i:] if 'INCLUDE_C_DECL' in os.environ: - doc += '\n\nC Declaration:~\n>\n' + doc += '\n\nC Declaration: ~\n>\n' doc += c_decl doc += '\n<' @@ -441,7 +441,7 @@ def gen_docs(config): doc += '\n\n' + functions if 'INCLUDE_DEPRECATED' in os.environ and deprecated: - doc += '\n\n\nDeprecated %s Functions:~\n\n' % name + doc += '\n\n\nDeprecated %s Functions: ~\n\n' % name doc += deprecated if doc: diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index b5dd72c513..8ff24b877e 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -191,11 +191,11 @@ ArrayOf(String) buffer_get_line_slice(Buffer buffer, return nvim_buf_get_lines(0, buffer, start , end, false, err); } -/// Retrieves a line range from the buffer +/// Gets a line-range from the buffer. /// /// Indexing is zero-based, end-exclusive. Negative indices are interpreted -/// as length+1+index, i e -1 refers to the index past the end. So to get the -/// last element set start=-2 and end=-1. +/// as length+1+index: -1 refers to the index past the end. So to get the +/// last element use start=-2 and end=-1. /// /// Out-of-bounds indices are clamped to the nearest valid value, unless /// `strict_indexing` is set. @@ -286,14 +286,14 @@ void buffer_set_line_slice(Buffer buffer, } -/// Replaces line range on the buffer +/// Sets (replaces) a line-range in the buffer. /// /// Indexing is zero-based, end-exclusive. Negative indices are interpreted -/// as length+1+index, i e -1 refers to the index past the end. So to change -/// or delete the last element set start=-2 and end=-1. +/// as length+1+index: -1 refers to the index past the end. So to change +/// or delete the last element use start=-2 and end=-1. /// -/// To insert lines at a given index, set both start and end to the same index. -/// To delete a range of lines, set replacement to an empty array. +/// To insert lines at a given index, set `start` and `end` to the same index. +/// To delete a range of lines, set `replacement` to an empty array. /// /// Out-of-bounds indices are clamped to the nearest valid value, unless /// `strict_indexing` is set. diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index b6e0b9a566..63c2c4a1b9 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -16,6 +16,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/popupmnu.h" #include "nvim/cursor_shape.h" +#include "nvim/highlight.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/ui.c.generated.h" @@ -25,6 +26,12 @@ typedef struct { uint64_t channel_id; Array buffer; + + int hl_id; // current higlight for legacy put event + Integer cursor_row, cursor_col; // Intended visibule cursor position + + // Position of legacy cursor, used both for drawing and visible user cursor. + Integer client_row, client_col; } UIData; static PMap(uint64_t) *connected_uis = NULL; @@ -70,10 +77,9 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, ui->width = (int)width; ui->height = (int)height; ui->rgb = true; - ui->resize = remote_ui_resize; - ui->clear = remote_ui_clear; - ui->eol_clear = remote_ui_eol_clear; - ui->cursor_goto = remote_ui_cursor_goto; + ui->grid_resize = remote_ui_grid_resize; + ui->grid_clear = remote_ui_grid_clear; + ui->grid_cursor_goto = remote_ui_grid_cursor_goto; ui->mode_info_set = remote_ui_mode_info_set; ui->update_menu = remote_ui_update_menu; ui->busy_start = remote_ui_busy_start; @@ -81,16 +87,12 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, ui->mouse_on = remote_ui_mouse_on; ui->mouse_off = remote_ui_mouse_off; ui->mode_change = remote_ui_mode_change; - ui->set_scroll_region = remote_ui_set_scroll_region; - ui->scroll = remote_ui_scroll; - ui->highlight_set = remote_ui_highlight_set; - ui->put = remote_ui_put; + ui->grid_scroll = remote_ui_grid_scroll; + ui->hl_attr_define = remote_ui_hl_attr_define; + ui->raw_line = remote_ui_raw_line; ui->bell = remote_ui_bell; ui->visual_bell = remote_ui_visual_bell; ui->default_colors_set = remote_ui_default_colors_set; - ui->update_fg = remote_ui_update_fg; - ui->update_bg = remote_ui_update_bg; - ui->update_sp = remote_ui_update_sp; ui->flush = remote_ui_flush; ui->suspend = remote_ui_suspend; ui->set_title = remote_ui_set_title; @@ -102,16 +104,22 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); for (size_t i = 0; i < options.size; i++) { - ui_set_option(ui, options.items[i].key, options.items[i].value, err); + ui_set_option(ui, true, options.items[i].key, options.items[i].value, err); if (ERROR_SET(err)) { xfree(ui); return; } } + if (ui->ui_ext[kUIHlState]) { + ui->ui_ext[kUINewgrid] = true; + } + UIData *data = xmalloc(sizeof(UIData)); data->channel_id = channel_id; data->buffer = (Array)ARRAY_DICT_INIT; + data->hl_id = 0; + data->client_col = -1; ui->data = data; pmap_put(uint64_t)(connected_uis, channel_id, ui); @@ -173,13 +181,11 @@ void nvim_ui_set_option(uint64_t channel_id, String name, } UI *ui = pmap_get(uint64_t)(connected_uis, channel_id); - ui_set_option(ui, name, value, error); - if (!ERROR_SET(error)) { - ui_refresh(); - } + ui_set_option(ui, false, name, value, error); } -static void ui_set_option(UI *ui, String name, Object value, Error *error) +static void ui_set_option(UI *ui, bool init, String name, Object value, + Error *error) { if (strequal(name.data, "rgb")) { if (value.type != kObjectTypeBoolean) { @@ -187,40 +193,46 @@ static void ui_set_option(UI *ui, String name, Object value, Error *error) return; } ui->rgb = value.data.boolean; + // A little drastic, but only legacy uis need to use this option + if (!init) { + ui_refresh(); + } return; } + // LEGACY: Deprecated option, use `ext_cmdline` instead. + bool is_popupmenu = strequal(name.data, "popupmenu_external"); + for (UIExtension i = 0; i < kUIExtCount; i++) { - if (strequal(name.data, ui_ext_names[i])) { + if (strequal(name.data, ui_ext_names[i]) + || (i == kUIPopupmenu && is_popupmenu)) { if (value.type != kObjectTypeBoolean) { snprintf((char *)IObuff, IOSIZE, "%s must be a Boolean", - ui_ext_names[i]); + name.data); api_set_error(error, kErrorTypeValidation, (char *)IObuff); return; } - ui->ui_ext[i] = value.data.boolean; - return; - } - } - - if (strequal(name.data, "popupmenu_external")) { - // LEGACY: Deprecated option, use `ext_cmdline` instead. - if (value.type != kObjectTypeBoolean) { - api_set_error(error, kErrorTypeValidation, - "popupmenu_external must be a Boolean"); + bool boolval = value.data.boolean; + if (!init && i == kUINewgrid && boolval != ui->ui_ext[i]) { + // There shouldn't be a reason for an UI to do this ever + // so explicitly don't support this. + api_set_error(error, kErrorTypeValidation, + "ext_newgrid option cannot be changed"); + } + ui->ui_ext[i] = boolval; + if (!init) { + ui_set_ext_option(ui, i, boolval); + } return; } - ui->ui_ext[kUIPopupmenu] = value.data.boolean; - return; } api_set_error(error, kErrorTypeValidation, "No such UI option: %s", name.data); -#undef UI_EXT_OPTION } /// Pushes data into UI.UIData, to be consumed later by remote_ui_flush(). -static void push_call(UI *ui, char *name, Array args) +static void push_call(UI *ui, const char *name, Array args) { Array call = ARRAY_DICT_INIT; UIData *data = ui->data; @@ -242,27 +254,293 @@ static void push_call(UI *ui, char *name, Array args) kv_A(data->buffer, kv_size(data->buffer) - 1).data.array = call; } +static void remote_ui_grid_clear(UI *ui, Integer grid) +{ + Array args = ARRAY_DICT_INIT; + if (ui->ui_ext[kUINewgrid]) { + ADD(args, INTEGER_OBJ(grid)); + } + const char *name = ui->ui_ext[kUINewgrid] ? "grid_clear" : "clear"; + push_call(ui, name, args); +} -static void remote_ui_highlight_set(UI *ui, HlAttrs attrs) +static void remote_ui_grid_resize(UI *ui, Integer grid, + Integer width, Integer height) { Array args = ARRAY_DICT_INIT; + if (ui->ui_ext[kUINewgrid]) { + ADD(args, INTEGER_OBJ(grid)); + } + ADD(args, INTEGER_OBJ(width)); + ADD(args, INTEGER_OBJ(height)); + const char *name = ui->ui_ext[kUINewgrid] ? "grid_resize" : "resize"; + push_call(ui, name, args); +} + +static void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top, + Integer bot, Integer left, Integer right, + Integer rows, Integer cols) +{ + if (ui->ui_ext[kUINewgrid]) { + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(grid)); + ADD(args, INTEGER_OBJ(top)); + ADD(args, INTEGER_OBJ(bot)); + ADD(args, INTEGER_OBJ(left)); + ADD(args, INTEGER_OBJ(right)); + ADD(args, INTEGER_OBJ(rows)); + ADD(args, INTEGER_OBJ(cols)); + push_call(ui, "grid_scroll", args); + } else { + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(top)); + ADD(args, INTEGER_OBJ(bot-1)); + ADD(args, INTEGER_OBJ(left)); + ADD(args, INTEGER_OBJ(right-1)); + push_call(ui, "set_scroll_region", args); + + args = (Array)ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(rows)); + push_call(ui, "scroll", args); + } +} + +static void remote_ui_default_colors_set(UI *ui, Integer rgb_fg, + Integer rgb_bg, Integer rgb_sp, + Integer cterm_fg, Integer cterm_bg) +{ + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(rgb_fg)); + ADD(args, INTEGER_OBJ(rgb_bg)); + ADD(args, INTEGER_OBJ(rgb_sp)); + ADD(args, INTEGER_OBJ(cterm_fg)); + ADD(args, INTEGER_OBJ(cterm_bg)); + push_call(ui, "default_colors_set", args); + + // Deprecated + if (!ui->ui_ext[kUINewgrid]) { + args = (Array)ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(ui->rgb ? rgb_fg : cterm_fg - 1)); + push_call(ui, "update_fg", args); + + args = (Array)ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(ui->rgb ? rgb_bg : cterm_bg - 1)); + push_call(ui, "update_bg", args); + + args = (Array)ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(ui->rgb ? rgb_sp : -1)); + push_call(ui, "update_sp", args); + } +} + +static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, + HlAttrs cterm_attrs, Array info) +{ + if (!ui->ui_ext[kUINewgrid]) { + return; + } + Array args = ARRAY_DICT_INIT; + + ADD(args, INTEGER_OBJ(id)); + + Dictionary rgb_hl = hlattrs2dict(&rgb_attrs, true); + ADD(args, DICTIONARY_OBJ(rgb_hl)); + + Dictionary cterm_hl = hlattrs2dict(&cterm_attrs, false); + ADD(args, DICTIONARY_OBJ(cterm_hl)); + + if (ui->ui_ext[kUIHlState]) { + ADD(args, ARRAY_OBJ(copy_array(info))); + } else { + ADD(args, ARRAY_OBJ((Array)ARRAY_DICT_INIT)); + } + + push_call(ui, "hl_attr_define", args); +} + +static void remote_ui_highlight_set(UI *ui, int id) +{ + Array args = ARRAY_DICT_INIT; + UIData *data = ui->data; + + HlAttrs attrs = HLATTRS_INIT; + + if (data->hl_id == id) { + return; + } + data->hl_id = id; + + if (id != 0) { + HlAttrs *aep = syn_attr2entry(id); + if (aep) { + attrs = *aep; + } + } + Dictionary hl = hlattrs2dict(&attrs, ui->rgb); ADD(args, DICTIONARY_OBJ(hl)); push_call(ui, "highlight_set", args); } +/// "true" cursor used only for input focus +static void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row, + Integer col) +{ + if (ui->ui_ext[kUINewgrid]) { + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(grid)); + ADD(args, INTEGER_OBJ(row)); + ADD(args, INTEGER_OBJ(col)); + push_call(ui, "grid_cursor_goto", args); + } else { + UIData *data = ui->data; + data->cursor_row = row; + data->cursor_col = col; + remote_ui_cursor_goto(ui, row, col); + } +} + +/// emulated cursor used both for drawing and for input focus +static void remote_ui_cursor_goto(UI *ui, Integer row, Integer col) +{ + UIData *data = ui->data; + if (data->client_row == row && data->client_col == col) { + return; + } + data->client_row = row; + data->client_col = col; + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(row)); + ADD(args, INTEGER_OBJ(col)); + push_call(ui, "cursor_goto", args); +} + +static void remote_ui_put(UI *ui, const char *cell) +{ + UIData *data = ui->data; + data->client_col++; + Array args = ARRAY_DICT_INIT; + ADD(args, STRING_OBJ(cstr_to_string(cell))); + push_call(ui, "put", args); +} + +static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, + Integer startcol, Integer endcol, + Integer clearcol, Integer clearattr, + const schar_T *chunk, const sattr_T *attrs) +{ + UIData *data = ui->data; + if (ui->ui_ext[kUINewgrid]) { + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(grid)); + ADD(args, INTEGER_OBJ(row)); + ADD(args, INTEGER_OBJ(startcol)); + Array cells = ARRAY_DICT_INIT; + int repeat = 0; + size_t ncells = (size_t)(endcol-startcol); + int last_hl = -1; + for (size_t i = 0; i < ncells; i++) { + repeat++; + if (i == ncells-1 || attrs[i] != attrs[i+1] + || STRCMP(chunk[i], chunk[i+1])) { + Array cell = ARRAY_DICT_INIT; + ADD(cell, STRING_OBJ(cstr_to_string((const char *)chunk[i]))); + if (attrs[i] != last_hl || repeat > 1) { + ADD(cell, INTEGER_OBJ(attrs[i])); + last_hl = attrs[i]; + } + if (repeat > 1) { + ADD(cell, INTEGER_OBJ(repeat)); + } + ADD(cells, ARRAY_OBJ(cell)); + repeat = 0; + } + } + if (endcol < clearcol) { + Array cell = ARRAY_DICT_INIT; + ADD(cell, STRING_OBJ(cstr_to_string(" "))); + ADD(cell, INTEGER_OBJ(clearattr)); + ADD(cell, INTEGER_OBJ(clearcol-endcol)); + ADD(cells, ARRAY_OBJ(cell)); + } + ADD(args, ARRAY_OBJ(cells)); + + push_call(ui, "grid_line", args); + } else { + for (int i = 0; i < endcol-startcol; i++) { + remote_ui_cursor_goto(ui, row, startcol+i); + remote_ui_highlight_set(ui, attrs[i]); + remote_ui_put(ui, (const char *)chunk[i]); + if (utf_ambiguous_width(utf_ptr2char(chunk[i]))) { + data->client_col = -1; // force cursor update + } + } + if (endcol < clearcol) { + remote_ui_cursor_goto(ui, row, endcol); + remote_ui_highlight_set(ui, (int)clearattr); + // legacy eol_clear was only ever used with cleared attributes + // so be on the safe side + if (clearattr == 0 && clearcol == Columns) { + Array args = ARRAY_DICT_INIT; + push_call(ui, "eol_clear", args); + } else { + for (Integer c = endcol; c < clearcol; c++) { + remote_ui_put(ui, " "); + } + } + } + } +} + static void remote_ui_flush(UI *ui) { UIData *data = ui->data; if (data->buffer.size > 0) { + if (!ui->ui_ext[kUINewgrid]) { + remote_ui_cursor_goto(ui, data->cursor_row, data->cursor_col); + } rpc_send_event(data->channel_id, "redraw", data->buffer); data->buffer = (Array)ARRAY_DICT_INIT; } } +static void remote_ui_cmdline_show(UI *ui, Array args) +{ + Array new_args = ARRAY_DICT_INIT; + Array contents = args.items[0].data.array; + Array new_contents = ARRAY_DICT_INIT; + for (size_t i = 0; i < contents.size; i++) { + Array item = contents.items[i].data.array; + Array new_item = ARRAY_DICT_INIT; + int attr = (int)item.items[0].data.integer; + if (attr) { + HlAttrs *aep = syn_attr2entry(attr); + Dictionary rgb_attrs = hlattrs2dict(aep, ui->rgb ? kTrue : kFalse); + ADD(new_item, DICTIONARY_OBJ(rgb_attrs)); + } else { + ADD(new_item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT)); + } + ADD(new_item, copy_object(item.items[1])); + ADD(new_contents, ARRAY_OBJ(new_item)); + } + ADD(new_args, ARRAY_OBJ(new_contents)); + for (size_t i = 1; i < args.size; i++) { + ADD(new_args, copy_object(args.items[i])); + } + push_call(ui, "cmdline_show", new_args); +} + static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed) { + if (!ui->ui_ext[kUINewgrid]) { + // the representation of cmdline_show changed, translate back + if (strequal(name, "cmdline_show")) { + remote_ui_cmdline_show(ui, args); + // never consumes args + return; + } + } Array my_args = ARRAY_DICT_INIT; // Objects are currently single-reference // make a copy, but only if necessary diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 3ef16a7ac3..456ad0c8cc 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -10,14 +10,6 @@ #include "nvim/func_attr.h" #include "nvim/ui.h" -void resize(Integer width, Integer height) - FUNC_API_SINCE(3); -void clear(void) - FUNC_API_SINCE(3); -void eol_clear(void) - FUNC_API_SINCE(3); -void cursor_goto(Integer row, Integer col) - FUNC_API_SINCE(3); void mode_info_set(Boolean enabled, Array cursor_styles) FUNC_API_SINCE(3); void update_menu(void) @@ -32,29 +24,12 @@ void mouse_off(void) FUNC_API_SINCE(3); void mode_change(String mode, Integer mode_idx) FUNC_API_SINCE(3); -void set_scroll_region(Integer top, Integer bot, Integer left, Integer right) - FUNC_API_SINCE(3); -void scroll(Integer count) - FUNC_API_SINCE(3); -void highlight_set(HlAttrs attrs) - FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; -void put(String str) - FUNC_API_SINCE(3); void bell(void) FUNC_API_SINCE(3); void visual_bell(void) FUNC_API_SINCE(3); void flush(void) FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL; -void update_fg(Integer fg) - FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; -void update_bg(Integer bg) - FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; -void update_sp(Integer sp) - FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; -void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, - Integer cterm_fg, Integer cterm_bg) - FUNC_API_SINCE(4); void suspend(void) FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; void set_title(String title) @@ -64,6 +39,49 @@ void set_icon(String icon) void option_set(String name, Object value) FUNC_API_SINCE(4) FUNC_API_BRIDGE_IMPL; +// First revison of the grid protocol, used by default +void update_fg(Integer fg) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void update_bg(Integer bg) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void update_sp(Integer sp) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void resize(Integer width, Integer height) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void clear(void) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void eol_clear(void) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void cursor_goto(Integer row, Integer col) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void highlight_set(HlAttrs attrs) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY FUNC_API_REMOTE_IMPL; +void put(String str) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void set_scroll_region(Integer top, Integer bot, Integer left, Integer right) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void scroll(Integer count) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; + +// Second revison of the grid protocol, used with ext_newgrid ui option +void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, + Integer cterm_fg, Integer cterm_bg) + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; +void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, + Array info) + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; +void grid_resize(Integer grid, Integer width, Integer height) + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; +void grid_clear(Integer grid) + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; +void grid_cursor_goto(Integer grid, Integer row, Integer col) + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; +void grid_line(Integer grid, Integer row, Integer col_start, Array data) + FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY; +void grid_scroll(Integer grid, Integer top, Integer bot, + Integer left, Integer right, Integer rows, Integer cols) + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; + void popupmenu_show(Array items, Integer selected, Integer row, Integer col) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void popupmenu_hide(void) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 03567ddfd8..1ffae8ef43 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -21,6 +21,7 @@ #include "nvim/vim.h" #include "nvim/buffer.h" #include "nvim/file_search.h" +#include "nvim/highlight.h" #include "nvim/window.h" #include "nvim/types.h" #include "nvim/ex_docmd.h" @@ -1850,3 +1851,22 @@ Object nvim_get_proc(Integer pid, Error *err) #endif return rvobj; } + +/// NB: if your UI doesn't use hlstate, this will not return hlstate first time +Array nvim__inspect_cell(Integer row, Integer col, Error *err) +{ + Array ret = ARRAY_DICT_INIT; + if (row < 0 || row >= screen_Rows + || col < 0 || col >= screen_Columns) { + return ret; + } + size_t off = LineOffset[(size_t)row] + (size_t)col; + ADD(ret, STRING_OBJ(cstr_to_string((char *)ScreenLines[off]))); + int attr = ScreenAttrs[off]; + ADD(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, err))); + // will not work first time + if (!highlight_use_hlstate()) { + ADD(ret, ARRAY_OBJ(hl_inspect(attr))); + } + return ret; +} diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 81bbc56eb9..4152c16588 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -45,6 +45,7 @@ #include "nvim/fold.h" #include "nvim/getchar.h" #include "nvim/hashtab.h" +#include "nvim/highlight.h" #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/main.h" diff --git a/src/nvim/event/defs.h b/src/nvim/event/defs.h index cc875d74b9..55b2d277bb 100644 --- a/src/nvim/event/defs.h +++ b/src/nvim/event/defs.h @@ -4,7 +4,7 @@ #include <assert.h> #include <stdarg.h> -#define EVENT_HANDLER_MAX_ARGC 6 +#define EVENT_HANDLER_MAX_ARGC 9 typedef void (*argv_callback)(void **argv); typedef struct message { diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 115df815c6..3c6a8b1074 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -34,6 +34,7 @@ #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" +#include "nvim/highlight.h" #include "nvim/indent.h" #include "nvim/buffer_updates.h" #include "nvim/main.h" diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index f07bc0e137..120278d3fb 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -2693,14 +2693,27 @@ void ex_packloadall(exarg_T *eap) /// ":packadd[!] {name}" void ex_packadd(exarg_T *eap) { - static const char *plugpat = "pack/*/opt/%s"; // NOLINT - - size_t len = STRLEN(plugpat) + STRLEN(eap->arg); - char *pat = (char *)xmallocz(len); - vim_snprintf(pat, len, plugpat, eap->arg); - do_in_path(p_pp, (char_u *)pat, DIP_ALL + DIP_DIR + DIP_ERR, add_pack_plugin, - eap->forceit ? &APP_ADD_DIR : &APP_BOTH); - xfree(pat); + static const char *plugpat = "pack/*/%s/%s"; // NOLINT + int res = OK; + + // Round 1: use "start", round 2: use "opt". + for (int round = 1; round <= 2; round++) { + // Only look under "start" when loading packages wasn't done yet. + if (round == 1 && did_source_packages) { + continue; + } + + const size_t len = STRLEN(plugpat) + STRLEN(eap->arg) + 5; + char *pat = xmallocz(len); + vim_snprintf(pat, len, plugpat, round == 1 ? "start" : "opt", eap->arg); + // The first round don't give a "not found" error, in the second round + // only when nothing was found in the first round. + res = do_in_path(p_pp, (char_u *)pat, + DIP_ALL + DIP_DIR + + (round == 2 && res == FAIL ? DIP_ERR : 0), + add_pack_plugin, eap->forceit ? &APP_ADD_DIR : &APP_BOTH); + xfree(pat); + } } /// ":options" diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index c1b9eff697..b077aefa1e 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -6320,8 +6320,10 @@ static void ex_stop(exarg_T *eap) autowrite_all(); } apply_autocmds(EVENT_VIMSUSPEND, NULL, NULL, false, NULL); + + // TODO(bfredl): the TUI should do this on suspend ui_cursor_goto((int)Rows - 1, 0); - ui_linefeed(); + ui_call_grid_scroll(1, 0, Rows, 0, Columns, 1, 0); ui_flush(); ui_call_suspend(); // call machine specific function diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 4b9ef5d819..775d002e58 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -31,6 +31,7 @@ #include "nvim/fileio.h" #include "nvim/func_attr.h" #include "nvim/getchar.h" +#include "nvim/highlight.h" #include "nvim/if_cscope.h" #include "nvim/indent.h" #include "nvim/main.h" @@ -214,6 +215,8 @@ static int hislen = 0; /* actual length of history tables */ /// user interrupting highlight function to not interrupt command-line. static bool getln_interrupted_highlight = false; +static bool need_cursor_update = false; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_getln.c.generated.h" @@ -2943,30 +2946,22 @@ static void ui_ext_cmdline_show(CmdlineInfo *line) char *buf = xmallocz(len); memset(buf, '*', len); Array item = ARRAY_DICT_INIT; - ADD(item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT)); + ADD(item, INTEGER_OBJ(0)); ADD(item, STRING_OBJ(((String) { .data = buf, .size = len }))); ADD(content, ARRAY_OBJ(item)); } else if (kv_size(line->last_colors.colors)) { for (size_t i = 0; i < kv_size(line->last_colors.colors); i++) { CmdlineColorChunk chunk = kv_A(line->last_colors.colors, i); Array item = ARRAY_DICT_INIT; + ADD(item, INTEGER_OBJ(chunk.attr)); - if (chunk.attr) { - HlAttrs *aep = syn_cterm_attr2entry(chunk.attr); - // TODO(bfredl): this desicion could be delayed by making attr_code a - // recognized type - Dictionary rgb_attrs = hlattrs2dict(aep, true); - ADD(item, DICTIONARY_OBJ(rgb_attrs)); - } else { - ADD(item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT)); - } ADD(item, STRING_OBJ(cbuf_to_string((char *)line->cmdbuff + chunk.start, chunk.end-chunk.start))); ADD(content, ARRAY_OBJ(item)); } } else { Array item = ARRAY_DICT_INIT; - ADD(item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT)); + ADD(item, INTEGER_OBJ(0)); ADD(item, STRING_OBJ(cstr_to_string((char *)(line->cmdbuff)))); ADD(content, ARRAY_OBJ(item)); } @@ -3032,6 +3027,8 @@ void cmdline_screen_cleared(void) } prev_ccline = prev_ccline->prev_ccline; } + + need_cursor_update = true; } /// called by ui_flush, do what redraws neccessary to keep cmdline updated. @@ -3500,6 +3497,10 @@ static void cursorcmd(void) if (ccline.redraw_state < kCmdRedrawPos) { ccline.redraw_state = kCmdRedrawPos; } + if (need_cursor_update) { + need_cursor_update = false; + setcursor(); + } return; } diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua index 2666ca6e6f..e76b601d8a 100644 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -132,19 +132,21 @@ for i = 1, #events do end end - call_output:write('void ui_call_'..ev.name) - write_signature(call_output, ev, '') - call_output:write('\n{\n') - if ev.remote_only then - write_arglist(call_output, ev, false) - call_output:write(' UI_LOG('..ev.name..', 0);\n') - call_output:write(' ui_event("'..ev.name..'", args);\n') - else - call_output:write(' UI_CALL') - write_signature(call_output, ev, ev.name, true) - call_output:write(";\n") + if not (ev.remote_only and ev.remote_impl) then + call_output:write('void ui_call_'..ev.name) + write_signature(call_output, ev, '') + call_output:write('\n{\n') + if ev.remote_only then + write_arglist(call_output, ev, false) + call_output:write(' UI_LOG('..ev.name..', 0);\n') + call_output:write(' ui_event("'..ev.name..'", args);\n') + else + call_output:write(' UI_CALL') + write_signature(call_output, ev, ev.name, true) + call_output:write(";\n") + end + call_output:write("}\n\n") end - call_output:write("}\n\n") end diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c index b3a9eabdb8..e1eb8f0251 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -2882,17 +2882,13 @@ void mch_print_start_line(int margin, int page_line) prt_half_width = FALSE; } -int mch_print_text_out(char_u *p, size_t len) +int mch_print_text_out(char_u *const textp, size_t len) { - int need_break; + char_u *p = textp; char_u ch; char_u ch_buff[8]; - double char_width; - double next_pos; - int in_ascii; - int half_width; - - char_width = prt_char_width; + char_u *tofree = NULL; + double char_width = prt_char_width; /* Ideally VIM would create a rearranged CID font to combine a Roman and * CJKV font to do what VIM is doing here - use a Roman font for characters @@ -2902,7 +2898,7 @@ int mch_print_text_out(char_u *p, size_t len) * years! If they ever do, a lot of this code will disappear. */ if (prt_use_courier) { - in_ascii = (len == 1 && *p < 0x80); + const bool in_ascii = (len == 1 && *p < 0x80); if (prt_in_ascii) { if (!in_ascii) { /* No longer in ASCII range - need to switch font */ @@ -2918,9 +2914,10 @@ int mch_print_text_out(char_u *p, size_t len) } } if (prt_out_mbyte) { - half_width = ((*mb_ptr2cells)(p) == 1); - if (half_width) + const bool half_width = ((*mb_ptr2cells)(p) == 1); + if (half_width) { char_width /= 2; + } if (prt_half_width) { if (!half_width) { prt_half_width = FALSE; @@ -2993,23 +2990,24 @@ int mch_print_text_out(char_u *p, size_t len) } if (prt_do_conv) { - /* Convert from multi-byte to 8-bit encoding */ - p = string_convert(&prt_conv, p, &len); - if (p == NULL) - p = (char_u *)xstrdup(""); + // Convert from multi-byte to 8-bit encoding + tofree = p = string_convert(&prt_conv, p, &len); + if (p == NULL) { + p = (char_u *)""; + len = 0; + } } if (prt_out_mbyte) { - /* Multi-byte character strings are represented more efficiently as hex - * strings when outputting clean 8 bit PS. - */ - do { + // Multi-byte character strings are represented more efficiently as hex + // strings when outputting clean 8 bit PS. + while (len-- > 0) { ch = prt_hexchar[(unsigned)(*p) >> 4]; ga_append(&prt_ps_buffer, (char)ch); ch = prt_hexchar[(*p) & 0xf]; ga_append(&prt_ps_buffer, (char)ch); p++; - } while (--len); + } } else { /* Add next character to buffer of characters to output. * Note: One printed character may require several PS characters to @@ -3043,20 +3041,20 @@ int mch_print_text_out(char_u *p, size_t len) ga_append(&prt_ps_buffer, (char)ch); } - /* Need to free any translated characters */ - if (prt_do_conv) - xfree(p); + // Need to free any translated characters + xfree(tofree); prt_text_run += char_width; prt_pos_x += char_width; // The downside of fp - use relative error on right margin check - next_pos = prt_pos_x + prt_char_width; - need_break = ((next_pos > prt_right_margin) - && ((next_pos - prt_right_margin) > (prt_right_margin * 1e-5))); + const double next_pos = prt_pos_x + prt_char_width; + const bool need_break = (next_pos > prt_right_margin) + && ((next_pos - prt_right_margin) > (prt_right_margin * 1e-5)); - if (need_break) + if (need_break) { prt_flush_buffer(); + } return need_break; } diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c new file mode 100644 index 0000000000..0b39ba442e --- /dev/null +++ b/src/nvim/highlight.c @@ -0,0 +1,418 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// highlight.c: low level code for UI and syntax highlighting + +#include "nvim/vim.h" +#include "nvim/highlight.h" +#include "nvim/highlight_defs.h" +#include "nvim/map.h" +#include "nvim/screen.h" +#include "nvim/syntax.h" +#include "nvim/ui.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "highlight.c.generated.h" +#endif + +static bool hlstate_active = false; + +static kvec_t(HlEntry) attr_entries = KV_INITIAL_VALUE; + +static Map(HlEntry, int) *attr_entry_ids; +static Map(int, int) *combine_attr_entries; + +void highlight_init(void) +{ + attr_entry_ids = map_new(HlEntry, int)(); + combine_attr_entries = map_new(int, int)(); + + // index 0 is no attribute, add dummy entry: + kv_push(attr_entries, ((HlEntry){ .attr = HLATTRS_INIT, .kind = kHlUnknown, + .id1 = 0, .id2 = 0 })); +} + +/// @return TRUE if hl table was reset +bool highlight_use_hlstate(void) +{ + if (hlstate_active) { + return false; + } + hlstate_active = true; + // hl tables must now be rebuilt. + clear_hl_tables(true); + return true; +} + +/// Return the attr number for a set of colors and font, and optionally +/// a semantic description (see ext_hlstate documentation). +/// Add a new entry to the attr_entries array if the combination is new. +/// @return 0 for error. +static int get_attr_entry(HlEntry entry) +{ + if (!hlstate_active) { + // This information will not be used, erase it and reduce the table size. + entry.kind = kHlUnknown; + entry.id1 = 0; + entry.id2 = 0; + } + + int id = map_get(HlEntry, int)(attr_entry_ids, entry); + if (id > 0) { + return id; + } + + static bool recursive = false; + if (kv_size(attr_entries) > MAX_TYPENR) { + // Running out of attribute entries! remove all attributes, and + // compute new ones for all groups. + // When called recursively, we are really out of numbers. + if (recursive) { + EMSG(_("E424: Too many different highlighting attributes in use")); + return 0; + } + recursive = true; + + clear_hl_tables(true); + + recursive = false; + if (entry.kind == kHlCombine) { + // This entry is now invalid, don't put it + return 0; + } + } + + id = (int)kv_size(attr_entries); + kv_push(attr_entries, entry); + + map_put(HlEntry, int)(attr_entry_ids, entry, id); + + Array inspect = hl_inspect(id); + + // Note: internally we don't distinguish between cterm and rgb attributes, + // remote_ui_hl_attr_define will however. + ui_call_hl_attr_define(id, entry.attr, entry.attr, inspect); + api_free_array(inspect); + return id; +} + +/// When a UI connects, we need to send it the table of higlights used so far. +void ui_send_all_hls(UI *ui) +{ + for (size_t i = 1; i < kv_size(attr_entries); i++) { + Array inspect = hl_inspect((int)i); + ui->hl_attr_define(ui, (Integer)i, kv_A(attr_entries, i).attr, + kv_A(attr_entries, i).attr, inspect); + api_free_array(inspect); + } +} + +/// Get attribute code for a syntax group. +int hl_get_syn_attr(int idx, HlAttrs at_en) +{ + // TODO(bfredl): should we do this unconditionally + if (at_en.cterm_fg_color != 0 || at_en.cterm_bg_color != 0 + || at_en.rgb_fg_color != -1 || at_en.rgb_bg_color != -1 + || at_en.rgb_sp_color != -1 || at_en.cterm_ae_attr != 0 + || at_en.rgb_ae_attr != 0) { + return get_attr_entry((HlEntry){ .attr = at_en, .kind = kHlSyntax, + .id1 = idx, .id2 = 0 }); + } else { + // If all the fields are cleared, clear the attr field back to default value + return 0; + } +} + +/// Get attribute code for a builtin highlight group. +/// +/// The final syntax group could be modified by hi-link or 'winhighlight'. +int hl_get_ui_attr(int idx, int final_id, bool optional) +{ + HlAttrs attrs = HLATTRS_INIT; + bool available = false; + + int syn_attr = syn_id2attr(final_id); + if (syn_attr != 0) { + HlAttrs *aep = syn_attr2entry(syn_attr); + if (aep) { + attrs = *aep; + available = true; + } + } + if (optional && !available) { + return 0; + } + return get_attr_entry((HlEntry){ .attr = attrs, .kind = kHlUI, + .id1 = idx, .id2 = final_id }); +} + +void update_window_hl(win_T *wp, bool invalid) +{ + if (!wp->w_hl_needs_update && !invalid) { + return; + } + wp->w_hl_needs_update = false; + + // determine window specific background set in 'winhighlight' + if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] > 0) { + wp->w_hl_attr_normal = hl_get_ui_attr(HLF_INACTIVE, + wp->w_hl_ids[HLF_INACTIVE], true); + } else if (wp->w_hl_id_normal > 0) { + wp->w_hl_attr_normal = hl_get_ui_attr(-1, wp->w_hl_id_normal, true); + } else { + wp->w_hl_attr_normal = 0; + } + if (wp != curwin) { + wp->w_hl_attr_normal = hl_combine_attr(HL_ATTR(HLF_INACTIVE), + wp->w_hl_attr_normal); + } + + for (int hlf = 0; hlf < (int)HLF_COUNT; hlf++) { + int attr; + if (wp->w_hl_ids[hlf] > 0) { + attr = hl_get_ui_attr(hlf, wp->w_hl_ids[hlf], false); + } else { + attr = HL_ATTR(hlf); + } + wp->w_hl_attrs[hlf] = attr; + } +} + +/// Get attribute code for forwarded :terminal highlights. +int get_term_attr_entry(HlAttrs *aep) +{ + return get_attr_entry((HlEntry){ .attr= *aep, .kind = kHlTerminal, + .id1 = 0, .id2 = 0 }); +} + +/// Clear all highlight tables. +void clear_hl_tables(bool reinit) +{ + if (reinit) { + kv_size(attr_entries) = 1; + map_clear(HlEntry, int)(attr_entry_ids); + map_clear(int, int)(combine_attr_entries); + highlight_attr_set_all(); + highlight_changed(); + redraw_all_later(NOT_VALID); + if (ScreenAttrs) { + // the meaning of 0 doesn't change anyway + // but the rest must be retransmitted + memset(ScreenAttrs, 0, + sizeof(*ScreenAttrs) * (size_t)(screen_Rows * screen_Columns)); + } + } else { + kv_destroy(attr_entries); + map_free(HlEntry, int)(attr_entry_ids); + map_free(int, int)(combine_attr_entries); + } +} + +// Combine special attributes (e.g., for spelling) with other attributes +// (e.g., for syntax highlighting). +// "prim_attr" overrules "char_attr". +// This creates a new group when required. +// Since we expect there to be few spelling mistakes we don't cache the +// result. +// Return the resulting attributes. +int hl_combine_attr(int char_attr, int prim_attr) +{ + if (char_attr == 0) { + return prim_attr; + } else if (prim_attr == 0) { + return char_attr; + } + + // TODO(bfredl): could use a struct for clearer intent. + int combine_tag = (char_attr << 16) + prim_attr; + int id = map_get(int, int)(combine_attr_entries, combine_tag); + if (id > 0) { + return id; + } + + HlAttrs *char_aep, *spell_aep; + HlAttrs new_en = HLATTRS_INIT; + + + // Find the entry for char_attr + char_aep = syn_attr2entry(char_attr); + + if (char_aep != NULL) { + // Copy all attributes from char_aep to the new entry + new_en = *char_aep; + } + + spell_aep = syn_attr2entry(prim_attr); + if (spell_aep != NULL) { + new_en.cterm_ae_attr |= spell_aep->cterm_ae_attr; + new_en.rgb_ae_attr |= spell_aep->rgb_ae_attr; + + if (spell_aep->cterm_fg_color > 0) { + new_en.cterm_fg_color = spell_aep->cterm_fg_color; + } + + if (spell_aep->cterm_bg_color > 0) { + new_en.cterm_bg_color = spell_aep->cterm_bg_color; + } + + if (spell_aep->rgb_fg_color >= 0) { + new_en.rgb_fg_color = spell_aep->rgb_fg_color; + } + + if (spell_aep->rgb_bg_color >= 0) { + new_en.rgb_bg_color = spell_aep->rgb_bg_color; + } + + if (spell_aep->rgb_sp_color >= 0) { + new_en.rgb_sp_color = spell_aep->rgb_sp_color; + } + } + + id = get_attr_entry((HlEntry){ .attr = new_en, .kind = kHlCombine, + .id1 = char_attr, .id2 = prim_attr }); + if (id > 0) { + map_put(int, int)(combine_attr_entries, combine_tag, id); + } + + return id; +} + +/// Get highlight attributes for a attribute code +HlAttrs *syn_attr2entry(int attr) +{ + if (attr <= 0 || attr >= (int)kv_size(attr_entries)) { + // invalid attribute code, or the tables were cleared + return NULL; + } + return &(kv_A(attr_entries, attr).attr); +} + +/// Gets highlight description for id `attr_id` as a map. +Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Error *err) +{ + HlAttrs *aep = NULL; + Dictionary dic = ARRAY_DICT_INIT; + + if (attr_id == 0) { + return dic; + } + + aep = syn_attr2entry((int)attr_id); + if (!aep) { + api_set_error(err, kErrorTypeException, + "Invalid attribute id: %" PRId64, attr_id); + return dic; + } + + return hlattrs2dict(aep, rgb); +} + +/// Converts an HlAttrs into Dictionary +/// +/// @param[in] aep data to convert +/// @param use_rgb use 'gui*' settings if true, else resorts to 'cterm*' +Dictionary hlattrs2dict(const HlAttrs *aep, bool use_rgb) +{ + assert(aep); + Dictionary hl = ARRAY_DICT_INIT; + int mask = use_rgb ? aep->rgb_ae_attr : aep->cterm_ae_attr; + + if (mask & HL_BOLD) { + PUT(hl, "bold", BOOLEAN_OBJ(true)); + } + + if (mask & HL_STANDOUT) { + PUT(hl, "standout", BOOLEAN_OBJ(true)); + } + + if (mask & HL_UNDERLINE) { + PUT(hl, "underline", BOOLEAN_OBJ(true)); + } + + if (mask & HL_UNDERCURL) { + PUT(hl, "undercurl", BOOLEAN_OBJ(true)); + } + + if (mask & HL_ITALIC) { + PUT(hl, "italic", BOOLEAN_OBJ(true)); + } + + if (mask & HL_INVERSE) { + PUT(hl, "reverse", BOOLEAN_OBJ(true)); + } + + if (use_rgb) { + if (aep->rgb_fg_color != -1) { + PUT(hl, "foreground", INTEGER_OBJ(aep->rgb_fg_color)); + } + + if (aep->rgb_bg_color != -1) { + PUT(hl, "background", INTEGER_OBJ(aep->rgb_bg_color)); + } + + if (aep->rgb_sp_color != -1) { + PUT(hl, "special", INTEGER_OBJ(aep->rgb_sp_color)); + } + } else { + if (cterm_normal_fg_color != aep->cterm_fg_color) { + PUT(hl, "foreground", INTEGER_OBJ(aep->cterm_fg_color - 1)); + } + + if (cterm_normal_bg_color != aep->cterm_bg_color) { + PUT(hl, "background", INTEGER_OBJ(aep->cterm_bg_color - 1)); + } + } + + return hl; +} + +Array hl_inspect(int attr) +{ + Array ret = ARRAY_DICT_INIT; + if (hlstate_active) { + hl_inspect_impl(&ret, attr); + } + return ret; +} + +static void hl_inspect_impl(Array *arr, int attr) +{ + Dictionary item = ARRAY_DICT_INIT; + if (attr <= 0 || attr >= (int)kv_size(attr_entries)) { + return; + } + + HlEntry e = kv_A(attr_entries, attr); + switch (e.kind) { + case kHlSyntax: + PUT(item, "kind", STRING_OBJ(cstr_to_string("syntax"))); + PUT(item, "hi_name", + STRING_OBJ(cstr_to_string((char *)syn_id2name(e.id1)))); + break; + + case kHlUI: + PUT(item, "kind", STRING_OBJ(cstr_to_string("ui"))); + const char *ui_name = (e.id1 == -1) ? "Normal" : hlf_names[e.id1]; + PUT(item, "ui_name", STRING_OBJ(cstr_to_string(ui_name))); + PUT(item, "hi_name", + STRING_OBJ(cstr_to_string((char *)syn_id2name(e.id2)))); + break; + + case kHlTerminal: + PUT(item, "kind", STRING_OBJ(cstr_to_string("term"))); + break; + + case kHlCombine: + // attribute combination is associative, so flatten to an array + hl_inspect_impl(arr, e.id1); + hl_inspect_impl(arr, e.id2); + return; + + case kHlUnknown: + return; + } + PUT(item, "id", INTEGER_OBJ(attr)); + ADD(*arr, DICTIONARY_OBJ(item)); +} diff --git a/src/nvim/highlight.h b/src/nvim/highlight.h new file mode 100644 index 0000000000..6be0d6200b --- /dev/null +++ b/src/nvim/highlight.h @@ -0,0 +1,13 @@ +#ifndef NVIM_HIGHLIGHT_H +#define NVIM_HIGHLIGHT_H + +#include <stdbool.h> +#include "nvim/highlight_defs.h" +#include "nvim/api/private/defs.h" +#include "nvim/ui.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "highlight.h.generated.h" +#endif + +#endif // NVIM_HIGHLIGHT_H diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 3518c8bdcc..09d20c75ea 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -8,6 +8,8 @@ typedef int32_t RgbValue; /// Highlighting attribute bits. +/// +/// sign bit should not be used here, as it identifies invalid highlight typedef enum { HL_INVERSE = 0x01, HL_BOLD = 0x02, @@ -35,6 +37,17 @@ typedef struct attr_entry { .cterm_bg_color = 0, \ } +// sentinel value that compares unequal to any valid highlight +#define HLATTRS_INVALID (HlAttrs) { \ + .rgb_ae_attr = -1, \ + .cterm_ae_attr = -1, \ + .rgb_fg_color = -1, \ + .rgb_bg_color = -1, \ + .rgb_sp_color = -1, \ + .cterm_fg_color = 0, \ + .cterm_bg_color = 0, \ +} + /// Values for index in highlight_attr[]. /// When making changes, also update hlf_names below! typedef enum { @@ -152,4 +165,19 @@ EXTERN RgbValue normal_fg INIT(= -1); EXTERN RgbValue normal_bg INIT(= -1); EXTERN RgbValue normal_sp INIT(= -1); +typedef enum { + kHlUnknown, + kHlUI, + kHlSyntax, + kHlTerminal, + kHlCombine, +} HlKind; + +typedef struct { + HlAttrs attr; + HlKind kind; + int id1; + int id2; +} HlEntry; + #endif // NVIM_HIGHLIGHT_DEFS_H diff --git a/src/nvim/lib/kvec.h b/src/nvim/lib/kvec.h index 6d54c7f78d..93b2f053bc 100644 --- a/src/nvim/lib/kvec.h +++ b/src/nvim/lib/kvec.h @@ -98,14 +98,14 @@ (*kv_pushp(v) = (x)) #define kv_a(v, i) \ - (((v).capacity <= (size_t) (i) \ + (*(((v).capacity <= (size_t) (i) \ ? ((v).capacity = (v).size = (i) + 1, \ kv_roundup32((v).capacity), \ - kv_resize((v), (v).capacity), 0) \ + kv_resize((v), (v).capacity), 0UL) \ : ((v).size <= (size_t) (i) \ ? (v).size = (i) + 1 \ - : 0)), \ - (v).items[(i)]) + : 0UL)), \ + &(v).items[(i)])) /// Type of a vector with a few first members allocated on stack /// diff --git a/src/nvim/main.c b/src/nvim/main.c index 6b382ae320..96c2168bca 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -23,6 +23,7 @@ #include "nvim/fold.h" #include "nvim/getchar.h" #include "nvim/hashtab.h" +#include "nvim/highlight.h" #include "nvim/iconv.h" #include "nvim/if_cscope.h" #ifdef HAVE_LOCALE_H @@ -182,6 +183,7 @@ void early_init(void) eval_init(); // init global variables init_path(argv0 ? argv0 : "nvim"); init_normal_cmds(); // Init the table of Normal mode commands. + highlight_init(); #if defined(HAVE_LOCALE_H) // Setup to use the current locale (for ctype() and many other things). @@ -452,7 +454,6 @@ int main(int argc, char **argv) } setmouse(); // may start using the mouse - ui_reset_scroll_region(); // In case Rows changed if (exmode_active) { must_redraw = CLEAR; // Don't clear the screen when starting in Ex mode. @@ -1372,7 +1373,7 @@ static void handle_quickfix(mparm_T *paramp) paramp->use_ef, OPT_FREE, SID_CARG); vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef); if (qf_init(NULL, p_ef, p_efm, true, IObuff, p_menc) < 0) { - ui_linefeed(); + msg_putchar('\n'); mch_exit(3); } TIME_MSG("reading errorfile"); diff --git a/src/nvim/map.c b/src/nvim/map.c index 537b6751e2..cc264f3729 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -140,6 +140,22 @@ static inline bool String_eq(String a, String b) return memcmp(a.data, b.data, a.size) == 0; } +static inline khint_t HlEntry_hash(HlEntry ae) +{ + const uint8_t *data = (const uint8_t *)&ae; + khint_t h = 0; + for (size_t i = 0; i < sizeof(ae); i++) { + h = (h << 5) - h + data[i]; + } + return h; +} + +static inline bool HlEntry_eq(HlEntry ae1, HlEntry ae2) +{ + return memcmp(&ae1, &ae2, sizeof(ae1)) == 0; +} + + MAP_IMPL(int, int, DEFAULT_INITIALIZER) MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER) @@ -149,3 +165,4 @@ MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER) #define MSGPACK_HANDLER_INITIALIZER { .fn = NULL, .async = false } MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER) #define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL } +MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER) diff --git a/src/nvim/map.h b/src/nvim/map.h index ac1239a548..65204a798b 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -7,6 +7,7 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/dispatch.h" #include "nvim/bufhl_defs.h" +#include "nvim/highlight_defs.h" #if defined(__NetBSD__) # undef uint64_t @@ -35,6 +36,7 @@ MAP_DECLS(ptr_t, ptr_t) MAP_DECLS(uint64_t, ptr_t) MAP_DECLS(handle_T, ptr_t) MAP_DECLS(String, MsgpackRpcRequestHandler) +MAP_DECLS(HlEntry, int) #define map_new(T, U) map_##T##_##U##_new #define map_free(T, U) map_##T##_##U##_free diff --git a/src/nvim/memory.c b/src/nvim/memory.c index b2aef13946..d3d0968a5c 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -10,6 +10,7 @@ #include "nvim/vim.h" #include "nvim/eval.h" +#include "nvim/highlight.h" #include "nvim/memfile.h" #include "nvim/memory.h" #include "nvim/message.h" @@ -696,7 +697,7 @@ void free_all_mem(void) /* screenlines (can't display anything now!) */ free_screenlines(); - clear_hl_tables(); + clear_hl_tables(false); list_free_log(); } diff --git a/src/nvim/message.c b/src/nvim/message.c index 9d4d421941..46fc9115b4 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1888,11 +1888,9 @@ static void msg_scroll_up(void) fill_msgsep, fill_msgsep, HL_ATTR(HLF_MSGSEP)); } int nscroll = MIN(msg_scrollsize()+1, Rows); - ui_call_set_scroll_region(Rows-nscroll, Rows-1, 0, Columns-1); - screen_del_lines(Rows-nscroll, 0, 1, nscroll, NULL); - ui_reset_scroll_region(); + screen_del_lines(Rows-nscroll, 1, Rows, 0, Columns); } else { - screen_del_lines(0, 0, 1, (int)Rows, NULL); + screen_del_lines(0, 1, (int)Rows, 0, Columns); } } @@ -2307,9 +2305,9 @@ static int do_more_prompt(int typed_char) mp_last = msg_sb_start(mp_last->sb_prev); } - if (toscroll == -1 && screen_ins_lines(0, 0, 1, - (int)Rows, NULL) == OK) { - /* display line at top */ + if (toscroll == -1 + && screen_ins_lines(0, 1, (int)Rows, 0, (int)Columns) == OK) { + // display line at top (void)disp_sb_line(0, mp); } else { /* redisplay all lines */ diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index 3d7399f151..684f486c04 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -2714,7 +2714,7 @@ int call_shell(char_u *cmd, ShellOpts opts, char_u *extra_shell_arg) if (p_verbose > 3) { verbose_enter(); smsg(_("Calling shell to execute: \"%s\""), cmd == NULL ? p_sh : cmd); - ui_linefeed(); + msg_putchar('\n'); verbose_leave(); } diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 1182d3d902..5bd4b4ddff 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -341,6 +341,8 @@ void pum_redraw(void) idx = i + pum_first; attr = (idx == pum_selected) ? attr_select : attr_norm; + screen_puts_line_start(row); + // prepend a space if there is room if (curwin->w_p_rl) { if (pum_col < curwin->w_wincol + curwin->w_width - 1) { @@ -488,6 +490,7 @@ void pum_redraw(void) ? attr_thumb : attr_scroll); } } + screen_puts_line_flush(false); row++; } } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 49aeaff3a6..65a3c17286 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -85,6 +85,7 @@ #include "nvim/fold.h" #include "nvim/indent.h" #include "nvim/getchar.h" +#include "nvim/highlight.h" #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -299,7 +300,8 @@ void update_screen(int type) type = CLEAR; } else if (type != CLEAR) { check_for_delay(false); - if (screen_ins_lines(0, 0, msg_scrolled, (int)Rows, NULL) == FAIL) { + if (screen_ins_lines(0, msg_scrolled, (int)Rows, 0, (int)Columns) + == FAIL) { type = CLEAR; } FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { @@ -1479,6 +1481,8 @@ static void win_update(win_T *wp) wp->w_empty_rows = 0; wp->w_filler_rows = 0; if (!eof && !didline) { + int at_attr = hl_combine_attr(wp->w_hl_attr_normal, + win_hl_attr(wp, HLF_AT)); if (lnum == wp->w_topline) { /* * Single line that does not fit! @@ -1493,12 +1497,11 @@ static void win_update(win_T *wp) int scr_row = wp->w_winrow + wp->w_height - 1; // Last line isn't finished: Display "@@@" in the last screen line. - screen_puts_len((char_u *)"@@", 2, scr_row, wp->w_wincol, - win_hl_attr(wp, HLF_AT)); + screen_puts_len((char_u *)"@@", 2, scr_row, wp->w_wincol, at_attr); screen_fill(scr_row, scr_row + 1, (int)wp->w_wincol + 2, (int)W_ENDCOL(wp), - '@', ' ', win_hl_attr(wp, HLF_AT)); + '@', ' ', at_attr); set_empty_rows(wp, srow); wp->w_botline = lnum; } else if (dy_flags & DY_LASTLINE) { // 'display' has "lastline" @@ -1506,7 +1509,7 @@ static void win_update(win_T *wp) screen_fill(wp->w_winrow + wp->w_height - 1, wp->w_winrow + wp->w_height, W_ENDCOL(wp) - 3, W_ENDCOL(wp), - '@', '@', win_hl_attr(wp, HLF_AT)); + '@', '@', at_attr); set_empty_rows(wp, srow); wp->w_botline = lnum; } else { @@ -1604,7 +1607,7 @@ static void win_draw_end(win_T *wp, int c1, int c2, int row, int endrow, hlf_T h # define FDC_OFF n int fdc = compute_foldcolumn(wp, 0); - int attr = win_hl_attr(wp, hl); + int attr = hl_combine_attr(wp->w_hl_attr_normal, win_hl_attr(wp, hl)); if (wp->w_p_rl) { // No check for cmdline window: should never be right-left. @@ -1991,7 +1994,7 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T } screen_line(row + wp->w_winrow, wp->w_wincol, wp->w_width, - wp->w_width, false, wp, 0); + wp->w_width, false, wp, wp->w_hl_attr_normal); /* * Update w_cline_height and w_cline_folded if the cursor line was @@ -2407,7 +2410,7 @@ win_line ( if (wp->w_p_cul && lnum == wp->w_cursor.lnum && !(wp == curwin && VIsual_active)) { int cul_attr = win_hl_attr(wp, HLF_CUL); - HlAttrs *aep = syn_cterm_attr2entry(cul_attr); + HlAttrs *aep = syn_attr2entry(cul_attr); // We make a compromise here (#7383): // * low-priority CursorLine if fg is not set @@ -4224,25 +4227,7 @@ win_line ( LineOffset[screen_row] + screen_Columns) == 2)) ) { - /* First make sure we are at the end of the screen line, - * then output the same character again to let the - * terminal know about the wrap. If the terminal doesn't - * auto-wrap, we overwrite the character. */ - if (ui_current_col() != wp->w_width) - screen_char(LineOffset[screen_row - 1] - + (unsigned)Columns - 1, - screen_row - 1, (int)(Columns - 1)); - - /* When there is a multi-byte character, just output a - * space to keep it simple. */ - if (ScreenLines[LineOffset[screen_row - 1] - + (Columns - 1)][1] != 0) { - ui_putc(' '); - } else { - ui_puts(ScreenLines[LineOffset[screen_row - 1] + (Columns - 1)]); - } - /* force a redraw of the first char on the next line */ - ScreenAttrs[LineOffset[screen_row]] = (sattr_T)-1; + ui_add_linewrap(screen_row-1); } } @@ -4330,13 +4315,14 @@ static void screen_line(int row, int coloff, int endcol, /* 2: occupies two display cells */ # define CHAR_CELLS char_cells + int start_dirty = -1, end_dirty = 0; + /* Check for illegal row and col, just in case. */ if (row >= Rows) row = Rows - 1; if (endcol > Columns) endcol = Columns; - off_from = (unsigned)(current_ScreenLine - ScreenLines); off_to = LineOffset[row] + coloff; max_off_from = off_from + screen_Columns; @@ -4384,6 +4370,10 @@ static void screen_line(int row, int coloff, int endcol, if (redraw_this) { + if (start_dirty == -1) { + start_dirty = col; + } + end_dirty = col + char_cells; // When writing a single-width character over a double-width // character and at the end of the redrawn text, need to clear out // the right halve of the old character. @@ -4404,12 +4394,11 @@ static void screen_line(int row, int coloff, int endcol, } ScreenAttrs[off_to] = ScreenAttrs[off_from]; - /* For simplicity set the attributes of second half of a - * double-wide character equal to the first half. */ - if (char_cells == 2) + // For simplicity set the attributes of second half of a + // double-wide character equal to the first half. + if (char_cells == 2) { ScreenAttrs[off_to + 1] = ScreenAttrs[off_from]; - - screen_char(off_to, row, col + coloff); + } } off_to += CHAR_CELLS; @@ -4421,23 +4410,29 @@ static void screen_line(int row, int coloff, int endcol, /* Clear the second half of a double-wide character of which the left * half was overwritten with a single-wide character. */ schar_from_ascii(ScreenLines[off_to], ' '); - screen_char(off_to, row, col + coloff); + end_dirty++; } + int clear_end = -1; if (clear_width > 0 && !rlflag) { // blank out the rest of the line - while (col < clear_width && ScreenLines[off_to][0] == ' ' - && ScreenLines[off_to][1] == NUL - && ScreenAttrs[off_to] == bg_attr - ) { - ++off_to; - ++col; - } - if (col < clear_width) { - screen_fill(row, row + 1, col + coloff, clear_width + coloff, ' ', ' ', - bg_attr); - off_to += clear_width - col; - col = clear_width; + // TODO(bfredl): we could cache winline widths + while (col < clear_width) { + if (ScreenLines[off_to][0] != ' ' || ScreenLines[off_to][1] != NUL + || ScreenAttrs[off_to] != bg_attr) { + ScreenLines[off_to][0] = ' '; + ScreenLines[off_to][1] = NUL; + ScreenAttrs[off_to] = bg_attr; + if (start_dirty == -1) { + start_dirty = col; + end_dirty = col; + } else if (clear_end == -1) { + end_dirty = endcol; + } + clear_end = col+1; + } + col++; + off_to++; } } @@ -4452,11 +4447,25 @@ static void screen_line(int row, int coloff, int endcol, || ScreenAttrs[off_to] != hl) { schar_copy(ScreenLines[off_to], sc); ScreenAttrs[off_to] = hl; - screen_char(off_to, row, col + coloff); + if (start_dirty == -1) { + start_dirty = col; + } + end_dirty = col+1; } } else LineWraps[row] = FALSE; } + + if (clear_end < end_dirty) { + clear_end = end_dirty; + } + if (start_dirty == -1) { + start_dirty = end_dirty; + } + if (clear_end > start_dirty) { + ui_line(row, coloff+start_dirty, coloff+end_dirty, coloff+clear_end, + bg_attr); + } } /* @@ -4738,11 +4747,11 @@ win_redr_status_matches ( /* Put the wildmenu just above the command line. If there is * no room, scroll the screen one line up. */ if (cmdline_row == Rows - 1) { - screen_del_lines(0, 0, 1, (int)Rows, NULL); - ++msg_scrolled; + screen_del_lines(0, 1, (int)Rows, 0, (int)Columns); + msg_scrolled++; } else { - ++cmdline_row; - ++row; + cmdline_row++; + row++; } wild_menu_showing = WM_SCROLLED; } else { @@ -5106,6 +5115,8 @@ win_redr_custom ( /* * Draw each snippet with the specified highlighting. */ + screen_puts_line_start(row); + curattr = attr; p = buf; for (n = 0; hltab[n].start != NULL; n++) { @@ -5126,6 +5137,8 @@ win_redr_custom ( // Make sure to use an empty string instead of p, if p is beyond buf + len. screen_puts(p >= buf + len ? (char_u *)"" : p, row, col, curattr); + screen_puts_line_flush(false); + if (wp == NULL) { // Fill the tab_page_click_defs array for clicking in the tab pages line. col = 0; @@ -5223,7 +5236,6 @@ void screen_getbytes(int row, int col, char_u *bytes, int *attrp) } } - /* * Put string '*text' on the screen at position 'row' and 'col', with * attributes 'attr', and update ScreenLines[] and ScreenAttrs[]. @@ -5235,6 +5247,20 @@ void screen_puts(char_u *text, int row, int col, int attr) screen_puts_len(text, -1, row, col, attr); } +static int put_dirty_row = -1; +static int put_dirty_first = -1; +static int put_dirty_last = 0; + +/// Start a group of screen_puts_len calls that builds a single screen line. +/// +/// Must be matched with a screen_puts_line_flush call before moving to +/// another line. +void screen_puts_line_start(int row) +{ + assert(put_dirty_row == -1); + put_dirty_row = row; +} + /* * Like screen_puts(), but output "text[len]". When "len" is -1 output up to * a NUL. @@ -5258,6 +5284,16 @@ void screen_puts_len(char_u *text, int textlen, int row, int col, int attr) int force_redraw_next = FALSE; int need_redraw; + bool do_flush = false; + if (put_dirty_row == -1) { + screen_puts_line_start(row); + do_flush = true; + } else { + if (row != put_dirty_row) { + abort(); + } + } + if (ScreenLines == NULL || row >= screen_Rows) /* safety check */ return; off = LineOffset[row] + col; @@ -5268,9 +5304,12 @@ void screen_puts_len(char_u *text, int textlen, int row, int col, int attr) schar_from_ascii(ScreenLines[off - 1], ' '); ScreenAttrs[off - 1] = 0; // redraw the previous cell, make it empty - screen_char(off - 1, row, col - 1); - /* force the cell at "col" to be redrawn */ - force_redraw_next = TRUE; + if (put_dirty_first == -1) { + put_dirty_first = col-1; + } + put_dirty_last = col+1; + // force the cell at "col" to be redrawn + force_redraw_next = true; } max_off = LineOffset[row] + screen_Columns; @@ -5349,8 +5388,12 @@ void screen_puts_len(char_u *text, int textlen, int row, int col, int attr) ScreenLines[off + 1][0] = 0; ScreenAttrs[off + 1] = attr; } - screen_char(off, row, col); + if (put_dirty_first == -1) { + put_dirty_first = col; + } + put_dirty_last = col+mbyte_cells; } + off += mbyte_cells; col += mbyte_cells; ptr += mbyte_blen; @@ -5361,11 +5404,29 @@ void screen_puts_len(char_u *text, int textlen, int row, int col, int attr) } } - /* If we detected the next character needs to be redrawn, but the text - * doesn't extend up to there, update the character here. */ - if (force_redraw_next && col < screen_Columns) { - screen_char(off, row, col); + if (do_flush) { + screen_puts_line_flush(true); + } +} + +/// End a group of screen_puts_len calls and send the screen buffer to the UI +/// layer. +/// +/// @param set_cursor Move the visible cursor to the end of the changed region. +/// This is a workaround for not yet refactored code paths +/// and shouldn't be used in new code. +void screen_puts_line_flush(bool set_cursor) +{ + assert(put_dirty_row != -1); + if (put_dirty_first != -1) { + if (set_cursor) { + ui_cursor_goto(put_dirty_row, put_dirty_last); + } + ui_line(put_dirty_row, put_dirty_first, put_dirty_last, put_dirty_last, 0); + put_dirty_first = -1; + put_dirty_last = 0; } + put_dirty_row = -1; } /* @@ -5391,41 +5452,6 @@ static void end_search_hl(void) } } -static void update_window_hl(win_T *wp, bool invalid) -{ - if (!wp->w_hl_needs_update && !invalid) { - return; - } - wp->w_hl_needs_update = false; - - // determine window specific background set in 'winhighlight' - if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] > 0) { - wp->w_hl_attr_normal = syn_id2attr(wp->w_hl_ids[HLF_INACTIVE]); - } else if (wp->w_hl_id_normal > 0) { - wp->w_hl_attr_normal = syn_id2attr(wp->w_hl_id_normal); - } else { - wp->w_hl_attr_normal = 0; - } - if (wp != curwin) { - wp->w_hl_attr_normal = hl_combine_attr(HL_ATTR(HLF_INACTIVE), - wp->w_hl_attr_normal); - } - - for (int hlf = 0; hlf < (int)HLF_COUNT; hlf++) { - int attr; - if (wp->w_hl_ids[hlf] > 0) { - attr = syn_id2attr(wp->w_hl_ids[hlf]); - } else { - attr = HL_ATTR(hlf); - } - if (wp->w_hl_attr_normal != 0) { - attr = hl_combine_attr(wp->w_hl_attr_normal, attr); - } - wp->w_hl_attrs[hlf] = attr; - } -} - - /* * Init for calling prepare_search_hl(). @@ -5692,32 +5718,6 @@ next_search_hl_pos( return 0; } -/* - * Put character ScreenLines["off"] on the screen at position "row" and "col", - * using the attributes from ScreenAttrs["off"]. - */ -static void screen_char(unsigned off, int row, int col) -{ - // Check for illegal values, just in case (could happen just after resizing). - if (row >= screen_Rows || col >= screen_Columns) { - return; - } - - // Outputting the last character on the screen may scrollup the screen. - // Don't to it! Mark the character invalid (update it when scrolled up) - // FIXME: The premise here is not actually true (cf. deferred wrap). - if (row == screen_Rows - 1 && col == screen_Columns - 1 - // account for first command-line character in rightleft mode - && !cmdmsg_rl) { - ScreenAttrs[off] = (sattr_T)-1; - return; - } - - ui_cursor_goto(row, col); - ui_set_highlight(ScreenAttrs[off]); - - ui_puts(ScreenLines[off]); -} /* * Fill the screen from 'start_row' to 'end_row', from 'start_col' to 'end_col' @@ -5726,12 +5726,6 @@ static void screen_char(unsigned off, int row, int col) */ void screen_fill(int start_row, int end_row, int start_col, int end_col, int c1, int c2, int attr) { - int row; - int col; - int off; - int end_off; - int did_delete; - int c; schar_T sc; if (end_row > screen_Rows) /* safety check */ @@ -5743,8 +5737,7 @@ void screen_fill(int start_row, int end_row, int start_col, int end_col, int c1, || start_col >= end_col) /* nothing to do */ return; - /* it's a "normal" terminal when not in a GUI or cterm */ - for (row = start_row; row < end_row; ++row) { + for (int row = start_row; row < end_row; row++) { if (has_mbyte) { // When drawing over the right halve of a double-wide char clear // out the left halve. When drawing over the left halve of a @@ -5757,71 +5750,52 @@ void screen_fill(int start_row, int end_row, int start_col, int end_col, int c1, screen_puts_len((char_u *)" ", 1, row, end_col, 0); } } - /* - * Try to use delete-line termcap code, when no attributes or in a - * "normal" terminal, where a bold/italic space is just a - * space. - */ - did_delete = FALSE; - if (c2 == ' ' - && end_col == Columns - && attr == 0) { - /* - * check if we really need to clear something - */ - col = start_col; - if (c1 != ' ') /* don't clear first char */ - ++col; - - off = LineOffset[row] + col; - end_off = LineOffset[row] + end_col; - // skip blanks (used often, keep it fast!) - while (off < end_off && ScreenLines[off][0] == ' ' - && ScreenLines[off][1] == 0 && ScreenAttrs[off] == 0) { - off++; - } - if (off < end_off) { // something to be cleared - col = off - LineOffset[row]; - ui_clear_highlight(); - ui_cursor_goto(row, col); // clear rest of this screen line - ui_call_eol_clear(); - col = end_col - col; - while (col--) { // clear chars in ScreenLines - schar_from_ascii(ScreenLines[off], ' '); - ScreenAttrs[off] = 0; - ++off; - } - } - did_delete = TRUE; /* the chars are cleared now */ - } - - off = LineOffset[row] + start_col; - c = c1; - schar_from_char(sc, c); + int dirty_first = INT_MAX; + int dirty_last = 0; + int col = start_col; + schar_from_char(sc, c1); + int lineoff = LineOffset[row]; for (col = start_col; col < end_col; col++) { + int off = lineoff + col; if (schar_cmp(ScreenLines[off], sc) || ScreenAttrs[off] != attr) { schar_copy(ScreenLines[off], sc); ScreenAttrs[off] = attr; - if (!did_delete || c != ' ') - screen_char(off, row, col); + if (dirty_first == INT_MAX) { + dirty_first = col; + } + dirty_last = col+1; } - ++off; if (col == start_col) { - if (did_delete) - break; - c = c2; - schar_from_char(sc, c); + schar_from_char(sc, c2); } } - if (end_col == Columns) - LineWraps[row] = FALSE; - if (row == Rows - 1) { /* overwritten the command line */ - redraw_cmdline = TRUE; - if (c1 == ' ' && c2 == ' ') - clear_cmdline = FALSE; /* command line has been cleared */ - if (start_col == 0) - mode_displayed = FALSE; /* mode cleared or overwritten */ + if (dirty_last > dirty_first) { + // TODO(bfredl): support a cleared suffix even with a batched line? + if (put_dirty_row == row) { + if (put_dirty_first == -1) { + put_dirty_first = dirty_first; + } + put_dirty_last = MAX(put_dirty_last, dirty_last); + } else { + int last = c2 != ' ' ? dirty_last : dirty_first + (c1 != ' '); + ui_line(row, dirty_first, last, dirty_last, attr); + } + } + + if (end_col == Columns) { + LineWraps[row] = false; + } + + // TODO(bfredl): The relevant caller should do this + if (row == Rows - 1) { // overwritten the command line + redraw_cmdline = true; + if (c1 == ' ' && c2 == ' ') { + clear_cmdline = false; // command line has been cleared + } + if (start_col == 0) { + mode_displayed = false; // mode cleared or overwritten + } } } } @@ -6078,15 +6052,13 @@ static void screenclear2(void) return; } - ui_clear_highlight(); // don't want highlighting here - /* blank out ScreenLines */ for (i = 0; i < Rows; ++i) { lineclear(LineOffset[i], (int)Columns); LineWraps[i] = FALSE; } - ui_call_clear(); // clear the display + ui_call_grid_clear(1); // clear the display clear_cmdline = false; mode_displayed = false; screen_cleared = true; // can use contents of ScreenLines now @@ -6115,18 +6087,16 @@ static void lineclear(unsigned off, int width) (void)memset(ScreenAttrs + off, 0, (size_t)width * sizeof(sattr_T)); } -/* - * Copy part of a Screenline for vertically split window "wp". - */ -static void linecopy(int to, int from, win_T *wp) +/// Copy part of a Screenline for vertically split window. +static void linecopy(int to, int from, int col, int width) { - const unsigned off_to = LineOffset[to] + wp->w_wincol; - const unsigned off_from = LineOffset[from] + wp->w_wincol; + unsigned off_to = LineOffset[to] + col; + unsigned off_from = LineOffset[from] + col; memmove(ScreenLines + off_to, ScreenLines + off_from, - wp->w_width * sizeof(schar_T)); + width * sizeof(schar_T)); memmove(ScreenAttrs + off_to, ScreenAttrs + off_from, - wp->w_width * sizeof(ScreenAttrs[0])); + width * sizeof(sattr_T)); } /* @@ -6204,15 +6174,16 @@ static int win_do_lines(win_T *wp, int row, int line_count, // otherwise it will stay there forever. clear_cmdline = TRUE; int retval; - ui_set_scroll_region(wp, row); + if (del) { - retval = screen_del_lines(wp->w_winrow + row, 0, line_count, - wp->w_height - row, wp); + retval = screen_del_lines(wp->w_winrow + row, line_count, + wp->w_winrow + wp->w_height, + wp->w_wincol, wp->w_width); } else { - retval = screen_ins_lines(wp->w_winrow + row, 0, line_count, - wp->w_height - row, wp); + retval = screen_ins_lines(wp->w_winrow + row, line_count, + wp->w_winrow + wp->w_height, + wp->w_wincol, wp->w_width); } - ui_reset_scroll_region(); return retval; } @@ -6240,19 +6211,13 @@ static void win_rest_invalid(win_T *wp) */ -// insert lines on the screen and update ScreenLines[] -// 'end' is the line after the scrolled part. Normally it is Rows. -// When scrolling region used 'off' is the offset from the top for the region. -// 'row' and 'end' are relative to the start of the region. -// -// return FAIL for failure, OK for success. -int screen_ins_lines ( - int off, - int row, - int line_count, - int end, - win_T *wp /* NULL or window to use width from */ -) +/// insert lines on the screen and update ScreenLines[] +/// 'end' is the line after the scrolled part. Normally it is Rows. +/// When scrolling region used 'off' is the offset from the top for the region. +/// 'row' and 'end' are relative to the start of the region. +/// +/// @return FAIL for failure, OK for success. +int screen_ins_lines(int row, int line_count, int end, int col, int width) { int i; int j; @@ -6264,18 +6229,16 @@ int screen_ins_lines ( // Shift LineOffset[] line_count down to reflect the inserted lines. // Clear the inserted lines in ScreenLines[]. - row += off; - end += off; - for (i = 0; i < line_count; ++i) { - if (wp != NULL && wp->w_width != Columns) { + for (i = 0; i < line_count; i++) { + if (width != Columns) { // need to copy part of a line j = end - 1 - i; while ((j -= line_count) >= row) { - linecopy(j + line_count, j, wp); + linecopy(j + line_count, j, col, width); } j += line_count; - lineclear(LineOffset[j] + wp->w_wincol, wp->w_width); - LineWraps[j] = FALSE; + lineclear(LineOffset[j] + col, width); + LineWraps[j] = false; } else { j = end - 1 - i; temp = LineOffset[j]; @@ -6284,29 +6247,23 @@ int screen_ins_lines ( LineWraps[j + line_count] = LineWraps[j]; } LineOffset[j + line_count] = temp; - LineWraps[j + line_count] = FALSE; + LineWraps[j + line_count] = false; lineclear(temp, (int)Columns); } } - ui_call_scroll(-line_count); + ui_call_grid_scroll(1, row, end, col, col+width, -line_count, 0); return OK; } -// delete lines on the screen and update ScreenLines[] -// 'end' is the line after the scrolled part. Normally it is Rows. -// When scrolling region used 'off' is the offset from the top for the region. -// 'row' and 'end' are relative to the start of the region. -// -// Return OK for success, FAIL if the lines are not deleted. -int screen_del_lines ( - int off, - int row, - int line_count, - int end, - win_T *wp /* NULL or window to use width from */ -) +/// delete lines on the screen and update ScreenLines[] +/// 'end' is the line after the scrolled part. Normally it is Rows. +/// When scrolling region used 'off' is the offset from the top for the region. +/// 'row' and 'end' are relative to the start of the region. +/// +/// Return OK for success, FAIL if the lines are not deleted. +int screen_del_lines(int row, int line_count, int end, int col, int width) { int j; int i; @@ -6318,18 +6275,16 @@ int screen_del_lines ( // Now shift LineOffset[] line_count up to reflect the deleted lines. // Clear the inserted lines in ScreenLines[]. - row += off; - end += off; - for (i = 0; i < line_count; ++i) { - if (wp != NULL && wp->w_width != Columns) { + for (i = 0; i < line_count; i++) { + if (width != Columns) { // need to copy part of a line j = row + i; while ((j += line_count) <= end - 1) { - linecopy(j - line_count, j, wp); + linecopy(j - line_count, j, col, width); } j -= line_count; - lineclear(LineOffset[j] + wp->w_wincol, wp->w_width); - LineWraps[j] = FALSE; + lineclear(LineOffset[j] + col, width); + LineWraps[j] = false; } else { // whole width, moving the line pointers is faster j = row + i; @@ -6339,16 +6294,17 @@ int screen_del_lines ( LineWraps[j - line_count] = LineWraps[j]; } LineOffset[j - line_count] = temp; - LineWraps[j - line_count] = FALSE; + LineWraps[j - line_count] = false; lineclear(temp, (int)Columns); } } - ui_call_scroll(line_count); + ui_call_grid_scroll(1, row, end, col, col+width, line_count, 0); return OK; } + /* * show the current mode and ruler * diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 98e457db18..ff47e443f9 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -22,6 +22,7 @@ #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/hashtab.h" +#include "nvim/highlight.h" #include "nvim/indent_c.h" #include "nvim/mbyte.h" #include "nvim/memline.h" @@ -42,7 +43,6 @@ #include "nvim/ui.h" #include "nvim/os/os.h" #include "nvim/os/time.h" -#include "nvim/api/private/helpers.h" #include "nvim/buffer.h" static bool did_syntax_onoff = false; @@ -216,12 +216,6 @@ struct name_list { # include "syntax.c.generated.h" #endif -/* - * An attribute number is the index in attr_table plus ATTR_OFF. - */ -#define ATTR_OFF 1 - - static char *(spo_name_tab[SPO_COUNT]) = {"ms=", "me=", "hs=", "he=", "rs=", "re=", "lc="}; @@ -3425,9 +3419,9 @@ static void syn_cmd_off(exarg_T *eap, int syncing) static void syn_cmd_onoff(exarg_T *eap, char *name) FUNC_ATTR_NONNULL_ALL { - did_syntax_onoff = true; eap->nextcmd = check_nextcmd(eap->arg); if (!eap->skip) { + did_syntax_onoff = true; char buf[100]; memcpy(buf, "so ", 4); vim_snprintf(buf + 3, sizeof(buf) - 3, SYNTAX_FNAME, name); @@ -6804,14 +6798,12 @@ void do_highlight(const char *line, const bool forceit, const bool init) HL_TABLE()[idx].sg_cterm_fg = color + 1; if (is_normal_group) { cterm_normal_fg_color = color + 1; - must_redraw = CLEAR; } } else { HL_TABLE()[idx].sg_cterm_bg = color + 1; if (is_normal_group) { cterm_normal_bg_color = color + 1; if (!ui_rgb_attached()) { - must_redraw = CLEAR; if (color >= 0) { int dark = -1; @@ -6915,8 +6907,16 @@ void do_highlight(const char *line, const bool forceit, const bool init) // Need to update all groups, because they might be using "bg" and/or // "fg", which have been changed now. highlight_attr_set_all(); - // If the normal group has changed, it is simpler to refresh every UI - ui_refresh(); + + if (!ui_is_external(kUINewgrid)) { + // Older UIs assume that we clear the screen after normal group is + // changed + ui_refresh(); + } else { + // TUI and newer UIs will repaint the screen themselves. NOT_VALID + // redraw below will still handle usages of guibg=fg etc. + ui_default_colors_set(); + } } else { set_hl_attr(idx); } @@ -7001,161 +7001,6 @@ static void highlight_clear(int idx) } -/// Table with the specifications for an attribute number. -/// Note that this table is used by ALL buffers. This is required because the -/// GUI can redraw at any time for any buffer. -static garray_T attr_table = GA_EMPTY_INIT_VALUE; - -static inline HlAttrs * ATTR_ENTRY(int idx) -{ - return &((HlAttrs *)attr_table.ga_data)[idx]; -} - - -/// Return the attr number for a set of colors and font. -/// Add a new entry to the term_attr_table, attr_table or gui_attr_table -/// if the combination is new. -/// @return 0 for error. -int get_attr_entry(HlAttrs *aep) -{ - garray_T *table = &attr_table; - HlAttrs *taep; - static int recursive = false; - - /* - * Init the table, in case it wasn't done yet. - */ - table->ga_itemsize = sizeof(HlAttrs); - ga_set_growsize(table, 7); - - // Try to find an entry with the same specifications. - for (int i = 0; i < table->ga_len; i++) { - taep = &(((HlAttrs *)table->ga_data)[i]); - if (aep->cterm_ae_attr == taep->cterm_ae_attr - && aep->cterm_fg_color == taep->cterm_fg_color - && aep->cterm_bg_color == taep->cterm_bg_color - && aep->rgb_ae_attr == taep->rgb_ae_attr - && aep->rgb_fg_color == taep->rgb_fg_color - && aep->rgb_bg_color == taep->rgb_bg_color - && aep->rgb_sp_color == taep->rgb_sp_color) { - return i + ATTR_OFF; - } - } - - if (table->ga_len + ATTR_OFF > MAX_TYPENR) { - /* - * Running out of attribute entries! remove all attributes, and - * compute new ones for all groups. - * When called recursively, we are really out of numbers. - */ - if (recursive) { - EMSG(_("E424: Too many different highlighting attributes in use")); - return 0; - } - recursive = TRUE; - - clear_hl_tables(); - - must_redraw = CLEAR; - - for (int i = 0; i < highlight_ga.ga_len; ++i) { - set_hl_attr(i); - } - - recursive = FALSE; - } - - - // This is a new combination of colors and font, add an entry. - taep = GA_APPEND_VIA_PTR(HlAttrs, table); - memset(taep, 0, sizeof(*taep)); - taep->cterm_ae_attr = aep->cterm_ae_attr; - taep->cterm_fg_color = aep->cterm_fg_color; - taep->cterm_bg_color = aep->cterm_bg_color; - taep->rgb_ae_attr = aep->rgb_ae_attr; - taep->rgb_fg_color = aep->rgb_fg_color; - taep->rgb_bg_color = aep->rgb_bg_color; - taep->rgb_sp_color = aep->rgb_sp_color; - - return table->ga_len - 1 + ATTR_OFF; -} - -// Clear all highlight tables. -void clear_hl_tables(void) -{ - ga_clear(&attr_table); -} - -// Combine special attributes (e.g., for spelling) with other attributes -// (e.g., for syntax highlighting). -// "prim_attr" overrules "char_attr". -// This creates a new group when required. -// Since we expect there to be few spelling mistakes we don't cache the -// result. -// Return the resulting attributes. -int hl_combine_attr(int char_attr, int prim_attr) -{ - HlAttrs *char_aep = NULL; - HlAttrs *spell_aep; - HlAttrs new_en = HLATTRS_INIT; - - if (char_attr == 0) { - return prim_attr; - } - - if (prim_attr == 0) { - return char_attr; - } - - // Find the entry for char_attr - char_aep = syn_cterm_attr2entry(char_attr); - - if (char_aep != NULL) { - // Copy all attributes from char_aep to the new entry - new_en = *char_aep; - } - - spell_aep = syn_cterm_attr2entry(prim_attr); - if (spell_aep != NULL) { - new_en.cterm_ae_attr |= spell_aep->cterm_ae_attr; - new_en.rgb_ae_attr |= spell_aep->rgb_ae_attr; - - if (spell_aep->cterm_fg_color > 0) { - new_en.cterm_fg_color = spell_aep->cterm_fg_color; - } - - if (spell_aep->cterm_bg_color > 0) { - new_en.cterm_bg_color = spell_aep->cterm_bg_color; - } - - if (spell_aep->rgb_fg_color >= 0) { - new_en.rgb_fg_color = spell_aep->rgb_fg_color; - } - - if (spell_aep->rgb_bg_color >= 0) { - new_en.rgb_bg_color = spell_aep->rgb_bg_color; - } - - if (spell_aep->rgb_sp_color >= 0) { - new_en.rgb_sp_color = spell_aep->rgb_sp_color; - } - } - return get_attr_entry(&new_en); -} - -/// \note this function does not apply exclusively to cterm attr contrary -/// to what its name implies -/// \warn don't call it with attr 0 (i.e., the null attribute) -HlAttrs *syn_cterm_attr2entry(int attr) -{ - attr -= ATTR_OFF; - if (attr >= attr_table.ga_len) { - // did ":syntax clear" - return NULL; - } - return ATTR_ENTRY(attr); -} - /// \addtogroup LIST_XXX /// @{ #define LIST_ATTR 1 @@ -7410,15 +7255,7 @@ static void set_hl_attr(int idx) at_en.rgb_bg_color = sgp->sg_rgb_bg_name ? sgp->sg_rgb_bg : -1; at_en.rgb_sp_color = sgp->sg_rgb_sp_name ? sgp->sg_rgb_sp : -1; - if (at_en.cterm_fg_color != 0 || at_en.cterm_bg_color != 0 - || at_en.rgb_fg_color != -1 || at_en.rgb_bg_color != -1 - || at_en.rgb_sp_color != -1 || at_en.cterm_ae_attr != 0 - || at_en.rgb_ae_attr != 0) { - sgp->sg_attr = get_attr_entry(&at_en); - } else { - // If all the fields are cleared, clear the attr field back to default value - sgp->sg_attr = 0; - } + sgp->sg_attr = hl_get_syn_attr(idx+1, at_en); } /// Lookup a highlight group name and return its ID. @@ -7553,7 +7390,7 @@ static void syn_unadd_group(void) /// Translate a group ID to highlight attributes. -/// @see syn_cterm_attr2entry +/// @see syn_attr2entry int syn_id2attr(int hl_id) { struct hl_group *sgp; @@ -7590,7 +7427,7 @@ int syn_get_final_id(int hl_id) } /// Refresh the color attributes of all highlight groups. -static void highlight_attr_set_all(void) +void highlight_attr_set_all(void) { for (int idx = 0; idx < highlight_ga.ga_len; idx++) { struct hl_group *sgp = &HL_TABLE()[idx]; @@ -7613,7 +7450,6 @@ static void highlight_attr_set_all(void) /// screen redraw after any :highlight command. void highlight_changed(void) { - int attr; int id; char_u userhl[10]; int id_SNC = -1; @@ -7628,13 +7464,15 @@ void highlight_changed(void) if (id == 0) { abort(); } - attr = syn_id2attr(id); + int final_id = syn_get_final_id(id); if (hlf == (int)HLF_SNC) { - id_SNC = syn_get_final_id(id); + id_SNC = final_id; } else if (hlf == (int)HLF_S) { - id_S = syn_get_final_id(id); + id_S = final_id; } - highlight_attr[hlf] = attr; + + highlight_attr[hlf] = hl_get_ui_attr(hlf, final_id, + hlf == (int)HLF_INACTIVE); } /* Setup the user highlights @@ -8522,26 +8360,6 @@ RgbValue name_to_color(const char_u *name) return -1; } -/// Gets highlight description for id `attr_id` as a map. -Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Error *err) -{ - HlAttrs *aep = NULL; - Dictionary dic = ARRAY_DICT_INIT; - - if (attr_id == 0) { - return dic; - } - - aep = syn_cterm_attr2entry((int)attr_id); - if (!aep) { - api_set_error(err, kErrorTypeException, - "Invalid attribute id: %" PRId64, attr_id); - return dic; - } - - return hlattrs2dict(aep, rgb); -} - /************************************** * End of Highlighting stuff * diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 6907529726..c91b959ce3 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -49,6 +49,7 @@ #include "nvim/message.h" #include "nvim/memory.h" #include "nvim/option.h" +#include "nvim/highlight.h" #include "nvim/macros.h" #include "nvim/mbyte.h" #include "nvim/buffer.h" @@ -602,7 +603,7 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, int attr_id = 0; if (hl_attrs || vt_fg != -1 || vt_bg != -1) { - attr_id = get_attr_entry(&(HlAttrs) { + attr_id = get_term_attr_entry(&(HlAttrs) { .cterm_ae_attr = (int16_t)hl_attrs, .cterm_fg_color = vt_fg_idx, .cterm_bg_color = vt_bg_idx, diff --git a/src/nvim/testdir/test_hardcopy.vim b/src/nvim/testdir/test_hardcopy.vim index f630556bef..ced13b107c 100644 --- a/src/nvim/testdir/test_hardcopy.vim +++ b/src/nvim/testdir/test_hardcopy.vim @@ -63,12 +63,27 @@ func Test_with_syntax() endfunc func Test_fname_with_spaces() - if has('postscript') - split t\ e\ s\ t.txt - call setline(1, ['just', 'some', 'text']) - hardcopy > %.ps - call assert_true(filereadable('t e s t.txt.ps')) - call delete('t e s t.txt.ps') - bwipe! + if !has('postscript') + return + endif + split t\ e\ s\ t.txt + call setline(1, ['just', 'some', 'text']) + hardcopy > %.ps + call assert_true(filereadable('t e s t.txt.ps')) + call delete('t e s t.txt.ps') + bwipe! +endfunc + +func Test_illegal_byte() + if !has('postscript') || &enc != 'utf-8' + return endif + new + " conversion of 0xff will fail, this used to cause a crash + call setline(1, "\xff") + hardcopy >Xpstest + + bwipe! + call delete('Xpstest') endfunc + diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index f41c715696..56c47ed6cc 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -20,6 +20,7 @@ #include "nvim/vim.h" #include "nvim/log.h" #include "nvim/ui.h" +#include "nvim/highlight.h" #include "nvim/map.h" #include "nvim/main.h" #include "nvim/memory.h" @@ -31,13 +32,13 @@ #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/strings.h" +#include "nvim/syntax.h" #include "nvim/ui_bridge.h" #include "nvim/ugrid.h" #include "nvim/tui/input.h" #include "nvim/tui/tui.h" #include "nvim/tui/terminfo.h" #include "nvim/cursor_shape.h" -#include "nvim/syntax.h" #include "nvim/macros.h" // Space reserved in two output buffers to make the cursor normal or invisible @@ -87,6 +88,7 @@ typedef struct { bool cont_received; UGrid grid; kvec_t(Rect) invalid_regions; + int row, col; int out_fd; bool scroll_region_is_full_screen; bool can_change_scroll_region; @@ -97,6 +99,8 @@ typedef struct { bool busy, is_invisible; bool cork, overflow; cursorentry_T cursor_shapes[SHAPE_IDX_COUNT]; + HlAttrs clear_attrs; + kvec_t(HlAttrs) attrs; HlAttrs print_attrs; bool default_attr; ModeShape showing_mode; @@ -125,10 +129,9 @@ UI *tui_start(void) { UI *ui = xcalloc(1, sizeof(UI)); // Freed by ui_bridge_stop(). ui->stop = tui_stop; - ui->resize = tui_resize; - ui->clear = tui_clear; - ui->eol_clear = tui_eol_clear; - ui->cursor_goto = tui_cursor_goto; + ui->grid_resize = tui_grid_resize; + ui->grid_clear = tui_grid_clear; + ui->grid_cursor_goto = tui_grid_cursor_goto; ui->mode_info_set = tui_mode_info_set; ui->update_menu = tui_update_menu; ui->busy_start = tui_busy_start; @@ -136,10 +139,8 @@ UI *tui_start(void) ui->mouse_on = tui_mouse_on; ui->mouse_off = tui_mouse_off; ui->mode_change = tui_mode_change; - ui->set_scroll_region = tui_set_scroll_region; - ui->scroll = tui_scroll; - ui->highlight_set = tui_highlight_set; - ui->put = tui_put; + ui->grid_scroll = tui_grid_scroll; + ui->hl_attr_define = tui_hl_attr_define; ui->bell = tui_bell; ui->visual_bell = tui_visual_bell; ui->default_colors_set = tui_default_colors_set; @@ -148,8 +149,10 @@ UI *tui_start(void) ui->set_title = tui_set_title; ui->set_icon = tui_set_icon; ui->option_set= tui_option_set; + ui->raw_line = tui_raw_line; memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); + ui->ui_ext[kUINewgrid] = true; return ui_bridge_attach(ui, tui_main, tui_scheduler); } @@ -289,7 +292,7 @@ static void terminfo_stop(UI *ui) static void tui_terminal_start(UI *ui) { TUIData *data = ui->data; - data->print_attrs = HLATTRS_INIT; + data->print_attrs = HLATTRS_INVALID; ugrid_init(&data->grid); terminfo_start(ui); update_size(ui); @@ -345,6 +348,9 @@ static void tui_main(UIBridgeData *bridge, UI *ui) signal_watcher_start(&data->cont_handle, sigcont_cb, SIGCONT); #endif + // TODO(bfredl): zero hl is empty, send this explicitly? + kv_push(data->attrs, HLATTRS_INIT); + #if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 data->input.tk_ti_hook_fn = tui_tk_ti_getstr; #endif @@ -379,6 +385,7 @@ static void tui_main(UIBridgeData *bridge, UI *ui) signal_watcher_close(&data->winch_handle, NULL); loop_close(&tui_loop, false); kv_destroy(data->invalid_regions); + kv_destroy(data->attrs); xfree(data); } @@ -437,18 +444,17 @@ static void update_attrs(UI *ui, HlAttrs attrs) } data->print_attrs = attrs; - UGrid *grid = &data->grid; int fg = ui->rgb ? attrs.rgb_fg_color : (attrs.cterm_fg_color - 1); if (fg == -1) { - fg = ui->rgb ? grid->clear_attrs.rgb_fg_color - : (grid->clear_attrs.cterm_fg_color - 1); + fg = ui->rgb ? data->clear_attrs.rgb_fg_color + : (data->clear_attrs.cterm_fg_color - 1); } int bg = ui->rgb ? attrs.rgb_bg_color : (attrs.cterm_bg_color - 1); if (bg == -1) { - bg = ui->rgb ? grid->clear_attrs.rgb_bg_color - : (grid->clear_attrs.cterm_bg_color - 1); + bg = ui->rgb ? data->clear_attrs.rgb_bg_color + : (data->clear_attrs.cterm_bg_color - 1); } int attr = ui->rgb ? attrs.rgb_ae_attr : attrs.cterm_ae_attr; @@ -591,6 +597,8 @@ static void cursor_goto(UI *ui, int row, int col) if (row == grid->row && col == grid->col) { return; } + grid->row = row; + grid->col = col; if (0 == row && 0 == col) { unibi_out(ui, unibi_cursor_home); ugrid_goto(grid, row, col); @@ -678,20 +686,20 @@ static void cursor_goto(UI *ui, int row, int col) ugrid_goto(grid, row, col); } -static void clear_region(UI *ui, int top, int bot, int left, int right) +static void clear_region(UI *ui, int top, int bot, int left, int right, + HlAttrs attrs) { TUIData *data = ui->data; UGrid *grid = &data->grid; - int saved_row = grid->row; - int saved_col = grid->col; bool cleared = false; - bool nobg = ui->rgb ? grid->clear_attrs.rgb_bg_color == -1 - : grid->clear_attrs.cterm_bg_color == 0; + // TODO(bfredl): support BCE for non-default background + bool nobg = ui->rgb ? attrs.rgb_bg_color == -1 + : attrs.cterm_bg_color == 0; if (nobg && right == ui->width -1) { // Background is set to the default color and the right edge matches the // screen end, try to use terminal codes for clearing the requested area. - update_attrs(ui, grid->clear_attrs); + update_attrs(ui, attrs); if (left == 0) { if (bot == ui->height - 1) { if (top == 0) { @@ -724,7 +732,7 @@ static void clear_region(UI *ui, int top, int bot, int left, int right) } // restore cursor - cursor_goto(ui, saved_row, saved_col); + cursor_goto(ui, data->row, data->col); } static bool can_use_scroll(UI * ui) @@ -791,7 +799,7 @@ static void reset_scroll_region(UI *ui) unibi_goto(ui, grid->row, grid->col); } -static void tui_resize(UI *ui, Integer width, Integer height) +static void tui_grid_resize(UI *ui, Integer g, Integer width, Integer height) { TUIData *data = ui->data; ugrid_resize(&data->grid, (int)width, (int)height); @@ -809,25 +817,21 @@ static void tui_resize(UI *ui, Integer width, Integer height) } } -static void tui_clear(UI *ui) +static void tui_grid_clear(UI *ui, Integer g) { TUIData *data = ui->data; UGrid *grid = &data->grid; ugrid_clear(grid); kv_size(data->invalid_regions) = 0; - clear_region(ui, grid->top, grid->bot, grid->left, grid->right); + clear_region(ui, grid->top, grid->bot, grid->left, grid->right, + data->clear_attrs); } -static void tui_eol_clear(UI *ui) +static void tui_grid_cursor_goto(UI *ui, Integer grid, Integer row, Integer col) { TUIData *data = ui->data; - UGrid *grid = &data->grid; - ugrid_eol_clear(grid); - clear_region(ui, grid->row, grid->row, grid->col, grid->right); -} - -static void tui_cursor_goto(UI *ui, Integer row, Integer col) -{ + data->row = (int)row; + data->col = (int)col; cursor_goto(ui, (int)row, (int)col); } @@ -932,7 +936,7 @@ static void tui_set_mode(UI *ui, ModeShape mode) if (c.id != 0 && ui->rgb) { int attr = syn_id2attr(c.id); if (attr > 0) { - HlAttrs *aep = syn_cterm_attr2entry(attr); + HlAttrs *aep = syn_attr2entry(attr); UNIBI_SET_NUM_VAR(data->params[0], aep->rgb_bg_color); unibi_out_ext(ui, data->unibi_ext.set_cursor_color); } @@ -957,27 +961,23 @@ static void tui_mode_change(UI *ui, String mode, Integer mode_idx) data->showing_mode = (ModeShape)mode_idx; } -static void tui_set_scroll_region(UI *ui, Integer top, Integer bot, - Integer left, Integer right) +static void tui_grid_scroll(UI *ui, Integer g, Integer top, Integer bot, + Integer left, Integer right, + Integer rows, Integer cols) { TUIData *data = ui->data; - ugrid_set_scroll_region(&data->grid, (int)top, (int)bot, - (int)left, (int)right); + UGrid *grid = &data->grid; + ugrid_set_scroll_region(&data->grid, (int)top, (int)bot-1, + (int)left, (int)right-1); + data->scroll_region_is_full_screen = - left == 0 && right == ui->width - 1 - && top == 0 && bot == ui->height - 1; -} + left == 0 && right == ui->width + && top == 0 && bot == ui->height; -static void tui_scroll(UI *ui, Integer count) -{ - TUIData *data = ui->data; - UGrid *grid = &data->grid; int clear_top, clear_bot; - ugrid_scroll(grid, (int)count, &clear_top, &clear_bot); + ugrid_scroll(grid, (int)rows, &clear_top, &clear_bot); if (can_use_scroll(ui)) { - int saved_row = grid->row; - int saved_col = grid->col; bool scroll_clears_to_current_colour = unibi_get_bool(data->ut, unibi_back_color_erase); @@ -988,21 +988,21 @@ static void tui_scroll(UI *ui, Integer count) cursor_goto(ui, grid->top, grid->left); // also set default color attributes or some terminals can become funny if (scroll_clears_to_current_colour) { - update_attrs(ui, grid->clear_attrs); + update_attrs(ui, data->clear_attrs); } - if (count > 0) { - if (count == 1) { + if (rows > 0) { + if (rows == 1) { unibi_out(ui, unibi_delete_line); } else { - UNIBI_SET_NUM_VAR(data->params[0], (int)count); + UNIBI_SET_NUM_VAR(data->params[0], (int)rows); unibi_out(ui, unibi_parm_delete_line); } } else { - if (count == -1) { + if (rows == -1) { unibi_out(ui, unibi_insert_line); } else { - UNIBI_SET_NUM_VAR(data->params[0], -(int)count); + UNIBI_SET_NUM_VAR(data->params[0], -(int)rows); unibi_out(ui, unibi_parm_insert_line); } } @@ -1011,12 +1011,13 @@ static void tui_scroll(UI *ui, Integer count) if (!data->scroll_region_is_full_screen) { reset_scroll_region(ui); } - cursor_goto(ui, saved_row, saved_col); + cursor_goto(ui, data->row, data->col); if (!scroll_clears_to_current_colour) { // Scrolling will leave wrong background in the cleared area on non-BCE // terminals. Update the cleared area. - clear_region(ui, clear_top, clear_bot, grid->left, grid->right); + clear_region(ui, clear_top, clear_bot, grid->left, grid->right, + data->clear_attrs); } } else { // Mark the entire scroll region as invalid for redrawing later @@ -1024,23 +1025,11 @@ static void tui_scroll(UI *ui, Integer count) } } -static void tui_highlight_set(UI *ui, HlAttrs attrs) -{ - ((TUIData *)ui->data)->grid.attrs = attrs; -} - -static void tui_put(UI *ui, String text) +static void tui_hl_attr_define(UI *ui, Integer id, HlAttrs attrs, + HlAttrs cterm_attrs, Array info) { TUIData *data = ui->data; - UGrid *grid = &data->grid; - UCell *cell; - - cell = ugrid_put(&data->grid, (uint8_t *)text.data, text.size); - // ugrid_put does not advance the cursor correctly, as the actual terminal - // will when we print. Its cursor motion model is simplistic and wrong. So - // we have to undo what it has just done before doing it right. - grid->col--; - print_cell(ui, cell); + kv_a(data->attrs, (size_t)id) = attrs; } static void tui_bell(UI *ui) @@ -1057,12 +1046,16 @@ static void tui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, Integer cterm_fg, Integer cterm_bg) { - UGrid *grid = &((TUIData *)ui->data)->grid; - grid->clear_attrs.rgb_fg_color = (int)rgb_fg; - grid->clear_attrs.rgb_bg_color = (int)rgb_bg; - grid->clear_attrs.rgb_sp_color = (int)rgb_sp; - grid->clear_attrs.cterm_fg_color = (int)cterm_fg; - grid->clear_attrs.cterm_bg_color = (int)cterm_bg; + TUIData *data = ui->data; + + data->clear_attrs.rgb_fg_color = (int)rgb_fg; + data->clear_attrs.rgb_bg_color = (int)rgb_bg; + data->clear_attrs.rgb_sp_color = (int)rgb_sp; + data->clear_attrs.cterm_fg_color = (int)cterm_fg; + data->clear_attrs.cterm_bg_color = (int)cterm_bg; + + data->print_attrs = HLATTRS_INVALID; + invalidate(ui, 0, data->grid.height-1, 0, data->grid.width-1); } static void tui_flush(UI *ui) @@ -1082,9 +1075,6 @@ static void tui_flush(UI *ui) tui_busy_stop(ui); // avoid hidden cursor } - int saved_row = grid->row; - int saved_col = grid->col; - while (kv_size(data->invalid_regions)) { Rect r = kv_pop(data->invalid_regions); assert(r.bot < grid->height && r.right < grid->width); @@ -1094,7 +1084,7 @@ static void tui_flush(UI *ui) }); } - cursor_goto(ui, saved_row, saved_col); + cursor_goto(ui, data->row, data->col); flush_buf(ui); } @@ -1175,10 +1165,37 @@ static void tui_option_set(UI *ui, String name, Object value) TUIData *data = ui->data; if (strequal(name.data, "termguicolors")) { ui->rgb = value.data.boolean; + + data->print_attrs = HLATTRS_INVALID; invalidate(ui, 0, data->grid.height-1, 0, data->grid.width-1); } } +static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol, + Integer endcol, Integer clearcol, Integer clearattr, + const schar_T *chunk, const sattr_T *attrs) +{ + TUIData *data = ui->data; + UGrid *grid = &data->grid; + for (Integer c = startcol; c < endcol; c++) { + memcpy(grid->cells[linerow][c].data, chunk[c-startcol], sizeof(schar_T)); + grid->cells[linerow][c].attrs = kv_A(data->attrs, attrs[c-startcol]); + } + UGRID_FOREACH_CELL(grid, (int)linerow, (int)linerow, (int)startcol, + (int)endcol-1, { + cursor_goto(ui, row, col); + print_cell(ui, cell); + }); + + if (clearcol > endcol) { + HlAttrs cl_attrs = kv_A(data->attrs, (size_t)clearattr); + ugrid_clear_chunk(grid, (int)linerow, (int)endcol, (int)clearcol, + cl_attrs); + clear_region(ui, (int)linerow, (int)linerow, (int)endcol, (int)clearcol-1, + cl_attrs); + } +} + static void invalidate(UI *ui, int top, int bot, int left, int right) { TUIData *data = ui->data; diff --git a/src/nvim/ugrid.c b/src/nvim/ugrid.c index 6d420ef2f8..48f3cff2d7 100644 --- a/src/nvim/ugrid.c +++ b/src/nvim/ugrid.c @@ -17,7 +17,6 @@ void ugrid_init(UGrid *grid) { grid->attrs = HLATTRS_INIT; - grid->clear_attrs = HLATTRS_INIT; grid->cells = NULL; } @@ -45,12 +44,13 @@ void ugrid_resize(UGrid *grid, int width, int height) void ugrid_clear(UGrid *grid) { - clear_region(grid, grid->top, grid->bot, grid->left, grid->right); + clear_region(grid, grid->top, grid->bot, grid->left, grid->right, + HLATTRS_INIT); } -void ugrid_eol_clear(UGrid *grid) +void ugrid_clear_chunk(UGrid *grid, int row, int col, int endcol, HlAttrs attrs) { - clear_region(grid, grid->row, grid->row, grid->col, grid->right); + clear_region(grid, row, row, col, endcol-1, attrs); } void ugrid_goto(UGrid *grid, int row, int col) @@ -99,7 +99,8 @@ void ugrid_scroll(UGrid *grid, int count, int *clear_top, int *clear_bot) *clear_bot = stop; *clear_top = stop + count + 1; } - clear_region(grid, *clear_top, *clear_bot, grid->left, grid->right); + clear_region(grid, *clear_top, *clear_bot, grid->left, grid->right, + HLATTRS_INIT); } UCell *ugrid_put(UGrid *grid, uint8_t *text, size_t size) @@ -117,13 +118,13 @@ UCell *ugrid_put(UGrid *grid, uint8_t *text, size_t size) return cell; } -static void clear_region(UGrid *grid, int top, int bot, int left, int right) +static void clear_region(UGrid *grid, int top, int bot, int left, int right, + HlAttrs attrs) { - HlAttrs clear_attrs = grid->clear_attrs; UGRID_FOREACH_CELL(grid, top, bot, left, right, { cell->data[0] = ' '; cell->data[1] = 0; - cell->attrs = clear_attrs; + cell->attrs = attrs; }); } diff --git a/src/nvim/ugrid.h b/src/nvim/ugrid.h index 035074846e..04e027bd46 100644 --- a/src/nvim/ugrid.h +++ b/src/nvim/ugrid.h @@ -7,7 +7,7 @@ typedef struct ucell UCell; typedef struct ugrid UGrid; -#define CELLBYTES (4 * (MAX_MCO+1)) +#define CELLBYTES (sizeof(schar_T)) struct ucell { char data[CELLBYTES + 1]; @@ -17,7 +17,6 @@ struct ucell { struct ugrid { int top, bot, left, right; int row, col; - HlAttrs clear_attrs; int width, height; HlAttrs attrs; UCell **cells; diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 3b632ace41..ef68b804ba 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -32,7 +32,7 @@ #include "nvim/os/signal.h" #include "nvim/popupmnu.h" #include "nvim/screen.h" -#include "nvim/syntax.h" +#include "nvim/highlight.h" #include "nvim/window.h" #include "nvim/cursor_shape.h" #ifdef FEAT_TUI @@ -52,14 +52,10 @@ static UI *uis[MAX_UI_COUNT]; static bool ui_ext[kUIExtCount] = { 0 }; static size_t ui_count = 0; static int row = 0, col = 0; -static struct { - int top, bot, left, right; -} sr; -static int current_attr_code = -1; static bool pending_cursor_update = false; static int busy = 0; -static int height, width; -static int old_mode_idx = -1; +static int mode_idx = SHAPE_IDX_N; +static bool pending_mode_update = false; #if MIN_LOG_LEVEL > DEBUG_LOG_LEVEL # define UI_LOG(funname, ...) @@ -89,7 +85,6 @@ static char uilog_last_event[1024] = { 0 }; #ifdef _MSC_VER # define UI_CALL(funname, ...) \ do { \ - flush_cursor_update(); \ UI_LOG(funname, 0); \ for (size_t i = 0; i < ui_count; i++) { \ UI *ui = uis[i]; \ @@ -99,7 +94,6 @@ static char uilog_last_event[1024] = { 0 }; #else # define UI_CALL(...) \ do { \ - flush_cursor_update(); \ UI_LOG(__VA_ARGS__, 0); \ for (size_t i = 0; i < ui_count; i++) { \ UI *ui = uis[i]; \ @@ -108,8 +102,8 @@ static char uilog_last_event[1024] = { 0 }; } while (0) #endif #define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, \ - MORE, MORE, ZERO, ignore) -#define SELECT_NTH(a1, a2, a3, a4, a5, a6, a7, ...) a7 + MORE, MORE, MORE, MORE, MORE, ZERO, ignore) +#define SELECT_NTH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, ...) a10 #define UI_CALL_HELPER(c, ...) UI_CALL_HELPER2(c, __VA_ARGS__) // Resolves to UI_CALL_MORE or UI_CALL_ZERO. #define UI_CALL_HELPER2(c, ...) UI_CALL_##c(__VA_ARGS__) @@ -172,66 +166,6 @@ void ui_event(char *name, Array args) } -/// Converts an HlAttrs into Dictionary -/// -/// @param[in] aep data to convert -/// @param use_rgb use 'gui*' settings if true, else resorts to 'cterm*' -Dictionary hlattrs2dict(const HlAttrs *aep, bool use_rgb) -{ - assert(aep); - Dictionary hl = ARRAY_DICT_INIT; - int mask = use_rgb ? aep->rgb_ae_attr : aep->cterm_ae_attr; - - if (mask & HL_BOLD) { - PUT(hl, "bold", BOOLEAN_OBJ(true)); - } - - if (mask & HL_STANDOUT) { - PUT(hl, "standout", BOOLEAN_OBJ(true)); - } - - if (mask & HL_UNDERLINE) { - PUT(hl, "underline", BOOLEAN_OBJ(true)); - } - - if (mask & HL_UNDERCURL) { - PUT(hl, "undercurl", BOOLEAN_OBJ(true)); - } - - if (mask & HL_ITALIC) { - PUT(hl, "italic", BOOLEAN_OBJ(true)); - } - - if (mask & HL_INVERSE) { - PUT(hl, "reverse", BOOLEAN_OBJ(true)); - } - - - if (use_rgb) { - if (aep->rgb_fg_color != -1) { - PUT(hl, "foreground", INTEGER_OBJ(aep->rgb_fg_color)); - } - - if (aep->rgb_bg_color != -1) { - PUT(hl, "background", INTEGER_OBJ(aep->rgb_bg_color)); - } - - if (aep->rgb_sp_color != -1) { - PUT(hl, "special", INTEGER_OBJ(aep->rgb_sp_color)); - } - } else { - if (cterm_normal_fg_color != aep->cterm_fg_color) { - PUT(hl, "foreground", INTEGER_OBJ(aep->cterm_fg_color - 1)); - } - - if (cterm_normal_bg_color != aep->cterm_bg_color) { - PUT(hl, "background", INTEGER_OBJ(aep->cterm_bg_color - 1)); - } - } - - return hl; -} - void ui_refresh(void) { if (!ui_active()) { @@ -259,6 +193,9 @@ void ui_refresh(void) } row = col = 0; + pending_cursor_update = true; + + ui_default_colors_set(); int save_p_lz = p_lz; p_lz = false; // convince redrawing() to return true ... @@ -267,13 +204,14 @@ void ui_refresh(void) for (UIExtension i = 0; (int)i < kUIExtCount; i++) { ui_ext[i] = ext_widgets[i]; - ui_call_option_set(cstr_as_string((char *)ui_ext_names[i]), - BOOLEAN_OBJ(ext_widgets[i])); + if (i < kUIGlobalCount) { + ui_call_option_set(cstr_as_string((char *)ui_ext_names[i]), + BOOLEAN_OBJ(ext_widgets[i])); + } } ui_mode_info_set(); - old_mode_idx = -1; + pending_mode_update = true; ui_cursor_shape(); - current_attr_code = -1; } static void ui_refresh_event(void **argv) @@ -286,25 +224,15 @@ void ui_schedule_refresh(void) loop_schedule(&main_loop, event_create(ui_refresh_event, 0)); } -void ui_resize(int new_width, int new_height) +void ui_resize(int width, int height) { - width = new_width; - height = new_height; + ui_call_grid_resize(1, width, height); +} - // TODO(bfredl): update default colors when they changed, NOT on resize. +void ui_default_colors_set(void) +{ ui_call_default_colors_set(normal_fg, normal_bg, normal_sp, cterm_normal_fg_color, cterm_normal_bg_color); - - // Deprecated: - UI_CALL(update_fg, (ui->rgb ? normal_fg : cterm_normal_fg_color - 1)); - UI_CALL(update_bg, (ui->rgb ? normal_bg : cterm_normal_bg_color - 1)); - UI_CALL(update_sp, (ui->rgb ? normal_sp : -1)); - - sr.top = 0; - sr.bot = height - 1; - sr.left = 0; - sr.right = width - 1; - ui_call_resize(width, height); } void ui_busy_start(void) @@ -329,6 +257,18 @@ void ui_attach_impl(UI *ui) uis[ui_count++] = ui; ui_refresh_options(); + + for (UIExtension i = kUIGlobalCount; (int)i < kUIExtCount; i++) { + ui_set_ext_option(ui, i, ui->ui_ext[i]); + } + + bool sent = false; + if (ui->ui_ext[kUIHlState]) { + sent = highlight_use_hlstate(); + } + if (!sent) { + ui_send_all_hls(ui); + } ui_refresh(); } @@ -362,97 +302,34 @@ void ui_detach_impl(UI *ui) } } -// Set scrolling region for window 'wp'. -// The region starts 'off' lines from the start of the window. -// Also set the vertical scroll region for a vertically split window. Always -// the full width of the window, excluding the vertical separator. -void ui_set_scroll_region(win_T *wp, int off) -{ - sr.top = wp->w_winrow + off; - sr.bot = wp->w_winrow + wp->w_height - 1; - - if (wp->w_width != Columns) { - sr.left = wp->w_wincol; - sr.right = wp->w_wincol + wp->w_width - 1; - } - - ui_call_set_scroll_region(sr.top, sr.bot, sr.left, sr.right); -} - -// Reset scrolling region to the whole screen. -void ui_reset_scroll_region(void) -{ - sr.top = 0; - sr.bot = (int)Rows - 1; - sr.left = 0; - sr.right = (int)Columns - 1; - ui_call_set_scroll_region(sr.top, sr.bot, sr.left, sr.right); -} - -void ui_set_highlight(int attr_code) +void ui_set_ext_option(UI *ui, UIExtension ext, bool active) { - if (current_attr_code == attr_code) { + if (ext < kUIGlobalCount) { + ui_refresh(); return; } - current_attr_code = attr_code; - - HlAttrs attrs = HLATTRS_INIT; - - if (attr_code != 0) { - HlAttrs *aep = syn_cterm_attr2entry(attr_code); - if (aep) { - attrs = *aep; - } + if (ui->option_set) { + ui->option_set(ui, cstr_as_string((char *)ui_ext_names[ext]), + BOOLEAN_OBJ(active)); } - - UI_CALL(highlight_set, attrs); -} - -void ui_clear_highlight(void) -{ - ui_set_highlight(0); } -void ui_puts(uint8_t *str) +void ui_line(int row, int startcol, int endcol, int clearcol, int clearattr) { - uint8_t *p = str; - uint8_t c; - - while ((c = *p)) { - if (c < 0x20) { - abort(); - } - - size_t clen = (size_t)mb_ptr2len(p); - ui_call_put((String){ .data = (char *)p, .size = clen }); - col++; - if (mb_ptr2cells(p) > 1) { - // double cell character, blank the next cell - ui_call_put((String)STRING_INIT); - col++; - } - if (utf_ambiguous_width(utf_ptr2char(p))) { - pending_cursor_update = true; - } - if (col >= width) { - ui_linefeed(); - } - p += clen; - - if (p_wd) { // 'writedelay': flush & delay each time. - ui_flush(); - uint64_t wd = (uint64_t)labs(p_wd); - os_microdelay(wd * 1000u, true); - } + size_t off = LineOffset[row]+(size_t)startcol; + UI_CALL(raw_line, 1, row, startcol, endcol, clearcol, clearattr, + (const schar_T *)ScreenLines+off, (const sattr_T *)ScreenAttrs+off); + if (p_wd) { // 'writedelay': flush & delay each time. + int old_row = row, old_col = col; + // If'writedelay is active, we set the cursor to highlight what was drawn + ui_cursor_goto(row, MIN(clearcol, (int)Columns-1)); + ui_flush(); + uint64_t wd = (uint64_t)labs(p_wd); + os_microdelay(wd * 1000u, true); + ui_cursor_goto(old_row, old_col); } } -void ui_putc(uint8_t c) -{ - uint8_t buf[2] = {c, 0}; - ui_puts(buf); -} - void ui_cursor_goto(int new_row, int new_col) { if (new_row == row && new_col == col) { @@ -463,6 +340,32 @@ void ui_cursor_goto(int new_row, int new_col) pending_cursor_update = true; } +void ui_add_linewrap(int row) +{ + // TODO(bfredl): check that this actually still works + // and move to TUI module in that case. +#if 0 + // First make sure we are at the end of the screen line, + // then output the same character again to let the + // terminal know about the wrap. If the terminal doesn't + // auto-wrap, we overwrite the character. + if (ui_current_col() != Columns) { + screen_char(LineOffset[row] + (unsigned)Columns - 1, row, + (int)(Columns - 1)); + } + + // When there is a multi-byte character, just output a + // space to keep it simple. */ + if (ScreenLines[LineOffset[row] + (Columns - 1)][1] != 0) { + ui_putc(' '); + } else { + ui_puts(ScreenLines[LineOffset[row] + (Columns - 1)]); + } + // force a redraw of the first char on the next line + ScreenAttrs[LineOffset[row+1]] = (sattr_T)-1; +#endif +} + void ui_mode_info_set(void) { Array style = mode_style_array(); @@ -484,30 +387,19 @@ int ui_current_col(void) void ui_flush(void) { cmdline_ui_flush(); - ui_call_flush(); -} - - -void ui_linefeed(void) -{ - int new_col = 0; - int new_row = row; - if (new_row < sr.bot) { - new_row++; - } else { - ui_call_scroll(1); - } - ui_cursor_goto(new_row, new_col); -} - -static void flush_cursor_update(void) -{ if (pending_cursor_update) { + ui_call_grid_cursor_goto(1, row, col); pending_cursor_update = false; - ui_call_cursor_goto(row, col); } + if (pending_mode_update) { + char *full_name = shape_table[mode_idx].full_name; + ui_call_mode_change(cstr_as_string(full_name), mode_idx); + pending_mode_update = false; + } + ui_call_flush(); } + /// Check if current mode has changed. /// May update the shape of the cursor. void ui_cursor_shape(void) @@ -515,12 +407,11 @@ void ui_cursor_shape(void) if (!full_screen) { return; } - int mode_idx = cursor_get_mode_idx(); + int new_mode_idx = cursor_get_mode_idx(); - if (old_mode_idx != mode_idx) { - old_mode_idx = mode_idx; - char *full_name = shape_table[mode_idx].full_name; - ui_call_mode_change(cstr_as_string(full_name), mode_idx); + if (new_mode_idx != mode_idx) { + mode_idx = new_mode_idx; + pending_mode_update = true; } conceal_check_cursur_line(); } diff --git a/src/nvim/ui.h b/src/nvim/ui.h index 6b04e9c67a..584d8a77c6 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -5,14 +5,18 @@ #include <stdbool.h> #include <stdint.h> -#include "api/private/defs.h" -#include "nvim/buffer_defs.h" +#include "nvim/globals.h" +#include "nvim/api/private/defs.h" +#include "nvim/highlight_defs.h" typedef enum { kUICmdline = 0, kUIPopupmenu, kUITabline, kUIWildmenu, +#define kUIGlobalCount (kUIWildmenu+1) + kUINewgrid, + kUIHlState, kUIExtCount, } UIExtension; @@ -20,7 +24,9 @@ EXTERN const char *ui_ext_names[] INIT(= { "ext_cmdline", "ext_popupmenu", "ext_tabline", - "ext_wildmenu" + "ext_wildmenu", + "ext_newgrid", + "ext_hlstate", }); @@ -31,9 +37,17 @@ struct ui_t { bool ui_ext[kUIExtCount]; ///< Externalized widgets int width, height; void *data; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ui_events.generated.h" #endif + + // For perfomance and simplicity, we use the dense screen representation + // in the bridge and the TUI. The remote_ui module will translate this + // in to the public grid_line format. + void (*raw_line)(UI *ui, Integer grid, Integer row, Integer startcol, + Integer endcol, Integer clearcol, Integer clearattr, + const schar_T *chunk, const sattr_T *attrs); void (*event)(UI *ui, char *name, Array args, bool *args_consumed); void (*stop)(UI *ui); void (*inspect)(UI *ui, Dictionary *info); diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 56db124a46..a96a24bde7 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -42,10 +42,9 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) rv->ui = ui; rv->bridge.rgb = ui->rgb; rv->bridge.stop = ui_bridge_stop; - rv->bridge.resize = ui_bridge_resize; - rv->bridge.clear = ui_bridge_clear; - rv->bridge.eol_clear = ui_bridge_eol_clear; - rv->bridge.cursor_goto = ui_bridge_cursor_goto; + rv->bridge.grid_resize = ui_bridge_grid_resize; + rv->bridge.grid_clear = ui_bridge_grid_clear; + rv->bridge.grid_cursor_goto = ui_bridge_grid_cursor_goto; rv->bridge.mode_info_set = ui_bridge_mode_info_set; rv->bridge.update_menu = ui_bridge_update_menu; rv->bridge.busy_start = ui_bridge_busy_start; @@ -53,10 +52,8 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) rv->bridge.mouse_on = ui_bridge_mouse_on; rv->bridge.mouse_off = ui_bridge_mouse_off; rv->bridge.mode_change = ui_bridge_mode_change; - rv->bridge.set_scroll_region = ui_bridge_set_scroll_region; - rv->bridge.scroll = ui_bridge_scroll; - rv->bridge.highlight_set = ui_bridge_highlight_set; - rv->bridge.put = ui_bridge_put; + rv->bridge.grid_scroll = ui_bridge_grid_scroll; + rv->bridge.hl_attr_define = ui_bridge_hl_attr_define; rv->bridge.bell = ui_bridge_bell; rv->bridge.visual_bell = ui_bridge_visual_bell; rv->bridge.default_colors_set = ui_bridge_default_colors_set; @@ -65,6 +62,7 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) rv->bridge.set_title = ui_bridge_set_title; rv->bridge.set_icon = ui_bridge_set_icon; rv->bridge.option_set = ui_bridge_option_set; + rv->bridge.raw_line = ui_bridge_raw_line; rv->scheduler = scheduler; for (UIExtension i = 0; (int)i < kUIExtCount; i++) { @@ -133,19 +131,45 @@ static void ui_bridge_stop_event(void **argv) ui->stop(ui); } -static void ui_bridge_highlight_set(UI *b, HlAttrs attrs) +static void ui_bridge_hl_attr_define(UI *ui, Integer id, HlAttrs attrs, + HlAttrs cterm_attrs, Array info) { HlAttrs *a = xmalloc(sizeof(HlAttrs)); *a = attrs; - UI_BRIDGE_CALL(b, highlight_set, 2, b, a); + UI_BRIDGE_CALL(ui, hl_attr_define, 3, ui, INT2PTR(id), a); } -static void ui_bridge_highlight_set_event(void **argv) +static void ui_bridge_hl_attr_define_event(void **argv) { UI *ui = UI(argv[0]); - ui->highlight_set(ui, *((HlAttrs *)argv[1])); - xfree(argv[1]); + Array info = ARRAY_DICT_INIT; + ui->hl_attr_define(ui, PTR2INT(argv[1]), *((HlAttrs *)argv[2]), + *((HlAttrs *)argv[2]), info); + xfree(argv[2]); } +static void ui_bridge_raw_line_event(void **argv) +{ + UI *ui = UI(argv[0]); + ui->raw_line(ui, PTR2INT(argv[1]), PTR2INT(argv[2]), PTR2INT(argv[3]), + PTR2INT(argv[4]), PTR2INT(argv[5]), PTR2INT(argv[6]), + argv[7], argv[8]); + xfree(argv[7]); + xfree(argv[8]); +} +static void ui_bridge_raw_line(UI *ui, Integer grid, Integer row, + Integer startcol, Integer endcol, + Integer clearcol, Integer clearattr, + const schar_T *chunk, const sattr_T *attrs) +{ + size_t ncol = (size_t)(endcol-startcol); + schar_T *c = xmemdup(chunk, ncol * sizeof(schar_T)); + sattr_T *hl = xmemdup(attrs, ncol * sizeof(sattr_T)); + UI_BRIDGE_CALL(ui, raw_line, 9, ui, INT2PTR(grid), INT2PTR(row), + INT2PTR(startcol), INT2PTR(endcol), INT2PTR(clearcol), + INT2PTR(clearattr), c, hl); +} + + static void ui_bridge_suspend(UI *b) { UIBridgeData *data = (UIBridgeData *)b; diff --git a/test/functional/api/version_spec.lua b/test/functional/api/version_spec.lua index 7bf54c0d1e..e0472977cc 100644 --- a/test/functional/api/version_spec.lua +++ b/test/functional/api/version_spec.lua @@ -156,6 +156,6 @@ describe("ui_options in metadata", function() local api = helpers.call('api_info') local options = api.ui_options eq({'rgb', 'ext_cmdline', 'ext_popupmenu', - 'ext_tabline', 'ext_wildmenu'}, options) + 'ext_tabline', 'ext_wildmenu', 'ext_newgrid', 'ext_hlstate'}, options) end) end) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index e4b343c123..ae8a8488d4 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -1242,6 +1242,8 @@ describe('API', function() ext_popupmenu = false, ext_tabline = false, ext_wildmenu = false, + ext_newgrid = screen._options.ext_newgrid or false, + ext_hlstate=false, height = 4, rgb = true, width = 20, @@ -1252,18 +1254,9 @@ describe('API', function() screen:detach() screen = Screen.new(44, 99) screen:attach({ rgb = false }) - expected = { - { - chan = 1, - ext_cmdline = false, - ext_popupmenu = false, - ext_tabline = false, - ext_wildmenu = false, - height = 99, - rgb = false, - width = 44, - } - } + expected[1].rgb = false + expected[1].width = 44 + expected[1].height = 99 eq(expected, nvim("list_uis")) end) end) diff --git a/test/functional/legacy/packadd_spec.lua b/test/functional/legacy/packadd_spec.lua index fb308475c0..67f6006d1d 100644 --- a/test/functional/legacy/packadd_spec.lua +++ b/test/functional/legacy/packadd_spec.lua @@ -58,6 +58,24 @@ describe('packadd', function() call assert_fails("packadd", 'E471:') endfunc + func Test_packadd_start() + let plugdir = expand(s:topdir . '/pack/mine/start/other') + call mkdir(plugdir . '/plugin', 'p') + set rtp& + let rtp = &rtp + filetype on + + exe 'split ' . plugdir . '/plugin/test.vim' + call setline(1, 'let g:plugin_works = 24') + wq + + packadd other + + call assert_equal(24, g:plugin_works) + call assert_true(len(&rtp) > len(rtp)) + call assert_true(&rtp =~ (escape(plugdir, '\') . '\($\|,\)')) + endfunc + func Test_packadd_noload() call mkdir(s:plugdir . '/plugin', 'p') call mkdir(s:plugdir . '/syntax', 'p') @@ -286,6 +304,11 @@ describe('packadd', function() expected_empty() end) + it('loads packages from "start" directory', function() + call('Test_packadd_start') + expected_empty() + end) + describe('command line completion', function() local Screen = require('test.functional.ui.screen') local screen diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index 787ec366b8..f6f3f02f45 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -118,6 +118,13 @@ describe('startup defaults', function() clear('-u', 'NORC', '-c', 'syntax off') eq(0, eval('exists("g:syntax_on")')) end) + + it('":if 0|syntax on|endif" does not affect default #8728', function() + clear('-u', 'NORC', '--cmd', ':if 0|syntax on|endif') + eq(1, eval('exists("g:syntax_on")')) + clear('-u', 'NORC', '--cmd', ':if 0|syntax off|endif') + eq(1, eval('exists("g:syntax_on")')) + end) end) describe("'fillchars'", function() diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 5603224975..c0404ff463 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -207,7 +207,7 @@ describe('tui', function() screen:set_default_attr_ids({ [1] = {reverse = true}, [2] = {foreground = 13, special = Screen.colors.Grey0}, - [3] = {special = Screen.colors.Grey0, bold = true, reverse = true}, + [3] = {bold = true, reverse = true, special = Screen.colors.Grey0}, [4] = {bold = true}, [5] = {special = Screen.colors.Grey0, reverse = true, foreground = 4}, [6] = {foreground = 4, special = Screen.colors.Grey0}, @@ -257,11 +257,11 @@ describe('tui', function() it('shows up in nvim_list_uis', function() feed_data(':echo map(nvim_list_uis(), {k,v -> sort(items(v))})\013') screen:expect([=[ - {5: }| - [[['ext_cmdline', v:false], ['ext_popupmenu', v:fa| - lse], ['ext_tabline', v:false], ['ext_wildmenu', v| - :false], ['height', 6], ['rgb', v:false], ['width'| - , 50]]] | + [[['ext_cmdline', v:false], ['ext_hlstate', v:fals| + e], ['ext_newgrid', v:true], ['ext_popupmenu', v:f| + alse], ['ext_tabline', v:false], ['ext_wildmenu', | + v:false], ['height', 6], ['rgb', v:false], ['width| + ', 50]]] | {10:Press ENTER or type command to continue}{1: } | {3:-- TERMINAL --} | ]=]) diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index 5ce49822e5..b2fc008dba 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -29,6 +29,9 @@ describe('external cmdline', function() if name == "cmdline_show" then local content, pos, firstc, prompt, indent, level = unpack(data) ok(level > 0) + for _,item in ipairs(content) do + item[1] = screen:get_hl(item[1]) + end cmdline[level] = {content=content, pos=pos, firstc=firstc, prompt=prompt, indent=indent} last_level = level @@ -87,6 +90,7 @@ describe('external cmdline', function() | ]], nil, nil, function() eq(1, last_level) + --print(require('inspect')(cmdline)) eq({{ content = { { {}, "" } }, firstc = ":", @@ -168,10 +172,10 @@ describe('external cmdline', function() it('from normal mode', function() feed(':') screen:expect([[ - | + ^ | {1:~ }| {1:~ }| - {3:c^ }| + {3:c }| | ]], nil, nil, function() eq({{ @@ -351,11 +355,11 @@ describe('external cmdline', function() -- redraw! forgets cursor position. Be OK with that, as UI should indicate -- focus is at external cmdline anyway. screen:expect([[ - | + ^ | {1:~ }| {1:~ }| {1:~ }| - ^ | + | ]], nil, nil, function() eq(expectation, cmdline) end) @@ -363,11 +367,11 @@ describe('external cmdline', function() feed('<cr>') screen:expect([[ - | + ^ | {1:~ }| {1:~ }| {1:~ }| - ^ | + | ]], nil, nil, function() eq({{ content = { { {}, "xx3" } }, @@ -424,11 +428,11 @@ describe('external cmdline', function() block = {} command("redraw!") screen:expect([[ - | + ^ | {1:~ }| {1:~ }| {1:~ }| - ^ | + | ]], nil, nil, function() eq({ { { {}, 'function Foo()'} }, { { {}, ' line1'} } }, block) @@ -528,9 +532,9 @@ describe('external cmdline', function() screen:expect([[ | {2:[No Name] }| - {1::}make | + {1::}make^ | {3:[Command Line] }| - ^ | + | ]], nil, nil, function() eq({nil, { content = { { {}, "yank" } }, @@ -572,11 +576,11 @@ describe('external cmdline', function() cmdline = {} command("redraw!") screen:expect([[ - | + ^ | {1:~ }| {1:~ }| {1:~ }| - ^ | + | ]], nil, nil, function() eq({{ content = { { {}, "make" } }, diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index b46a6c1e46..4b6fbc0d74 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -834,7 +834,7 @@ describe("'winhighlight' highlight", function() {1:a^a }| {2:~ }| {2:~ }| - {11:[No Name] [+] }| + {3:[No Name] [+] }| aa | {0:~ }| {4:[No Name] [+] }| @@ -846,7 +846,7 @@ describe("'winhighlight' highlight", function() {1:^ }| {2:~ }| {2:~ }| - {11:[No Name] }| + {3:[No Name] }| aa | {0:~ }| {4:[No Name] [+] }| @@ -891,7 +891,7 @@ describe("'winhighlight' highlight", function() {1:a^a }| {2:~ }| {2:~ }| - {11:[No Name] [+] }| + {3:[No Name] [+] }| aa | {0:~ }| {4:[No Name] [+] }| @@ -915,7 +915,7 @@ describe("'winhighlight' highlight", function() {1:^aa }| {2:~ }| {2:~ }| - {11:[No Name] [+] }| + {3:[No Name] [+] }| aa | {0:~ }| {4:[No Name] [+] }| @@ -931,10 +931,10 @@ describe("'winhighlight' highlight", function() {1:^ }| {2:~ }| {2:~ }| - {11:[No Name] }| + {3:[No Name] }| {5: }| {6:~ }| - {12:[No Name] }| + {4:[No Name] }| | ]]) @@ -943,10 +943,10 @@ describe("'winhighlight' highlight", function() {5: }| {6:~ }| {6:~ }| - {12:[No Name] }| + {4:[No Name] }| {1:^ }| {2:~ }| - {11:[No Name] }| + {3:[No Name] }| | ]]) @@ -955,10 +955,10 @@ describe("'winhighlight' highlight", function() {1:^ }| {2:~ }| {2:~ }| - {11:[No Name] }| + {3:[No Name] }| {5: }| {6:~ }| - {12:[No Name] }| + {4:[No Name] }| | ]]) end) @@ -974,7 +974,7 @@ describe("'winhighlight' highlight", function() {3:[No Name] }| {7: }| {8:~ }| - {13:[No Name] }| + {4:[No Name] }| | ]]) @@ -983,7 +983,7 @@ describe("'winhighlight' highlight", function() {7: }| {8:~ }| {8:~ }| - {13:[No Name] }| + {4:[No Name] }| ^ | {0:~ }| {3:[No Name] }| @@ -997,10 +997,10 @@ describe("'winhighlight' highlight", function() {7: }| {8:~ }| {8:~ }| - {13:[No Name] }| + {4:[No Name] }| {1:^ }| {2:~ }| - {11:[No Name] }| + {3:[No Name] }| | ]]) @@ -1012,7 +1012,7 @@ describe("'winhighlight' highlight", function() {3:[No Name] }| {1: }| {2:~ }| - {14:[No Name] }| + {4:[No Name] }| | ]]) @@ -1022,10 +1022,10 @@ describe("'winhighlight' highlight", function() {7: }| {8:~ }| {8:~ }| - {13:[No Name] }| + {4:[No Name] }| {1:^ }| {2:~ }| - {11:[No Name] }| + {3:[No Name] }| | ]]) @@ -1037,7 +1037,7 @@ describe("'winhighlight' highlight", function() {3:[No Name] }| {5: }| {6:~ }| - {12:[No Name] }| + {4:[No Name] }| | ]]) end) diff --git a/test/functional/ui/hlstate_spec.lua b/test/functional/ui/hlstate_spec.lua new file mode 100644 index 0000000000..672af5fb22 --- /dev/null +++ b/test/functional/ui/hlstate_spec.lua @@ -0,0 +1,287 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local clear, insert = helpers.clear, helpers.insert +local command = helpers.command +local meths = helpers.meths +local iswin = helpers.iswin +local nvim_dir = helpers.nvim_dir +local thelpers = require('test.functional.terminal.helpers') + +describe('ext_hlstate detailed highlights', function() + local screen + + before_each(function() + clear() + command('syntax on') + screen = Screen.new(40, 8) + screen:attach({ext_hlstate=true}) + end) + + after_each(function() + screen:detach() + end) + + + it('work with combined UI and syntax highlights', function() + insert([[ + these are some lines + with colorful text]]) + meths.buf_add_highlight(0, -1, "String", 0 , 10, 14) + meths.buf_add_highlight(0, -1, "Statement", 1 , 5, -1) + command("/th co") + + screen:expect([[ + these are {1:some} lines | + ^wi{2:th }{4:co}{3:lorful text} | + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {6:search hit BOTTOM, continuing at TOP} | + ]], { + [1] = {{foreground = Screen.colors.Magenta}, + {{hi_name = "Constant", kind = "syntax"}}}, + [2] = {{background = Screen.colors.Yellow}, + {{hi_name = "Search", ui_name = "Search", kind = "ui"}}}, + [3] = {{bold = true, foreground = Screen.colors.Brown}, + {{hi_name = "Statement", kind = "syntax"}}}, + [4] = {{bold = true, background = Screen.colors.Yellow, foreground = Screen.colors.Brown}, {3, 2}}, + [5] = {{bold = true, foreground = Screen.colors.Blue1}, + {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}}, + [6] = {{foreground = Screen.colors.Red}, + {{hi_name = "WarningMsg", ui_name = "WarningMsg", kind = "ui"}}}, + }) + end) + + it('work with cleared UI highlights', function() + screen:set_default_attr_ids({ + [1] = {{}, {{hi_name = "VertSplit", ui_name = "VertSplit", kind = "ui"}}}, + [2] = {{bold = true, foreground = Screen.colors.Blue1}, + {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}}, + [3] = {{bold = true, reverse = true}, + {{hi_name = "StatusLine", ui_name = "StatusLine", kind = "ui"}}} , + [4] = {{reverse = true}, + {{hi_name = "StatusLineNC", ui_name = "StatusLineNC" , kind = "ui"}}}, + [5] = {{}, {{hi_name = "StatusLine", ui_name = "StatusLine", kind = "ui"}}}, + [6] = {{}, {{hi_name = "StatusLineNC", ui_name = "StatusLineNC", kind = "ui"}}}, + }) + command("hi clear VertSplit") + command("vsplit") + + screen:expect([[ + ^ {1:│} | + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {3:[No Name] }{4:[No Name] }| + | + ]]) + + command("hi clear StatusLine | hi clear StatuslineNC") + screen:expect([[ + ^ {1:│} | + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {5:[No Name] }{6:[No Name] }| + | + ]]) + + -- redrawing is done even if visible highlights didn't change + command("wincmd w") + screen:expect([[ + {1:│}^ | + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {6:[No Name] }{5:[No Name] }| + | + ]]) + + end) + + it("work with window-local highlights", function() + screen:set_default_attr_ids({ + [1] = {{foreground = Screen.colors.Brown}, {{hi_name = "LineNr", ui_name = "LineNr", kind = "ui"}}}, + [2] = {{bold = true, foreground = Screen.colors.Blue1}, {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}}, + [3] = {{bold = true, reverse = true}, {{hi_name = "StatusLine", ui_name = "StatusLine", kind = "ui"}}}, + [4] = {{reverse = true}, {{hi_name = "StatusLineNC", ui_name = "StatusLineNC", kind = "ui"}}}, + [5] = {{background = Screen.colors.Red, foreground = Screen.colors.Grey100}, {{hi_name = "ErrorMsg", ui_name = "LineNr", kind = "ui"}}}, + [6] = {{bold = true, reverse = true}, {{hi_name = "MsgSeparator", ui_name = "Normal", kind = "ui"}}}, + [7] = {{foreground = Screen.colors.Brown, bold = true, reverse = true}, {6, 1}}, + [8] = {{foreground = Screen.colors.Blue1, bold = true, reverse = true}, {6, 2}}, + [9] = {{bold = true, foreground = Screen.colors.Brown}, {{hi_name = "Statement", ui_name = "NormalNC", kind = "ui"}}}, + [10] = {{bold = true, foreground = Screen.colors.Brown}, {9, 1}}, + [11] = {{bold = true, foreground = Screen.colors.Blue1}, {9, 2}} + }) + + command("set number") + command("split") + -- NormalNC is not applied if not set, to avoid spurious redraws + screen:expect([[ + {1: 1 }^ | + {2:~ }| + {2:~ }| + {3:[No Name] }| + {1: 1 } | + {2:~ }| + {4:[No Name] }| + | + ]]) + + command("set winhl=LineNr:ErrorMsg") + screen:expect([[ + {5: 1 }^ | + {2:~ }| + {2:~ }| + {3:[No Name] }| + {1: 1 } | + {2:~ }| + {4:[No Name] }| + | + ]]) + + command("set winhl=Normal:MsgSeparator,NormalNC:Statement") + screen:expect([[ + {7: 1 }{6:^ }| + {8:~ }| + {8:~ }| + {3:[No Name] }| + {1: 1 } | + {2:~ }| + {4:[No Name] }| + | + ]]) + + command("wincmd w") + screen:expect([[ + {10: 1 }{9: }| + {11:~ }| + {11:~ }| + {4:[No Name] }| + {1: 1 }^ | + {2:~ }| + {3:[No Name] }| + | + ]]) + end) + + it("work with :terminal", function() + screen:set_default_attr_ids({ + [1] = {{}, {{hi_name = "TermCursorNC", ui_name = "TermCursorNC", kind = "ui"}}}, + [2] = {{special = Screen.colors.Grey0, foreground = 52479}, {{kind = "term"}}}, + [3] = {{special = Screen.colors.Grey0, bold = true, foreground = 52479}, {{kind = "term"}}}, + [4] = {{special = Screen.colors.Grey0, foreground = 52479}, {2, 1}}, + [5] = {{special = Screen.colors.Grey0, foreground = 4259839}, {{kind = "term"}}}, + [6] = {{special = Screen.colors.Grey0, foreground = 4259839}, {5, 1}}, + }) + command('enew | call termopen(["'..nvim_dir..'/tty-test"])') + screen:expect([[ + ^tty ready | + {1: } | + | + | + | + | + | + | + ]]) + + thelpers.feed_data('x ') + thelpers.set_fg(45) + thelpers.feed_data('y ') + thelpers.set_bold() + thelpers.feed_data('z\n') + -- TODO(bfredl): check if this distinction makes sense + if iswin() then + screen:expect([[ + ^tty ready | + x {5:y z} | + {1: } | + | + | + | + | + | + ]]) + else + screen:expect([[ + ^tty ready | + x {2:y }{3:z} | + {1: } | + | + | + | + | + | + ]]) + end + + thelpers.feed_termcode("[A") + thelpers.feed_termcode("[2C") + if iswin() then + screen:expect([[ + ^tty ready | + x {6:y}{5: z} | + | + | + | + | + | + | + ]]) + else + screen:expect([[ + ^tty ready | + x {4:y}{2: }{3:z} | + | + | + | + | + | + | + ]]) + end + end) + + it("can use independent cterm and rgb colors", function() + -- tell test module to save all attributes (doesn't change nvim options) + screen:set_hlstate_cterm(true) + + screen:set_default_attr_ids({ + [1] = {{bold = true, foreground = Screen.colors.Blue1}, {foreground = 12}, {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}}, + [2] = {{reverse = true, foreground = Screen.colors.Red}, {foreground = 10, italic=true}, {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}}, + }) + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + + command("hi NonText guifg=Red gui=reverse ctermfg=Green cterm=italic") + screen:expect([[ + ^ | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]) + + end) +end) diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua index 62b08c0967..322a94763f 100644 --- a/test/functional/ui/options_spec.lua +++ b/test/functional/ui/options_spec.lua @@ -30,10 +30,15 @@ describe('ui receives option updates', function() ext_popupmenu=false, ext_tabline=false, ext_wildmenu=false, + ext_newgrid=false, + ext_hlstate=false, } it("for defaults", function() screen:attach() + -- NB: UI test suite can be run in both "newgrid" and legacy grid mode. + -- In both cases check that the received value is the one requested. + defaults.ext_newgrid = screen._options.ext_newgrid or false screen:expect(function() eq(defaults, screen.options) end) @@ -41,6 +46,7 @@ describe('ui receives option updates', function() it("when setting options", function() screen:attach() + defaults.ext_newgrid = screen._options.ext_newgrid or false local changed = {} for k,v in pairs(defaults) do changed[k] = v @@ -89,6 +95,7 @@ describe('ui receives option updates', function() end screen:attach({ext_cmdline=true, ext_wildmenu=true}) + defaults.ext_newgrid = screen._options.ext_newgrid or false changed.ext_cmdline = true changed.ext_wildmenu = true screen:expect(function() diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 7607131e9b..d71d8cf3a8 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -142,6 +142,10 @@ function Screen.new(width, height) _default_attr_ignore = nil, _mouse_enabled = true, _attrs = {}, + _hl_info = {}, + _attr_table = {[0]={{},{}}}, + _clear_attrs = {}, + _new_attrs = false, _cursor = { row = 1, col = 1 }, @@ -159,10 +163,19 @@ function Screen:set_default_attr_ignore(attr_ignore) self._default_attr_ignore = attr_ignore end +function Screen:set_hlstate_cterm(val) + self._hlstate_cterm = val +end + function Screen:attach(options) if options == nil then options = {rgb=true} end + if options.ext_newgrid == nil then + options.ext_newgrid = true + end + self._options = options + self._clear_attrs = (options.ext_newgrid and {{},{}}) or {} uimeths.attach(self._width, self._height, options) end @@ -176,6 +189,7 @@ end function Screen:set_option(option, value) uimeths.set_option(option, value) + self._options[option] = value end -- Asserts that `expected` eventually matches the screen state. @@ -210,6 +224,11 @@ function Screen:expect(expected, attr_ids, attr_ignore, condition, any) end local ids = attr_ids or self._default_attr_ids local ignore = attr_ignore or self._default_attr_ignore + local id_to_index + if self._options.ext_hlstate then + id_to_index = self:hlstate_check_attrs(ids or {}) + end + self._new_attrs = false self:wait(function() if condition ~= nil then local status, res = pcall(condition) @@ -223,9 +242,14 @@ function Screen:expect(expected, attr_ids, attr_ignore, condition, any) .. ') differs from configured height(' .. self._height .. ') of Screen.') end + if self._options.ext_hlstate and self._new_attrs then + id_to_index = self:hlstate_check_attrs(ids or {}) + end + + local info = self._options.ext_hlstate and id_to_index or ids local actual_rows = {} for i = 1, self._height do - actual_rows[i] = self:_row_repr(self._rows[i], ids, ignore) + actual_rows[i] = self:_row_repr(self._rows[i], info, ignore) end if expected == nil then @@ -339,7 +363,7 @@ function Screen:_handle_resize(width, height) for _ = 1, height do local cols = {} for _ = 1, width do - table.insert(cols, {text = ' ', attrs = {}}) + table.insert(cols, {text = ' ', attrs = self._clear_attrs, hl_id = 0}) end table.insert(rows, cols) end @@ -353,14 +377,24 @@ function Screen:_handle_resize(width, height) } end +function Screen:_handle_grid_resize(grid, width, height) + assert(grid == 1) + self:_handle_resize(width, height) +end + + function Screen:_handle_mode_info_set(cursor_style_enabled, mode_info) self._cursor_style_enabled = cursor_style_enabled self._mode_info = mode_info end function Screen:_handle_clear() - self:_clear_block(self._scroll_region.top, self._scroll_region.bot, - self._scroll_region.left, self._scroll_region.right) + self:_clear_block(1, self._height, 1, self._width) +end + +function Screen:_handle_grid_clear(grid) + assert(grid == 1) + self:_handle_clear() end function Screen:_handle_eol_clear() @@ -373,6 +407,12 @@ function Screen:_handle_cursor_goto(row, col) self._cursor.col = col + 1 end +function Screen:_handle_grid_cursor_goto(grid, row, col) + assert(grid == 1) + self._cursor.row = row + 1 + self._cursor.col = col + 1 +end + function Screen:_handle_busy_start() self._busy = true end @@ -425,6 +465,7 @@ function Screen:_handle_scroll(count) for j = left, right do target[j].text = source[j].text target[j].attrs = source[j].attrs + target[j].hl_id = source[j].hl_id end end @@ -434,6 +475,28 @@ function Screen:_handle_scroll(count) end end +function Screen:_handle_grid_scroll(grid, top, bot, left, right, rows, cols) + assert(grid == 1) + assert(cols == 0) + -- TODO: if we truly believe we should translate the other way + self:_handle_set_scroll_region(top,bot-1,left,right-1) + self:_handle_scroll(rows) +end + +function Screen:_handle_hl_attr_define(id, rgb_attrs, cterm_attrs, info) + self._attr_table[id] = {rgb_attrs, cterm_attrs} + self._hl_info[id] = info + self._new_attrs = true +end + +function Screen:get_hl(val) + if self._options.ext_newgrid then + return self._attr_table[val][1] + else + return val + end +end + function Screen:_handle_highlight_set(attrs) self._attrs = attrs end @@ -442,9 +505,32 @@ function Screen:_handle_put(str) local cell = self._rows[self._cursor.row][self._cursor.col] cell.text = str cell.attrs = self._attrs + cell.hl_id = -1 self._cursor.col = self._cursor.col + 1 end +function Screen:_handle_grid_line(grid, row, col, items) + assert(grid == 1) + local line = self._rows[row+1] + local colpos = col+1 + local hl = self._clear_attrs + local hl_id = 0 + for _,item in ipairs(items) do + local text, hl_id_cell, count = unpack(item) + if hl_id_cell ~= nil then + hl_id = hl_id_cell + hl = self._attr_table[hl_id] + end + for _ = 1, (count or 1) do + local cell = line[colpos] + cell.text = text + cell.hl_id = hl_id + cell.attrs = hl + colpos = colpos+1 + end + end +end + function Screen:_handle_bell() self.bell = true end @@ -498,7 +584,7 @@ function Screen:_clear_row_section(rownum, startcol, stopcol) local row = self._rows[rownum] for i = startcol, stopcol do row[i].text = ' ' - row[i].attrs = {} + row[i].attrs = self._clear_attrs end end @@ -506,7 +592,11 @@ function Screen:_row_repr(row, attr_ids, attr_ignore) local rv = {} local current_attr_id for i = 1, self._width do - local attr_id = self:_get_attr_id(attr_ids, attr_ignore, row[i].attrs) + local attrs = row[i].attrs + if self._options.ext_newgrid then + attrs = attrs[(self._options.rgb and 1) or 2] + end + local attr_id = self:_get_attr_id(attr_ids, attr_ignore, attrs, row[i].hl_id) if current_attr_id and attr_id ~= current_attr_id then -- close current attribute bracket, add it before any whitespace -- up to the current cell @@ -573,22 +663,33 @@ function Screen:print_snapshot(attrs, ignore) if ignore == nil then ignore = self._default_attr_ignore end + local id_to_index = {} if attrs == nil then attrs = {} if self._default_attr_ids ~= nil then for i, a in pairs(self._default_attr_ids) do attrs[i] = a end + if self._options.ext_hlstate then + id_to_index = self:hlstate_check_attrs(attrs) + end end if ignore ~= true then for i = 1, self._height do local row = self._rows[i] for j = 1, self._width do - local attr = row[j].attrs - if self:_attr_index(attrs, attr) == nil and self:_attr_index(ignore, attr) == nil then - if not self:_equal_attrs(attr, {}) then - table.insert(attrs, attr) + if self._options.ext_hlstate then + local hl_id = row[j].hl_id + if hl_id ~= 0 then + self:_insert_hl_id(attrs, id_to_index, hl_id) + end + else + local attr = row[j].attrs + if self:_attr_index(attrs, attr) == nil and self:_attr_index(ignore, attr) == nil then + if not self:_equal_attrs(attr, {}) then + table.insert(attrs, attr) + end end end end @@ -597,8 +698,9 @@ function Screen:print_snapshot(attrs, ignore) end local rv = {} + local info = self._options.ext_hlstate and id_to_index or attrs for i = 1, self._height do - table.insert(rv, " "..self:_row_repr(self._rows[i],attrs, ignore).."|") + table.insert(rv, " "..self:_row_repr(self._rows[i], info, ignore).."|") end local attrstrs = {} local alldefault = true @@ -606,7 +708,12 @@ function Screen:print_snapshot(attrs, ignore) if self._default_attr_ids == nil or self._default_attr_ids[i] ~= a then alldefault = false end - local dict = "{"..self:_pprint_attrs(a).."}" + local dict + if self._options.ext_hlstate then + dict = self:_pprint_hlstate(a) + else + dict = "{"..self:_pprint_attrs(a).."}" + end table.insert(attrstrs, "["..tostring(i).."] = "..dict) end local attrstr = "{"..table.concat(attrstrs, ", ").."}" @@ -620,6 +727,117 @@ function Screen:print_snapshot(attrs, ignore) io.stdout:flush() end +function Screen:_insert_hl_id(attrs, id_to_index, hl_id) + if id_to_index[hl_id] ~= nil then + return id_to_index[hl_id] + end + local raw_info = self._hl_info[hl_id] + local info = {} + if #raw_info > 1 then + for i, item in ipairs(raw_info) do + info[i] = self:_insert_hl_id(attrs, id_to_index, item.id) + end + else + info[1] = {} + for k, v in pairs(raw_info[1]) do + if k ~= "id" then + info[1][k] = v + end + end + end + + local entry = self._attr_table[hl_id] + local attrval + if self._hlstate_cterm then + attrval = {entry[1], entry[2], info} -- unpack() doesn't work + else + attrval = {entry[1], info} + end + + + table.insert(attrs, attrval) + id_to_index[hl_id] = #attrs + return #attrs +end + +function Screen:hlstate_check_attrs(attrs) + local id_to_index = {} + for i = 1,#self._attr_table do + local iinfo = self._hl_info[i] + local matchinfo = {} + if #iinfo > 1 then + for k,item in ipairs(iinfo) do + matchinfo[k] = id_to_index[item.id] + end + else + matchinfo = iinfo + end + for k,v in pairs(attrs) do + local attr, info, attr_rgb, attr_cterm + if self._hlstate_cterm then + attr_rgb, attr_cterm, info = unpack(v) + attr = {attr_rgb, attr_cterm} + else + attr, info = unpack(v) + end + if self:_equal_attr_def(attr, self._attr_table[i]) then + if #info == #matchinfo then + local match = false + if #info == 1 then + if self:_equal_info(info[1],matchinfo[1]) then + match = true + end + else + match = true + for j = 1,#info do + if info[j] ~= matchinfo[j] then + match = false + end + end + end + if match then + id_to_index[i] = k + end + end + end + end + end + return id_to_index +end + + +function Screen:_pprint_hlstate(item) + --print(require('inspect')(item)) + local attrdict = "{"..self:_pprint_attrs(item[1]).."}, " + local attrdict2, hlinfo + if self._hlstate_cterm then + attrdict2 = "{"..self:_pprint_attrs(item[2]).."}, " + hlinfo = item[3] + else + attrdict2 = "" + hlinfo = item[2] + end + local descdict = "{"..self:_pprint_hlinfo(hlinfo).."}" + return "{"..attrdict..attrdict2..descdict.."}" +end + +function Screen:_pprint_hlinfo(states) + if #states == 1 then + local items = {} + for f, v in pairs(states[1]) do + local desc = tostring(v) + if type(v) == type("") then + desc = '"'..desc..'"' + end + table.insert(items, f.." = "..desc) + end + return "{"..table.concat(items, ", ").."}" + else + return table.concat(states, ", ") + end +end + + function Screen:_pprint_attrs(attrs) local items = {} for f, v in pairs(attrs) do @@ -643,32 +861,53 @@ local function backward_find_meaningful(tbl, from) -- luacheck: no unused return from end -function Screen:_get_attr_id(attr_ids, ignore, attrs) +function Screen:_get_attr_id(attr_ids, ignore, attrs, hl_id) if not attr_ids then return end - for id, a in pairs(attr_ids) do - if self:_equal_attrs(a, attrs) then - return id - end + + if self._options.ext_hlstate then + local id = attr_ids[hl_id] + if id ~= nil or hl_id == 0 then + return id + end + return "UNEXPECTED "..self:_pprint_attrs(self._attr_table[hl_id][1]) + else + for id, a in pairs(attr_ids) do + if self:_equal_attrs(a, attrs) then + return id + end + end + if self:_equal_attrs(attrs, {}) or + ignore == true or self:_attr_index(ignore, attrs) ~= nil then + -- ignore this attrs + return nil + end + return "UNEXPECTED "..self:_pprint_attrs(attrs) end - if self:_equal_attrs(attrs, {}) or - ignore == true or self:_attr_index(ignore, attrs) ~= nil then - -- ignore this attrs - return nil +end + +function Screen:_equal_attr_def(a, b) + if self._hlstate_cterm then + return self:_equal_attrs(a[1],b[1]) and self:_equal_attrs(a[2],b[2]) + else + return self:_equal_attrs(a,b[1]) end - return "UNEXPECTED "..self:_pprint_attrs(attrs) end function Screen:_equal_attrs(a, b) return a.bold == b.bold and a.standout == b.standout and a.underline == b.underline and a.undercurl == b.undercurl and a.italic == b.italic and a.reverse == b.reverse and - a.foreground == b.foreground and - a.background == b.background and + a.foreground == b.foreground and a.background == b.background and a.special == b.special end +function Screen:_equal_info(a, b) + return a.kind == b.kind and a.hi_name == b.hi_name and + a.ui_name == b.ui_name +end + function Screen:_attr_index(attrs, attr) if not attrs then return nil diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index 6f04cde4d4..75a2d4978d 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -48,13 +48,13 @@ describe('screen', function() end) end) -describe('Screen', function() +local function screen_tests(newgrid) local screen before_each(function() clear() screen = Screen.new() - screen:attach() + screen:attach({rgb=true,ext_newgrid=newgrid}) screen:set_default_attr_ids( { [0] = {bold=true, foreground=255}, [1] = {bold=true, reverse=true}, @@ -741,4 +741,12 @@ describe('Screen', function() | ]]) end) +end + +describe("Screen (char-based)", function() + screen_tests(false) +end) + +describe("Screen (line-based)", function() + screen_tests(true) end) |