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