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);  }  | 
