diff options
Diffstat (limited to 'src')
90 files changed, 3053 insertions, 1379 deletions
diff --git a/src/coverity-model.c b/src/coverity-model.c index 3c38e4ae4d..2fd55c332c 100644 --- a/src/coverity-model.c +++ b/src/coverity-model.c @@ -34,32 +34,6 @@ int uv_pipe_open(struct uv_pipe_s *handle, int fd) return result; } -// Issue 2422 -// -// Teach coverity about jemalloc functions, so that it understands -// they are equivalent to malloc ones. - -void *je_malloc(size_t size) -{ - return __coverity_alloc__(size); -} - -void je_free(void *ptr) -{ - __coverity_free__(ptr); -} - -void *je_calloc(size_t count, size_t size) -{ - return je_malloc(count * size); -} - -void *je_realloc(void *ptr, size_t size) -{ - je_free(ptr); - return je_malloc(size); -} - // Hint Coverity that adding item to d avoids losing track // of the memory allocated for item. typedef struct {} dictitem_T; diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index a2c4e677d4..8abc43c2aa 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -23,6 +23,7 @@ set(GENERATOR_DIR ${CMAKE_CURRENT_LIST_DIR}/generators) set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto) set(API_DISPATCH_GENERATOR ${GENERATOR_DIR}/gen_api_dispatch.lua) set(API_UI_EVENTS_GENERATOR ${GENERATOR_DIR}/gen_api_ui_events.lua) +set(GENERATOR_C_GRAMMAR ${GENERATOR_DIR}/c_grammar.lua) set(API_METADATA ${PROJECT_BINARY_DIR}/api_metadata.mpack) set(FUNCS_DATA ${PROJECT_BINARY_DIR}/funcs_data.mpack) set(MSGPACK_LUA_C_BINDINGS ${GENERATED_DIR}/msgpack_lua_c_bindings.generated.c) @@ -269,6 +270,7 @@ add_custom_command( ${API_HEADERS} ${MSGPACK_RPC_HEADERS} ${API_DISPATCH_GENERATOR} + ${GENERATOR_C_GRAMMAR} ${CMAKE_CURRENT_LIST_DIR}/api/dispatch_deprecated.lua ) @@ -300,6 +302,7 @@ add_custom_command( ${GENERATED_UI_EVENTS_METADATA} DEPENDS ${API_UI_EVENTS_GENERATOR} + ${GENERATOR_C_GRAMMAR} ${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h ) @@ -397,11 +400,6 @@ endif() set(NVIM_EXEC_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES} ${LUA_PREFERRED_LIBRARIES}) -# Don't use jemalloc in the unit test library. -if(JEMALLOC_FOUND) - list(APPEND NVIM_EXEC_LINK_LIBRARIES ${JEMALLOC_LIBRARIES}) -endif() - if(POLICY CMP0069) cmake_policy(SET CMP0069 NEW) endif() diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 5df0f0bb47..9cd178eaeb 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -104,7 +104,7 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) /// the whole buffer. If so, the first notification will be a /// `nvim_buf_lines_event`. Otherwise, the first notification will be /// a `nvim_buf_changedtick_event` -/// @param opts Optional parameters. Currently not used. +/// @param opts Optional parameters. Reserved for future use. /// @param[out] err Details of an error that may have occurred /// @return False when updates couldn't be enabled because the buffer isn't /// loaded or `opts` contained an invalid key; otherwise True. diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 82c9a1da67..19a3368c1c 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -27,6 +27,7 @@ #include "nvim/version.h" #include "nvim/lib/kvec.h" #include "nvim/getchar.h" +#include "nvim/fileio.h" #include "nvim/ui.h" /// Helper structure for vim_to_object @@ -711,6 +712,12 @@ String cbuf_to_string(const char *buf, size_t size) }; } +String cstrn_to_string(const char *str, size_t maxsize) + FUNC_ATTR_NONNULL_ALL +{ + return cbuf_to_string(str, strnlen(str, maxsize)); +} + /// Creates a String using the given C string. Unlike /// cstr_to_string this function DOES NOT copy the C string. /// @@ -725,6 +732,18 @@ String cstr_as_string(char *str) FUNC_ATTR_PURE return (String){ .data = str, .size = strlen(str) }; } +/// Return the owned memory of a ga as a String +/// +/// Reinitializes the ga to a valid empty state. +String ga_take_string(garray_T *ga) +{ + String str = { .data = (char *)ga->ga_data, .size = (size_t)ga->ga_len }; + ga->ga_data = NULL; + ga->ga_len = 0; + ga->ga_maxlen = 0; + return str; +} + /// Collects `n` buffer lines into array `l`, optionally replacing newlines /// with NUL. /// @@ -1094,7 +1113,7 @@ static void set_option_value_for(char *key, { win_T *save_curwin = NULL; tabpage_T *save_curtab = NULL; - bufref_T save_curbuf = { NULL, 0, 0 }; + aco_save_T aco; try_start(); switch (opt_type) @@ -1115,9 +1134,9 @@ static void set_option_value_for(char *key, restore_win(save_curwin, save_curtab, true); break; case SREQ_BUF: - switch_buffer(&save_curbuf, (buf_T *)from); + aucmd_prepbuf(&aco, (buf_T *)from); set_option_value_err(key, numval, stringval, opt_flags, err); - restore_buffer(&save_curbuf); + aucmd_restbuf(&aco); break; case SREQ_GLOBAL: set_option_value_err(key, numval, stringval, opt_flags, err); diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 7ba5251c60..9e9be588e3 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -113,6 +113,8 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, ui->set_title = remote_ui_set_title; ui->set_icon = remote_ui_set_icon; ui->option_set = remote_ui_option_set; + ui->win_scroll_over_start = remote_ui_win_scroll_over_start; + ui->win_scroll_over_reset = remote_ui_win_scroll_over_reset; ui->event = remote_ui_event; ui->inspect = remote_ui_inspect; @@ -130,6 +132,13 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, ui->ui_ext[kUILinegrid] = true; } + if (ui->ui_ext[kUIMessages]) { + // This uses attribute indicies, so ext_linegrid is needed. + ui->ui_ext[kUILinegrid] = true; + // Cmdline uses the messages area, so it should be externalized too. + ui->ui_ext[kUICmdline] = true; + } + UIData *data = xmalloc(sizeof(UIData)); data->channel_id = channel_id; data->buffer = (Array)ARRAY_DICT_INIT; @@ -208,8 +217,9 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, return; } ui->rgb = value.data.boolean; - // A little drastic, but only legacy uis need to use this option - if (!init) { + // A little drastic, but only takes effect for legacy uis. For linegrid UI + // only changes metadata for nvim_list_uis(), no refresh needed. + if (!init && !ui->ui_ext[kUILinegrid]) { ui_refresh(); } return; @@ -245,9 +255,8 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, name.data); } -/// Tell nvim to resize a grid. Nvim sends grid_resize event with the -/// requested grid size is within size limits and with maximum allowed size -/// otherwise. +/// Tell Nvim to resize a grid. Triggers a grid_resize event with the requested +/// grid size or the maximum size if it exceeds size limits. /// /// On invalid grid handle, fails with error. /// @@ -354,6 +363,9 @@ static void remote_ui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, Integer cterm_fg, Integer cterm_bg) { + if (!ui->ui_ext[kUITermColors]) { + HL_SET_DEFAULT_COLORS(rgb_fg, rgb_bg, rgb_sp); + } Array args = ARRAY_DICT_INIT; ADD(args, INTEGER_OBJ(rgb_fg)); ADD(args, INTEGER_OBJ(rgb_bg)); @@ -460,7 +472,7 @@ static void remote_ui_put(UI *ui, const char *cell) static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol, Integer clearcol, Integer clearattr, - Boolean wrap, const schar_T *chunk, + LineFlags flags, const schar_T *chunk, const sattr_T *attrs) { UIData *data = ui->data; diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 59a7780651..b57cf8d3ef 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -38,6 +38,9 @@ void set_icon(String icon) FUNC_API_SINCE(3); void option_set(String name, Object value) FUNC_API_SINCE(4) FUNC_API_BRIDGE_IMPL; +// Stop event is not exported as such, represented by EOF in the msgpack stream. +void stop(void) + FUNC_API_NOEXPORT; // First revison of the grid protocol, used by default void update_fg(Integer fg) @@ -71,28 +74,39 @@ 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; + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; void grid_clear(Integer grid) - FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; void grid_cursor_goto(Integer grid, Integer row, Integer col) - FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_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; + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; void grid_destroy(Integer grid) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; +// For perfomance and simplicity, we use the dense screen representation +// in internal code, such as compositor and TUI. The remote_ui module will +// translate this in to the public grid_line format. +void raw_line(Integer grid, Integer row, Integer startcol, + Integer endcol, Integer clearcol, Integer clearattr, + LineFlags flags, const schar_T *chunk, const sattr_T *attrs) + FUNC_API_NOEXPORT FUNC_API_COMPOSITOR_IMPL; + +void event(char *name, Array args, bool *args_consumed) + FUNC_API_NOEXPORT; + void win_pos(Integer grid, Integer win, Integer startrow, Integer startcol, Integer width, Integer height) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; void win_hide(Integer grid) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; void win_scroll_over_start(void) - FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; + FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL; void win_scroll_over_reset(void) - FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; + FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL; void popupmenu_show(Array items, Integer selected, Integer row, Integer col, Integer grid) @@ -127,4 +141,17 @@ void wildmenu_select(Integer selected) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void wildmenu_hide(void) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; + +void msg_show(String kind, Array content, Boolean replace_last) + FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; +void msg_clear(void) + FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; +void msg_showcmd(Array content) + FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; +void msg_showmode(Array content) + FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; +void msg_ruler(Array content) + FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; +void msg_history_show(Array entries) + FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; #endif // NVIM_API_UI_EVENTS_IN_H diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index ce7ef681ef..5a4d0a11e7 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -198,6 +198,9 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi) /// @note |keycodes| like <CR> are translated, so "<" is special. /// To input a literal "<", send <LT>. /// +/// @note For mouse events use |nvim_input_mouse()|. The pseudokey form +/// "<LeftMouse><col,row>" is deprecated since |api-level| 6. +/// /// @param keys to be typed /// @return Number of bytes actually written (can be fewer than /// requested if the buffer becomes full). @@ -207,6 +210,96 @@ Integer nvim_input(String keys) return (Integer)input_enqueue(keys); } +/// Send mouse event from GUI. +/// +/// The call is non-blocking. It doesn't wait on any resulting action, but +/// queues the event to be processed soon by the event loop. +/// +/// @note Currently this doesn't support "scripting" multiple mouse events +/// by calling it multiple times in a loop: the intermediate mouse +/// positions will be ignored. It should be used to implement real-time +/// mouse input in a GUI. The deprecated pseudokey form +/// ("<LeftMouse><col,row>") of |nvim_input()| has the same limitiation. +/// +/// @param button Mouse button: one of "left", "right", "middle", "wheel". +/// @param action For ordinary buttons, one of "press", "drag", "release". +/// For the wheel, one of "up", "down", "left", "right". +/// @param modifier String of modifiers each represented by a single char. +/// The same specifiers are used as for a key press, except +/// that the "-" separator is optional, so "C-A-", "c-a" +/// and "CA" can all be used to specify Ctrl+Alt+click. +/// @param grid Grid number if the client uses |ui-multigrid|, else 0. +/// @param row Mouse row-position (zero-based, like redraw events) +/// @param col Mouse column-position (zero-based, like redraw events) +void nvim_input_mouse(String button, String action, String modifier, + Integer grid, Integer row, Integer col, Error *err) + FUNC_API_SINCE(6) FUNC_API_ASYNC +{ + if (button.data == NULL || action.data == NULL) { + goto error; + } + + int code = 0; + + if (strequal(button.data, "left")) { + code = KE_LEFTMOUSE; + } else if (strequal(button.data, "middle")) { + code = KE_MIDDLEMOUSE; + } else if (strequal(button.data, "right")) { + code = KE_RIGHTMOUSE; + } else if (strequal(button.data, "wheel")) { + code = KE_MOUSEDOWN; + } else { + goto error; + } + + if (code == KE_MOUSEDOWN) { + if (strequal(action.data, "down")) { + code = KE_MOUSEUP; + } else if (strequal(action.data, "up")) { + code = KE_MOUSEDOWN; + } else if (strequal(action.data, "left")) { + code = KE_MOUSERIGHT; + } else if (strequal(action.data, "right")) { + code = KE_MOUSELEFT; + } else { + goto error; + } + } else { + if (strequal(action.data, "press")) { + // pass + } else if (strequal(action.data, "drag")) { + code += KE_LEFTDRAG - KE_LEFTMOUSE; + } else if (strequal(action.data, "release")) { + code += KE_LEFTRELEASE - KE_LEFTMOUSE; + } else { + goto error; + } + } + + int modmask = 0; + for (size_t i = 0; i < modifier.size; i++) { + char byte = modifier.data[i]; + if (byte == '-') { + continue; + } + int mod = name_to_mod_mask(byte); + if (mod == 0) { + api_set_error(err, kErrorTypeValidation, + "invalid modifier %c", byte); + return; + } + modmask |= mod; + } + + input_enqueue_mouse(code, (uint8_t)modmask, (int)grid, (int)row, (int)col); + return; + +error: + api_set_error(err, kErrorTypeValidation, + "invalid button or action"); +} + /// Replaces terminal codes and |keycodes| (<CR>, <Esc>, ...) in a string with /// the internal representation. /// @@ -598,7 +691,7 @@ void nvim_set_current_dir(String dir, Error *err) try_end(err); } -/// Gets the current line +/// Gets the current line. /// /// @param[out] err Error details, if any /// @return Current line string @@ -608,7 +701,7 @@ String nvim_get_current_line(Error *err) return buffer_get_line(curbuf->handle, curwin->w_cursor.lnum - 1, err); } -/// Sets the current line +/// Sets the current line. /// /// @param line Line contents /// @param[out] err Error details, if any @@ -618,7 +711,7 @@ void nvim_set_current_line(String line, Error *err) buffer_set_line(curbuf->handle, curwin->w_cursor.lnum - 1, line, err); } -/// Deletes the current line +/// Deletes the current line. /// /// @param[out] err Error details, if any void nvim_del_current_line(Error *err) @@ -627,7 +720,7 @@ void nvim_del_current_line(Error *err) buffer_del_line(curbuf->handle, curwin->w_cursor.lnum - 1, err); } -/// Gets a global (g:) variable +/// Gets a global (g:) variable. /// /// @param name Variable name /// @param[out] err Error details, if any @@ -638,7 +731,7 @@ Object nvim_get_var(String name, Error *err) return dict_get_value(&globvardict, name, err); } -/// Sets a global (g:) variable +/// Sets a global (g:) variable. /// /// @param name Variable name /// @param value Variable value @@ -649,7 +742,7 @@ void nvim_set_var(String name, Object value, Error *err) dict_set_var(&globvardict, name, value, false, false, err); } -/// Removes a global (g:) variable +/// Removes a global (g:) variable. /// /// @param name Variable name /// @param[out] err Error details, if any @@ -676,7 +769,7 @@ Object vim_del_var(String name, Error *err) return dict_set_var(&globvardict, name, NIL, true, true, err); } -/// Gets a v: variable +/// Gets a v: variable. /// /// @param name Variable name /// @param[out] err Error details, if any @@ -687,7 +780,7 @@ Object nvim_get_vvar(String name, Error *err) return dict_get_value(&vimvardict, name, err); } -/// Sets a v: variable, if it is not readonly +/// Sets a v: variable, if it is not readonly. /// /// @param name Variable name /// @param value Variable value @@ -698,7 +791,7 @@ void nvim_set_vvar(String name, Object value, Error *err) dict_set_var(&vimvardict, name, value, false, false, err); } -/// Gets an option value string +/// Gets an option value string. /// /// @param name Option name /// @param[out] err Error details, if any @@ -709,7 +802,7 @@ Object nvim_get_option(String name, Error *err) return get_option_from(NULL, SREQ_GLOBAL, name, err); } -/// Sets an option value +/// Sets an option value. /// /// @param name Option name /// @param value New option value @@ -777,7 +870,7 @@ ArrayOf(Buffer) nvim_list_bufs(void) return rv; } -/// Gets the current buffer +/// Gets the current buffer. /// /// @return Buffer handle Buffer nvim_get_current_buf(void) @@ -786,7 +879,7 @@ Buffer nvim_get_current_buf(void) return curbuf->handle; } -/// Sets the current buffer +/// Sets the current buffer. /// /// @param buffer Buffer handle /// @param[out] err Error details, if any @@ -809,7 +902,7 @@ void nvim_set_current_buf(Buffer buffer, Error *err) } } -/// Gets the current list of window handles +/// Gets the current list of window handles. /// /// @return List of window handles ArrayOf(Window) nvim_list_wins(void) @@ -831,7 +924,7 @@ ArrayOf(Window) nvim_list_wins(void) return rv; } -/// Gets the current window +/// Gets the current window. /// /// @return Window handle Window nvim_get_current_win(void) @@ -840,7 +933,7 @@ Window nvim_get_current_win(void) return curwin->handle; } -/// Sets the current window +/// Sets the current window. /// /// @param window Window handle void nvim_set_current_win(Window window, Error *err) @@ -862,7 +955,39 @@ void nvim_set_current_win(Window window, Error *err) } } -/// Gets the current list of tabpage handles +/// Creates a new, empty, unnamed buffer. +/// +/// @param listed Controls 'buflisted' +/// @param scratch Creates a "throwaway" |scratch-buffer| for temporary work +/// (always 'nomodified') +/// @param[out] err Error details, if any +/// @return Buffer handle, or 0 on error +/// +/// @see buf_open_scratch +Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err) + FUNC_API_SINCE(6) +{ + try_start(); + buf_T *buf = buflist_new(NULL, NULL, (linenr_T)0, + BLN_NOOPT | BLN_NEW | (listed ? BLN_LISTED : 0)); + try_end(err); + if (buf == NULL) { + if (!ERROR_SET(err)) { + api_set_error(err, kErrorTypeException, "Failed to create buffer"); + } + return 0; + } + if (scratch) { + WITH_BUFFER(buf, { + set_option_value("bh", 0L, "hide", OPT_LOCAL); + set_option_value("bt", 0L, "nofile", OPT_LOCAL); + set_option_value("swf", 0L, NULL, OPT_LOCAL); + }); + } + return buf->b_fnum; +} + +/// Gets the current list of tabpage handles. /// /// @return List of tabpage handles ArrayOf(Tabpage) nvim_list_tabpages(void) @@ -884,7 +1009,7 @@ ArrayOf(Tabpage) nvim_list_tabpages(void) return rv; } -/// Gets the current tabpage +/// Gets the current tabpage. /// /// @return Tabpage handle Tabpage nvim_get_current_tabpage(void) @@ -893,7 +1018,7 @@ Tabpage nvim_get_current_tabpage(void) return curtab->handle; } -/// Sets the current tabpage +/// Sets the current tabpage. /// /// @param tabpage Tabpage handle /// @param[out] err Error details, if any @@ -916,7 +1041,7 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err) } } -/// Creates a new namespace, or gets an existing one +/// Creates a new namespace, or gets an existing one. /// /// Namespaces are used for buffer highlights and virtual text, see /// |nvim_buf_add_highlight()| and |nvim_buf_set_virtual_text()|. @@ -942,7 +1067,7 @@ Integer nvim_create_namespace(String name) return (Integer)id; } -/// Gets existing, non-anonymous namespaces +/// Gets existing, non-anonymous namespaces. /// /// @return dict that maps from names to namespace ids. Dictionary nvim_get_namespaces(void) @@ -959,7 +1084,7 @@ Dictionary nvim_get_namespaces(void) return retval; } -/// Subscribes to event broadcasts +/// Subscribes to event broadcasts. /// /// @param channel_id Channel id (passed automatically by the dispatcher) /// @param event Event type string @@ -973,7 +1098,7 @@ void nvim_subscribe(uint64_t channel_id, String event) rpc_subscribe(channel_id, e); } -/// Unsubscribes to event broadcasts +/// Unsubscribes to event broadcasts. /// /// @param channel_id Channel id (passed automatically by the dispatcher) /// @param event Event type string @@ -1283,7 +1408,7 @@ typedef struct { typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// @endcond -/// Parse a VimL expression +/// Parse a VimL expression. /// /// @param[in] expr Expression to parse. Is always treated as a single line. /// @param[in] flags Flags: @@ -1764,7 +1889,7 @@ static void write_msg(String message, bool to_err) // Functions used for testing purposes -/// Returns object given as argument +/// Returns object given as argument. /// /// This API function is used for testing. One should not rely on its presence /// in plugins. @@ -1777,7 +1902,7 @@ Object nvim__id(Object obj) return copy_object(obj); } -/// Returns array given as argument +/// Returns array given as argument. /// /// This API function is used for testing. One should not rely on its presence /// in plugins. @@ -1790,7 +1915,7 @@ Array nvim__id_array(Array arr) return copy_object(ARRAY_OBJ(arr)).data.array; } -/// Returns dictionary given as argument +/// Returns dictionary given as argument. /// /// This API function is used for testing. One should not rely on its presence /// in plugins. @@ -1803,7 +1928,7 @@ Dictionary nvim__id_dictionary(Dictionary dct) return copy_object(DICTIONARY_OBJ(dct)).data.dictionary; } -/// Returns floating-point value given as argument +/// Returns floating-point value given as argument. /// /// This API function is used for testing. One should not rely on its presence /// in plugins. @@ -1927,19 +2052,19 @@ Object nvim_get_proc(Integer pid, Error *err) return rvobj; } -/// Selects an item in the completion popupmenu +/// Selects an item in the completion popupmenu. /// -/// When insert completion is not active, this API call is silently ignored. -/// It is mostly useful for an external UI using |ui-popupmenu| for instance -/// to control the popupmenu with the mouse. But it can also be used in an -/// insert mode mapping, use <cmd> mapping |:map-cmd| to ensure the mapping -/// doesn't end completion mode. +/// If |ins-completion| is not active this API call is silently ignored. +/// Useful for an external UI using |ui-popupmenu| to control the popupmenu +/// with the mouse. Can also be used in a mapping; use <cmd> |:map-cmd| to +/// ensure the mapping doesn't end completion mode. /// -/// @param item Index of the item to select, starting with zero. Pass in "-1" -/// to select no item (restore original text). +/// @param item Index (zero-based) of the item to select. Value of -1 selects +/// nothing and restores the original text. /// @param insert Whether the selection should be inserted in the buffer. -/// @param finish If true, completion will be finished with this item, and the -/// popupmenu dissmissed. Implies `insert`. +/// @param finish Finish the completion and dismiss the popupmenu. Implies +/// `insert`. +/// @param opts Optional parameters. Reserved for future use. void nvim_select_popupmenu_item(Integer item, Boolean insert, Boolean finish, Dictionary opts, Error *err) FUNC_API_SINCE(6) diff --git a/src/nvim/assert.h b/src/nvim/assert.h index 29195a49dc..34734f294d 100644 --- a/src/nvim/assert.h +++ b/src/nvim/assert.h @@ -1,6 +1,8 @@ #ifndef NVIM_ASSERT_H #define NVIM_ASSERT_H +#include "auto/config.h" + // support static asserts (aka compile-time asserts) // some compilers don't properly support short-circuiting apparently, giving @@ -78,7 +80,7 @@ # undef STATIC_ASSERT_PRAGMA_END # define STATIC_ASSERT_PRAGMA_END \ - _Pragma("GCC diagnostic pop") \ + _Pragma("GCC diagnostic pop") // the same goes for clang in C99 mode, but we suppress a different warning #elif defined(__clang__) && __has_extension(c_static_assert) @@ -88,11 +90,11 @@ # undef STATIC_ASSERT_PRAGMA_START # define STATIC_ASSERT_PRAGMA_START \ _Pragma("clang diagnostic push") \ - _Pragma("clang diagnostic ignored \"-Wc11-extensions\"") \ + _Pragma("clang diagnostic ignored \"-Wc11-extensions\"") # undef STATIC_ASSERT_PRAGMA_END # define STATIC_ASSERT_PRAGMA_END \ - _Pragma("clang diagnostic pop") \ + _Pragma("clang diagnostic pop") // TODO(aktau): verify that this works, don't have MSVC on hand. #elif _MSC_VER >= 1600 @@ -133,22 +135,32 @@ /// /// @param MAX Maximum value of the narrowest type of operand. /// Not used if compiler supports __builtin_add_overflow. -#if HAVE_BUILTIN_ADD_OVERFLOW +#ifdef HAVE_BUILTIN_ADD_OVERFLOW # define STRICT_ADD(a, b, c, t) \ - do { if (__builtin_add_overflow(a, b, c)) { abort(); } } while (0) + do { \ + if (__builtin_add_overflow(a, b, c)) { \ + ELOG("STRICT_ADD overflow"); \ + abort(); \ + } \ + } while (0) #else # define STRICT_ADD(a, b, c, t) \ - do { *(c) = (t)(a + b); } while (0) + do { *(c) = (t)((a) + (b)); } while (0) #endif /// @def STRICT_SUB /// @brief Subtracts (a - b) and stores result in `c`. Aborts on overflow. -#if HAVE_BUILTIN_ADD_OVERFLOW +#ifdef HAVE_BUILTIN_ADD_OVERFLOW # define STRICT_SUB(a, b, c, t) \ - do { if (__builtin_sub_overflow(a, b, c)) { abort(); } } while (0) + do { \ + if (__builtin_sub_overflow(a, b, c)) { \ + ELOG("STRICT_SUB overflow"); \ + abort(); \ + } \ + } while (0) #else # define STRICT_SUB(a, b, c, t) \ - do { *(c) = (t)(a - b); } while (0) + do { *(c) = (t)((a) - (b)); } while (0) #endif #endif // NVIM_ASSERT_H diff --git a/src/nvim/aucmd.c b/src/nvim/aucmd.c index 9ad3414b79..3bb0fcec3b 100644 --- a/src/nvim/aucmd.c +++ b/src/nvim/aucmd.c @@ -6,6 +6,7 @@ #include "nvim/vim.h" #include "nvim/main.h" #include "nvim/ui.h" +#include "nvim/aucmd.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "aucmd.c.generated.h" diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 3cffd66dee..cc0ed0f587 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -74,6 +74,7 @@ return { 'SessionLoadPost', -- after loading a session file 'ShellCmdPost', -- after ":!cmd" 'ShellFilterPost', -- after ":1,2!cmd", ":w !cmd", ":r !cmd". + 'Signal', -- after nvim process received a signal 'SourceCmd', -- sourcing a Vim script using command 'SourcePre', -- before sourcing a Vim script 'SpellFileMissing', -- spell file missing @@ -115,6 +116,7 @@ return { -- syntax file nvim_specific = { DirChanged=true, + Signal=true, TabClosed=true, TabNew=true, TabNewEntered=true, diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index c15a6f1330..7fd4326914 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1472,6 +1472,10 @@ void set_curbuf(buf_T *buf, int action) if (old_tw != curbuf->b_p_tw) check_colorcolumn(curwin); } + + if (bufref_valid(&prevbufref) && prevbuf->terminal != NULL) { + terminal_check_size(prevbuf->terminal); + } } /* @@ -1625,7 +1629,7 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags) FileID file_id; bool file_id_valid = (sfname != NULL && os_fileid((char *)sfname, &file_id)); - if (ffname != NULL && !(flags & BLN_DUMMY) + if (ffname != NULL && !(flags & (BLN_DUMMY | BLN_NEW)) && (buf = buflist_findname_file_id(ffname, &file_id, file_id_valid)) != NULL) { xfree(ffname); @@ -3220,6 +3224,9 @@ int build_stl_str_hl( #define TMPLEN 70 char_u tmp[TMPLEN]; char_u *usefmt = fmt; + const int save_must_redraw = must_redraw; + const int save_redr_type = curwin->w_redr_type; + const int save_highlight_shcnaged = need_highlight_changed; // When the format starts with "%!" then evaluate it as an expression and // use the result as the actual format string. @@ -3628,16 +3635,16 @@ int build_stl_str_hl( vim_snprintf((char *)tmp, sizeof(tmp), "%d", curbuf->b_fnum); set_internal_string_var((char_u *)"g:actual_curbuf", tmp); - buf_T *o_curbuf = curbuf; - win_T *o_curwin = curwin; + buf_T *const save_curbuf = curbuf; + win_T *const save_curwin = curwin; curwin = wp; curbuf = wp->w_buffer; // Note: The result stored in `t` is unused. str = eval_to_string_safe(out_p, &t, use_sandbox); - curwin = o_curwin; - curbuf = o_curbuf; + curwin = save_curwin; + curbuf = save_curbuf; // Remove the variable we just stored do_unlet(S_LEN("g:actual_curbuf"), true); @@ -3676,10 +3683,10 @@ int build_stl_str_hl( { // In list mode virtcol needs to be recomputed colnr_T virtcol = wp->w_virtcol; - if (wp->w_p_list && lcs_tab1 == NUL) { - wp->w_p_list = FALSE; + if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) { + wp->w_p_list = false; getvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL); - wp->w_p_list = TRUE; + wp->w_p_list = true; } ++virtcol; // Don't display %V if it's the same as %c. @@ -4258,6 +4265,13 @@ int build_stl_str_hl( cur_tab_rec->def.func = NULL; } + // We do not want redrawing a stausline, ruler, title, etc. to trigger + // another redraw, it may cause an endless loop. This happens when a + // statusline changes a highlight group. + must_redraw = save_must_redraw; + curwin->w_redr_type = save_redr_type; + need_highlight_changed = save_highlight_shcnaged; + return width; } diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h index e61c312fb1..79bed049ea 100644 --- a/src/nvim/buffer.h +++ b/src/nvim/buffer.h @@ -31,7 +31,7 @@ enum bln_values { BLN_CURBUF = 1, // May re-use curbuf for new buffer BLN_LISTED = 2, // Put new buffer in buffer list BLN_DUMMY = 4, // Allocating dummy buffer - // TODO(mhinz): merge patch that introduces BLN_NEW + BLN_NEW = 8, // create a new buffer BLN_NOOPT = 16, // Don't copy options to existing buffer }; diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 05688790c2..07d8ab2e0c 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -248,6 +248,10 @@ typedef struct { # define w_p_scl w_onebuf_opt.wo_scl // 'signcolumn' char_u *wo_winhl; # define w_p_winhl w_onebuf_opt.wo_winhl // 'winhighlight' + char_u *wo_fcs; +# define w_p_fcs w_onebuf_opt.wo_fcs // 'fillchars' + char_u *wo_lcs; +# define w_p_lcs w_onebuf_opt.wo_lcs // 'listchars' LastSet wo_scriptID[WV_COUNT]; // SIDs for window-local options # define w_p_scriptID w_onebuf_opt.wo_scriptID @@ -1003,6 +1007,31 @@ struct window_S { colnr_T w_old_visual_col; ///< last known start of visual part colnr_T w_old_curswant; ///< last known value of Curswant + // 'listchars' characters. Defaults set in set_chars_option(). + struct { + int eol; + int ext; + int prec; + int nbsp; + int space; + int tab1; ///< first tab character + int tab2; ///< second tab character + int tab3; ///< third tab character + int trail; + int conceal; + } w_p_lcs_chars; + + // 'fillchars' characters. Defaults set in set_chars_option(). + struct { + int stl; + int stlnc; + int vert; + int fold; + int diff; + int msgsep; + int eob; + } w_p_fcs_chars; + /* * "w_topline", "w_leftcol" and "w_skipcol" specify the offsets for * displaying the buffer. @@ -1034,6 +1063,13 @@ struct window_S { int w_width; /* Width of window, excluding separation. */ int w_vsep_width; /* Number of separator columns (0 or 1). */ + // inner size of window, which can be overridden by external UI + int w_height_inner; + int w_width_inner; + // external UI request. If non-zero, the inner size will use this. + int w_height_request; + int w_width_request; + /* * === start of cached values ==== */ diff --git a/src/nvim/charset.c b/src/nvim/charset.c index c220c4e347..08ecff149c 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -749,8 +749,8 @@ int vim_strnsize(char_u *s, int len) /// /// @return Number of characters. #define RET_WIN_BUF_CHARTABSIZE(wp, buf, p, col) \ - if (*(p) == TAB && (!(wp)->w_p_list || lcs_tab1)) { \ - const int ts = (int) (buf)->b_p_ts; \ + if (*(p) == TAB && (!(wp)->w_p_list || wp->w_p_lcs_chars.tab1)) { \ + const int ts = (int)(buf)->b_p_ts; \ return (ts - (int)(col % ts)); \ } else { \ return ptr2cells(p); \ @@ -1022,12 +1022,12 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he && vim_isbreak(c) && !vim_isbreak((int)s[1]) && wp->w_p_wrap - && (wp->w_grid.Columns != 0)) { + && (wp->w_width_inner != 0)) { // Count all characters from first non-blank after a blank up to next // non-blank after a blank. numberextra = win_col_off(wp); col2 = col; - colmax = (colnr_T)(wp->w_grid.Columns - numberextra - col_adj); + colmax = (colnr_T)(wp->w_width_inner - numberextra - col_adj); if (col >= colmax) { colmax += col_adj; @@ -1076,9 +1076,9 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he numberextra = numberwidth; col += numberextra + mb_added; - if (col >= (colnr_T)wp->w_grid.Columns) { - col -= wp->w_grid.Columns; - numberextra = wp->w_grid.Columns - (numberextra - win_col_off2(wp)); + if (col >= (colnr_T)wp->w_width_inner) { + col -= wp->w_width_inner; + numberextra = wp->w_width_inner - (numberextra - win_col_off2(wp)); if (col >= numberextra && numberextra > 0) { col %= numberextra; } @@ -1097,17 +1097,17 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he numberwidth -= win_col_off2(wp); } - if (col == 0 || (col + size + sbrlen > (colnr_T)wp->w_grid.Columns)) { + if (col == 0 || (col + size + sbrlen > (colnr_T)wp->w_width_inner)) { added = 0; if (*p_sbr != NUL) { - if (size + sbrlen + numberwidth > (colnr_T)wp->w_grid.Columns) { + if (size + sbrlen + numberwidth > (colnr_T)wp->w_width_inner) { // Calculate effective window width. - int width = (colnr_T)wp->w_grid.Columns - sbrlen - numberwidth; - int prev_width = col ? ((colnr_T)wp->w_grid.Columns - (sbrlen + col)) + int width = (colnr_T)wp->w_width_inner - sbrlen - numberwidth; + int prev_width = col ? ((colnr_T)wp->w_width_inner - (sbrlen + col)) : 0; if (width == 0) { - width = (colnr_T)wp->w_grid.Columns; + width = (colnr_T)wp->w_width_inner; } added += ((size - prev_width) / width) * vim_strsize(p_sbr); if ((size - prev_width) % width) { @@ -1149,7 +1149,7 @@ static int win_nolbr_chartabsize(win_T *wp, char_u *s, colnr_T col, int *headp) { int n; - if ((*s == TAB) && (!wp->w_p_list || lcs_tab1)) { + if ((*s == TAB) && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { n = (int)wp->w_buffer->b_p_ts; return n - (col % n); } @@ -1176,11 +1176,11 @@ bool in_win_border(win_T *wp, colnr_T vcol) int width1; // width of first line (after line number) int width2; // width of further lines - if (wp->w_grid.Columns == 0) { + if (wp->w_width_inner == 0) { // there is no border return false; } - width1 = wp->w_grid.Columns - win_col_off(wp); + width1 = wp->w_width_inner - win_col_off(wp); if ((int)vcol < width1 - 1) { return false; @@ -1241,7 +1241,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, // When 'list', 'linebreak', 'showbreak' and 'breakindent' are not set // use a simple loop. // Also use this when 'list' is set but tabs take their normal size. - if ((!wp->w_p_list || (lcs_tab1 != NUL)) + if ((!wp->w_p_list || (wp->w_p_lcs_chars.tab1 != NUL)) && !wp->w_p_lbr && (*p_sbr == NUL) && !wp->w_p_bri ) { diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index 6c1bd01ff5..bc14761877 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -121,11 +121,11 @@ static int coladvance2( --curwin->w_curswant; } } else { - int width = curwin->w_grid.Columns - win_col_off(curwin); + int width = curwin->w_width_inner - win_col_off(curwin); if (finetune && curwin->w_p_wrap - && curwin->w_grid.Columns != 0 + && curwin->w_width_inner != 0 && wcol >= (colnr_T)width) { csize = linetabsize(line); if (csize > 0) @@ -230,7 +230,7 @@ static int coladvance2( int b = (int)wcol - (int)col; // The difference between wcol and col is used to set coladd. - if (b > 0 && b < (MAXCOL - 2 * curwin->w_grid.Columns)) { + if (b > 0 && b < (MAXCOL - 2 * curwin->w_width_inner)) { pos->coladd = b; } @@ -444,7 +444,7 @@ bool leftcol_changed(void) bool retval = false; changed_cline_bef_curs(); - lastcol = curwin->w_leftcol + curwin->w_grid.Columns - curwin_col_off() - 1; + lastcol = curwin->w_leftcol + curwin->w_width_inner - curwin_col_off() - 1; validate_virtcol(); /* diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 866161e5cf..ee4a48ff5d 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -1541,7 +1541,7 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout) diffstyle = DIFF_ED; } else if ((STRNCMP(line, "@@ ", 3) == 0)) { diffstyle = DIFF_UNIFIED; - } else if ((STRNCMP(line, "--- ", 4) == 0) + } else if ((STRNCMP(line, "--- ", 4) == 0) // -V501 && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501 && (STRNCMP(line, "+++ ", 4) == 0) && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501 diff --git a/src/nvim/edit.c b/src/nvim/edit.c index bb3c0ec196..62b35fa708 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -473,6 +473,8 @@ static void insert_enter(InsertState *s) o_lnum = curwin->w_cursor.lnum; } + pum_check_clear(); + foldUpdateAfterInsert(); // When CTRL-C was typed got_int will be set, with the result // that the autocommands won't be executed. When mapped got_int @@ -566,7 +568,7 @@ static int insert_check(VimState *state) if (curwin->w_wcol < s->mincol - curbuf->b_p_ts && curwin->w_wrow == curwin->w_winrow - + curwin->w_grid.Rows - 1 - p_so + + curwin->w_height_inner - 1 - p_so && (curwin->w_cursor.lnum != curwin->w_topline || curwin->w_topfill > 0)) { if (curwin->w_topfill > 0) { @@ -1447,6 +1449,7 @@ ins_redraw ( redrawWinline(curwin, curwin->w_cursor.lnum); } + pum_check_clear(); if (must_redraw) { update_screen(0); } else if (clear_cmdline || redraw_cmdline) { @@ -2490,20 +2493,6 @@ void set_completion(colnr_T startcol, list_T *list) static pumitem_T *compl_match_array = NULL; static int compl_match_arraysize; -/* - * Update the screen and when there is any scrolling remove the popup menu. - */ -static void ins_compl_upd_pum(void) -{ - int h; - - if (compl_match_array != NULL) { - h = curwin->w_cline_height; - update_screen(0); - if (h != curwin->w_cline_height) - ins_compl_del_pum(); - } -} /* * Remove any popup menu. @@ -2511,7 +2500,7 @@ static void ins_compl_upd_pum(void) static void ins_compl_del_pum(void) { if (compl_match_array != NULL) { - pum_undisplay(); + pum_undisplay(false); xfree(compl_match_array); compl_match_array = NULL; } @@ -4305,17 +4294,14 @@ ins_compl_next ( } if (!allow_get_expansion) { - /* may undisplay the popup menu first */ - ins_compl_upd_pum(); - - /* redraw to show the user what was inserted */ + // redraw to show the user what was inserted update_screen(0); - /* display the updated popup menu */ + // display the updated popup menu ins_compl_show_pum(); - /* Delete old text to be replaced, since we're still searching and - * don't want to match ourselves! */ + // Delete old text to be replaced, since we're still searching and + // don't want to match ourselves! ins_compl_delete(); } @@ -4862,8 +4848,6 @@ static int ins_complete(int c, bool enable_pum) save_w_leftcol = curwin->w_leftcol; n = ins_compl_next(true, ins_compl_key2count(c), insert_match, false); - /* may undisplay the popup menu */ - ins_compl_upd_pum(); if (n > 1) /* all matches have been found */ compl_matches = n; @@ -5868,7 +5852,7 @@ static void check_auto_format( /* * Find out textwidth to be used for formatting: * if 'textwidth' option is set, use it - * else if 'wrapmargin' option is set, use curwin->w_grid.Columns-'wrapmargin' + * else if 'wrapmargin' option is set, use curwin->w_width_inner-'wrapmargin' * if invalid value, use 0. * Set default to window width (maximum 79) for "gq" operator. */ @@ -5883,7 +5867,7 @@ comp_textwidth ( if (textwidth == 0 && curbuf->b_p_wm) { /* The width is the window width minus 'wrapmargin' minus all the * things that add to the margin. */ - textwidth = curwin->w_grid.Columns - curbuf->b_p_wm; + textwidth = curwin->w_width_inner - curbuf->b_p_wm; if (cmdwin_type != 0) { textwidth -= 1; } @@ -5899,7 +5883,7 @@ comp_textwidth ( if (textwidth < 0) textwidth = 0; if (ff && textwidth == 0) { - textwidth = curwin->w_grid.Columns - 1; + textwidth = curwin->w_width_inner - 1; if (textwidth > 79) { textwidth = 79; } @@ -7144,11 +7128,17 @@ static void ins_reg(void) * message for it. Only call it explicitly. */ ++no_u_sync; if (regname == '=') { - /* Sync undo when evaluating the expression calls setline() or - * append(), so that it can be undone separately. */ + pos_T curpos = curwin->w_cursor; + + // Sync undo when evaluating the expression calls setline() or + // append(), so that it can be undone separately. u_sync_once = 2; regname = get_expr_register(); + + // Cursor may be moved back a column. + curwin->w_cursor = curpos; + check_cursor(); } if (regname == NUL || !valid_yank_reg(regname, false)) { vim_beep(BO_REG); @@ -7939,15 +7929,13 @@ static void ins_mouse(int c) static void ins_mousescroll(int dir) { win_T *const old_curwin = curwin; - bool did_scroll = false; pos_T tpos = curwin->w_cursor; if (mouse_row >= 0 && mouse_col >= 0) { - int row = mouse_row; - int col = mouse_col; + int row = mouse_row, col = mouse_col, grid = mouse_grid; // find the window at the pointer coordinates - win_T *const wp = mouse_find_win(&row, &col); + win_T *wp = mouse_find_win(&grid, &row, &col); if (wp == NULL) { return; } @@ -7970,7 +7958,6 @@ static void ins_mousescroll(int dir) } else { mouse_scroll_horiz(dir); } - did_scroll = true; } curwin->w_redr_status = TRUE; @@ -7978,14 +7965,6 @@ static void ins_mousescroll(int dir) curwin = old_curwin; curbuf = curwin->w_buffer; - /* The popup menu may overlay the window, need to redraw it. - * TODO: Would be more efficient to only redraw the windows that are - * overlapped by the popup menu. */ - if (pum_visible() && did_scroll) { - redraw_all_later(NOT_VALID); - ins_compl_show_pum(); - } - if (!equalpos(curwin->w_cursor, tpos)) { start_arrow(&tpos); can_cindent = true; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 4cf8a01ddb..d63e45d3c7 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5958,7 +5958,9 @@ static int get_env_tv(char_u **arg, typval_T *rettv, int evaluate) #pragma function (floor) #endif +PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES # include "funcs.generated.h" +PRAGMA_DIAG_POP #endif /* @@ -9521,6 +9523,7 @@ static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (is_mouse_key(n)) { int row = mouse_row; int col = mouse_col; + int grid = mouse_grid; win_T *win; linenr_T lnum; win_T *wp; @@ -9529,7 +9532,7 @@ static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (row >= 0 && col >= 0) { /* Find the window at the mouse coordinates and compute the * text position. */ - win = mouse_find_win(&row, &col); + win = mouse_find_win(&grid, &row, &col); if (win == NULL) { return; } @@ -11264,7 +11267,7 @@ void get_user_input(const typval_T *const argvars, // Only the part of the message after the last NL is considered as // prompt for the command line, unlsess cmdline is externalized const char *p = prompt; - if (!ui_is_external(kUICmdline)) { + if (!ui_has(kUICmdline)) { const char *lastnl = strrchr(prompt, '\n'); if (lastnl != NULL) { p = lastnl+1; @@ -16424,7 +16427,9 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) if ((syntax_flags & HL_CONCEAL) && curwin->w_p_cole < 3) { cchar = syn_get_sub_char(); if (cchar == NUL && curwin->w_p_cole == 1) { - cchar = (lcs_conceal == NUL) ? ' ' : lcs_conceal; + cchar = (curwin->w_p_lcs_chars.conceal == NUL) + ? ' ' + : curwin->w_p_lcs_chars.conceal; } if (cchar != NUL) { utf_char2bytes(cchar, str); @@ -16795,10 +16800,10 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - uint16_t term_width = MAX(0, curwin->w_grid.Columns - win_col_off(curwin)); + uint16_t term_width = MAX(0, curwin->w_width_inner - win_col_off(curwin)); Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, true, false, false, cwd, - term_width, curwin->w_grid.Rows, + term_width, curwin->w_height_inner, xstrdup("xterm-256color"), &rettv->vval.v_number); if (rettv->vval.v_number <= 0) { @@ -19592,23 +19597,9 @@ void ex_echo(exarg_T *eap) msg_puts_attr(" ", echo_attr); } char *tofree = encode_tv2echo(&rettv, NULL); - const char *p = tofree; - if (p != NULL) { - for (; *p != NUL && !got_int; ++p) { - if (*p == '\n' || *p == '\r' || *p == TAB) { - if (*p != TAB && needclr) { - /* remove any text still there from the command */ - msg_clr_eos(); - needclr = false; - } - msg_putchar_attr((uint8_t)(*p), echo_attr); - } else { - int i = (*mb_ptr2len)((const char_u *)p); - - (void)msg_outtrans_len_attr((char_u *)p, i, echo_attr); - p += i - 1; - } - } + if (*tofree != NUL) { + msg_ext_set_kind("echo"); + msg_multiline_attr(tofree, echo_attr); } xfree(tofree); } @@ -19701,11 +19692,13 @@ void ex_execute(exarg_T *eap) } if (eap->cmdidx == CMD_echomsg) { + msg_ext_set_kind("echomsg"); MSG_ATTR(ga.ga_data, echo_attr); ui_flush(); } else if (eap->cmdidx == CMD_echoerr) { /* We don't want to abort following commands, restore did_emsg. */ save_did_emsg = did_emsg; + msg_ext_set_kind("echoerr"); EMSG((char_u *)ga.ga_data); if (!force_abort) did_emsg = save_did_emsg; @@ -19902,13 +19895,15 @@ void ex_function(exarg_T *eap) if (FUNCLINE(fp, j) == NULL) continue; msg_putchar('\n'); - msg_outnum((long)(j + 1)); - if (j < 9) + msg_outnum((long)j + 1); + if (j < 9) { msg_putchar(' '); - if (j < 99) + } + if (j < 99) { msg_putchar(' '); - msg_prt_line(FUNCLINE(fp, j), FALSE); - ui_flush(); /* show a line at a time */ + } + msg_prt_line(FUNCLINE(fp, j), false); + ui_flush(); // show a line at a time os_breakcheck(); } if (!got_int) { @@ -19963,7 +19958,7 @@ void ex_function(exarg_T *eap) goto errret_2; } - if (KeyTyped && ui_is_external(kUICmdline)) { + if (KeyTyped && ui_has(kUICmdline)) { show_block = true; ui_ext_cmdline_block_append(0, (const char *)eap->cmd); } @@ -20019,7 +20014,7 @@ void ex_function(exarg_T *eap) if (!eap->skip && did_emsg) goto erret; - if (!ui_is_external(kUICmdline)) { + if (!ui_has(kUICmdline)) { msg_putchar('\n'); // don't overwrite the function name } cmdline_row = msg_row; diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index 4d75c7bda1..42999ddd62 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -7,6 +7,7 @@ #include "nvim/eval/typval.h" #include "nvim/eval.h" +#include "nvim/eval/decode.h" #include "nvim/eval/encode.h" #include "nvim/ascii.h" #include "nvim/macros.h" diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 64658b52d9..6074e4ee69 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -19,6 +19,7 @@ #include "nvim/eval/typval.h" #include "nvim/garray.h" #include "nvim/mbyte.h" +#include "nvim/math.h" #include "nvim/message.h" #include "nvim/memory.h" #include "nvim/charset.h" // vim_isprintc() @@ -28,11 +29,6 @@ #include "nvim/lib/kvec.h" #include "nvim/eval/typval_encode.h" -#ifdef __MINGW32__ -# undef fpclassify -# define fpclassify __fpclassify -#endif - #define ga_concat(a, b) ga_concat(a, (char_u *)b) #define utf_ptr2char(b) utf_ptr2char((char_u *)b) #define utf_ptr2len(b) ((size_t)utf_ptr2len((char_u *)b)) @@ -327,7 +323,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, #define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ do { \ const float_T flt_ = (flt); \ - switch (fpclassify(flt_)) { \ + switch (xfpclassify(flt_)) { \ case FP_NAN: { \ ga_concat(gap, (char_u *) "str2float('nan')"); \ break; \ @@ -531,7 +527,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, #define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ do { \ const float_T flt_ = (flt); \ - switch (fpclassify(flt_)) { \ + switch (xfpclassify(flt_)) { \ case FP_NAN: { \ EMSG(_("E474: Unable to represent NaN value in JSON")); \ return FAIL; \ diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 85844c37bd..b3ac456979 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -257,7 +257,7 @@ void ex_align(exarg_T *eap) if (width <= 0) width = curbuf->b_p_tw; if (width == 0 && curbuf->b_p_wm > 0) { - width = curwin->w_grid.Columns - curbuf->b_p_wm; + width = curwin->w_width_inner - curbuf->b_p_wm; } if (width <= 0) { width = 80; @@ -2678,6 +2678,10 @@ int do_ecmd( theend: + if (bufref_valid(&old_curbuf) && old_curbuf.br_buf->terminal != NULL) { + terminal_check_size(old_curbuf.br_buf->terminal); + } + if (did_inc_redrawing_disabled) { RedrawingDisabled--; } @@ -2872,11 +2876,11 @@ void ex_z(exarg_T *eap) // Vi compatible: ":z!" uses display height, without a count uses // 'scroll' if (eap->forceit) { - bigness = curwin->w_grid.Rows; + bigness = curwin->w_height_inner; } else if (ONE_WINDOW) { bigness = curwin->w_p_scr * 2; } else { - bigness = curwin->w_grid.Rows - 3; + bigness = curwin->w_height_inner - 3; } if (bigness < 1) { bigness = 1; @@ -4131,7 +4135,7 @@ skip: buf_T *preview_buf = NULL; size_t subsize = preview_lines.subresults.size; if (preview && !aborting()) { - if (got_quit) { // Substitution is too slow, disable 'inccommand'. + if (got_quit || profile_passed_limit(timeout)) { // Too slow, disable. set_string_option_direct((char_u *)"icm", -1, (char_u *)"", OPT_FREE, SID_NONE); } else if (*p_icm != NUL && pat != NULL) { @@ -4895,7 +4899,7 @@ int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches, } } - *matches = (char_u **)""; + *matches = NULL; *num_matches = 0; int flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE; if (keep_lang) { diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index f5c16d883a..cda80dad39 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -9191,33 +9191,38 @@ static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin) if (restore_size && (ssop_flags & SSOP_WINSIZE)) { for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { - if (!ses_do_win(wp)) + if (!ses_do_win(wp)) { continue; - ++n; + } + n++; - /* restore height when not full height */ + // restore height when not full height if (wp->w_height + wp->w_status_height < topframe->fr_height && (fprintf(fd, "exe '%dresize ' . ((&lines * %" PRId64 " + %" PRId64 ") / %" PRId64 ")", - n, (int64_t)wp->w_grid.Rows, - (int64_t)(Rows / 2), (int64_t)Rows) < 0 - || put_eol(fd) == FAIL)) + n, (int64_t)wp->w_height, + (int64_t)Rows / 2, (int64_t)Rows) < 0 + || put_eol(fd) == FAIL)) { return FAIL; + } - /* restore width when not full width */ + // restore width when not full width if (wp->w_width < Columns - && (fprintf(fd, "exe 'vert %dresize ' . ((&columns * %" PRId64 - " + %" PRId64 ") / %" PRId64 ")", - n, (int64_t)wp->w_width, (int64_t)(Columns / 2), + && (fprintf(fd, + "exe 'vert %dresize ' . ((&columns * %" PRId64 + " + %" PRId64 ") / %" PRId64 ")", + n, (int64_t)wp->w_width, (int64_t)Columns / 2, (int64_t)Columns) < 0 - || put_eol(fd) == FAIL)) + || put_eol(fd) == FAIL)) { return FAIL; + } } } else { - /* Just equalise window sizes */ - if (put_line(fd, "wincmd =") == FAIL) + // Just equalise window sizes + if (put_line(fd, "wincmd =") == FAIL) { return FAIL; + } } return OK; } @@ -9365,10 +9370,11 @@ put_view( * arguments may have been deleted, check if the index is valid. */ if (wp->w_arg_idx != current_arg_idx && wp->w_arg_idx < WARGCOUNT(wp) && flagp == &ssop_flags) { - if (fprintf(fd, "%" PRId64 "argu", (int64_t)(wp->w_arg_idx + 1)) < 0 - || put_eol(fd) == FAIL) + if (fprintf(fd, "%" PRId64 "argu", (int64_t)wp->w_arg_idx + 1) < 0 + || put_eol(fd) == FAIL) { return FAIL; - did_next = TRUE; + } + did_next = true; } /* Edit the file. Skip this when ":next" already did it. */ @@ -9385,9 +9391,9 @@ put_view( // Note, if a buffer for that file already exists, use :badd to // edit that buffer, to not lose folding information (:edit resets // folds in other buffers) - if (fputs("if bufexists('", fd) < 0 + if (fputs("if bufexists(\"", fd) < 0 || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL - || fputs("') | buffer ", fd) < 0 + || fputs("\") | buffer ", fd) < 0 || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL || fputs(" | else | edit ", fd) < 0 || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL @@ -9463,8 +9469,8 @@ put_view( " * winheight(0) + %" PRId64 ") / %" PRId64 ")", (int64_t)wp->w_cursor.lnum, (int64_t)(wp->w_cursor.lnum - wp->w_topline), - (int64_t)(wp->w_grid.Rows / 2), - (int64_t)wp->w_grid.Rows) < 0 + (int64_t)(wp->w_height_inner / 2), + (int64_t)wp->w_height_inner) < 0 || put_eol(fd) == FAIL || put_line(fd, "if s:l < 1 | let s:l = 1 | endif") == FAIL || put_line(fd, "exe s:l") == FAIL @@ -9478,21 +9484,23 @@ put_view( return FAIL; } else { if (!wp->w_p_wrap && wp->w_leftcol > 0 && wp->w_width > 0) { - if (fprintf(fd, "let s:c = %" PRId64 " - ((%" PRId64 - " * winwidth(0) + %" PRId64 ") / %" PRId64 ")", - (int64_t)(wp->w_virtcol + 1), + if (fprintf(fd, + "let s:c = %" PRId64 " - ((%" PRId64 + " * winwidth(0) + %" PRId64 ") / %" PRId64 ")", + (int64_t)wp->w_virtcol + 1, (int64_t)(wp->w_virtcol - wp->w_leftcol), (int64_t)(wp->w_width / 2), (int64_t)wp->w_width) < 0 || put_eol(fd) == FAIL || put_line(fd, "if s:c > 0") == FAIL || fprintf(fd, " exe 'normal! ' . s:c . '|zs' . %" PRId64 " . '|'", - (int64_t)(wp->w_virtcol + 1)) < 0 + (int64_t)wp->w_virtcol + 1) < 0 || put_eol(fd) == FAIL || put_line(fd, "else") == FAIL || put_view_curpos(fd, wp, " ") == FAIL - || put_line(fd, "endif") == FAIL) + || put_line(fd, "endif") == FAIL) { return FAIL; + } } else if (put_view_curpos(fd, wp, "") == FAIL) { return FAIL; } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 8efb027575..d70b81409d 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -51,6 +51,7 @@ #include "nvim/option.h" #include "nvim/os_unix.h" #include "nvim/path.h" +#include "nvim/popupmnu.h" #include "nvim/regexp.h" #include "nvim/screen.h" #include "nvim/search.h" @@ -307,6 +308,9 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) gotocmdline(true); redrawcmdprompt(); // draw prompt or indent set_cmdspos(); + if (!msg_scroll) { + msg_ext_clear(false); + } } s->xpc.xp_context = EXPAND_NOTHING; s->xpc.xp_backslash = XP_BS_NONE; @@ -493,8 +497,14 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) char_u *p = ccline.cmdbuff; - if (ui_is_external(kUICmdline)) { + if (ui_has(kUICmdline)) { ui_call_cmdline_hide(ccline.level); + if (msg_ext_is_visible()) { + msg_ext_did_cmdline = true; + if (must_redraw < VALID) { + must_redraw = VALID; + } + } } cmdline_level--; @@ -613,7 +623,7 @@ static int command_line_execute(VimState *state, int key) if (!(s->c == p_wc && KeyTyped) && s->c != p_wcm && s->c != Ctrl_N && s->c != Ctrl_P && s->c != Ctrl_A && s->c != Ctrl_L) { - if (ui_is_external(kUIWildmenu)) { + if (ui_has(kUIWildmenu)) { ui_call_wildmenu_hide(); } if (s->xpc.xp_numfiles != -1) { @@ -895,7 +905,7 @@ static int command_line_execute(VimState *state, int key) } if (!cmd_silent) { - if (!ui_is_external(kUICmdline)) { + if (!ui_has(kUICmdline)) { ui_cursor_goto(msg_row, 0); } ui_flush(); @@ -1253,7 +1263,7 @@ static int command_line_handle_key(CommandLineState *s) xfree(ccline.cmdbuff); // no commandline to return ccline.cmdbuff = NULL; - if (!cmd_silent && !ui_is_external(kUICmdline)) { + if (!cmd_silent && !ui_has(kUICmdline)) { if (cmdmsg_rl) { msg_col = Columns; } else { @@ -1703,7 +1713,7 @@ static int command_line_handle_key(CommandLineState *s) s->do_abbr = false; // don't do abbreviation now // may need to remove ^ when composing char was typed if (enc_utf8 && utf_iscomposing(s->c) && !cmd_silent) { - if (ui_is_external(kUICmdline)) { + if (ui_has(kUICmdline)) { // TODO(bfredl): why not make unputcmdline also work with true? unputcmdline(); } else { @@ -1967,7 +1977,7 @@ static int command_line_changed(CommandLineState *s) // Do it only when there are no characters left to read // to avoid useless intermediate redraws. // if cmdline is external the ui handles shaping, no redraw needed. - if (!ui_is_external(kUICmdline) && vpeekc() == NUL) { + if (!ui_has(kUICmdline) && vpeekc() == NUL) { redrawcmd(); } } @@ -2834,7 +2844,7 @@ static void draw_cmdline(int start, int len) return; } - if (ui_is_external(kUICmdline)) { + if (ui_has(kUICmdline)) { ccline.special_char = NUL; ccline.redraw_state = kCmdRedrawAll; return; @@ -3027,7 +3037,7 @@ void ui_ext_cmdline_block_leave(void) /// assumes "redrawcmdline()" will already be invoked void cmdline_screen_cleared(void) { - if (!ui_is_external(kUICmdline)) { + if (!ui_has(kUICmdline)) { return; } @@ -3052,7 +3062,7 @@ void cmdline_screen_cleared(void) /// called by ui_flush, do what redraws neccessary to keep cmdline updated. void cmdline_ui_flush(void) { - if (!ui_is_external(kUICmdline)) { + if (!ui_has(kUICmdline)) { return; } int level = ccline.level; @@ -3081,7 +3091,7 @@ void putcmdline(int c, int shift) if (cmd_silent) { return; } - if (!ui_is_external(kUICmdline)) { + if (!ui_has(kUICmdline)) { msg_no_more = true; msg_putchar(c); if (shift) { @@ -3107,7 +3117,7 @@ void unputcmdline(void) return; } msg_no_more = true; - if (ccline.cmdlen == ccline.cmdpos && !ui_is_external(kUICmdline)) { + if (ccline.cmdlen == ccline.cmdpos && !ui_has(kUICmdline)) { msg_putchar(' '); } else { draw_cmdline(ccline.cmdpos, mb_ptr2len(ccline.cmdbuff + ccline.cmdpos)); @@ -3393,14 +3403,11 @@ void cmdline_paste_str(char_u *s, int literally) } } -/* - * Delete characters on the command line, from "from" to the current - * position. - */ +/// Delete characters on the command line, from "from" to the current position. static void cmdline_del(int from) { memmove(ccline.cmdbuff + from, ccline.cmdbuff + ccline.cmdpos, - (size_t)(ccline.cmdlen - ccline.cmdpos + 1)); + (size_t)ccline.cmdlen - ccline.cmdpos + 1); ccline.cmdlen -= ccline.cmdpos - from; ccline.cmdpos = from; } @@ -3425,7 +3432,7 @@ static void redrawcmdprompt(void) if (cmd_silent) return; - if (ui_is_external(kUICmdline)) { + if (ui_has(kUICmdline)) { ccline.redraw_state = kCmdRedrawAll; return; } @@ -3454,7 +3461,7 @@ void redrawcmd(void) if (cmd_silent) return; - if (ui_is_external(kUICmdline)) { + if (ui_has(kUICmdline)) { draw_cmdline(0, ccline.cmdlen); return; } @@ -3502,7 +3509,7 @@ static void cursorcmd(void) if (cmd_silent) return; - if (ui_is_external(kUICmdline)) { + if (ui_has(kUICmdline)) { if (ccline.redraw_state < kCmdRedrawPos) { ccline.redraw_state = kCmdRedrawPos; } @@ -3527,7 +3534,7 @@ static void cursorcmd(void) void gotocmdline(int clr) { - if (ui_is_external(kUICmdline)) { + if (ui_has(kUICmdline)) { return; } msg_start(); @@ -3615,7 +3622,7 @@ nextwild ( return FAIL; } - if (!ui_is_external(kUIWildmenu)) { + if (!(ui_has(kUICmdline) || ui_has(kUIWildmenu))) { MSG_PUTS("..."); // show that we are busy ui_flush(); } @@ -3661,8 +3668,8 @@ nextwild ( xp->xp_pattern = ccline.cmdbuff + i; } memmove(&ccline.cmdbuff[ccline.cmdpos + difflen], - &ccline.cmdbuff[ccline.cmdpos], - (size_t)(ccline.cmdlen - ccline.cmdpos + 1)); + &ccline.cmdbuff[ccline.cmdpos], + (size_t)ccline.cmdlen - ccline.cmdpos + 1); memmove(&ccline.cmdbuff[i], p2, STRLEN(p2)); ccline.cmdlen += difflen; ccline.cmdpos += difflen; @@ -3765,7 +3772,7 @@ ExpandOne ( findex = -1; } if (p_wmnu) { - if (ui_is_external(kUIWildmenu)) { + if (ui_has(kUIWildmenu)) { ui_call_wildmenu_select(findex); } else { win_redr_status_matches(xp, xp->xp_numfiles, xp->xp_files, @@ -4120,7 +4127,7 @@ static int showmatches(expand_T *xp, int wildmenu) showtail = cmd_showtail; } - if (ui_is_external(kUIWildmenu)) { + if (ui_has(kUIWildmenu)) { Array args = ARRAY_DICT_INIT; for (i = 0; i < num_files; i++) { ADD(args, STRING_OBJ(cstr_to_string((char *)files_found[i]))); @@ -4705,7 +4712,7 @@ ExpandFromContext ( return ret; } - *file = (char_u **)""; + *file = NULL; *num_file = 0; if (xp->xp_context == EXPAND_HELP) { /* With an empty argument we would get all the help tags, which is @@ -6054,7 +6061,12 @@ static int open_cmdwin(void) /* Don't execute autocommands while creating the window. */ block_autocmds(); - /* don't use a new tab page */ + + // When using completion in Insert mode with <C-R>=<C-F> one can open the + // command line window, but we don't want the popup menu then. + pum_undisplay(true); + + // don't use a new tab page cmdmod.tab = 0; cmdmod.noswapfile = 1; @@ -6121,7 +6133,7 @@ static int open_cmdwin(void) curwin->w_cursor.col = ccline.cmdpos; changed_line_abv_curs(); invalidate_botline(); - if (ui_is_external(kUICmdline)) { + if (ui_has(kUICmdline)) { ccline.redraw_state = kCmdRedrawNone; ui_call_cmdline_hide(ccline.level); } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index ba154ea36a..6356290b9c 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -6917,6 +6917,7 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, || event == EVENT_REMOTEREPLY || event == EVENT_SPELLFILEMISSING || event == EVENT_SYNTAX + || event == EVENT_SIGNAL || event == EVENT_TABCLOSED) { fname = vim_strsave(fname); } else { diff --git a/src/nvim/garray.c b/src/nvim/garray.c index 2d2af54c95..74fd9d89cb 100644 --- a/src/nvim/garray.c +++ b/src/nvim/garray.c @@ -91,8 +91,8 @@ void ga_grow(garray_T *gap, int n) } int new_maxlen = gap->ga_len + n; - size_t new_size = (size_t)(gap->ga_itemsize * new_maxlen); - size_t old_size = (size_t)(gap->ga_itemsize * gap->ga_maxlen); + size_t new_size = (size_t)gap->ga_itemsize * (size_t)new_maxlen; + size_t old_size = (size_t)gap->ga_itemsize * (size_t)gap->ga_maxlen; // reallocate and clear the new memory char *pp = xrealloc(gap->ga_data, new_size); diff --git a/src/nvim/garray.h b/src/nvim/garray.h index 58738df691..e2cbdd4eab 100644 --- a/src/nvim/garray.h +++ b/src/nvim/garray.h @@ -18,6 +18,7 @@ typedef struct growarray { } garray_T; #define GA_EMPTY_INIT_VALUE { 0, 0, 0, 1, NULL } +#define GA_INIT(itemsize, growsize) { 0, 0, (itemsize), (growsize), NULL } #define GA_EMPTY(ga_ptr) ((ga_ptr)->ga_len <= 0) diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua index d3047e1a9c..40bdc3e30c 100644 --- a/src/nvim/generators/c_grammar.lua +++ b/src/nvim/generators/c_grammar.lua @@ -25,6 +25,7 @@ local c_id = ( local c_void = P('void') local c_param_type = ( ((P('Error') * fill * P('*') * fill) * Cc('error')) + + C((P('const ') ^ -1) * (c_id) * (ws ^ 1) * P('*')) + (C(c_id) * (ws ^ 1)) ) local c_type = (C(c_void) * (ws ^ 1)) + c_param_type @@ -43,6 +44,7 @@ local c_proto = Ct( (fill * Cg((P('FUNC_API_REMOTE_ONLY') * Cc(true)), 'remote_only') ^ -1) * (fill * Cg((P('FUNC_API_REMOTE_IMPL') * Cc(true)), 'remote_impl') ^ -1) * (fill * Cg((P('FUNC_API_BRIDGE_IMPL') * Cc(true)), 'bridge_impl') ^ -1) * + (fill * Cg((P('FUNC_API_COMPOSITOR_IMPL') * Cc(true)), 'compositor_impl') ^ -1) * fill * P(';') ) diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index bd9650e4d1..3703b76973 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -448,6 +448,7 @@ for _, fn in ipairs(functions) do end output:write(string.format([[ +void nlua_add_api_functions(lua_State *lstate); // silence -Wmissing-prototypes void nlua_add_api_functions(lua_State *lstate) FUNC_ATTR_NONNULL_ALL { diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua index e76b601d8a..c8ab310b02 100644 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -54,7 +54,7 @@ for i = 1, #events do ev = events[i] assert(ev.return_type == 'void') - if ev.since == nil then + if ev.since == nil and not ev.noexport then print("Ui event "..ev.name.." lacks since field.\n") os.exit(1) end @@ -65,7 +65,7 @@ for i = 1, #events do write_signature(proto_output, ev, 'UI *ui') proto_output:write(';\n') - if not ev.remote_impl then + if not ev.remote_impl and not ev.noexport then remote_output:write('static void remote_ui_'..ev.name) write_signature(remote_output, ev, 'UI *ui') remote_output:write('\n{\n') @@ -74,8 +74,7 @@ for i = 1, #events do remote_output:write('}\n\n') end - if not ev.bridge_impl then - + if not ev.bridge_impl and not ev.noexport then send, argv, recv, recv_argv, recv_cleanup = '', '', '', '', '' argc = 1 for j = 1, #ev.parameters do @@ -138,21 +137,36 @@ for i = 1, #events do 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_LOG('..ev.name..');\n') call_output:write(' ui_event("'..ev.name..'", args);\n') + elseif ev.compositor_impl then + call_output:write(' UI_CALL') + write_signature(call_output, ev, '!ui->composed, '..ev.name..', ui', true) + call_output:write(";\n") else call_output:write(' UI_CALL') - write_signature(call_output, ev, ev.name, true) + write_signature(call_output, ev, 'true, '..ev.name..', ui', true) call_output:write(";\n") end call_output:write("}\n\n") end + if ev.compositor_impl then + call_output:write('void ui_composed_call_'..ev.name) + write_signature(call_output, ev, '') + call_output:write('\n{\n') + call_output:write(' UI_CALL') + write_signature(call_output, ev, 'ui->composed, '..ev.name..', ui', true) + call_output:write(";\n") + call_output:write("}\n\n") + end + end proto_output:close() call_output:close() remote_output:close() +bridge_output:close() -- don't expose internal attributes like "impl_name" in public metadata exported_attributes = {'name', 'parameters', @@ -168,7 +182,9 @@ for _,ev in ipairs(events) do p[1] = 'Dictionary' end end - exported_events[#exported_events+1] = ev_exported + if not ev.noexport then + exported_events[#exported_events+1] = ev_exported + end end packed = mpack.pack(exported_events) diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 53e9846c2d..b24acb5ebb 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1187,10 +1187,11 @@ void save_typebuf(void) alloc_typebuf(); } -static int old_char = -1; /* character put back by vungetc() */ -static int old_mod_mask; /* mod_mask for ungotten character */ -static int old_mouse_row; /* mouse_row related to old_char */ -static int old_mouse_col; /* mouse_col related to old_char */ +static int old_char = -1; // character put back by vungetc() +static int old_mod_mask; // mod_mask for ungotten character +static int old_mouse_grid; // mouse_grid related to old_char +static int old_mouse_row; // mouse_row related to old_char +static int old_mouse_col; // mouse_col related to old_char /* @@ -1391,6 +1392,7 @@ int vgetc(void) c = old_char; old_char = -1; mod_mask = old_mod_mask; + mouse_grid = old_mouse_grid; mouse_row = old_mouse_row; mouse_col = old_mouse_col; } else { @@ -1585,6 +1587,7 @@ vungetc ( /* unget one character (can only be done once!) */ { old_char = c; old_mod_mask = mod_mask; + old_mouse_grid = mouse_grid; old_mouse_row = mouse_row; old_mouse_col = mouse_col; } @@ -2120,8 +2123,8 @@ static int vgetorpeek(int advance) ++col; } curwin->w_wrow = curwin->w_cline_row - + curwin->w_wcol / curwin->w_grid.Columns; - curwin->w_wcol %= curwin->w_grid.Columns; + + curwin->w_wcol / curwin->w_width_inner; + curwin->w_wcol %= curwin->w_width_inner; curwin->w_wcol += curwin_col_off(); col = 0; /* no correction needed */ } else { @@ -2130,7 +2133,7 @@ static int vgetorpeek(int advance) } } else if (curwin->w_p_wrap && curwin->w_wrow) { curwin->w_wrow--; - curwin->w_wcol = curwin->w_grid.Columns - 1; + curwin->w_wcol = curwin->w_width_inner - 1; col = curwin->w_cursor.col - 1; } if (col > 0 && curwin->w_wcol > 0) { diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 2550fb8163..f47697b190 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -194,12 +194,15 @@ EXTERN int compl_cont_status INIT(= 0); EXTERN int cmdmsg_rl INIT(= false); // cmdline is drawn right to left EXTERN int msg_col; EXTERN int msg_row; -EXTERN int msg_scrolled; /* Number of screen lines that windows have - * scrolled because of printing messages. */ -EXTERN int msg_scrolled_ign INIT(= FALSE); -/* when TRUE don't set need_wait_return in - msg_puts_attr() when msg_scrolled is - non-zero */ +EXTERN int msg_scrolled; // Number of screen lines that windows have + // scrolled because of printing messages. +// when true don't set need_wait_return in msg_puts_attr() +// when msg_scrolled is non-zero +EXTERN bool msg_scrolled_ign INIT(= false); +// Whether the screen is damaged due to scrolling. Sometimes msg_scrolled +// is reset before the screen is redrawn, so we need to keep track of this. +EXTERN bool msg_did_scroll INIT(= false); + EXTERN char_u *keep_msg INIT(= NULL); /* msg to be shown after redraw */ EXTERN int keep_msg_attr INIT(= 0); /* highlight attr for keep_msg */ @@ -394,9 +397,8 @@ EXTERN bufref_T au_new_curbuf INIT(= { NULL, 0, 0 }); EXTERN buf_T *au_pending_free_buf INIT(= NULL); EXTERN win_T *au_pending_free_win INIT(= NULL); -/* - * Mouse coordinates, set by check_termcode() - */ +// Mouse coordinates, set by handle_mouse_event() +EXTERN int mouse_grid; EXTERN int mouse_row; EXTERN int mouse_col; EXTERN bool mouse_past_bottom INIT(= false); /* mouse below last line */ @@ -843,26 +845,6 @@ extern char_u *compiled_sys; * directory is not a local directory, globaldir is NULL. */ EXTERN char_u *globaldir INIT(= NULL); -// 'listchars' characters. Defaults are overridden in set_chars_option(). -EXTERN int lcs_eol INIT(= '$'); -EXTERN int lcs_ext INIT(= NUL); -EXTERN int lcs_prec INIT(= NUL); -EXTERN int lcs_nbsp INIT(= NUL); -EXTERN int lcs_space INIT(= NUL); -EXTERN int lcs_tab1 INIT(= NUL); -EXTERN int lcs_tab2 INIT(= NUL); -EXTERN int lcs_trail INIT(= NUL); -EXTERN int lcs_conceal INIT(= ' '); - -// 'fillchars' characters. Defaults are overridden in set_chars_option(). -EXTERN int fill_stl INIT(= ' '); -EXTERN int fill_stlnc INIT(= ' '); -EXTERN int fill_vert INIT(= 9474); // │ -EXTERN int fill_fold INIT(= 183); // · -EXTERN int fill_diff INIT(= '-'); -EXTERN int fill_msgsep INIT(= ' '); -EXTERN int fill_eob INIT(= '~'); - /* Whether 'keymodel' contains "stopsel" and "startsel". */ EXTERN int km_stopsel INIT(= FALSE); EXTERN int km_startsel INIT(= FALSE); @@ -1072,16 +1054,14 @@ EXTERN char_u e_cmdmap_key[] INIT(=N_( EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM")); EXTERN char bot_top_msg[] INIT(= N_("search hit BOTTOM, continuing at TOP")); -/* For undo we need to know the lowest time possible. */ +// For undo we need to know the lowest time possible. EXTERN time_t starttime; EXTERN FILE *time_fd INIT(= NULL); /* where to write startup timing */ -/* - * Some compilers warn for not using a return value, but in some situations we - * can't do anything useful with the value. Assign to this variable to avoid - * the warning. - */ +// Some compilers warn for not using a return value, but in some situations we +// can't do anything useful with the value. Assign to this variable to avoid +// the warning. EXTERN int vim_ignored; // Start a msgpack-rpc channel over stdin/stdout. diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h index 37d85ead0c..d9ccdbbdab 100644 --- a/src/nvim/grid_defs.h +++ b/src/nvim/grid_defs.h @@ -1,6 +1,8 @@ #ifndef NVIM_GRID_DEFS_H #define NVIM_GRID_DEFS_H +#include <stdbool.h> +#include <stddef.h> #include <stdint.h> #include "nvim/types.h" @@ -49,11 +51,14 @@ typedef struct { int row_offset; int col_offset; - // grid size requested by the UI. Used for window grids only. - int requested_rows; - int requested_cols; - - int was_resized; + // state owned by the compositor. + int comp_row; + int comp_col; + size_t comp_index; + bool comp_disabled; } ScreenGrid; +#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, \ + false } + #endif // NVIM_GRID_DEFS_H diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 41d60fa3ea..4c5fca6d39 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -23,11 +23,15 @@ static kvec_t(HlEntry) attr_entries = KV_INITIAL_VALUE; static Map(HlEntry, int) *attr_entry_ids; static Map(int, int) *combine_attr_entries; +static Map(int, int) *blend_attr_entries; +static Map(int, int) *blendthrough_attr_entries; void highlight_init(void) { attr_entry_ids = map_new(HlEntry, int)(); combine_attr_entries = map_new(int, int)(); + blend_attr_entries = map_new(int, int)(); + blendthrough_attr_entries = map_new(int, int)(); // index 0 is no attribute, add dummy entry: kv_push(attr_entries, ((HlEntry){ .attr = HLATTRS_INIT, .kind = kHlUnknown, @@ -101,6 +105,9 @@ static int get_attr_entry(HlEntry entry) /// When a UI connects, we need to send it the table of highlights used so far. void ui_send_all_hls(UI *ui) { + if (!ui->hl_attr_define) { + return; + } 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, @@ -210,6 +217,8 @@ void clear_hl_tables(bool reinit) kv_size(attr_entries) = 1; map_clear(HlEntry, int)(attr_entry_ids); map_clear(int, int)(combine_attr_entries); + map_clear(int, int)(blend_attr_entries); + map_clear(int, int)(blendthrough_attr_entries); highlight_attr_set_all(); highlight_changed(); screen_invalidate_highlights(); @@ -217,15 +226,22 @@ void clear_hl_tables(bool reinit) kv_destroy(attr_entries); map_free(HlEntry, int)(attr_entry_ids); map_free(int, int)(combine_attr_entries); + map_free(int, int)(blend_attr_entries); + map_free(int, int)(blendthrough_attr_entries); } } +void hl_invalidate_blends(void) +{ + map_clear(int, int)(blend_attr_entries); + map_clear(int, int)(blendthrough_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. +// Since we expect there to be a lot of spelling mistakes we cache the result. // Return the resulting attributes. int hl_combine_attr(int char_attr, int prim_attr) { @@ -280,6 +296,188 @@ int hl_combine_attr(int char_attr, int prim_attr) return id; } +/// Get the used rgb colors for an attr group. +/// +/// If colors are unset, use builtin default colors. Never returns -1 +/// Cterm colors are unchanged. +static HlAttrs get_colors_force(int attr) +{ + HlAttrs attrs = syn_attr2entry(attr); + if (attrs.rgb_bg_color == -1) { + attrs.rgb_bg_color = normal_bg; + } + if (attrs.rgb_fg_color == -1) { + attrs.rgb_fg_color = normal_fg; + } + if (attrs.rgb_sp_color == -1) { + attrs.rgb_sp_color = normal_sp; + } + HL_SET_DEFAULT_COLORS(attrs.rgb_fg_color, attrs.rgb_bg_color, + attrs.rgb_sp_color); + + if (attrs.rgb_ae_attr & HL_INVERSE) { + int temp = attrs.rgb_bg_color; + attrs.rgb_bg_color = attrs.rgb_fg_color; + attrs.rgb_fg_color = temp; + attrs.rgb_ae_attr &= ~HL_INVERSE; + } + + return attrs; +} + +/// Blend overlay attributes (for popupmenu) with other attributes +/// +/// This creates a new group when required. +/// This is called per-cell, so cache the result. +/// +/// @return the resulting attributes. +int hl_blend_attrs(int back_attr, int front_attr, bool through) +{ + int combine_tag = (back_attr << 16) + front_attr; + Map(int, int) *map = through ? blendthrough_attr_entries : blend_attr_entries; + int id = map_get(int, int)(map, combine_tag); + if (id > 0) { + return id; + } + + HlAttrs battrs = get_colors_force(back_attr); + HlAttrs fattrs = get_colors_force(front_attr); + HlAttrs cattrs; + if (through) { + cattrs = battrs; + cattrs.rgb_fg_color = rgb_blend((int)p_pb, battrs.rgb_fg_color, + fattrs.rgb_bg_color); + if (cattrs.rgb_ae_attr & (HL_UNDERLINE|HL_UNDERCURL)) { + cattrs.rgb_sp_color = rgb_blend((int)p_pb, battrs.rgb_sp_color, + fattrs.rgb_bg_color); + } else { + cattrs.rgb_sp_color = -1; + } + + cattrs.cterm_bg_color = fattrs.cterm_bg_color; + cattrs.cterm_fg_color = cterm_blend((int)p_pb, battrs.cterm_fg_color, + fattrs.cterm_bg_color); + } else { + cattrs = fattrs; + if (p_pb >= 50) { + cattrs.rgb_ae_attr |= battrs.rgb_ae_attr; + } + cattrs.rgb_fg_color = rgb_blend((int)p_pb/2, battrs.rgb_fg_color, + fattrs.rgb_fg_color); + if (cattrs.rgb_ae_attr & (HL_UNDERLINE|HL_UNDERCURL)) { + cattrs.rgb_sp_color = rgb_blend((int)p_pb/2, battrs.rgb_bg_color, + fattrs.rgb_sp_color); + } else { + cattrs.rgb_sp_color = -1; + } + } + cattrs.rgb_bg_color = rgb_blend((int)p_pb, battrs.rgb_bg_color, + fattrs.rgb_bg_color); + + HlKind kind = through ? kHlBlendThrough : kHlBlend; + id = get_attr_entry((HlEntry){ .attr = cattrs, .kind = kind, + .id1 = back_attr, .id2 = front_attr }); + if (id > 0) { + map_put(int, int)(map, combine_tag, id); + } + return id; +} + +static int rgb_blend(int ratio, int rgb1, int rgb2) +{ + int a = ratio, b = 100-ratio; + int r1 = (rgb1 & 0xFF0000) >> 16; + int g1 = (rgb1 & 0x00FF00) >> 8; + int b1 = (rgb1 & 0x0000FF) >> 0; + int r2 = (rgb2 & 0xFF0000) >> 16; + int g2 = (rgb2 & 0x00FF00) >> 8; + int b2 = (rgb2 & 0x0000FF) >> 0; + int mr = (a * r1 + b * r2)/100; + int mg = (a * g1 + b * g2)/100; + int mb = (a * b1 + b * b2)/100; + return (mr << 16) + (mg << 8) + mb; +} + +static int cterm_blend(int ratio, int c1, int c2) +{ + // 1. Convert cterm color numbers to RGB. + // 2. Blend the RGB colors. + // 3. Convert the RGB result to a cterm color. + int rgb1 = hl_cterm2rgb_color(c1); + int rgb2 = hl_cterm2rgb_color(c2); + int rgb_blended = rgb_blend(ratio, rgb1, rgb2); + return hl_rgb2cterm_color(rgb_blended); +} + +/// Converts RGB color to 8-bit color (0-255). +static int hl_rgb2cterm_color(int rgb) +{ + int r = (rgb & 0xFF0000) >> 16; + int g = (rgb & 0x00FF00) >> 8; + int b = (rgb & 0x0000FF) >> 0; + + return (r * 6 / 256) * 36 + (g * 6 / 256) * 6 + (b * 6 / 256); +} + +/// Converts 8-bit color (0-255) to RGB color. +/// This is compatible with xterm. +static int hl_cterm2rgb_color(int nr) +{ + static int cube_value[] = { + 0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF + }; + static int grey_ramp[] = { + 0x08, 0x12, 0x1C, 0x26, 0x30, 0x3A, 0x44, 0x4E, 0x58, 0x62, 0x6C, 0x76, + 0x80, 0x8A, 0x94, 0x9E, 0xA8, 0xB2, 0xBC, 0xC6, 0xD0, 0xDA, 0xE4, 0xEE + }; + static char_u ansi_table[16][4] = { + // R G B idx + { 0, 0, 0, 1 } , // black + { 224, 0, 0, 2 } , // dark red + { 0, 224, 0, 3 } , // dark green + { 224, 224, 0, 4 } , // dark yellow / brown + { 0, 0, 224, 5 } , // dark blue + { 224, 0, 224, 6 } , // dark magenta + { 0, 224, 224, 7 } , // dark cyan + { 224, 224, 224, 8 } , // light grey + + { 128, 128, 128, 9 } , // dark grey + { 255, 64, 64, 10 } , // light red + { 64, 255, 64, 11 } , // light green + { 255, 255, 64, 12 } , // yellow + { 64, 64, 255, 13 } , // light blue + { 255, 64, 255, 14 } , // light magenta + { 64, 255, 255, 15 } , // light cyan + { 255, 255, 255, 16 } , // white + }; + + int r = 0; + int g = 0; + int b = 0; + int idx; + // *ansi_idx = 0; + + if (nr < 16) { + r = ansi_table[nr][0]; + g = ansi_table[nr][1]; + b = ansi_table[nr][2]; + // *ansi_idx = ansi_table[nr][3]; + } else if (nr < 232) { // 216 color-cube + idx = nr - 16; + r = cube_value[idx / 36 % 6]; + g = cube_value[idx / 6 % 6]; + b = cube_value[idx % 6]; + // *ansi_idx = -1; + } else if (nr < 256) { // 24 greyscale ramp + idx = nr - 232; + r = grey_ramp[idx]; + g = grey_ramp[idx]; + b = grey_ramp[idx]; + // *ansi_idx = -1; + } + return (r << 16) + (g << 8) + b; +} + /// Get highlight attributes for a attribute code HlAttrs syn_attr2entry(int attr) { @@ -403,6 +601,8 @@ static void hl_inspect_impl(Array *arr, int attr) break; case kHlCombine: + case kHlBlend: + case kHlBlendThrough: // attribute combination is associative, so flatten to an array hl_inspect_impl(arr, e.id1); hl_inspect_impl(arr, e.id2); diff --git a/src/nvim/highlight.h b/src/nvim/highlight.h index 6be0d6200b..a237ddbc34 100644 --- a/src/nvim/highlight.h +++ b/src/nvim/highlight.h @@ -10,4 +10,12 @@ # include "highlight.h.generated.h" #endif +# define HL_SET_DEFAULT_COLORS(rgb_fg, rgb_bg, rgb_sp) \ + do { \ + bool dark_ = (*p_bg == 'd'); \ + rgb_fg = rgb_fg != -1 ? rgb_fg : (dark_ ? 0xFFFFFF : 0x000000); \ + rgb_bg = rgb_bg != -1 ? rgb_bg : (dark_ ? 0x000000 : 0xFFFFFF); \ + rgb_sp = rgb_sp != -1 ? rgb_sp : 0xFF0000; \ + } while (0); + #endif // NVIM_HIGHLIGHT_H diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 40025fcbbb..1da33bfea5 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -160,6 +160,8 @@ typedef enum { kHlSyntax, kHlTerminal, kHlCombine, + kHlBlend, + kHlBlendThrough, } HlKind; typedef struct { diff --git a/src/nvim/indent.c b/src/nvim/indent.c index 13534ac1a9..8e20aa5be4 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -61,7 +61,8 @@ int get_indent_str(char_u *ptr, int ts, int list) for (; *ptr; ++ptr) { // Count a tab for what it is worth. if (*ptr == TAB) { - if (!list || lcs_tab1) { // count a tab for what it is worth + if (!list || curwin->w_p_lcs_chars.tab1) { + // count a tab for what it is worth count += ts - (count % ts); } else { // In list mode, when tab is not set, count screen char width @@ -204,13 +205,12 @@ int set_indent(int size, int flags) // characters and allocate accordingly. We will fill the rest with spaces // after the if (!curbuf->b_p_et) below. if (orig_char_len != -1) { - assert(orig_char_len + size - ind_done + line_len >= 0); - size_t n; // = orig_char_len + size - ind_done + line_len - size_t n2; - STRICT_ADD(orig_char_len, size, &n, size_t); - STRICT_ADD(ind_done, line_len, &n2, size_t); - STRICT_SUB(n, n2, &n, size_t); - newline = xmalloc(n); + int newline_size; // = orig_char_len + size - ind_done + line_len + STRICT_ADD(orig_char_len, size, &newline_size, int); + STRICT_SUB(newline_size, ind_done, &newline_size, int); + STRICT_ADD(newline_size, line_len, &newline_size, int); + assert(newline_size >= 0); + newline = xmalloc((size_t)newline_size); todo = size - ind_done; // Set total length of indent in characters, which may have been @@ -474,7 +474,7 @@ int get_breakindent_win(win_T *wp, char_u *line) static varnumber_T prev_tick = 0; // Changedtick of cached value. int bri = 0; // window width minus window margin space, i.e. what rests for text - const int eff_wwidth = wp->w_grid.Columns + const int eff_wwidth = wp->w_width_inner - ((wp->w_p_nu || wp->w_p_rnu) && (vim_strchr(p_cpo, CPO_NUMCOL) == NULL) ? number_width(wp) + 1 : 0); diff --git a/src/nvim/log.c b/src/nvim/log.c index 4d912c452b..8066b6e828 100644 --- a/src/nvim/log.c +++ b/src/nvim/log.c @@ -12,6 +12,7 @@ #endif #include <uv.h> +#include "auto/config.h" #include "nvim/log.h" #include "nvim/types.h" #include "nvim/os/os.h" diff --git a/src/nvim/log.h b/src/nvim/log.h index 7d4c033565..17ff095473 100644 --- a/src/nvim/log.h +++ b/src/nvim/log.h @@ -4,6 +4,8 @@ #include <stdio.h> #include <stdbool.h> +#include "auto/config.h" + #define DEBUG_LOG_LEVEL 0 #define INFO_LOG_LEVEL 1 #define WARN_LOG_LEVEL 2 diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index a7bda9d037..93069893cf 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -50,7 +50,7 @@ static void nlua_error(lua_State *const lstate, const char *const msg) size_t len; const char *const str = lua_tolstring(lstate, -1, &len); - emsgf(msg, (int)len, str); + emsgf_multiline(msg, (int)len, str); lua_pop(lstate, 1); } diff --git a/src/nvim/macros.h b/src/nvim/macros.h index d447bff765..61009528a8 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -198,4 +198,25 @@ # define IO_COUNT(x) (x) #endif +/// +/// PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES +/// +#if defined(__clang__) && __clang__ == 1 +# define PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wmissing-prototypes\"") +# define PRAGMA_DIAG_POP \ + _Pragma("clang diagnostic pop") +#elif defined(__GNUC__) +# define PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wmissing-prototypes\"") +# define PRAGMA_DIAG_POP \ + _Pragma("GCC diagnostic pop") +#else +# define PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES +# define PRAGMA_DIAG_POP +#endif + + #endif // NVIM_MACROS_H diff --git a/src/nvim/main.c b/src/nvim/main.c index 17f3a894d4..c0dea196fb 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -154,6 +154,7 @@ void event_init(void) remote_ui_init(); api_vim_init(); terminal_init(); + ui_init(); } /// @returns false if main_loop could not be closed gracefully @@ -221,6 +222,7 @@ void early_init(void) } #ifdef MAKE_LIB +int nvim_main(int argc, char **argv); // silence -Wmissing-prototypes int nvim_main(int argc, char **argv) #elif defined(WIN32) int wmain(int argc, wchar_t **argv_w) // multibyte args on Windows. #7060 @@ -298,7 +300,7 @@ int main(int argc, char **argv) assert(p_ch >= 0 && Rows >= p_ch && Rows - p_ch <= INT_MAX); cmdline_row = (int)(Rows - p_ch); msg_row = cmdline_row; - screenalloc(false); // allocate screen buffers + screenalloc(); // allocate screen buffers set_init_2(headless_mode); TIME_MSG("inits 2"); diff --git a/src/nvim/math.c b/src/nvim/math.c new file mode 100644 index 0000000000..b51f335ed7 --- /dev/null +++ b/src/nvim/math.c @@ -0,0 +1,42 @@ +// 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 + +#include <math.h> + +#include "nvim/math.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "math.c.generated.h" +#endif + +#if defined(__clang__) && __clang__ == 1 && __clang_major__ >= 6 +// Workaround glibc + Clang 6+ bug. #8274 +// https://bugzilla.redhat.com/show_bug.cgi?id=1472437 +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wconversion" +#endif +int xfpclassify(double d) +{ +#if defined(__MINGW32__) + // Workaround mingw warning. #7863 + return __fpclassify(d); +#else + return fpclassify(d); +#endif +} +int xisinf(double d) +{ + return isinf(d); +} +int xisnan(double d) +{ +#if defined(__MINGW32__) + // Workaround mingw warning. #7863 + return _isnan(d); +#else + return isnan(d); +#endif +} +#if defined(__clang__) && __clang__ == 1 && __clang_major__ >= 6 +# pragma clang diagnostic pop +#endif diff --git a/src/nvim/math.h b/src/nvim/math.h new file mode 100644 index 0000000000..7969323905 --- /dev/null +++ b/src/nvim/math.h @@ -0,0 +1,7 @@ +#ifndef NVIM_MATH_H +#define NVIM_MATH_H + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "math.h.generated.h" +#endif +#endif // NVIM_MATH_H diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 4c4f7d65bd..662eda3c7c 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -3412,7 +3412,9 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, # endif xfree(name); - /* pretend screen didn't scroll, need redraw anyway */ + // pretend screen didn't scroll, need redraw anyway + // TODO(bfredl): when doing the message grid refactor, + // simplify this special case. msg_scrolled = 0; redraw_all_later(NOT_VALID); } diff --git a/src/nvim/memline_defs.h b/src/nvim/memline_defs.h index 34a002af5d..edd933b2cd 100644 --- a/src/nvim/memline_defs.h +++ b/src/nvim/memline_defs.h @@ -3,54 +3,65 @@ #include "nvim/memfile_defs.h" -/* - * When searching for a specific line, we remember what blocks in the tree - * are the branches leading to that block. This is stored in ml_stack. Each - * entry is a pointer to info in a block (may be data block or pointer block) - */ +/// +/// When searching for a specific line, we remember what blocks in the tree +/// are the branches leading to that block. This is stored in ml_stack. Each +/// entry is a pointer to info in a block (may be data block or pointer block) +/// typedef struct info_pointer { - blocknr_T ip_bnum; /* block number */ - linenr_T ip_low; /* lowest lnum in this block */ - linenr_T ip_high; /* highest lnum in this block */ - int ip_index; /* index for block with current lnum */ -} infoptr_T; /* block/index pair */ + blocknr_T ip_bnum; // block number + linenr_T ip_low; // lowest lnum in this block + linenr_T ip_high; // highest lnum in this block + int ip_index; // index for block with current lnum +} infoptr_T; // block/index pair typedef struct ml_chunksize { int mlcs_numlines; long mlcs_totalsize; } chunksize_T; -/* Flags when calling ml_updatechunk() */ - +// Flags when calling ml_updatechunk() #define ML_CHNK_ADDLINE 1 #define ML_CHNK_DELLINE 2 #define ML_CHNK_UPDLINE 3 -/* - * the memline structure holds all the information about a memline - */ +/// memline structure: the contents of a buffer. +/// Essentially a tree with a branch factor of 128. +/// Lines are stored at leaf nodes. +/// Nodes are stored on ml_mfp (memfile_T): +/// pointer_block: internal nodes +/// data_block: leaf nodes +/// +/// Memline also has "chunks" of 800 lines that are separate from the 128-tree +/// structure, primarily used to speed up line2byte() and byte2line(). +/// +/// Motivation: If you have a file that is 10000 lines long, and you insert +/// a line at linenr 1000, you don't want to move 9000 lines in +/// memory. With this structure it is roughly (N * 128) pointer +/// moves, where N is the height (typically 1-3). +/// typedef struct memline { - linenr_T ml_line_count; /* number of lines in the buffer */ + linenr_T ml_line_count; // number of lines in the buffer - memfile_T *ml_mfp; /* pointer to associated memfile */ + memfile_T *ml_mfp; // pointer to associated memfile -#define ML_EMPTY 1 /* empty buffer */ -#define ML_LINE_DIRTY 2 /* cached line was changed and allocated */ -#define ML_LOCKED_DIRTY 4 /* ml_locked was changed */ -#define ML_LOCKED_POS 8 /* ml_locked needs positive block number */ +#define ML_EMPTY 1 // empty buffer +#define ML_LINE_DIRTY 2 // cached line was changed and allocated +#define ML_LOCKED_DIRTY 4 // ml_locked was changed +#define ML_LOCKED_POS 8 // ml_locked needs positive block number int ml_flags; - infoptr_T *ml_stack; /* stack of pointer blocks (array of IPTRs) */ - int ml_stack_top; /* current top of ml_stack */ - int ml_stack_size; /* total number of entries in ml_stack */ + infoptr_T *ml_stack; // stack of pointer blocks (array of IPTRs) + int ml_stack_top; // current top of ml_stack + int ml_stack_size; // total number of entries in ml_stack - linenr_T ml_line_lnum; /* line number of cached line, 0 if not valid */ - char_u *ml_line_ptr; /* pointer to cached line */ + linenr_T ml_line_lnum; // line number of cached line, 0 if not valid + char_u *ml_line_ptr; // pointer to cached line - bhdr_T *ml_locked; /* block used by last ml_get */ - linenr_T ml_locked_low; /* first line in ml_locked */ - linenr_T ml_locked_high; /* last line in ml_locked */ - int ml_locked_lineadd; /* number of lines inserted in ml_locked */ + bhdr_T *ml_locked; // block used by last ml_get + linenr_T ml_locked_low; // first line in ml_locked + linenr_T ml_locked_high; // last line in ml_locked + int ml_locked_lineadd; // number of lines inserted in ml_locked chunksize_T *ml_chunksize; int ml_numchunks; int ml_usedchunks; diff --git a/src/nvim/memory.c b/src/nvim/memory.c index d38079ca72..b49b521bc9 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -18,35 +18,15 @@ #include "nvim/ui.h" #include "nvim/api/vim.h" -#ifdef HAVE_JEMALLOC -// Force je_ prefix on jemalloc functions. -# define JEMALLOC_NO_DEMANGLE -# include <jemalloc/jemalloc.h> -#endif - #ifdef UNIT_TESTING # define malloc(size) mem_malloc(size) # define calloc(count, size) mem_calloc(count, size) # define realloc(ptr, size) mem_realloc(ptr, size) # define free(ptr) mem_free(ptr) -# ifdef HAVE_JEMALLOC -MemMalloc mem_malloc = &je_malloc; -MemFree mem_free = &je_free; -MemCalloc mem_calloc = &je_calloc; -MemRealloc mem_realloc = &je_realloc; -# else MemMalloc mem_malloc = &malloc; MemFree mem_free = &free; MemCalloc mem_calloc = &calloc; MemRealloc mem_realloc = &realloc; -# endif -#else -# ifdef HAVE_JEMALLOC -# define malloc(size) je_malloc(size) -# define calloc(count, size) je_calloc(count, size) -# define realloc(ptr, size) je_realloc(ptr, size) -# define free(ptr) je_free(ptr) -# endif #endif #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 1c54db10eb..aea297fce2 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -660,7 +660,8 @@ static void free_menu_string(vimmenu_T *menu, int idx) /// /// @param[in] menu if null, starts from root_menu /// @param modes, a choice of \ref MENU_MODES -/// @return a dict with name/commands +/// @return dict with name/commands +/// @see show_menus_recursive /// @see menu_get static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes) { @@ -715,10 +716,10 @@ static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes) // visit recursively all children list_T *const children_list = tv_list_alloc(kListLenMayKnow); for (menu = menu->children; menu != NULL; menu = menu->next) { - dict_T *dic = menu_get_recursive(menu, modes); - if (tv_dict_len(dict) > 0) { - tv_list_append_dict(children_list, dic); - } + dict_T *d = menu_get_recursive(menu, modes); + if (tv_dict_len(d) > 0) { + tv_list_append_dict(children_list, d); + } } tv_dict_add_list(dict, S_LEN("submenus"), children_list); } @@ -734,42 +735,51 @@ static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes) /// @return false if could not find path_name bool menu_get(char_u *const path_name, int modes, list_T *list) { - vimmenu_T *menu; - menu = find_menu(root_menu, path_name, modes); + vimmenu_T *menu = find_menu(root_menu, path_name, modes); if (!menu) { return false; } for (; menu != NULL; menu = menu->next) { - dict_T *dict = menu_get_recursive(menu, modes); - if (dict && tv_dict_len(dict) > 0) { - tv_list_append_dict(list, dict); + dict_T *d = menu_get_recursive(menu, modes); + if (d && tv_dict_len(d) > 0) { + tv_list_append_dict(list, d); + } + if (*path_name != NUL) { + // If a (non-empty) path query was given, only the first node in the + // find_menu() result is relevant. Else we want all nodes. + break; } } return true; } -/// Find menu matching required name and modes +/// Find menu matching `name` and `modes`. /// /// @param menu top menu to start looking from /// @param name path towards the menu /// @return menu if \p name is null, found menu or NULL -vimmenu_T * -find_menu(vimmenu_T *menu, char_u * name, int modes) +static vimmenu_T *find_menu(vimmenu_T *menu, char_u *name, int modes) { char_u *p; while (*name) { + // find the end of one dot-separated name and put a NUL at the dot p = menu_name_skip(name); while (menu != NULL) { if (menu_name_equal(name, menu)) { - /* Found menu */ + // Found menu if (*p != NUL && menu->children == NULL) { - EMSG(_(e_notsubmenu)); - return NULL; - } else if ((menu->modes & modes) == 0x0) { - EMSG(_(e_othermode)); - return NULL; + if (*p != NUL) { + EMSG(_(e_notsubmenu)); + return NULL; + } else if ((menu->modes & modes) == 0x0) { + EMSG(_(e_othermode)); + return NULL; + } + } + if (*p == NUL) { // found a full match + return menu; } break; } @@ -780,6 +790,7 @@ find_menu(vimmenu_T *menu, char_u * name, int modes) EMSG2(_(e_nomenu), name); return NULL; } + // Found a match, search the sub-menu. name = p; menu = menu->children; } @@ -1235,7 +1246,7 @@ static char_u *popup_mode_name(char_u *name, int idx) /// /// @return a pointer to allocated memory. static char_u *menu_text(const char_u *str, int *mnemonic, char_u **actext) - FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1) { char_u *p; @@ -1520,7 +1531,7 @@ static char_u *menutrans_lookup(char_u *name, int len) char_u *dname; for (int i = 0; i < menutrans_ga.ga_len; i++) { - if (STRNCMP(name, tp[i].from, len) == 0 && tp[i].from[len] == NUL) { + if (STRNICMP(name, tp[i].from, len) == 0 && tp[i].from[len] == NUL) { return tp[i].to; } } @@ -1531,7 +1542,7 @@ static char_u *menutrans_lookup(char_u *name, int len) dname = menu_text(name, NULL, NULL); name[len] = c; for (int i = 0; i < menutrans_ga.ga_len; i++) { - if (STRCMP(dname, tp[i].from_noamp) == 0) { + if (STRICMP(dname, tp[i].from_noamp) == 0) { xfree(dname); return tp[i].to; } diff --git a/src/nvim/message.c b/src/nvim/message.c index 37e40c3cc1..b22508c23f 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -34,11 +34,13 @@ #include "nvim/regexp.h" #include "nvim/screen.h" #include "nvim/strings.h" +#include "nvim/syntax.h" #include "nvim/ui.h" #include "nvim/mouse.h" #include "nvim/os/os.h" #include "nvim/os/input.h" #include "nvim/os/time.h" +#include "nvim/api/private/helpers.h" /* * To be able to scroll back at the "more" and "hit-enter" prompts we need to @@ -108,6 +110,19 @@ static int verbose_did_open = FALSE; * This is an allocated string or NULL when not used. */ + +// Extended msg state, currently used for external UIs with ext_messages +static const char *msg_ext_kind = NULL; +static Array msg_ext_chunks = ARRAY_DICT_INIT; +static garray_T msg_ext_last_chunk = GA_INIT(sizeof(char), 40); +static sattr_T msg_ext_last_attr = -1; + +static bool msg_ext_overwrite = false; ///< will overwrite last message +static int msg_ext_visible = 0; ///< number of messages currently visible + +/// Shouldn't clear message after leaving cmdline +static bool msg_ext_keep_after_cmdline = false; + /* * msg(s) - displays the string 's' on the status line * When terminal not initialized (yet) mch_errmsg(..) is used. @@ -115,7 +130,7 @@ static int verbose_did_open = FALSE; */ int msg(char_u *s) { - return msg_attr_keep(s, 0, FALSE); + return msg_attr_keep(s, 0, false, false); } /* @@ -126,29 +141,62 @@ int verb_msg(char_u *s) int n; verbose_enter(); - n = msg_attr_keep(s, 0, FALSE); + n = msg_attr_keep(s, 0, false, false); verbose_leave(); return n; } -int msg_attr(const char *s, const int attr) FUNC_ATTR_NONNULL_ARG(1) +int msg_attr(const char *s, const int attr) + FUNC_ATTR_NONNULL_ARG(1) { - return msg_attr_keep((char_u *)s, attr, false); + return msg_attr_keep((char_u *)s, attr, false, false); } -int -msg_attr_keep ( - char_u *s, - int attr, - int keep /* TRUE: set keep_msg if it doesn't scroll */ -) - FUNC_ATTR_NONNULL_ARG(1) +/// similar to msg_outtrans_attr, but support newlines and tabs. +void msg_multiline_attr(const char *s, int attr) + FUNC_ATTR_NONNULL_ALL +{ + const char *next_spec = s; + + while (next_spec != NULL) { + next_spec = strpbrk(s, "\t\n\r"); + + if (next_spec != NULL) { + // Printing all char that are before the char found by strpbrk + msg_outtrans_len_attr((char_u *)s, next_spec - s, attr); + + if (*next_spec != TAB) { + msg_clr_eos(); + } + msg_putchar_attr((uint8_t)(*next_spec), attr); + s = next_spec + 1; + } + } + + // Print the rest of the message. We know there is no special + // character because strpbrk returned NULL + if (*s != NUL) { + msg_outtrans_attr((char_u *)s, attr); + } +} + + +/// @param keep set keep_msg if it doesn't scroll +bool msg_attr_keep(char_u *s, int attr, bool keep, bool multiline) + FUNC_ATTR_NONNULL_ALL { static int entered = 0; int retval; char_u *buf = NULL; + if (keep && multiline) { + // Not implemented. 'multiline' is only used by nvim-added messages, + // which should avoid 'keep' behavior (just show the message at + // the correct time already). + abort(); + } + // Skip messages not match ":filter pattern". // Don't filter when there is an error. if (!emsg_on_display && message_filtered(s)) { @@ -175,7 +223,7 @@ msg_attr_keep ( && last_msg_hist != NULL && last_msg_hist->msg != NULL && STRCMP(s, last_msg_hist->msg))) { - add_msg_hist((const char *)s, -1, attr); + add_msg_hist((const char *)s, -1, attr, multiline); } /* When displaying keep_msg, don't let msg_start() free it, caller must do @@ -189,13 +237,18 @@ msg_attr_keep ( if (buf != NULL) s = buf; - msg_outtrans_attr(s, attr); + if (multiline) { + msg_multiline_attr((char *)s, attr); + } else { + msg_outtrans_attr(s, attr); + } msg_clr_eos(); retval = msg_end(); if (keep && retval && vim_strsize(s) < (int)(Rows - cmdline_row - 1) - * Columns + sc_col) + * Columns + sc_col) { set_keep_msg(s, 0); + } xfree(buf); --entered; @@ -218,7 +271,8 @@ msg_strtrunc ( /* May truncate message to avoid a hit-return prompt */ if ((!msg_scroll && !need_wait_return && shortmess(SHM_TRUNCALL) - && !exmode_active && msg_silent == 0) || force) { + && !exmode_active && msg_silent == 0 && !ui_has(kUIMessages)) + || force) { len = vim_strsize(s); if (msg_scrolled != 0) /* Use all the columns. */ @@ -468,17 +522,8 @@ int emsg_not_now(void) return FALSE; } -/* - * emsg() - display an error message - * - * Rings the bell, if appropriate, and calls message() to do the real work - * When terminal not initialized (yet) mch_errmsg(..) is used. - * - * return TRUE if wait_return not called - */ -int emsg(const char_u *s_) +static bool emsg_multiline(const char *s, bool multiline) { - const char *s = (const char *)s_; int attr; int ignore = false; int severe; @@ -565,6 +610,9 @@ int emsg(const char_u *s_) } // wait_return has reset need_wait_return // and a redraw is expected because // msg_scrolled is non-zero + if (msg_ext_kind == NULL) { + msg_ext_set_kind("emsg"); + } /* * Display name and line number for the source of the error. @@ -573,7 +621,18 @@ int emsg(const char_u *s_) // Display the error message itself. msg_nowait = false; // Wait for this msg. - return msg_attr(s, attr); + return msg_attr_keep((char_u *)s, attr, false, multiline); +} + +/// emsg() - display an error message +/// +/// Rings the bell, if appropriate, and calls message() to do the real work +/// When terminal not initialized (yet) mch_errmsg(..) is used. +/// +/// @return true if wait_return not called +bool emsg(const char_u *s) +{ + return emsg_multiline((const char *)s, false); } void emsg_invreg(int name) @@ -595,6 +654,28 @@ bool emsgf(const char *const fmt, ...) return ret; } +#define MULTILINE_BUFSIZE 8192 + +bool emsgf_multiline(const char *const fmt, ...) +{ + bool ret; + va_list ap; + + + static char errbuf[MULTILINE_BUFSIZE]; + if (emsg_not_now()) { + return true; + } + + va_start(ap, fmt); + vim_vsnprintf(errbuf, sizeof(errbuf), fmt, ap, NULL); + va_end(ap); + + ret = emsg_multiline(errbuf, true); + + return ret; +} + /// Print an error message with unknown number of arguments static bool emsgfv(const char *fmt, va_list ap) { @@ -669,7 +750,7 @@ char_u *msg_trunc_attr(char_u *s, int force, int attr) int n; // Add message to history before truncating. - add_msg_hist((const char *)s, -1, attr); + add_msg_hist((const char *)s, -1, attr, false); s = msg_may_trunc(force, s); @@ -715,7 +796,7 @@ char_u *msg_may_trunc(int force, char_u *s) } /// @param[in] len Length of s or -1. -static void add_msg_hist(const char *s, int len, int attr) +static void add_msg_hist(const char *s, int len, int attr, bool multiline) { if (msg_hist_off || msg_silent != 0) return; @@ -739,12 +820,16 @@ static void add_msg_hist(const char *s, int len, int attr) p->msg = (char_u *)xmemdupz(s, (size_t)len); p->next = NULL; p->attr = attr; - if (last_msg_hist != NULL) + p->multiline = multiline; + p->kind = msg_ext_kind; + if (last_msg_hist != NULL) { last_msg_hist->next = p; + } last_msg_hist = p; - if (first_msg_hist == NULL) + if (first_msg_hist == NULL) { first_msg_hist = last_msg_hist; - ++msg_hist_len; + } + msg_hist_len++; } /* @@ -791,7 +876,6 @@ void ex_messages(void *const eap_p) return; } - msg_hist_off = true; p = first_msg_hist; @@ -809,13 +893,31 @@ void ex_messages(void *const eap_p) } // Display what was not skipped. - for (; p != NULL && !got_int; p = p->next) { - if (p->msg != NULL) { - msg_attr((const char *)p->msg, p->attr); + if (ui_has(kUIMessages)) { + Array entries = ARRAY_DICT_INIT; + for (; p != NULL; p = p->next) { + if (p->msg != NULL && p->msg[0] != NUL) { + Array entry = ARRAY_DICT_INIT; + ADD(entry, STRING_OBJ(cstr_to_string(p->kind))); + Array content_entry = ARRAY_DICT_INIT; + ADD(content_entry, INTEGER_OBJ(p->attr)); + ADD(content_entry, STRING_OBJ(cstr_to_string((char *)(p->msg)))); + Array content = ARRAY_DICT_INIT; + ADD(content, ARRAY_OBJ(content_entry)); + ADD(entry, ARRAY_OBJ(content)); + ADD(entries, ARRAY_OBJ(entry)); + } + } + ui_call_msg_history_show(entries); + } else { + msg_hist_off = true; + for (; p != NULL && !got_int; p = p->next) { + if (p->msg != NULL) { + msg_attr_keep(p->msg, p->attr, false, p->multiline); + } } + msg_hist_off = false; } - - msg_hist_off = false; } /* @@ -881,9 +983,9 @@ void wait_return(int redraw) c = CAR; /* no need for a return in ex mode */ got_int = FALSE; } else { - /* Make sure the hit-return prompt is on screen when 'guioptions' was - * just changed. */ - screenalloc(false); + // Make sure the hit-return prompt is on screen when 'guioptions' was + // just changed. + screenalloc(); State = HITRETURN; setmouse(); @@ -993,8 +1095,9 @@ void wait_return(int redraw) if (c == ':' || c == '?' || c == '/') { if (!exmode_active) cmdline_row = msg_row; - skip_redraw = TRUE; /* skip redraw once */ - do_redraw = FALSE; + skip_redraw = true; // skip redraw once + do_redraw = false; + msg_ext_keep_after_cmdline = true; } /* @@ -1019,9 +1122,13 @@ void wait_return(int redraw) if (tmpState == SETWSIZE) { /* got resize event while in vgetc() */ ui_refresh(); - } else if (!skip_redraw - && (redraw == TRUE || (msg_scrolled != 0 && redraw != -1))) { - redraw_later(VALID); + } else if (!skip_redraw) { + if (redraw == true || (msg_scrolled != 0 && redraw != -1)) { + redraw_later(VALID); + } + if (ui_has(kUIMessages)) { + msg_ext_clear(true); + } } } @@ -1035,8 +1142,10 @@ static void hit_return_msg(void) p_more = FALSE; /* don't want see this message when scrolling back */ if (msg_didout) /* start on a new line */ msg_putchar('\n'); - if (got_int) + msg_ext_set_kind("return_prompt"); + if (got_int) { MSG_PUTS(_("Interrupt: ")); + } MSG_PUTS_ATTR(_("Press ENTER or type command to continue"), HL_ATTR(HLF_R)); if (!msg_use_printf()) { @@ -1059,6 +1168,17 @@ void set_keep_msg(char_u *s, int attr) keep_msg_attr = attr; } +void msg_ext_set_kind(const char *msg_kind) +{ + // Don't change the label of an existing batch: + msg_ext_ui_flush(); + + // TODO(bfredl): would be nice to avoid dynamic scoping, but that would + // need refactoring the msg_ interface to not be "please pretend nvim is + // a terminal for a moment" + msg_ext_kind = msg_kind; +} + /* * Prepare for outputting characters in the command line. */ @@ -1095,6 +1215,14 @@ void msg_start(void) msg_didout = FALSE; /* no output on current line yet */ } + if (ui_has(kUIMessages)) { + msg_ext_ui_flush(); + if (!msg_scroll && msg_ext_visible) { + // Will overwrite last message. + msg_ext_overwrite = true; + } + } + // When redirecting, may need to start a new line. if (!did_return) { redir_write("\n", 1); @@ -1205,7 +1333,7 @@ int msg_outtrans_len_attr(char_u *msgstr, int len, int attr) /* if MSG_HIST flag set, add message to history */ if (attr & MSG_HIST) { - add_msg_hist(str, len, attr); + add_msg_hist(str, len, attr, false); attr &= ~MSG_HIST; } @@ -1452,31 +1580,36 @@ void msg_prt_line(char_u *s, int list) int col = 0; int n_extra = 0; int c_extra = 0; - char_u *p_extra = NULL; /* init to make SASC shut up */ + int c_final = 0; + char_u *p_extra = NULL; // init to make SASC shut up int n; int attr = 0; - char_u *trail = NULL; + char_u *trail = NULL; int l; - if (curwin->w_p_list) - list = TRUE; + if (curwin->w_p_list) { + list = true; + } - /* find start of trailing whitespace */ - if (list && lcs_trail) { + // find start of trailing whitespace + if (list && curwin->w_p_lcs_chars.trail) { trail = s + STRLEN(s); - while (trail > s && ascii_iswhite(trail[-1])) - --trail; + while (trail > s && ascii_iswhite(trail[-1])) { + trail--; + } } - /* output a space for an empty line, otherwise the line will be - * overwritten */ - if (*s == NUL && !(list && lcs_eol != NUL)) + // output a space for an empty line, otherwise the line will be overwritten + if (*s == NUL && !(list && curwin->w_p_lcs_chars.eol != NUL)) { msg_putchar(' '); + } while (!got_int) { if (n_extra > 0) { n_extra--; - if (c_extra) { + if (n_extra == 0 && c_final) { + c = c_final; + } else if (c_extra) { c = c_extra; } else { assert(p_extra != NULL); @@ -1485,9 +1618,9 @@ void msg_prt_line(char_u *s, int list) } else if ((l = utfc_ptr2len(s)) > 1) { col += utf_ptr2cells(s); char buf[MB_MAXBYTES + 1]; - if (lcs_nbsp != NUL && list + if (curwin->w_p_lcs_chars.nbsp != NUL && list && (utf_ptr2char(s) == 160 || utf_ptr2char(s) == 0x202f)) { - utf_char2bytes(lcs_nbsp, (char_u *)buf); + utf_char2bytes(curwin->w_p_lcs_chars.nbsp, (char_u *)buf); buf[utfc_ptr2len((char_u *)buf)] = NUL; } else { memmove(buf, s, (size_t)l); @@ -1499,40 +1632,46 @@ void msg_prt_line(char_u *s, int list) } else { attr = 0; c = *s++; - if (c == TAB && (!list || lcs_tab1)) { - /* tab amount depends on current column */ + if (c == TAB && (!list || curwin->w_p_lcs_chars.tab1)) { + // tab amount depends on current column n_extra = curbuf->b_p_ts - col % curbuf->b_p_ts - 1; if (!list) { c = ' '; c_extra = ' '; + c_final = NUL; } else { - c = lcs_tab1; - c_extra = lcs_tab2; + c = (n_extra == 0 && curwin->w_p_lcs_chars.tab3) + ? curwin->w_p_lcs_chars.tab3 + : curwin->w_p_lcs_chars.tab1; + c_extra = curwin->w_p_lcs_chars.tab2; + c_final = curwin->w_p_lcs_chars.tab3; attr = HL_ATTR(HLF_8); } - } else if (c == 160 && list && lcs_nbsp != NUL) { - c = lcs_nbsp; + } else if (c == 160 && list && curwin->w_p_lcs_chars.nbsp != NUL) { + c = curwin->w_p_lcs_chars.nbsp; attr = HL_ATTR(HLF_8); - } else if (c == NUL && list && lcs_eol != NUL) { + } else if (c == NUL && list && curwin->w_p_lcs_chars.eol != NUL) { p_extra = (char_u *)""; c_extra = NUL; + c_final = NUL; n_extra = 1; - c = lcs_eol; + c = curwin->w_p_lcs_chars.eol; attr = HL_ATTR(HLF_AT); s--; } else if (c != NUL && (n = byte2cells(c)) > 1) { n_extra = n - 1; p_extra = transchar_byte(c); c_extra = NUL; + c_final = NUL; c = *p_extra++; /* Use special coloring to be able to distinguish <hex> from * the same in plain text. */ attr = HL_ATTR(HLF_8); } else if (c == ' ' && trail != NULL && s > trail) { - c = lcs_trail; + c = curwin->w_p_lcs_chars.trail; attr = HL_ATTR(HLF_8); - } else if (c == ' ' && list && lcs_space != NUL) { - c = lcs_space; + } else if (c == ' ' && list && curwin->w_p_lcs_chars.space != NUL) { + c = curwin->w_p_lcs_chars.space; attr = HL_ATTR(HLF_8); } } @@ -1643,7 +1782,7 @@ void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr) // if MSG_HIST flag set, add message to history if (attr & MSG_HIST) { - add_msg_hist(str, (int)len, attr); + add_msg_hist(str, (int)len, attr, false); attr &= ~MSG_HIST; } @@ -1651,7 +1790,18 @@ void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr) // wait-return prompt later. Needed when scrolling, resetting // need_wait_return after some prompt, and then outputting something // without scrolling - if (msg_scrolled != 0 && !msg_scrolled_ign) { + bool overflow = false; + if (ui_has(kUIMessages)) { + int count = msg_ext_visible + (msg_ext_overwrite ? 0 : 1); + // TODO(bfredl): possible extension point, let external UI control this + if (count > 1) { + overflow = true; + } + } else { + overflow = msg_scrolled != 0; + } + + if (overflow && !msg_scrolled_ign) { need_wait_return = true; } msg_didany = true; // remember that something was outputted @@ -1689,6 +1839,20 @@ void msg_printf_attr(const int attr, const char *const fmt, ...) msg_puts_attr_len(msgbuf, (ptrdiff_t)len, attr); } +static void msg_ext_emit_chunk(void) +{ + // Color was changed or a message flushed, end current chunk. + if (msg_ext_last_attr == -1) { + return; // no chunk + } + Array chunk = ARRAY_DICT_INIT; + ADD(chunk, INTEGER_OBJ(msg_ext_last_attr)); + msg_ext_last_attr = -1; + String text = ga_take_string(&msg_ext_last_chunk); + ADD(chunk, STRING_OBJ(text)); + ADD(msg_ext_chunks, ARRAY_OBJ(chunk)); +} + /* * The display part of msg_puts_attr_len(). * May be called recursively to display scroll-back text. @@ -1707,6 +1871,18 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int did_last_char; did_wait_return = false; + + if (ui_has(kUIMessages)) { + if (attr != msg_ext_last_attr) { + msg_ext_emit_chunk(); + msg_ext_last_attr = attr; + } + // Concat pieces with the same highlight + ga_concat_len(&msg_ext_last_chunk, (char *)str, + strnlen((char *)str, maxlen)); + return; + } + while ((maxlen < 0 || (int)(s - str) < maxlen) && *s != NUL) { // We are at the end of the screen line when: // - When outputting a newline. @@ -1884,13 +2060,15 @@ int msg_scrollsize(void) */ static void msg_scroll_up(void) { - if (msg_scrolled == 0) { + if (!msg_did_scroll) { ui_call_win_scroll_over_start(); + msg_did_scroll = true; } if (dy_flags & DY_MSGSEP) { if (msg_scrolled == 0) { grid_fill(&default_grid, Rows-p_ch-1, Rows-p_ch, 0, (int)Columns, - fill_msgsep, fill_msgsep, HL_ATTR(HLF_MSGSEP)); + curwin->w_p_fcs_chars.msgsep, curwin->w_p_fcs_chars.msgsep, + HL_ATTR(HLF_MSGSEP)); } int nscroll = MIN(msg_scrollsize()+1, Rows); grid_del_lines(&default_grid, Rows-nscroll, 1, Rows, 0, Columns); @@ -2314,9 +2492,8 @@ static int do_more_prompt(int typed_char) mp_last = msg_sb_start(mp_last->sb_prev); } - if (toscroll == -1 - && grid_ins_lines(&default_grid, 0, 1, (int)Rows, - 0, (int)Columns) == OK) { + if (toscroll == -1) { + grid_ins_lines(&default_grid, 0, 1, (int)Rows, 0, (int)Columns); grid_fill(&default_grid, 0, 1, 0, (int)Columns, ' ', ' ', 0); // display line at top (void)disp_sb_line(0, mp); @@ -2530,6 +2707,9 @@ void msg_clr_eos(void) */ void msg_clr_eos_force(void) { + if (ui_has(kUIMessages)) { + return; + } int msg_startcol = (cmdmsg_rl) ? 0 : msg_col; int msg_endcol = (cmdmsg_rl) ? msg_col + 1 : (int)Columns; @@ -2566,8 +2746,66 @@ int msg_end(void) wait_return(FALSE); return FALSE; } - ui_flush(); - return TRUE; + + // @TODO(bfredl): calling flush here inhibits substantial performance + // improvements. Caller should call ui_flush before waiting on user input or + // CPU busywork. + ui_flush(); // calls msg_ext_ui_flush + return true; +} + +void msg_ext_ui_flush(void) +{ + if (!ui_has(kUIMessages)) { + return; + } + + msg_ext_emit_chunk(); + if (msg_ext_chunks.size > 0) { + ui_call_msg_show(cstr_to_string(msg_ext_kind), + msg_ext_chunks, msg_ext_overwrite); + if (!msg_ext_overwrite) { + msg_ext_visible++; + } + msg_ext_kind = NULL; + msg_ext_chunks = (Array)ARRAY_DICT_INIT; + msg_ext_overwrite = false; + } +} + +void msg_ext_flush_showmode(void) +{ + // Showmode messages doesn't interrupt normal message flow, so we use + // separate event. Still reuse the same chunking logic, for simplicity. + msg_ext_emit_chunk(); + ui_call_msg_showmode(msg_ext_chunks); + msg_ext_chunks = (Array)ARRAY_DICT_INIT; +} + +void msg_ext_clear(bool force) +{ + if (msg_ext_visible && (!msg_ext_keep_after_cmdline || force)) { + ui_call_msg_clear(); + msg_ext_visible = 0; + msg_ext_overwrite = false; // nothing to overwrite + } + + // Only keep once. + msg_ext_keep_after_cmdline = false; +} + +void msg_ext_check_prompt(void) +{ + // Redraw after cmdline is expected to clear messages. + if (msg_ext_did_cmdline) { + msg_ext_clear(true); + msg_ext_did_cmdline = false; + } +} + +bool msg_ext_is_visible(void) +{ + return ui_has(kUIMessages) && msg_ext_visible > 0; } /* @@ -2576,6 +2814,9 @@ int msg_end(void) */ void msg_check(void) { + if (ui_has(kUIMessages)) { + return; + } if (msg_row == Rows - 1 && msg_col >= sc_col) { need_wait_return = TRUE; redraw_cmdline = TRUE; diff --git a/src/nvim/message.h b/src/nvim/message.h index 82935a36a9..7938fd91d3 100644 --- a/src/nvim/message.h +++ b/src/nvim/message.h @@ -5,6 +5,7 @@ #include <stdarg.h> #include <stddef.h> +#include "nvim/macros.h" #include "nvim/types.h" /* @@ -77,7 +78,9 @@ typedef struct msg_hist { struct msg_hist *next; ///< Next message. char_u *msg; ///< Message text. + const char *kind; ///< Message kind (for msg_ext) int attr; ///< Message highlighting. + bool multiline; ///< Multiline message. } MessageHistoryEntry; /// First message @@ -85,6 +88,8 @@ extern MessageHistoryEntry *first_msg_hist; /// Last message extern MessageHistoryEntry *last_msg_hist; +EXTERN bool msg_ext_did_cmdline INIT(= false); + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "message.h.generated.h" #endif diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index a66ded13f1..e752910a4b 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -1251,7 +1251,7 @@ int plines_win_nofill( return 1; } - if (wp->w_grid.Columns == 0) { + if (wp->w_width_inner == 0) { return 1; } @@ -1261,8 +1261,8 @@ int plines_win_nofill( } const int lines = plines_win_nofold(wp, lnum); - if (winheight && lines > wp->w_grid.Rows) { - return wp->w_grid.Rows; + if (winheight && lines > wp->w_height_inner) { + return wp->w_height_inner; } return lines; } @@ -1282,17 +1282,16 @@ int plines_win_nofold(win_T *wp, linenr_T lnum) return 1; col = win_linetabsize(wp, s, (colnr_T)MAXCOL); - /* - * If list mode is on, then the '$' at the end of the line may take up one - * extra column. - */ - if (wp->w_p_list && lcs_eol != NUL) + // If list mode is on, then the '$' at the end of the line may take up one + // extra column. + if (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL) { col += 1; + } /* * Add column offset for 'number', 'relativenumber' and 'foldcolumn'. */ - width = wp->w_grid.Columns - win_col_off(wp); + width = wp->w_width_inner - win_col_off(wp); if (width <= 0 || col > 32000) { return 32000; // bigger than the number of screen columns } @@ -1318,7 +1317,7 @@ int plines_win_col(win_T *wp, linenr_T lnum, long column) if (!wp->w_p_wrap) return lines + 1; - if (wp->w_grid.Columns == 0) { + if (wp->w_width_inner == 0) { return lines + 1; } @@ -1336,12 +1335,13 @@ int plines_win_col(win_T *wp, linenr_T lnum, long column) // screen position of the TAB. This only fixes an error when the TAB wraps // from one screen line to the next (when 'columns' is not a multiple of // 'ts') -- webb. - if (*s == TAB && (State & NORMAL) && (!wp->w_p_list || lcs_tab1)) { + if (*s == TAB && (State & NORMAL) + && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { col += win_lbr_chartabsize(wp, line, s, col, NULL) - 1; } // Add column offset for 'number', 'relativenumber', 'foldcolumn', etc. - int width = wp->w_grid.Columns - win_col_off(wp); + int width = wp->w_width_inner - win_col_off(wp); if (width <= 0) { return 9999; } diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index 887cbde921..50dba92eca 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -70,6 +70,7 @@ int jump_to_mouse(int flags, bool first; int row = mouse_row; int col = mouse_col; + int grid = mouse_grid; int mouse_char; mouse_past_bottom = false; @@ -125,20 +126,22 @@ retnomove: return IN_UNKNOWN; // find the window where the row is in - wp = mouse_find_win(&row, &col); + wp = mouse_find_win(&grid, &row, &col); if (wp == NULL) { return IN_UNKNOWN; } dragwin = NULL; // winpos and height may change in win_enter()! - if (row >= wp->w_height) { // In (or below) status line + if (grid == DEFAULT_GRID_HANDLE && row >= wp->w_height) { + // In (or below) status line on_status_line = row - wp->w_height + 1; dragwin = wp; } else { on_status_line = 0; } - if (col >= wp->w_width) { // In separator line + if (grid == DEFAULT_GRID_HANDLE && col >= wp->w_width) { + // In separator line on_sep_line = col - wp->w_width + 1; dragwin = wp; } else { @@ -160,12 +163,10 @@ retnomove: && (wp->w_buffer != curwin->w_buffer || (!on_status_line && !on_sep_line - && ( - wp->w_p_rl ? col < wp->w_width - wp->w_p_fdc : - col >= wp->w_p_fdc - + (cmdwin_type == 0 && wp == - curwin ? 0 : 1) - ) + && (wp->w_p_rl + ? col < wp->w_width_inner - wp->w_p_fdc + : col >= wp->w_p_fdc + (cmdwin_type == 0 && wp == curwin + ? 0 : 1)) && (flags & MOUSE_MAY_STOP_VIS)))) { end_visual_mode(); redraw_curbuf_later(INVERTED); // delete the inversion @@ -257,7 +258,7 @@ retnomove: ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); redraw_later(VALID); row = 0; - } else if (row >= curwin->w_height) { + } else if (row >= curwin->w_height_inner) { count = 0; for (first = true; curwin->w_topline < curbuf->b_ml.ml_line_count; ) { if (curwin->w_topfill > 0) { @@ -266,7 +267,7 @@ retnomove: count += plines(curwin->w_topline); } - if (!first && count > row - curwin->w_height + 1) { + if (!first && count > row - curwin->w_height_inner + 1) { break; } first = false; @@ -288,7 +289,7 @@ retnomove: redraw_later(VALID); curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); - row = curwin->w_height - 1; + row = curwin->w_height_inner - 1; } else if (row == 0) { // When dragging the mouse, while the text has been scrolled up as // far as it goes, moving the mouse in the top line should scroll @@ -303,7 +304,7 @@ retnomove: } // Check for position outside of the fold column. - if (curwin->w_p_rl ? col < curwin->w_width - curwin->w_p_fdc : + if (curwin->w_p_rl ? col < curwin->w_width_inner - curwin->w_p_fdc : col >= curwin->w_p_fdc + (cmdwin_type == 0 ? 0 : 1)) { mouse_char = ' '; } @@ -369,8 +370,9 @@ bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump) int off; int count; - if (win->w_p_rl) - col = win->w_width - 1 - col; + if (win->w_p_rl) { + col = win->w_width_inner - 1 - col; + } lnum = win->w_topline; @@ -407,7 +409,7 @@ bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump) off = win_col_off(win) - win_col_off2(win); if (col < off) col = off; - col += row * (win->w_width - off); + col += row * (win->w_width_inner - off); // add skip column (for long wrapping line) col += win->w_skipcol; } @@ -428,11 +430,23 @@ bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump) return retval; } -// Find the window at screen position "*rowp" and "*colp". The positions are -// updated to become relative to the top-left of the window. -// Returns NULL when something is wrong. -win_T *mouse_find_win(int *rowp, int *colp) +/// Find the window at "grid" position "*rowp" and "*colp". The positions are +/// updated to become relative to the top-left of the window. +/// +/// @return NULL when something is wrong. +win_T *mouse_find_win(int *gridp, int *rowp, int *colp) { + win_T *wp_grid = mouse_find_grid_win(gridp, rowp, colp); + if (wp_grid) { + return wp_grid; + } + + // TODO(bfredl): grid zero will have floats displayed on it, and will + // be adjusted to float grids. + if (*gridp == 0) { + *gridp = DEFAULT_GRID_HANDLE; + } + frame_T *fp; fp = topframe; @@ -464,6 +478,19 @@ win_T *mouse_find_win(int *rowp, int *colp) return NULL; } +static win_T *mouse_find_grid_win(int *grid, int *rowp, int *colp) +{ + if (*grid > 1) { + win_T *wp = get_win_by_grid_handle(*grid); + if (wp && wp->w_grid.chars) { + *rowp = MIN(*rowp, wp->w_grid.Rows-1); + *colp = MIN(*colp, wp->w_grid.Columns-1); + return wp; + } + } + return NULL; +} + /* * setmouse() - switch mouse on/off depending on current mode and 'mouse' */ @@ -595,7 +622,7 @@ bool mouse_scroll_horiz(int dir) int step = 6; if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { - step = curwin->w_width; + step = curwin->w_width_inner; } int leftcol = curwin->w_leftcol + (dir == MSCR_RIGHT ? -step : +step); @@ -647,7 +674,7 @@ static int mouse_adjust_click(win_T *wp, int row, int col) // Find the offset where scanning should begin. int offset = wp->w_leftcol; if (row > 0) { - offset += row * (wp->w_width - win_col_off(wp) - win_col_off2(wp) - + offset += row * (wp->w_width_inner - win_col_off(wp) - win_col_off2(wp) - wp->w_leftcol + wp->w_skipcol); } @@ -704,7 +731,7 @@ static int mouse_adjust_click(win_T *wp, int row, int col) } else { if (!(row > 0 && ptr == ptr_row_offset) && (wp->w_p_cole == 1 || (wp->w_p_cole == 2 - && (lcs_conceal != NUL + && (wp->w_p_lcs_chars.conceal != NUL || syn_get_sub_char() != NUL)))) { // At least one placeholder character will be displayed. decr(); diff --git a/src/nvim/move.c b/src/nvim/move.c index 07b355e603..7aa7f922c1 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -83,7 +83,7 @@ static void comp_botline(win_T *wp) redraw_for_cursorline(wp); wp->w_valid |= (VALID_CROW|VALID_CHEIGHT); } - if (done + n > wp->w_grid.Rows) { + if (done + n > wp->w_height_inner) { break; } done += n; @@ -150,12 +150,9 @@ void update_topline(void) bool check_botline = false; long save_so = p_so; - // need to have w_grid.Rows/Columns updated - win_grid_alloc(curwin); - // If there is no valid screen and when the window height is zero just use // the cursor line. - if (!screen_valid(true) || curwin->w_grid.Rows == 0) { + if (!default_grid.chars || curwin->w_height_inner == 0) { curwin->w_topline = curwin->w_cursor.lnum; curwin->w_botline = curwin->w_topline; curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP; @@ -204,7 +201,7 @@ void update_topline(void) check_topline = true; if (check_topline) { - int halfheight = curwin->w_grid.Rows / 2 - 1; + int halfheight = curwin->w_height_inner / 2 - 1; if (halfheight < 2) { halfheight = 2; } @@ -297,7 +294,7 @@ void update_topline(void) lnum >= curwin->w_botline - p_so; lnum--) { line_count++; // stop at end of file or when we know we are far off - if (lnum <= 0 || line_count > curwin->w_grid.Rows + 1) { + if (lnum <= 0 || line_count > curwin->w_height_inner + 1) { break; } (void)hasFolding(lnum, &lnum, NULL); @@ -305,7 +302,7 @@ void update_topline(void) } else line_count = curwin->w_cursor.lnum - curwin->w_botline + 1 + p_so; - if (line_count <= curwin->w_grid.Rows + 1) { + if (line_count <= curwin->w_height_inner + 1) { scroll_cursor_bot(scrolljump_value(), false); } else { scroll_cursor_halfway(false); @@ -353,7 +350,7 @@ void update_topline_win(win_T* win) */ static int scrolljump_value(void) { - long result = p_sj >= 0 ? p_sj : (curwin->w_grid.Rows * -p_sj) / 100; + long result = p_sj >= 0 ? p_sj : (curwin->w_height_inner * -p_sj) / 100; assert(result <= INT_MAX); return (int)result; } @@ -529,7 +526,6 @@ int cursor_valid(void) */ void validate_cursor(void) { - win_grid_alloc(curwin); // we need to have w_grid.Rows/Columns updated check_cursor_moved(curwin); if ((curwin->w_valid & (VALID_WCOL|VALID_WROW)) != (VALID_WCOL|VALID_WROW)) curs_columns(true); @@ -668,13 +664,13 @@ void validate_cursor_col(void) colnr_T col = curwin->w_virtcol; colnr_T off = curwin_col_off(); col += off; - int width = curwin->w_grid.Columns - off + curwin_col_off2(); + int width = curwin->w_width_inner - off + curwin_col_off2(); // long line wrapping, adjust curwin->w_wrow - if (curwin->w_p_wrap && col >= (colnr_T)curwin->w_grid.Columns + if (curwin->w_p_wrap && col >= (colnr_T)curwin->w_width_inner && width > 0) { // use same formula as what is used in curs_columns() - col -= ((col - curwin->w_grid.Columns) / width + 1) * width; + col -= ((col - curwin->w_width_inner) / width + 1) * width; } if (col > (int)curwin->w_leftcol) { col -= curwin->w_leftcol; @@ -769,20 +765,20 @@ void curs_columns( */ curwin->w_wrow = curwin->w_cline_row; - int textwidth = curwin->w_grid.Columns - extra; + int textwidth = curwin->w_width_inner - extra; if (textwidth <= 0) { // No room for text, put cursor in last char of window. - curwin->w_wcol = curwin->w_grid.Columns - 1; - curwin->w_wrow = curwin->w_grid.Rows - 1; + curwin->w_wcol = curwin->w_width_inner - 1; + curwin->w_wrow = curwin->w_height_inner - 1; } else if (curwin->w_p_wrap - && curwin->w_grid.Columns != 0 + && curwin->w_width_inner != 0 ) { width = textwidth + curwin_col_off2(); // long line wrapping, adjust curwin->w_wrow - if (curwin->w_wcol >= curwin->w_grid.Columns) { + if (curwin->w_wcol >= curwin->w_width_inner) { // this same formula is used in validate_cursor_col() - n = (curwin->w_wcol - curwin->w_grid.Columns) / width + 1; + n = (curwin->w_wcol - curwin->w_width_inner) / width + 1; curwin->w_wcol -= n * width; curwin->w_wrow += n; @@ -809,7 +805,7 @@ void curs_columns( assert(p_siso <= INT_MAX); int off_left = startcol - curwin->w_leftcol - (int)p_siso; int off_right = - endcol - curwin->w_leftcol - curwin->w_grid.Columns + (int)p_siso + 1; + endcol - curwin->w_leftcol - curwin->w_width_inner + (int)p_siso + 1; if (off_left < 0 || off_right > 0) { int diff = (off_left < 0) ? -off_left: off_right; @@ -852,16 +848,16 @@ void curs_columns( prev_skipcol = curwin->w_skipcol; int p_lines = 0; - if ((curwin->w_wrow >= curwin->w_grid.Rows + if ((curwin->w_wrow >= curwin->w_height_inner || ((prev_skipcol > 0 - || curwin->w_wrow + p_so >= curwin->w_grid.Rows) + || curwin->w_wrow + p_so >= curwin->w_height_inner) && (p_lines = plines_win_nofill(curwin, curwin->w_cursor.lnum, false)) - 1 - >= curwin->w_grid.Rows)) - && curwin->w_grid.Rows != 0 + >= curwin->w_height_inner)) + && curwin->w_height_inner != 0 && curwin->w_cursor.lnum == curwin->w_topline && width > 0 - && curwin->w_grid.Columns != 0 + && curwin->w_width_inner != 0 ) { /* Cursor past end of screen. Happens with a single line that does * not fit on screen. Find a skipcol to show the text around the @@ -883,21 +879,21 @@ void curs_columns( } else n = p_lines; - if ((colnr_T)n >= curwin->w_grid.Rows + curwin->w_skipcol / width) { + if ((colnr_T)n >= curwin->w_height_inner + curwin->w_skipcol / width) { extra += 2; } if (extra == 3 || p_lines < p_so * 2) { // not enough room for 'scrolloff', put cursor in the middle n = curwin->w_virtcol / width; - if (n > curwin->w_grid.Rows / 2) { - n -= curwin->w_grid.Rows / 2; + if (n > curwin->w_height_inner / 2) { + n -= curwin->w_height_inner / 2; } else { n = 0; } // don't skip more than necessary - if (n > p_lines - curwin->w_grid.Rows + 1) { - n = p_lines - curwin->w_grid.Rows + 1; + if (n > p_lines - curwin->w_height_inner + 1) { + n = p_lines - curwin->w_height_inner + 1; } curwin->w_skipcol = n * width; } else if (extra == 1) { @@ -912,7 +908,7 @@ void curs_columns( } } else if (extra == 2) { // less then 'scrolloff' lines below, increase skipcol - endcol = (n - curwin->w_grid.Rows + 1) * width; + endcol = (n - curwin->w_height_inner + 1) * width; while (endcol > curwin->w_virtcol) { endcol -= width; } @@ -922,19 +918,16 @@ void curs_columns( } curwin->w_wrow -= curwin->w_skipcol / width; - if (curwin->w_wrow >= curwin->w_grid.Rows) { + if (curwin->w_wrow >= curwin->w_height_inner) { // small window, make sure cursor is in it - extra = curwin->w_wrow - curwin->w_grid.Rows + 1; + extra = curwin->w_wrow - curwin->w_height_inner + 1; curwin->w_skipcol += extra * width; curwin->w_wrow -= extra; } + // extra could be either positive or negative extra = ((int)prev_skipcol - (int)curwin->w_skipcol) / width; - if (extra > 0) { - win_ins_lines(curwin, 0, extra); - } else if (extra < 0) { - win_del_lines(curwin, 0, -extra); - } + win_scroll_lines(curwin, 0, extra); } else { curwin->w_skipcol = 0; } @@ -966,7 +959,7 @@ scrolldown ( validate_cursor(); /* w_wrow needs to be valid */ while (line_count-- > 0) { if (curwin->w_topfill < diff_check(curwin, curwin->w_topline) - && curwin->w_topfill < curwin->w_grid.Rows - 1) { + && curwin->w_topfill < curwin->w_height_inner - 1) { curwin->w_topfill++; done++; } else { @@ -1001,15 +994,15 @@ scrolldown ( */ int wrow = curwin->w_wrow; if (curwin->w_p_wrap - && curwin->w_grid.Columns != 0 + && curwin->w_width_inner != 0 ) { validate_virtcol(); validate_cheight(); wrow += curwin->w_cline_height - 1 - - curwin->w_virtcol / curwin->w_grid.Columns; + curwin->w_virtcol / curwin->w_width_inner; } bool moved = false; - while (wrow >= curwin->w_grid.Rows && curwin->w_cursor.lnum > 1) { + while (wrow >= curwin->w_height_inner && curwin->w_cursor.lnum > 1) { linenr_T first; if (hasFolding(curwin->w_cursor.lnum, &first, NULL)) { --wrow; @@ -1094,12 +1087,12 @@ check_topfill ( { if (wp->w_topfill > 0) { int n = plines_win_nofill(wp, wp->w_topline, true); - if (wp->w_topfill + n > wp->w_grid.Rows) { + if (wp->w_topfill + n > wp->w_height_inner) { if (down && wp->w_topline > 1) { --wp->w_topline; wp->w_topfill = 0; } else { - wp->w_topfill = wp->w_grid.Rows - n; + wp->w_topfill = wp->w_height_inner - n; if (wp->w_topfill < 0) { wp->w_topfill = 0; } @@ -1115,12 +1108,12 @@ check_topfill ( static void max_topfill(void) { int n = plines_nofill(curwin->w_topline); - if (n >= curwin->w_grid.Rows) { + if (n >= curwin->w_height_inner) { curwin->w_topfill = 0; } else { curwin->w_topfill = diff_check_fill(curwin, curwin->w_topline); - if (curwin->w_topfill + n > curwin->w_grid.Rows) { - curwin->w_topfill = curwin->w_grid.Rows - n; + if (curwin->w_topfill + n > curwin->w_height_inner) { + curwin->w_topfill = curwin->w_height_inner - n; } } } @@ -1152,14 +1145,14 @@ void scrolldown_clamp(void) else end_row += plines_nofill(curwin->w_topline - 1); if (curwin->w_p_wrap - && curwin->w_grid.Columns != 0 + && curwin->w_width_inner != 0 ) { validate_cheight(); validate_virtcol(); end_row += curwin->w_cline_height - 1 - - curwin->w_virtcol / curwin->w_grid.Columns; + curwin->w_virtcol / curwin->w_width_inner; } - if (end_row < curwin->w_grid.Rows - p_so) { + if (end_row < curwin->w_height_inner - p_so) { if (can_fill) { ++curwin->w_topfill; check_topfill(curwin, true); @@ -1194,10 +1187,10 @@ void scrollup_clamp(void) int start_row = curwin->w_wrow - plines_nofill(curwin->w_topline) - curwin->w_topfill; if (curwin->w_p_wrap - && curwin->w_grid.Columns != 0 + && curwin->w_width_inner != 0 ) { validate_virtcol(); - start_row -= curwin->w_virtcol / curwin->w_grid.Columns; + start_row -= curwin->w_virtcol / curwin->w_width_inner; } if (start_row >= p_so) { if (curwin->w_topfill > 0) @@ -1351,7 +1344,7 @@ void scroll_cursor_top(int min_scroll, int always) else used += plines(bot); } - if (used > curwin->w_grid.Rows) { + if (used > curwin->w_height_inner) { break; } if (top < curwin->w_topline) { @@ -1376,7 +1369,7 @@ void scroll_cursor_top(int min_scroll, int always) * This makes sure we get the same position when using "k" and "j" * in a small window. */ - if (used > curwin->w_grid.Rows) { + if (used > curwin->w_height_inner) { scroll_cursor_halfway(false); } else { /* @@ -1413,7 +1406,7 @@ void set_empty_rows(win_T *wp, int used) if (used == 0) { wp->w_empty_rows = 0; // single line that doesn't fit } else { - wp->w_empty_rows = wp->w_grid.Rows - used; + wp->w_empty_rows = wp->w_height_inner - used; if (wp->w_botline <= wp->w_buffer->b_ml.ml_line_count) { wp->w_filler_rows = diff_check_fill(wp, wp->w_botline); if (wp->w_empty_rows > wp->w_filler_rows) @@ -1456,7 +1449,8 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) curwin->w_topline = loff.lnum) { loff.lnum = curwin->w_topline; topline_back(&loff); - if (loff.height == MAXCOL || used + loff.height > curwin->w_grid.Rows) { + if (loff.height == MAXCOL + || used + loff.height > curwin->w_height_inner) { break; } used += loff.height; @@ -1520,7 +1514,7 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) } else { used += loff.height; } - if (used > curwin->w_grid.Rows) { + if (used > curwin->w_height_inner) { break; } if (loff.lnum >= curwin->w_botline @@ -1539,7 +1533,7 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) /* Add one line below */ botline_forw(&boff); used += boff.height; - if (used > curwin->w_grid.Rows) { + if (used > curwin->w_height_inner) { break; } if (extra < ( @@ -1566,7 +1560,7 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) if (scrolled <= 0) { line_count = 0; // more than a screenfull, don't scroll but redraw - } else if (used > curwin->w_grid.Rows) { + } else if (used > curwin->w_height_inner) { line_count = used; // scroll minimal number of lines } else { @@ -1587,7 +1581,7 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) * Scroll up if the cursor is off the bottom of the screen a bit. * Otherwise put it at 1/2 of the screen. */ - if (line_count >= curwin->w_grid.Rows && line_count > min_scroll) { + if (line_count >= curwin->w_height_inner && line_count > min_scroll) { scroll_cursor_halfway(false); } else { scrollup(line_count, true); @@ -1630,7 +1624,7 @@ void scroll_cursor_halfway(int atend) if (boff.lnum < curbuf->b_ml.ml_line_count) { botline_forw(&boff); used += boff.height; - if (used > curwin->w_grid.Rows) { + if (used > curwin->w_height_inner) { break; } below += boff.height; @@ -1647,7 +1641,7 @@ void scroll_cursor_halfway(int atend) used = MAXCOL; else used += loff.height; - if (used > curwin->w_grid.Rows) { + if (used > curwin->w_height_inner) { break; } above += loff.height; @@ -1658,7 +1652,7 @@ void scroll_cursor_halfway(int atend) if (!hasFolding(topline, &curwin->w_topline, NULL)) curwin->w_topline = topline; curwin->w_topfill = topfill; - if (old_topline > curwin->w_topline + curwin->w_grid.Rows) { + if (old_topline > curwin->w_topline + curwin->w_height_inner) { curwin->w_botfill = false; } check_topfill(curwin, false); @@ -1687,7 +1681,7 @@ void cursor_correct(void) } if (curwin->w_topline == 1) { above_wanted = 0; - int max_off = curwin->w_grid.Rows / 2; + int max_off = curwin->w_height_inner / 2; if (below_wanted > max_off) { below_wanted = max_off; } @@ -1697,7 +1691,7 @@ void cursor_correct(void) && mouse_dragging == 0 ) { below_wanted = 0; - int max_off = (curwin->w_grid.Rows - 1) / 2; + int max_off = (curwin->w_height_inner - 1) / 2; if (above_wanted > max_off) { above_wanted = max_off; } @@ -1871,7 +1865,7 @@ int onepage(Direction dir, long count) /* Find the line just above the new topline to get the right line * at the bottom of the window. */ n = 0; - while (n <= curwin->w_grid.Rows && loff.lnum >= 1) { + while (n <= curwin->w_height_inner && loff.lnum >= 1) { topline_back(&loff); if (loff.height == MAXCOL) n = MAXCOL; @@ -1962,7 +1956,7 @@ int onepage(Direction dir, long count) */ static void get_scroll_overlap(lineoff_T *lp, int dir) { - int min_height = curwin->w_grid.Rows - 2; + int min_height = curwin->w_height_inner - 2; if (lp->fill > 0) lp->height = 1; @@ -2017,12 +2011,12 @@ void halfpage(bool flag, linenr_T Prenum) int i; if (Prenum) { - curwin->w_p_scr = (Prenum > curwin->w_grid.Rows) ? curwin->w_grid.Rows + curwin->w_p_scr = (Prenum > curwin->w_height_inner) ? curwin->w_height_inner : Prenum; } assert(curwin->w_p_scr <= INT_MAX); - int n = curwin->w_p_scr <= curwin->w_grid.Rows ? (int)curwin->w_p_scr - : curwin->w_grid.Rows; + int n = curwin->w_p_scr <= curwin->w_height_inner ? (int)curwin->w_p_scr + : curwin->w_height_inner; update_topline(); validate_botline(); diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 0e3946740a..d361d81ac7 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -61,6 +61,7 @@ #include "nvim/event/loop.h" #include "nvim/os/time.h" #include "nvim/os/input.h" +#include "nvim/api/private/helpers.h" typedef struct normal_state { VimState state; @@ -1258,8 +1259,9 @@ static void normal_redraw(NormalState *s) maketitle(); } - // display message after redraw - if (keep_msg != NULL) { + // Display message after redraw. If an external message is still visible, + // it contains the kept message already. + if (keep_msg != NULL && !msg_ext_is_visible()) { // msg_attr_keep() will set keep_msg to NULL, must free the string here. // Don't reset keep_msg, msg_attr_keep() uses it to check for duplicates. char *p = (char *)keep_msg; @@ -3284,11 +3286,11 @@ void clear_showcmd(void) p_sbr = empty_option; getvcols(curwin, &curwin->w_cursor, &VIsual, &leftcol, &rightcol); p_sbr = saved_sbr; - sprintf((char *)showcmd_buf, "%" PRId64 "x%" PRId64, - (int64_t)lines, (int64_t)(rightcol - leftcol + 1)); - } else if (VIsual_mode == 'V' || VIsual.lnum != curwin->w_cursor.lnum) - sprintf((char *)showcmd_buf, "%" PRId64, (int64_t)lines); - else { + snprintf((char *)showcmd_buf, SHOWCMD_BUFLEN, "%" PRId64 "x%" PRId64, + (int64_t)lines, (int64_t)rightcol - leftcol + 1); + } else if (VIsual_mode == 'V' || VIsual.lnum != curwin->w_cursor.lnum) { + snprintf((char *)showcmd_buf, SHOWCMD_BUFLEN, "%" PRId64, (int64_t)lines); + } else { char_u *s, *e; int l; int bytes = 0; @@ -3317,7 +3319,8 @@ void clear_showcmd(void) else sprintf((char *)showcmd_buf, "%d-%d", chars, bytes); } - showcmd_buf[SHOWCMD_COLS] = NUL; /* truncate */ + int limit = ui_has(kUIMessages) ? SHOWCMD_BUFLEN-1 : SHOWCMD_COLS; + showcmd_buf[limit] = NUL; // truncate showcmd_visual = true; } else { showcmd_buf[0] = NUL; @@ -3370,8 +3373,9 @@ bool add_to_showcmd(int c) STRCPY(p, "<20>"); size_t old_len = STRLEN(showcmd_buf); size_t extra_len = STRLEN(p); - if (old_len + extra_len > SHOWCMD_COLS) { - size_t overflow = old_len + extra_len - SHOWCMD_COLS; + size_t limit = ui_has(kUIMessages) ? SHOWCMD_BUFLEN-1 : SHOWCMD_COLS; + if (old_len + extra_len > limit) { + size_t overflow = old_len + extra_len - limit; memmove(showcmd_buf, showcmd_buf + overflow, old_len - overflow + 1); } STRCAT(showcmd_buf, p); @@ -3432,13 +3436,24 @@ void pop_showcmd(void) static void display_showcmd(void) { int len; - len = (int)STRLEN(showcmd_buf); - if (len == 0) { - showcmd_is_clear = true; - } else { + showcmd_is_clear = (len == 0); + + if (ui_has(kUIMessages)) { + Array content = ARRAY_DICT_INIT; + if (len > 0) { + Array chunk = ARRAY_DICT_INIT; + // placeholder for future highlight support + ADD(chunk, INTEGER_OBJ(0)); + ADD(chunk, STRING_OBJ(cstr_to_string((char *)showcmd_buf))); + ADD(content, ARRAY_OBJ(chunk)); + } + ui_call_msg_showcmd(content); + return; + } + + if (!showcmd_is_clear) { grid_puts(&default_grid, showcmd_buf, (int)Rows - 1, sc_col, 0); - showcmd_is_clear = false; } /* @@ -3875,14 +3890,14 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) col_off1 = curwin_col_off(); col_off2 = col_off1 - curwin_col_off2(); - width1 = curwin->w_grid.Columns - col_off1; - width2 = curwin->w_grid.Columns - col_off2; + width1 = curwin->w_width_inner - col_off1; + width2 = curwin->w_width_inner - col_off2; if (width2 == 0) { width2 = 1; // Avoid divide by zero. } - if (curwin->w_grid.Columns != 0) { + if (curwin->w_width_inner != 0) { // Instead of sticking at the last character of the buffer line we // try to stick in the last column of the screen. if (curwin->w_curswant == MAXCOL) { @@ -3997,13 +4012,14 @@ static void nv_mousescroll(cmdarg_T *cap) win_T *old_curwin = curwin; if (mouse_row >= 0 && mouse_col >= 0) { - int row, col; + int grid, row, col; + grid = mouse_grid; row = mouse_row; col = mouse_col; // find the window at the pointer coordinates - win_T *const wp = mouse_find_win(&row, &col); + win_T *wp = mouse_find_win(&grid, &row, &col); if (wp == NULL) { return; } @@ -4225,7 +4241,7 @@ dozet: /* "zH" - scroll screen right half-page */ case 'H': - cap->count1 *= curwin->w_grid.Columns / 2; + cap->count1 *= curwin->w_width_inner / 2; FALLTHROUGH; /* "zh" - scroll screen to the right */ @@ -4241,7 +4257,7 @@ dozet: break; // "zL" - scroll screen left half-page - case 'L': cap->count1 *= curwin->w_grid.Columns / 2; + case 'L': cap->count1 *= curwin->w_width_inner / 2; FALLTHROUGH; /* "zl" - scroll screen to the left */ @@ -4277,7 +4293,7 @@ dozet: col = 0; /* like the cursor is in col 0 */ else getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col); - n = curwin->w_grid.Columns - curwin_col_off(); + n = curwin->w_width_inner - curwin_col_off(); if (col + l_p_siso < n) { col = 0; } else { @@ -4930,10 +4946,10 @@ get_visual_text ( } else { if (lt(curwin->w_cursor, VIsual)) { *pp = ml_get_pos(&curwin->w_cursor); - *lenp = (size_t)(VIsual.col - curwin->w_cursor.col + 1); + *lenp = (size_t)VIsual.col - (size_t)curwin->w_cursor.col + 1; } else { *pp = ml_get_pos(&VIsual); - *lenp = (size_t)(curwin->w_cursor.col - VIsual.col + 1); + *lenp = (size_t)curwin->w_cursor.col - (size_t)VIsual.col + 1; } if (has_mbyte) /* Correct the length to include the whole last character. */ @@ -4988,7 +5004,7 @@ static void nv_scroll(cmdarg_T *cap) used -= diff_check_fill(curwin, curwin->w_topline) - curwin->w_topfill; validate_botline(); // make sure w_empty_rows is valid - half = (curwin->w_grid.Rows - curwin->w_empty_rows + 1) / 2; + half = (curwin->w_height_inner - curwin->w_empty_rows + 1) / 2; for (n = 0; curwin->w_topline + n < curbuf->b_ml.ml_line_count; n++) { // Count half he number of filler lines to be "below this // line" and half to be "above the next line". @@ -5003,7 +5019,7 @@ static void nv_scroll(cmdarg_T *cap) if (hasFolding(curwin->w_topline + n, NULL, &lnum)) n = lnum - curwin->w_topline; } - if (n > 0 && used > curwin->w_grid.Rows) { + if (n > 0 && used > curwin->w_height_inner) { n--; } } else { // (cap->cmdchar == 'H') @@ -6715,9 +6731,9 @@ static void nv_g_cmd(cmdarg_T *cap) oap->motion_type = kMTCharWise; oap->inclusive = false; if (curwin->w_p_wrap - && curwin->w_grid.Columns != 0 + && curwin->w_width_inner != 0 ) { - int width1 = curwin->w_grid.Columns - curwin_col_off(); + int width1 = curwin->w_width_inner - curwin_col_off(); int width2 = width1 + curwin_col_off2(); validate_virtcol(); @@ -6730,7 +6746,7 @@ static void nv_g_cmd(cmdarg_T *cap) * 'relativenumber' is on and lines are wrapping the middle can be more * to the left. */ if (cap->nchar == 'm') { - i += (curwin->w_grid.Columns - curwin_col_off() + i += (curwin->w_width_inner - curwin_col_off() + ((curwin->w_p_wrap && i > 0) ? curwin_col_off2() : 0)) / 2; } @@ -6777,11 +6793,11 @@ static void nv_g_cmd(cmdarg_T *cap) oap->motion_type = kMTCharWise; oap->inclusive = true; if (curwin->w_p_wrap - && curwin->w_grid.Columns != 0 + && curwin->w_width_inner != 0 ) { curwin->w_curswant = MAXCOL; /* so we stay at the end */ if (cap->count1 == 1) { - int width1 = curwin->w_grid.Columns - col_off; + int width1 = curwin->w_width_inner - col_off; int width2 = width1 + curwin_col_off2(); validate_virtcol(); @@ -6807,7 +6823,7 @@ static void nv_g_cmd(cmdarg_T *cap) } else if (nv_screengo(oap, FORWARD, cap->count1 - 1) == false) clearopbeep(oap); } else { - i = curwin->w_leftcol + curwin->w_grid.Columns - col_off - 1; + i = curwin->w_leftcol + curwin->w_width_inner - col_off - 1; coladvance((colnr_T)i); /* Make sure we stick in this column. */ @@ -7917,7 +7933,7 @@ static void get_op_vcol( colnr_T end; if (VIsual_mode != Ctrl_V - || (!initial && oap->end.col < curwin->w_grid.Columns)) { + || (!initial && oap->end.col < curwin->w_width_inner)) { return; } diff --git a/src/nvim/ops.c b/src/nvim/ops.c index e9cb480647..674a9244f0 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -865,8 +865,12 @@ int do_record(int c) * needs to be removed again to put it in a register. exec_reg then * adds the escaping back later. */ - Recording = FALSE; - MSG(""); + Recording = false; + if (ui_has(kUIMessages)) { + showmode(); + } else { + MSG(""); + } p = get_recorded(); if (p == NULL) retval = FAIL; diff --git a/src/nvim/option.c b/src/nvim/option.c index b8f5957c09..b85ec6dbcc 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -50,6 +50,7 @@ #include "nvim/fold.h" #include "nvim/getchar.h" #include "nvim/hardcopy.h" +#include "nvim/highlight.h" #include "nvim/indent_c.h" #include "nvim/mbyte.h" #include "nvim/memfile.h" @@ -65,6 +66,7 @@ #include "nvim/normal.h" #include "nvim/os_unix.h" #include "nvim/path.h" +#include "nvim/popupmnu.h" #include "nvim/regexp.h" #include "nvim/screen.h" #include "nvim/spell.h" @@ -2121,10 +2123,10 @@ static void didset_options2(void) (void)opt_strings_flags(p_cb, p_cb_values, &cb_flags, true); // Parse default for 'fillchars'. - (void)set_chars_option(&p_fcs); + (void)set_chars_option(curwin, &curwin->w_p_fcs); // Parse default for 'listchars'. - (void)set_chars_option(&p_lcs); + (void)set_chars_option(curwin, &curwin->w_p_lcs); // Parse default for 'wildmode'. check_opt_wim(); @@ -2567,10 +2569,19 @@ did_set_string_option ( // 'ambiwidth' if (check_opt_strings(p_ambw, p_ambw_values, false) != OK) { errmsg = e_invarg; - } else if (set_chars_option(&p_lcs) != NULL) { - errmsg = (char_u *)_("E834: Conflicts with value of 'listchars'"); - } else if (set_chars_option(&p_fcs) != NULL) { - errmsg = (char_u *)_("E835: Conflicts with value of 'fillchars'"); + } else { + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (set_chars_option(wp, &wp->w_p_lcs) != NULL) { + errmsg = (char_u *)_("E834: Conflicts with value of 'listchars'"); + goto ambw_end; + } + if (set_chars_option(wp, &wp->w_p_fcs) != NULL) { + errmsg = (char_u *)_("E835: Conflicts with value of 'fillchars'"); + goto ambw_end; + } + } +ambw_end: + {} // clint prefers {} over ; as an empty statement } } /* 'background' */ @@ -2756,14 +2767,10 @@ did_set_string_option ( } s = skip_to_option_part(s); } - } - /* 'listchars' */ - else if (varp == &p_lcs) { - errmsg = set_chars_option(varp); - } - /* 'fillchars' */ - else if (varp == &p_fcs) { - errmsg = set_chars_option(varp); + } else if (varp == &curwin->w_p_lcs) { // 'listchars' + errmsg = set_chars_option(curwin, varp); + } else if (varp == &curwin->w_p_fcs) { // 'fillchars' + errmsg = set_chars_option(curwin, varp); } /* 'cedit' */ else if (varp == &p_cedit) { @@ -3394,53 +3401,55 @@ skip: /// Handle setting 'listchars' or 'fillchars'. /// Assume monocell characters /// -/// @param varp either &p_lcs ('listchars') or &p_fcs ('fillchar') +/// @param varp either &curwin->w_p_lcs or &curwin->w_p_fcs /// @return error message, NULL if it's OK. -static char_u *set_chars_option(char_u **varp) +static char_u *set_chars_option(win_T *wp, char_u **varp) { int round, i, len, entries; - char_u *p, *s; - int c1, c2 = 0; - struct charstab { + char_u *p, *s; + int c1 = 0, c2 = 0, c3 = 0; + + struct chars_tab { int *cp; ///< char value char *name; ///< char id int def; ///< default value }; - static struct charstab filltab[] = { - { &fill_stl, "stl" , ' ' }, - { &fill_stlnc, "stlnc", ' ' }, - { &fill_vert, "vert" , 9474 }, // │ - { &fill_fold, "fold" , 183 }, // · - { &fill_diff, "diff" , '-' }, - { &fill_msgsep, "msgsep", ' ' }, - { &fill_eob, "eob", '~' }, + struct chars_tab *tab; + + struct chars_tab fcs_tab[] = { + { &wp->w_p_fcs_chars.stl, "stl", ' ' }, + { &wp->w_p_fcs_chars.stlnc, "stlnc", ' ' }, + { &wp->w_p_fcs_chars.vert, "vert", 9474 }, // │ + { &wp->w_p_fcs_chars.fold, "fold", 183 }, // · + { &wp->w_p_fcs_chars.diff, "diff", '-' }, + { &wp->w_p_fcs_chars.msgsep, "msgsep", ' ' }, + { &wp->w_p_fcs_chars.eob, "eob", '~' }, }; - static struct charstab lcstab[] = { - { &lcs_eol, "eol", NUL }, - { &lcs_ext, "extends", NUL }, - { &lcs_nbsp, "nbsp", NUL }, - { &lcs_prec, "precedes", NUL }, - { &lcs_space, "space", NUL }, - { &lcs_tab2, "tab", NUL }, - { &lcs_trail, "trail", NUL }, - { &lcs_conceal, "conceal", NUL }, + struct chars_tab lcs_tab[] = { + { &wp->w_p_lcs_chars.eol, "eol", NUL }, + { &wp->w_p_lcs_chars.ext, "extends", NUL }, + { &wp->w_p_lcs_chars.nbsp, "nbsp", NUL }, + { &wp->w_p_lcs_chars.prec, "precedes", NUL }, + { &wp->w_p_lcs_chars.space, "space", NUL }, + { &wp->w_p_lcs_chars.tab2, "tab", NUL }, + { &wp->w_p_lcs_chars.trail, "trail", NUL }, + { &wp->w_p_lcs_chars.conceal, "conceal", NUL }, }; - struct charstab *tab; - if (varp == &p_lcs) { - tab = lcstab; - entries = ARRAY_SIZE(lcstab); + if (varp == &wp->w_p_lcs) { + tab = lcs_tab; + entries = ARRAY_SIZE(lcs_tab); } else { - tab = filltab; - entries = ARRAY_SIZE(filltab); + tab = fcs_tab; + entries = ARRAY_SIZE(fcs_tab); if (*p_ambw == 'd') { // XXX: If ambiwidth=double then "|" and "·" take 2 columns, which is // forbidden (TUI limitation?). Set old defaults. - filltab[2].def = '|'; - filltab[3].def = '-'; + fcs_tab[2].def = '|'; + fcs_tab[3].def = '-'; } else { - filltab[2].def = 9474; // │ - filltab[3].def = 183; // · + fcs_tab[2].def = 9474; // │ + fcs_tab[3].def = 183; // · } } @@ -3453,8 +3462,9 @@ static char_u *set_chars_option(char_u **varp) *(tab[i].cp) = tab[i].def; } } - if (varp == &p_lcs) { - lcs_tab1 = NUL; + if (varp == &wp->w_p_lcs) { + wp->w_p_lcs_chars.tab1 = NUL; + wp->w_p_lcs_chars.tab3 = NUL; } } p = *varp; @@ -3464,6 +3474,7 @@ static char_u *set_chars_option(char_u **varp) if (STRNCMP(p, tab[i].name, len) == 0 && p[len] == ':' && p[len + 1] != NUL) { + c1 = c2 = c3 = 0; s = p + len + 1; // TODO(bfredl): use schar_T representation and utfc_ptr2len @@ -3472,7 +3483,7 @@ static char_u *set_chars_option(char_u **varp) if (mb_char2cells(c1) > 1 || (c1len == 1 && c1 > 127)) { continue; } - if (tab[i].cp == &lcs_tab2) { + if (tab[i].cp == &wp->w_p_lcs_chars.tab2) { if (*s == NUL) { continue; } @@ -3481,15 +3492,23 @@ static char_u *set_chars_option(char_u **varp) if (mb_char2cells(c2) > 1 || (c2len == 1 && c2 > 127)) { continue; } + if (!(*s == ',' || *s == NUL)) { + int c3len = utf_ptr2len(s); + c3 = mb_cptr2char_adv((const char_u **)&s); + if (mb_char2cells(c3) > 1 || (c3len == 1 && c3 > 127)) { + continue; + } + } } if (*s == ',' || *s == NUL) { if (round) { - if (tab[i].cp == &lcs_tab2) { - lcs_tab1 = c1; - lcs_tab2 = c2; - } else if (tab[i].cp != NULL) + if (tab[i].cp == &wp->w_p_lcs_chars.tab2) { + wp->w_p_lcs_chars.tab1 = c1; + wp->w_p_lcs_chars.tab2 = c2; + wp->w_p_lcs_chars.tab3 = c3; + } else if (tab[i].cp != NULL) { *(tab[i].cp) = c1; - + } } p = s; break; @@ -4149,7 +4168,8 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_positive; } } else if (pp == &p_ch) { - if (value < 1) { + int minval = ui_has(kUIMessages) ? 0 : 1; + if (value < minval) { errmsg = e_positive; } } else if (pp == &p_tm) { @@ -4223,8 +4243,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } else if (pp == &curbuf->b_p_channel || pp == &p_channel) { errmsg = e_invarg; } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { - if (value < -1 || value > SB_MAX - || (value != -1 && opt_flags == OPT_LOCAL && !curbuf->terminal)) { + if (value < -1 || value > SB_MAX) { errmsg = e_invarg; } } else if (pp == &curbuf->b_p_sw || pp == &p_sw) { @@ -4258,6 +4277,9 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, p_window = Rows - 1; } } else if (pp == &p_ch) { + if (ui_has(kUIMessages)) { + p_ch = 0; + } if (p_ch > Rows - min_rows() + 1) { p_ch = Rows - min_rows() + 1; } @@ -4322,6 +4344,14 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (p_uc && !old_value) { ml_open_files(); } + } else if (pp == &p_pb) { + p_pb = MAX(MIN(p_pb, 100), 0); + if (old_value != 0) { + hl_invalidate_blends(); + } + if (pum_drawn()) { + pum_recompose(); + } } else if (pp == &p_pyx) { if (p_pyx != 0 && p_pyx != 2 && p_pyx != 3) { errmsg = e_invarg; @@ -4339,7 +4369,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { if (curbuf->terminal) { // Force the scrollback to take effect. - terminal_resize(curbuf->terminal, UINT16_MAX, UINT16_MAX); + terminal_check_size(curbuf->terminal); } } else if (pp == &curwin->w_p_nuw) { curwin->w_nrwidth_line_count = 0; @@ -4418,11 +4448,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = *pp; } - if (pp == &curbuf->b_p_scbk && !curbuf->terminal) { - // Normal buffer: reset local 'scrollback' after updating the global value. - curbuf->b_p_scbk = -1; - } - options[opt_idx].flags |= P_WAS_SET; // Don't do this while starting up, failure or recursively. @@ -5613,6 +5638,8 @@ static char_u *get_varp(vimoption_T *p) case PV_KMAP: return (char_u *)&(curbuf->b_p_keymap); case PV_SCL: return (char_u *)&(curwin->w_p_scl); case PV_WINHL: return (char_u *)&(curwin->w_p_winhl); + case PV_FCS: return (char_u *)&(curwin->w_p_fcs); + case PV_LCS: return (char_u *)&(curwin->w_p_lcs); default: IEMSG(_("E356: get_varp ERROR")); } /* always return a valid pointer to avoid a crash! */ @@ -5691,6 +5718,8 @@ void copy_winopt(winopt_T *from, winopt_T *to) to->wo_fmr = vim_strsave(from->wo_fmr); to->wo_scl = vim_strsave(from->wo_scl); to->wo_winhl = vim_strsave(from->wo_winhl); + to->wo_fcs = vim_strsave(from->wo_fcs); + to->wo_lcs = vim_strsave(from->wo_lcs); check_winopt(to); // don't want NULL pointers } @@ -5721,6 +5750,8 @@ static void check_winopt(winopt_T *wop) check_string_option(&wop->wo_cocu); check_string_option(&wop->wo_briopt); check_string_option(&wop->wo_winhl); + check_string_option(&wop->wo_fcs); + check_string_option(&wop->wo_lcs); } /* @@ -5741,12 +5772,16 @@ void clear_winopt(winopt_T *wop) clear_string_option(&wop->wo_cocu); clear_string_option(&wop->wo_briopt); clear_string_option(&wop->wo_winhl); + clear_string_option(&wop->wo_fcs); + clear_string_option(&wop->wo_lcs); } void didset_window_options(win_T *wp) { check_colorcolumn(wp); briopt_check(wp); + set_chars_option(wp, &wp->w_p_fcs); + set_chars_option(wp, &wp->w_p_lcs); parse_winhl_opt(wp); } @@ -5835,7 +5870,7 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_ai = p_ai; buf->b_p_ai_nopaste = p_ai_nopaste; buf->b_p_sw = p_sw; - buf->b_p_scbk = -1; + buf->b_p_scbk = p_scbk; buf->b_p_tw = p_tw; buf->b_p_tw_nopaste = p_tw_nopaste; buf->b_p_tw_nobin = p_tw_nobin; diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 7b99b6f266..ccb0496495 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -375,6 +375,7 @@ EXTERN int p_confirm; // 'confirm' EXTERN int p_cp; // 'compatible' EXTERN char_u *p_cot; // 'completeopt' EXTERN long p_ph; // 'pumheight' +EXTERN long p_pb; // 'pumblend' EXTERN char_u *p_cpo; // 'cpoptions' EXTERN char_u *p_csprg; // 'cscopeprg' EXTERN int p_csre; // 'cscoperelative' @@ -486,7 +487,6 @@ EXTERN long *p_linespace; // 'linespace' EXTERN char_u *p_lispwords; // 'lispwords' EXTERN long p_ls; // 'laststatus' EXTERN long p_stal; // 'showtabline' -EXTERN char_u *p_lcs; // 'listchars' EXTERN int p_lz; // 'lazyredraw' EXTERN int p_lpl; // 'loadplugins' @@ -638,7 +638,6 @@ EXTERN long p_ul; ///< 'undolevels' EXTERN long p_ur; ///< 'undoreload' EXTERN long p_uc; ///< 'updatecount' EXTERN long p_ut; ///< 'updatetime' -EXTERN char_u *p_fcs; ///< 'fillchar' EXTERN char_u *p_shada; ///< 'shada' EXTERN char_u *p_vdir; ///< 'viewdir' EXTERN char_u *p_vop; ///< 'viewoptions' @@ -814,6 +813,8 @@ enum { , WV_WRAP , WV_SCL , WV_WINHL + , WV_FCS + , WV_LCS , WV_COUNT // must be the last one }; diff --git a/src/nvim/options.lua b/src/nvim/options.lua index aba8f8b893..b8f128103c 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -806,11 +806,11 @@ return { }, { full_name='fillchars', abbreviation='fcs', - type='string', list='onecomma', scope={'global'}, + type='string', list='onecomma', scope={'window'}, deny_duplicates=true, vi_def=true, - redraw={'all_windows'}, - varname='p_fcs', + alloced=true, + redraw={'current_window'}, defaults={if_true={vi=''}} }, { @@ -1173,9 +1173,7 @@ return { vi_def=true, varname='p_iminsert', pv_name='p_imi', defaults={ - condition='B_IMODE_IM', - if_true={vi=macros('B_IMODE_IM')}, - if_false={vi=macros('B_IMODE_NONE')}, + if_true={vi=macros('B_IMODE_NONE')}, } }, { @@ -1184,9 +1182,7 @@ return { vi_def=true, varname='p_imsearch', pv_name='p_ims', defaults={ - condition='B_IMODE_IM', - if_true={vi=macros('B_IMODE_IM')}, - if_false={vi=macros('B_IMODE_NONE')}, + if_true={vi=macros('B_IMODE_USE_INSERT')}, } }, { @@ -1425,11 +1421,11 @@ return { }, { full_name='listchars', abbreviation='lcs', - type='string', list='onecomma', scope={'global'}, + type='string', list='onecomma', scope={'window'}, deny_duplicates=true, vim=true, - redraw={'all_windows'}, - varname='p_lcs', + alloced=true, + redraw={'current_window'}, defaults={if_true={vi="eol:$", vim="tab:> ,trail:-,nbsp:+"}} }, { @@ -1810,6 +1806,14 @@ return { defaults={if_true={vi=0}} }, { + full_name='pumblend', abbreviation='pb', + type='number', scope={'global'}, + vi_def=true, + redraw={'ui_option'}, + varname='p_pb', + defaults={if_true={vi=0}} + }, + { full_name='pyxversion', abbreviation='pyx', type='number', scope={'global'}, secure=true, @@ -1934,7 +1938,7 @@ return { vi_def=true, varname='p_scbk', redraw={'current_buffer'}, - defaults={if_true={vi=10000}} + defaults={if_true={vi=-1}} }, { full_name='scrollbind', abbreviation='scb', diff --git a/src/nvim/os/dl.c b/src/nvim/os/dl.c index 267cf5ae4b..bbd0424a82 100644 --- a/src/nvim/os/dl.c +++ b/src/nvim/os/dl.c @@ -7,6 +7,7 @@ #include <stdint.h> #include <uv.h> +#include "nvim/os/dl.h" #include "nvim/os/os.h" #include "nvim/memory.h" #include "nvim/message.h" diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index f62253cbce..5e2c9ecb36 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -236,6 +236,53 @@ size_t input_enqueue(String keys) return rv; } +static uint8_t check_multiclick(int code, int grid, int row, int col) +{ + static int orig_num_clicks = 0; + static int orig_mouse_code = 0; + static int orig_mouse_grid = 0; + static int orig_mouse_col = 0; + static int orig_mouse_row = 0; + static uint64_t orig_mouse_time = 0; // time of previous mouse click + + if (code == KE_LEFTRELEASE || code == KE_RIGHTRELEASE + || code == KE_MIDDLERELEASE) { + return 0; + } + uint64_t mouse_time = os_hrtime(); // time of current mouse click (ns) + + // compute the time elapsed since the previous mouse click and + // convert p_mouse from ms to ns + uint64_t timediff = mouse_time - orig_mouse_time; + uint64_t mouset = (uint64_t)p_mouset * 1000000; + if (code == orig_mouse_code + && timediff < mouset + && orig_num_clicks != 4 + && orig_mouse_grid == grid + && orig_mouse_col == col + && orig_mouse_row == row) { + orig_num_clicks++; + } else { + orig_num_clicks = 1; + } + orig_mouse_code = code; + orig_mouse_grid = grid; + orig_mouse_col = col; + orig_mouse_row = row; + orig_mouse_time = mouse_time; + + uint8_t modifiers = 0; + if (orig_num_clicks == 2) { + modifiers |= MOD_MASK_2CLICK; + } else if (orig_num_clicks == 3) { + modifiers |= MOD_MASK_3CLICK; + } else if (orig_num_clicks == 4) { + modifiers |= MOD_MASK_4CLICK; + } + return modifiers; +} + + // Mouse event handling code(Extract row/col if available and detect multiple // clicks) static unsigned int handle_mouse_event(char **ptr, uint8_t *buf, @@ -274,48 +321,16 @@ static unsigned int handle_mouse_event(char **ptr, uint8_t *buf, if (row >= Rows) { row = (int)Rows - 1; } + mouse_grid = 0; mouse_row = row; mouse_col = col; } *ptr += advance; } - static int orig_num_clicks = 0; - if (mouse_code != KE_LEFTRELEASE && mouse_code != KE_RIGHTRELEASE - && mouse_code != KE_MIDDLERELEASE) { - static int orig_mouse_code = 0; - static int orig_mouse_col = 0; - static int orig_mouse_row = 0; - static uint64_t orig_mouse_time = 0; // time of previous mouse click - uint64_t mouse_time = os_hrtime(); // time of current mouse click (ns) - - // compute the time elapsed since the previous mouse click and - // convert p_mouse from ms to ns - uint64_t timediff = mouse_time - orig_mouse_time; - uint64_t mouset = (uint64_t)p_mouset * 1000000; - if (mouse_code == orig_mouse_code - && timediff < mouset - && orig_num_clicks != 4 - && orig_mouse_col == mouse_col - && orig_mouse_row == mouse_row) { - orig_num_clicks++; - } else { - orig_num_clicks = 1; - } - orig_mouse_code = mouse_code; - orig_mouse_col = mouse_col; - orig_mouse_row = mouse_row; - orig_mouse_time = mouse_time; - } + uint8_t modifiers = check_multiclick(mouse_code, mouse_grid, + mouse_row, mouse_col); - uint8_t modifiers = 0; - if (orig_num_clicks == 2) { - modifiers |= MOD_MASK_2CLICK; - } else if (orig_num_clicks == 3) { - modifiers |= MOD_MASK_3CLICK; - } else if (orig_num_clicks == 4) { - modifiers |= MOD_MASK_4CLICK; - } if (modifiers) { if (buf[1] != KS_MODIFIER) { @@ -334,6 +349,30 @@ static unsigned int handle_mouse_event(char **ptr, uint8_t *buf, return bufsize; } +size_t input_enqueue_mouse(int code, uint8_t modifier, + int grid, int row, int col) +{ + modifier |= check_multiclick(code, grid, row, col); + uint8_t buf[7], *p = buf; + if (modifier) { + p[0] = K_SPECIAL; + p[1] = KS_MODIFIER; + p[2] = modifier; + p += 3; + } + p[0] = K_SPECIAL; + p[1] = KS_EXTRA; + p[2] = (uint8_t)code; + + mouse_grid = grid; + mouse_row = row; + mouse_col = col; + + size_t written = 3 + (size_t)(p-buf); + rbuffer_write(input_buffer, (char *)buf, written); + return written; +} + /// @return true if the main loop is blocked and waiting for input. bool input_blocking(void) { diff --git a/src/nvim/os/lang.c b/src/nvim/os/lang.c index 108a9c6c39..fe2d7986bf 100644 --- a/src/nvim/os/lang.c +++ b/src/nvim/os/lang.c @@ -11,6 +11,7 @@ #ifdef HAVE_LOCALE_H # include <locale.h> #endif +#include "nvim/os/lang.h" #include "nvim/os/os.h" void lang_init(void) diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index fc7f9cefd1..20f68233e7 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -15,6 +15,7 @@ #include "nvim/globals.h" #include "nvim/memline.h" #include "nvim/eval.h" +#include "nvim/fileio.h" #include "nvim/main.h" #include "nvim/memory.h" #include "nvim/misc1.h" @@ -22,7 +23,7 @@ #include "nvim/os/signal.h" #include "nvim/event/loop.h" -static SignalWatcher spipe, shup, squit, sterm; +static SignalWatcher spipe, shup, squit, sterm, susr1; #ifdef SIGPWR static SignalWatcher spwr; #endif @@ -61,6 +62,10 @@ void signal_init(void) signal_watcher_init(&main_loop, &spwr, NULL); signal_watcher_start(&spwr, on_signal, SIGPWR); #endif +#ifdef SIGUSR1 + signal_watcher_init(&main_loop, &susr1, NULL); + signal_watcher_start(&susr1, on_signal, SIGUSR1); +#endif } void signal_teardown(void) @@ -73,6 +78,9 @@ void signal_teardown(void) #ifdef SIGPWR signal_watcher_close(&spwr, NULL); #endif +#ifdef SIGUSR1 + signal_watcher_close(&susr1, NULL); +#endif } void signal_stop(void) @@ -84,6 +92,9 @@ void signal_stop(void) #ifdef SIGPWR signal_watcher_stop(&spwr); #endif +#ifdef SIGUSR1 + signal_watcher_stop(&susr1); +#endif } void signal_reject_deadly(void) @@ -115,6 +126,10 @@ static char * signal_name(int signum) #endif case SIGHUP: return "SIGHUP"; +#ifdef SIGUSR1 + case SIGUSR1: + return "SIGUSR1"; +#endif default: return "Unknown"; } @@ -162,6 +177,12 @@ static void on_signal(SignalWatcher *handle, int signum, void *data) deadly_signal(signum); } break; +#ifdef SIGUSR1 + case SIGUSR1: + apply_autocmds(EVENT_SIGNAL, (char_u *)"SIGUSR1", curbuf->b_fname, true, + curbuf); + break; +#endif default: ELOG("invalid signal: %d", signum); break; diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index 09ba718302..351350d939 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -139,7 +139,7 @@ void mch_exit(int r) exiting = true; ui_flush(); - ui_builtin_stop(); + ui_call_stop(); ml_close_all(true); // remove all memfiles if (!event_teardown() && r == 0) { diff --git a/src/nvim/path.c b/src/nvim/path.c index de697642c7..7903e3f4f4 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -1267,7 +1267,7 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file, } *num_file = ga.ga_len; - *file = (ga.ga_data != NULL) ? (char_u **)ga.ga_data : (char_u **)""; + *file = (ga.ga_data != NULL) ? (char_u **)ga.ga_data : NULL; recursive = false; @@ -2039,6 +2039,7 @@ int expand_wildcards(int num_pat, char_u **pat, int *num_files, char_u ***files, char_u *ffname; // check all files in (*files)[] + assert(*num_files == 0 || *files != NULL); for (i = 0; i < *num_files; i++) { ffname = (char_u *)FullName_save((char *)(*files)[i], false); assert((*files)[i] != NULL); @@ -2056,16 +2057,16 @@ int expand_wildcards(int num_pat, char_u **pat, int *num_files, char_u ***files, } } - /* - * Move the names where 'suffixes' match to the end. - */ + // + // Move the names where 'suffixes' match to the end. + // + assert(*num_files == 0 || *files != NULL); if (*num_files > 1) { non_suf_match = 0; for (i = 0; i < *num_files; i++) { if (!match_suffix((*files)[i])) { // - // Move the name without matching suffix to the front - // of the list. + // Move the name without matching suffix to the front of the list. // p = (*files)[i]; for (j = i; j > non_suf_match; j--) { diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 056770f2c0..3c10b7ae0f 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -19,6 +19,7 @@ #include "nvim/move.h" #include "nvim/option.h" #include "nvim/screen.h" +#include "nvim/ui_compositor.h" #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/memory.h" @@ -41,7 +42,11 @@ static int pum_row; // top row of pum static int pum_col; // left column of pum static bool pum_is_visible = false; +static bool pum_is_drawn = false; static bool pum_external = false; +static bool pum_invalid = false; // the screen was just cleared + +static ScreenGrid pum_grid = SCREEN_GRID_INIT; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "popupmnu.c.generated.h" @@ -78,13 +83,14 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed) if (!pum_is_visible) { // To keep the code simple, we only allow changing the // draw mode when the popup menu is not being displayed - pum_external = ui_is_external(kUIPopupmenu); + pum_external = ui_has(kUIPopupmenu); } do { // Mark the pum as visible already here, // to avoid that must_redraw is set when 'cursorcolumn' is on. pum_is_visible = true; + pum_is_drawn = true; validate_cursor_col(); above_row = 0; below_row = cmdline_row; @@ -98,7 +104,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed) } int grid = (int)curwin->w_grid.handle; - if (!ui_is_external(kUIMultigrid)) { + if (!ui_has(kUIMultigrid)) { grid = (int)default_grid.handle; row += curwin->w_winrow; col += curwin->w_wincol; @@ -317,7 +323,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed) /// Redraw the popup menu, using "pum_first" and "pum_selected". void pum_redraw(void) { - int row = pum_row; + int row = 0; int col; int attr_norm = win_hl_attr(curwin, HLF_PNI); int attr_select = win_hl_attr(curwin, HLF_PSI); @@ -334,6 +340,39 @@ void pum_redraw(void) int round; int n; + int grid_width = pum_width; + int col_off = 0; + bool extra_space = false; + if (curwin->w_p_rl) { + col_off = pum_width; + if (pum_col < curwin->w_wincol + curwin->w_width - 1) { + grid_width += 1; + extra_space = true; + } + } else if (pum_col > 0) { + grid_width += 1; + col_off = 1; + extra_space = true; + } + if (pum_scrollbar > 0) { + grid_width++; + } + + grid_assign_handle(&pum_grid); + bool moved = ui_comp_put_grid(&pum_grid, pum_row, pum_col-col_off, + pum_height, grid_width); + bool invalid_grid = moved || pum_invalid; + pum_invalid = false; + + if (!pum_grid.chars + || pum_grid.Rows != pum_height || pum_grid.Columns != grid_width) { + grid_alloc(&pum_grid, pum_height, grid_width, !invalid_grid, false); + ui_call_grid_resize(pum_grid.handle, pum_grid.Columns, pum_grid.Rows); + } else if (invalid_grid) { + grid_invalidate(&pum_grid); + } + + // Never display more than we have if (pum_first > pum_size - pum_height) { pum_first = pum_size - pum_height; @@ -356,17 +395,17 @@ void pum_redraw(void) 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) { - grid_putchar(&default_grid, ' ', row, pum_col + 1, attr); + if (extra_space) { + if (curwin->w_p_rl) { + grid_putchar(&pum_grid, ' ', row, col_off + 1, attr); + } else { + grid_putchar(&pum_grid, ' ', row, col_off - 1, attr); } - } else if (pum_col > 0) { - grid_putchar(&default_grid, ' ', row, pum_col - 1, attr); } // Display each entry, use two spaces for a Tab. // Do this 3 times: For the main text, kind and extra info - col = pum_col; + col = col_off; totwidth = 0; for (round = 1; round <= 3; ++round) { @@ -423,13 +462,13 @@ void pum_redraw(void) size++; } } - grid_puts_len(&default_grid, rt, (int)STRLEN(rt), row, + grid_puts_len(&pum_grid, rt, (int)STRLEN(rt), row, col - size + 1, attr); xfree(rt_start); xfree(st); col -= width; } else { - grid_puts_len(&default_grid, st, (int)STRLEN(st), row, col, attr); + grid_puts_len(&pum_grid, st, (int)STRLEN(st), row, col, attr); xfree(st); col += width; } @@ -440,11 +479,11 @@ void pum_redraw(void) // Display two spaces for a Tab. if (curwin->w_p_rl) { - grid_puts_len(&default_grid, (char_u *)" ", 2, row, col - 1, + grid_puts_len(&pum_grid, (char_u *)" ", 2, row, col - 1, attr); col -= 2; } else { - grid_puts_len(&default_grid, (char_u *)" ", 2, row, col, attr); + grid_puts_len(&pum_grid, (char_u *)" ", 2, row, col, attr); col += 2; } totwidth += 2; @@ -475,37 +514,37 @@ void pum_redraw(void) } if (curwin->w_p_rl) { - grid_fill(&default_grid, row, row + 1, pum_col - pum_base_width - n + 1, + grid_fill(&pum_grid, row, row + 1, col_off - pum_base_width - n + 1, col + 1, ' ', ' ', attr); - col = pum_col - pum_base_width - n + 1; + col = col_off - pum_base_width - n + 1; } else { - grid_fill(&default_grid, row, row + 1, col, - pum_col + pum_base_width + n, ' ', ' ', attr); - col = pum_col + pum_base_width + n; + grid_fill(&pum_grid, row, row + 1, col, + col_off + pum_base_width + n, ' ', ' ', attr); + col = col_off + pum_base_width + n; } totwidth = pum_base_width + n; } if (curwin->w_p_rl) { - grid_fill(&default_grid, row, row + 1, pum_col - pum_width + 1, col + 1, + grid_fill(&pum_grid, row, row + 1, col_off - pum_width + 1, col + 1, ' ', ' ', attr); } else { - grid_fill(&default_grid, row, row + 1, col, pum_col + pum_width, ' ', ' ', + grid_fill(&pum_grid, row, row + 1, col, col_off + pum_width, ' ', ' ', attr); } if (pum_scrollbar > 0) { if (curwin->w_p_rl) { - grid_putchar(&default_grid, ' ', row, pum_col - pum_width, + grid_putchar(&pum_grid, ' ', row, col_off - pum_width, i >= thumb_pos && i < thumb_pos + thumb_heigth ? attr_thumb : attr_scroll); } else { - grid_putchar(&default_grid, ' ', row, pum_col + pum_width, + grid_putchar(&pum_grid, ' ', row, col_off + pum_width, i >= thumb_pos && i < thumb_pos + thumb_heigth ? attr_thumb : attr_scroll); } } - grid_puts_line_flush(&default_grid, false); + grid_puts_line_flush(&pum_grid, false); row++; } } @@ -696,6 +735,8 @@ static int pum_set_selected(int n, int repeat) // Update the screen before drawing the popup menu. // Enable updating the status lines. + // TODO(bfredl): can simplify, get rid of the flag munging? + // or at least eliminate extra redraw before win_enter()? pum_is_visible = false; update_screen(0); pum_is_visible = true; @@ -725,17 +766,27 @@ static int pum_set_selected(int n, int repeat) } /// Undisplay the popup menu (later). -void pum_undisplay(void) +void pum_undisplay(bool immediate) { pum_is_visible = false; pum_array = NULL; - if (pum_external) { - ui_call_popupmenu_hide(); - } else { - redraw_all_later(SOME_VALID); - redraw_tabline = true; - status_redraw_all(); + if (immediate) { + pum_check_clear(); + } +} + +void pum_check_clear(void) +{ + if (!pum_is_visible && pum_is_drawn) { + if (pum_external) { + ui_call_popupmenu_hide(); + } else { + ui_comp_remove_grid(&pum_grid); + // TODO(bfredl): consider keeping float grids allocated. + grid_free(&pum_grid); + } + pum_is_drawn = false; } } @@ -758,6 +809,17 @@ bool pum_drawn(void) return pum_visible() && !pum_external; } +/// Screen was cleared, need to redraw next time +void pum_invalidate(void) +{ + pum_invalid = true; +} + +void pum_recompose(void) +{ + ui_comp_compose_grid(&pum_grid); +} + /// Gets the height of the menu. /// /// @return the height of the popup menu, the number of entries visible. diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 4eeddf1d5a..f0c37c0e38 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -44,6 +44,7 @@ #include "nvim/window.h" #include "nvim/os/os.h" #include "nvim/os/input.h" +#include "nvim/api/private/helpers.h" struct dir_stack_T { @@ -2155,7 +2156,8 @@ win_found: } else if (!msg_scrolled && shortmess(SHM_OVERALL)) { msg_scroll = false; } - msg_attr_keep(IObuff, 0, true); + msg_ext_set_kind("quickfix"); + msg_attr_keep(IObuff, 0, true, false); msg_scroll = (int)i; } } else { diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 0b9e1cfdec..a70b150e9b 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -230,6 +230,7 @@ #define UPPER 47 /* Match uppercase char */ #define NUPPER 48 /* Match non-uppercase char */ #define LAST_NL NUPPER + ADD_NL +// -V:WITH_NL:560 #define WITH_NL(op) ((op) >= FIRST_NL && (op) <= LAST_NL) #define MOPEN 80 // -89 Mark this point in input as start of diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 2467cf192f..5ac90ab601 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -110,6 +110,7 @@ #include "nvim/syntax.h" #include "nvim/terminal.h" #include "nvim/ui.h" +#include "nvim/ui_compositor.h" #include "nvim/undo.h" #include "nvim/version.h" #include "nvim/window.h" @@ -153,6 +154,8 @@ static bool highlights_invalid = false; static bool conceal_cursor_used = false; +static bool redraw_popupmenu = false; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "screen.c.generated.h" #endif @@ -271,9 +274,10 @@ void update_screen(int type) static int did_intro = FALSE; int did_one; - /* Don't do anything if the screen structures are (not yet) valid. */ - if (!screen_valid(TRUE)) + // Don't do anything if the screen structures are (not yet) valid. + if (!default_grid.chars) { return; + } if (must_redraw) { if (type < must_redraw) /* use maximal type */ @@ -304,11 +308,15 @@ void update_screen(int type) ++display_tick; /* let syntax code know we're in a next round of * display updating */ - /* - * if the screen was scrolled up when displaying a message, scroll it down - */ - if (msg_scrolled) { + // Tricky: vim code can reset msg_scrolled behind our back, so need + // separate bookkeeping for now. + if (msg_did_scroll) { ui_call_win_scroll_over_reset(); + msg_did_scroll = false; + } + + // if the screen was scrolled up when displaying a message, scroll it down + if (msg_scrolled) { clear_cmdline = true; if (dy_flags & DY_MSGSEP) { int valid = MAX(Rows - msg_scrollsize(), 0); @@ -324,14 +332,12 @@ void update_screen(int type) wp->w_redr_status = true; } } - } else if (msg_scrolled > default_grid.Rows - 5) { // clearing is faster + } else if (msg_scrolled > Rows - 5) { // clearing is faster type = CLEAR; } else if (type != CLEAR) { check_for_delay(false); - if (grid_ins_lines(&default_grid, 0, msg_scrolled, (int)Rows, - 0, (int)Columns) == FAIL) { - type = CLEAR; - } + grid_ins_lines(&default_grid, 0, msg_scrolled, (int)Rows, + 0, (int)Columns); FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_winrow < msg_scrolled) { if (W_ENDROW(wp) > msg_scrolled @@ -356,6 +362,8 @@ void update_screen(int type) need_wait_return = FALSE; } + msg_ext_check_prompt(); + /* reset cmdline_row now (may have been changed temporarily) */ compute_cmdrow(); @@ -453,17 +461,20 @@ void update_screen(int type) /* redraw status line after the window to minimize cursor movement */ if (wp->w_redr_status) { - win_redr_status(wp, true); // any popup menu will be redrawn below + win_redr_status(wp); } } - send_grid_resize = false; - highlights_invalid = false; + end_search_hl(); // May need to redraw the popup menu. - if (pum_drawn()) { + if (pum_drawn() && redraw_popupmenu) { pum_redraw(); } + send_grid_resize = false; + highlights_invalid = false; + redraw_popupmenu = false; + /* Reset b_mod_set flags. Going through all windows is probably faster * than going through all buffers (there could be many buffers). */ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { @@ -474,8 +485,9 @@ void update_screen(int type) /* Clear or redraw the command line. Done last, because scrolling may * mess up the command line. */ - if (clear_cmdline || redraw_cmdline) + if (clear_cmdline || redraw_cmdline) { showmode(); + } /* May put up an introductory message when not editing a file */ if (!did_intro) @@ -629,9 +641,7 @@ static void win_update(win_T *wp) wp->w_nrwidth = i; if (buf->terminal) { - terminal_resize(buf->terminal, - (uint16_t)(MAX(0, wp->w_grid.Columns - win_col_off(wp))), - (uint16_t)wp->w_grid.Rows); + terminal_check_size(buf->terminal); } } else if (buf->b_mod_set && buf->b_mod_xlines != 0 @@ -820,31 +830,31 @@ static void win_update(win_T *wp) // Try to insert the correct number of lines. // If not the last window, delete the lines at the bottom. // win_ins_lines may fail when the terminal can't do it. - if (win_ins_lines(wp, 0, i) == OK) { - if (wp->w_lines_valid != 0) { - /* Need to update rows that are new, stop at the - * first one that scrolled down. */ - top_end = i; - scrolled_down = TRUE; - - /* Move the entries that were scrolled, disable - * the entries for the lines to be redrawn. */ - if ((wp->w_lines_valid += j) > wp->w_grid.Rows) { - wp->w_lines_valid = wp->w_grid.Rows; - } - for (idx = wp->w_lines_valid; idx - j >= 0; idx--) { - wp->w_lines[idx] = wp->w_lines[idx - j]; - } - while (idx >= 0) { - wp->w_lines[idx--].wl_valid = false; - } + win_scroll_lines(wp, 0, i); + if (wp->w_lines_valid != 0) { + // Need to update rows that are new, stop at the + // first one that scrolled down. + top_end = i; + scrolled_down = true; + + // Move the entries that were scrolled, disable + // the entries for the lines to be redrawn. + if ((wp->w_lines_valid += j) > wp->w_grid.Rows) { + wp->w_lines_valid = wp->w_grid.Rows; } - } else - mid_start = 0; /* redraw all lines */ - } else - mid_start = 0; /* redraw all lines */ - } else - mid_start = 0; /* redraw all lines */ + for (idx = wp->w_lines_valid; idx - j >= 0; idx--) { + wp->w_lines[idx] = wp->w_lines[idx - j]; + } + while (idx >= 0) { + wp->w_lines[idx--].wl_valid = false; + } + } + } else { + mid_start = 0; // redraw all lines + } + } else { + mid_start = 0; // redraw all lines + } } else { /* * New topline is at or below old topline: May scroll up. @@ -881,11 +891,8 @@ static void win_update(win_T *wp) /* ... but don't delete new filler lines. */ row -= wp->w_topfill; if (row > 0) { - if (win_del_lines(wp, 0, row) == OK) { - bot_start = wp->w_grid.Rows - row; - } else { - mid_start = 0; // redraw all lines - } + win_scroll_lines(wp, 0, -row); + bot_start = wp->w_grid.Rows - row; } if ((row == 0 || bot_start < 999) && wp->w_lines_valid != 0) { /* @@ -1140,7 +1147,7 @@ static void win_update(win_T *wp) // Update a line when it is in an area that needs updating, when it // has changes or w_lines[idx] is invalid. // "bot_start" may be halfway a wrapped line after using - // win_del_lines(), check if the current line includes it. + // win_scroll_lines(), check if the current line includes it. // When syntax folding is being used, the saved syntax states will // already have been updated, we can't see where the syntax state is // the same again, just update until the end of the window. @@ -1236,11 +1243,8 @@ static void win_update(win_T *wp) if (row - xtra_rows >= wp->w_grid.Rows - 2) { mod_bot = MAXLNUM; } else { - if (win_del_lines(wp, row, -xtra_rows) == FAIL) { - mod_bot = MAXLNUM; - } else { - bot_start = wp->w_grid.Rows + xtra_rows; - } + win_scroll_lines(wp, row, xtra_rows); + bot_start = wp->w_grid.Rows + xtra_rows; } } else if (xtra_rows > 0) { /* May scroll text down. If there is not enough @@ -1249,9 +1253,8 @@ static void win_update(win_T *wp) if (row + xtra_rows >= wp->w_grid.Rows - 2) { mod_bot = MAXLNUM; } else { - if (win_ins_lines(wp, row + old_rows, xtra_rows) == FAIL) { - mod_bot = MAXLNUM; - } else if (top_end > row + old_rows) { + win_scroll_lines(wp, row + old_rows, xtra_rows); + if (top_end > row + old_rows) { // Scrolled the part at the top that requires // updating down. top_end += xtra_rows; @@ -1448,13 +1451,12 @@ static void win_update(win_T *wp) wp->w_botline = buf->b_ml.ml_line_count + 1; j = diff_check_fill(wp, wp->w_botline); if (j > 0 && !wp->w_botfill) { - /* - * Display filler lines at the end of the file - */ - if (char2cells(fill_diff) > 1) + // display filler lines at the end of the file + if (char2cells(wp->w_p_fcs_chars.diff) > 1) { i = '-'; - else - i = fill_diff; + } else { + i = wp->w_p_fcs_chars.diff; + } if (row + j > wp->w_grid.Rows) { j = wp->w_grid.Rows - row; } @@ -1465,8 +1467,8 @@ static void win_update(win_T *wp) wp->w_botline = lnum; // make sure the rest of the screen is blank - // write the 'fill_eob' character to rows that aren't part of the file. - win_draw_end(wp, fill_eob, ' ', row, wp->w_grid.Rows, HLF_EOB); + // write the 'eob' character to rows that aren't part of the file. + win_draw_end(wp, wp->w_p_fcs_chars.eob, ' ', row, wp->w_grid.Rows, HLF_EOB); } if (wp->w_redr_type >= REDRAW_TOP) { @@ -1817,7 +1819,7 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T txtcol = col; /* remember where text starts */ - // 5. move the text to linebuf_char[off]. Fill up with "fill_fold". + // 5. move the text to linebuf_char[off]. Fill up with "fold". // Right-left text is put in columns 0 - number-col, normal text is put // in columns number-col - window-width. int idx; @@ -1849,7 +1851,7 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T col -= txtcol; schar_T sc; - schar_from_char(sc, fill_fold); + schar_from_char(sc, wp->w_p_fcs_chars.fold); while (col < wp->w_grid.Columns - (wp->w_p_rl ? txtcol : 0) ) { @@ -2042,30 +2044,33 @@ win_line ( bool number_only // only update the number column ) { - int c = 0; // init for GCC - long vcol = 0; // virtual column (for tabs) - long vcol_sbr = -1; // virtual column after showbreak - long vcol_prev = -1; // "vcol" of previous character - char_u *line; // current line - char_u *ptr; // current position in "line" - int row; // row in the window, excl w_winrow - ScreenGrid *grid = &wp->w_grid; // grid specfic to the window - - char_u extra[18]; /* line number and 'fdc' must fit in here */ - int n_extra = 0; /* number of extra chars */ - char_u *p_extra = NULL; /* string of extra chars, plus NUL */ - char_u *p_extra_free = NULL; /* p_extra needs to be freed */ - int c_extra = NUL; /* extra chars, all the same */ - int extra_attr = 0; /* attributes when n_extra != 0 */ - static char_u *at_end_str = (char_u *)""; /* used for p_extra when - displaying lcs_eol at end-of-line */ - int lcs_eol_one = lcs_eol; /* lcs_eol until it's been used */ - int lcs_prec_todo = lcs_prec; /* lcs_prec until it's been used */ + int c = 0; // init for GCC + long vcol = 0; // virtual column (for tabs) + long vcol_sbr = -1; // virtual column after showbreak + long vcol_prev = -1; // "vcol" of previous character + char_u *line; // current line + char_u *ptr; // current position in "line" + int row; // row in the window, excl w_winrow + ScreenGrid *grid = &wp->w_grid; // grid specfic to the window + + char_u extra[18]; // line number and 'fdc' must fit in here + int n_extra = 0; // number of extra chars + char_u *p_extra = NULL; // string of extra chars, plus NUL + char_u *p_extra_free = NULL; // p_extra needs to be freed + int c_extra = NUL; // extra chars, all the same + int c_final = NUL; // final char, mandatory if set + int extra_attr = 0; // attributes when n_extra != 0 + static char_u *at_end_str = (char_u *)""; // used for p_extra when displaying + // curwin->w_p_lcs_chars.eol at + // end-of-line + int lcs_eol_one = wp->w_p_lcs_chars.eol; // 'eol' until it's been used + int lcs_prec_todo = wp->w_p_lcs_chars.prec; // 'prec' until it's been used /* saved "extra" items for when draw_state becomes WL_LINE (again) */ int saved_n_extra = 0; char_u *saved_p_extra = NULL; int saved_c_extra = 0; + int saved_c_final = 0; int saved_char_attr = 0; int n_attr = 0; /* chars with special attr */ @@ -2432,11 +2437,11 @@ win_line ( } if (wp->w_p_list) { - if (lcs_space || lcs_trail) { + if (curwin->w_p_lcs_chars.space || wp->w_p_lcs_chars.trail) { extra_check = true; } // find start of trailing whitespace - if (lcs_trail) { + if (wp->w_p_lcs_chars.trail) { trailcol = (colnr_T)STRLEN(ptr); while (trailcol > (colnr_T)0 && ascii_iswhite(ptr[trailcol - 1])) { trailcol--; @@ -2644,6 +2649,7 @@ win_line ( /* Draw the cmdline character. */ n_extra = 1; c_extra = cmdwin_type; + c_final = NUL; char_attr = win_hl_attr(wp, HLF_AT); } } @@ -2662,6 +2668,7 @@ win_line ( p_extra_free[n_extra] = NUL; p_extra = p_extra_free; c_extra = NUL; + c_final = NUL; char_attr = win_hl_attr(wp, HLF_FC); } } @@ -2675,6 +2682,7 @@ win_line ( int text_sign; // Draw cells with the sign value or blank. c_extra = ' '; + c_final = NUL; char_attr = win_hl_attr(wp, HLF_SC); n_extra = win_signcol_width(wp); @@ -2685,6 +2693,7 @@ win_line ( int symbol_blen = (int)STRLEN(p_extra); if (p_extra != NULL) { c_extra = NUL; + c_final = NUL; // symbol(s) bytes + (filling spaces) (one byte each) n_extra = symbol_blen + (win_signcol_width(wp) - mb_string2cells(p_extra)); @@ -2736,8 +2745,11 @@ win_line ( rl_mirror(extra); p_extra = extra; c_extra = NUL; - } else + c_final = NUL; + } else { c_extra = ' '; + c_final = NUL; + } n_extra = number_width(wp) + 1; char_attr = win_hl_attr(wp, HLF_N); @@ -2792,11 +2804,14 @@ win_line ( if (draw_state == WL_SBR - 1 && n_extra == 0) { draw_state = WL_SBR; if (filler_todo > 0) { - /* Draw "deleted" diff line(s). */ - if (char2cells(fill_diff) > 1) + // draw "deleted" diff line(s) + if (char2cells(wp->w_p_fcs_chars.diff) > 1) { c_extra = '-'; - else - c_extra = fill_diff; + c_final = NUL; + } else { + c_extra = wp->w_p_fcs_chars.diff; + c_final = NUL; + } if (wp->w_p_rl) { n_extra = col + 1; } else { @@ -2808,6 +2823,7 @@ win_line ( /* Draw 'showbreak' at the start of each broken line. */ p_extra = p_sbr; c_extra = NUL; + c_final = NUL; n_extra = (int)STRLEN(p_sbr); char_attr = win_hl_attr(wp, HLF_AT); need_showbreak = false; @@ -2829,6 +2845,7 @@ win_line ( /* Continue item from end of wrapped line. */ n_extra = saved_n_extra; c_extra = saved_c_extra; + c_final = saved_c_final; p_extra = saved_p_extra; char_attr = saved_char_attr; } else { @@ -3026,20 +3043,18 @@ win_line ( } } - /* - * Get the next character to put on the screen. - */ - /* - * The "p_extra" points to the extra stuff that is inserted to - * represent special characters (non-printable stuff) and other - * things. When all characters are the same, c_extra is used. - * "p_extra" must end in a NUL to avoid mb_ptr2len() reads past - * "p_extra[n_extra]". - * For the '$' of the 'list' option, n_extra == 1, p_extra == "". - */ + // Get the next character to put on the screen. + // + // The "p_extra" points to the extra stuff that is inserted to + // represent special characters (non-printable stuff) and other + // things. When all characters are the same, c_extra is used. + // If c_final is set, it will compulsorily be used at the end. + // "p_extra" must end in a NUL to avoid mb_ptr2len() reads past + // "p_extra[n_extra]". + // For the '$' of the 'list' option, n_extra == 1, p_extra == "". if (n_extra > 0) { - if (c_extra != NUL) { - c = c_extra; + if (c_extra != NUL || (n_extra == 1 && c_final != NUL)) { + c = (n_extra == 1 && c_final != NUL) ? c_final : c_extra; mb_c = c; // doesn't handle non-utf-8 multi-byte! if (enc_utf8 && utf_char2len(c) > 1) { mb_utf8 = true; @@ -3146,6 +3161,7 @@ win_line ( mb_utf8 = (c >= 0x80); n_extra = (int)STRLEN(p_extra); c_extra = NUL; + c_final = NUL; if (area_attr == 0 && search_attr == 0) { n_attr = n_extra + 1; extra_attr = win_hl_attr(wp, HLF_8); @@ -3198,6 +3214,7 @@ win_line ( p_extra = extra; n_extra = (int)STRLEN(extra) - 1; c_extra = NUL; + c_final = NUL; c = *p_extra++; if (area_attr == 0 && search_attr == 0) { n_attr = n_extra + 1; @@ -3232,6 +3249,7 @@ win_line ( if (n_skip > 0 && mb_l > 1 && n_extra == 0) { n_extra = 1; c_extra = MB_FILLER_CHAR; + c_final = NUL; c = ' '; if (area_attr == 0 && search_attr == 0) { n_attr = n_extra + 1; @@ -3391,6 +3409,7 @@ win_line ( - vcol % (int)wp->w_buffer->b_p_ts - 1; } c_extra = mb_off > 0 ? MB_FILLER_CHAR : ' '; + c_final = NUL; if (ascii_iswhite(c)) { if (c == TAB) /* See "Tab alignment" below. */ @@ -3401,13 +3420,14 @@ win_line ( } } - // 'list': change char 160 to lcs_nbsp and space to lcs_space. + // 'list': change char 160 to 'nbsp' and space to 'space'. if (wp->w_p_list && (((c == 160 || (mb_utf8 && (mb_c == 160 || mb_c == 0x202f))) - && lcs_nbsp) - || (c == ' ' && lcs_space && ptr - line <= trailcol))) { - c = (c == ' ') ? lcs_space : lcs_nbsp; + && curwin->w_p_lcs_chars.nbsp) + || (c == ' ' && curwin->w_p_lcs_chars.space + && ptr - line <= trailcol))) { + c = (c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp; n_attr = 1; extra_attr = win_hl_attr(wp, HLF_0); saved_attr2 = char_attr; // save current attr @@ -3422,7 +3442,7 @@ win_line ( } if (trailcol != MAXCOL && ptr > line + trailcol && c == ' ') { - c = lcs_trail; + c = wp->w_p_lcs_chars.trail; n_attr = 1; extra_attr = win_hl_attr(wp, HLF_0); saved_attr2 = char_attr; // save current attr @@ -3443,7 +3463,7 @@ win_line ( if (!vim_isprintc(c)) { // when getting a character from the file, we may have to // turn it into something else on the way to putting it on the screen. - if (c == TAB && (!wp->w_p_list || lcs_tab1)) { + if (c == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { int tab_len = 0; long vcol_adjusted = vcol; // removed showbreak length // Only adjust the tab_len, when at the first column after the @@ -3467,26 +3487,28 @@ win_line ( tab_len += vcol_off; } // boguscols before FIX_FOR_BOGUSCOLS macro from above. - if (lcs_tab1 && old_boguscols > 0 && n_extra > tab_len) { + if (wp->w_p_lcs_chars.tab1 && old_boguscols > 0 + && n_extra > tab_len) { tab_len += n_extra - tab_len; } /* if n_extra > 0, it gives the number of chars to use for * a tab, else we need to calculate the width for a tab */ - int len = (tab_len * mb_char2len(lcs_tab2)); + int len = (tab_len * mb_char2len(wp->w_p_lcs_chars.tab2)); if (n_extra > 0) { len += n_extra - tab_len; } - c = lcs_tab1; + c = wp->w_p_lcs_chars.tab1; p = xmalloc(len + 1); memset(p, ' ', len); p[len] = NUL; xfree(p_extra_free); p_extra_free = p; for (i = 0; i < tab_len; i++) { - utf_char2bytes(lcs_tab2, p); - p += mb_char2len(lcs_tab2); - n_extra += mb_char2len(lcs_tab2) - (saved_nextra > 0 ? 1: 0); + utf_char2bytes(wp->w_p_lcs_chars.tab2, p); + p += mb_char2len(wp->w_p_lcs_chars.tab2); + n_extra += mb_char2len(wp->w_p_lcs_chars.tab2) + - (saved_nextra > 0 ? 1: 0); } p_extra = p_extra_free; @@ -3511,19 +3533,23 @@ win_line ( // Make sure, the highlighting for the tab char will be // correctly set further below (effectively reverts the // FIX_FOR_BOGSUCOLS macro. - if (n_extra == tab_len + vc_saved && wp->w_p_list && lcs_tab1) { + if (n_extra == tab_len + vc_saved && wp->w_p_list + && wp->w_p_lcs_chars.tab1) { tab_len += vc_saved; } } mb_utf8 = false; // don't draw as UTF-8 if (wp->w_p_list) { - c = lcs_tab1; + c = (n_extra == 0 && wp->w_p_lcs_chars.tab3) + ? wp->w_p_lcs_chars.tab3 + : wp->w_p_lcs_chars.tab1; if (wp->w_p_lbr) { c_extra = NUL; /* using p_extra from above */ } else { - c_extra = lcs_tab2; + c_extra = wp->w_p_lcs_chars.tab2; } + c_final = wp->w_p_lcs_chars.tab3; n_attr = tab_len + 1; extra_attr = win_hl_attr(wp, HLF_0); saved_attr2 = char_attr; // save current attr @@ -3534,6 +3560,7 @@ win_line ( c = 0xc0; } } else { + c_final = NUL; c_extra = ' '; c = ' '; } @@ -3561,10 +3588,11 @@ win_line ( p_extra = at_end_str; n_extra = 1; c_extra = NUL; + c_final = NUL; } } - if (wp->w_p_list && lcs_eol > 0) { - c = lcs_eol; + if (wp->w_p_list && wp->w_p_lcs_chars.eol > 0) { + c = wp->w_p_lcs_chars.eol; } else { c = ' '; } @@ -3588,6 +3616,7 @@ win_line ( if ((dy_flags & DY_UHEX) && wp->w_p_rl) rl_mirror(p_extra); /* reverse "<12>" */ c_extra = NUL; + c_final = NUL; if (wp->w_p_lbr) { char_u *p; @@ -3634,8 +3663,8 @@ win_line ( c = match_conc; } else if (syn_get_sub_char() != NUL) { c = syn_get_sub_char(); - } else if (lcs_conceal != NUL) { - c = lcs_conceal; + } else if (wp->w_p_lcs_chars.conceal != NUL) { + c = wp->w_p_lcs_chars.conceal; } else { c = ' '; } @@ -3705,12 +3734,13 @@ win_line ( && filler_todo <= 0 && draw_state > WL_NR && c != NUL) { - c = lcs_prec; + c = wp->w_p_lcs_chars.prec; lcs_prec_todo = NUL; if (has_mbyte && (*mb_char2cells)(mb_c) > 1) { /* Double-width character being overwritten by the "precedes" * character, need to fill up half the character. */ c_extra = MB_FILLER_CHAR; + c_final = NUL; n_extra = 1; n_attr = 2; extra_attr = win_hl_attr(wp, HLF_AT); @@ -3755,7 +3785,7 @@ win_line ( cur = cur->next; } } - if (lcs_eol == lcs_eol_one + if (wp->w_p_lcs_chars.eol == lcs_eol_one && ((area_attr != 0 && vcol == fromcol && (VIsual_mode != Ctrl_V || lnum == VIsual.lnum @@ -3858,7 +3888,8 @@ win_line ( // Make sure alignment is the same regardless // if listchars=eol:X is used or not. - bool delay_virttext = lcs_eol == lcs_eol_one && eol_hl_off == 0; + bool delay_virttext = wp->w_p_lcs_chars.eol == lcs_eol_one + && eol_hl_off == 0; if (wp->w_p_cuc) { rightmost_vcol = wp->w_virtcol; @@ -3978,15 +4009,15 @@ win_line ( break; } - /* line continues beyond line end */ - if (lcs_ext + // line continues beyond line end + if (wp->w_p_lcs_chars.ext && !wp->w_p_wrap && filler_todo <= 0 && (wp->w_p_rl ? col == 0 : col == grid->Columns - 1) && (*ptr != NUL || (wp->w_p_list && lcs_eol_one > 0) || (n_extra && (c_extra != NUL || *p_extra != NUL)))) { - c = lcs_ext; + c = wp->w_p_lcs_chars.ext; char_attr = win_hl_attr(wp, HLF_AT); mb_c = c; if (enc_utf8 && utf_char2len(c) > 1) { @@ -4164,7 +4195,8 @@ win_line ( if ((wp->w_p_rl ? (col < 0) : (col >= grid->Columns)) && (*ptr != NUL || filler_todo > 0 - || (wp->w_p_list && lcs_eol != NUL && p_extra != at_end_str) + || (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL + && p_extra != at_end_str) || (n_extra != 0 && (c_extra != NUL || *p_extra != NUL))) ) { bool wrap = wp->w_p_wrap // Wrapping enabled. @@ -4172,7 +4204,7 @@ win_line ( && lcs_eol_one != -1 // Haven't printed the lcs_eol character. && row != endrow - 1 // Not the last line being displayed. && (grid->Columns == Columns // Window spans the width of the screen, - || ui_is_external(kUIMultigrid)) // or has dedicated grid. + || ui_has(kUIMultigrid)) // or has dedicated grid. && !wp->w_p_rl; // Not right-to-left. grid_put_linebuf(grid, row, 0, col - boguscols, grid->Columns, wp->w_p_rl, wp, wp->w_hl_attr_normal, wrap); @@ -4224,16 +4256,19 @@ win_line ( saved_n_extra = n_extra; saved_p_extra = p_extra; saved_c_extra = c_extra; + saved_c_final = c_final; saved_char_attr = char_attr; n_extra = 0; - lcs_prec_todo = lcs_prec; - if (filler_todo <= 0) - need_showbreak = TRUE; - --filler_todo; - /* When the filler lines are actually below the last line of the - * file, don't draw the line itself, break here. */ - if (filler_todo == 0 && wp->w_botfill) + lcs_prec_todo = wp->w_p_lcs_chars.prec; + if (filler_todo <= 0) { + need_showbreak = true; + } + filler_todo--; + // When the filler lines are actually below the last line of the + // file, don't draw the line itself, break here. + if (filler_todo == 0 && wp->w_botfill) { break; + } } } /* for every character in the line */ @@ -4257,7 +4292,7 @@ win_line ( /// screen positions. static void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off) { - if (!ui_is_external(kUIMultigrid) && *grid != &default_grid) { + if (!(*grid)->chars && *grid != &default_grid) { *row_off += (*grid)->row_offset; *col_off += (*grid)->col_offset; *grid = &default_grid; @@ -4507,7 +4542,7 @@ void redraw_statuslines(void) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_redr_status) { - win_redr_status(wp, false); + win_redr_status(wp); } } if (redraw_tabline) @@ -4629,8 +4664,7 @@ win_redr_status_matches ( if (matches == NULL) /* interrupted completion? */ return; - buf = xmalloc(has_mbyte ? default_grid.Columns * MB_MAXBYTES + 1 - : default_grid.Columns + 1); + buf = xmalloc(Columns * MB_MAXBYTES + 1); if (match == -1) { /* don't show match but original text */ match = 0; @@ -4651,13 +4685,13 @@ win_redr_status_matches ( if (first_match > 0) clen += 2; // jumping right, put match at the left - if ((long)clen > default_grid.Columns) { + if ((long)clen > Columns) { first_match = match; /* if showing the last match, we can add some on the left */ clen = 2; for (i = match; i < num_matches; ++i) { clen += status_match_len(xp, L_MATCH(i)) + 2; - if ((long)clen >= default_grid.Columns) { + if ((long)clen >= Columns) { break; } } @@ -4668,7 +4702,7 @@ win_redr_status_matches ( if (add_left) while (first_match > 0) { clen += status_match_len(xp, L_MATCH(first_match - 1)) + 2; - if ((long)clen >= default_grid.Columns) { + if ((long)clen >= Columns) { break; } first_match--; @@ -4686,8 +4720,7 @@ win_redr_status_matches ( clen = len; i = first_match; - while ((long)(clen + status_match_len(xp, L_MATCH(i)) + 2) - < default_grid.Columns) { + while ((long)(clen + status_match_len(xp, L_MATCH(i)) + 2) < Columns) { if (i == match) { selstart = buf + len; selstart_col = clen; @@ -4738,7 +4771,7 @@ win_redr_status_matches ( if (msg_scrolled > 0) { /* Put the wildmenu just above the command line. If there is * no room, scroll the screen one line up. */ - if (cmdline_row == default_grid.Rows - 1) { + if (cmdline_row == Rows - 1) { grid_del_lines(&default_grid, 0, 1, (int)Rows, 0, (int)Columns); msg_scrolled++; } else { @@ -4767,7 +4800,7 @@ win_redr_status_matches ( grid_puts(&default_grid, selstart, row, selstart_col, HL_ATTR(HLF_WM)); } - grid_fill(&default_grid, row, row + 1, clen, (int)default_grid.Columns, + grid_fill(&default_grid, row, row + 1, clen, (int)Columns, fillchar, fillchar, attr); } @@ -4778,9 +4811,7 @@ win_redr_status_matches ( /// Redraw the status line of window `wp`. /// /// If inversion is possible we use it. Else '=' characters are used. -/// If "ignore_pum" is true, also redraw statusline when the popup menu is -/// displayed. -static void win_redr_status(win_T *wp, int ignore_pum) +static void win_redr_status(win_T *wp) { int row; char_u *p; @@ -4794,7 +4825,7 @@ static void win_redr_status(win_T *wp, int ignore_pum) // invokes ":redrawstatus". Simply ignore the call then. if (busy // Also ignore if wildmenu is showing. - || (wild_menu_showing != 0 && !ui_is_external(kUIWildmenu))) { + || (wild_menu_showing != 0 && !ui_has(kUIWildmenu))) { return; } busy = true; @@ -4803,7 +4834,7 @@ static void win_redr_status(win_T *wp, int ignore_pum) if (wp->w_status_height == 0) { // no status line, can only be last window redraw_cmdline = true; - } else if (!redrawing() || (!ignore_pum && pum_drawn())) { + } else if (!redrawing()) { // Don't redraw right now, do it later. Don't update status line when // popup menu is visible and may be drawn over it wp->w_redr_status = true; @@ -5033,7 +5064,7 @@ win_redr_custom ( row = 0; fillchar = ' '; attr = HL_ATTR(HLF_TPF); - maxwidth = default_grid.Columns; + maxwidth = Columns; use_sandbox = was_set_insecurely((char_u *)"tabline", 0); } else { row = W_ENDROW(wp); @@ -5052,13 +5083,13 @@ win_redr_custom ( if (*stl++ != '(') stl = p_ruf; } - col = ru_col - (default_grid.Columns - wp->w_width); + col = ru_col - (Columns - wp->w_width); if (col < (wp->w_width + 1) / 2) { col = (wp->w_width + 1) / 2; } maxwidth = wp->w_width - col; if (!wp->w_status_height) { - row = default_grid.Rows - 1; + row = Rows - 1; maxwidth--; // writing in last column may cause scrolling fillchar = ' '; attr = 0; @@ -5152,7 +5183,7 @@ win_redr_custom ( p = (char_u *) tabtab[n].start; cur_click_def = tabtab[n].def; } - while (col < default_grid.Columns) { + while (col < Columns) { tab_page_click_defs[col++] = cur_click_def; } } @@ -5282,7 +5313,7 @@ void grid_puts(ScreenGrid *grid, char_u *text, int row, int col, int attr) } static int put_dirty_row = -1; -static int put_dirty_first = -1; +static int put_dirty_first = INT_MAX; static int put_dirty_last = 0; /// Start a group of screen_puts_len calls that builds a single screen line. @@ -5313,8 +5344,6 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int prev_c = 0; /* previous Arabic character */ int pc, nc, nc1; int pcc[MAX_MCO]; - int force_redraw_this; - int force_redraw_next = FALSE; int need_redraw; bool do_flush = false; @@ -5337,16 +5366,10 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, /* When drawing over the right halve of a double-wide char clear out the * left halve. Only needed in a terminal. */ - if (col > 0 && col < grid->Columns && grid_fix_col(grid, col, row) != col) { - schar_from_ascii(grid->chars[off - 1], ' '); - grid->attrs[off - 1] = 0; + if (grid != &default_grid && col == 0 && grid_invalid_row(grid, row)) { // redraw the previous cell, make it empty - 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; + put_dirty_first = -1; + put_dirty_last = MAX(put_dirty_last, 1); } max_off = grid->line_offset[row] + grid->Columns; @@ -5394,15 +5417,12 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, schar_from_cc(buf, u8c, u8cc); - force_redraw_this = force_redraw_next; - force_redraw_next = FALSE; - need_redraw = schar_cmp(grid->chars[off], buf) || (mbyte_cells == 2 && grid->chars[off + 1][0] != 0) || grid->attrs[off] != attr || exmode_active; - if (need_redraw || force_redraw_this) { + if (need_redraw) { // When at the end of the text and overwriting a two-cell // character with a one-cell character, need to clear the next // cell. Also when overwriting the left halve of a two-cell char @@ -5426,10 +5446,8 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, grid->chars[off + 1][0] = 0; grid->attrs[off + 1] = attr; } - if (put_dirty_first == -1) { - put_dirty_first = col; - } - put_dirty_last = col+mbyte_cells; + put_dirty_first = MIN(put_dirty_first, col); + put_dirty_last = MAX(put_dirty_last, col+mbyte_cells); } off += mbyte_cells; @@ -5457,14 +5475,14 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, void grid_puts_line_flush(ScreenGrid *grid, bool set_cursor) { assert(put_dirty_row != -1); - if (put_dirty_first != -1) { + if (put_dirty_first < put_dirty_last) { if (set_cursor) { ui_grid_cursor_goto(grid->handle, put_dirty_row, MIN(put_dirty_last, grid->Columns-1)); } ui_line(grid, put_dirty_row, put_dirty_first, put_dirty_last, put_dirty_last, 0, false); - put_dirty_first = -1; + put_dirty_first = INT_MAX; put_dirty_last = 0; } put_dirty_row = -1; @@ -5790,7 +5808,7 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, } // nothing to do - if (grid->chars == NULL || start_row >= end_row || start_col >= end_col) { + if (start_row >= end_row || start_col >= end_col) { return; } @@ -5836,9 +5854,7 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, 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_first = MIN(put_dirty_first, dirty_first); put_dirty_last = MAX(put_dirty_last, dirty_last); } else { int last = c2 != ' ' ? dirty_last : dirty_first + (c1 != ' '); @@ -5851,7 +5867,8 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, } // TODO(bfredl): The relevant caller should do this - if (row == default_grid.Rows - 1) { // overwritten the command line + if (row == Rows - 1 && !ui_has(kUIMessages)) { + // overwritten the command line redraw_cmdline = true; if (start_col == 0 && end_col == Columns && c1 == ' ' && c2 == ' ' && attr == 0) { @@ -5881,18 +5898,6 @@ void check_for_delay(int check_msg_scroll) } } -/* - * screen_valid - allocate screen buffers if size changed - * If "doclear" is TRUE: clear screen if it has been resized. - * Returns TRUE if there is a valid screen to write to. - * Returns FALSE when starting up and screen not initialized yet. - */ -int screen_valid(int doclear) -{ - screenalloc(doclear); // allocate screen buffers if size changed - return default_grid.chars != NULL; -} - /// (Re)allocates a window grid if size changed while in ext_multigrid mode. /// Updates size, offsets and handle for the grid regardless. /// @@ -5902,38 +5907,35 @@ void win_grid_alloc(win_T *wp) { ScreenGrid *grid = &wp->w_grid; - int rows = grid->requested_rows; - if (rows == 0) { - rows = wp->w_height; - } - - int columns = grid->requested_cols; - if (columns == 0) { - columns = wp->w_width; - } + int rows = wp->w_height_inner; + int cols = wp->w_width_inner; // TODO(bfredl): floating windows should force this to true - bool want_allocation = ui_is_external(kUIMultigrid); + bool want_allocation = ui_has(kUIMultigrid); bool has_allocation = (grid->chars != NULL); if (want_allocation && has_allocation && highlights_invalid) { grid_invalidate(grid); } + if (grid->Rows != rows) { + wp->w_lines_valid = 0; + xfree(wp->w_lines); + wp->w_lines = xcalloc(rows+1, sizeof(wline_T)); + } + int was_resized = false; if ((has_allocation != want_allocation) || grid->Rows != rows - || grid->Columns != columns) { + || grid->Columns != cols) { if (want_allocation) { - grid_alloc(grid, rows, columns, true); - win_free_lsize(wp); - win_alloc_lines(wp); + grid_alloc(grid, rows, cols, true, true); } else { // Single grid mode, all rendering will be redirected to default_grid. // Only keep track of the size and offset of the window. grid_free(grid); grid->Rows = rows; - grid->Columns = columns; + grid->Columns = cols; } was_resized = true; } @@ -5945,7 +5947,7 @@ void win_grid_alloc(win_T *wp) // - a grid was just resized // - screen_resize was called and all grid sizes must be sent // - the UI wants multigrid event (necessary) - if ((send_grid_resize || was_resized) && ui_is_external(kUIMultigrid)) { + if ((send_grid_resize || was_resized) && ui_has(kUIMultigrid)) { ui_call_grid_resize(grid->handle, grid->Columns, grid->Rows); } } @@ -5971,7 +5973,7 @@ void grid_assign_handle(ScreenGrid *grid) /// default_grid.Columns to access items in default_grid.chars[]. Use Rows /// and Columns for positioning text etc. where the final size of the shell is /// needed. -void screenalloc(bool doclear) +void screenalloc(void) { static bool entered = false; // avoid recursiveness int retry_count = 0; @@ -6005,6 +6007,10 @@ retry: */ ++RedrawingDisabled; + // win_new_shellsize will recompute floats position, but tell the + // compositor to not redraw them yet + ui_comp_invalidate_screen(); + win_new_shellsize(); /* fit the windows in the new sized shell */ comp_col(); /* recompute columns for shown command and ruler */ @@ -6018,37 +6024,22 @@ retry: // If anything fails, make grid arrays NULL, so we don't do anything! // Continuing with the old arrays may result in a crash, because the // size is wrong. - FOR_ALL_TAB_WINDOWS(tp, wp) { - win_free_lsize(wp); - } - if (aucmd_win != NULL) - win_free_lsize(aucmd_win); - grid_alloc(&default_grid, Rows, Columns, !doclear); + grid_alloc(&default_grid, Rows, Columns, true, true); StlClickDefinition *new_tab_page_click_defs = xcalloc( (size_t)Columns, sizeof(*new_tab_page_click_defs)); - FOR_ALL_TAB_WINDOWS(tp, wp) { - win_alloc_lines(wp); - } - if (aucmd_win != NULL && aucmd_win->w_lines == NULL) { - win_alloc_lines(aucmd_win); - } - clear_tab_page_click_defs(tab_page_click_defs, tab_page_click_defs_size); xfree(tab_page_click_defs); tab_page_click_defs = new_tab_page_click_defs; - tab_page_click_defs_size = default_grid.Columns; + tab_page_click_defs_size = Columns; default_grid.row_offset = 0; default_grid.col_offset = 0; default_grid.handle = DEFAULT_GRID_HANDLE; - must_redraw = CLEAR; /* need to clear the screen later */ - if (doclear) - screenclear2(); - + must_redraw = CLEAR; // need to clear the screen later entered = FALSE; --RedrawingDisabled; @@ -6065,7 +6056,7 @@ retry: } } -void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy) +void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy, bool valid) { int new_row; ScreenGrid new = *grid; @@ -6083,7 +6074,7 @@ void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy) new.line_offset[new_row] = new_row * new.Columns; new.line_wraps[new_row] = false; - grid_clear_line(&new, new.line_offset[new_row], columns, true); + grid_clear_line(&new, new.line_offset[new_row], columns, valid); if (copy) { // If the screen is not going to be cleared, copy as much as @@ -6155,13 +6146,9 @@ void clear_tab_page_click_defs(StlClickDefinition *const tpcd, void screenclear(void) { - check_for_delay(FALSE); - screenalloc(false); /* allocate screen buffers if size changed */ - screenclear2(); /* clear the screen */ -} + check_for_delay(false); + screenalloc(); // allocate screen buffers if size changed -static void screenclear2(void) -{ int i; if (starting == NO_SCREEN || default_grid.chars == NULL) { @@ -6182,6 +6169,8 @@ static void screenclear2(void) redraw_all_later(NOT_VALID); redraw_cmdline = true; redraw_tabline = true; + redraw_popupmenu = true; + pum_invalidate(); if (must_redraw == CLEAR) { must_redraw = NOT_VALID; // no need to clear again } @@ -6205,11 +6194,17 @@ static void grid_clear_line(ScreenGrid *grid, unsigned off, int width, (void)memset(grid->attrs + off, fill, (size_t)width * sizeof(sattr_T)); } -static void grid_invalidate(ScreenGrid *grid) +void grid_invalidate(ScreenGrid *grid) { (void)memset(grid->attrs, -1, grid->Rows * grid->Columns * sizeof(sattr_T)); } +bool grid_invalid_row(ScreenGrid *grid, int row) +{ + return grid->attrs[grid->line_offset[row]] < 0; +} + + /// Copy part of a grid line for vertically split window. static void linecopy(ScreenGrid *grid, int to, int from, int col, int width) @@ -6237,7 +6232,7 @@ void setcursor(void) if (curwin->w_p_rl) { // With 'rightleft' set and the cursor on a double-wide character, // position it on the leftmost column. - col = curwin->w_grid.Columns - curwin->w_wcol + col = curwin->w_width_inner - curwin->w_wcol - ((utf_ptr2cells(get_cursor_pos_ptr()) == 2 && vim_isprintc(gchar_cursor())) ? 2 : 1); } @@ -6247,42 +6242,28 @@ void setcursor(void) } } -/// Insert 'line_count' lines at 'row' in window 'wp'. -/// Returns FAIL if the lines are not inserted, OK for success. -int win_ins_lines(win_T *wp, int row, int line_count) -{ - return win_do_lines(wp, row, line_count, false); -} - -/// Delete "line_count" window lines at "row" in window "wp". -/// Return OK for success, FAIL if the lines are not deleted. -int win_del_lines(win_T *wp, int row, int line_count) -{ - return win_do_lines(wp, row, line_count, true); -} - -// Common code for win_ins_lines() and win_del_lines(). -// Returns OK or FAIL when the work has been done. -static int win_do_lines(win_T *wp, int row, int line_count, int del) +/// Scroll 'line_count' lines at 'row' in window 'wp'. +/// +/// Positive `line_count' means scrolling down, so that more space is available +/// at 'row'. Negative `line_count` implies deleting lines at `row`. +void win_scroll_lines(win_T *wp, int row, int line_count) { - if (!redrawing() || line_count <= 0) { - return FAIL; + if (!redrawing() || line_count == 0) { + return; } // No lines are being moved, just draw over the entire area - if (row + line_count >= wp->w_grid.Rows) { - return OK; + if (row + abs(line_count) >= wp->w_grid.Rows) { + return; } - int retval; - if (del) { - retval = grid_del_lines(&wp->w_grid, row, line_count, - wp->w_grid.Rows, 0, wp->w_grid.Columns); + if (line_count < 0) { + grid_del_lines(&wp->w_grid, row, -line_count, + wp->w_grid.Rows, 0, wp->w_grid.Columns); } else { - retval = grid_ins_lines(&wp->w_grid, row, line_count, - wp->w_grid.Rows, 0, wp->w_grid.Columns); + grid_ins_lines(&wp->w_grid, row, line_count, + wp->w_grid.Rows, 0, wp->w_grid.Columns); } - return retval; } /* @@ -6302,10 +6283,8 @@ static int win_do_lines(win_T *wp, int row, int line_count, int del) /// 'col' is the column from with we start inserting. // /// 'row', 'col' and 'end' are relative to the start of the region. -/// -/// @return FAIL for failure, OK for success. -int grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, - int width) +void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, + int width) { int i; int j; @@ -6316,8 +6295,8 @@ int grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, row += row_off; end += row_off; - if (!screen_valid(TRUE) || line_count <= 0) { - return FAIL; + if (line_count <= 0) { + return; } // Shift line_offset[] line_count down to reflect the inserted lines. @@ -6347,17 +6326,15 @@ int grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, ui_call_grid_scroll(grid->handle, row, end, col, col+width, -line_count, 0); - return OK; + return; } /// delete lines on the screen and move lines up. /// '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 grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, - int width) +void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, + int width) { int j; int i; @@ -6368,8 +6345,8 @@ int grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, row += row_off; end += row_off; - if (!screen_valid(TRUE) || line_count <= 0) { - return FAIL; + if (line_count <= 0) { + return; } // Now shift line_offset[] line_count up to reflect the deleted lines. @@ -6400,7 +6377,7 @@ int grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, ui_call_grid_scroll(grid->handle, row, end, col, col+width, line_count, 0); - return OK; + return; } @@ -6421,6 +6398,13 @@ int showmode(void) int nwr_save; int sub_attr; + if (ui_has(kUIMessages) && clear_cmdline) { + msg_ext_clear(true); + } + + // don't make non-flushed message part of the showmode + msg_ext_ui_flush(); + do_mode = ((p_smd && msg_silent == 0) && ((State & TERM_FOCUS) || (State & INSERT) @@ -6445,7 +6429,7 @@ int showmode(void) /* if the cmdline is more than one line high, erase top lines */ need_clear = clear_cmdline; - if (clear_cmdline && cmdline_row < default_grid.Rows - 1) { + if (clear_cmdline && cmdline_row < Rows - 1) { msg_clr_cmdline(); // will reset clear_cmdline } @@ -6463,9 +6447,14 @@ int showmode(void) MSG_PUTS_ATTR("--", attr); // CTRL-X in Insert mode if (edit_submode != NULL && !shortmess(SHM_COMPLETIONMENU)) { - /* These messages can get long, avoid a wrap in a narrow - * window. Prefer showing edit_submode_extra. */ - length = (default_grid.Rows - msg_row) * default_grid.Columns - 3; + // These messages can get long, avoid a wrap in a narrow window. + // Prefer showing edit_submode_extra. With external messages there + // is no imposed limit. + if (ui_has(kUIMessages)) { + length = INT_MAX; + } else { + length = (Rows - msg_row) * Columns - 3; + } if (edit_submode_extra != NULL) { length -= vim_strsize(edit_submode_extra); } @@ -6567,6 +6556,9 @@ int showmode(void) msg_clr_cmdline(); } + // NB: also handles clearing the showmode if it was emtpy or disabled + msg_ext_flush_showmode(); + /* In Visual mode the size of the selected area must be redrawn. */ if (VIsual_active) clear_showcmd(); @@ -6589,7 +6581,7 @@ int showmode(void) static void msg_pos_mode(void) { msg_col = 0; - msg_row = default_grid.Rows - 1; + msg_row = Rows - 1; } /// Delete mode message. Used when ESC is typed which is expected to end @@ -6608,11 +6600,13 @@ void unshowmode(bool force) // Clear the mode message. void clearmode(void) { - msg_pos_mode(); - if (Recording) { - recording_mode(HL_ATTR(HLF_CM)); - } - msg_clr_eos(); + msg_ext_ui_flush(); + msg_pos_mode(); + if (Recording) { + recording_mode(HL_ATTR(HLF_CM)); + } + msg_clr_eos(); + msg_ext_flush_showmode(); } static void recording_mode(int attr) @@ -6653,7 +6647,7 @@ static void draw_tabline(void) } redraw_tabline = false; - if (ui_is_external(kUITabline)) { + if (ui_has(kUITabline)) { ui_ext_tabline_update(); return; } @@ -6663,7 +6657,7 @@ static void draw_tabline(void) // Init TabPageIdxs[] to zero: Clicking outside of tabs has no effect. - assert(default_grid.Columns == tab_page_click_defs_size); + assert(Columns == tab_page_click_defs_size); clear_tab_page_click_defs(tab_page_click_defs, tab_page_click_defs_size); /* Use the 'tabline' option if it's set. */ @@ -6685,7 +6679,7 @@ static void draw_tabline(void) } if (tabcount > 0) { - tabwidth = (default_grid.Columns - 1 + tabcount / 2) / tabcount; + tabwidth = (Columns - 1 + tabcount / 2) / tabcount; } if (tabwidth < 6) { @@ -6696,7 +6690,7 @@ static void draw_tabline(void) tabcount = 0; FOR_ALL_TABS(tp) { - if (col >= default_grid.Columns - 4) { + if (col >= Columns - 4) { break; } @@ -6737,7 +6731,7 @@ static void draw_tabline(void) if (wincount > 1) { vim_snprintf((char *)NameBuff, MAXPATHL, "%d", wincount); len = (int)STRLEN(NameBuff); - if (col + len >= default_grid.Columns - 3) { + if (col + len >= Columns - 3) { break; } grid_puts_len(&default_grid, NameBuff, len, 0, col, @@ -6766,8 +6760,8 @@ static void draw_tabline(void) p += len - room; len = room; } - if (len > default_grid.Columns - col - 1) { - len = default_grid.Columns - col - 1; + if (len > Columns - col - 1) { + len = Columns - col - 1; } grid_puts_len(&default_grid, p, (int)STRLEN(p), 0, col, attr); @@ -6791,14 +6785,14 @@ static void draw_tabline(void) c = '_'; else c = ' '; - grid_fill(&default_grid, 0, 1, col, (int)default_grid.Columns, c, c, + grid_fill(&default_grid, 0, 1, col, (int)Columns, c, c, attr_fill); /* Put an "X" for closing the current tab if there are several. */ if (first_tabpage->tp_next != NULL) { - grid_putchar(&default_grid, 'X', 0, (int)default_grid.Columns - 1, + grid_putchar(&default_grid, 'X', 0, (int)Columns - 1, attr_nosel); - tab_page_click_defs[default_grid.Columns - 1] = (StlClickDefinition) { + tab_page_click_defs[Columns - 1] = (StlClickDefinition) { .type = kStlClickTabClose, .tabnr = 999, .func = NULL, @@ -6849,17 +6843,17 @@ static int fillchar_status(int *attr, win_T *wp) bool is_curwin = (wp == curwin); if (is_curwin) { *attr = win_hl_attr(wp, HLF_S); - fill = fill_stl; + fill = wp->w_p_fcs_chars.stl; } else { *attr = win_hl_attr(wp, HLF_SNC); - fill = fill_stlnc; + fill = wp->w_p_fcs_chars.stlnc; } /* Use fill when there is highlighting, and highlighting of current * window differs, or the fillchars differ, or this is not the * current window */ if (*attr != 0 && ((win_hl_attr(wp, HLF_S) != win_hl_attr(wp, HLF_SNC) || !is_curwin || ONE_WINDOW) - || (fill_stl != fill_stlnc))) { + || (wp->w_p_fcs_chars.stl != wp->w_p_fcs_chars.stlnc))) { return fill; } if (is_curwin) { @@ -6875,7 +6869,7 @@ static int fillchar_status(int *attr, win_T *wp) static int fillchar_vsep(win_T *wp, int *attr) { *attr = win_hl_attr(wp, HLF_C); - return fill_vert; + return wp->w_p_fcs_chars.vert; } /* @@ -6903,11 +6897,6 @@ void showruler(int always) { if (!always && !redrawing()) return; - if (pum_drawn()) { - // Don't redraw right now, do it later. - curwin->w_redr_status = true; - return; - } if ((*p_stl != NUL || *curwin->w_p_stl != NUL) && curwin->w_status_height) { redraw_custom_statusline(curwin); } else { @@ -6926,9 +6915,12 @@ void showruler(int always) static void win_redr_ruler(win_T *wp, int always) { - /* If 'ruler' off or redrawing disabled, don't do anything */ - if (!p_ru) + static bool did_show_ext_ruler = false; + + // If 'ruler' off or redrawing disabled, don't do anything + if (!p_ru) { return; + } /* * Check if cursor.lnum is valid, since win_redr_ruler() may be called @@ -6942,10 +6934,6 @@ static void win_redr_ruler(win_T *wp, int always) if (wp == lastwin && lastwin->w_status_height == 0) if (edit_submode != NULL) return; - // Don't draw the ruler when the popup menu is visible, it may overlap. - if (pum_drawn()) { - return; - } if (*p_ruf) { int save_called_emsg = called_emsg; @@ -6987,26 +6975,28 @@ static void win_redr_ruler(win_T *wp, int always) int fillchar; int attr; int off; + bool part_of_status = false; if (wp->w_status_height) { row = W_ENDROW(wp); fillchar = fillchar_status(&attr, wp); off = wp->w_wincol; width = wp->w_width; + part_of_status = true; } else { - row = default_grid.Rows - 1; + row = Rows - 1; fillchar = ' '; attr = 0; - width = default_grid.Columns; + width = Columns; off = 0; } - /* In list mode virtcol needs to be recomputed */ + // In list mode virtcol needs to be recomputed colnr_T virtcol = wp->w_virtcol; - if (wp->w_p_list && lcs_tab1 == NUL) { - wp->w_p_list = FALSE; + if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) { + wp->w_p_list = false; getvvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL); - wp->w_p_list = TRUE; + wp->w_p_list = true; } #define RULER_BUF_LEN 70 @@ -7035,7 +7025,7 @@ static void win_redr_ruler(win_T *wp, int always) if (wp->w_status_height == 0) { // can't use last char of screen o++; } - int this_ru_col = ru_col - (default_grid.Columns - width); + int this_ru_col = ru_col - (Columns - width); if (this_ru_col < 0) { this_ru_col = 0; } @@ -7052,23 +7042,39 @@ static void win_redr_ruler(win_T *wp, int always) } get_rel_pos(wp, buffer + i, RULER_BUF_LEN - i); } - // Truncate at window boundary. - o = 0; - for (i = 0; buffer[i] != NUL; i += utfc_ptr2len(buffer + i)) { - o += utf_ptr2cells(buffer + i); - if (this_ru_col + o > width) { - buffer[i] = NUL; - break; + + if (ui_has(kUIMessages) && !part_of_status) { + Array content = ARRAY_DICT_INIT; + Array chunk = ARRAY_DICT_INIT; + ADD(chunk, INTEGER_OBJ(attr)); + ADD(chunk, STRING_OBJ(cstr_to_string((char *)buffer))); + ADD(content, ARRAY_OBJ(chunk)); + ui_call_msg_ruler(content); + did_show_ext_ruler = true; + } else { + if (did_show_ext_ruler) { + ui_call_msg_ruler((Array)ARRAY_DICT_INIT); + did_show_ext_ruler = false; + } + // Truncate at window boundary. + o = 0; + for (i = 0; buffer[i] != NUL; i += utfc_ptr2len(buffer + i)) { + o += utf_ptr2cells(buffer + i); + if (this_ru_col + o > width) { + buffer[i] = NUL; + break; + } } + + grid_puts(&default_grid, buffer, row, this_ru_col + off, attr); + i = redraw_cmdline; + grid_fill(&default_grid, row, row + 1, + this_ru_col + off + (int)STRLEN(buffer), off + width, fillchar, + fillchar, attr); + // don't redraw the cmdline because of showing the ruler + redraw_cmdline = i; } - grid_puts(&default_grid, buffer, row, this_ru_col + off, attr); - i = redraw_cmdline; - grid_fill(&default_grid, row, row + 1, - this_ru_col + off + (int)STRLEN(buffer), off + width, fillchar, - fillchar, attr); - // don't redraw the cmdline because of showing the ruler - redraw_cmdline = i; wp->w_ru_cursor = wp->w_cursor; wp->w_ru_virtcol = wp->w_virtcol; wp->w_ru_empty = empty_line; @@ -7090,7 +7096,7 @@ int number_width(win_T *wp) if (wp->w_p_rnu && !wp->w_p_nu) { // cursor line shows "0" - lnum = wp->w_grid.Rows; + lnum = wp->w_height_inner; } else { // cursor line shows absolute line number lnum = wp->w_buffer->b_ml.ml_line_count; @@ -7177,7 +7183,7 @@ void screen_resize(int width, int height) */ if (State == ASKMORE || State == EXTERNCMD || State == CONFIRM || exmode_active) { - screenalloc(false); + screenalloc(); repeat_message(); } else { if (curwin->w_p_scb) @@ -7188,7 +7194,10 @@ void screen_resize(int width, int height) } else { update_topline(); if (pum_drawn()) { - redraw_later(NOT_VALID); + // TODO(bfredl): ins_compl_show_pum wants to redraw the screen first. + // For now make sure the nested update_screen(0) won't redraw the + // pum at the old position. Try to untangle this later. + redraw_popupmenu = false; ins_compl_show_pum(); } update_screen(NOT_VALID); diff --git a/src/nvim/screen.h b/src/nvim/screen.h index 109541ef07..61ed98247d 100644 --- a/src/nvim/screen.h +++ b/src/nvim/screen.h @@ -28,8 +28,7 @@ /// /// Note: before the screen is initialized and when out of memory these can be /// NULL. -EXTERN ScreenGrid default_grid INIT(= { 0, NULL, NULL, NULL, NULL, 0, 0, 0, 0, - 0, 0, 0 }); +EXTERN ScreenGrid default_grid INIT(= SCREEN_GRID_INIT); #define DEFAULT_GRID_HANDLE 1 // handle for the default_grid diff --git a/src/nvim/search.c b/src/nvim/search.c index cf0f1ea287..5f0ff96f95 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -2168,7 +2168,7 @@ showmatch( } if (curwin->w_p_wrap || (vcol >= curwin->w_leftcol - && vcol < curwin->w_leftcol + curwin->w_grid.Columns)) { + && vcol < curwin->w_leftcol + curwin->w_width_inner)) { mpos = *lpos; // save the pos, update_screen() may change it save_cursor = curwin->w_cursor; save_so = p_so; diff --git a/src/nvim/strings.c b/src/nvim/strings.c index 4921824316..96a8dfd295 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -27,6 +27,7 @@ #include "nvim/func_attr.h" #include "nvim/getchar.h" #include "nvim/mark.h" +#include "nvim/math.h" #include "nvim/mbyte.h" #include "nvim/memfile.h" #include "nvim/memline.h" @@ -50,16 +51,7 @@ #include "nvim/os/shell.h" #include "nvim/eval/encode.h" -#ifdef __MINGW32__ -# undef fpclassify -# define fpclassify __fpclassify -# undef isnan -# define isnan _isnan -#endif - -/* - * Copy "string" into newly allocated memory. - */ +/// Copy "string" into newly allocated memory. char_u *vim_strsave(const char_u *string) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { @@ -1214,14 +1206,14 @@ int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap, remove_trailing_zeroes = true; } - if (isinf(f) + if (xisinf(f) || (strchr("fF", fmt_spec) != NULL && abs_f > 1.0e307)) { xstrlcpy(tmp, infinity_str(f > 0.0, fmt_spec, force_sign, space_for_positive), sizeof(tmp)); str_arg_l = strlen(tmp); zero_padding = 0; - } else if (isnan(f)) { + } else if (xisnan(f)) { // Not a number: nan or NAN memmove(tmp, ASCII_ISUPPER(fmt_spec) ? "NAN" : "nan", 4); str_arg_l = 3; diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 81c78ca6a9..b6b7dfff11 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6893,7 +6893,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) // "fg", which have been changed now. highlight_attr_set_all(); - if (!ui_is_external(kUILinegrid) && starting == 0) { + if (!ui_has(kUILinegrid) && starting == 0) { // Older UIs assume that we clear the screen after normal group is // changed ui_refresh(); diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 51bf22b31c..8b4ad4d3af 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -236,7 +236,8 @@ Terminal *terminal_open(TerminalOptions opts) // Default settings for terminal buffers curbuf->b_p_ma = false; // 'nomodifiable' curbuf->b_p_ul = -1; // 'undolevels' - curbuf->b_p_scbk = p_scbk; // 'scrollback' + curbuf->b_p_scbk = // 'scrollback' (initialize local from global) + (p_scbk < 0) ? 10000 : MAX(1, p_scbk); curbuf->b_p_tw = 0; // 'textwidth' set_option_value("wrap", false, NULL, OPT_LOCAL); set_option_value("list", false, NULL, OPT_LOCAL); @@ -244,13 +245,13 @@ Terminal *terminal_open(TerminalOptions opts) RESET_BINDING(curwin); // Reset cursor in current window. curwin->w_cursor = (pos_T){ .lnum = 1, .col = 0, .coladd = 0 }; - // Apply TermOpen autocmds _before_ configuring the scrollback buffer. apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, curbuf); + // Local 'scrollback' _after_ autocmds. + curbuf->b_p_scbk = (curbuf->b_p_scbk < 1) ? SB_MAX : curbuf->b_p_scbk; // Configure the scrollback buffer. - rv->sb_size = curbuf->b_p_scbk < 0 - ? SB_MAX : (size_t)MAX(1, curbuf->b_p_scbk); + rv->sb_size = (size_t)curbuf->b_p_scbk; rv->sb_buffer = xmalloc(sizeof(ScrollbackLine *) * rv->sb_size); if (!true_color) { @@ -334,41 +335,32 @@ void terminal_close(Terminal *term, char *msg) } } -void terminal_resize(Terminal *term, uint16_t width, uint16_t height) +void terminal_check_size(Terminal *term) { if (term->closed) { - // If two windows display the same terminal and one is closed by keypress. return; } - bool force = width == UINT16_MAX || height == UINT16_MAX; + int curwidth, curheight; vterm_get_size(term->vt, &curheight, &curwidth); + uint16_t width = 0, height = 0; - if (force || !width) { - width = (uint16_t)curwidth; - } - - if (force || !height) { - height = (uint16_t)curheight; - } - - if (!force && curheight == height && curwidth == width) { - return; - } - - if (height == 0 || width == 0) { - return; - } FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->w_buffer && wp->w_buffer->terminal == term) { const uint16_t win_width = - (uint16_t)(MAX(0, wp->w_width - win_col_off(wp))); + (uint16_t)(MAX(0, wp->w_width_inner - win_col_off(wp))); width = MAX(width, win_width); - height = (uint16_t)MAX(height, wp->w_height); + height = (uint16_t)MAX(height, wp->w_height_inner); } } + // if no window displays the terminal, or such all windows are zero-height, + // don't resize the terminal. + if ((curheight == height && curwidth == width) || height == 0 || width == 0) { + return; + } + vterm_set_size(term->vt, height, width); vterm_screen_flush_damage(term->vts); term->pending_resize = true; @@ -383,8 +375,10 @@ void terminal_enter(void) memset(s, 0, sizeof(TerminalState)); s->term = buf->terminal; - // Ensure the terminal is properly sized. - terminal_resize(s->term, 0, 0); + // Ensure the terminal is properly sized. Ideally window size management + // code should always have resized the terminal already, but check here to + // be sure. + terminal_check_size(s->term); int save_state = State; s->save_rd = RedrawingDisabled; @@ -981,8 +975,8 @@ static void mouse_action(Terminal *term, int button, int row, int col, // terminal should lose focus static bool send_mouse_event(Terminal *term, int c) { - int row = mouse_row, col = mouse_col; - win_T *mouse_win = mouse_find_win(&row, &col); + int row = mouse_row, col = mouse_col, grid = mouse_grid; + win_T *mouse_win = mouse_find_win(&grid, &row, &col); if (term->forward_mouse && mouse_win->w_buffer->terminal == term) { // event in the terminal window and mouse events was enabled by the @@ -1169,8 +1163,10 @@ static void refresh_size(Terminal *term, buf_T *buf) /// Adjusts scrollback storage after 'scrollback' option changed. static void on_scrollback_option_changed(Terminal *term, buf_T *buf) { - const size_t scbk = curbuf->b_p_scbk < 0 - ? SB_MAX : (size_t)MAX(1, curbuf->b_p_scbk); + if (buf->b_p_scbk < 1) { // Local 'scrollback' was set to -1. + buf->b_p_scbk = SB_MAX; + } + const size_t scbk = (size_t)buf->b_p_scbk; assert(term->sb_current < SIZE_MAX); if (term->sb_pending > 0) { // Pending rows must be processed first. abort(); @@ -1318,8 +1314,6 @@ static void redraw(bool restore_cursor) static void adjust_topline(Terminal *term, buf_T *buf, long added) { - int height, width; - vterm_get_size(term->vt, &height, &width); FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_buffer == buf) { linenr_T ml_end = buf->b_ml.ml_line_count; @@ -1328,7 +1322,7 @@ static void adjust_topline(Terminal *term, buf_T *buf, long added) if (following || (wp == curwin && is_focused(term))) { // "Follow" the terminal output wp->w_cursor.lnum = ml_end; - set_topline(wp, MAX(wp->w_cursor.lnum - height + 1, 1)); + set_topline(wp, MAX(wp->w_cursor.lnum - wp->w_height_inner + 1, 1)); } else { // Ensure valid cursor for each window displaying this terminal. wp->w_cursor.lnum = MIN(wp->w_cursor.lnum, ml_end); diff --git a/src/nvim/testdir/load.vim b/src/nvim/testdir/load.vim new file mode 100644 index 0000000000..2e01338dd0 --- /dev/null +++ b/src/nvim/testdir/load.vim @@ -0,0 +1,30 @@ +function! s:load_factor() abort + let timeout = 200 + let times = [] + + for _ in range(5) + let g:val = 0 + call timer_start(timeout, {-> nvim_set_var('val', 1)}) + let start = reltime() + while 1 + sleep 10m + if g:val == 1 + let g:waited_in_ms = float2nr(reltimefloat(reltime(start)) * 1000) + break + endif + endwhile + call insert(times, g:waited_in_ms, 0) + endfor + + let longest = max(times) + let factor = (longest + 50.0) / timeout + + return factor +endfunction + +" Compute load factor only once. +let s:load_factor = s:load_factor() + +function! LoadAdjust(num) abort + return float2nr(ceil(a:num * s:load_factor)) +endfunction diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index c7c3b378cc..011433f19e 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -24,6 +24,9 @@ let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' set rtp=$VIM/vimfiles,$VIMRUNTIME,$VIM/vimfiles/after let &packpath = &rtp +" Avoid storing shell history. +let $HISTFILE = "" + " Make sure $HOME does not get read or written. let $HOME = expand(getcwd() . '/XfakeHOME') if !isdirectory($HOME) diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 253d6750ed..f1fb8e67b9 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -18,6 +18,8 @@ func Test_vim_did_enter() endfunc if has('timers') + source load.vim + func ExitInsertMode(id) call feedkeys("\<Esc>") endfunc @@ -29,7 +31,7 @@ if has('timers') let g:triggered = 0 au CursorHoldI * let g:triggered += 1 set updatetime=20 - call timer_start(100, 'ExitInsertMode') + call timer_start(LoadAdjust(100), 'ExitInsertMode') call feedkeys('a', 'x!') call assert_equal(1, g:triggered) au! CursorHoldI @@ -40,7 +42,7 @@ if has('timers') let g:triggered = 0 au CursorHoldI * let g:triggered += 1 set updatetime=20 - call timer_start(100, 'ExitInsertMode') + call timer_start(LoadAdjust(100), 'ExitInsertMode') " CursorHoldI does not trigger after CTRL-X call feedkeys("a\<C-X>", 'x!') call assert_equal(0, g:triggered) diff --git a/src/nvim/testdir/test_findfile.vim b/src/nvim/testdir/test_findfile.vim index d9a89801ea..78e51ed836 100644 --- a/src/nvim/testdir/test_findfile.vim +++ b/src/nvim/testdir/test_findfile.vim @@ -1,25 +1,169 @@ -" Test for findfile() -" +" Test findfile() and finddir() + +let s:files = [ 'Xdir1/foo', + \ 'Xdir1/bar', + \ 'Xdir1/Xdir2/foo', + \ 'Xdir1/Xdir2/foobar', + \ 'Xdir1/Xdir2/Xdir3/bar', + \ 'Xdir1/Xdir2/Xdir3/barfoo' ] + +func CreateFiles() + call mkdir('Xdir1/Xdir2/Xdir3/Xdir2', 'p') + for f in s:files + call writefile([], f) + endfor +endfunc + +func CleanFiles() + " Safer to delete each file even if it's more verbose + " than doing a recursive delete('Xdir1', 'rf'). + for f in s:files + call delete(f) + endfor + + call delete('Xdir1/Xdir2/Xdir3/Xdir2', 'd') + call delete('Xdir1/Xdir2/Xdir3', 'd') + call delete('Xdir1/Xdir2', 'd') + call delete('Xdir1', 'd') +endfunc + +" Test findfile({name} [, {path} [, {count}]]) func Test_findfile() - new - let cwd=getcwd() - cd .. + let save_path = &path + let save_shellslash = &shellslash + let save_dir = getcwd() + set shellslash + call CreateFiles() + cd Xdir1 + e Xdir2/foo + + " With ,, in path, findfile() searches in current directory. + set path=,, + call assert_equal('foo', findfile('foo')) + call assert_equal('bar', findfile('bar')) + call assert_equal('', findfile('foobar')) + + " Directories should not be found (finddir() finds them). + call assert_equal('', findfile('Xdir2')) + + " With . in 'path', findfile() searches relatively to current file. + set path=. + call assert_equal('Xdir2/foo', findfile('foo')) + call assert_equal('', findfile('bar')) + call assert_equal('Xdir2/foobar', findfile('foobar')) + + " Empty {path} 2nd argument is the same as no 2nd argument. + call assert_equal('Xdir2/foo', findfile('foo', '')) + call assert_equal('', findfile('bar', '')) + + " Test with * + call assert_equal('Xdir2/foo', findfile('foo', '*')) + call assert_equal('', findfile('bar', '*')) + call assert_equal('Xdir2/Xdir3/bar', findfile('bar', '*/*')) + call assert_equal('Xdir2/Xdir3/bar', findfile('bar', 'Xdir2/*')) + call assert_equal('Xdir2/Xdir3/bar', findfile('bar', 'Xdir*/Xdir3')) + call assert_equal('Xdir2/Xdir3/bar', findfile('bar', '*2/*3')) + + " Test with ** + call assert_equal('bar', findfile('bar', '**')) + call assert_equal('Xdir2/Xdir3/bar', findfile('bar', '**/Xdir3')) + call assert_equal('Xdir2/Xdir3/bar', findfile('bar', 'Xdir2/**')) + + call assert_equal('Xdir2/Xdir3/barfoo', findfile('barfoo', '**2')) + call assert_equal('', findfile('barfoo', '**1')) + call assert_equal('Xdir2/foobar', findfile('foobar', '**1')) + + " Test with {count} 3rd argument. + call assert_equal('bar', findfile('bar', '**', 0)) + call assert_equal('bar', findfile('bar', '**', 1)) + call assert_equal('Xdir2/Xdir3/bar', findfile('bar', '**', 2)) + call assert_equal('', findfile('bar', '**', 3)) + call assert_equal(['bar', 'Xdir2/Xdir3/bar'], findfile('bar', '**', -1)) + + " Test upwards search. + cd Xdir2/Xdir3 + call assert_equal('bar', findfile('bar', ';')) + call assert_match('.*/Xdir1/Xdir2/foo', findfile('foo', ';')) + call assert_match('.*/Xdir1/Xdir2/foo', findfile('foo', ';', 1)) + call assert_match('.*/Xdir1/foo', findfile('foo', ';', 2)) + call assert_match('.*/Xdir1/foo', findfile('foo', ';', 2)) + call assert_match('.*/Xdir1/Xdir2/foo', findfile('foo', 'Xdir2;', 1)) + call assert_equal('', findfile('foo', 'Xdir2;', 2)) + + " List l should have at least 2 values (possibly more if foo file + " happens to be found upwards above Xdir1). + let l = findfile('foo', ';', -1) + call assert_match('.*/Xdir1/Xdir2/foo', l[0]) + call assert_match('.*/Xdir1/foo', l[1]) + + " Test upwards search with stop-directory. + cd Xdir2 + let l = findfile('bar', ';' . save_dir . '/Xdir1/Xdir2/', -1) + call assert_equal(1, len(l)) + call assert_match('.*/Xdir1/Xdir2/Xdir3/bar', l[0]) + + let l = findfile('bar', ';' . save_dir . '/Xdir1/', -1) + call assert_equal(2, len(l)) + call assert_match('.*/Xdir1/Xdir2/Xdir3/bar', l[0]) + call assert_match('.*/Xdir1/bar', l[1]) + + " Test combined downwards and upwards search from Xdir2/. + cd ../.. + call assert_equal('Xdir3/bar', findfile('bar', '**;', 1)) + call assert_match('.*/Xdir1/bar', findfile('bar', '**;', 2)) + + bwipe! + exe 'cd ' . save_dir + call CleanFiles() + let &path = save_path + let &shellslash = save_shellslash +endfunc + +" Test finddir({name} [, {path} [, {count}]]) +func Test_finddir() + let save_path = &path + let save_shellslash = &shellslash + let save_dir = getcwd() + set path=,, + call CreateFiles() + cd Xdir1 + + call assert_equal('Xdir2', finddir('Xdir2')) + call assert_equal('', finddir('Xdir3')) + + " Files should not be found (findfile() finds them). + call assert_equal('', finddir('foo')) + + call assert_equal('Xdir2', finddir('Xdir2', '**')) + call assert_equal('Xdir2/Xdir3', finddir('Xdir3', '**')) + + call assert_equal('Xdir2', finddir('Xdir2', '**', 1)) + call assert_equal('Xdir2/Xdir3/Xdir2', finddir('Xdir2', '**', 2)) + call assert_equal(['Xdir2', + \ 'Xdir2/Xdir3/Xdir2'], finddir('Xdir2', '**', -1)) + + call assert_equal('Xdir2', finddir('Xdir2', '**1')) + call assert_equal('Xdir2', finddir('Xdir2', '**0')) + call assert_equal('Xdir2/Xdir3', finddir('Xdir3', '**1')) + call assert_equal('', finddir('Xdir3', '**0')) + + " Test upwards dir search. + cd Xdir2/Xdir3 + call assert_match('.*/Xdir1', finddir('Xdir1', ';')) + + " Test upwards search with stop-directory. + call assert_match('.*/Xdir1', finddir('Xdir1', ';' . save_dir . '/')) + call assert_equal('', finddir('Xdir1', ';' . save_dir . '/Xdir1/')) - " Tests may be run from a shadow directory, so an extra cd needs to be done to - " get above src/ - if fnamemodify(getcwd(), ':t') != 'src' - cd ../.. - else - cd .. - endif - set ssl - - call assert_equal('src/nvim/testdir/test_findfile.vim', findfile('test_findfile.vim','src/nvim/test*')) - exe "cd" cwd + " Test combined downwards and upwards dir search from Xdir2/. cd .. - call assert_equal('testdir/test_findfile.vim', findfile('test_findfile.vim','test*')) - call assert_equal('testdir/test_findfile.vim', findfile('test_findfile.vim','testdir')) + call assert_match('.*/Xdir1', finddir('Xdir1', '**;', 1)) + call assert_equal('Xdir3/Xdir2', finddir('Xdir2', '**;', 1)) + call assert_match('.*/Xdir1/Xdir2', finddir('Xdir2', '**;', 2)) + call assert_equal('Xdir3', finddir('Xdir3', '**;', 1)) - exe "cd" cwd - q! + exe 'cd ' . save_dir + call CleanFiles() + let &path = save_path + let &shellslash = save_shellslash endfunc diff --git a/src/nvim/testdir/test_help_tagjump.vim b/src/nvim/testdir/test_help_tagjump.vim index c873487b92..a6494c531c 100644 --- a/src/nvim/testdir/test_help_tagjump.vim +++ b/src/nvim/testdir/test_help_tagjump.vim @@ -28,9 +28,9 @@ func Test_help_tagjump() call assert_true(getline('.') =~ '\*quotestar\*') helpclose - help sp?it + help ch?ckhealth call assert_equal("help", &filetype) - call assert_true(getline('.') =~ '\*'.(has('win32') ? 'split()' : ':split').'\*') + call assert_true(getline('.') =~ '\*:checkhealth\*') helpclose help :? diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim index 6e07c874b4..ada25da4a8 100644 --- a/src/nvim/testdir/test_lambda.vim +++ b/src/nvim/testdir/test_lambda.vim @@ -23,6 +23,8 @@ function! Test_lambda_with_timer() return endif + source load.vim + let s:n = 0 let s:timer_id = 0 function! s:Foo() @@ -31,15 +33,19 @@ function! Test_lambda_with_timer() endfunction call s:Foo() - sleep 210ms + sleep 210m " do not collect lambda call test_garbagecollect_now() - let m = s:n - sleep 230ms + let m = LoadAdjust(s:n) + sleep 230m call timer_stop(s:timer_id) + + let n = LoadAdjust(s:n) + let nine = LoadAdjust(9) + call assert_true(m > 1) - call assert_true(s:n > m + 1) - call assert_true(s:n < 9) + call assert_true(n > m + 1) + call assert_true(n < nine) endfunction function! Test_lambda_with_partial() diff --git a/src/nvim/testdir/test_listchars.vim b/src/nvim/testdir/test_listchars.vim index 57ea7ca5a9..4899f59910 100644 --- a/src/nvim/testdir/test_listchars.vim +++ b/src/nvim/testdir/test_listchars.vim @@ -42,6 +42,38 @@ func Test_listchars() call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) endfor + " tab with 3rd character. + set listchars-=tab:>- + set listchars+=tab:<=>,trail:- + let expected = [ + \ '<======>aa<====>$', + \ '..bb<==>--$', + \ '...cccc>-$', + \ 'dd........ee--<>$', + \ '-$' + \ ] + redraw! + for i in range(1, 5) + call cursor(i, 1) + call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) + endfor + + set listchars-=trail:- + let expected = [ + \ '<======>aa<====>$', + \ '..bb<==>..$', + \ '...cccc>.$', + \ 'dd........ee..<>$', + \ '.$' + \ ] + redraw! + for i in range(1, 5) + call cursor(i, 1) + call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) + endfor + + set listchars-=tab:<=> + set listchars+=tab:>- set listchars+=trail:< set nolist normal ggdG diff --git a/src/nvim/testdir/test_menu.vim b/src/nvim/testdir/test_menu.vim index af18760065..055d944b15 100644 --- a/src/nvim/testdir/test_menu.vim +++ b/src/nvim/testdir/test_menu.vim @@ -1,9 +1,32 @@ " Test that the system menu can be loaded. +if !has('menu') + finish +endif + func Test_load_menu() try source $VIMRUNTIME/menu.vim catch call assert_report('error while loading menus: ' . v:exception) endtry + call assert_match('browse confirm w', execute(':menu File.Save')) + source $VIMRUNTIME/delmenu.vim +endfunc + +func Test_translate_menu() + if !has('multi_lang') + return + endif + if !filereadable($VIMRUNTIME . '/lang/menu_de_de.latin1.vim') + throw 'Skipped: translated menu not found' + endif + + " First delete any English menus. + source $VIMRUNTIME/delmenu.vim + set langmenu=de_de + source $VIMRUNTIME/menu.vim + call assert_match('browse confirm w', execute(':menu Datei.Speichern')) + + source $VIMRUNTIME/delmenu.vim endfunc diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim index 9ba264deb6..c2e7bb7bf9 100644 --- a/src/nvim/testdir/test_mksession.vim +++ b/src/nvim/testdir/test_mksession.vim @@ -238,4 +238,18 @@ func Test_mkview_no_file_name() %bwipe endfunc +func Test_mksession_quote_in_filename() + let v:errmsg = '' + %bwipe! + split another + split x'y\"z + mksession! Xtest_mks_quoted.out + %bwipe! + source Xtest_mks_quoted.out + call assert_true(bufexists("x'y\"z")) + + %bwipe! + call delete('Xtest_mks_quoted.out') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index cdfdd473b9..62ddad5dce 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -5,6 +5,7 @@ if !has('timers') endif source shared.vim +source load.vim func MyHandler(timer) let g:val += 1 @@ -14,13 +15,17 @@ func MyHandlerWithLists(lists, timer) let x = string(a:lists) endfunc +func s:assert_inrange(lower, upper, actual) + return assert_inrange(a:lower, LoadAdjust(a:upper), a:actual) +endfunc + func Test_oneshot() let g:val = 0 let timer = timer_start(50, 'MyHandler') let slept = WaitFor('g:val == 1') call assert_equal(1, g:val) if has('reltime') - call assert_inrange(40, 120, slept) + call s:assert_inrange(40, 120, slept) else call assert_inrange(20, 120, slept) endif @@ -32,7 +37,7 @@ func Test_repeat_three() let slept = WaitFor('g:val == 3') call assert_equal(3, g:val) if has('reltime') - call assert_inrange(120, 250, slept) + call s:assert_inrange(120, 250, slept) else call assert_inrange(80, 200, slept) endif @@ -58,7 +63,7 @@ func Test_with_partial_callback() let slept = WaitFor('g:val == 1') call assert_equal(1, g:val) if has('reltime') - call assert_inrange(40, 130, slept) + call s:assert_inrange(40, 130, slept) else call assert_inrange(20, 100, slept) endif @@ -121,7 +126,7 @@ func Test_paused() let slept = WaitFor('g:val == 1') call assert_equal(1, g:val) if has('reltime') - call assert_inrange(0, 140, slept) + call s:assert_inrange(0, 140, slept) else call assert_inrange(0, 10, slept) endif diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index dbffabd707..0fc153e8ce 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -240,6 +240,8 @@ static void terminfo_start(UI *ui) const char *vte_version_env = os_getenv("VTE_VERSION"); long vtev = vte_version_env ? strtol(vte_version_env, NULL, 10) : 0; bool iterm_env = termprg && strstr(termprg, "iTerm.app"); + bool nsterm = (termprg && strstr(termprg, "Apple_Terminal")) + || terminfo_is_term_family(term, "nsterm"); bool konsole = terminfo_is_term_family(term, "konsole") || os_getenv("KONSOLE_PROFILE_NAME") || os_getenv("KONSOLE_DBUS_SESSION"); @@ -247,8 +249,8 @@ static void terminfo_start(UI *ui) long konsolev = konsolev_env ? strtol(konsolev_env, NULL, 10) : (konsole ? 1 : 0); - patch_terminfo_bugs(data, term, colorterm, vtev, konsolev, iterm_env); - augment_terminfo(data, term, colorterm, vtev, konsolev, iterm_env); + patch_terminfo_bugs(data, term, colorterm, vtev, konsolev, iterm_env, nsterm); + augment_terminfo(data, term, colorterm, vtev, konsolev, iterm_env, nsterm); data->can_change_scroll_region = !!unibi_get_str(data->ut, unibi_change_scroll_region); data->can_set_lr_margin = @@ -1277,7 +1279,7 @@ static void tui_option_set(UI *ui, String name, Object value) static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol, Integer endcol, Integer clearcol, Integer clearattr, - Boolean wrap, const schar_T *chunk, + LineFlags flags, const schar_T *chunk, const sattr_T *attrs) { TUIData *data = ui->data; @@ -1299,7 +1301,8 @@ static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol, (int)clearattr); } - if (wrap && ui->width == grid->width && linerow + 1 < grid->height) { + if (flags & kLineFlagWrap && ui->width == grid->width + && linerow + 1 < grid->height) { // Only do line wrapping if the grid width is equal to the terminal // width and the line continuation is within the grid. @@ -1490,7 +1493,7 @@ static int unibi_find_ext_bool(unibi_term *ut, const char *name) /// and several terminal emulators falsely announce incorrect terminal types. static void patch_terminfo_bugs(TUIData *data, const char *term, const char *colorterm, long vte_version, - long konsolev, bool iterm_env) + long konsolev, bool iterm_env, bool nsterm) { unibi_term *ut = data->ut; const char *xterm_version = os_getenv("XTERM_VERSION"); @@ -1499,7 +1502,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, #endif bool xterm = terminfo_is_term_family(term, "xterm") // Treat Terminal.app as generic xterm-like, for now. - || terminfo_is_term_family(term, "nsterm"); + || nsterm; bool kitty = terminfo_is_term_family(term, "xterm-kitty"); bool linuxvt = terminfo_is_term_family(term, "linux"); bool bsdvt = terminfo_is_bsd_console(term); @@ -1518,7 +1521,6 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, bool alacritty = terminfo_is_term_family(term, "alacritty"); // None of the following work over SSH; see :help TERM . bool iterm_pretending_xterm = xterm && iterm_env; - bool konsole_pretending_xterm = xterm && konsolev; bool gnome_pretending_xterm = xterm && colorterm && strstr(colorterm, "gnome-terminal"); bool mate_pretending_xterm = xterm && colorterm @@ -1572,11 +1574,12 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, // claim to be xterm. Or they would mimic xterm properly enough to be // treatable as xterm. - // 2017-04 terminfo.src lacks these. genuine Xterm has them, as have - // the false claimants. + // 2017-04 terminfo.src lacks these. Xterm-likes have them. unibi_set_if_empty(ut, unibi_to_status_line, "\x1b]0;"); unibi_set_if_empty(ut, unibi_from_status_line, "\x07"); unibi_set_if_empty(ut, unibi_set_tb_margin, "\x1b[%i%p1%d;%p2%dr"); + unibi_set_if_empty(ut, unibi_enter_italics_mode, "\x1b[3m"); + unibi_set_if_empty(ut, unibi_exit_italics_mode, "\x1b[23m"); if (true_xterm) { // 2017-04 terminfo.src lacks these. genuine Xterm has them. @@ -1584,15 +1587,6 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, unibi_set_if_empty(ut, unibi_set_left_margin_parm, "\x1b[%i%p1%ds"); unibi_set_if_empty(ut, unibi_set_right_margin_parm, "\x1b[%i;%p2%ds"); } - if (true_xterm - || iterm_pretending_xterm - || gnome_pretending_xterm - || konsole_pretending_xterm) { - // Apple's outdated copy of terminfo.src for MacOS lacks these. - // genuine Xterm and three false claimants have them. - unibi_set_if_empty(ut, unibi_enter_italics_mode, "\x1b[3m"); - unibi_set_if_empty(ut, unibi_exit_italics_mode, "\x1b[23m"); - } } else if (rxvt) { // 2017-04 terminfo.src lacks these. Unicode rxvt has them. unibi_set_if_empty(ut, unibi_enter_italics_mode, "\x1b[3m"); @@ -1609,11 +1603,12 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, } else if (tmux) { unibi_set_if_empty(ut, unibi_to_status_line, "\x1b_"); unibi_set_if_empty(ut, unibi_from_status_line, "\x1b\\"); + unibi_set_if_empty(ut, unibi_enter_italics_mode, "\x1b[3m"); + unibi_set_if_empty(ut, unibi_exit_italics_mode, "\x1b[23m"); } else if (terminfo_is_term_family(term, "interix")) { // 2017-04 terminfo.src lacks this. unibi_set_if_empty(ut, unibi_carriage_return, "\x0d"); } else if (linuxvt) { - // Apple's outdated copy of terminfo.src for MacOS lacks these. unibi_set_if_empty(ut, unibi_parm_up_cursor, "\x1b[%p1%dA"); unibi_set_if_empty(ut, unibi_parm_down_cursor, "\x1b[%p1%dB"); unibi_set_if_empty(ut, unibi_parm_right_cursor, "\x1b[%p1%dC"); @@ -1781,12 +1776,12 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, /// capabilities. static void augment_terminfo(TUIData *data, const char *term, const char *colorterm, long vte_version, - long konsolev, bool iterm_env) + long konsolev, bool iterm_env, bool nsterm) { unibi_term *ut = data->ut; bool xterm = terminfo_is_term_family(term, "xterm") // Treat Terminal.app as generic xterm-like, for now. - || terminfo_is_term_family(term, "nsterm"); + || nsterm; bool bsdvt = terminfo_is_bsd_console(term); bool dtterm = terminfo_is_term_family(term, "dtterm"); bool rxvt = terminfo_is_term_family(term, "rxvt"); diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 96232ab223..16370f2d10 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -33,6 +33,7 @@ #include "nvim/popupmnu.h" #include "nvim/screen.h" #include "nvim/highlight.h" +#include "nvim/ui_compositor.h" #include "nvim/window.h" #include "nvim/cursor_shape.h" #ifdef FEAT_TUI @@ -60,11 +61,11 @@ static bool pending_mode_update = false; static handle_T cursor_grid_handle = DEFAULT_GRID_HANDLE; #if MIN_LOG_LEVEL > DEBUG_LOG_LEVEL -# define UI_LOG(funname, ...) +# define UI_LOG(funname) #else static size_t uilog_seen = 0; static char uilog_last_event[1024] = { 0 }; -# define UI_LOG(funname, ...) \ +# define UI_LOG(funname) \ do { \ if (strequal(uilog_last_event, STR(funname))) { \ uilog_seen++; \ @@ -80,42 +81,34 @@ static char uilog_last_event[1024] = { 0 }; } while (0) #endif -// UI_CALL invokes a function on all registered UI instances. The functions can -// have 0-10 arguments (configurable by SELECT_NTH). -// -// See http://stackoverflow.com/a/11172679 for how it works. -#ifdef _MSC_VER -# define UI_CALL(funname, ...) \ - do { \ - UI_LOG(funname, 0); \ - for (size_t i = 0; i < ui_count; i++) { \ - UI *ui = uis[i]; \ - UI_CALL_MORE(funname, __VA_ARGS__); \ - } \ - } while (0) -#else -# define UI_CALL(...) \ - do { \ - UI_LOG(__VA_ARGS__, 0); \ - for (size_t i = 0; i < ui_count; i++) { \ - UI *ui = uis[i]; \ - UI_CALL_HELPER(CNT(__VA_ARGS__), __VA_ARGS__); \ +// UI_CALL invokes a function on all registered UI instances. +// This is called by code generated by generators/gen_api_ui_events.lua +// C code should use ui_call_{funname} instead. +# define UI_CALL(cond, funname, ...) \ + do { \ + bool any_call = false; \ + for (size_t i = 0; i < ui_count; i++) { \ + UI *ui = uis[i]; \ + if (ui->funname && (cond)) { \ + ui->funname(__VA_ARGS__); \ + any_call = true; \ } \ - } while (0) -#endif -#define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, MORE, MORE, \ - MORE, MORE, MORE, MORE, ZERO, ignore) -#define SELECT_NTH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, ...) a11 -#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__) -#define UI_CALL_MORE(method, ...) if (ui->method) ui->method(ui, __VA_ARGS__) -#define UI_CALL_ZERO(method) if (ui->method) ui->method(ui) + } \ + if (any_call) { \ + UI_LOG(funname); \ + } \ + } while (0) #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ui_events_call.generated.h" #endif +void ui_init(void) +{ + default_grid.handle = 1; + ui_comp_init(); +} + void ui_builtin_start(void) { #ifdef FEAT_TUI @@ -135,17 +128,12 @@ void ui_builtin_start(void) #endif } -void ui_builtin_stop(void) -{ - UI_CALL(stop); -} - bool ui_rgb_attached(void) { if (!headless_mode && p_tgc) { return true; } - for (size_t i = 0; i < ui_count; i++) { + for (size_t i = 1; i < ui_count; i++) { if (uis[i]->rgb) { return true; } @@ -155,13 +143,13 @@ bool ui_rgb_attached(void) bool ui_active(void) { - return ui_count != 0; + return ui_count > 1; } void ui_event(char *name, Array args) { bool args_consumed = false; - UI_CALL(event, name, args, &args_consumed); + ui_call_event(name, args, &args_consumed); if (!args_consumed) { api_free_array(args); } @@ -212,6 +200,10 @@ void ui_refresh(void) screen_resize(width, height); p_lz = save_p_lz; + if (ext_widgets[kUIMessages]) { + p_ch = 0; + command_height(); + } ui_mode_info_set(); pending_mode_update = true; ui_cursor_shape(); @@ -257,6 +249,9 @@ void ui_attach_impl(UI *ui) if (ui_count == MAX_UI_COUNT) { abort(); } + if (!ui->ui_ext[kUIMultigrid]) { + ui_comp_attach(ui); + } uis[ui_count++] = ui; ui_refresh_options(); @@ -303,6 +298,10 @@ void ui_detach_impl(UI *ui) && !exiting) { ui_schedule_refresh(); } + + if (!ui->ui_ext[kUIMultigrid]) { + ui_comp_detach(ui); + } } void ui_set_ext_option(UI *ui, UIExtension ext, bool active) @@ -315,16 +314,25 @@ void ui_set_ext_option(UI *ui, UIExtension ext, bool active) ui->option_set(ui, cstr_as_string((char *)ui_ext_names[ext]), BOOLEAN_OBJ(active)); } + if (ext == kUITermColors) { + ui_default_colors_set(); + } } void ui_line(ScreenGrid *grid, int row, int startcol, int endcol, int clearcol, int clearattr, bool wrap) { + LineFlags flags = wrap ? kLineFlagWrap : 0; + if (startcol == -1) { + startcol = 0; + flags |= kLineFlagInvalid; + } + size_t off = grid->line_offset[row] + (size_t)startcol; - UI_CALL(raw_line, grid->handle, row, startcol, endcol, clearcol, clearattr, - wrap, (const schar_T *)grid->chars + off, - (const sattr_T *)grid->attrs + off); + ui_call_raw_line(grid->handle, row, startcol, endcol, clearcol, clearattr, + flags, (const schar_T *)grid->chars + off, + (const sattr_T *)grid->attrs + off); if (p_wd) { // 'writedelay': flush & delay each time. int old_row = cursor_row, old_col = cursor_col; @@ -376,6 +384,8 @@ void ui_flush(void) { cmdline_ui_flush(); win_ui_flush(); + msg_ext_ui_flush(); + if (pending_cursor_update) { ui_call_grid_cursor_goto(cursor_grid_handle, cursor_row, cursor_col); pending_cursor_update = false; @@ -412,16 +422,16 @@ void ui_cursor_shape(void) conceal_check_cursor_line(); } -/// Returns true if `widget` is externalized. -bool ui_is_external(UIExtension widget) +/// Returns true if the given UI extension is enabled. +bool ui_has(UIExtension ext) { - return ui_ext[widget]; + return ui_ext[ext]; } Array ui_array(void) { Array all_uis = ARRAY_DICT_INIT; - for (size_t i = 0; i < ui_count; i++) { + for (size_t i = 1; i < ui_count; i++) { UI *ui = uis[i]; Dictionary info = ARRAY_DICT_INIT; PUT(info, "width", INTEGER_OBJ(ui->width)); @@ -452,7 +462,8 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error) return; } - wp->w_grid.requested_rows = (int)height; - wp->w_grid.requested_cols = (int)width; - redraw_win_later(wp, SOME_VALID); + // non-positive indicates no request + wp->w_height_request = (int)MAX(height, 0); + wp->w_width_request = (int)MAX(width, 0); + win_set_inner_size(wp); } diff --git a/src/nvim/ui.h b/src/nvim/ui.h index 16237214cb..490cc930b1 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -14,10 +14,12 @@ typedef enum { kUIPopupmenu, kUITabline, kUIWildmenu, -#define kUIGlobalCount (kUIWildmenu+1) + kUIMessages, +#define kUIGlobalCount kUILinegrid kUILinegrid, kUIMultigrid, kUIHlState, + kUITermColors, kUIExtCount, } UIExtension; @@ -26,16 +28,26 @@ EXTERN const char *ui_ext_names[] INIT(= { "ext_popupmenu", "ext_tabline", "ext_wildmenu", + "ext_messages", "ext_linegrid", "ext_multigrid", "ext_hlstate", + "ext_termcolors", }); typedef struct ui_t UI; +enum { + kLineFlagWrap = 1, + kLineFlagInvalid = 2, +}; + +typedef int LineFlags; + struct ui_t { bool rgb; + bool composed; bool ui_ext[kUIExtCount]; ///< Externalized widgets int width, height; void *data; @@ -44,14 +56,6 @@ struct ui_t { # 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, - Boolean wrap, 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 bd5d37be73..91cd458702 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -153,14 +153,14 @@ 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]), - PTR2INT(argv[7]), argv[8], argv[9]); + (LineFlags)PTR2INT(argv[7]), argv[8], argv[9]); xfree(argv[8]); xfree(argv[9]); } static void ui_bridge_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol, Integer clearcol, Integer clearattr, - Boolean wrap, const schar_T *chunk, + LineFlags flags, const schar_T *chunk, const sattr_T *attrs) { size_t ncol = (size_t)(endcol-startcol); @@ -168,7 +168,7 @@ static void ui_bridge_raw_line(UI *ui, Integer grid, Integer row, sattr_T *hl = xmemdup(attrs, ncol * sizeof(sattr_T)); UI_BRIDGE_CALL(ui, raw_line, 10, ui, INT2PTR(grid), INT2PTR(row), INT2PTR(startcol), INT2PTR(endcol), INT2PTR(clearcol), - INT2PTR(clearattr), INT2PTR(wrap), c, hl); + INT2PTR(clearattr), INT2PTR(flags), c, hl); } static void ui_bridge_suspend(UI *b) diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c new file mode 100644 index 0000000000..c7ba0306e4 --- /dev/null +++ b/src/nvim/ui_compositor.c @@ -0,0 +1,442 @@ +// 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 + +// Compositor: merge floating grids with the main grid for display in +// TUI and non-multigrid UIs. + +#include <assert.h> +#include <stdbool.h> +#include <stdio.h> +#include <limits.h> + +#include "nvim/lib/kvec.h" +#include "nvim/log.h" +#include "nvim/main.h" +#include "nvim/ascii.h" +#include "nvim/vim.h" +#include "nvim/ui.h" +#include "nvim/highlight.h" +#include "nvim/memory.h" +#include "nvim/ui_compositor.h" +#include "nvim/ugrid.h" +#include "nvim/screen.h" +#include "nvim/syntax.h" +#include "nvim/api/private/helpers.h" +#include "nvim/os/os.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "ui_compositor.c.generated.h" +#endif + +static UI *compositor = NULL; +static int composed_uis = 0; +kvec_t(ScreenGrid *) layers = KV_INITIAL_VALUE; + +static size_t bufsize = 0; +static schar_T *linebuf; +static sattr_T *attrbuf; + +#ifndef NDEBUG +static int chk_width = 0, chk_height = 0; +#endif + +static ScreenGrid *curgrid; + +static bool valid_screen = true; +static bool msg_scroll_mode = false; +static int msg_first_invalid = 0; + +void ui_comp_init(void) +{ + if (compositor != NULL) { + return; + } + compositor = xcalloc(1, sizeof(UI)); + + compositor->rgb = true; + compositor->grid_resize = ui_comp_grid_resize; + compositor->grid_clear = ui_comp_grid_clear; + compositor->grid_scroll = ui_comp_grid_scroll; + compositor->grid_cursor_goto = ui_comp_grid_cursor_goto; + compositor->raw_line = ui_comp_raw_line; + compositor->win_scroll_over_start = ui_comp_win_scroll_over_start; + compositor->win_scroll_over_reset = ui_comp_win_scroll_over_reset; + + // Be unopinionated: will be attached together with a "real" ui anyway + compositor->width = INT_MAX; + compositor->height = INT_MAX; + for (UIExtension i = 0; (int)i < kUIExtCount; i++) { + compositor->ui_ext[i] = true; + } + + // TODO(bfredl): this will be more complicated if we implement + // hlstate per UI (i e reduce hl ids for non-hlstate UIs) + compositor->ui_ext[kUIHlState] = false; + + kv_push(layers, &default_grid); + curgrid = &default_grid; + + ui_attach_impl(compositor); +} + + +void ui_comp_attach(UI *ui) +{ + composed_uis++; + ui->composed = true; +} + +void ui_comp_detach(UI *ui) +{ + composed_uis--; + if (composed_uis == 0) { + xfree(linebuf); + xfree(attrbuf); + linebuf = NULL; + attrbuf = NULL; + bufsize = 0; + } + ui->composed = false; +} + +bool ui_comp_should_draw(void) +{ + return composed_uis != 0 && valid_screen; +} + +/// TODO(bfredl): later on the compositor should just use win_float_pos events, +/// though that will require slight event order adjustment: emit the win_pos +/// events in the beginning of update_screen(0), rather than in ui_flush() +bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width) +{ + if (grid->comp_index != 0) { + bool moved = (row != grid->comp_row) || (col != grid->comp_col); + if (ui_comp_should_draw()) { + // Redraw the area covered by the old position, and is not covered + // by the new position. Disable the grid so that compose_area() will not + // use it. + grid->comp_disabled = true; + compose_area(grid->comp_row, row, + grid->comp_col, grid->comp_col + grid->Columns); + if (grid->comp_col < col) { + compose_area(MAX(row, grid->comp_row), + MIN(row+height, grid->comp_row+grid->Rows), + grid->comp_col, col); + } + if (col+width < grid->comp_col+grid->Columns) { + compose_area(MAX(row, grid->comp_row), + MIN(row+height, grid->comp_row+grid->Rows), + col+width, grid->comp_col+grid->Columns); + } + compose_area(row+height, grid->comp_row+grid->Rows, + grid->comp_col, grid->comp_col + grid->Columns); + grid->comp_disabled = false; + } + grid->comp_row = row; + grid->comp_col = col; + return moved; + } +#ifndef NDEBUG + for (size_t i = 0; i < kv_size(layers); i++) { + if (kv_A(layers, i) == grid) { + assert(false); + } + } +#endif + // not found: new grid + kv_push(layers, grid); + grid->comp_row = row; + grid->comp_col = col; + grid->comp_index = kv_size(layers)-1; + return true; +} + +void ui_comp_remove_grid(ScreenGrid *grid) +{ + assert(grid != &default_grid); + if (grid->comp_index == 0) { + // grid wasn't present + return; + } + + if (curgrid == grid) { + curgrid = &default_grid; + } + + for (size_t i = grid->comp_index; i < kv_size(layers)-1; i++) { + kv_A(layers, i) = kv_A(layers, i+1); + kv_A(layers, i)->comp_index = i; + } + (void)kv_pop(layers); + grid->comp_index = 0; + + // recompose the area under the grid + // inefficent when being overlapped: only draw up to grid->comp_index + ui_comp_compose_grid(grid); +} + +bool ui_comp_set_grid(handle_T handle) +{ + if (curgrid->handle == handle) { + return true; + } + ScreenGrid *grid = NULL; + for (size_t i = 0; i < kv_size(layers); i++) { + if (kv_A(layers, i)->handle == handle) { + grid = kv_A(layers, i); + break; + } + } + if (grid != NULL) { + curgrid = grid; + return true; + } + return false; +} + +static void ui_comp_grid_cursor_goto(UI *ui, Integer grid_handle, + Integer r, Integer c) +{ + if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid_handle)) { + return; + } + int cursor_row = curgrid->comp_row+(int)r; + int cursor_col = curgrid->comp_col+(int)c; + + if (cursor_col >= default_grid.Columns || cursor_row >= default_grid.Rows) { + // TODO(bfredl): this happens with 'writedelay', refactor? + // abort(); + return; + } + ui_composed_call_grid_cursor_goto(1, cursor_row, cursor_col); +} + + +/// Baseline implementation. This is always correct, but we can sometimes +/// do something more efficient (where efficiency means smaller deltas to +/// the downstream UI.) +static void compose_line(Integer row, Integer startcol, Integer endcol, + LineFlags flags) +{ + // in case we start on the right half of a double-width char, we need to + // check the left half. But skip it in output if it wasn't doublewidth. + int skip = 0; + if (startcol > 0 && (flags & kLineFlagInvalid)) { + startcol--; + skip = 1; + } + + int col = (int)startcol; + ScreenGrid *grid = NULL; + schar_T *bg_line = &default_grid.chars[default_grid.line_offset[row] + +(size_t)startcol]; + sattr_T *bg_attrs = &default_grid.attrs[default_grid.line_offset[row] + +(size_t)startcol]; + + while (col < endcol) { + int until = 0; + for (size_t i = 0; i < kv_size(layers); i++) { + ScreenGrid *g = kv_A(layers, i); + if (g->comp_row > row || row >= g->comp_row + g->Rows + || g->comp_disabled) { + continue; + } + if (g->comp_col <= col && col < g->comp_col+g->Columns) { + grid = g; + until = g->comp_col+g->Columns; + } else if (g->comp_col > col) { + until = MIN(until, g->comp_col); + } + } + until = MIN(until, (int)endcol); + + assert(grid != NULL); + assert(until > col); + assert(until <= default_grid.Columns); + size_t n = (size_t)(until-col); + size_t off = grid->line_offset[row-grid->comp_row] + + (size_t)(col-grid->comp_col); + memcpy(linebuf+(col-startcol), grid->chars+off, n * sizeof(*linebuf)); + memcpy(attrbuf+(col-startcol), grid->attrs+off, n * sizeof(*attrbuf)); + + // 'pumblend' + if (grid != &default_grid && p_pb) { + for (int i = col-(int)startcol; i < until-startcol; i++) { + bool thru = strequal((char *)linebuf[i], " "); // negative space + attrbuf[i] = (sattr_T)hl_blend_attrs(bg_attrs[i], attrbuf[i], thru); + if (thru) { + memcpy(linebuf[i], bg_line[i], sizeof(linebuf[i])); + } + } + } + + // Tricky: if overlap caused a doublewidth char to get cut-off, must + // replace the visible half with a space. + if (linebuf[col-startcol][0] == NUL) { + linebuf[col-startcol][0] = ' '; + linebuf[col-startcol][1] = NUL; + } else if (n > 1 && linebuf[col-startcol+1][0] == NUL) { + skip = 0; + } + if (grid->comp_col+grid->Columns > until + && grid->chars[off+n][0] == NUL) { + linebuf[until-1-startcol][0] = ' '; + linebuf[until-1-startcol][1] = '\0'; + if (col == startcol && n == 1) { + skip = 0; + } + } + + col = until; + } + assert(endcol <= chk_width); + assert(row < chk_height); + + if (!(grid && grid == &default_grid)) { + // TODO(bfredl): too conservative, need check + // grid->line_wraps if grid->Width == Width + flags = flags & ~kLineFlagWrap; + } + + ui_composed_call_raw_line(1, row, startcol+skip, endcol, endcol, 0, flags, + (const schar_T *)linebuf+skip, + (const sattr_T *)attrbuf+skip); +} + +static void compose_area(Integer startrow, Integer endrow, + Integer startcol, Integer endcol) +{ + endrow = MIN(endrow, default_grid.Rows); + endcol = MIN(endcol, default_grid.Columns); + if (endcol <= startcol) { + return; + } + for (int r = (int)startrow; r < endrow; r++) { + compose_line(r, startcol, endcol, kLineFlagInvalid); + } +} + +/// compose the area under the grid. +/// +/// This is needed when some option affecting composition is changed, +/// such as 'pumblend' for popupmenu grid. +void ui_comp_compose_grid(ScreenGrid *grid) +{ + if (ui_comp_should_draw()) { + compose_area(grid->comp_row, grid->comp_row+grid->Rows, + grid->comp_col, grid->comp_col+grid->Columns); + } +} + +static void ui_comp_raw_line(UI *ui, Integer grid, Integer row, + Integer startcol, Integer endcol, + Integer clearcol, Integer clearattr, + LineFlags flags, const schar_T *chunk, + const sattr_T *attrs) +{ + if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid)) { + return; + } + + row += curgrid->comp_row; + startcol += curgrid->comp_col; + endcol += curgrid->comp_col; + clearcol += curgrid->comp_col; + if (curgrid != &default_grid) { + flags = flags & ~kLineFlagWrap; + } + assert(row < default_grid.Rows); + assert(clearcol <= default_grid.Columns); + if (flags & kLineFlagInvalid + || kv_size(layers) > (p_pb ? 1 : curgrid->comp_index+1)) { + compose_line(row, startcol, clearcol, flags); + } else { + ui_composed_call_raw_line(1, row, startcol, endcol, clearcol, clearattr, + flags, chunk, attrs); + } +} + +/// The screen is invalid and will soon be cleared +/// +/// Don't redraw floats until screen is cleared +void ui_comp_invalidate_screen(void) +{ + valid_screen = false; +} + +static void ui_comp_grid_clear(UI *ui, Integer grid) +{ + // By design, only first grid uses clearing. + assert(grid == 1); + ui_composed_call_grid_clear(1); + valid_screen = true; +} + +// TODO(bfredl): These events are somewhat of a hack. multiline messages +// should later on be a separate grid, then this would just be ordinary +// ui_comp_put_grid and ui_comp_remove_grid calls. +static void ui_comp_win_scroll_over_start(UI *ui) +{ + msg_scroll_mode = true; + msg_first_invalid = ui->height; +} + +static void ui_comp_win_scroll_over_reset(UI *ui) +{ + msg_scroll_mode = false; + for (size_t i = 1; i < kv_size(layers); i++) { + ScreenGrid *grid = kv_A(layers, i); + if (grid->comp_row+grid->Rows > msg_first_invalid) { + compose_area(msg_first_invalid, grid->comp_row+grid->Rows, + grid->comp_col, grid->comp_col+grid->Columns); + } + } +} + +static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top, + Integer bot, Integer left, Integer right, + Integer rows, Integer cols) +{ + if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid)) { + return; + } + top += curgrid->comp_row; + bot += curgrid->comp_row; + left += curgrid->comp_col; + right += curgrid->comp_col; + if (!msg_scroll_mode && kv_size(layers) > curgrid->comp_index+1) { + // TODO(bfredl): + // 1. check if rectangles actually overlap + // 2. calulate subareas that can scroll. + if (rows > 0) { + bot -= rows; + } else { + top += (-rows); + } + compose_area(top, bot, left, right); + } else { + msg_first_invalid = MIN(msg_first_invalid, (int)top); + ui_composed_call_grid_scroll(1, top, bot, left, right, rows, cols); + } +} + +static void ui_comp_grid_resize(UI *ui, Integer grid, + Integer width, Integer height) +{ + if (grid == 1) { + ui_composed_call_grid_resize(1, width, height); +#ifndef NDEBUG + chk_width = (int)width; + chk_height = (int)height; +#endif + size_t new_bufsize = (size_t)width; + if (bufsize != new_bufsize) { + xfree(linebuf); + xfree(attrbuf); + linebuf = xmalloc(new_bufsize * sizeof(*linebuf)); + attrbuf = xmalloc(new_bufsize * sizeof(*attrbuf)); + bufsize = new_bufsize; + } + } +} + diff --git a/src/nvim/ui_compositor.h b/src/nvim/ui_compositor.h new file mode 100644 index 0000000000..b3780db532 --- /dev/null +++ b/src/nvim/ui_compositor.h @@ -0,0 +1,12 @@ +// Bridge for communication between a UI thread and nvim core. +// Used by the built-in TUI and libnvim-based UIs. +#ifndef NVIM_UI_COMPOSITOR_H +#define NVIM_UI_COMPOSITOR_H + +#include "nvim/ui.h" +#include "nvim/event/defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "ui_compositor.h.generated.h" +#endif +#endif // NVIM_UI_COMPOSITOR_H diff --git a/src/nvim/version.c b/src/nvim/version.c index 20b71ab724..b7c9140b7f 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -62,12 +62,6 @@ static char *features[] = { "-iconv", #endif -#ifdef HAVE_JEMALLOC -"+jemalloc", -#else -"-jemalloc", -#endif - #ifdef FEAT_TUI "+tui", #else diff --git a/src/nvim/window.c b/src/nvim/window.c index 3cadfe612a..24fe529fd6 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1300,6 +1300,7 @@ static void win_rotate(int upwards, int count) if (upwards) { /* first window becomes last window */ /* remove first window/frame from the list */ frp = curwin->w_frame->fr_parent->fr_child; + assert(frp != NULL); wp1 = frp->fr_win; win_remove(wp1, NULL); frame_remove(frp); @@ -3025,8 +3026,10 @@ static void new_frame(win_T *wp) void win_init_size(void) { firstwin->w_height = ROWS_AVAIL; + firstwin->w_height_inner = firstwin->w_height; topframe->fr_height = ROWS_AVAIL; firstwin->w_width = Columns; + firstwin->w_width_inner = firstwin->w_width; topframe->fr_width = Columns; } @@ -3118,7 +3121,7 @@ int win_new_tabpage(int after, char_u *filename) redraw_all_later(NOT_VALID); - if (ui_is_external(kUIMultigrid)) { + if (ui_has(kUIMultigrid)) { tabpage_check_windows(tp); } @@ -3319,7 +3322,7 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, int trigger_enter_au lastwin = tp->tp_lastwin; topframe = tp->tp_topframe; - if (old_curtab != curtab && ui_is_external(kUIMultigrid)) { + if (old_curtab != curtab && ui_has(kUIMultigrid)) { tabpage_check_windows(old_curtab); } @@ -3889,7 +3892,6 @@ static win_T *win_alloc(win_T *after, int hidden) // allocate window structure and linesizes arrays win_T *new_wp = xcalloc(1, sizeof(win_T)); - win_alloc_lines(new_wp); new_wp->handle = ++last_win_id; handle_register_window(new_wp); @@ -3970,7 +3972,7 @@ win_free ( } } - win_free_lsize(wp); + xfree(wp->w_lines); for (i = 0; i < wp->w_tagstacklen; ++i) xfree(wp->w_tagstack[i].tagname); @@ -4010,7 +4012,7 @@ win_free ( void win_free_grid(win_T *wp, bool reinit) { - if (wp->w_grid.handle != 0 && ui_is_external(kUIMultigrid)) { + if (wp->w_grid.handle != 0 && ui_has(kUIMultigrid)) { ui_call_grid_destroy(wp->w_grid.handle); wp->w_grid.handle = 0; } @@ -4118,28 +4120,6 @@ static void frame_remove(frame_T *frp) /* - * Allocate w_lines[] for window "wp". - */ -void win_alloc_lines(win_T *wp) -{ - wp->w_lines_valid = 0; - assert(wp->w_grid.Rows >= 0); - wp->w_lines = xcalloc(MAX(wp->w_grid.Rows + 1, Rows), sizeof(wline_T)); -} - -/* - * free lsize arrays for a window - */ -void win_free_lsize(win_T *wp) -{ - // TODO: why would wp be NULL here? - if (wp != NULL) { - xfree(wp->w_lines); - wp->w_lines = NULL; - } -} - -/* * Called from win_new_shellsize() after Rows changed. * This only does the current tab page, others must be done when made active. */ @@ -4876,9 +4856,9 @@ void win_drag_vsep_line(win_T *dragwin, int offset) // Has no effect when the window is less than two lines. void set_fraction(win_T *wp) { - if (wp->w_height > 1) { - wp->w_fraction = ((long)wp->w_wrow * FRACTION_MULT + wp->w_height / 2) - / (long)wp->w_height; + if (wp->w_height_inner > 1) { + wp->w_fraction = ((long)wp->w_wrow * FRACTION_MULT + wp->w_height_inner / 2) + / (long)wp->w_height_inner; } } @@ -4889,46 +4869,25 @@ void set_fraction(win_T *wp) */ void win_new_height(win_T *wp, int height) { - int prev_height = wp->w_height; - - /* Don't want a negative height. Happens when splitting a tiny window. - * Will equalize heights soon to fix it. */ - if (height < 0) + // Don't want a negative height. Happens when splitting a tiny window. + // Will equalize heights soon to fix it. + if (height < 0) { height = 0; - if (wp->w_height == height) - return; /* nothing to do */ - - if (wp->w_height > 0) { - if (wp == curwin) { - // w_wrow needs to be valid. When setting 'laststatus' this may - // call win_new_height() recursively. - validate_cursor(); - } - if (wp->w_height != prev_height) { // -V547 - return; // Recursive call already changed the size, bail out. - } - if (wp->w_wrow != wp->w_prev_fraction_row) { - set_fraction(wp); - } } - - wp->w_height = height; - wp->w_skipcol = 0; - - // There is no point in adjusting the scroll position when exiting. Some - // values might be invalid. - if (!exiting) { - scroll_to_fraction(wp, prev_height); + if (wp->w_height == height) { + return; // nothing to do } + wp->w_height = height; wp->w_pos_changed = true; + win_set_inner_size(wp); } void scroll_to_fraction(win_T *wp, int prev_height) { linenr_T lnum; int sline, line_size; - int height = wp->w_height; + int height = wp->w_height_inner; /* Don't change w_topline when height is zero. Don't set w_topline when * 'scrollbind' is set and this isn't the current window. */ @@ -4951,8 +4910,8 @@ void scroll_to_fraction(win_T *wp, int prev_height) // Make sure the whole cursor line is visible, if possible. const int rows = plines_win(wp, lnum, false); - if (sline > wp->w_height - rows) { - sline = wp->w_height - rows; + if (sline > wp->w_height_inner - rows) { + sline = wp->w_height_inner - rows; wp->w_wrow -= rows - line_size; } } @@ -4964,14 +4923,14 @@ void scroll_to_fraction(win_T *wp, int prev_height) * room use w_skipcol; */ wp->w_wrow = line_size; - if (wp->w_wrow >= wp->w_height - && (wp->w_width - win_col_off(wp)) > 0) { - wp->w_skipcol += wp->w_width - win_col_off(wp); - --wp->w_wrow; - while (wp->w_wrow >= wp->w_height) { - wp->w_skipcol += wp->w_width - win_col_off(wp) + if (wp->w_wrow >= wp->w_height_inner + && (wp->w_width_inner - win_col_off(wp)) > 0) { + wp->w_skipcol += wp->w_width_inner - win_col_off(wp); + wp->w_wrow--; + while (wp->w_wrow >= wp->w_height_inner) { + wp->w_skipcol += wp->w_width_inner - win_col_off(wp) + win_col_off2(wp); - --wp->w_wrow; + wp->w_wrow--; } } set_topline(wp, lnum); @@ -5024,10 +4983,58 @@ void scroll_to_fraction(win_T *wp, int prev_height) redraw_win_later(wp, SOME_VALID); wp->w_redr_status = TRUE; invalidate_botline_win(wp); +} + +void win_set_inner_size(win_T *wp) +{ + int width = wp->w_width_request; + if (width == 0) { + width = wp->w_width; + } + + int prev_height = wp->w_height_inner; + int height = wp->w_height_request; + if (height == 0) { + height = wp->w_height; + } + + if (height != prev_height) { + if (height > 0) { + if (wp == curwin) { + // w_wrow needs to be valid. When setting 'laststatus' this may + // call win_new_height() recursively. + validate_cursor(); + } + if (wp->w_height_inner != prev_height) { // -V547 + return; // Recursive call already changed the size, bail out. + } + if (wp->w_wrow != wp->w_prev_fraction_row) { + set_fraction(wp); + } + } + wp->w_height_inner = height; + wp->w_skipcol = 0; + + // There is no point in adjusting the scroll position when exiting. Some + // values might be invalid. + if (!exiting) { + scroll_to_fraction(wp, prev_height); + } + } + + if (width != wp->w_width_inner) { + wp->w_width_inner = width; + wp->w_lines_valid = 0; + changed_line_abv_curs_win(wp); + invalidate_botline_win(wp); + if (wp == curwin) { + update_topline(); + curs_columns(true); // validate w_wrow + } + } if (wp->w_buffer->terminal) { - terminal_resize(wp->w_buffer->terminal, 0, wp->w_height); - redraw_win_later(wp, NOT_VALID); + terminal_check_size(wp->w_buffer->terminal); } } @@ -5035,23 +5042,11 @@ void scroll_to_fraction(win_T *wp, int prev_height) void win_new_width(win_T *wp, int width) { wp->w_width = width; - wp->w_lines_valid = 0; - changed_line_abv_curs_win(wp); - invalidate_botline_win(wp); - if (wp == curwin) { - update_topline(); - curs_columns(TRUE); /* validate w_wrow */ - } + win_set_inner_size(wp); + redraw_win_later(wp, NOT_VALID); wp->w_redr_status = TRUE; - if (wp->w_buffer->terminal) { - if (wp->w_height != 0) { - terminal_resize(wp->w_buffer->terminal, - (uint16_t)(MAX(0, wp->w_width - win_col_off(wp))), - 0); - } - } wp->w_pos_changed = true; } @@ -5355,7 +5350,7 @@ static void last_status_rec(frame_T *fr, int statusline) */ int tabline_height(void) { - if (ui_is_external(kUITabline)) { + if (ui_has(kUITabline)) { return 0; } assert(first_tabpage); @@ -6092,7 +6087,7 @@ void win_findbuf(typval_T *argvars, list_T *list) void win_ui_flush(void) { - if (!ui_is_external(kUIMultigrid)) { + if (!ui_has(kUIMultigrid)) { return; } |