diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/nvim/api/private/helpers.c | 46 | ||||
| -rw-r--r-- | src/nvim/api/private/helpers.h | 12 | ||||
| -rw-r--r-- | src/nvim/eval.c | 12 | ||||
| -rw-r--r-- | src/nvim/ex_getln.c | 384 | ||||
| -rw-r--r-- | src/nvim/ex_getln.h | 2 | ||||
| -rw-r--r-- | src/nvim/mbyte.c | 2 | ||||
| -rw-r--r-- | src/nvim/mbyte.h | 3 | ||||
| -rw-r--r-- | src/nvim/message.c | 21 | 
8 files changed, 424 insertions, 58 deletions
| diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 1ed2bc013e..a04cc9a312 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -37,6 +37,52 @@ typedef struct {  # include "api/private/ui_events_metadata.generated.h"  #endif +/// Start block that may cause VimL exceptions while evaluating another code +/// +/// Used when caller is supposed to be operating when other VimL code is being +/// processed and that “other VimL code” must not be affected. +/// +/// @param[out]  tstate  Location where try state should be saved. +void try_enter(TryState *const tstate) +{ +  *tstate = (TryState) { +    .trylevel = trylevel, +    .got_int = got_int, +    .did_throw = did_throw, +    .msg_list = (const struct msglist *const *)msg_list, +    .private_msg_list = NULL, +  }; +  trylevel = 1; +  got_int = false; +  did_throw = false; +  msg_list = &tstate->private_msg_list; +} + +/// End try block, set the error message if any and restore previous state +/// +/// @warning Return is consistent with most functions (false on error), not with +///          try_end (true on error). +/// +/// @param[in]  tstate  Previous state to restore. +/// @param[out]  err  Location where error should be saved. +/// +/// @return false if error occurred, true otherwise. +bool try_leave(const TryState *const tstate, Error *const err) +  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ +  const bool ret = !try_end(err); +  assert(trylevel == 0); +  assert(!got_int); +  assert(!did_throw); +  assert(msg_list == &tstate->private_msg_list); +  assert(*msg_list == NULL); +  trylevel = tstate->trylevel; +  got_int = tstate->got_int; +  did_throw = tstate->did_throw; +  msg_list = (struct msglist **)tstate->msg_list; +  return ret; +} +  /// Start block that may cause vimscript exceptions  void try_start(void)  { diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 159b9d5c2a..112d785bfd 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -82,6 +82,18 @@  #define api_free_window(value)  #define api_free_tabpage(value) +/// Structure used for saving state for :try +/// +/// Used when caller is supposed to be operating when other VimL code is being +/// processed and that “other VimL code” must not be affected. +typedef struct { +  int trylevel; +  int got_int; +  int did_throw; +  struct msglist *private_msg_list; +  const struct msglist *const *msg_list; +} TryState; +  #ifdef INCLUDE_GENERATED_DECLARATIONS  # include "api/private/helpers.h.generated.h"  #endif diff --git a/src/nvim/eval.c b/src/nvim/eval.c index e5bb7f1b38..694c34731d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -11010,6 +11010,7 @@ void get_user_input(const typval_T *const argvars,    const char *defstr = "";    const char *cancelreturn = NULL;    const char *xp_name = NULL; +  Callback input_callback = { .type = kCallbackNone };    char prompt_buf[NUMBUFLEN];    char defstr_buf[NUMBUFLEN];    char cancelreturn_buf[NUMBUFLEN]; @@ -11019,7 +11020,7 @@ void get_user_input(const typval_T *const argvars,        emsgf(_("E5050: {opts} must be the only argument"));        return;      } -    const dict_T *const dict = argvars[0].vval.v_dict; +    dict_T *const dict = argvars[0].vval.v_dict;      prompt = tv_dict_get_string_buf_chk(dict, S_LEN("prompt"), prompt_buf, "");      if (prompt == NULL) {        return; @@ -11045,6 +11046,9 @@ void get_user_input(const typval_T *const argvars,      if (xp_name == def) {  // default to NULL        xp_name = NULL;      } +    if (!tv_dict_get_callback(dict, S_LEN("highlight"), &input_callback)) { +      return; +    }    } else {      prompt = tv_get_string_buf_chk(&argvars[0], prompt_buf);      if (prompt == NULL) { @@ -11103,12 +11107,16 @@ void get_user_input(const typval_T *const argvars,    stuffReadbuffSpec(defstr); -  int save_ex_normal_busy = ex_normal_busy; +  const int save_ex_normal_busy = ex_normal_busy; +  const Callback save_getln_input_callback = getln_input_callback;    ex_normal_busy = 0; +  getln_input_callback = input_callback;    rettv->vval.v_string =      getcmdline_prompt(inputsecret_flag ? NUL : '@', (char_u *)p, echo_attr,                        xp_type, (char_u *)xp_arg); +  getln_input_callback = save_getln_input_callback;    ex_normal_busy = save_ex_normal_busy; +  callback_free(&input_callback);    if (rettv->vval.v_string == NULL && cancelreturn != NULL) {      rettv->vval.v_string = (char_u *)xstrdup(cancelreturn); diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 0ba6c79a71..6c61e30f3d 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -62,6 +62,9 @@  #include "nvim/os/os.h"  #include "nvim/event/loop.h"  #include "nvim/os/time.h" +#include "nvim/lib/kvec.h" +#include "nvim/api/private/helpers.h" +#include "nvim/highlight_defs.h"  /*   * Variables shared between getcmdline(), redrawcmdline() and others. @@ -69,23 +72,26 @@   * structure.   */  struct cmdline_info { -  char_u      *cmdbuff;         /* pointer to command line buffer */ -  int cmdbufflen;               /* length of cmdbuff */ -  int cmdlen;                   /* number of chars in command line */ -  int cmdpos;                   /* current cursor position */ -  int cmdspos;                  /* cursor column on screen */ -  int cmdfirstc;                /* ':', '/', '?', '=', '>' or NUL */ -  int cmdindent;                /* number of spaces before cmdline */ -  char_u      *cmdprompt;       /* message in front of cmdline */ -  int cmdattr;                  /* attributes for prompt */ -  int overstrike;               /* Typing mode on the command line.  Shared by -                                   getcmdline() and put_on_cmdline(). */ -  expand_T    *xpc;             /* struct being used for expansion, xp_pattern -                                   may point into cmdbuff */ -  int xp_context;               /* type of expansion */ -  char_u      *xp_arg;          /* user-defined expansion arg */ -  int input_fn;                 /* when TRUE Invoked for input() function */ +  char_u      *cmdbuff;         // pointer to command line buffer +  int cmdbufflen;               // length of cmdbuff +  int cmdlen;                   // number of chars in command line +  int cmdpos;                   // current cursor position +  int cmdspos;                  // cursor column on screen +  int cmdfirstc;                // ':', '/', '?', '=', '>' or NUL +  int cmdindent;                // number of spaces before cmdline +  char_u      *cmdprompt;       // message in front of cmdline +  int cmdattr;                  // attributes for prompt +  int overstrike;               // Typing mode on the command line.  Shared by +                                // getcmdline() and put_on_cmdline(). +  expand_T    *xpc;             // struct being used for expansion, xp_pattern +                                // may point into cmdbuff +  int xp_context;               // type of expansion +  char_u      *xp_arg;          // user-defined expansion arg +  int input_fn;                 // when TRUE Invoked for input() function +  unsigned prompt_id;  ///< Prompt number, used to disable coloring on errors.  }; +/// Last value of prompt_id, incremented when doing new prompt +static unsigned last_prompt_id = 0;  typedef struct command_line_state {    VimState state; @@ -135,6 +141,18 @@ typedef struct command_line_state {    struct cmdline_info save_ccline;  } CommandLineState; +/// Command-line colors +typedef struct { +  int start;  ///< Colored chunk start. +  int end;  ///< Colored chunk end (exclusive, > start). +  int attr;  ///< Highlight attr. +} ColoredCmdlineChunk; + +/// Callback used for coloring input() prompts +Callback getln_input_callback = { .type = kCallbackNone }; + +kvec_t(ColoredCmdlineChunk) ccline_colors; +  /* The current cmdline_info.  It is initialized in getcmdline() and after that   * used by other functions.  When invoking getcmdline() recursively it needs   * to be saved with save_cmdline() and restored with restore_cmdline(). @@ -156,6 +174,12 @@ static int hisnum[HIST_COUNT] = {0, 0, 0, 0, 0};  /* identifying (unique) number of newest history entry */  static int hislen = 0;                  /* actual length of history tables */ +/// Flag for command_line_handle_key to ignore <C-c> +/// +/// Used if it was received while processing highlight function in order for +/// user interrupting highlight function to not interrupt command-line. +static bool getln_interrupted_highlight = false; +  #ifdef INCLUDE_GENERATED_DECLARATIONS  # include "ex_getln.c.generated.h" @@ -192,6 +216,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)      cmd_hkmap = 0;    } +  ccline.prompt_id = last_prompt_id++;    ccline.overstrike = false;                // always start in insert mode    clearpos(&s->match_end);    s->save_cursor = curwin->w_cursor;        // may be restored later @@ -1158,8 +1183,11 @@ static int command_line_handle_key(CommandLineState *s)    case ESC:           // get here if p_wc != ESC or when ESC typed twice    case Ctrl_C:      // In exmode it doesn't make sense to return.  Except when -    // ":normal" runs out of characters. -    if (exmode_active && (ex_normal_busy == 0 || typebuf.tb_len > 0)) { +    // ":normal" runs out of characters. Also when highlight callback is active +    // <C-c> should interrupt only it. +    if ((exmode_active && (ex_normal_busy == 0 || typebuf.tb_len > 0)) +        || (getln_interrupted_highlight && s->c == Ctrl_C)) { +      getln_interrupted_highlight = false;        return command_line_not_changed(s);      } @@ -1807,6 +1835,7 @@ getcmdline_prompt (    int msg_col_save = msg_col;    save_cmdline(&save_ccline); +  ccline.prompt_id = last_prompt_id++;    ccline.cmdprompt = prompt;    ccline.cmdattr = attr;    ccline.xp_context = xp_context; @@ -2282,75 +2311,305 @@ void free_cmdline_buf(void)  # endif +enum { MAX_CB_ERRORS = 1 }; + +/// Color command-line +/// +/// Should use built-in command parser or user-specified one. Currently only the  +/// latter is supported. +/// +/// Operates on ccline, saving results to ccline_colors. +/// +/// Always colors the whole cmdline. +/// +/// @return true if draw_cmdline may proceed, false if it does not need anything +///         to do. +static bool color_cmdline(void) +{ +  bool printed_errmsg = false; +#define PRINT_ERRMSG(...) \ +  do { \ +    msg_putchar('\n'); \ +    msg_printf_attr(hl_attr(HLF_E)|MSG_HIST, __VA_ARGS__); \ +    printed_errmsg = true; \ +  } while (0) +  bool ret = true; +  kv_size(ccline_colors) = 0; + +  if (ccline.cmdbuff == NULL || *ccline.cmdbuff == NUL) { +    // Nothing to do, exiting. +    return ret; +  } + +  const int saved_force_abort = force_abort; +  force_abort = true; +  bool arg_allocated = false; +  typval_T arg = { +    .v_type = VAR_STRING, +    .vval.v_string = ccline.cmdbuff, +  }; +  typval_T tv = { .v_type = VAR_UNKNOWN }; + +  static unsigned prev_prompt_id = UINT_MAX; +  static int prev_prompt_errors = 0; +  Callback color_cb = { .type = kCallbackNone }; +  bool can_free_cb = false; +  TryState tstate; +  Error err = ERROR_INIT; +  const char *err_errmsg = (const char *)e_intern2; +  bool dgc_ret = true; + +  try_enter(&tstate); +  if (ccline.input_fn) { +    color_cb = getln_input_callback; +  } else if (ccline.cmdfirstc == ':') { +    err_errmsg = N_( +        "E5408: Unable to get Nvim_color_cmdline callback from g:: %s"); +    dgc_ret = tv_dict_get_callback(&globvardict, S_LEN("Nvim_color_cmdline"), +                                   &color_cb); +    can_free_cb = true; +  } else if (ccline.cmdfirstc == '=') { +    err_errmsg = N_( +        "E5409: Unable to get Nvim_color_expr callback from g:: %s"); +    dgc_ret = tv_dict_get_callback(&globvardict, S_LEN("Nvim_color_expr"), +                                   &color_cb); +    can_free_cb = true; +  } else { +    goto color_cmdline_end; +  } +  if (!try_leave(&tstate, &err) || !dgc_ret) { +    goto color_cmdline_error; +  } + +  if (color_cb.type == kCallbackNone) { +    goto color_cmdline_end; +  } +  if (ccline.prompt_id != prev_prompt_id) { +    prev_prompt_errors = 0; +    prev_prompt_id = ccline.prompt_id; +  } else if (prev_prompt_errors >= MAX_CB_ERRORS) { +    goto color_cmdline_end; +  } +  if (ccline.cmdbuff[ccline.cmdlen] != NUL) { +    arg_allocated = true; +    arg.vval.v_string = xmemdupz((const char *)ccline.cmdbuff, +                                 (size_t)ccline.cmdlen); +  } +  // msg_start() called by e.g. :echo may shift command-line to the first column +  // even though msg_silent is here. Two ways to workaround this problem without +  // altering message.c: use full_screen or save and restore msg_col. +  // +  // Saving and restoring full_screen does not work well with :redraw!. Saving +  // and restoring msg_col is neither ideal, but while with full_screen it +  // appears shifted one character to the right and cursor position is no longer +  // correct, with msg_col it just misses leading `:`. Since `redraw!` in +  // callback lags this is least of the user problems. +  // +  // Also using try_enter() because error messages may overwrite typed +  // command-line which is not expected. +  getln_interrupted_highlight = false; +  try_enter(&tstate); +  err_errmsg = N_("E5407: Callback has thrown an exception: %s"); +  const int saved_msg_col = msg_col; +  msg_silent++; +  const bool cbcall_ret = callback_call(&color_cb, 1, &arg, &tv); +  msg_silent--; +  msg_col = saved_msg_col; +  if (got_int) { +    getln_interrupted_highlight = true; +  } +  if (!try_leave(&tstate, &err) || !cbcall_ret) { +    goto color_cmdline_error; +  } +  if (tv.v_type != VAR_LIST) { +    PRINT_ERRMSG(_("E5400: Callback should return list")); +    goto color_cmdline_error; +  } +  if (tv.vval.v_list == NULL) { +    goto color_cmdline_end; +  } +  varnumber_T prev_end = 0; +  int i = 0; +  for (const listitem_T *li = tv.vval.v_list->lv_first; +       li != NULL; li = li->li_next, i++) { +    if (li->li_tv.v_type != VAR_LIST) { +      PRINT_ERRMSG(_("E5401: List item %i is not a List"), i); +      goto color_cmdline_error; +    } +    const list_T *const l = li->li_tv.vval.v_list; +    if (tv_list_len(l) != 3) { +      PRINT_ERRMSG(_("E5402: List item %i has incorrect length: %li /= 3"), +                   i, tv_list_len(l)); +      goto color_cmdline_error; +    } +    bool error = false; +    const varnumber_T start = tv_get_number_chk(&l->lv_first->li_tv, &error); +    if (error) { +      goto color_cmdline_error; +    } else if (!(prev_end <= start && start < ccline.cmdlen)) { +      PRINT_ERRMSG(_("E5403: Chunk %i start %" PRIdVARNUMBER " not in range " +                     "[%" PRIdVARNUMBER ", %i)"), +                   i, start, prev_end, ccline.cmdlen); +      goto color_cmdline_error; +    } else if (utf8len_tab_zero[(uint8_t)ccline.cmdbuff[start]] == 0) { +      PRINT_ERRMSG(_("E5405: Chunk %i start %" PRIdVARNUMBER " splits " +                     "multibyte character"), i, start); +      goto color_cmdline_error; +    } +    if (start != prev_end) { +      kv_push(ccline_colors, ((ColoredCmdlineChunk) { +        .start = prev_end, +        .end = start, +        .attr = 0, +      })); +    } +    const varnumber_T end = tv_get_number_chk(&l->lv_first->li_next->li_tv, +                                              &error); +    if (error) { +      goto color_cmdline_error; +    } else if (!(start < end && end <= ccline.cmdlen)) { +      PRINT_ERRMSG(_("E5404: Chunk %i end %" PRIdVARNUMBER " not in range " +                     "(%" PRIdVARNUMBER ", %i]"), +                   i, end, start, ccline.cmdlen); +      goto color_cmdline_error; +    } else if (end < ccline.cmdlen +               && utf8len_tab_zero[(uint8_t)ccline.cmdbuff[end]] == 0) { +      PRINT_ERRMSG(_("E5406: Chunk %i end %" PRIdVARNUMBER " splits multibyte " +                     "character"), i, end); +      goto color_cmdline_error; +    } +    prev_end = end; +    const char *const group = tv_get_string_chk(&l->lv_last->li_tv); +    if (group == NULL) { +      goto color_cmdline_error; +    } +    const int id = syn_name2id((char_u *)group); +    const int attr = (id == 0 ? 0 : syn_id2attr(id)); +    kv_push(ccline_colors, ((ColoredCmdlineChunk) { +      .start = start, +      .end = end, +      .attr = attr, +    })); +  } +  if (prev_end < ccline.cmdlen) { +    kv_push(ccline_colors, ((ColoredCmdlineChunk) { +      .start = prev_end, +      .end = ccline.cmdlen, +      .attr = 0, +    })); +  } +  prev_prompt_errors = 0; +color_cmdline_end: +  assert(!ERROR_SET(&err)); +  if (can_free_cb) { +    callback_free(&color_cb); +  } +  force_abort = saved_force_abort; +  if (arg_allocated) { +    tv_clear(&arg); +  } +  tv_clear(&tv); +  return ret; +color_cmdline_error: +  if (ERROR_SET(&err)) { +    PRINT_ERRMSG(_(err_errmsg), err.msg); +    api_clear_error(&err); +  } +  assert(printed_errmsg); + +  prev_prompt_errors++; +  kv_size(ccline_colors) = 0; +  prev_prompt_errors += MAX_CB_ERRORS; +  redrawcmdline(); +  prev_prompt_errors -= MAX_CB_ERRORS; +  ret = false; +  goto color_cmdline_end; +#undef PRINT_ERRMSG +} +  /*   * Draw part of the cmdline at the current cursor position.  But draw stars   * when cmdline_star is TRUE.   */  static void draw_cmdline(int start, int len)  { -  int i; +  if (!color_cmdline()) { +    return; +  } -  if (cmdline_star > 0) -    for (i = 0; i < len; ++i) { +  if (cmdline_star > 0) { +    for (int i = 0; i < len; i++) {        msg_putchar('*'); -      if (has_mbyte) +      if (has_mbyte) {          i += (*mb_ptr2len)(ccline.cmdbuff + start + i) - 1; +      }      } -  else if (p_arshape && !p_tbidi && enc_utf8 && len > 0) { -    static int buflen = 0; -    char_u          *p; -    int j; -    int newlen = 0; +  } else if (p_arshape && !p_tbidi && enc_utf8 && len > 0) { +    bool do_arabicshape = false;      int mb_l; -    int pc, pc1 = 0; -    int prev_c = 0; -    int prev_c1 = 0; -    int u8c; -    int u8cc[MAX_MCO]; -    int nc = 0; +    for (int i = start; i < start + len; i += mb_l) { +      char_u *p = ccline.cmdbuff + i; +      int u8cc[MAX_MCO]; +      int u8c = utfc_ptr2char_len(p, u8cc, start + len - i); +      mb_l = utfc_ptr2len_len(p, start + len - i); +      if (arabic_char(u8c)) { +        do_arabicshape = true; +        break; +      } +    } +    if (!do_arabicshape) { +      goto draw_cmdline_no_arabicshape; +    } -    /* -     * Do arabic shaping into a temporary buffer.  This is very -     * inefficient! -     */ +    static int buflen = 0; + +    // Do arabic shaping into a temporary buffer.  This is very +    // inefficient!      if (len * 2 + 2 > buflen) { -      /* Re-allocate the buffer.  We keep it around to avoid a lot of -       * alloc()/free() calls. */ +      // Re-allocate the buffer.  We keep it around to avoid a lot of +      // alloc()/free() calls.        xfree(arshape_buf);        buflen = len * 2 + 2;        arshape_buf = xmalloc(buflen);      } +    int newlen = 0;      if (utf_iscomposing(utf_ptr2char(ccline.cmdbuff + start))) { -      /* Prepend a space to draw the leading composing char on. */ +      // Prepend a space to draw the leading composing char on.        arshape_buf[0] = ' ';        newlen = 1;      } -    for (j = start; j < start + len; j += mb_l) { -      p = ccline.cmdbuff + j; -      u8c = utfc_ptr2char_len(p, u8cc, start + len - j); -      mb_l = utfc_ptr2len_len(p, start + len - j); +    int prev_c = 0; +    int prev_c1 = 0; +    for (int i = start; i < start + len; i += mb_l) { +      char_u *p = ccline.cmdbuff + i; +      int u8cc[MAX_MCO]; +      int u8c = utfc_ptr2char_len(p, u8cc, start + len - i); +      mb_l = utfc_ptr2len_len(p, start + len - i);        if (arabic_char(u8c)) { -        /* Do Arabic shaping. */ +        int pc; +        int pc1 = 0; +        int nc = 0; +        // Do Arabic shaping.          if (cmdmsg_rl) { -          /* displaying from right to left */ +          // Displaying from right to left.            pc = prev_c;            pc1 = prev_c1;            prev_c1 = u8cc[0]; -          if (j + mb_l >= start + len) +          if (i + mb_l >= start + len) {              nc = NUL; -          else +          } else {              nc = utf_ptr2char(p + mb_l); +          }          } else { -          /* displaying from left to right */ -          if (j + mb_l >= start + len) +          // Displaying from left to right. +          if (i + mb_l >= start + len) {              pc = NUL; -          else { +          } else {              int pcc[MAX_MCO]; -            pc = utfc_ptr2char_len(p + mb_l, pcc, -                start + len - j - mb_l); +            pc = utfc_ptr2char_len(p + mb_l, pcc, start + len - i - mb_l);              pc1 = pcc[0];            }            nc = prev_c; @@ -2374,8 +2633,23 @@ static void draw_cmdline(int start, int len)      }      msg_outtrans_len(arshape_buf, newlen); -  } else -    msg_outtrans_len(ccline.cmdbuff + start, len); +  } else { +draw_cmdline_no_arabicshape: +    if (kv_size(ccline_colors)) { +      for (size_t i = 0; i < kv_size(ccline_colors); i++) { +        ColoredCmdlineChunk chunk = kv_A(ccline_colors, i); +        if (chunk.end <= start) { +          continue; +        } +        const int chunk_start = MAX(chunk.start, start); +        msg_outtrans_len_attr(ccline.cmdbuff + chunk_start, +                              chunk.end - chunk_start, +                              chunk.attr); +      } +    } else { +      msg_outtrans_len(ccline.cmdbuff + start, len); +    } +  }  }  /* diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h index 051564fbe1..92cb274010 100644 --- a/src/nvim/ex_getln.h +++ b/src/nvim/ex_getln.h @@ -52,6 +52,8 @@ typedef struct hist_entry {    list_T *additional_elements;  ///< Additional entries from ShaDa file.  } histentry_T; +extern Callback getln_input_callback; +  #ifdef INCLUDE_GENERATED_DECLARATIONS  # include "ex_getln.h.generated.h"  #endif diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 2acfb896d8..4aee1c8e68 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -75,7 +75,7 @@ struct interval {  /*   * Like utf8len_tab above, but using a zero for illegal lead bytes.   */ -static uint8_t utf8len_tab_zero[256] = +const uint8_t utf8len_tab_zero[256] =  {    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h index ad9e38004c..02c6065672 100644 --- a/src/nvim/mbyte.h +++ b/src/nvim/mbyte.h @@ -1,6 +1,7 @@  #ifndef NVIM_MBYTE_H  #define NVIM_MBYTE_H +#include <stdint.h>  #include <stdbool.h>  #include <string.h> @@ -67,6 +68,8 @@ typedef struct {                   ///< otherwise use '?'.  } vimconv_T; +extern const uint8_t utf8len_tab_zero[256]; +  #ifdef INCLUDE_GENERATED_DECLARATIONS  # include "mbyte.h.generated.h"  #endif diff --git a/src/nvim/message.c b/src/nvim/message.c index 36f9ca84ed..92dd5a4695 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1628,6 +1628,27 @@ void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr)    }  } +/// Print a formatted message +/// +/// Message printed is limited by #IOSIZE. Must not be used from inside +/// msg_puts_attr(). +/// +/// @param[in]  attr  Highlight attributes. +/// @param[in]  fmt  Format string. +void msg_printf_attr(const int attr, const char *const fmt, ...) +  FUNC_ATTR_NONNULL_ALL +{ +  static char msgbuf[IOSIZE]; + +  va_list ap; +  va_start(ap, fmt); +  const size_t len = vim_vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap, NULL); +  va_end(ap); + +  msg_scroll = true; +  msg_puts_attr_len(msgbuf, (ptrdiff_t)len, attr); +} +  /*   * The display part of msg_puts_attr_len().   * May be called recursively to display scroll-back text. | 
