diff options
Diffstat (limited to 'src/nvim/ui.c')
-rw-r--r-- | src/nvim/ui.c | 521 |
1 files changed, 229 insertions, 292 deletions
diff --git a/src/nvim/ui.c b/src/nvim/ui.c index d968cbc390..96232ab223 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -1,3 +1,6 @@ +// 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 <assert.h> #include <inttypes.h> #include <stdbool.h> @@ -5,17 +8,17 @@ #include <limits.h> #include "nvim/vim.h" +#include "nvim/log.h" #include "nvim/ui.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" #include "nvim/ex_cmds2.h" +#include "nvim/ex_getln.h" #include "nvim/fold.h" #include "nvim/main.h" -#include "nvim/mbyte.h" #include "nvim/ascii.h" #include "nvim/misc1.h" -#include "nvim/misc2.h" #include "nvim/mbyte.h" #include "nvim/garray.h" #include "nvim/memory.h" @@ -27,14 +30,17 @@ #include "nvim/os/time.h" #include "nvim/os/input.h" #include "nvim/os/signal.h" +#include "nvim/popupmnu.h" #include "nvim/screen.h" -#include "nvim/syntax.h" +#include "nvim/highlight.h" #include "nvim/window.h" +#include "nvim/cursor_shape.h" #ifdef FEAT_TUI # include "nvim/tui/tui.h" #else # include "nvim/msgpack_rpc/server.h" #endif +#include "nvim/api/private/helpers.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ui.c.generated.h" @@ -43,26 +49,45 @@ #define MAX_UI_COUNT 16 static UI *uis[MAX_UI_COUNT]; +static bool ui_ext[kUIExtCount] = { 0 }; static size_t ui_count = 0; -static int row = 0, col = 0; -static struct { - int top, bot, left, right; -} sr; -static int current_attr_code = 0; +static int ui_mode_idx = SHAPE_IDX_N; +static int cursor_row = 0, cursor_col = 0; static bool pending_cursor_update = false; static int busy = 0; -static int height, width; +static bool pending_mode_info_update = false; +static bool pending_mode_update = false; +static handle_T cursor_grid_handle = DEFAULT_GRID_HANDLE; -// This set of macros allow us to use UI_CALL to invoke any function on -// registered UI instances. The functions can have 0-5 arguments(configurable -// by SELECT_NTH) +#if MIN_LOG_LEVEL > DEBUG_LOG_LEVEL +# define UI_LOG(funname, ...) +#else +static size_t uilog_seen = 0; +static char uilog_last_event[1024] = { 0 }; +# define UI_LOG(funname, ...) \ + do { \ + if (strequal(uilog_last_event, STR(funname))) { \ + uilog_seen++; \ + } else { \ + if (uilog_seen > 0) { \ + logmsg(DEBUG_LOG_LEVEL, "UI: ", NULL, -1, true, \ + "%s (+%zu times...)", uilog_last_event, uilog_seen); \ + } \ + logmsg(DEBUG_LOG_LEVEL, "UI: ", NULL, -1, true, STR(funname)); \ + uilog_seen = 0; \ + xstrlcpy(uilog_last_event, STR(funname), sizeof(uilog_last_event)); \ + } \ + } 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 a better explanation of how it -// works. +// See http://stackoverflow.com/a/11172679 for how it works. #ifdef _MSC_VER # define UI_CALL(funname, ...) \ do { \ - flush_cursor_update(); \ + UI_LOG(funname, 0); \ for (size_t i = 0; i < ui_count; i++) { \ UI *ui = uis[i]; \ UI_CALL_MORE(funname, __VA_ARGS__); \ @@ -71,37 +96,42 @@ static int height, width; #else # define UI_CALL(...) \ do { \ - flush_cursor_update(); \ + UI_LOG(__VA_ARGS__, 0); \ for (size_t i = 0; i < ui_count; i++) { \ UI *ui = uis[i]; \ UI_CALL_HELPER(CNT(__VA_ARGS__), __VA_ARGS__); \ } \ } while (0) #endif -#define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, MORE, ZERO, ignore) -#define SELECT_NTH(a1, a2, a3, a4, a5, a6, ...) a6 +#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) +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "ui_events_call.generated.h" +#endif + void ui_builtin_start(void) { #ifdef FEAT_TUI tui_start(); #else - fprintf(stderr, "Neovim was built without a Terminal UI," \ - "press Ctrl+C to exit\n"); - + fprintf(stderr, "Nvim headless-mode started.\n"); size_t len; char **addrs = server_address_list(&len); if (addrs != NULL) { - fprintf(stderr, "currently listening on the following address(es)\n"); + fprintf(stderr, "Listening on:\n"); for (size_t i = 0; i < len; i++) { fprintf(stderr, "\t%s\n", addrs[i]); } xfree(addrs); } + fprintf(stderr, "Press CTRL+C to exit.\n"); #endif } @@ -112,6 +142,9 @@ void ui_builtin_stop(void) bool ui_rgb_attached(void) { + if (!headless_mode && p_tgc) { + return true; + } for (size_t i = 0; i < ui_count; i++) { if (uis[i]->rgb) { return true; @@ -125,29 +158,15 @@ bool ui_active(void) return ui_count != 0; } -void ui_suspend(void) -{ - UI_CALL(suspend); - UI_CALL(flush); -} - -void ui_set_title(char *title) +void ui_event(char *name, Array args) { - UI_CALL(set_title, title); - UI_CALL(flush); + bool args_consumed = false; + UI_CALL(event, name, args, &args_consumed); + if (!args_consumed) { + api_free_array(args); + } } -void ui_set_icon(char *icon) -{ - UI_CALL(set_icon, icon); - UI_CALL(flush); -} - -// May update the shape of the cursor. -void ui_cursor_shape(void) -{ - ui_mode_change(); -} void ui_refresh(void) { @@ -155,56 +174,82 @@ void ui_refresh(void) return; } + if (updating_screen) { + ui_schedule_refresh(); + return; + } + int width = INT_MAX, height = INT_MAX; + bool ext_widgets[kUIExtCount]; + for (UIExtension i = 0; (int)i < kUIExtCount; i++) { + ext_widgets[i] = true; + } for (size_t i = 0; i < ui_count; i++) { UI *ui = uis[i]; - width = ui->width < width ? ui->width : width; - height = ui->height < height ? ui->height : height; + width = MIN(ui->width, width); + height = MIN(ui->height, height); + for (UIExtension j = 0; (int)j < kUIExtCount; j++) { + ext_widgets[j] &= ui->ui_ext[j]; + } } - row = col = 0; + cursor_row = cursor_col = 0; + pending_cursor_update = true; + + for (UIExtension i = 0; (int)i < kUIExtCount; i++) { + ui_ext[i] = ext_widgets[i]; + if (i < kUIGlobalCount) { + ui_call_option_set(cstr_as_string((char *)ui_ext_names[i]), + BOOLEAN_OBJ(ext_widgets[i])); + } + } + + ui_default_colors_set(); + + int save_p_lz = p_lz; + p_lz = false; // convince redrawing() to return true ... screen_resize(width, height); + p_lz = save_p_lz; + + ui_mode_info_set(); + pending_mode_update = true; + ui_cursor_shape(); } -void ui_resize(int new_width, int new_height) +static void ui_refresh_event(void **argv) { - width = new_width; - height = new_height; - - UI_CALL(update_fg, (ui->rgb ? normal_fg : cterm_normal_fg_color - 1)); - UI_CALL(update_bg, (ui->rgb ? normal_bg : cterm_normal_bg_color - 1)); - UI_CALL(update_sp, (ui->rgb ? normal_sp : -1)); - - sr.top = 0; - sr.bot = height - 1; - sr.left = 0; - sr.right = width - 1; - UI_CALL(resize, width, height); + ui_refresh(); } -void ui_busy_start(void) +void ui_schedule_refresh(void) { - if (!(busy++)) { - UI_CALL(busy_start); - } + loop_schedule(&main_loop, event_create(ui_refresh_event, 0)); } -void ui_busy_stop(void) +void ui_resize(int width, int height) { - if (!(--busy)) { - UI_CALL(busy_stop); - } + ui_call_grid_resize(1, width, height); } -void ui_mouse_on(void) +void ui_default_colors_set(void) +{ + ui_call_default_colors_set(normal_fg, normal_bg, normal_sp, + cterm_normal_fg_color, cterm_normal_bg_color); +} + +void ui_busy_start(void) { - UI_CALL(mouse_on); + if (!(busy++)) { + ui_call_busy_start(); + } } -void ui_mouse_off(void) +void ui_busy_stop(void) { - UI_CALL(mouse_off); + if (!(--busy)) { + ui_call_busy_stop(); + } } void ui_attach_impl(UI *ui) @@ -214,6 +259,19 @@ void ui_attach_impl(UI *ui) } uis[ui_count++] = ui; + ui_refresh_options(); + + for (UIExtension i = kUIGlobalCount; (int)i < kUIExtCount; i++) { + ui_set_ext_option(ui, i, ui->ui_ext[i]); + } + + bool sent = false; + if (ui->ui_ext[kUIHlState]) { + sent = highlight_use_hlstate(); + } + if (!sent) { + ui_send_all_hls(ui); + } ui_refresh(); } @@ -239,283 +297,162 @@ void ui_detach_impl(UI *ui) shift_index++; } - if (--ui_count) { - ui_refresh(); + if (--ui_count + // During teardown/exit the loop was already destroyed, cannot schedule. + // https://github.com/neovim/neovim/pull/5119#issuecomment-258667046 + && !exiting) { + ui_schedule_refresh(); } } -void ui_clear(void) -{ - UI_CALL(clear); -} - -// Set scrolling region for window 'wp'. -// The region starts 'off' lines from the start of the window. -// Also set the vertical scroll region for a vertically split window. Always -// the full width of the window, excluding the vertical separator. -void ui_set_scroll_region(win_T *wp, int off) -{ - sr.top = wp->w_winrow + off; - sr.bot = wp->w_winrow + wp->w_height - 1; - - if (wp->w_width != Columns) { - sr.left = wp->w_wincol; - sr.right = wp->w_wincol + wp->w_width - 1; - } - - UI_CALL(set_scroll_region, sr.top, sr.bot, sr.left, sr.right); -} - -// Reset scrolling region to the whole screen. -void ui_reset_scroll_region(void) -{ - sr.top = 0; - sr.bot = (int)Rows - 1; - sr.left = 0; - sr.right = (int)Columns - 1; - UI_CALL(set_scroll_region, sr.top, sr.bot, sr.left, sr.right); -} - -void ui_append_lines(int count) -{ - UI_CALL(scroll, -count); -} - -void ui_delete_lines(int count) +void ui_set_ext_option(UI *ui, UIExtension ext, bool active) { - UI_CALL(scroll, count); -} - -void ui_eol_clear(void) -{ - UI_CALL(eol_clear); -} - -void ui_start_highlight(int attr_code) -{ - current_attr_code = attr_code; - - if (!ui_count) { + if (ext < kUIGlobalCount) { + ui_refresh(); return; } - - set_highlight_args(current_attr_code); -} - -void ui_stop_highlight(void) -{ - current_attr_code = HL_NORMAL; - - if (!ui_count) { - return; + if (ui->option_set) { + ui->option_set(ui, cstr_as_string((char *)ui_ext_names[ext]), + BOOLEAN_OBJ(active)); } - - set_highlight_args(current_attr_code); } -void ui_visual_bell(void) +void ui_line(ScreenGrid *grid, int row, int startcol, int endcol, int clearcol, + int clearattr, bool wrap) { - UI_CALL(visual_bell); -} + size_t off = grid->line_offset[row] + (size_t)startcol; -void ui_puts(uint8_t *str) -{ - uint8_t *ptr = str; - uint8_t c; - - while ((c = *ptr)) { - if (c < 0x20) { - parse_control_character(c); - ptr++; - } else { - send_output(&ptr); - } + UI_CALL(raw_line, grid->handle, row, startcol, endcol, clearcol, clearattr, + wrap, (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; + handle_T old_grid = cursor_grid_handle; + // If 'writedelay' is active, set the cursor to indicate what was drawn. + ui_grid_cursor_goto(grid->handle, row, MIN(clearcol, (int)Columns-1)); + ui_flush(); + uint64_t wd = (uint64_t)labs(p_wd); + os_microdelay(wd * 1000u, true); + ui_grid_cursor_goto(old_grid, old_row, old_col); } } -void ui_putc(uint8_t c) +void ui_cursor_goto(int new_row, int new_col) { - uint8_t buf[2] = {c, 0}; - ui_puts(buf); + ui_grid_cursor_goto(DEFAULT_GRID_HANDLE, new_row, new_col); } -void ui_cursor_goto(int new_row, int new_col) +void ui_grid_cursor_goto(handle_T grid_handle, int new_row, int new_col) { - if (new_row == row && new_col == col) { + if (new_row == cursor_row + && new_col == cursor_col + && grid_handle == cursor_grid_handle) { return; } - row = new_row; - col = new_col; + + cursor_row = new_row; + cursor_col = new_col; + cursor_grid_handle = grid_handle; pending_cursor_update = true; } -void ui_update_menu(void) +void ui_mode_info_set(void) { - UI_CALL(update_menu); + pending_mode_info_update = true; } int ui_current_row(void) { - return row; + return cursor_row; } int ui_current_col(void) { - return col; + return cursor_col; } void ui_flush(void) { - UI_CALL(flush); -} - -static void send_output(uint8_t **ptr) -{ - uint8_t *p = *ptr; - - while (*p >= 0x20) { - size_t clen = (size_t)mb_ptr2len(p); - UI_CALL(put, p, (size_t)clen); - col++; - if (mb_ptr2cells(p) > 1) { - // double cell character, blank the next cell - UI_CALL(put, NULL, 0); - col++; - } - if (col >= width) { - ui_linefeed(); - } - p += clen; - } - - *ptr = p; -} - -static void parse_control_character(uint8_t c) -{ - if (c == '\n') { - ui_linefeed(); - } else if (c == '\r') { - ui_carriage_return(); - } else if (c == '\b') { - ui_cursor_left(); - } else if (c == Ctrl_L) { - ui_cursor_right(); - } else if (c == Ctrl_G) { - UI_CALL(bell); - } -} - -static void set_highlight_args(int attr_code) -{ - HlAttrs rgb_attrs = { false, false, false, false, false, -1, -1, -1 }; - HlAttrs cterm_attrs = rgb_attrs; - - if (attr_code == HL_NORMAL) { - goto end; - } - - int rgb_mask = 0; - int cterm_mask = 0; - attrentry_T *aep = syn_cterm_attr2entry(attr_code); - - if (!aep) { - goto end; - } - - rgb_mask = aep->rgb_ae_attr; - cterm_mask = aep->cterm_ae_attr; - - rgb_attrs.bold = rgb_mask & HL_BOLD; - rgb_attrs.underline = rgb_mask & HL_UNDERLINE; - rgb_attrs.undercurl = rgb_mask & HL_UNDERCURL; - rgb_attrs.italic = rgb_mask & HL_ITALIC; - rgb_attrs.reverse = rgb_mask & (HL_INVERSE | HL_STANDOUT); - cterm_attrs.bold = cterm_mask & HL_BOLD; - cterm_attrs.underline = cterm_mask & HL_UNDERLINE; - cterm_attrs.undercurl = cterm_mask & HL_UNDERCURL; - cterm_attrs.italic = cterm_mask & HL_ITALIC; - cterm_attrs.reverse = cterm_mask & (HL_INVERSE | HL_STANDOUT); - - if (aep->rgb_fg_color != normal_fg) { - rgb_attrs.foreground = aep->rgb_fg_color; - } - - if (aep->rgb_bg_color != normal_bg) { - rgb_attrs.background = aep->rgb_bg_color; + cmdline_ui_flush(); + win_ui_flush(); + if (pending_cursor_update) { + ui_call_grid_cursor_goto(cursor_grid_handle, cursor_row, cursor_col); + pending_cursor_update = false; } - - if (aep->rgb_sp_color != normal_sp) { - rgb_attrs.special = aep->rgb_sp_color; + if (pending_mode_info_update) { + Array style = mode_style_array(); + bool enabled = (*p_guicursor != NUL); + ui_call_mode_info_set(enabled, style); + api_free_array(style); + pending_mode_info_update = false; } - - if (cterm_normal_fg_color != aep->cterm_fg_color) { - cterm_attrs.foreground = aep->cterm_fg_color - 1; + if (pending_mode_update) { + char *full_name = shape_table[ui_mode_idx].full_name; + ui_call_mode_change(cstr_as_string(full_name), ui_mode_idx); + pending_mode_update = false; } - - if (cterm_normal_bg_color != aep->cterm_bg_color) { - cterm_attrs.background = aep->cterm_bg_color - 1; - } - -end: - UI_CALL(highlight_set, (ui->rgb ? rgb_attrs : cterm_attrs)); + ui_call_flush(); } -static void ui_linefeed(void) + +/// Check if current mode has changed. +/// May update the shape of the cursor. +void ui_cursor_shape(void) { - int new_col = 0; - int new_row = row; - if (new_row < sr.bot) { - new_row++; - } else { - UI_CALL(scroll, 1); + if (!full_screen) { + return; } - ui_cursor_goto(new_row, new_col); -} + int new_mode_idx = cursor_get_mode_idx(); -static void ui_carriage_return(void) -{ - int new_col = 0; - ui_cursor_goto(row, new_col); + if (new_mode_idx != ui_mode_idx) { + ui_mode_idx = new_mode_idx; + pending_mode_update = true; + } + conceal_check_cursor_line(); } -static void ui_cursor_left(void) +/// Returns true if `widget` is externalized. +bool ui_is_external(UIExtension widget) { - int new_col = col - 1; - assert(new_col >= 0); - ui_cursor_goto(row, new_col); + return ui_ext[widget]; } -static void ui_cursor_right(void) +Array ui_array(void) { - int new_col = col + 1; - assert(new_col < width); - ui_cursor_goto(row, new_col); + Array all_uis = ARRAY_DICT_INIT; + for (size_t i = 0; i < ui_count; i++) { + UI *ui = uis[i]; + Dictionary info = ARRAY_DICT_INIT; + PUT(info, "width", INTEGER_OBJ(ui->width)); + PUT(info, "height", INTEGER_OBJ(ui->height)); + PUT(info, "rgb", BOOLEAN_OBJ(ui->rgb)); + for (UIExtension j = 0; j < kUIExtCount; j++) { + PUT(info, ui_ext_names[j], BOOLEAN_OBJ(ui->ui_ext[j])); + } + if (ui->inspect) { + ui->inspect(ui, &info); + } + ADD(all_uis, DICTIONARY_OBJ(info)); + } + return all_uis; } -static void flush_cursor_update(void) +void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error) { - if (pending_cursor_update) { - pending_cursor_update = false; - UI_CALL(cursor_goto, row, col); + if (grid_handle == DEFAULT_GRID_HANDLE) { + screen_resize(width, height); + return; } -} -// Notify that the current mode has changed. Can be used to change cursor -// shape, for example. -static void ui_mode_change(void) -{ - int mode; - if (!full_screen) { + win_T *wp = get_win_by_grid_handle(grid_handle); + if (wp == NULL) { + api_set_error(error, kErrorTypeValidation, + "No window with the given handle"); return; } - /* Get a simple UI mode out of State. */ - if ((State & REPLACE) == REPLACE) - mode = REPLACE; - else if (State & INSERT) - mode = INSERT; - else - mode = NORMAL; - UI_CALL(mode_change, mode); - conceal_check_cursur_line(); + + wp->w_grid.requested_rows = (int)height; + wp->w_grid.requested_cols = (int)width; + redraw_win_later(wp, SOME_VALID); } |