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