neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

message.c (115175B)


      1 // message.c: functions for displaying messages on the command line
      2 
      3 #include <assert.h>
      4 #include <inttypes.h>
      5 #include <limits.h>
      6 #include <stdarg.h>
      7 #include <stdbool.h>
      8 #include <stddef.h>
      9 #include <stdio.h>
     10 #include <stdlib.h>
     11 #include <string.h>
     12 #include <uv.h>
     13 
     14 #include "klib/kvec.h"
     15 #include "nvim/api/private/defs.h"
     16 #include "nvim/api/private/helpers.h"
     17 #include "nvim/ascii_defs.h"
     18 #include "nvim/autocmd.h"
     19 #include "nvim/buffer_defs.h"
     20 #include "nvim/channel.h"
     21 #include "nvim/charset.h"
     22 #include "nvim/drawscreen.h"
     23 #include "nvim/errors.h"
     24 #include "nvim/eval.h"
     25 #include "nvim/eval/typval.h"
     26 #include "nvim/eval/typval_defs.h"
     27 #include "nvim/eval/vars.h"
     28 #include "nvim/event/defs.h"
     29 #include "nvim/event/loop.h"
     30 #include "nvim/event/multiqueue.h"
     31 #include "nvim/ex_cmds_defs.h"
     32 #include "nvim/ex_docmd.h"
     33 #include "nvim/ex_eval.h"
     34 #include "nvim/ex_getln.h"
     35 #include "nvim/fileio.h"
     36 #include "nvim/garray.h"
     37 #include "nvim/garray_defs.h"
     38 #include "nvim/getchar.h"
     39 #include "nvim/gettext_defs.h"
     40 #include "nvim/globals.h"
     41 #include "nvim/grid.h"
     42 #include "nvim/highlight.h"
     43 #include "nvim/highlight_defs.h"
     44 #include "nvim/highlight_group.h"
     45 #include "nvim/indent.h"
     46 #include "nvim/input.h"
     47 #include "nvim/keycodes.h"
     48 #include "nvim/log.h"
     49 #include "nvim/main.h"
     50 #include "nvim/mbyte.h"
     51 #include "nvim/mbyte_defs.h"
     52 #include "nvim/memory.h"
     53 #include "nvim/memory_defs.h"
     54 #include "nvim/message.h"
     55 #include "nvim/message_defs.h"
     56 #include "nvim/mouse.h"
     57 #include "nvim/option.h"
     58 #include "nvim/option_vars.h"
     59 #include "nvim/os/fs.h"
     60 #include "nvim/os/input.h"
     61 #include "nvim/os/os.h"
     62 #include "nvim/os/time.h"
     63 #include "nvim/pos_defs.h"
     64 #include "nvim/regexp.h"
     65 #include "nvim/register.h"
     66 #include "nvim/runtime.h"
     67 #include "nvim/runtime_defs.h"
     68 #include "nvim/state_defs.h"
     69 #include "nvim/strings.h"
     70 #include "nvim/types_defs.h"
     71 #include "nvim/ui.h"
     72 #include "nvim/ui_compositor.h"
     73 #include "nvim/ui_defs.h"
     74 #include "nvim/vim_defs.h"
     75 
     76 // To be able to scroll back at the "more" and "hit-enter" prompts we need to
     77 // store the displayed text and remember where screen lines start.
     78 typedef struct msgchunk_S msgchunk_T;
     79 struct msgchunk_S {
     80  msgchunk_T *sb_next;
     81  msgchunk_T *sb_prev;
     82  char sb_eol;                  // true when line ends after this text
     83  int sb_msg_col;               // column in which text starts
     84  int sb_hl_id;                 // text highlight id
     85  char sb_text[];               // text to be displayed
     86 };
     87 
     88 // Magic chars used in confirm dialog strings
     89 enum {
     90  DLG_BUTTON_SEP = '\n',
     91  DLG_HOTKEY_CHAR = '&',
     92 };
     93 
     94 static int confirm_msg_used = false;            // displaying confirm_msg
     95 #include "message.c.generated.h"
     96 static char *confirm_msg = NULL;            // ":confirm" message
     97 static char *confirm_buttons;               // ":confirm" buttons sent to cmdline as prompt
     98 
     99 MessageHistoryEntry *msg_hist_last = NULL;          // Last message (extern for unittest)
    100 static MessageHistoryEntry *msg_hist_first = NULL;  // First message
    101 static MessageHistoryEntry *msg_hist_temp = NULL;   // First potentially temporary message
    102 static int msg_hist_len = 0;
    103 static int msg_hist_max = 500;  // The default max value is 500
    104 
    105 // args in 'messagesopt' option
    106 #define MESSAGES_OPT_HIT_ENTER "hit-enter"
    107 #define MESSAGES_OPT_WAIT "wait:"
    108 #define MESSAGES_OPT_HISTORY "history:"
    109 
    110 // The default is "hit-enter,history:500"
    111 static int msg_flags = kOptMoptFlagHitEnter | kOptMoptFlagHistory;
    112 static int msg_wait = 0;
    113 
    114 static FILE *verbose_fd = NULL;
    115 static bool verbose_did_open = false;
    116 
    117 bool keep_msg_more = false;    // keep_msg was set by msgmore()
    118 
    119 // When writing messages to the screen, there are many different situations.
    120 // A number of variables is used to remember the current state:
    121 // msg_didany       true when messages were written since the last time the
    122 //                  user reacted to a prompt.
    123 //                  Reset: After hitting a key for the hit-return prompt,
    124 //                  hitting <CR> for the command line or input().
    125 //                  Set: When any message is written to the screen.
    126 // msg_didout       true when something was written to the current line.
    127 //                  Reset: When advancing to the next line, when the current
    128 //                  text can be overwritten.
    129 //                  Set: When any message is written to the screen.
    130 // msg_nowait       No extra delay for the last drawn message.
    131 //                  Used in normal_cmd() before the mode message is drawn.
    132 // emsg_on_display  There was an error message recently.  Indicates that there
    133 //                  should be a delay before redrawing.
    134 // msg_scroll       The next message should not overwrite the current one.
    135 // msg_scrolled     How many lines the screen has been scrolled (because of
    136 //                  messages).  Used in update_screen() to scroll the screen
    137 //                  back.  Incremented each time the screen scrolls a line.
    138 // msg_scrolled_ign  true when msg_scrolled is non-zero and msg_puts_hl()
    139 //                  writes something without scrolling should not make
    140 //                  need_wait_return to be set.  This is a hack to make ":ts"
    141 //                  work without an extra prompt.
    142 // lines_left       Number of lines available for messages before the
    143 //                  more-prompt is to be given.  -1 when not set.
    144 // need_wait_return true when the hit-return prompt is needed.
    145 //                  Reset: After giving the hit-return prompt, when the user
    146 //                  has answered some other prompt.
    147 //                  Set: When the ruler or typeahead display is overwritten,
    148 //                  scrolling the screen for some message.
    149 // keep_msg         Message to be displayed after redrawing the screen, in
    150 //                  Normal mode main loop.
    151 //                  This is an allocated string or NULL when not used.
    152 
    153 // Extended msg state, currently used for external UIs with ext_messages
    154 static const char *msg_ext_kind = NULL;
    155 static MsgID msg_ext_id = { .type = kObjectTypeInteger, .data.integer = 1 };
    156 static Array *msg_ext_chunks = NULL;
    157 static garray_T msg_ext_last_chunk = GA_INIT(sizeof(char), 40);
    158 static sattr_T msg_ext_last_attr = -1;
    159 static int msg_ext_last_hl_id;
    160 
    161 static bool msg_ext_history = false;  ///< message was added to history
    162 
    163 static int msg_grid_pos_at_flush = 0;
    164 
    165 static int64_t msg_id_next = 1;           ///< message id to be allocated to next message
    166 
    167 static void ui_ext_msg_set_pos(int row, bool scrolled)
    168 {
    169  char buf[MAX_SCHAR_SIZE];
    170  size_t size = schar_get(buf, curwin->w_p_fcs_chars.msgsep);
    171  ui_call_msg_set_pos(msg_grid.handle, row, scrolled,
    172                      (String){ .data = buf, .size = size }, msg_grid.zindex,
    173                      (int)msg_grid.comp_index);
    174  msg_grid.pending_comp_index_update = false;
    175 }
    176 
    177 void msg_grid_set_pos(int row, bool scrolled)
    178 {
    179  if (!msg_grid.throttled) {
    180    ui_ext_msg_set_pos(row, scrolled);
    181    msg_grid_pos_at_flush = row;
    182  }
    183  msg_grid_pos = row;
    184  if (msg_grid.chars) {
    185    msg_grid_adj.row_offset = -row;
    186  }
    187 }
    188 
    189 bool msg_use_grid(void)
    190 {
    191  return default_grid.chars && !ui_has(kUIMessages);
    192 }
    193 
    194 void msg_grid_validate(void)
    195 {
    196  grid_assign_handle(&msg_grid);
    197  bool should_alloc = msg_use_grid();
    198  int max_rows = Rows - (int)p_ch;
    199  if (should_alloc && (msg_grid.rows != Rows || msg_grid.cols != Columns
    200                       || !msg_grid.chars)) {
    201    // TODO(bfredl): eventually should be set to "invalid". I e all callers
    202    // will use the grid including clear to EOS if necessary.
    203    grid_alloc(&msg_grid, Rows, Columns, false, true);
    204    msg_grid.zindex = kZIndexMessages;
    205 
    206    xfree(msg_grid.dirty_col);
    207    msg_grid.dirty_col = xcalloc((size_t)Rows, sizeof(*msg_grid.dirty_col));
    208 
    209    // Tricky: allow resize while pager or ex mode is active
    210    int pos = (State & MODE_ASKMORE) ? 0 : MAX(max_rows - msg_scrolled, 0);
    211    msg_grid.throttled = false;  // don't throttle in 'cmdheight' area
    212    msg_grid_set_pos(pos, msg_scrolled);
    213    ui_comp_put_grid(&msg_grid, pos, 0, msg_grid.rows, msg_grid.cols,
    214                     false, true);
    215    ui_call_grid_resize(msg_grid.handle, msg_grid.cols, msg_grid.rows);
    216 
    217    msg_scrolled_at_flush = msg_scrolled;
    218    msg_grid.mouse_enabled = false;
    219    msg_grid_adj.target = &msg_grid;
    220  } else if (!should_alloc && msg_grid.chars) {
    221    ui_comp_remove_grid(&msg_grid);
    222    grid_free(&msg_grid);
    223    XFREE_CLEAR(msg_grid.dirty_col);
    224    ui_call_grid_destroy(msg_grid.handle);
    225    msg_grid.throttled = false;
    226    msg_grid_adj.row_offset = 0;
    227    msg_grid_adj.target = &default_grid;
    228    redraw_cmdline = true;
    229  } else if (msg_grid.chars && !msg_scrolled && msg_grid_pos != max_rows) {
    230    int diff = msg_grid_pos - max_rows;
    231    msg_grid_set_pos(max_rows, false);
    232    if (diff > 0) {
    233      grid_clear(&msg_grid_adj, Rows - diff, Rows, 0, Columns, HL_ATTR(HLF_MSG));
    234    }
    235  }
    236 
    237  if (msg_grid.chars && !msg_scrolled && cmdline_row < msg_grid_pos) {
    238    // TODO(bfredl): this should already be the case, but fails in some
    239    // "batched" executions where compute_cmdrow() use stale positions or
    240    // something.
    241    cmdline_row = msg_grid_pos;
    242  }
    243 }
    244 
    245 /// Like msg() but keep it silent when 'verbosefile' is set.
    246 int verb_msg(const char *s)
    247 {
    248  verbose_enter();
    249  int n = msg_keep(s, 0, false, false);
    250  verbose_leave();
    251 
    252  return n;
    253 }
    254 
    255 /// Displays the string 's' on the status line
    256 /// When terminal not initialized (yet) printf("%s", ..) is used.
    257 ///
    258 /// @return  true if wait_return() not called
    259 bool msg(const char *s, const int hl_id)
    260  FUNC_ATTR_NONNULL_ARG(1)
    261 {
    262  return msg_keep(s, hl_id, false, false);
    263 }
    264 
    265 /// Similar to msg_outtrans_len, but support newlines and tabs.
    266 void msg_multiline(String str, int hl_id, bool check_int, bool hist, bool *need_clear)
    267  FUNC_ATTR_NONNULL_ALL
    268 {
    269  const char *s = str.data;
    270  const char *chunk = s;
    271  while ((size_t)(s - str.data) < str.size) {
    272    if (check_int && got_int) {
    273      return;
    274    }
    275    if (*s == '\n' || *s == TAB || *s == '\r') {
    276      // Print all chars before the delimiter
    277      msg_outtrans_len(chunk, (int)(s - chunk), hl_id, hist);
    278 
    279      if (*s != TAB && *need_clear) {
    280        msg_clr_eos();
    281        *need_clear = false;
    282      }
    283      msg_putchar_hl((uint8_t)(*s), hl_id);
    284      chunk = s + 1;
    285    }
    286    s++;
    287  }
    288 
    289  // Print the remainder or emit empty event if entire message is empty.
    290  if (*chunk != NUL || chunk == str.data) {
    291    msg_outtrans_len(chunk, (int)(str.size - (size_t)(chunk - str.data)), hl_id, hist);
    292  }
    293 }
    294 
    295 // Avoid starting a new message for each chunk and adding message to history in msg_keep().
    296 static bool is_multihl = false;
    297 
    298 /// Format a progress message, adding title and percent if given.
    299 ///
    300 /// @param hl_msg Message chunks
    301 /// @param msg_data Additional data for progress messages
    302 static HlMessage format_progress_message(HlMessage hl_msg, MessageData *msg_data)
    303 {
    304  HlMessage updated_msg = KV_INITIAL_VALUE;
    305  // progress messages are special. displayed as "title: percent% msg"
    306  if (msg_data->title.size != 0) {
    307    // this block draws the "title:" before the progress-message
    308    int hl_id = 0;
    309    if (msg_data->status.data == NULL) {
    310      hl_id = 0;
    311    } else if (strequal(msg_data->status.data, "success")) {
    312      hl_id = syn_check_group("OkMsg", STRLEN_LITERAL("OkMsg"));
    313    } else if (strequal(msg_data->status.data, "failed")) {
    314      hl_id = syn_check_group("ErrorMsg", STRLEN_LITERAL("ErrorMsg"));
    315    } else if (strequal(msg_data->status.data, "running")) {
    316      hl_id = syn_check_group("MoreMsg", STRLEN_LITERAL("MoreMsg"));
    317    } else if (strequal(msg_data->status.data, "cancel")) {
    318      hl_id = syn_check_group("WarningMsg", STRLEN_LITERAL("WarningMsg"));
    319    }
    320    kv_push(updated_msg,
    321            ((HlMessageChunk){ .text = copy_string(msg_data->title, NULL), .hl_id = hl_id }));
    322    kv_push(updated_msg, ((HlMessageChunk){ .text = cstr_to_string(": "), .hl_id = 0 }));
    323  }
    324  if (msg_data->percent > 0) {
    325    char percent_buf[10];
    326    vim_snprintf(percent_buf, sizeof(percent_buf), "%3ld%% ", (long)msg_data->percent);
    327    String percent = cstr_to_string(percent_buf);
    328    int hl_id = syn_check_group("WarningMsg", STRLEN_LITERAL("WarningMsg"));
    329    kv_push(updated_msg, ((HlMessageChunk){ .text = percent, .hl_id = hl_id }));
    330  }
    331 
    332  if (kv_size(updated_msg) != 0) {
    333    for (uint32_t i = 0; i < kv_size(hl_msg); i++) {
    334      kv_push(updated_msg,
    335              ((HlMessageChunk){ .text = copy_string(kv_A(hl_msg, i).text, NULL),
    336                                 .hl_id = kv_A(hl_msg, i).hl_id }));
    337    }
    338    return updated_msg;
    339  } else {
    340    return hl_msg;
    341  }
    342 }
    343 
    344 /// Print message chunks, each with their own highlight ID.
    345 ///
    346 /// @param hl_msg Message chunks
    347 /// @param kind Message kind (can be NULL to avoid setting kind)
    348 /// @param history Whether to add message to history
    349 /// @param err Whether to print message as an error
    350 /// @param msg_data Progress-message data
    351 MsgID msg_multihl(MsgID id, HlMessage hl_msg, const char *kind, bool history, bool err,
    352                  MessageData *msg_data, bool *needs_msg_clear)
    353 {
    354  no_wait_return++;
    355  msg_start();
    356  msg_clr_eos();
    357  bool need_clear = false;
    358  bool hl_msg_updated = false;
    359  msg_ext_history = history;
    360  if (kind != NULL) {
    361    msg_ext_set_kind(kind);
    362  }
    363  is_multihl = true;
    364  msg_ext_skip_flush = true;
    365 
    366  // provide a new id if not given
    367  if (id.type == kObjectTypeNil) {
    368    id = INTEGER_OBJ(msg_id_next++);
    369  } else if (id.type == kObjectTypeInteger) {
    370    id = id.data.integer > 0 ? id : INTEGER_OBJ(msg_id_next++);
    371    msg_id_next = MAX(msg_id_next, id.data.integer + 1);
    372  }
    373  msg_ext_id = id;
    374 
    375  // progress message are special displayed as "title: percent% msg"
    376  if (strequal(kind, "progress") && msg_data) {
    377    HlMessage formated_message = format_progress_message(hl_msg, msg_data);
    378    if (formated_message.items != hl_msg.items) {
    379      *needs_msg_clear = true;
    380      hl_msg_updated = true;
    381      hl_msg = formated_message;
    382    }
    383  }
    384 
    385  for (uint32_t i = 0; i < kv_size(hl_msg); i++) {
    386    HlMessageChunk chunk = kv_A(hl_msg, i);
    387    if (err) {
    388      emsg_multiline(chunk.text.data, kind, chunk.hl_id, true);
    389    } else {
    390      msg_multiline(chunk.text, chunk.hl_id, true, false, &need_clear);
    391    }
    392    assert(!ui_has(kUIMessages) || kind == NULL || msg_ext_kind == kind);
    393  }
    394 
    395  if (history && kv_size(hl_msg)) {
    396    msg_hist_add_multihl(hl_msg, false, msg_data);
    397  }
    398 
    399  msg_ext_skip_flush = false;
    400  is_multihl = false;
    401  no_wait_return--;
    402  msg_end();
    403 
    404  if (hl_msg_updated && !(history && kv_size(hl_msg))) {
    405    hl_msg_free(hl_msg);
    406  }
    407  return id;
    408 }
    409 
    410 /// @param keep set keep_msg if it doesn't scroll
    411 bool msg_keep(const char *s, int hl_id, bool keep, bool multiline)
    412  FUNC_ATTR_NONNULL_ALL
    413 {
    414  static int entered = 0;
    415 
    416  if (keep && multiline) {
    417    // Not implemented. 'multiline' is only used by nvim-added messages,
    418    // which should avoid 'keep' behavior (just show the message at
    419    // the correct time already).
    420    abort();
    421  }
    422 
    423  // Skip messages not match ":filter pattern".
    424  // Don't filter when there is an error.
    425  if (!emsg_on_display && message_filtered(s)) {
    426    return true;
    427  }
    428 
    429  if (hl_id == 0) {
    430    set_vim_var_string(VV_STATUSMSG, s, -1);
    431  }
    432 
    433  // It is possible that displaying a messages causes a problem (e.g.,
    434  // when redrawing the window), which causes another message, etc..    To
    435  // break this loop, limit the recursiveness to 3 levels.
    436  if (entered >= 3) {
    437    return true;
    438  }
    439  entered++;
    440 
    441  // Add message to history unless it's a multihl, repeated kept or truncated message.
    442  if (!is_multihl
    443      && (s != keep_msg
    444          || (*s != '<' && msg_hist_last != NULL
    445              && strcmp(s, msg_hist_last->msg.items[0].text.data) != 0))) {
    446    msg_hist_add(s, -1, hl_id);
    447  }
    448 
    449  if (!is_multihl) {
    450    msg_start();
    451  }
    452  // Truncate the message if needed.
    453  char *buf = msg_strtrunc(s, false);
    454  if (buf != NULL) {
    455    s = buf;
    456  }
    457 
    458  bool need_clear = true;
    459  if (multiline) {
    460    msg_multiline(cstr_as_string(s), hl_id, false, false, &need_clear);
    461  } else {
    462    msg_outtrans(s, hl_id, false);
    463  }
    464  if (need_clear) {
    465    msg_clr_eos();
    466  }
    467  bool retval = true;
    468  if (!is_multihl) {
    469    retval = msg_end();
    470  }
    471 
    472  if (keep && retval && vim_strsize(s) < (Rows - cmdline_row - 1) * Columns + sc_col) {
    473    set_keep_msg(s, 0);
    474  }
    475 
    476  need_fileinfo = false;
    477 
    478  xfree(buf);
    479  entered--;
    480  return retval;
    481 }
    482 
    483 /// Truncate a string such that it can be printed without causing a scroll.
    484 ///
    485 /// @return  an allocated string or NULL when no truncating is done.
    486 ///
    487 /// @param force  always truncate
    488 char *msg_strtrunc(const char *s, int force)
    489 {
    490  char *buf = NULL;
    491 
    492  // May truncate message to avoid a hit-return prompt
    493  if ((!msg_scroll && !need_wait_return && shortmess(SHM_TRUNCALL)
    494       && !exmode_active && msg_silent == 0 && !ui_has(kUIMessages))
    495      || force) {
    496    int room;
    497    int len = vim_strsize(s);
    498    if (msg_scrolled != 0) {
    499      // Use all the columns.
    500      room = (Rows - msg_row) * Columns - 1;
    501    } else {
    502      // Use up to 'showcmd' column.
    503      room = (Rows - msg_row - 1) * Columns + sc_col - 1;
    504    }
    505    if (len > room && room > 0) {
    506      // may have up to 18 bytes per cell (6 per char, up to two
    507      // composing chars)
    508      len = (room + 2) * 18;
    509      buf = xmalloc((size_t)len);
    510      trunc_string(s, buf, room, len);
    511    }
    512  }
    513  return buf;
    514 }
    515 
    516 /// Truncate a string "s" to "buf" with cell width "room".
    517 /// "s" and "buf" may be equal.
    518 void trunc_string(const char *s, char *buf, int room_in, int buflen)
    519 {
    520  int room = room_in - 3;  // "..." takes 3 chars
    521  int len = 0;
    522  int e;
    523  int i;
    524  int n;
    525 
    526  if (*s == NUL) {
    527    if (buflen > 0) {
    528      *buf = NUL;
    529    }
    530    return;
    531  }
    532 
    533  if (room_in < 3) {
    534    room = 0;
    535  }
    536  int half = room / 2;
    537 
    538  // First part: Start of the string.
    539  for (e = 0; len < half && e < buflen; e++) {
    540    if (s[e] == NUL) {
    541      // text fits without truncating!
    542      buf[e] = NUL;
    543      return;
    544    }
    545    n = ptr2cells(s + e);
    546    if (len + n > half) {
    547      break;
    548    }
    549    len += n;
    550    buf[e] = s[e];
    551    for (n = utfc_ptr2len(s + e); --n > 0;) {
    552      if (++e == buflen) {
    553        break;
    554      }
    555      buf[e] = s[e];
    556    }
    557  }
    558 
    559  // Last part: End of the string.
    560  half = i = (int)strlen(s);
    561  while (true) {
    562    half = half - utf_head_off(s, s + half - 1) - 1;
    563    n = ptr2cells(s + half);
    564    if (len + n > room || half == 0) {
    565      break;
    566    }
    567    len += n;
    568    i = half;
    569  }
    570 
    571  if (i <= e + 3) {
    572    // text fits without truncating
    573    if (s != buf) {
    574      len = (int)strlen(s);
    575      if (len >= buflen) {
    576        len = buflen - 1;
    577      }
    578      len = len - e + 1;
    579      if (len < 1) {
    580        buf[e - 1] = NUL;
    581      } else {
    582        memmove(buf + e, s + e, (size_t)len);
    583      }
    584    }
    585  } else if (e + 3 < buflen) {
    586    // set the middle and copy the last part
    587    memmove(buf + e, "...", 3);
    588    len = (int)strlen(s + i) + 1;
    589    if (len >= buflen - e - 3) {
    590      len = buflen - e - 3 - 1;
    591    }
    592    memmove(buf + e + 3, s + i, (size_t)len);
    593    buf[e + 3 + len - 1] = NUL;
    594  } else {
    595    // can't fit in the "...", just truncate it
    596    buf[buflen - 1] = NUL;
    597  }
    598 }
    599 
    600 /// Shows a printf-style message with highlight id.
    601 ///
    602 /// Note: Caller must check the resulting string is shorter than IOSIZE!!!
    603 ///
    604 /// @see semsg
    605 /// @see swmsg
    606 ///
    607 /// @param s printf-style format message
    608 int smsg(int hl_id, const char *s, ...)
    609  FUNC_ATTR_PRINTF(2, 3)
    610 {
    611  va_list arglist;
    612 
    613  va_start(arglist, s);
    614  vim_vsnprintf(IObuff, IOSIZE, s, arglist);
    615  va_end(arglist);
    616  return msg(IObuff, hl_id);
    617 }
    618 
    619 int smsg_keep(int hl_id, const char *s, ...)
    620  FUNC_ATTR_PRINTF(2, 3)
    621 {
    622  va_list arglist;
    623 
    624  va_start(arglist, s);
    625  vim_vsnprintf(IObuff, IOSIZE, s, arglist);
    626  va_end(arglist);
    627  return msg_keep(IObuff, hl_id, true, false);
    628 }
    629 
    630 // Remember the last sourcing name/lnum used in an error message, so that it
    631 // isn't printed each time when it didn't change.
    632 static int last_sourcing_lnum = 0;
    633 static char *last_sourcing_name = NULL;
    634 
    635 /// Reset the last used sourcing name/lnum.  Makes sure it is displayed again
    636 /// for the next error message;
    637 void reset_last_sourcing(void)
    638 {
    639  XFREE_CLEAR(last_sourcing_name);
    640  last_sourcing_lnum = 0;
    641 }
    642 
    643 /// @return  true if "SOURCING_NAME" differs from "last_sourcing_name".
    644 static bool other_sourcing_name(void)
    645 {
    646  if (HAVE_SOURCING_INFO && SOURCING_NAME != NULL) {
    647    if (last_sourcing_name != NULL) {
    648      return strcmp(SOURCING_NAME, last_sourcing_name) != 0;
    649    }
    650    return true;
    651  }
    652  return false;
    653 }
    654 
    655 /// Get the message about the source, as used for an error message
    656 ///
    657 /// @return [allocated] String with room for one more character. NULL when no
    658 ///                     message is to be given.
    659 static char *get_emsg_source(void)
    660  FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
    661 {
    662  if (HAVE_SOURCING_INFO && SOURCING_NAME != NULL && other_sourcing_name()) {
    663    char *sname = estack_sfile(ESTACK_NONE);
    664    char *tofree = sname;
    665 
    666    if (sname == NULL) {
    667      sname = SOURCING_NAME;
    668    }
    669 
    670    const char *const p = _("Error in %s:");
    671    const size_t buf_len = strlen(sname) + strlen(p) + 1;
    672    char *const buf = xmalloc(buf_len);
    673    snprintf(buf, buf_len, p, sname);
    674    xfree(tofree);
    675    return buf;
    676  }
    677  return NULL;
    678 }
    679 
    680 /// Get the message about the source lnum, as used for an error message.
    681 ///
    682 /// @return [allocated] String with room for one more character. NULL when no
    683 ///                     message is to be given.
    684 static char *get_emsg_lnum(void)
    685  FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
    686 {
    687  // lnum is 0 when executing a command from the command line
    688  // argument, we don't want a line number then
    689  if (SOURCING_NAME != NULL
    690      && (other_sourcing_name() || SOURCING_LNUM != last_sourcing_lnum)
    691      && SOURCING_LNUM != 0) {
    692    const char *const p = _("line %4" PRIdLINENR ":");
    693    const size_t buf_len = 20 + strlen(p);
    694    char *const buf = xmalloc(buf_len);
    695    snprintf(buf, buf_len, p, SOURCING_LNUM);
    696    return buf;
    697  }
    698  return NULL;
    699 }
    700 
    701 /// Display name and line number for the source of an error.
    702 /// Remember the file name and line number, so that for the next error the info
    703 /// is only displayed if it changed.
    704 void msg_source(int hl_id)
    705 {
    706  static bool recursive = false;
    707 
    708  // Bail out if something called here causes an error.
    709  if (recursive) {
    710    return;
    711  }
    712  recursive = true;
    713 
    714  no_wait_return++;
    715  char *p = get_emsg_source();
    716  if (p != NULL) {
    717    msg_scroll = true;  // this will take more than one line
    718    msg(p, hl_id);
    719    xfree(p);
    720  }
    721  p = get_emsg_lnum();
    722  if (p != NULL) {
    723    msg(p, HLF_N);
    724    xfree(p);
    725    last_sourcing_lnum = SOURCING_LNUM;      // only once for each line
    726  }
    727 
    728  // remember the last sourcing name printed, also when it's empty
    729  if (SOURCING_NAME == NULL || other_sourcing_name()) {
    730    XFREE_CLEAR(last_sourcing_name);
    731    if (SOURCING_NAME != NULL) {
    732      last_sourcing_name = xstrdup(SOURCING_NAME);
    733      if (!redirecting()) {
    734        msg_putchar_hl('\n', hl_id);
    735      }
    736    }
    737  }
    738  no_wait_return--;
    739 
    740  recursive = false;
    741 }
    742 
    743 /// @return  true if not giving error messages right now:
    744 ///            If "emsg_off" is set: no error messages at the moment.
    745 ///            If "msg" is in 'debug': do error message but without side effects.
    746 ///            If "emsg_skip" is set: never do error messages.
    747 static int emsg_not_now(void)
    748 {
    749  if ((emsg_off > 0 && vim_strchr(p_debug, 'm') == NULL
    750       && vim_strchr(p_debug, 't') == NULL)
    751      || emsg_skip > 0) {
    752    return true;
    753  }
    754  return false;
    755 }
    756 
    757 bool emsg_multiline(const char *s, const char *kind, int hl_id, bool multiline)
    758 {
    759  bool ignore = false;
    760 
    761  // Skip this if not giving error messages at the moment.
    762  if (emsg_not_now()) {
    763    return true;
    764  }
    765 
    766  called_emsg++;
    767 
    768  // If "emsg_severe" is true: When an error exception is to be thrown,
    769  // prefer this message over previous messages for the same command.
    770  bool severe = emsg_severe;
    771  emsg_severe = false;
    772 
    773  if (!emsg_off || vim_strchr(p_debug, 't') != NULL) {
    774    // Cause a throw of an error exception if appropriate.  Don't display
    775    // the error message in this case.  (If no matching catch clause will
    776    // be found, the message will be displayed later on.)  "ignore" is set
    777    // when the message should be ignored completely (used for the
    778    // interrupt message).
    779    if (cause_errthrow(s, multiline, severe, &ignore)) {
    780      if (!ignore) {
    781        did_emsg++;
    782      }
    783      return true;
    784    }
    785 
    786    if (in_assert_fails && emsg_assert_fails_msg == NULL) {
    787      emsg_assert_fails_msg = xstrdup(s);
    788      emsg_assert_fails_lnum = SOURCING_LNUM;
    789      xfree(emsg_assert_fails_context);
    790      emsg_assert_fails_context = xstrdup(SOURCING_NAME == NULL ? "" : SOURCING_NAME);
    791    }
    792 
    793    // set "v:errmsg", also when using ":silent! cmd"
    794    set_vim_var_string(VV_ERRMSG, s, -1);
    795 
    796    // When using ":silent! cmd" ignore error messages.
    797    // But do write it to the redirection file.
    798    if (emsg_silent != 0) {
    799      if (!emsg_noredir) {
    800        msg_start();
    801        char *p = get_emsg_source();
    802        if (p != NULL) {
    803          const size_t p_len = strlen(p);
    804          p[p_len] = '\n';
    805          redir_write(p, (ptrdiff_t)p_len + 1);
    806          xfree(p);
    807        }
    808        p = get_emsg_lnum();
    809        if (p != NULL) {
    810          const size_t p_len = strlen(p);
    811          p[p_len] = '\n';
    812          redir_write(p, (ptrdiff_t)p_len + 1);
    813          xfree(p);
    814        }
    815        redir_write(s, (ptrdiff_t)strlen(s));
    816      }
    817 
    818      // Log (silent) errors as debug messages.
    819      if (SOURCING_NAME != NULL && SOURCING_LNUM != 0) {
    820        DLOG("(:silent) %s (%s (line %" PRIdLINENR "))",
    821             s, SOURCING_NAME, SOURCING_LNUM);
    822      } else {
    823        DLOG("(:silent) %s", s);
    824      }
    825 
    826      return true;
    827    }
    828 
    829    // Log editor errors as INFO.
    830    if (SOURCING_NAME != NULL && SOURCING_LNUM != 0) {
    831      ILOG("%s (%s (line %" PRIdLINENR "))", s, SOURCING_NAME, SOURCING_LNUM);
    832    } else {
    833      ILOG("%s", s);
    834    }
    835 
    836    ex_exitval = 1;
    837 
    838    // Reset msg_silent, an error causes messages to be switched back on.
    839    msg_silent = 0;
    840    cmd_silent = false;
    841 
    842    if (global_busy) {        // break :global command
    843      global_busy++;
    844    }
    845 
    846    if (p_eb) {
    847      beep_flush();           // also includes flush_buffers()
    848    } else {
    849      flush_buffers(FLUSH_MINIMAL);  // flush internal buffers
    850    }
    851    did_emsg++;               // flag for DoOneCmd()
    852  }
    853 
    854  emsg_on_display = true;     // remember there is an error message
    855  if (msg_scrolled != 0) {
    856    need_wait_return = true;  // needed in case emsg() is called after
    857  }                           // wait_return() has reset need_wait_return
    858                              // and a redraw is expected because
    859                              // msg_scrolled is non-zero
    860  msg_ext_set_kind(kind);
    861 
    862  // Display name and line number for the source of the error.
    863  msg_scroll = true;
    864  bool save_msg_skip_flush = msg_ext_skip_flush;
    865  msg_ext_skip_flush = true;
    866  msg_source(hl_id);
    867 
    868  // Display the error message itself.
    869  msg_nowait = false;  // Wait for this msg.
    870  int rv = msg_keep(s, hl_id, false, multiline);
    871  msg_ext_skip_flush = save_msg_skip_flush;
    872  return rv;
    873 }
    874 
    875 /// emsg() - display an error message
    876 ///
    877 /// Rings the bell, if appropriate, and calls message() to do the real work
    878 /// When terminal not initialized (yet) fprintf(stderr, "%s", ..) is used.
    879 ///
    880 /// @return true if wait_return() not called
    881 bool emsg(const char *s)
    882 {
    883  return emsg_multiline(s, "emsg", HLF_E, false);
    884 }
    885 
    886 void emsg_invreg(int name)
    887 {
    888  semsg(_("E354: Invalid register name: '%s'"), transchar_buf(NULL, name));
    889 }
    890 
    891 /// Print an error message with unknown number of arguments
    892 ///
    893 /// @return whether the message was displayed
    894 bool semsg(const char *const fmt, ...)
    895  FUNC_ATTR_PRINTF(1, 2)
    896 {
    897  bool ret;
    898 
    899  va_list ap;
    900  va_start(ap, fmt);
    901  ret = semsgv(fmt, ap);
    902  va_end(ap);
    903 
    904  return ret;
    905 }
    906 
    907 #define MULTILINE_BUFSIZE 8192
    908 
    909 bool semsg_multiline(const char *kind, const char *const fmt, ...)
    910 {
    911  bool ret;
    912  va_list ap;
    913 
    914  static char errbuf[MULTILINE_BUFSIZE];
    915  if (emsg_not_now()) {
    916    return true;
    917  }
    918 
    919  va_start(ap, fmt);
    920  vim_vsnprintf(errbuf, sizeof(errbuf), fmt, ap);
    921  va_end(ap);
    922 
    923  ret = emsg_multiline(errbuf, kind, HLF_E, true);
    924 
    925  return ret;
    926 }
    927 
    928 /// Print an error message with unknown number of arguments
    929 static bool semsgv(const char *fmt, va_list ap)
    930 {
    931  static char errbuf[IOSIZE];
    932  if (emsg_not_now()) {
    933    return true;
    934  }
    935 
    936  vim_vsnprintf(errbuf, sizeof(errbuf), fmt, ap);
    937 
    938  return emsg(errbuf);
    939 }
    940 
    941 /// Same as emsg(...), but abort on error when ABORT_ON_INTERNAL_ERROR is
    942 /// defined. It is used for internal errors only, so that they can be
    943 /// detected when fuzzing vim.
    944 void iemsg(const char *s)
    945 {
    946  if (emsg_not_now()) {
    947    return;
    948  }
    949 
    950  emsg(s);
    951 #ifdef ABORT_ON_INTERNAL_ERROR
    952  set_vim_var_string(VV_ERRMSG, s, -1);
    953  msg_putchar('\n');  // avoid overwriting the error message
    954  ui_flush();
    955  abort();
    956 #endif
    957 }
    958 
    959 /// Same as semsg(...) but abort on error when ABORT_ON_INTERNAL_ERROR is
    960 /// defined. It is used for internal errors only, so that they can be
    961 /// detected when fuzzing vim.
    962 void siemsg(const char *s, ...)
    963 {
    964  if (emsg_not_now()) {
    965    return;
    966  }
    967 
    968  va_list ap;
    969  va_start(ap, s);
    970  semsgv(s, ap);
    971  va_end(ap);
    972 #ifdef ABORT_ON_INTERNAL_ERROR
    973  msg_putchar('\n');  // avoid overwriting the error message
    974  ui_flush();
    975  abort();
    976 #endif
    977 }
    978 
    979 /// Give an "Internal error" message.
    980 void internal_error(const char *where)
    981 {
    982  siemsg(_(e_intern2), where);
    983 }
    984 
    985 static void msg_semsg_event(void **argv)
    986 {
    987  char *s = argv[0];
    988  emsg(s);
    989  xfree(s);
    990 }
    991 
    992 void msg_schedule_semsg(const char *const fmt, ...)
    993  FUNC_ATTR_PRINTF(1, 2)
    994 {
    995  va_list ap;
    996  va_start(ap, fmt);
    997  vim_vsnprintf(IObuff, IOSIZE, fmt, ap);
    998  va_end(ap);
    999 
   1000  char *s = xstrdup(IObuff);
   1001  loop_schedule_deferred(&main_loop, event_create(msg_semsg_event, s));
   1002 }
   1003 
   1004 static void msg_semsg_multiline_event(void **argv)
   1005 {
   1006  char *s = argv[0];
   1007  emsg_multiline(s, "emsg", HLF_E, true);
   1008  xfree(s);
   1009 }
   1010 
   1011 void msg_schedule_semsg_multiline(const char *const fmt, ...)
   1012 {
   1013  va_list ap;
   1014  va_start(ap, fmt);
   1015  vim_vsnprintf(IObuff, IOSIZE, fmt, ap);
   1016  va_end(ap);
   1017 
   1018  char *s = xstrdup(IObuff);
   1019  loop_schedule_deferred(&main_loop, event_create(msg_semsg_multiline_event, s));
   1020 }
   1021 
   1022 /// Like msg(), but truncate to a single line if p_shm contains 't', or when
   1023 /// "force" is true.  This truncates in another way as for normal messages.
   1024 /// Careful: The string may be changed by msg_may_trunc()!
   1025 ///
   1026 /// @return  a pointer to the printed message, if wait_return() not called.
   1027 char *msg_trunc(char *s, bool force, int hl_id)
   1028 {
   1029  // Add message to history before truncating.
   1030  msg_hist_add(s, -1, hl_id);
   1031 
   1032  char *ts = msg_may_trunc(force, s);
   1033 
   1034  msg_hist_off = true;
   1035  bool n = msg(ts, hl_id);
   1036  msg_hist_off = false;
   1037 
   1038  if (n) {
   1039    return ts;
   1040  }
   1041  return NULL;
   1042 }
   1043 
   1044 /// Check if message "s" should be truncated at the start (for filenames).
   1045 ///
   1046 /// @return  a pointer to where the truncated message starts.
   1047 ///
   1048 /// @note: May change the message by replacing a character with '<'.
   1049 char *msg_may_trunc(bool force, char *s)
   1050 {
   1051  if (ui_has(kUIMessages)) {
   1052    return s;
   1053  }
   1054 
   1055  // If something unexpected happened "room" may be negative, check for that
   1056  // just in case.
   1057  int room = (Rows - cmdline_row - 1) * Columns + sc_col - 1;
   1058  if (room > 0
   1059      && (force || (shortmess(SHM_TRUNC) && !exmode_active))
   1060      && (int)strlen(s) - room > 0) {
   1061    int size = vim_strsize(s);
   1062 
   1063    // There may be room anyway when there are multibyte chars.
   1064    if (size <= room) {
   1065      return s;
   1066    }
   1067    int n;
   1068    for (n = 0; size >= room;) {
   1069      size -= utf_ptr2cells(s + n);
   1070      n += utfc_ptr2len(s + n);
   1071    }
   1072    n--;
   1073    s += n;
   1074    *s = '<';
   1075  }
   1076  return s;
   1077 }
   1078 
   1079 void hl_msg_free(HlMessage hl_msg)
   1080 {
   1081  for (size_t i = 0; i < kv_size(hl_msg); i++) {
   1082    xfree(kv_A(hl_msg, i).text.data);
   1083  }
   1084  kv_destroy(hl_msg);
   1085 }
   1086 
   1087 /// Add the message at the end of the history
   1088 ///
   1089 /// @param[in]  len  Length of s or -1.
   1090 static void msg_hist_add(const char *s, int len, int hl_id)
   1091 {
   1092  String text = { .size = len < 0 ? strlen(s) : (size_t)len };
   1093  // Remove leading and trailing newlines.
   1094  while (text.size > 0 && *s == '\n') {
   1095    text.size--;
   1096    s++;
   1097  }
   1098  while (text.size > 0 && s[text.size - 1] == '\n') {
   1099    text.size--;
   1100  }
   1101  if (text.size == 0) {
   1102    return;
   1103  }
   1104  text.data = xmemdupz(s, text.size);
   1105 
   1106  HlMessage msg = KV_INITIAL_VALUE;
   1107  kv_push(msg, ((HlMessageChunk){ text, hl_id }));
   1108  msg_hist_add_multihl(msg, false, NULL);
   1109 }
   1110 
   1111 static bool do_clear_hist_temp = true;
   1112 
   1113 void do_autocmd_progress(MsgID msg_id, HlMessage msg, MessageData *msg_data)
   1114 {
   1115  if (!has_event(EVENT_PROGRESS)) {
   1116    return;
   1117  }
   1118 
   1119  MAXSIZE_TEMP_DICT(data, 7);
   1120  ArrayOf(String) messages = ARRAY_DICT_INIT;
   1121  for (size_t i = 0; i < msg.size; i++) {
   1122    ADD(messages, STRING_OBJ(msg.items[i].text));
   1123  }
   1124 
   1125  PUT_C(data, "id", OBJECT_OBJ(msg_id));
   1126  PUT_C(data, "text", ARRAY_OBJ(messages));
   1127  if (msg_data != NULL) {
   1128    PUT_C(data, "percent", INTEGER_OBJ(msg_data->percent));
   1129    PUT_C(data, "status", STRING_OBJ(msg_data->status));
   1130    PUT_C(data, "title", STRING_OBJ(msg_data->title));
   1131    PUT_C(data, "data", DICT_OBJ(msg_data->data));
   1132  }
   1133 
   1134  apply_autocmds_group(EVENT_PROGRESS, msg_data ? msg_data->title.data : "", NULL, true,
   1135                       AUGROUP_ALL, NULL, NULL, &DICT_OBJ(data));
   1136  kv_destroy(messages);
   1137 }
   1138 
   1139 static void msg_hist_add_multihl(HlMessage msg, bool temp, MessageData *msg_data)
   1140 {
   1141  if (do_clear_hist_temp) {
   1142    msg_hist_clear_temp();
   1143    do_clear_hist_temp = false;
   1144  }
   1145 
   1146  if (msg_hist_off || msg_silent != 0) {
   1147    hl_msg_free(msg);
   1148    return;
   1149  }
   1150 
   1151  // Allocate an entry and add the message at the end of the history.
   1152  MessageHistoryEntry *entry = xmalloc(sizeof(MessageHistoryEntry));
   1153  entry->msg = msg;
   1154  entry->temp = temp;
   1155  entry->kind = msg_ext_kind;
   1156  entry->prev = msg_hist_last;
   1157  entry->next = NULL;
   1158  // NOTE: this does not encode if the message was actually appended to the
   1159  // previous entry in the message history. However append is currently only
   1160  // true for :echon, which is stored in the history as a temporary entry for
   1161  // "g<" where it is guaranteed to be after the entry it was appended to.
   1162  entry->append = msg_ext_append;
   1163 
   1164  if (msg_hist_first == NULL) {
   1165    msg_hist_first = entry;
   1166  }
   1167  if (msg_hist_last != NULL) {
   1168    msg_hist_last->next = entry;
   1169  }
   1170  if (msg_hist_temp == NULL) {
   1171    msg_hist_temp = entry;
   1172  }
   1173 
   1174  msg_hist_len += !temp;
   1175  msg_hist_last = entry;
   1176  msg_ext_history = true;
   1177 
   1178  msg_hist_clear(msg_hist_max);
   1179 }
   1180 
   1181 static void msg_hist_free_msg(MessageHistoryEntry *entry)
   1182 {
   1183  if (entry->next == NULL) {
   1184    msg_hist_last = entry->prev;
   1185  } else {
   1186    entry->next->prev = entry->prev;
   1187  }
   1188  if (entry->prev == NULL) {
   1189    msg_hist_first = entry->next;
   1190  } else {
   1191    entry->prev->next = entry->next;
   1192  }
   1193  if (entry == msg_hist_temp) {
   1194    msg_hist_temp = entry->next;
   1195  }
   1196  hl_msg_free(entry->msg);
   1197  xfree(entry);
   1198 }
   1199 
   1200 /// Delete oldest messages from the history until there are "keep" messages.
   1201 void msg_hist_clear(int keep)
   1202 {
   1203  while (msg_hist_len > keep || (keep == 0 && msg_hist_first != NULL)) {
   1204    msg_hist_len -= !msg_hist_first->temp;
   1205    msg_hist_free_msg(msg_hist_first);
   1206  }
   1207 }
   1208 
   1209 void msg_hist_clear_temp(void)
   1210 {
   1211  while (msg_hist_temp != NULL) {
   1212    MessageHistoryEntry *next = msg_hist_temp->next;
   1213    if (msg_hist_temp->temp) {
   1214      msg_hist_free_msg(msg_hist_temp);
   1215    }
   1216    msg_hist_temp = next;
   1217  }
   1218 }
   1219 
   1220 int messagesopt_changed(void)
   1221 {
   1222  int messages_flags_new = 0;
   1223  int messages_wait_new = 0;
   1224  int messages_history_new = 0;
   1225 
   1226  char *p = p_mopt;
   1227  while (*p != NUL) {
   1228    if (strnequal(p, S_LEN(MESSAGES_OPT_HIT_ENTER))) {
   1229      p += STRLEN_LITERAL(MESSAGES_OPT_HIT_ENTER);
   1230      messages_flags_new |= kOptMoptFlagHitEnter;
   1231    } else if (strnequal(p, S_LEN(MESSAGES_OPT_WAIT))
   1232               && ascii_isdigit(p[STRLEN_LITERAL(MESSAGES_OPT_WAIT)])) {
   1233      p += STRLEN_LITERAL(MESSAGES_OPT_WAIT);
   1234      messages_wait_new = getdigits_int(&p, false, INT_MAX);
   1235      messages_flags_new |= kOptMoptFlagWait;
   1236    } else if (strnequal(p, S_LEN(MESSAGES_OPT_HISTORY))
   1237               && ascii_isdigit(p[STRLEN_LITERAL(MESSAGES_OPT_HISTORY)])) {
   1238      p += STRLEN_LITERAL(MESSAGES_OPT_HISTORY);
   1239      messages_history_new = getdigits_int(&p, false, INT_MAX);
   1240      messages_flags_new |= kOptMoptFlagHistory;
   1241    }
   1242 
   1243    if (*p != ',' && *p != NUL) {
   1244      return FAIL;
   1245    }
   1246    if (*p == ',') {
   1247      p++;
   1248    }
   1249  }
   1250 
   1251  // Either "wait" or "hit-enter" is required
   1252  if (!(messages_flags_new & (kOptMoptFlagHitEnter | kOptMoptFlagWait))) {
   1253    return FAIL;
   1254  }
   1255 
   1256  // "history" must be set
   1257  if (!(messages_flags_new & kOptMoptFlagHistory)) {
   1258    return FAIL;
   1259  }
   1260 
   1261  assert(messages_history_new >= 0);
   1262  // "history" must be <= 10000
   1263  if (messages_history_new > 10000) {
   1264    return FAIL;
   1265  }
   1266 
   1267  assert(messages_wait_new >= 0);
   1268  // "wait" must be <= 10000
   1269  if (messages_wait_new > 10000) {
   1270    return FAIL;
   1271  }
   1272 
   1273  msg_flags = messages_flags_new;
   1274  msg_wait = messages_wait_new;
   1275 
   1276  msg_hist_max = messages_history_new;
   1277  msg_hist_clear(msg_hist_max);
   1278 
   1279  return OK;
   1280 }
   1281 
   1282 /// :messages command implementation
   1283 void ex_messages(exarg_T *eap)
   1284  FUNC_ATTR_NONNULL_ALL
   1285 {
   1286  if (strcmp(eap->arg, "clear") == 0) {
   1287    msg_hist_clear(eap->addr_count ? eap->line2 : 0);
   1288    return;
   1289  }
   1290 
   1291  if (*eap->arg != NUL) {
   1292    emsg(_(e_invarg));
   1293    return;
   1294  }
   1295 
   1296  Array entries = ARRAY_DICT_INIT;
   1297  MessageHistoryEntry *p = eap->skip ? msg_hist_temp : msg_hist_first;
   1298  int skip = eap->addr_count ? (msg_hist_len - eap->line2) : 0;
   1299  for (; p != NULL; p = p->next) {
   1300    // Skip over count or temporary "g<" messages.
   1301    if ((p->temp && !eap->skip) || skip-- > 0) {
   1302      continue;
   1303    }
   1304    if (ui_has(kUIMessages) && !msg_silent) {
   1305      Array entry = ARRAY_DICT_INIT;
   1306      ADD(entry, CSTR_TO_OBJ(p->kind));
   1307      Array content = ARRAY_DICT_INIT;
   1308      for (uint32_t i = 0; i < kv_size(p->msg); i++) {
   1309        HlMessageChunk chunk = kv_A(p->msg, i);
   1310        Array content_entry = ARRAY_DICT_INIT;
   1311        ADD(content_entry, INTEGER_OBJ(chunk.hl_id ? syn_id2attr(chunk.hl_id) : 0));
   1312        ADD(content_entry, STRING_OBJ(copy_string(chunk.text, NULL)));
   1313        ADD(content_entry, INTEGER_OBJ(chunk.hl_id));
   1314        ADD(content, ARRAY_OBJ(content_entry));
   1315      }
   1316      ADD(entry, ARRAY_OBJ(content));
   1317      ADD(entry, BOOLEAN_OBJ(p->append));
   1318      ADD(entries, ARRAY_OBJ(entry));
   1319    }
   1320    if (redirecting() || !ui_has(kUIMessages)) {
   1321      msg_silent += ui_has(kUIMessages);
   1322      bool needs_clear = false;
   1323      msg_multihl(INTEGER_OBJ(0), p->msg, p->kind, false, false, NULL, &needs_clear);
   1324      msg_silent -= ui_has(kUIMessages);
   1325    }
   1326  }
   1327  if (kv_size(entries) > 0) {
   1328    ui_call_msg_history_show(entries, eap->skip != 0);
   1329    api_free_array(entries);
   1330  }
   1331 }
   1332 
   1333 /// Call this after prompting the user.  This will avoid a hit-return message
   1334 /// and a delay.
   1335 void msg_end_prompt(void)
   1336 {
   1337  need_wait_return = false;
   1338  emsg_on_display = false;
   1339  cmdline_row = msg_row;
   1340  msg_col = 0;
   1341  msg_clr_eos();
   1342  lines_left = -1;
   1343 }
   1344 
   1345 /// Wait for the user to hit a key (normally Enter)
   1346 ///
   1347 /// @param redraw  if true, redraw the entire screen UPD_NOT_VALID
   1348 ///                if false, do a normal redraw
   1349 ///                if -1, don't redraw at all
   1350 void wait_return(int redraw)
   1351 {
   1352  int c;
   1353  int had_got_int;
   1354  FILE *save_scriptout;
   1355 
   1356  if (redraw == true) {
   1357    redraw_all_later(UPD_NOT_VALID);
   1358  }
   1359 
   1360  if (ui_has(kUIMessages)) {
   1361    prompt_for_input("Press any key to continue", HLF_M, true, NULL);
   1362    return;
   1363  }
   1364 
   1365  // If using ":silent cmd", don't wait for a return.  Also don't set
   1366  // need_wait_return to do it later.
   1367  if (msg_silent != 0) {
   1368    return;
   1369  }
   1370 
   1371  if (headless_mode && !ui_active()) {
   1372    return;
   1373  }
   1374 
   1375  // When inside vgetc(), we can't wait for a typed character at all.
   1376  // With the global command (and some others) we only need one return at
   1377  // the end. Adjust cmdline_row to avoid the next message overwriting the
   1378  // last one.
   1379  if (vgetc_busy > 0) {
   1380    return;
   1381  }
   1382  need_wait_return = true;
   1383  if (no_wait_return) {
   1384    if (!exmode_active) {
   1385      cmdline_row = msg_row;
   1386    }
   1387    return;
   1388  }
   1389 
   1390  redir_off = true;             // don't redirect this message
   1391  int oldState = State;
   1392  if (quit_more) {
   1393    c = CAR;                    // just pretend CR was hit
   1394    quit_more = false;
   1395    got_int = false;
   1396  } else if (exmode_active) {
   1397    msg_puts(" ");              // make sure the cursor is on the right line
   1398    c = CAR;                    // no need for a return in ex mode
   1399    got_int = false;
   1400  } else if (!stuff_empty()) {
   1401    // When there are stuffed characters, the next stuffed character will
   1402    // dismiss the hit-enter prompt immediately and have to be put back, so
   1403    // instead just don't show the hit-enter prompt at all.
   1404    c = CAR;
   1405  } else {
   1406    State = MODE_HITRETURN;
   1407    setmouse();
   1408    cmdline_row = msg_row;
   1409    // Avoid the sequence that the user types ":" at the hit-return prompt
   1410    // to start an Ex command, but the file-changed dialog gets in the
   1411    // way.
   1412    if (need_check_timestamps) {
   1413      check_timestamps(false);
   1414    }
   1415 
   1416    // if cmdheight=0, we need to scroll in the first line of msg_grid upon the screen
   1417    if (p_ch == 0 && !ui_has(kUIMessages) && !msg_scrolled) {
   1418      msg_grid_validate();
   1419      msg_scroll_up(false, true);
   1420      msg_scrolled++;
   1421      cmdline_row = Rows - 1;
   1422    }
   1423 
   1424    if (msg_flags & kOptMoptFlagHitEnter) {
   1425      hit_return_msg(true);
   1426 
   1427      do {
   1428        // Remember "got_int", if it is set vgetc() probably returns a
   1429        // CTRL-C, but we need to loop then.
   1430        had_got_int = got_int;
   1431 
   1432        // Don't do mappings here, we put the character back in the
   1433        // typeahead buffer.
   1434        no_mapping++;
   1435        allow_keys++;
   1436 
   1437        // Temporarily disable Recording. If Recording is active, the
   1438        // character will be recorded later, since it will be added to the
   1439        // typebuf after the loop
   1440        const int save_reg_recording = reg_recording;
   1441        save_scriptout = scriptout;
   1442        reg_recording = 0;
   1443        scriptout = NULL;
   1444        c = safe_vgetc();
   1445        if (had_got_int && !global_busy) {
   1446          got_int = false;
   1447        }
   1448        no_mapping--;
   1449        allow_keys--;
   1450        reg_recording = save_reg_recording;
   1451        scriptout = save_scriptout;
   1452 
   1453        // Allow scrolling back in the messages.
   1454        // Also accept scroll-down commands when messages fill the screen,
   1455        // to avoid that typing one 'j' too many makes the messages
   1456        // disappear.
   1457        if (p_more) {
   1458          if (c == 'b' || c == Ctrl_B || c == 'k' || c == 'u' || c == 'g'
   1459              || c == K_UP || c == K_PAGEUP) {
   1460            if (msg_scrolled > Rows) {
   1461              // scroll back to show older messages
   1462              do_more_prompt(c);
   1463            } else {
   1464              msg_didout = false;
   1465              c = K_IGNORE;
   1466              msg_col = 0;
   1467            }
   1468            if (quit_more) {
   1469              c = CAR;  // just pretend CR was hit
   1470              quit_more = false;
   1471              got_int = false;
   1472            } else if (c != K_IGNORE) {
   1473              c = K_IGNORE;
   1474              hit_return_msg(false);
   1475            }
   1476          } else if (msg_scrolled > Rows - 2
   1477                     && (c == 'j' || c == 'd' || c == 'f' || c == Ctrl_F
   1478                         || c == K_DOWN || c == K_PAGEDOWN)) {
   1479            c = K_IGNORE;
   1480          }
   1481        }
   1482      } while ((had_got_int && c == Ctrl_C)
   1483               || c == K_IGNORE
   1484               || c == K_LEFTDRAG || c == K_LEFTRELEASE
   1485               || c == K_MIDDLEDRAG || c == K_MIDDLERELEASE
   1486               || c == K_RIGHTDRAG || c == K_RIGHTRELEASE
   1487               || c == K_MOUSELEFT || c == K_MOUSERIGHT
   1488               || c == K_MOUSEDOWN || c == K_MOUSEUP
   1489               || c == K_MOUSEMOVE);
   1490      os_breakcheck();
   1491 
   1492      // Avoid that the mouse-up event causes visual mode to start.
   1493      if (c == K_LEFTMOUSE || c == K_MIDDLEMOUSE || c == K_RIGHTMOUSE
   1494          || c == K_X1MOUSE || c == K_X2MOUSE) {
   1495        jump_to_mouse(MOUSE_SETPOS, NULL, 0);
   1496      } else if (vim_strchr("\r\n ", c) == NULL && c != Ctrl_C && c != 'q') {
   1497        // Put the character back in the typeahead buffer.  Don't use the
   1498        // stuff buffer, because lmaps wouldn't work.
   1499        ins_char_typebuf(vgetc_char, vgetc_mod_mask, true);
   1500        do_redraw = true;  // need a redraw even though there is typeahead
   1501      }
   1502    } else {
   1503      c = CAR;
   1504      // Wait to allow the user to verify the output.
   1505      do_sleep(msg_wait, true);
   1506    }
   1507  }
   1508  redir_off = false;
   1509 
   1510  // If the user hits ':', '?' or '/' we get a command line from the next
   1511  // line.
   1512  if (c == ':' || c == '?' || c == '/') {
   1513    if (!exmode_active) {
   1514      cmdline_row = msg_row;
   1515    }
   1516    skip_redraw = true;  // skip redraw once
   1517    do_redraw = false;
   1518  }
   1519 
   1520  // If the screen size changed screen_resize() will redraw the screen.
   1521  // Otherwise the screen is only redrawn if 'redraw' is set and no ':'
   1522  // typed.
   1523  int tmpState = State;
   1524  State = oldState;  // restore State before screen_resize()
   1525  setmouse();
   1526  msg_check();
   1527  need_wait_return = false;
   1528  did_wait_return = true;
   1529  emsg_on_display = false;      // can delete error message now
   1530  lines_left = -1;              // reset lines_left at next msg_start()
   1531  reset_last_sourcing();
   1532  if (keep_msg != NULL && vim_strsize(keep_msg) >=
   1533      (Rows - cmdline_row - 1) * Columns + sc_col) {
   1534    XFREE_CLEAR(keep_msg);          // don't redisplay message, it's too long
   1535  }
   1536 
   1537  if (tmpState == MODE_SETWSIZE) {       // got resize event while in vgetc()
   1538    ui_refresh();
   1539  } else if (!skip_redraw) {
   1540    if (redraw == true || (msg_scrolled != 0 && redraw != -1)) {
   1541      redraw_later(curwin, UPD_VALID);
   1542    }
   1543  }
   1544 }
   1545 
   1546 /// Write the hit-return prompt.
   1547 ///
   1548 /// @param newline_sb  if starting a new line, add it to the scrollback.
   1549 static void hit_return_msg(bool newline_sb)
   1550 {
   1551  int save_p_more = p_more;
   1552 
   1553  if (!newline_sb) {
   1554    p_more = false;
   1555  }
   1556  if (msg_didout) {     // start on a new line
   1557    msg_putchar('\n');
   1558  }
   1559  p_more = false;       // don't want to see this message when scrolling back
   1560  if (got_int) {
   1561    msg_puts(_("Interrupt: "));
   1562  }
   1563 
   1564  msg_puts_hl(_("Press ENTER or type command to continue"), HLF_R, false);
   1565  if (!msg_use_printf()) {
   1566    msg_clr_eos();
   1567  }
   1568  p_more = save_p_more;
   1569 }
   1570 
   1571 /// Set "keep_msg" to "s".  Free the old value and check for NULL pointer.
   1572 void set_keep_msg(const char *s, int hl_id)
   1573 {
   1574  // Kept message is not cleared and re-emitted with ext_messages: #20416.
   1575  if (ui_has(kUIMessages)) {
   1576    return;
   1577  }
   1578 
   1579  xfree(keep_msg);
   1580  if (s != NULL && msg_silent == 0) {
   1581    keep_msg = xstrdup(s);
   1582  } else {
   1583    keep_msg = NULL;
   1584  }
   1585  keep_msg_more = false;
   1586  keep_msg_hl_id = hl_id;
   1587 }
   1588 
   1589 /// Return true if printing messages should currently be done.
   1590 bool messaging(void)
   1591 {
   1592  // TODO(bfredl): with general support for "async" messages with p_ch,
   1593  // this should be re-enabled.
   1594  return !(p_lz && char_avail() && !KeyTyped) && (p_ch > 0 || ui_has(kUIMessages));
   1595 }
   1596 
   1597 void msgmore(int n)
   1598 {
   1599  int pn;
   1600 
   1601  if (global_busy           // no messages now, wait until global is finished
   1602      || !messaging()) {      // 'lazyredraw' set, don't do messages now
   1603    return;
   1604  }
   1605 
   1606  // We don't want to overwrite another important message, but do overwrite
   1607  // a previous "more lines" or "fewer lines" message, so that "5dd" and
   1608  // then "put" reports the last action.
   1609  if (keep_msg != NULL && !keep_msg_more) {
   1610    return;
   1611  }
   1612 
   1613  pn = abs(n);
   1614 
   1615  if (pn > p_report) {
   1616    if (n > 0) {
   1617      vim_snprintf(msg_buf, MSG_BUF_LEN,
   1618                   NGETTEXT("%d more line", "%d more lines", pn),
   1619                   pn);
   1620    } else {
   1621      vim_snprintf(msg_buf, MSG_BUF_LEN,
   1622                   NGETTEXT("%d line less", "%d fewer lines", pn),
   1623                   pn);
   1624    }
   1625    if (got_int) {
   1626      xstrlcat(msg_buf, _(" (Interrupted)"), MSG_BUF_LEN);
   1627    }
   1628    if (msg(msg_buf, 0)) {
   1629      set_keep_msg(msg_buf, 0);
   1630      keep_msg_more = true;
   1631    }
   1632  }
   1633 }
   1634 
   1635 static int redir_col = 0;  // Message column used in redir_write().
   1636 void msg_ext_set_kind(const char *msg_kind)
   1637 {
   1638  // Don't change the label of an existing batch:
   1639  msg_ext_ui_flush();
   1640 
   1641  // TODO(bfredl): would be nice to avoid dynamic scoping, but that would
   1642  // need refactoring the msg_ interface to not be "please pretend nvim is
   1643  // a terminal for a moment"
   1644  msg_ext_kind = msg_kind;
   1645 
   1646  // Need to reset the redirection column at the start of a message, for which
   1647  // leading newlines are responsible without kUIMessages. Unrelated to setting
   1648  // the kind but this is called more consistently at the start of a message
   1649  // than msg_start() at this point.
   1650  redir_col = msg_ext_append ? redir_col : 0;
   1651 }
   1652 
   1653 /// Prepare for outputting characters in the command line.
   1654 void msg_start(void)
   1655 {
   1656  bool did_return = false;
   1657 
   1658  msg_row = MAX(msg_row, cmdline_row);
   1659 
   1660  if (!msg_silent) {
   1661    XFREE_CLEAR(keep_msg);              // don't display old message now
   1662    need_fileinfo = false;
   1663  }
   1664 
   1665  if (need_highlight_changed) {
   1666    highlight_changed();
   1667  }
   1668 
   1669  if (need_clr_eos || (p_ch == 0 && redrawing_cmdline)) {
   1670    // Halfway an ":echo" command and getting an (error) message: clear
   1671    // any text from the command.
   1672    need_clr_eos = false;
   1673    msg_clr_eos();
   1674  }
   1675 
   1676  // if cmdheight=0, we need to scroll in the first line of msg_grid upon the screen
   1677  if (p_ch == 0 && !ui_has(kUIMessages) && !msg_scrolled) {
   1678    msg_grid_validate();
   1679    msg_scroll_up(false, true);
   1680    msg_scrolled++;
   1681    cmdline_row = Rows - 1;
   1682  }
   1683 
   1684  if (!msg_scroll && full_screen) {     // overwrite last message
   1685    msg_row = cmdline_row;
   1686    msg_col = 0;
   1687  } else if ((msg_didout || p_ch == 0) && !ui_has(kUIMessages)) {  // start message on next line
   1688    msg_putchar('\n');
   1689    did_return = true;
   1690    cmdline_row = msg_row;
   1691  }
   1692  if (!msg_didany || lines_left < 0) {
   1693    msg_starthere();
   1694  }
   1695  if (msg_silent == 0) {
   1696    msg_didout = false;                     // no output on current line yet
   1697  }
   1698 
   1699  if (ui_has(kUIMessages)) {
   1700    msg_ext_ui_flush();
   1701  }
   1702 
   1703  // When redirecting, may need to start a new line.
   1704  if (!did_return) {
   1705    redir_write("\n", 1);
   1706  }
   1707 }
   1708 
   1709 /// Note that the current msg position is where messages start.
   1710 void msg_starthere(void)
   1711 {
   1712  lines_left = cmdline_row;
   1713  msg_didany = false;
   1714 }
   1715 
   1716 void msg_putchar(int c)
   1717 {
   1718  msg_putchar_hl(c, 0);
   1719 }
   1720 
   1721 void msg_putchar_hl(int c, int hl_id)
   1722 {
   1723  char buf[MB_MAXCHAR + 1];
   1724 
   1725  if (IS_SPECIAL(c)) {
   1726    buf[0] = (char)K_SPECIAL;
   1727    buf[1] = (char)K_SECOND(c);
   1728    buf[2] = (char)K_THIRD(c);
   1729    buf[3] = NUL;
   1730  } else {
   1731    buf[utf_char2bytes(c, buf)] = NUL;
   1732  }
   1733  msg_puts_hl(buf, hl_id, false);
   1734 }
   1735 
   1736 void msg_outnum(int n)
   1737 {
   1738  char buf[20];
   1739 
   1740  snprintf(buf, sizeof(buf), "%d", n);
   1741  msg_puts(buf);
   1742 }
   1743 
   1744 void msg_home_replace(const char *fname)
   1745 {
   1746  msg_home_replace_hl(fname, 0);
   1747 }
   1748 
   1749 static void msg_home_replace_hl(const char *fname, int hl_id)
   1750 {
   1751  char *name = home_replace_save(NULL, fname);
   1752  msg_outtrans(name, hl_id, false);
   1753  xfree(name);
   1754 }
   1755 
   1756 /// Output "len" characters in "str" (including NULs) with translation
   1757 /// if "len" is -1, output up to a NUL character. Use highlight "hl_id".
   1758 ///
   1759 /// @return  the number of characters it takes on the screen.
   1760 int msg_outtrans(const char *str, int hl_id, bool hist)
   1761 {
   1762  return msg_outtrans_len(str, (int)strlen(str), hl_id, hist);
   1763 }
   1764 
   1765 /// Output one character at "p".
   1766 /// Handles multi-byte characters.
   1767 ///
   1768 /// @return  pointer to the next character.
   1769 const char *msg_outtrans_one(const char *p, int hl_id, bool hist)
   1770 {
   1771  int l;
   1772 
   1773  if ((l = utfc_ptr2len(p)) > 1) {
   1774    msg_outtrans_len(p, l, hl_id, hist);
   1775    return p + l;
   1776  }
   1777  msg_puts_hl(transchar_byte_buf(NULL, (uint8_t)(*p)), hl_id, hist);
   1778  return p + 1;
   1779 }
   1780 
   1781 int msg_outtrans_len(const char *msgstr, int len, int hl_id, bool hist)
   1782 {
   1783  int retval = 0;
   1784  const char *str = msgstr;
   1785  const char *plain_start = msgstr;
   1786  char *s;
   1787  int c;
   1788  int save_got_int = got_int;
   1789 
   1790  // Only quit when got_int was set in here.
   1791  got_int = false;
   1792 
   1793  if (hist) {
   1794    msg_hist_add(str, len, hl_id);
   1795  }
   1796 
   1797  // When drawing over the command line no need to clear it later or remove
   1798  // the mode message.
   1799  if (msg_silent == 0 && len > 0 && msg_row >= cmdline_row && msg_col == 0) {
   1800    clear_cmdline = false;
   1801    mode_displayed = false;
   1802  }
   1803 
   1804  // Go over the string.  Special characters are translated and printed.
   1805  // Normal characters are printed several at a time.
   1806  while (--len >= 0 && !got_int) {
   1807    // Don't include composing chars after the end.
   1808    int mb_l = utfc_ptr2len_len(str, len + 1);
   1809    if (mb_l > 1) {
   1810      c = utf_ptr2char(str);
   1811      if (vim_isprintc(c)) {
   1812        // Printable multi-byte char: count the cells.
   1813        retval += utf_ptr2cells(str);
   1814      } else {
   1815        // Unprintable multi-byte char: print the printable chars so
   1816        // far and the translation of the unprintable char.
   1817        if (str > plain_start) {
   1818          msg_puts_len(plain_start, str - plain_start, hl_id, hist);
   1819        }
   1820        plain_start = str + mb_l;
   1821        msg_puts_hl(transchar_buf(NULL, c), hl_id == 0 ? HLF_8 : hl_id, false);
   1822        retval += char2cells(c);
   1823      }
   1824      len -= mb_l - 1;
   1825      str += mb_l;
   1826    } else {
   1827      s = transchar_byte_buf(NULL, (uint8_t)(*str));
   1828      if (s[1] != NUL) {
   1829        // Unprintable char: print the printable chars so far and the
   1830        // translation of the unprintable char.
   1831        if (str > plain_start) {
   1832          msg_puts_len(plain_start, str - plain_start, hl_id, hist);
   1833        }
   1834        plain_start = str + 1;
   1835        msg_puts_hl(s, hl_id == 0 ? HLF_8 : hl_id, false);
   1836        retval += (int)strlen(s);
   1837      } else {
   1838        retval++;
   1839      }
   1840      str++;
   1841    }
   1842  }
   1843 
   1844  if ((str > plain_start || plain_start == msgstr) && !got_int) {
   1845    // Print the printable chars at the end (or emit empty string).
   1846    msg_puts_len(plain_start, str - plain_start, hl_id, hist);
   1847  }
   1848 
   1849  got_int |= save_got_int;
   1850 
   1851  return retval;
   1852 }
   1853 
   1854 void msg_make(const char *arg)
   1855 {
   1856  int i;
   1857  static const char *str = "eeffoc";
   1858  static const char *rs = "Plon#dqg#vxjduB";
   1859 
   1860  arg = skipwhite(arg);
   1861  for (i = 5; *arg && i >= 0; i--) {
   1862    if (*arg++ != str[i]) {
   1863      break;
   1864    }
   1865  }
   1866  if (i < 0) {
   1867    msg_putchar('\n');
   1868    for (i = 0; rs[i]; i++) {
   1869      msg_putchar(rs[i] - 3);
   1870    }
   1871  }
   1872 }
   1873 
   1874 /// Output the string 'str' up to a NUL character.
   1875 /// Return the number of characters it takes on the screen.
   1876 ///
   1877 /// If K_SPECIAL is encountered, then it is taken in conjunction with the
   1878 /// following character and shown as <F1>, <S-Up> etc.  Any other character
   1879 /// which is not printable shown in <> form.
   1880 /// If 'from' is true (lhs of a mapping), a space is shown as <Space>.
   1881 /// If a character is displayed in one of these special ways, is also
   1882 /// highlighted (its highlight name is '8' in the p_hl variable).
   1883 /// Otherwise characters are not highlighted.
   1884 /// This function is used to show mappings, where we want to see how to type
   1885 /// the character/string -- webb
   1886 ///
   1887 /// @param from  true for LHS of a mapping
   1888 /// @param maxlen  screen columns, 0 for unlimited
   1889 int msg_outtrans_special(const char *strstart, bool from, int maxlen)
   1890 {
   1891  if (strstart == NULL) {
   1892    return 0;  // Do nothing.
   1893  }
   1894  const char *str = strstart;
   1895  int retval = 0;
   1896  int hl_id = HLF_8;
   1897 
   1898  while (*str != NUL) {
   1899    const char *text;
   1900    // Leading and trailing spaces need to be displayed in <> form.
   1901    if ((str == strstart || str[1] == NUL) && *str == ' ') {
   1902      text = "<Space>";
   1903      str++;
   1904    } else {
   1905      text = str2special(&str, from, false);
   1906    }
   1907    if (text[0] != NUL && text[1] == NUL) {
   1908      // single-byte character or illegal byte
   1909      text = transchar_byte_buf(NULL, (uint8_t)text[0]);
   1910    }
   1911    const int len = vim_strsize(text);
   1912    if (maxlen > 0 && retval + len >= maxlen) {
   1913      break;
   1914    }
   1915    // Highlight special keys
   1916    msg_puts_hl(text, (len > 1 && utfc_ptr2len(text) <= 1 ? hl_id : 0), false);
   1917    retval += len;
   1918  }
   1919  return retval;
   1920 }
   1921 
   1922 /// Convert string, replacing key codes with printables
   1923 ///
   1924 /// Used for lhs or rhs of mappings.
   1925 ///
   1926 /// @param[in]  str  String to convert.
   1927 /// @param[in]  replace_spaces  Convert spaces into `<Space>`, normally used for
   1928 ///                             lhs of mapping and keytrans(), but not rhs.
   1929 /// @param[in]  replace_lt  Convert `<` into `<lt>`.
   1930 ///
   1931 /// @return [allocated] Converted string.
   1932 char *str2special_save(const char *const str, const bool replace_spaces, const bool replace_lt)
   1933  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC
   1934  FUNC_ATTR_NONNULL_RET
   1935 {
   1936  garray_T ga;
   1937  ga_init(&ga, 1, 40);
   1938 
   1939  const char *p = str;
   1940  while (*p != NUL) {
   1941    ga_concat(&ga, str2special(&p, replace_spaces, replace_lt));
   1942  }
   1943  ga_append(&ga, NUL);
   1944  return (char *)ga.ga_data;
   1945 }
   1946 
   1947 /// Convert string, replacing key codes with printables
   1948 ///
   1949 /// Used for lhs or rhs of mappings.
   1950 ///
   1951 /// @param[in]  str  String to convert.
   1952 /// @param[in]  replace_spaces  Convert spaces into `<Space>`, normally used for
   1953 ///                             lhs of mapping and keytrans(), but not rhs.
   1954 /// @param[in]  replace_lt  Convert `<` into `<lt>`.
   1955 ///
   1956 /// @return [allocated] Converted string.
   1957 char *str2special_arena(const char *str, bool replace_spaces, bool replace_lt, Arena *arena)
   1958  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC
   1959  FUNC_ATTR_NONNULL_RET
   1960 {
   1961  const char *p = str;
   1962  size_t len = 0;
   1963  while (*p) {
   1964    len += strlen(str2special(&p, replace_spaces, replace_lt));
   1965  }
   1966 
   1967  char *buf = arena_alloc(arena, len + 1, false);
   1968  size_t pos = 0;
   1969  p = str;
   1970  while (*p) {
   1971    const char *s = str2special(&p, replace_spaces, replace_lt);
   1972    size_t s_len = strlen(s);
   1973    memcpy(buf + pos, s, s_len);
   1974    pos += s_len;
   1975  }
   1976  buf[pos] = NUL;
   1977  return buf;
   1978 }
   1979 
   1980 /// Convert character, replacing key with printable representation.
   1981 ///
   1982 /// @param[in,out]  sp  String to convert. Is advanced to the next key code.
   1983 /// @param[in]  replace_spaces  Convert spaces into `<Space>`, normally used for
   1984 ///                             lhs of mapping and keytrans(), but not rhs.
   1985 /// @param[in]  replace_lt  Convert `<` into `<lt>`.
   1986 ///
   1987 /// @return Converted key code, in a static buffer. Buffer is always one and the
   1988 ///         same, so save converted string somewhere before running str2special
   1989 ///         for the second time.
   1990 ///         On illegal byte return a string with only that byte.
   1991 const char *str2special(const char **const sp, const bool replace_spaces, const bool replace_lt)
   1992  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET
   1993 {
   1994  static char buf[7];
   1995 
   1996  {
   1997    // Try to un-escape a multi-byte character.  Return the un-escaped
   1998    // string if it is a multi-byte character.
   1999    const char *const p = mb_unescape(sp);
   2000    if (p != NULL) {
   2001      return p;
   2002    }
   2003  }
   2004 
   2005  const char *str = *sp;
   2006  int c = (uint8_t)(*str);
   2007  int modifiers = 0;
   2008  bool special = false;
   2009  if (c == K_SPECIAL && str[1] != NUL && str[2] != NUL) {
   2010    if ((uint8_t)str[1] == KS_MODIFIER) {
   2011      modifiers = (uint8_t)str[2];
   2012      str += 3;
   2013      c = (uint8_t)(*str);
   2014    }
   2015    if (c == K_SPECIAL && str[1] != NUL && str[2] != NUL) {
   2016      c = TO_SPECIAL((uint8_t)str[1], (uint8_t)str[2]);
   2017      str += 2;
   2018    }
   2019    if (IS_SPECIAL(c) || modifiers) {  // Special key.
   2020      special = true;
   2021    }
   2022  }
   2023 
   2024  if (!IS_SPECIAL(c) && MB_BYTE2LEN(c) > 1) {
   2025    *sp = str;
   2026    // Try to un-escape a multi-byte character after modifiers.
   2027    const char *p = mb_unescape(sp);
   2028    if (p != NULL) {
   2029      // Since 'special' is true the multi-byte character 'c' will be
   2030      // processed by get_special_key_name().
   2031      c = utf_ptr2char(p);
   2032    } else {
   2033      // illegal byte
   2034      *sp = str + 1;
   2035    }
   2036  } else {
   2037    // single-byte character, NUL or illegal byte
   2038    *sp = str + (*str == NUL ? 0 : 1);
   2039  }
   2040 
   2041  // Make special keys and C0 control characters in <> form, also <M-Space>.
   2042  if (special
   2043      || c < ' '
   2044      || (replace_spaces && c == ' ')
   2045      || (replace_lt && c == '<')) {
   2046    return get_special_key_name(c, modifiers);
   2047  }
   2048  buf[0] = (char)c;
   2049  buf[1] = NUL;
   2050  return buf;
   2051 }
   2052 
   2053 /// Convert string, replacing key codes with printables
   2054 ///
   2055 /// @param[in]  str  String to convert.
   2056 /// @param[out]  buf  Buffer to save results to.
   2057 /// @param[in]  len  Buffer length.
   2058 void str2specialbuf(const char *sp, char *buf, size_t len)
   2059  FUNC_ATTR_NONNULL_ALL
   2060 {
   2061  while (*sp) {
   2062    const char *s = str2special(&sp, false, false);
   2063    const size_t s_len = strlen(s);
   2064    if (len <= s_len) {
   2065      break;
   2066    }
   2067    memcpy(buf, s, s_len);
   2068    buf += s_len;
   2069    len -= s_len;
   2070  }
   2071  *buf = NUL;
   2072 }
   2073 
   2074 /// print line for :print or :list command
   2075 void msg_prt_line(const char *s, bool list)
   2076 {
   2077  schar_T sc;
   2078  int col = 0;
   2079  int n_extra = 0;
   2080  schar_T sc_extra = 0;
   2081  schar_T sc_final = 0;
   2082  const char *p_extra = NULL;  // init to make SASC shut up. ASCII only!
   2083  int n;
   2084  int hl_id = 0;
   2085  const char *lead = NULL;
   2086  bool in_multispace = false;
   2087  int multispace_pos = 0;
   2088  const char *trail = NULL;
   2089  int l;
   2090 
   2091  if (curwin->w_p_list) {
   2092    list = true;
   2093  }
   2094 
   2095  if (list) {
   2096    // find start of trailing whitespace
   2097    if (curwin->w_p_lcs_chars.trail) {
   2098      trail = s + strlen(s);
   2099      while (trail > s && ascii_iswhite(trail[-1])) {
   2100        trail--;
   2101      }
   2102    }
   2103    // find end of leading whitespace
   2104    if (curwin->w_p_lcs_chars.lead || curwin->w_p_lcs_chars.leadmultispace != NULL) {
   2105      lead = s;
   2106      while (ascii_iswhite(lead[0])) {
   2107        lead++;
   2108      }
   2109      // in a line full of spaces all of them are treated as trailing
   2110      if (*lead == NUL) {
   2111        lead = NULL;
   2112      }
   2113    }
   2114  }
   2115 
   2116  // output a space for an empty line, otherwise the line will be overwritten
   2117  if (*s == NUL && !(list && curwin->w_p_lcs_chars.eol != NUL)) {
   2118    msg_putchar(' ');
   2119  }
   2120 
   2121  while (!got_int) {
   2122    if (n_extra > 0) {
   2123      n_extra--;
   2124      if (n_extra == 0 && sc_final) {
   2125        sc = sc_final;
   2126      } else if (sc_extra) {
   2127        sc = sc_extra;
   2128      } else {
   2129        assert(p_extra != NULL);
   2130        sc = schar_from_ascii((unsigned char)(*p_extra++));
   2131      }
   2132    } else if ((l = utfc_ptr2len(s)) > 1) {
   2133      col += utf_ptr2cells(s);
   2134      char buf[MB_MAXBYTES + 1];
   2135      if (l >= MB_MAXBYTES) {
   2136        xstrlcpy(buf, "?", sizeof(buf));
   2137      } else if (curwin->w_p_lcs_chars.nbsp != NUL && list
   2138                 && (utf_ptr2char(s) == 160 || utf_ptr2char(s) == 0x202f)) {
   2139        schar_get(buf, curwin->w_p_lcs_chars.nbsp);
   2140      } else {
   2141        memmove(buf, s, (size_t)l);
   2142        buf[l] = NUL;
   2143      }
   2144      msg_puts(buf);
   2145      s += l;
   2146      continue;
   2147    } else {
   2148      hl_id = 0;
   2149      int c = (uint8_t)(*s++);
   2150      if (c >= 0x80) {  // Illegal byte
   2151        col += utf_char2cells(c);
   2152        msg_putchar(c);
   2153        continue;
   2154      }
   2155      sc_extra = NUL;
   2156      sc_final = NUL;
   2157      if (list) {
   2158        in_multispace = c == ' ' && (*s == ' '
   2159                                     || (col > 0 && s[-2] == ' '));
   2160        if (!in_multispace) {
   2161          multispace_pos = 0;
   2162        }
   2163      }
   2164      if (c == TAB && (!list || curwin->w_p_lcs_chars.tab1)) {
   2165        // tab amount depends on current column
   2166        n_extra = tabstop_padding(col, curbuf->b_p_ts,
   2167                                  curbuf->b_p_vts_array) - 1;
   2168        if (!list) {
   2169          sc = schar_from_ascii(' ');
   2170          sc_extra = schar_from_ascii(' ');
   2171        } else {
   2172          sc = (n_extra == 0 && curwin->w_p_lcs_chars.tab3)
   2173               ? curwin->w_p_lcs_chars.tab3
   2174               : curwin->w_p_lcs_chars.tab1;
   2175          sc_extra = curwin->w_p_lcs_chars.tab2;
   2176          sc_final = curwin->w_p_lcs_chars.tab3;
   2177          hl_id = HLF_0;
   2178        }
   2179      } else if (c == NUL && list && curwin->w_p_lcs_chars.eol != NUL) {
   2180        p_extra = "";
   2181        n_extra = 1;
   2182        sc = curwin->w_p_lcs_chars.eol;
   2183        hl_id = HLF_AT;
   2184        s--;
   2185      } else if (c != NUL && (n = byte2cells(c)) > 1) {
   2186        n_extra = n - 1;
   2187        p_extra = transchar_byte_buf(NULL, c);
   2188        sc = schar_from_ascii(*p_extra++);
   2189        // Use special coloring to be able to distinguish <hex> from
   2190        // the same in plain text.
   2191        hl_id = HLF_0;
   2192      } else if (c == ' ') {
   2193        if (lead != NULL && s <= lead && in_multispace
   2194            && curwin->w_p_lcs_chars.leadmultispace != NULL) {
   2195          sc = curwin->w_p_lcs_chars.leadmultispace[multispace_pos++];
   2196          if (curwin->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) {
   2197            multispace_pos = 0;
   2198          }
   2199          hl_id = HLF_0;
   2200        } else if (lead != NULL && s <= lead && curwin->w_p_lcs_chars.lead != NUL) {
   2201          sc = curwin->w_p_lcs_chars.lead;
   2202          hl_id = HLF_0;
   2203        } else if (trail != NULL && s > trail) {
   2204          sc = curwin->w_p_lcs_chars.trail;
   2205          hl_id = HLF_0;
   2206        } else if (in_multispace
   2207                   && curwin->w_p_lcs_chars.multispace != NULL) {
   2208          sc = curwin->w_p_lcs_chars.multispace[multispace_pos++];
   2209          if (curwin->w_p_lcs_chars.multispace[multispace_pos] == NUL) {
   2210            multispace_pos = 0;
   2211          }
   2212          hl_id = HLF_0;
   2213        } else if (list && curwin->w_p_lcs_chars.space != NUL) {
   2214          sc = curwin->w_p_lcs_chars.space;
   2215          hl_id = HLF_0;
   2216        } else {
   2217          sc = schar_from_ascii(' ');  // SPACE!
   2218        }
   2219      } else {
   2220        sc = schar_from_ascii(c);
   2221      }
   2222    }
   2223 
   2224    if (sc == NUL) {
   2225      break;
   2226    }
   2227 
   2228    // TODO(bfredl): this is such baloney. need msg_put_schar
   2229    char buf[MAX_SCHAR_SIZE];
   2230    schar_get(buf, sc);
   2231    msg_puts_hl(buf, hl_id, false);
   2232    col++;
   2233  }
   2234  msg_clr_eos();
   2235 }
   2236 
   2237 /// Output a string to the screen at position msg_row, msg_col.
   2238 /// Update msg_row and msg_col for the next message.
   2239 void msg_puts(const char *s)
   2240 {
   2241  msg_puts_hl(s, 0, false);
   2242 }
   2243 
   2244 void msg_puts_title(const char *s)
   2245 {
   2246  s += (ui_has(kUIMessages) && *s == '\n');
   2247  msg_puts_hl(s, HLF_T, false);
   2248 }
   2249 
   2250 /// Show a message in such a way that it always fits in the line.  Cut out a
   2251 /// part in the middle and replace it with "..." when necessary.
   2252 /// Does not handle multi-byte characters!
   2253 void msg_outtrans_long(const char *longstr, int hl_id)
   2254 {
   2255  int len = (int)strlen(longstr);
   2256  int slen = len;
   2257  int room = Columns - msg_col;
   2258  if (!ui_has(kUIMessages) && len > room && room >= 20) {
   2259    slen = (room - 3) / 2;
   2260    msg_outtrans_len(longstr, slen, hl_id, false);
   2261    msg_puts_hl("...", HLF_8, false);
   2262  }
   2263  msg_outtrans_len(longstr + len - slen, slen, hl_id, false);
   2264 }
   2265 
   2266 /// Basic function for writing a message with highlight id.
   2267 void msg_puts_hl(const char *const s, const int hl_id, const bool hist)
   2268 {
   2269  msg_puts_len(s, -1, hl_id, hist);
   2270 }
   2271 
   2272 /// Write a message with highlight id.
   2273 ///
   2274 /// @param[in]  str  NUL-terminated message string.
   2275 /// @param[in]  len  Length of the string or -1.
   2276 /// @param[in]  hl_id  Highlight id.
   2277 void msg_puts_len(const char *const str, const ptrdiff_t len, int hl_id, bool hist)
   2278  FUNC_ATTR_NONNULL_ALL
   2279 {
   2280  assert(len < 0 || memchr(str, 0, (size_t)len) == NULL);
   2281  // If redirection is on, also write to the redirection file.
   2282  redir_write(str, len);
   2283 
   2284  // Don't print anything when using ":silent cmd" or empty message.
   2285  if (msg_silent != 0 || *str == NUL) {
   2286    if (*str == NUL && ui_has(kUIMessages)) {
   2287      ui_call_msg_show(cstr_as_string("empty"), (Array)ARRAY_DICT_INIT, false, false, false,
   2288                       INTEGER_OBJ(-1));
   2289    }
   2290    return;
   2291  }
   2292 
   2293  if (hist) {
   2294    msg_hist_add(str, (int)len, hl_id);
   2295  }
   2296 
   2297  // When writing something to the screen after it has scrolled, requires a
   2298  // wait-return prompt later.  Needed when scrolling, resetting
   2299  // need_wait_return after some prompt, and then outputting something
   2300  // without scrolling
   2301  // Not needed when only using CR to move the cursor.
   2302  bool overflow = !ui_has(kUIMessages) && msg_scrolled > (p_ch == 0 ? 1 : 0);
   2303 
   2304  if (overflow && !msg_scrolled_ign && strcmp(str, "\r") != 0) {
   2305    need_wait_return = true;
   2306  }
   2307  msg_didany = true;  // remember that something was outputted
   2308 
   2309  // If there is no valid screen, use fprintf so we can see error messages.
   2310  // If termcap is not active, we may be writing in an alternate console
   2311  // window, cursor positioning may not work correctly (window size may be
   2312  // different, e.g. for Win32 console) or we just don't know where the
   2313  // cursor is.
   2314  if (msg_use_printf()) {
   2315    int saved_msg_col = msg_col;
   2316    msg_puts_printf(str, len);
   2317    if (headless_mode) {
   2318      msg_col = saved_msg_col;
   2319    }
   2320  }
   2321  if (!msg_use_printf() || (headless_mode && default_grid.chars)) {
   2322    msg_puts_display(str, (int)len, hl_id, false);
   2323  }
   2324 
   2325  need_fileinfo = false;
   2326 }
   2327 
   2328 static void msg_ext_emit_chunk(void)
   2329 {
   2330  if (msg_ext_chunks == NULL) {
   2331    msg_ext_init_chunks();
   2332  }
   2333  // Color was changed or a message flushed, end current chunk.
   2334  if (msg_ext_last_attr == -1) {
   2335    return;  // no chunk
   2336  }
   2337  Array chunk = ARRAY_DICT_INIT;
   2338  ADD(chunk, INTEGER_OBJ(msg_ext_last_attr));
   2339  msg_ext_last_attr = -1;
   2340  String text = ga_take_string(&msg_ext_last_chunk);
   2341  ADD(chunk, STRING_OBJ(text));
   2342  ADD(chunk, INTEGER_OBJ(msg_ext_last_hl_id));
   2343  ADD(*msg_ext_chunks, ARRAY_OBJ(chunk));
   2344 }
   2345 
   2346 /// The display part of msg_puts_len().
   2347 /// May be called recursively to display scroll-back text.
   2348 static void msg_puts_display(const char *str, int maxlen, int hl_id, int recurse)
   2349 {
   2350  const char *s = str;
   2351  const char *sb_str = str;
   2352  int sb_col = msg_col;
   2353  int attr = hl_id ? syn_id2attr(hl_id) : 0;
   2354 
   2355  did_wait_return = false;
   2356 
   2357  if (ui_has(kUIMessages)) {
   2358    if (attr != msg_ext_last_attr) {
   2359      msg_ext_emit_chunk();
   2360      msg_ext_last_attr = attr;
   2361      msg_ext_last_hl_id = hl_id;
   2362    }
   2363    // Concat pieces with the same highlight
   2364    size_t len = maxlen < 0 ? strlen(str) : strnlen(str, (size_t)maxlen);
   2365    ga_concat_len(&msg_ext_last_chunk, str, len);
   2366 
   2367    // Find last newline in the message and calculate the current message column
   2368    const char *lastline = xmemrchr(str, '\n', len);
   2369    maxlen -= (int)(lastline ? (lastline - str) : 0);
   2370    const char *p = lastline ? lastline + 1 : str;
   2371    int col = (int)(maxlen < 0 ? mb_string2cells(p) : mb_string2cells_len(p, (size_t)(maxlen)));
   2372    msg_col = (lastline ? 0 : msg_col) + col;
   2373 
   2374    return;
   2375  }
   2376 
   2377  int print_attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr);
   2378  msg_grid_validate();
   2379 
   2380  cmdline_was_last_drawn = redrawing_cmdline;
   2381 
   2382  int msg_row_pending = -1;
   2383 
   2384  while (true) {
   2385    if (msg_col >= Columns) {
   2386      if (p_more && !recurse) {
   2387        // Store text for scrolling back.
   2388        store_sb_text(&sb_str, s, hl_id, &sb_col, true);
   2389      }
   2390      if (msg_no_more && lines_left == 0) {
   2391        break;
   2392      }
   2393 
   2394      msg_col = 0;
   2395      msg_row++;
   2396      msg_didout = false;
   2397    }
   2398 
   2399    if (msg_row >= Rows) {
   2400      msg_row = Rows - 1;
   2401 
   2402      // When no more prompt and no more room, truncate here
   2403      if (msg_no_more && lines_left == 0) {
   2404        break;
   2405      }
   2406 
   2407      if (!recurse) {
   2408        if (msg_row_pending >= 0) {
   2409          msg_line_flush();
   2410          msg_row_pending = -1;
   2411        }
   2412 
   2413        // Scroll the screen up one line.
   2414        msg_scroll_up(true, false);
   2415 
   2416        inc_msg_scrolled();
   2417        need_wait_return = true;       // may need wait_return() in main()
   2418        redraw_cmdline = true;
   2419        if (cmdline_row > 0 && !exmode_active) {
   2420          cmdline_row--;
   2421        }
   2422 
   2423        // If screen is completely filled and 'more' is set then wait
   2424        // for a character.
   2425        if (lines_left > 0) {
   2426          lines_left--;
   2427        }
   2428 
   2429        if (p_more && lines_left == 0 && State != MODE_HITRETURN
   2430            && !msg_no_more && !exmode_active) {
   2431          if (do_more_prompt(NUL)) {
   2432            s = confirm_buttons;
   2433          }
   2434          if (quit_more) {
   2435            return;
   2436          }
   2437        }
   2438      }
   2439    }
   2440 
   2441    if (!((maxlen < 0 || (int)(s - str) < maxlen) && *s != NUL)) {
   2442      break;
   2443    }
   2444 
   2445    if (msg_row != msg_row_pending && ((uint8_t)(*s) >= 0x20 || *s == TAB)) {
   2446      // TODO(bfredl): this logic is messier that it has to be. What
   2447      // messages really want is its own private linebuf_char buffer.
   2448      if (msg_row_pending >= 0) {
   2449        msg_line_flush();
   2450      }
   2451      grid_line_start(&msg_grid_adj, msg_row);
   2452      msg_row_pending = msg_row;
   2453    }
   2454 
   2455    if ((uint8_t)(*s) >= 0x20) {  // printable char
   2456      int cw = utf_ptr2cells(s);
   2457      // avoid including composing chars after the end
   2458      int l = (maxlen >= 0) ? utfc_ptr2len_len(s, (int)((str + maxlen) - s)) : utfc_ptr2len(s);
   2459 
   2460      if (cw > 1 && (msg_col == Columns - 1)) {
   2461        // Doesn't fit, print a highlighted '>' to fill it up.
   2462        grid_line_puts(msg_col, ">", 1, HL_ATTR(HLF_AT));
   2463        cw = 1;
   2464      } else {
   2465        grid_line_puts(msg_col, s, l, print_attr);
   2466        s += l;
   2467      }
   2468      msg_didout = true;  // remember that line is not empty
   2469      msg_col += cw;
   2470    } else {
   2471      char c = *s++;
   2472      if (c == '\n') {  // go to next line
   2473        msg_didout = false;  // remember that line is empty
   2474        msg_col = 0;
   2475        msg_row++;
   2476        if (p_more && !recurse) {
   2477          // Store text for scrolling back.
   2478          store_sb_text(&sb_str, s, hl_id, &sb_col, true);
   2479        }
   2480      } else if (c == '\r') {  // go to column 0
   2481        msg_col = 0;
   2482      } else if (c == '\b') {  // go to previous char
   2483        if (msg_col) {
   2484          msg_col--;
   2485        }
   2486      } else if (c == TAB) {  // translate Tab into spaces
   2487        do {
   2488          grid_line_puts(msg_col, " ", 1, print_attr);
   2489          msg_col += 1;
   2490 
   2491          if (msg_col == Columns) {
   2492            break;
   2493          }
   2494        } while (msg_col & 7);
   2495      } else if (c == BELL) {  // beep (from ":sh")
   2496        vim_beep(kOptBoFlagShell);
   2497      }
   2498    }
   2499  }
   2500 
   2501  if (msg_row_pending >= 0) {
   2502    msg_line_flush();
   2503  }
   2504  msg_cursor_goto(msg_row, msg_col);
   2505 
   2506  if (p_more && !recurse) {
   2507    store_sb_text(&sb_str, s, hl_id, &sb_col, false);
   2508  }
   2509 
   2510  msg_check();
   2511 }
   2512 
   2513 void msg_line_flush(void)
   2514 {
   2515  if (cmdmsg_rl) {
   2516    grid_line_mirror(msg_grid.cols);
   2517  }
   2518  grid_line_flush_if_valid_row();
   2519 }
   2520 
   2521 void msg_cursor_goto(int row, int col)
   2522 {
   2523  if (cmdmsg_rl) {
   2524    col = Columns - 1 - col;
   2525  }
   2526  ScreenGrid *grid = grid_adjust(&msg_grid_adj, &row, &col);
   2527  ui_grid_cursor_goto(grid->handle, row, col);
   2528 }
   2529 
   2530 /// @return  true when ":filter pattern" was used and "msg" does not match
   2531 ///          "pattern".
   2532 bool message_filtered(const char *msg)
   2533 {
   2534  if (cmdmod.cmod_filter_regmatch.regprog == NULL) {
   2535    return false;
   2536  }
   2537 
   2538  bool match = vim_regexec(&cmdmod.cmod_filter_regmatch, msg, 0);
   2539  return cmdmod.cmod_filter_force ? match : !match;
   2540 }
   2541 
   2542 /// including horizontal separator
   2543 int msg_scrollsize(void)
   2544 {
   2545  return msg_scrolled + (int)p_ch + ((p_ch > 0 || msg_scrolled > 1) ? 1 : 0);
   2546 }
   2547 
   2548 bool msg_do_throttle(void)
   2549 {
   2550  return msg_use_grid() && !(rdb_flags & kOptRdbFlagNothrottle);
   2551 }
   2552 
   2553 /// Scroll the screen up one line for displaying the next message line.
   2554 void msg_scroll_up(bool may_throttle, bool zerocmd)
   2555 {
   2556  if (may_throttle && msg_do_throttle()) {
   2557    msg_grid.throttled = true;
   2558  }
   2559  msg_did_scroll = true;
   2560  if (msg_grid_pos > 0) {
   2561    msg_grid_set_pos(msg_grid_pos - 1, !zerocmd);
   2562 
   2563    // When displaying the first line with cmdheight=0, we need to draw over
   2564    // the existing last line of the screen.
   2565    if (zerocmd && msg_grid.chars) {
   2566      grid_clear_line(&msg_grid, msg_grid.line_offset[0], msg_grid.cols, false);
   2567    }
   2568  } else {
   2569    grid_del_lines(&msg_grid, 0, 1, msg_grid.rows, 0, msg_grid.cols);
   2570    memmove(msg_grid.dirty_col, msg_grid.dirty_col + 1,
   2571            (size_t)(msg_grid.rows - 1) * sizeof(*msg_grid.dirty_col));
   2572    msg_grid.dirty_col[msg_grid.rows - 1] = 0;
   2573  }
   2574 
   2575  grid_clear(&msg_grid_adj, Rows - 1, Rows, 0, Columns, HL_ATTR(HLF_MSG));
   2576 }
   2577 
   2578 /// Send throttled message output to UI clients
   2579 ///
   2580 /// The way message.c uses the grid_xx family of functions is quite inefficient
   2581 /// relative to the "gridline" UI protocol used by TUI and modern clients.
   2582 /// For instance scrolling is done one line at a time. By throttling drawing
   2583 /// on the message grid, we can coalesce scrolling to a single grid_scroll
   2584 /// per screen update.
   2585 ///
   2586 /// NB: The bookkeeping is quite messy, and rests on a bunch of poorly
   2587 /// documented assumptions. For instance that the message area always grows
   2588 /// while being throttled, messages are only being output on the last line
   2589 /// etc.
   2590 ///
   2591 /// Probably message scrollback storage should be reimplemented as a
   2592 /// file_buffer, and message scrolling in TUI be reimplemented as a modal
   2593 /// floating window. Then we get throttling "for free" using standard
   2594 /// redraw_later code paths.
   2595 void msg_scroll_flush(void)
   2596 {
   2597  if (msg_grid.throttled) {
   2598    msg_grid.throttled = false;
   2599    int pos_delta = msg_grid_pos_at_flush - msg_grid_pos;
   2600    assert(pos_delta >= 0);
   2601    int delta = MIN(msg_scrolled - msg_scrolled_at_flush, msg_grid.rows);
   2602 
   2603    if (pos_delta > 0) {
   2604      ui_ext_msg_set_pos(msg_grid_pos, true);
   2605    }
   2606 
   2607    int to_scroll = delta - pos_delta - msg_grid_scroll_discount;
   2608    assert(to_scroll >= 0);
   2609 
   2610    // TODO(bfredl): msg_grid_pos should be 0 already when starting scrolling
   2611    // but this sometimes fails in "headless" message printing.
   2612    if (to_scroll > 0 && msg_grid_pos == 0) {
   2613      ui_call_grid_scroll(msg_grid.handle, 0, Rows, 0, Columns, to_scroll, 0);
   2614    }
   2615 
   2616    for (int i = MAX(Rows - MAX(delta, 1), 0); i < Rows; i++) {
   2617      int row = i - msg_grid_pos;
   2618      assert(row >= 0);
   2619      ui_line(&msg_grid, row, false, 0, msg_grid.dirty_col[row], msg_grid.cols,
   2620              HL_ATTR(HLF_MSG), false);
   2621      msg_grid.dirty_col[row] = 0;
   2622    }
   2623  }
   2624  msg_scrolled_at_flush = msg_scrolled;
   2625  msg_grid_scroll_discount = 0;
   2626  msg_grid_pos_at_flush = msg_grid_pos;
   2627 }
   2628 
   2629 void msg_reset_scroll(void)
   2630 {
   2631  if (ui_has(kUIMessages)) {
   2632    return;
   2633  }
   2634  // TODO(bfredl): some duplicate logic with update_screen(). Later on
   2635  // we should properly disentangle message clear with full screen redraw.
   2636  msg_grid.throttled = false;
   2637  // TODO(bfredl): risk for extra flicker i e with
   2638  // "nvim -o has_swap also_has_swap"
   2639  msg_grid_set_pos(Rows - (int)p_ch, false);
   2640  clear_cmdline = true;
   2641  if (msg_grid.chars) {
   2642    // non-displayed part of msg_grid is considered invalid.
   2643    for (int i = 0; i < MIN(msg_scrollsize(), msg_grid.rows); i++) {
   2644      grid_clear_line(&msg_grid, msg_grid.line_offset[i],
   2645                      msg_grid.cols, false);
   2646    }
   2647  }
   2648  msg_scrolled = 0;
   2649  msg_scrolled_at_flush = 0;
   2650  msg_grid_scroll_discount = 0;
   2651 }
   2652 
   2653 void msg_ui_refresh(void)
   2654 {
   2655  if (ui_has(kUIMultigrid) && msg_grid.chars) {
   2656    ui_call_grid_resize(msg_grid.handle, msg_grid.cols, msg_grid.rows);
   2657    ui_ext_msg_set_pos(msg_grid_pos, msg_scrolled);
   2658  }
   2659 }
   2660 
   2661 void msg_ui_flush(void)
   2662 {
   2663  if (ui_has(kUIMultigrid) && msg_grid.chars && msg_grid.pending_comp_index_update) {
   2664    ui_ext_msg_set_pos(msg_grid_pos, msg_scrolled);
   2665  }
   2666 }
   2667 
   2668 /// Increment "msg_scrolled".
   2669 static void inc_msg_scrolled(void)
   2670 {
   2671  if (*get_vim_var_str(VV_SCROLLSTART) == NUL) {
   2672    char *p = SOURCING_NAME;
   2673    char *tofree = NULL;
   2674 
   2675    // v:scrollstart is empty, set it to the script/function name and line
   2676    // number
   2677    if (p == NULL) {
   2678      p = _("Unknown");
   2679    } else {
   2680      size_t len = strlen(p) + 40;
   2681      tofree = xmalloc(len);
   2682      vim_snprintf(tofree, len, _("%s line %" PRId64),
   2683                   p, (int64_t)SOURCING_LNUM);
   2684      p = tofree;
   2685    }
   2686    set_vim_var_string(VV_SCROLLSTART, p, -1);
   2687    xfree(tofree);
   2688  }
   2689  msg_scrolled++;
   2690  set_must_redraw(UPD_VALID);
   2691 }
   2692 
   2693 static msgchunk_T *last_msgchunk = NULL;  // last displayed text
   2694 
   2695 typedef enum {
   2696  SB_CLEAR_NONE = 0,
   2697  SB_CLEAR_ALL,
   2698  SB_CLEAR_CMDLINE_BUSY,
   2699  SB_CLEAR_CMDLINE_DONE,
   2700 } sb_clear_T;
   2701 
   2702 // When to clear text on next msg.
   2703 static sb_clear_T do_clear_sb_text = SB_CLEAR_NONE;
   2704 
   2705 /// Store part of a printed message for displaying when scrolling back.
   2706 ///
   2707 /// @param sb_str  start of string
   2708 /// @param s  just after string
   2709 /// @param finish  line ends
   2710 static void store_sb_text(const char **sb_str, const char *s, int hl_id, int *sb_col, int finish)
   2711 {
   2712  msgchunk_T *mp;
   2713 
   2714  if (do_clear_sb_text == SB_CLEAR_ALL
   2715      || do_clear_sb_text == SB_CLEAR_CMDLINE_DONE) {
   2716    clear_sb_text(do_clear_sb_text == SB_CLEAR_ALL);
   2717    msg_sb_eol();  // prevent messages from overlapping
   2718    if (do_clear_sb_text == SB_CLEAR_CMDLINE_DONE && s > *sb_str && **sb_str == '\n') {
   2719      (*sb_str)++;
   2720    }
   2721    do_clear_sb_text = SB_CLEAR_NONE;
   2722  }
   2723 
   2724  if (s > *sb_str) {
   2725    mp = xmalloc(offsetof(msgchunk_T, sb_text) + (size_t)(s - *sb_str) + 1);
   2726    mp->sb_eol = (char)finish;
   2727    mp->sb_msg_col = *sb_col;
   2728    mp->sb_hl_id = hl_id;
   2729    memcpy(mp->sb_text, *sb_str, (size_t)(s - *sb_str));
   2730    mp->sb_text[s - *sb_str] = NUL;
   2731 
   2732    if (last_msgchunk == NULL) {
   2733      last_msgchunk = mp;
   2734      mp->sb_prev = NULL;
   2735    } else {
   2736      mp->sb_prev = last_msgchunk;
   2737      last_msgchunk->sb_next = mp;
   2738      last_msgchunk = mp;
   2739    }
   2740    mp->sb_next = NULL;
   2741  } else if (finish && last_msgchunk != NULL) {
   2742    last_msgchunk->sb_eol = true;
   2743  }
   2744 
   2745  *sb_str = s;
   2746  *sb_col = 0;
   2747 }
   2748 
   2749 /// Finished showing messages, clear the scroll-back text on the next message.
   2750 void may_clear_sb_text(void)
   2751 {
   2752  msg_ext_ui_flush();  // ensure messages until now are emitted
   2753  do_clear_sb_text = SB_CLEAR_ALL;
   2754  do_clear_hist_temp = true;
   2755 }
   2756 
   2757 /// Starting to edit the command line: do not clear messages now.
   2758 void sb_text_start_cmdline(void)
   2759 {
   2760  if (do_clear_sb_text == SB_CLEAR_CMDLINE_BUSY) {
   2761    // Invoking command line recursively: the previous-level command line
   2762    // doesn't need to be remembered as it will be redrawn when returning
   2763    // to that level.
   2764    sb_text_restart_cmdline();
   2765  } else {
   2766    msg_sb_eol();
   2767    do_clear_sb_text = SB_CLEAR_CMDLINE_BUSY;
   2768  }
   2769 }
   2770 
   2771 /// Redrawing the command line: clear the last unfinished line.
   2772 void sb_text_restart_cmdline(void)
   2773 {
   2774  // Needed when returning from nested command line.
   2775  do_clear_sb_text = SB_CLEAR_CMDLINE_BUSY;
   2776 
   2777  if (last_msgchunk == NULL || last_msgchunk->sb_eol) {
   2778    // No unfinished line: don't clear anything.
   2779    return;
   2780  }
   2781 
   2782  msgchunk_T *tofree = msg_sb_start(last_msgchunk);
   2783  last_msgchunk = tofree->sb_prev;
   2784  if (last_msgchunk != NULL) {
   2785    last_msgchunk->sb_next = NULL;
   2786  }
   2787  while (tofree != NULL) {
   2788    msgchunk_T *tofree_next = tofree->sb_next;
   2789    xfree(tofree);
   2790    tofree = tofree_next;
   2791  }
   2792 }
   2793 
   2794 /// Ending to edit the command line: clear old lines but the last one later.
   2795 void sb_text_end_cmdline(void)
   2796 {
   2797  do_clear_sb_text = SB_CLEAR_CMDLINE_DONE;
   2798 }
   2799 
   2800 /// Clear any text remembered for scrolling back.
   2801 /// When "all" is false keep the last line.
   2802 /// Called when redrawing the screen.
   2803 void clear_sb_text(bool all)
   2804 {
   2805  msgchunk_T *mp;
   2806  msgchunk_T **lastp;
   2807 
   2808  if (all) {
   2809    lastp = &last_msgchunk;
   2810  } else {
   2811    if (last_msgchunk == NULL) {
   2812      return;
   2813    }
   2814    lastp = &msg_sb_start(last_msgchunk)->sb_prev;
   2815  }
   2816 
   2817  while (*lastp != NULL) {
   2818    mp = (*lastp)->sb_prev;
   2819    xfree(*lastp);
   2820    *lastp = mp;
   2821  }
   2822 }
   2823 
   2824 /// "g<" command.
   2825 void show_sb_text(void)
   2826 {
   2827  if (ui_has(kUIMessages)) {
   2828    exarg_T ea = { .arg = "", .skip = true };
   2829    ex_messages(&ea);
   2830    return;
   2831  }
   2832  // Only show something if there is more than one line, otherwise it looks
   2833  // weird, typing a command without output results in one line.
   2834  msgchunk_T *mp = msg_sb_start(last_msgchunk);
   2835  if (mp == NULL || mp->sb_prev == NULL) {
   2836    vim_beep(kOptBoFlagMess);
   2837  } else {
   2838    do_more_prompt('G');
   2839    wait_return(false);
   2840  }
   2841 }
   2842 
   2843 /// Move to the start of screen line in already displayed text.
   2844 static msgchunk_T *msg_sb_start(msgchunk_T *mps)
   2845 {
   2846  msgchunk_T *mp = mps;
   2847 
   2848  while (mp != NULL && mp->sb_prev != NULL && !mp->sb_prev->sb_eol) {
   2849    mp = mp->sb_prev;
   2850  }
   2851  return mp;
   2852 }
   2853 
   2854 /// Mark the last message chunk as finishing the line.
   2855 void msg_sb_eol(void)
   2856 {
   2857  if (last_msgchunk != NULL) {
   2858    last_msgchunk->sb_eol = true;
   2859  }
   2860 }
   2861 
   2862 /// Display a screen line from previously displayed text at row "row".
   2863 ///
   2864 /// @return  a pointer to the text for the next line (can be NULL).
   2865 static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp)
   2866 {
   2867  msgchunk_T *mp = smp;
   2868 
   2869  while (true) {
   2870    msg_row = row;
   2871    msg_col = mp->sb_msg_col;
   2872    char *p = mp->sb_text;
   2873    msg_puts_display(p, -1, mp->sb_hl_id, true);
   2874    if (mp->sb_eol || mp->sb_next == NULL) {
   2875      break;
   2876    }
   2877    mp = mp->sb_next;
   2878  }
   2879 
   2880  return mp->sb_next;
   2881 }
   2882 
   2883 /// @return  true when messages should be printed to stdout/stderr:
   2884 ///          - "batch mode" ("silent mode", -es/-Es/-l)
   2885 ///          - no UI and not embedded
   2886 ///          - no ext_messages
   2887 int msg_use_printf(void)
   2888 {
   2889  return !embedded_mode && !ui_active() && !ui_has(kUIMessages);
   2890 }
   2891 
   2892 /// Print a message when there is no valid screen.
   2893 static void msg_puts_printf(const char *str, const ptrdiff_t maxlen)
   2894 {
   2895  const char *s = str;
   2896  char buf[7];
   2897  char *p;
   2898 
   2899  if (on_print.type != kCallbackNone) {
   2900    typval_T argv[1];
   2901    argv[0].v_type = VAR_STRING;
   2902    argv[0].v_lock = VAR_UNLOCKED;
   2903    argv[0].vval.v_string = (char *)str;
   2904    typval_T rettv = TV_INITIAL_VALUE;
   2905    callback_call(&on_print, 1, argv, &rettv);
   2906    tv_clear(&rettv);
   2907    return;
   2908  }
   2909 
   2910  while ((maxlen < 0 || s - str < maxlen) && *s != NUL) {
   2911    int len = utf_ptr2len(s);
   2912    if (!(silent_mode && p_verbose == 0)) {
   2913      // NL --> CR NL translation (for Unix, not for "--version")
   2914      p = &buf[0];
   2915      if (*s == '\n' && !info_message) {
   2916        *p++ = '\r';
   2917      }
   2918      memcpy(p, s, (size_t)len);
   2919      *(p + len) = NUL;
   2920      if (info_message) {
   2921        printf("%s", buf);
   2922      } else {
   2923        fprintf(stderr, "%s", buf);
   2924      }
   2925    }
   2926 
   2927    int cw = utf_char2cells(utf_ptr2char(s));
   2928    // primitive way to compute the current column
   2929    if (*s == '\r' || *s == '\n') {
   2930      msg_col = 0;
   2931      msg_didout = false;
   2932    } else {
   2933      msg_col += cw;
   2934      msg_didout = true;
   2935    }
   2936    s += len;
   2937  }
   2938 }
   2939 
   2940 /// Show the more-prompt and handle the user response.
   2941 /// This takes care of scrolling back and displaying previously displayed text.
   2942 /// When at hit-enter prompt "typed_char" is the already typed character,
   2943 /// otherwise it's NUL.
   2944 ///
   2945 /// @return  true when jumping ahead to "confirm_buttons".
   2946 static bool do_more_prompt(int typed_char)
   2947 {
   2948  static bool entered = false;
   2949  int used_typed_char = typed_char;
   2950  int oldState = State;
   2951  int c;
   2952  bool retval = false;
   2953  bool to_redraw = false;
   2954  msgchunk_T *mp_last = NULL;
   2955  msgchunk_T *mp;
   2956 
   2957  // If headless mode is enabled and no input is required, this variable
   2958  // will be true. However If server mode is enabled, the message "--more--"
   2959  // should be displayed.
   2960  bool no_need_more = headless_mode && !embedded_mode && !ui_active();
   2961 
   2962  // We get called recursively when a timer callback outputs a message. In
   2963  // that case don't show another prompt. Also when at the hit-Enter prompt
   2964  // and nothing was typed.
   2965  if (no_need_more || entered || (State == MODE_HITRETURN && typed_char == 0)) {
   2966    return false;
   2967  }
   2968  entered = true;
   2969 
   2970  if (typed_char == 'G') {
   2971    // "g<": Find first line on the last page.
   2972    mp_last = msg_sb_start(last_msgchunk);
   2973    for (int i = 0; i < Rows - 2 && mp_last != NULL
   2974         && mp_last->sb_prev != NULL; i++) {
   2975      mp_last = msg_sb_start(mp_last->sb_prev);
   2976    }
   2977  }
   2978 
   2979  State = MODE_ASKMORE;
   2980  setmouse();
   2981  if (typed_char == NUL) {
   2982    msg_moremsg(false);
   2983  }
   2984  while (true) {
   2985    // Get a typed character directly from the user.
   2986    if (used_typed_char != NUL) {
   2987      c = used_typed_char;              // was typed at hit-enter prompt
   2988      used_typed_char = NUL;
   2989    } else {
   2990      c = get_keystroke(resize_events);
   2991    }
   2992 
   2993    int toscroll = 0;
   2994    switch (c) {
   2995    case BS:                    // scroll one line back
   2996    case K_BS:
   2997    case 'k':
   2998    case K_UP:
   2999      toscroll = -1;
   3000      break;
   3001 
   3002    case CAR:                   // one extra line
   3003    case NL:
   3004    case 'j':
   3005    case K_DOWN:
   3006      toscroll = 1;
   3007      break;
   3008 
   3009    case 'u':                   // Up half a page
   3010      toscroll = -(Rows / 2);
   3011      break;
   3012 
   3013    case 'd':                   // Down half a page
   3014      toscroll = Rows / 2;
   3015      break;
   3016 
   3017    case 'b':                   // one page back
   3018    case Ctrl_B:
   3019    case K_PAGEUP:
   3020      toscroll = -(Rows - 1);
   3021      break;
   3022 
   3023    case ' ':                   // one extra page
   3024    case 'f':
   3025    case Ctrl_F:
   3026    case K_PAGEDOWN:
   3027    case K_LEFTMOUSE:
   3028      toscroll = Rows - 1;
   3029      break;
   3030 
   3031    case 'g':                   // all the way back to the start
   3032      toscroll = -999999;
   3033      break;
   3034 
   3035    case 'G':                   // all the way to the end
   3036      toscroll = 999999;
   3037      lines_left = 999999;
   3038      break;
   3039 
   3040    case ':':                   // start new command line
   3041      if (!confirm_msg_used) {
   3042        // Since got_int is set all typeahead will be flushed, but we
   3043        // want to keep this ':', remember that in a special way.
   3044        typeahead_noflush(':');
   3045        cmdline_row = Rows - 1;                 // put ':' on this line
   3046        skip_redraw = true;                     // skip redraw once
   3047        need_wait_return = false;               // don't wait in main()
   3048      }
   3049      FALLTHROUGH;
   3050    case 'q':                   // quit
   3051    case Ctrl_C:
   3052    case ESC:
   3053      if (confirm_msg_used) {
   3054        // Jump to the choices of the dialog.
   3055        retval = true;
   3056      } else {
   3057        got_int = true;
   3058        quit_more = true;
   3059      }
   3060      // When there is some more output (wrapping line) display that
   3061      // without another prompt.
   3062      lines_left = Rows - 1;
   3063      break;
   3064 
   3065    case K_EVENT:
   3066      // only resize_events are processed here
   3067      // Attempt to redraw the screen. sb_text doesn't support reflow
   3068      // so this only really works for vertical resize.
   3069      multiqueue_process_events(resize_events);
   3070      to_redraw = true;
   3071      break;
   3072 
   3073    default:                    // no valid response
   3074      msg_moremsg(true);
   3075      continue;
   3076    }
   3077 
   3078    // code assumes we only do one at a time
   3079    assert((toscroll == 0) || !to_redraw);
   3080 
   3081    if (toscroll != 0 || to_redraw) {
   3082      if (toscroll < 0 || to_redraw) {
   3083        // go to start of last line
   3084        if (mp_last == NULL) {
   3085          mp = msg_sb_start(last_msgchunk);
   3086        } else if (mp_last->sb_prev != NULL) {
   3087          mp = msg_sb_start(mp_last->sb_prev);
   3088        } else {
   3089          mp = NULL;
   3090        }
   3091 
   3092        // go to start of line at top of the screen
   3093        for (int i = 0; i < Rows - 2 && mp != NULL && mp->sb_prev != NULL; i++) {
   3094          mp = msg_sb_start(mp->sb_prev);
   3095        }
   3096 
   3097        if (mp != NULL && (mp->sb_prev != NULL || to_redraw)) {
   3098          // Find line to be displayed at top
   3099          for (int i = 0; i > toscroll; i--) {
   3100            if (mp == NULL || mp->sb_prev == NULL) {
   3101              break;
   3102            }
   3103            mp = msg_sb_start(mp->sb_prev);
   3104            if (mp_last == NULL) {
   3105              mp_last = msg_sb_start(last_msgchunk);
   3106            } else {
   3107              mp_last = msg_sb_start(mp_last->sb_prev);
   3108            }
   3109          }
   3110 
   3111          if (toscroll == -1 && !to_redraw) {
   3112            grid_ins_lines(&msg_grid, 0, 1, Rows, 0, Columns);
   3113            grid_clear(&msg_grid_adj, 0, 1, 0, Columns, HL_ATTR(HLF_MSG));
   3114            // display line at top
   3115            disp_sb_line(0, mp);
   3116          } else {
   3117            // redisplay all lines
   3118            // TODO(bfredl): this case is not optimized (though only concerns
   3119            // event fragmentation, not unnecessary scroll events).
   3120            grid_clear(&msg_grid_adj, 0, Rows, 0, Columns, HL_ATTR(HLF_MSG));
   3121            for (int i = 0; mp != NULL && i < Rows - 1; i++) {
   3122              mp = disp_sb_line(i, mp);
   3123              msg_scrolled++;
   3124            }
   3125            to_redraw = false;
   3126          }
   3127          toscroll = 0;
   3128        }
   3129      } else {
   3130        // First display any text that we scrolled back.
   3131        // if p_ch=0 we need to allocate a line for "press enter" messages!
   3132        if (cmdline_row >= Rows && !ui_has(kUIMessages)) {
   3133          msg_scroll_up(true, false);
   3134          msg_scrolled++;
   3135        }
   3136        while (toscroll > 0 && mp_last != NULL) {
   3137          if (msg_do_throttle() && !msg_grid.throttled) {
   3138            // Tricky: we redraw at one line higher than usual. Therefore
   3139            // the non-flushed area is one line larger.
   3140            msg_scrolled_at_flush--;
   3141            msg_grid_scroll_discount++;
   3142          }
   3143          // scroll up, display line at bottom
   3144          msg_scroll_up(true, false);
   3145          inc_msg_scrolled();
   3146          grid_clear(&msg_grid_adj, Rows - 2, Rows - 1, 0, Columns, HL_ATTR(HLF_MSG));
   3147          mp_last = disp_sb_line(Rows - 2, mp_last);
   3148          toscroll--;
   3149        }
   3150      }
   3151 
   3152      if (toscroll <= 0) {
   3153        // displayed the requested text, more prompt again
   3154        grid_clear(&msg_grid_adj, Rows - 1, Rows, 0, Columns, HL_ATTR(HLF_MSG));
   3155        msg_moremsg(false);
   3156        continue;
   3157      }
   3158 
   3159      // display more text, return to caller
   3160      lines_left = toscroll;
   3161    }
   3162 
   3163    break;
   3164  }
   3165 
   3166  // clear the --more-- message
   3167  grid_clear(&msg_grid_adj, Rows - 1, Rows, 0, Columns, HL_ATTR(HLF_MSG));
   3168  redraw_cmdline = true;
   3169  clear_cmdline = false;
   3170  mode_displayed = false;
   3171 
   3172  State = oldState;
   3173  setmouse();
   3174  if (quit_more) {
   3175    msg_row = Rows - 1;
   3176    msg_col = 0;
   3177  }
   3178 
   3179  entered = false;
   3180  return retval;
   3181 }
   3182 
   3183 static void msg_moremsg(bool full)
   3184 {
   3185  int attr = hl_combine_attr(HL_ATTR(HLF_MSG), HL_ATTR(HLF_M));
   3186  grid_line_start(&msg_grid_adj, Rows - 1);
   3187  int len = grid_line_puts(0, _("-- More --"), -1, attr);
   3188  if (full) {
   3189    len += grid_line_puts(len, _(" SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "),
   3190                          -1, attr);
   3191  }
   3192  grid_line_cursor_goto(len);
   3193  grid_line_flush();
   3194 }
   3195 
   3196 /// Repeat the message for the current mode: MODE_ASKMORE, MODE_EXTERNCMD,
   3197 /// confirm() prompt or exmode_active.
   3198 void repeat_message(void)
   3199 {
   3200  if (ui_has(kUIMessages)) {
   3201    return;
   3202  }
   3203 
   3204  if (State == MODE_ASKMORE) {
   3205    msg_moremsg(true);          // display --more-- message again
   3206    msg_row = Rows - 1;
   3207  } else if ((State & MODE_CMDLINE) && confirm_msg != NULL) {
   3208    display_confirm_msg();      // display ":confirm" message again
   3209    msg_row = Rows - 1;
   3210  } else if (State == MODE_EXTERNCMD) {
   3211    ui_cursor_goto(msg_row, msg_col);     // put cursor back
   3212  } else if (State == MODE_HITRETURN || State == MODE_SETWSIZE) {
   3213    if (msg_row == Rows - 1) {
   3214      // Avoid drawing the "hit-enter" prompt below the previous one,
   3215      // overwrite it.  Esp. useful when regaining focus and a
   3216      // FocusGained autocmd exists but didn't draw anything.
   3217      msg_didout = false;
   3218      msg_col = 0;
   3219      msg_clr_eos();
   3220    }
   3221    hit_return_msg(false);
   3222    msg_row = Rows - 1;
   3223  }
   3224 }
   3225 
   3226 /// Clear from current message position to end of screen.
   3227 /// Skip this when ":silent" was used, no need to clear for redirection.
   3228 void msg_clr_eos(void)
   3229 {
   3230  if (msg_silent == 0) {
   3231    msg_clr_eos_force();
   3232  }
   3233 }
   3234 
   3235 /// Clear from current message position to end of screen.
   3236 /// Note: msg_col is not updated, so we remember the end of the message
   3237 /// for msg_check().
   3238 void msg_clr_eos_force(void)
   3239 {
   3240  if (ui_has(kUIMessages)) {
   3241    return;
   3242  }
   3243  int msg_startcol = (cmdmsg_rl) ? 0 : msg_col;
   3244  int msg_endcol = (cmdmsg_rl) ? Columns - msg_col : Columns;
   3245 
   3246  // TODO(bfredl): ugly, this state should already been validated at this
   3247  // point. But msg_clr_eos() is called in a lot of places.
   3248  if (msg_grid.chars && msg_row < msg_grid_pos) {
   3249    msg_grid_validate();
   3250    if (msg_row < msg_grid_pos) {
   3251      msg_row = msg_grid_pos;
   3252    }
   3253  }
   3254 
   3255  grid_clear(&msg_grid_adj, msg_row, msg_row + 1, msg_startcol, msg_endcol, HL_ATTR(HLF_MSG));
   3256  grid_clear(&msg_grid_adj, msg_row + 1, Rows, 0, Columns, HL_ATTR(HLF_MSG));
   3257 
   3258  redraw_cmdline = true;  // overwritten the command line
   3259  if (msg_row < Rows - 1 || msg_col == 0) {
   3260    clear_cmdline = false;  // command line has been cleared
   3261    mode_displayed = false;  // mode cleared or overwritten
   3262  }
   3263 }
   3264 
   3265 /// Clear the command line.
   3266 void msg_clr_cmdline(void)
   3267 {
   3268  msg_row = cmdline_row;
   3269  msg_col = 0;
   3270  msg_clr_eos_force();
   3271 }
   3272 
   3273 /// end putting a message on the screen
   3274 /// call wait_return() if the message does not fit in the available space
   3275 ///
   3276 /// @return  true if wait_return() not called.
   3277 bool msg_end(void)
   3278 {
   3279  // If the string is larger than the window,
   3280  // or the ruler option is set and we run into it,
   3281  // we have to redraw the window.
   3282  // Do not do this if we are abandoning the file or editing the command line.
   3283  if (!exiting && need_wait_return && !(State & MODE_CMDLINE)) {
   3284    wait_return(false);
   3285    return false;
   3286  }
   3287 
   3288  // NOTE: ui_flush() used to be called here. This had to be removed, as it
   3289  // inhibited substantial performance improvements. It is assumed that relevant
   3290  // callers invoke ui_flush() before going into CPU busywork, or restricted
   3291  // event processing after displaying a message to the user.
   3292  msg_ext_ui_flush();
   3293  return true;
   3294 }
   3295 
   3296 /// Clear "msg_ext_chunks" before flushing so that ui_flush() does not re-emit
   3297 /// the same message recursively.
   3298 static Array *msg_ext_init_chunks(void)
   3299 {
   3300  Array *tofree = msg_ext_chunks;
   3301  msg_ext_chunks = xcalloc(1, sizeof(*msg_ext_chunks));
   3302  msg_col = 0;
   3303  return tofree;
   3304 }
   3305 
   3306 void msg_ext_ui_flush(void)
   3307 {
   3308  if (!ui_has(kUIMessages)) {
   3309    msg_ext_kind = NULL;
   3310    return;
   3311  } else if (msg_ext_skip_flush) {
   3312    return;
   3313  }
   3314 
   3315  msg_ext_emit_chunk();
   3316  if (msg_ext_chunks->size > 0) {
   3317    Array *tofree = msg_ext_init_chunks();
   3318 
   3319    ui_call_msg_show(cstr_as_string(msg_ext_kind), *tofree, msg_ext_overwrite, msg_ext_history,
   3320                     msg_ext_append, msg_ext_id);
   3321    // clear info after emitting message.
   3322    if (msg_ext_history) {
   3323      api_free_array(*tofree);
   3324    } else {
   3325      // Add to history as temporary message for "g<".
   3326      HlMessage msg = KV_INITIAL_VALUE;
   3327      for (size_t i = 0; i < kv_size(*tofree); i++) {
   3328        Object *chunk = kv_A(*tofree, i).data.array.items;
   3329        kv_push(msg, ((HlMessageChunk){ chunk[1].data.string, (int)chunk[2].data.integer }));
   3330        xfree(chunk);
   3331      }
   3332      xfree(tofree->items);
   3333      msg_hist_add_multihl(msg, true, NULL);
   3334    }
   3335    xfree(tofree);
   3336    msg_ext_overwrite = false;
   3337    msg_ext_history = false;
   3338    msg_ext_append = false;
   3339    msg_ext_kind = NULL;
   3340    msg_id_next += (msg_ext_id.data.integer == msg_id_next);
   3341    msg_ext_id = INTEGER_OBJ(msg_id_next);
   3342  }
   3343 }
   3344 
   3345 void msg_ext_flush_showmode(void)
   3346 {
   3347  // Showmode messages doesn't interrupt normal message flow, so we use
   3348  // separate event. Still reuse the same chunking logic, for simplicity.
   3349  // This is called unconditionally; check if we are emitting, or have
   3350  // emitted non-empty "content".
   3351  static bool clear = false;
   3352  if (ui_has(kUIMessages) && (msg_ext_last_attr != -1 || clear)) {
   3353    clear = msg_ext_last_attr != -1;
   3354    msg_ext_emit_chunk();
   3355    Array *tofree = msg_ext_init_chunks();
   3356    ui_call_msg_showmode(*tofree);
   3357    api_free_array(*tofree);
   3358    xfree(tofree);
   3359  }
   3360 }
   3361 
   3362 /// If the written message runs into the shown command or ruler, we have to
   3363 /// wait for hit-return and redraw the window later.
   3364 void msg_check(void)
   3365 {
   3366  if (ui_has(kUIMessages)) {
   3367    return;
   3368  }
   3369  if (msg_row == Rows - 1 && msg_col >= sc_col) {
   3370    need_wait_return = true;
   3371    redraw_cmdline = true;
   3372  }
   3373 }
   3374 
   3375 /// May write a string to the redirection file.
   3376 ///
   3377 /// @param maxlen  if -1, write the whole string, otherwise up to "maxlen" bytes.
   3378 static void redir_write(const char *const str, const ptrdiff_t maxlen)
   3379 {
   3380  const char *s = str;
   3381 
   3382  if (maxlen == 0) {
   3383    return;
   3384  }
   3385 
   3386  // Don't do anything for displaying prompts and the like.
   3387  if (redir_off) {
   3388    return;
   3389  }
   3390 
   3391  // If 'verbosefile' is set prepare for writing in that file.
   3392  if (*p_vfile != NUL && verbose_fd == NULL) {
   3393    verbose_open();
   3394  }
   3395 
   3396  if (redirecting()) {
   3397    // If the string doesn't start with CR or NL, go to msg_col
   3398    if (*s != '\n' && *s != '\r') {
   3399      while (redir_col < msg_col) {
   3400        if (capture_ga) {
   3401          ga_concat_len(capture_ga, " ", 1);
   3402        }
   3403        if (redir_reg) {
   3404          write_reg_contents(redir_reg, " ", 1, true);
   3405        } else if (redir_vname) {
   3406          var_redir_str(" ", -1);
   3407        } else if (redir_fd != NULL) {
   3408          fputs(" ", redir_fd);
   3409        }
   3410        if (verbose_fd != NULL) {
   3411          fputs(" ", verbose_fd);
   3412        }
   3413        redir_col++;
   3414      }
   3415    }
   3416 
   3417    size_t len = maxlen == -1 ? strlen(s) : (size_t)maxlen;
   3418    if (capture_ga) {
   3419      ga_concat_len(capture_ga, str, len);
   3420    }
   3421    if (redir_reg) {
   3422      write_reg_contents(redir_reg, s, (ssize_t)len, true);
   3423    }
   3424    if (redir_vname) {
   3425      var_redir_str(s, (int)maxlen);
   3426    }
   3427 
   3428    // Write and adjust the current column.
   3429    while (*s != NUL
   3430           && (maxlen < 0 || (int)(s - str) < maxlen)) {
   3431      if (!redir_reg && !redir_vname && !capture_ga) {
   3432        if (redir_fd != NULL) {
   3433          putc(*s, redir_fd);
   3434        }
   3435      }
   3436      if (verbose_fd != NULL) {
   3437        putc(*s, verbose_fd);
   3438      }
   3439      if (*s == '\r' || *s == '\n') {
   3440        redir_col = 0;
   3441      } else if (*s == '\t') {
   3442        redir_col += (8 - redir_col % 8);
   3443      } else {
   3444        redir_col++;
   3445      }
   3446      s++;
   3447    }
   3448 
   3449    if (msg_silent != 0) {      // should update msg_col
   3450      msg_col = redir_col;
   3451    }
   3452  }
   3453 }
   3454 
   3455 int redirecting(void)
   3456 {
   3457  return redir_fd != NULL || *p_vfile != NUL
   3458         || redir_reg || redir_vname || capture_ga != NULL;
   3459 }
   3460 
   3461 // Save and restore message kind when emitting a verbose message.
   3462 static const char *pre_verbose_kind = NULL;
   3463 static const char *verbose_kind = "verbose";
   3464 
   3465 /// Before giving verbose message.
   3466 /// Must always be called paired with verbose_leave()!
   3467 void verbose_enter(void)
   3468 {
   3469  if (*p_vfile != NUL) {
   3470    msg_silent++;
   3471  }
   3472  // last_set_msg unsets p_verbose to avoid setting the verbose kind.
   3473  if (!msg_ext_skip_verbose) {
   3474    if (msg_ext_kind != verbose_kind) {
   3475      pre_verbose_kind = msg_ext_kind;
   3476    }
   3477    msg_ext_set_kind("verbose");
   3478  }
   3479  msg_ext_skip_verbose = false;
   3480 }
   3481 
   3482 /// After giving verbose message.
   3483 /// Must always be called paired with verbose_enter()!
   3484 void verbose_leave(void)
   3485 {
   3486  if (*p_vfile != NUL) {
   3487    if (--msg_silent < 0) {
   3488      msg_silent = 0;
   3489    }
   3490  }
   3491  if (pre_verbose_kind != NULL) {
   3492    msg_ext_set_kind(pre_verbose_kind);
   3493    pre_verbose_kind = NULL;
   3494  }
   3495 }
   3496 
   3497 /// Like verbose_enter() and set msg_scroll when displaying the message.
   3498 void verbose_enter_scroll(void)
   3499 {
   3500  verbose_enter();
   3501  if (*p_vfile == NUL) {
   3502    // always scroll up, don't overwrite
   3503    msg_scroll = true;
   3504  }
   3505 }
   3506 
   3507 /// Like verbose_leave() and set cmdline_row when displaying the message.
   3508 void verbose_leave_scroll(void)
   3509 {
   3510  verbose_leave();
   3511  if (*p_vfile == NUL) {
   3512    cmdline_row = msg_row;
   3513  }
   3514 }
   3515 
   3516 /// Called when 'verbosefile' is set: stop writing to the file.
   3517 void verbose_stop(void)
   3518 {
   3519  if (verbose_fd != NULL) {
   3520    fclose(verbose_fd);
   3521    verbose_fd = NULL;
   3522  }
   3523  verbose_did_open = false;
   3524 }
   3525 
   3526 /// Open the file 'verbosefile'.
   3527 ///
   3528 /// @return  FAIL or OK.
   3529 int verbose_open(void)
   3530 {
   3531  if (verbose_fd == NULL && !verbose_did_open) {
   3532    // Only give the error message once.
   3533    verbose_did_open = true;
   3534 
   3535    verbose_fd = os_fopen(p_vfile, "a");
   3536    if (verbose_fd == NULL) {
   3537      semsg(_(e_notopen), p_vfile);
   3538      return FAIL;
   3539    }
   3540  }
   3541  return OK;
   3542 }
   3543 
   3544 /// Give a warning message (for searching).
   3545 /// Use 'w' highlighting and may repeat the message after redrawing
   3546 void give_warning(const char *message, bool hl, bool hist)
   3547  FUNC_ATTR_NONNULL_ARG(1)
   3548 {
   3549  // Don't do this for ":silent".
   3550  if (msg_silent != 0) {
   3551    return;
   3552  }
   3553 
   3554  bool save_msg_hist_off = msg_hist_off;
   3555  msg_hist_off = !hist;
   3556 
   3557  // Don't want a hit-enter prompt here.
   3558  no_wait_return++;
   3559 
   3560  set_vim_var_string(VV_WARNINGMSG, message, -1);
   3561  XFREE_CLEAR(keep_msg);
   3562  if (hl) {
   3563    keep_msg_hl_id = HLF_W;
   3564  } else {
   3565    keep_msg_hl_id = 0;
   3566  }
   3567 
   3568  if (msg_ext_kind == NULL) {
   3569    msg_ext_set_kind("wmsg");
   3570  }
   3571 
   3572  if (msg(message, keep_msg_hl_id) && msg_scrolled == 0) {
   3573    set_keep_msg(message, keep_msg_hl_id);
   3574  }
   3575  msg_didout = false;  // Overwrite this message.
   3576  msg_nowait = true;   // Don't wait for this message.
   3577  msg_col = 0;
   3578 
   3579  no_wait_return--;
   3580  msg_hist_off = save_msg_hist_off;
   3581 }
   3582 
   3583 /// Shows a warning, with optional highlighting.
   3584 ///
   3585 /// @param hl enable highlighting
   3586 /// @param fmt printf-style format message
   3587 ///
   3588 /// @see smsg
   3589 /// @see semsg
   3590 void swmsg(bool hl, const char *const fmt, ...)
   3591  FUNC_ATTR_PRINTF(2, 3)
   3592 {
   3593  va_list args;
   3594 
   3595  va_start(args, fmt);
   3596  vim_vsnprintf(IObuff, IOSIZE, fmt, args);
   3597  va_end(args);
   3598 
   3599  give_warning(IObuff, hl, true);
   3600 }
   3601 
   3602 /// Advance msg cursor to column "col".
   3603 void msg_advance(int col)
   3604 {
   3605  if (msg_silent != 0) {        // nothing to advance to
   3606    msg_col = col;              // for redirection, may fill it up later
   3607    return;
   3608  }
   3609  col = MIN(col, Columns - 1);  // not enough room
   3610  while (msg_col < col) {
   3611    msg_putchar(' ');
   3612  }
   3613 }
   3614 
   3615 /// Used for "confirm()" function, and the :confirm command prefix.
   3616 /// Versions which haven't got flexible dialogs yet, and console
   3617 /// versions, get this generic handler which uses the command line.
   3618 ///
   3619 /// type  = one of:
   3620 ///         VIM_QUESTION, VIM_INFO, VIM_WARNING, VIM_ERROR or VIM_GENERIC
   3621 /// title = title string (can be NULL for default)
   3622 /// (neither used in console dialogs at the moment)
   3623 ///
   3624 /// Format of the "buttons" string:
   3625 /// "Button1Name\nButton2Name\nButton3Name"
   3626 /// The first button should normally be the default/accept
   3627 /// The second button should be the 'Cancel' button
   3628 /// Other buttons- use your imagination!
   3629 /// A '&' in a button name becomes a shortcut, so each '&' should be before a
   3630 /// different letter.
   3631 ///
   3632 /// @param textfiel  IObuff for inputdialog(), NULL otherwise
   3633 /// @param ex_cmd  when true pressing : accepts default and starts Ex command
   3634 /// @returns 0 if cancelled, otherwise the nth button (1-indexed).
   3635 int do_dialog(int type, const char *title, const char *message, const char *buttons, int dfltbutton,
   3636              const char *textfield, int ex_cmd)
   3637 {
   3638  int retval = 0;
   3639  int i;
   3640 
   3641  if (silent_mode) {  // No dialogs in silent mode ("ex -s")
   3642    return dfltbutton;  // return default option
   3643  }
   3644 
   3645  int save_msg_silent = msg_silent;
   3646  int oldState = State;
   3647 
   3648  msg_silent = 0;  // If dialog prompts for input, user needs to see it! #8788
   3649 
   3650  // Since we wait for a keypress, don't make the
   3651  // user press RETURN as well afterwards.
   3652  no_wait_return++;
   3653  char *hotkeys = msg_show_console_dialog(message, buttons, dfltbutton);
   3654 
   3655  while (true) {
   3656    // Without a UI Nvim waits for input forever.
   3657    if (!ui_active() && !input_available()) {
   3658      retval = dfltbutton;
   3659      break;
   3660    }
   3661 
   3662    // Get a typed character directly from the user.
   3663    int c = prompt_for_input(confirm_buttons, HLF_M, true, NULL);
   3664    switch (c) {
   3665    case CAR:                 // User accepts default option
   3666    case NUL:
   3667      retval = dfltbutton;
   3668      break;
   3669    case Ctrl_C:              // User aborts/cancels
   3670    case ESC:
   3671      retval = 0;
   3672      break;
   3673    default:                  // Could be a hotkey?
   3674      if (c < 0) {            // special keys are ignored here
   3675        msg_didout = msg_didany = false;
   3676        continue;
   3677      }
   3678      if (c == ':' && ex_cmd) {
   3679        retval = dfltbutton;
   3680        ins_char_typebuf(':', 0, false);
   3681        break;
   3682      }
   3683 
   3684      // Make the character lowercase, as chars in "hotkeys" are.
   3685      c = mb_tolower(c);
   3686      retval = 1;
   3687      for (i = 0; hotkeys[i]; i++) {
   3688        if (utf_ptr2char(hotkeys + i) == c) {
   3689          break;
   3690        }
   3691        i += utfc_ptr2len(hotkeys + i) - 1;
   3692        retval++;
   3693      }
   3694      if (hotkeys[i]) {
   3695        break;
   3696      }
   3697      // No hotkey match, so keep waiting
   3698      msg_didout = msg_didany = false;
   3699      continue;
   3700    }
   3701    break;
   3702  }
   3703 
   3704  xfree(hotkeys);
   3705  xfree(confirm_msg);
   3706  confirm_msg = NULL;
   3707 
   3708  msg_silent = save_msg_silent;
   3709  State = oldState;
   3710  setmouse();
   3711  no_wait_return--;
   3712  msg_end_prompt();
   3713 
   3714  return retval;
   3715 }
   3716 
   3717 /// Copy one character from "*from" to "*to", taking care of multi-byte
   3718 /// characters.  Return the length of the character in bytes.
   3719 ///
   3720 /// @param lowercase  make character lower case
   3721 static int copy_char(const char *from, char *to, bool lowercase)
   3722  FUNC_ATTR_NONNULL_ALL
   3723 {
   3724  if (lowercase) {
   3725    int c = mb_tolower(utf_ptr2char(from));
   3726    return utf_char2bytes(c, to);
   3727  }
   3728  int len = utfc_ptr2len(from);
   3729  memmove(to, from, (size_t)len);
   3730  return len;
   3731 }
   3732 
   3733 #define HAS_HOTKEY_LEN 30
   3734 #define HOTK_LEN MB_MAXBYTES
   3735 
   3736 /// Allocates memory for dialog string & for storing hotkeys
   3737 ///
   3738 /// Finds the size of memory required for the confirm_msg & for storing hotkeys
   3739 /// and then allocates the memory for them.
   3740 /// has_hotkey array is also filled-up.
   3741 ///
   3742 /// @param message Message which will be part of the confirm_msg
   3743 /// @param buttons String containing button names
   3744 /// @param[out] has_hotkey An element in this array is set to true if
   3745 ///                        corresponding button has a hotkey
   3746 ///
   3747 /// @return Pointer to memory allocated for storing hotkeys
   3748 static char *console_dialog_alloc(const char *message, const char *buttons, bool has_hotkey[])
   3749 {
   3750  int lenhotkey = HOTK_LEN;  // count first button
   3751  has_hotkey[0] = false;
   3752 
   3753  // Compute the size of memory to allocate.
   3754  int msg_len = 0;
   3755  int button_len = 0;
   3756  int idx = 0;
   3757  const char *r = buttons;
   3758  while (*r) {
   3759    if (*r == DLG_BUTTON_SEP) {
   3760      button_len += 3;                  // '\n' -> ', '; 'x' -> '(x)'
   3761      lenhotkey += HOTK_LEN;            // each button needs a hotkey
   3762      if (idx < HAS_HOTKEY_LEN - 1) {
   3763        has_hotkey[++idx] = false;
   3764      }
   3765    } else if (*r == DLG_HOTKEY_CHAR) {
   3766      r++;
   3767      button_len++;                     // '&a' -> '[a]'
   3768      if (idx < HAS_HOTKEY_LEN - 1) {
   3769        has_hotkey[idx] = true;
   3770      }
   3771    }
   3772 
   3773    // Advance to the next character
   3774    MB_PTR_ADV(r);
   3775  }
   3776 
   3777  msg_len += (int)strlen(message) + 3;     // for the NL's and NUL
   3778  button_len += (int)strlen(buttons) + 3;  // for the ": " and NUL
   3779  lenhotkey++;                             // for the NUL
   3780 
   3781  // If no hotkey is specified, first char is used.
   3782  if (!has_hotkey[0]) {
   3783    button_len += 2;                       // "x" -> "[x]"
   3784  }
   3785 
   3786  // Now allocate space for the strings
   3787  confirm_msg = xmalloc((size_t)msg_len);
   3788  snprintf(confirm_msg, (size_t)msg_len, ui_has(kUIMessages) ? "%s" : "\n%s\n", message);
   3789 
   3790  xfree(confirm_buttons);
   3791  confirm_buttons = xmalloc((size_t)button_len);
   3792 
   3793  return xmalloc((size_t)lenhotkey);
   3794 }
   3795 
   3796 /// Format the dialog string, and display it at the bottom of
   3797 /// the screen. Return a string of hotkey chars (if defined) for
   3798 /// each 'button'. If a button has no hotkey defined, the first character of
   3799 /// the button is used.
   3800 /// The hotkeys can be multi-byte characters, but without combining chars.
   3801 ///
   3802 /// @return  an allocated string with hotkeys.
   3803 static char *msg_show_console_dialog(const char *message, const char *buttons, int dfltbutton)
   3804  FUNC_ATTR_NONNULL_RET
   3805 {
   3806  bool has_hotkey[HAS_HOTKEY_LEN] = { false };
   3807  char *hotk = console_dialog_alloc(message, buttons, has_hotkey);
   3808 
   3809  copy_confirm_hotkeys(buttons, dfltbutton, has_hotkey, hotk);
   3810 
   3811  display_confirm_msg();
   3812  return hotk;
   3813 }
   3814 
   3815 /// Copies hotkeys into the memory allocated for it
   3816 ///
   3817 /// @param buttons String containing button names
   3818 /// @param default_button_idx Number of default button
   3819 /// @param has_hotkey An element in this array is true if corresponding button
   3820 ///                   has a hotkey
   3821 /// @param[out] hotkeys_ptr Pointer to the memory location where hotkeys will be copied
   3822 static void copy_confirm_hotkeys(const char *buttons, int default_button_idx,
   3823                                 const bool has_hotkey[], char *hotkeys_ptr)
   3824 {
   3825  // Define first default hotkey. Keep the hotkey string NUL
   3826  // terminated to avoid reading past the end.
   3827  hotkeys_ptr[copy_char(buttons, hotkeys_ptr, true)] = NUL;
   3828 
   3829  bool first_hotkey = false;  // Is the first char of button a hotkey
   3830  if (!has_hotkey[0]) {
   3831    first_hotkey = true;     // If no hotkey is specified, first char is used
   3832  }
   3833 
   3834  // Remember where the choices start, sent as prompt to cmdline.
   3835  char *msgp = confirm_buttons;
   3836 
   3837  int idx = 0;
   3838  const char *r = buttons;
   3839  while (*r) {
   3840    if (*r == DLG_BUTTON_SEP) {
   3841      *msgp++ = ',';
   3842      *msgp++ = ' ';                    // '\n' -> ', '
   3843 
   3844      // Advance to next hotkey and set default hotkey
   3845      hotkeys_ptr += strlen(hotkeys_ptr);
   3846      hotkeys_ptr[copy_char(r + 1, hotkeys_ptr, true)] = NUL;
   3847 
   3848      if (default_button_idx) {
   3849        default_button_idx--;
   3850      }
   3851 
   3852      // If no hotkey is specified, first char is used.
   3853      if (idx < HAS_HOTKEY_LEN - 1 && !has_hotkey[++idx]) {
   3854        first_hotkey = true;
   3855      }
   3856    } else if (*r == DLG_HOTKEY_CHAR || first_hotkey) {
   3857      if (*r == DLG_HOTKEY_CHAR) {
   3858        r++;
   3859      }
   3860 
   3861      first_hotkey = false;
   3862      if (*r == DLG_HOTKEY_CHAR) {                 // '&&a' -> '&a'
   3863        *msgp++ = *r;
   3864      } else {
   3865        // '&a' -> '[a]'
   3866        *msgp++ = (default_button_idx == 1) ? '[' : '(';
   3867        msgp += copy_char(r, msgp, false);
   3868        *msgp++ = (default_button_idx == 1) ? ']' : ')';
   3869 
   3870        // redefine hotkey
   3871        hotkeys_ptr[copy_char(r, hotkeys_ptr, true)] = NUL;
   3872      }
   3873    } else {
   3874      // everything else copy literally
   3875      msgp += copy_char(r, msgp, false);
   3876    }
   3877 
   3878    // advance to the next character
   3879    MB_PTR_ADV(r);
   3880  }
   3881 
   3882  *msgp++ = ':';
   3883  *msgp++ = ' ';
   3884  *msgp = NUL;
   3885 }
   3886 
   3887 /// Display the ":confirm" message.  Also called when screen resized.
   3888 static void display_confirm_msg(void)
   3889 {
   3890  // Avoid that 'q' at the more prompt truncates the message here.
   3891  confirm_msg_used++;
   3892  if (confirm_msg != NULL) {
   3893    msg_ext_set_kind("confirm");
   3894    msg_puts_hl(confirm_msg, HLF_M, false);
   3895  }
   3896  confirm_msg_used--;
   3897 }
   3898 
   3899 int vim_dialog_yesno(int type, char *title, char *message, int dflt)
   3900 {
   3901  if (do_dialog(type,
   3902                title == NULL ? _("Question") : title,
   3903                message,
   3904                _("&Yes\n&No"), dflt, NULL, false) == 1) {
   3905    return VIM_YES;
   3906  }
   3907  return VIM_NO;
   3908 }
   3909 
   3910 int vim_dialog_yesnocancel(int type, char *title, char *message, int dflt)
   3911 {
   3912  switch (do_dialog(type,
   3913                    title == NULL ? _("Question") : title,
   3914                    message,
   3915                    _("&Yes\n&No\n&Cancel"), dflt, NULL, false)) {
   3916  case 1:
   3917    return VIM_YES;
   3918  case 2:
   3919    return VIM_NO;
   3920  }
   3921  return VIM_CANCEL;
   3922 }
   3923 
   3924 int vim_dialog_yesnoallcancel(int type, char *title, char *message, int dflt)
   3925 {
   3926  switch (do_dialog(type,
   3927                    title == NULL ? "Question" : title,
   3928                    message,
   3929                    _("&Yes\n&No\nSave &All\n&Discard All\n&Cancel"),
   3930                    dflt, NULL, false)) {
   3931  case 1:
   3932    return VIM_YES;
   3933  case 2:
   3934    return VIM_NO;
   3935  case 3:
   3936    return VIM_ALL;
   3937  case 4:
   3938    return VIM_DISCARDALL;
   3939  }
   3940  return VIM_CANCEL;
   3941 }
   3942 
   3943 /// Only for legacy UI (`!ui_has(kUIMessages)`): Pause to display a message for `ms` milliseconds.
   3944 ///
   3945 /// TODO(justinmk): Most of these cases may not be needed after "ui2"...
   3946 void msg_delay(uint64_t ms, bool ignoreinput)
   3947 {
   3948  if (ui_has(kUIMessages)) {
   3949    return;
   3950  }
   3951 
   3952  if (nvim_testing) {
   3953    // XXX: Reduce non-functional (UI only) delay in tests/CI.
   3954    ms = 100;
   3955  }
   3956 
   3957  DLOG("%" PRIu64 " ms%s", ms, nvim_testing ? " (skipped by NVIM_TEST)" : "");
   3958  ui_flush();
   3959  os_delay(ms, ignoreinput);
   3960 }
   3961 
   3962 /// Check if there should be a delay to allow the user to see a message.
   3963 ///
   3964 /// Used before clearing or redrawing the screen or the command line.
   3965 void msg_check_for_delay(bool check_msg_scroll)
   3966 {
   3967  if ((emsg_on_display || (check_msg_scroll && msg_scroll))
   3968      && !did_wait_return && emsg_silent == 0 && !in_assert_fails && !ui_has(kUIMessages)) {
   3969    msg_delay(1006, true);
   3970    emsg_on_display = false;
   3971    if (check_msg_scroll) {
   3972      msg_scroll = false;
   3973    }
   3974  }
   3975 }