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 | */ |
51 | typedef struct msgchunk_S msgchunk_T; |
52 | struct 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 | |
65 | static int confirm_msg_used = FALSE; /* displaying confirm_msg */ |
66 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
67 | # include "message.c.generated.h" |
68 | #endif |
69 | static char_u *confirm_msg = NULL; /* ":confirm" message */ |
70 | static char_u *confirm_msg_tail; /* tail of confirm_msg */ |
71 | |
72 | MessageHistoryEntry *first_msg_hist = NULL; |
73 | MessageHistoryEntry *last_msg_hist = NULL; |
74 | static int msg_hist_len = 0; |
75 | |
76 | static FILE *verbose_fd = NULL; |
77 | static 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 |
117 | static const char *msg_ext_kind = NULL; |
118 | static Array msg_ext_chunks = ARRAY_DICT_INIT; |
119 | static garray_T msg_ext_last_chunk = GA_INIT(sizeof(char), 40); |
120 | static sattr_T msg_ext_last_attr = -1; |
121 | static size_t msg_ext_cur_len = 0; |
122 | |
123 | static bool msg_ext_overwrite = false; ///< will overwrite last message |
124 | static int msg_ext_visible = 0; ///< number of messages currently visible |
125 | |
126 | /// Shouldn't clear message after leaving cmdline |
127 | static bool msg_ext_keep_after_cmdline = false; |
128 | |
129 | static int msg_grid_pos_at_flush = 0; |
130 | static int msg_grid_scroll_discount = 0; |
131 | |
132 | static 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 | |
141 | void 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 | |
153 | bool msg_use_grid(void) |
154 | { |
155 | return default_grid.chars && msg_use_msgsep() |
156 | && !ui_has(kUIMessages); |
157 | } |
158 | |
159 | void 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 | */ |
209 | int 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. |
215 | int 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 | |
224 | int 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. |
231 | void 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 |
260 | bool 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 | */ |
336 | char_u * |
337 | msg_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 | */ |
372 | void 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 | |
455 | int 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 | |
466 | int 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 | |
477 | int 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 | */ |
492 | static int last_sourcing_lnum = 0; |
493 | static 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 | */ |
499 | void 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 | */ |
508 | static 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. |
522 | static 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. |
539 | static 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 | */ |
561 | void 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 | */ |
593 | int 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 | |
603 | static 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 |
727 | bool emsg(const char_u *s) |
728 | { |
729 | return emsg_multiline((const char *)s, false); |
730 | } |
731 | |
732 | void 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 |
738 | bool 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 | |
753 | bool 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 |
774 | static 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. |
789 | void 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. |
800 | void 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. |
812 | void internal_error(char *where) |
813 | { |
814 | IEMSG2(_(e_intern2), where); |
815 | } |
816 | |
817 | static void msg_emsgf_event(void **argv) |
818 | { |
819 | char *s = argv[0]; |
820 | (void)emsg((char_u *)s); |
821 | xfree(s); |
822 | } |
823 | |
824 | void 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 | */ |
842 | char_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 | */ |
865 | char_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. |
893 | static 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 | */ |
933 | int 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 |
952 | void 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 | */ |
1021 | void 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 |
1037 | void 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 | */ |
1227 | static 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 | */ |
1249 | void 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 | |
1260 | void 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 | */ |
1274 | void 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 | */ |
1323 | void msg_starthere(void) |
1324 | { |
1325 | lines_left = cmdline_row; |
1326 | msg_didany = FALSE; |
1327 | } |
1328 | |
1329 | void msg_putchar(int c) |
1330 | { |
1331 | msg_putchar_attr(c, 0); |
1332 | } |
1333 | |
1334 | void 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 | |
1349 | void msg_outnum(long n) |
1350 | { |
1351 | char buf[20]; |
1352 | |
1353 | snprintf(buf, sizeof(buf), "%ld" , n); |
1354 | msg_puts(buf); |
1355 | } |
1356 | |
1357 | void msg_home_replace(char_u *fname) |
1358 | { |
1359 | msg_home_replace_attr(fname, 0); |
1360 | } |
1361 | |
1362 | void msg_home_replace_hl(char_u *fname) |
1363 | { |
1364 | msg_home_replace_attr(fname, HL_ATTR(HLF_D)); |
1365 | } |
1366 | |
1367 | static 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 | */ |
1382 | int msg_outtrans(char_u *str) |
1383 | { |
1384 | return msg_outtrans_attr(str, 0); |
1385 | } |
1386 | |
1387 | int msg_outtrans_attr(char_u *str, int attr) |
1388 | { |
1389 | return msg_outtrans_len_attr(str, (int)STRLEN(str), attr); |
1390 | } |
1391 | |
1392 | int 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 | */ |
1401 | char_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 | |
1413 | int 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 | |
1485 | void 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 |
1513 | int 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. |
1554 | char *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. |
1580 | const 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. |
1646 | void 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 | */ |
1665 | void msg_prt_line(char_u *s, int list) |
1666 | { |
1667 | int c; |
1668 | int col = 0; |
1669 | int = 0; |
1670 | int = 0; |
1671 | int c_final = 0; |
1672 | char_u * = 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. |
1778 | static 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 | */ |
1813 | void msg_puts(const char *s) |
1814 | { |
1815 | msg_puts_attr(s, 0); |
1816 | } |
1817 | |
1818 | void 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 | */ |
1828 | void msg_puts_long_attr(char_u *longstr, int attr) |
1829 | { |
1830 | msg_puts_long_len_attr(longstr, (int)STRLEN(longstr), attr); |
1831 | } |
1832 | |
1833 | void 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 | */ |
1850 | void 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. |
1860 | void 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. |
1922 | void 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 | |
1936 | static 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 | */ |
1954 | static 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". |
2151 | bool 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 |
2162 | int msg_scrollsize(void) |
2163 | { |
2164 | return msg_scrolled + p_ch + 1; |
2165 | } |
2166 | |
2167 | bool 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 | |
2174 | bool 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. |
2180 | void 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. |
2218 | void 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 | |
2252 | void 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 | */ |
2283 | static 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 | |
2309 | static msgchunk_T *last_msgchunk = NULL; // last displayed text |
2310 | |
2311 | typedef 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. |
2319 | static sb_clear_T do_clear_sb_text = SB_CLEAR_NONE; |
2320 | |
2321 | /// Store part of a printed message for displaying when scrolling back. |
2322 | static 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 | */ |
2365 | void 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. |
2371 | void 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. |
2378 | void 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. |
2386 | void 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 | */ |
2410 | void 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 | */ |
2428 | static 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 | */ |
2440 | void 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 | */ |
2450 | static 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 | */ |
2473 | static 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 |
2495 | int msg_use_printf(void) |
2496 | { |
2497 | return !embedded_mode && !ui_active(); |
2498 | } |
2499 | |
2500 | /// Print a message when there is no valid screen. |
2501 | static 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 | */ |
2551 | static 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) |
2786 | void 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. |
2800 | void 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 | */ |
2818 | static 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 | |
2836 | void 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 | */ |
2854 | void 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 | */ |
2882 | void 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 | */ |
2893 | void 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 | */ |
2922 | void 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 | */ |
2934 | int 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 | |
2955 | void 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 | |
2975 | void 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 | |
2987 | void 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 | |
2999 | void 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 | |
3009 | void 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 | |
3018 | bool 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 | */ |
3027 | void 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 | */ |
3042 | static 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 | |
3117 | int 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 | */ |
3127 | void 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 | */ |
3137 | void 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 | */ |
3147 | void 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 | */ |
3159 | void 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 | */ |
3171 | void 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 | */ |
3184 | int 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 | */ |
3203 | void 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 | |
3235 | void 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 | */ |
3244 | void 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 | */ |
3286 | int |
3287 | do_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 | */ |
3381 | static int |
3382 | copy_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 |
3412 | static 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 | */ |
3471 | static 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 |
3491 | static 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 | */ |
3568 | void 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 | |
3579 | int 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 | |
3589 | int 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 | |
3601 | int 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 | |