buffer.c (24078B)
1 // eval/buffer.c: Buffer related builtin functions 2 3 #include <stdbool.h> 4 #include <string.h> 5 6 #include "klib/kvec.h" 7 #include "nvim/ascii_defs.h" 8 #include "nvim/autocmd.h" 9 #include "nvim/autocmd_defs.h" 10 #include "nvim/buffer.h" 11 #include "nvim/buffer_defs.h" 12 #include "nvim/change.h" 13 #include "nvim/cursor.h" 14 #include "nvim/drawscreen.h" 15 #include "nvim/edit.h" 16 #include "nvim/eval.h" 17 #include "nvim/eval/buffer.h" 18 #include "nvim/eval/funcs.h" 19 #include "nvim/eval/typval.h" 20 #include "nvim/eval/typval_defs.h" 21 #include "nvim/eval/window.h" 22 #include "nvim/ex_cmds.h" 23 #include "nvim/extmark.h" 24 #include "nvim/globals.h" 25 #include "nvim/macros_defs.h" 26 #include "nvim/memline.h" 27 #include "nvim/memory.h" 28 #include "nvim/move.h" 29 #include "nvim/path.h" 30 #include "nvim/pos_defs.h" 31 #include "nvim/sign.h" 32 #include "nvim/strings.h" 33 #include "nvim/types_defs.h" 34 #include "nvim/undo.h" 35 #include "nvim/vim_defs.h" 36 37 typedef struct { 38 win_T *cob_curwin_save; 39 aco_save_T cob_aco; 40 int cob_using_aco; 41 int cob_save_VIsual_active; 42 } cob_T; 43 44 #include "eval/buffer.c.generated.h" 45 46 /// Find a buffer by number or exact name. 47 buf_T *find_buffer(typval_T *avar) 48 { 49 buf_T *buf = NULL; 50 51 if (avar->v_type == VAR_NUMBER) { 52 buf = buflist_findnr((int)avar->vval.v_number); 53 } else if (avar->v_type == VAR_STRING && avar->vval.v_string != NULL) { 54 buf = buflist_findname_exp(avar->vval.v_string); 55 if (buf == NULL) { 56 // No full path name match, try a match with a URL or a "nofile" 57 // buffer, these don't use the full path. 58 FOR_ALL_BUFFERS(bp) { 59 if (bp->b_fname != NULL 60 && (path_with_url(bp->b_fname) || bt_nofilename(bp)) 61 && strcmp(bp->b_fname, avar->vval.v_string) == 0) { 62 buf = bp; 63 break; 64 } 65 } 66 } 67 } 68 return buf; 69 } 70 71 /// If there is a window for "curbuf", make it the current window. 72 static void find_win_for_curbuf(void) 73 { 74 // The b_wininfo list should have the windows that recently contained the 75 // buffer, going over this is faster than going over all the windows. 76 // Do check the buffer is still there. 77 for (size_t i = 0; i < kv_size(curbuf->b_wininfo); i++) { 78 WinInfo *wip = kv_A(curbuf->b_wininfo, i); 79 if (wip->wi_win != NULL && wip->wi_win->w_buffer == curbuf) { 80 curwin = wip->wi_win; 81 break; 82 } 83 } 84 } 85 86 /// Used before making a change in "buf", which is not the current one: Make 87 /// "buf" the current buffer and find a window for this buffer, so that side 88 /// effects are done correctly (e.g., adjusting marks). 89 /// 90 /// Information is saved in "cob" and MUST be restored by calling 91 /// change_other_buffer_restore(). 92 static void change_other_buffer_prepare(cob_T *cob, buf_T *buf) 93 { 94 CLEAR_POINTER(cob); 95 96 // Set "curbuf" to the buffer being changed. Then make sure there is a 97 // window for it to handle any side effects. 98 cob->cob_save_VIsual_active = VIsual_active; 99 VIsual_active = false; 100 cob->cob_curwin_save = curwin; 101 curbuf = buf; 102 find_win_for_curbuf(); // simplest: find existing window for "buf" 103 104 if (curwin->w_buffer != buf) { 105 // No existing window for this buffer. It is dangerous to have 106 // curwin->w_buffer differ from "curbuf", use the autocmd window. 107 curbuf = curwin->w_buffer; 108 aucmd_prepbuf(&cob->cob_aco, buf); 109 cob->cob_using_aco = true; 110 } 111 } 112 113 static void change_other_buffer_restore(cob_T *cob) 114 { 115 if (cob->cob_using_aco) { 116 aucmd_restbuf(&cob->cob_aco); 117 } else { 118 curwin = cob->cob_curwin_save; 119 curbuf = curwin->w_buffer; 120 } 121 VIsual_active = cob->cob_save_VIsual_active; 122 } 123 124 /// Set line or list of lines in buffer "buf" to "lines". 125 /// Any type is allowed and converted to a string. 126 static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, typval_T *lines, 127 typval_T *rettv) 128 FUNC_ATTR_NONNULL_ARG(4, 5) 129 { 130 linenr_T lnum = lnum_arg + (append ? 1 : 0); 131 int added = 0; 132 133 // When using the current buffer ml_mfp will be set if needed. Useful when 134 // setline() is used on startup. For other buffers the buffer must be 135 // loaded. 136 const bool is_curbuf = buf == curbuf; 137 if (buf == NULL || (!is_curbuf && buf->b_ml.ml_mfp == NULL) || lnum < 1) { 138 rettv->vval.v_number = 1; // FAIL 139 return; 140 } 141 142 // After this don't use "return", goto "cleanup"! 143 cob_T cob; 144 if (!is_curbuf) { 145 // set "curbuf" to "buf" and find a window for this buffer 146 change_other_buffer_prepare(&cob, buf); 147 } 148 149 linenr_T append_lnum; 150 if (append) { 151 // appendbufline() uses the line number below which we insert 152 append_lnum = lnum - 1; 153 } else { 154 // setbufline() uses the line number above which we insert, we only 155 // append if it's below the last line 156 append_lnum = curbuf->b_ml.ml_line_count; 157 } 158 159 list_T *l = NULL; 160 listitem_T *li = NULL; 161 char *line = NULL; 162 if (lines->v_type == VAR_LIST) { 163 l = lines->vval.v_list; 164 if (l == NULL || tv_list_len(l) == 0) { 165 // not appending anything always succeeds 166 goto cleanup; 167 } 168 li = tv_list_first(l); 169 } else { 170 line = typval_tostring(lines, false); 171 } 172 173 // Default result is zero == OK. 174 while (true) { 175 if (lines->v_type == VAR_LIST) { 176 // List argument, get next string. 177 if (li == NULL) { 178 break; 179 } 180 xfree(line); 181 line = typval_tostring(TV_LIST_ITEM_TV(li), false); 182 li = TV_LIST_ITEM_NEXT(l, li); 183 } 184 185 rettv->vval.v_number = 1; // FAIL 186 if (line == NULL || lnum > curbuf->b_ml.ml_line_count + 1) { 187 break; 188 } 189 190 // When coming here from Insert mode, sync undo, so that this can be 191 // undone separately from what was previously inserted. 192 if (u_sync_once == 2) { 193 u_sync_once = 1; // notify that u_sync() was called 194 u_sync(true); 195 } 196 197 if (!append && lnum <= curbuf->b_ml.ml_line_count) { 198 // Existing line, replace it. 199 int old_len = (int)strlen(ml_get(lnum)); 200 if (u_savesub(lnum) == OK 201 && ml_replace(lnum, line, true) == OK) { 202 inserted_bytes(lnum, 0, old_len, (int)strlen(line)); 203 if (is_curbuf && lnum == curwin->w_cursor.lnum) { 204 check_cursor_col(curwin); 205 } 206 rettv->vval.v_number = 0; // OK 207 } 208 } else if (added > 0 || u_save(lnum - 1, lnum) == OK) { 209 // append the line. 210 added++; 211 if (ml_append(lnum - 1, line, 0, false) == OK) { 212 rettv->vval.v_number = 0; // OK 213 } 214 } 215 216 if (l == NULL) { // only one string argument 217 break; 218 } 219 lnum++; 220 } 221 xfree(line); 222 223 if (added > 0) { 224 appended_lines_mark(append_lnum, added); 225 226 // Only adjust the cursor for buffers other than the current, unless it 227 // is the current window. For curbuf and other windows it has been done 228 // in mark_adjust_internal(). 229 FOR_ALL_TAB_WINDOWS(tp, wp) { 230 if (wp->w_buffer == buf 231 && (wp->w_buffer != curbuf || wp == curwin) 232 && wp->w_cursor.lnum > append_lnum) { 233 wp->w_cursor.lnum += (linenr_T)added; 234 } 235 } 236 check_cursor_col(curwin); 237 update_topline(curwin); 238 } 239 240 cleanup: 241 if (!is_curbuf) { 242 change_other_buffer_restore(&cob); 243 } 244 } 245 246 /// "append(lnum, string/list)" function 247 void f_append(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 248 { 249 const int did_emsg_before = did_emsg; 250 const linenr_T lnum = tv_get_lnum(&argvars[0]); 251 if (did_emsg == did_emsg_before) { 252 set_buffer_lines(curbuf, lnum, true, &argvars[1], rettv); 253 } 254 } 255 256 /// Set or append lines to a buffer. 257 static void buf_set_append_line(typval_T *argvars, typval_T *rettv, bool append) 258 { 259 const int did_emsg_before = did_emsg; 260 buf_T *const buf = tv_get_buf(&argvars[0], false); 261 if (buf == NULL) { 262 rettv->vval.v_number = 1; // FAIL 263 } else { 264 const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf); 265 if (did_emsg == did_emsg_before) { 266 set_buffer_lines(buf, lnum, append, &argvars[2], rettv); 267 } 268 } 269 } 270 271 /// "appendbufline(buf, lnum, string/list)" function 272 void f_appendbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 273 { 274 buf_set_append_line(argvars, rettv, true); 275 } 276 277 /// "bufadd(expr)" function 278 void f_bufadd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 279 { 280 char *name = (char *)tv_get_string(&argvars[0]); 281 282 rettv->vval.v_number = buflist_add(*name == NUL ? NULL : name, 0); 283 } 284 285 /// "bufexists(expr)" function 286 void f_bufexists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 287 { 288 rettv->vval.v_number = (find_buffer(&argvars[0]) != NULL); 289 } 290 291 /// "buflisted(expr)" function 292 void f_buflisted(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 293 { 294 buf_T *buf; 295 296 buf = find_buffer(&argvars[0]); 297 rettv->vval.v_number = (buf != NULL && buf->b_p_bl); 298 } 299 300 /// "bufload(expr)" function 301 void f_bufload(typval_T *argvars, typval_T *unused, EvalFuncData fptr) 302 { 303 buf_T *buf = get_buf_arg(&argvars[0]); 304 305 if (buf != NULL) { 306 if (swap_exists_action != SEA_READONLY) { 307 swap_exists_action = SEA_NONE; 308 } 309 buf_ensure_loaded(buf); 310 } 311 } 312 313 /// "bufloaded(expr)" function 314 void f_bufloaded(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 315 { 316 buf_T *buf; 317 318 buf = find_buffer(&argvars[0]); 319 rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL); 320 } 321 322 /// "bufname(expr)" function 323 void f_bufname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 324 { 325 const buf_T *buf; 326 rettv->v_type = VAR_STRING; 327 rettv->vval.v_string = NULL; 328 if (argvars[0].v_type == VAR_UNKNOWN) { 329 buf = curbuf; 330 } else { 331 buf = tv_get_buf_from_arg(&argvars[0]); 332 } 333 if (buf != NULL && buf->b_fname != NULL) { 334 rettv->vval.v_string = xstrdup(buf->b_fname); 335 } 336 } 337 338 /// "bufnr(expr)" function 339 void f_bufnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 340 { 341 const buf_T *buf; 342 bool error = false; 343 344 rettv->vval.v_number = -1; 345 346 if (argvars[0].v_type == VAR_UNKNOWN) { 347 buf = curbuf; 348 } else { 349 // Don't use tv_get_buf_from_arg(); we continue if the buffer wasn't found 350 // and the second argument isn't zero, but we want to return early if the 351 // first argument isn't a string or number so only one error is shown. 352 if (!tv_check_str_or_nr(&argvars[0])) { 353 return; 354 } 355 emsg_off++; 356 buf = tv_get_buf(&argvars[0], false); 357 emsg_off--; 358 } 359 360 // If the buffer isn't found and the second argument is not zero create a 361 // new buffer. 362 const char *name; 363 if (buf == NULL 364 && argvars[1].v_type != VAR_UNKNOWN 365 && tv_get_number_chk(&argvars[1], &error) != 0 366 && !error 367 && (name = tv_get_string_chk(&argvars[0])) != NULL) { 368 buf = buflist_new((char *)name, NULL, 1, 0); 369 } 370 371 if (buf != NULL) { 372 rettv->vval.v_number = buf->b_fnum; 373 } 374 } 375 376 static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr) 377 { 378 const buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); 379 if (buf == NULL) { // no need to search if invalid arg or buffer not found 380 rettv->vval.v_number = -1; 381 return; 382 } 383 384 int winnr = 0; 385 int winid; 386 bool found_buf = false; 387 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 388 winnr += win_has_winnr(wp, curtab); 389 if (wp->w_buffer == buf && (!get_nr || win_has_winnr(wp, curtab))) { 390 found_buf = true; 391 winid = wp->handle; 392 break; 393 } 394 } 395 rettv->vval.v_number = (found_buf ? (get_nr ? winnr : winid) : -1); 396 } 397 398 /// "bufwinid(nr)" function 399 void f_bufwinid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 400 { 401 buf_win_common(argvars, rettv, false); 402 } 403 404 /// "bufwinnr(nr)" function 405 void f_bufwinnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 406 { 407 buf_win_common(argvars, rettv, true); 408 } 409 410 /// "deletebufline()" function 411 void f_deletebufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 412 { 413 const int did_emsg_before = did_emsg; 414 rettv->vval.v_number = 1; // FAIL by default 415 buf_T *const buf = tv_get_buf(&argvars[0], false); 416 if (buf == NULL) { 417 return; 418 } 419 420 linenr_T last; 421 const linenr_T first = tv_get_lnum_buf(&argvars[1], buf); 422 if (did_emsg > did_emsg_before) { 423 return; 424 } 425 if (argvars[2].v_type != VAR_UNKNOWN) { 426 last = tv_get_lnum_buf(&argvars[2], buf); 427 } else { 428 last = first; 429 } 430 431 if (buf->b_ml.ml_mfp == NULL || first < 1 432 || first > buf->b_ml.ml_line_count || last < first) { 433 return; 434 } 435 436 // After this don't use "return", goto "cleanup"! 437 const bool is_curbuf = buf == curbuf; 438 cob_T cob; 439 if (!is_curbuf) { 440 // set "curbuf" to "buf" and find a window for this buffer 441 change_other_buffer_prepare(&cob, buf); 442 } 443 444 if (last > curbuf->b_ml.ml_line_count) { 445 last = curbuf->b_ml.ml_line_count; 446 } 447 const int count = last - first + 1; 448 449 // When coming here from Insert mode, sync undo, so that this can be 450 // undone separately from what was previously inserted. 451 if (u_sync_once == 2) { 452 u_sync_once = 1; // notify that u_sync() was called 453 u_sync(true); 454 } 455 456 if (u_save(first - 1, last + 1) == FAIL) { 457 goto cleanup; 458 } 459 460 for (linenr_T lnum = first; lnum <= last; lnum++) { 461 ml_delete_flags(first, ML_DEL_MESSAGE); 462 } 463 464 FOR_ALL_TAB_WINDOWS(tp, wp) { 465 if (wp->w_buffer == buf) { 466 if (wp->w_cursor.lnum > last) { 467 wp->w_cursor.lnum -= (linenr_T)count; 468 } else if (wp->w_cursor.lnum > first) { 469 wp->w_cursor.lnum = first; 470 } 471 if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) { 472 wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count; 473 } 474 } 475 } 476 check_cursor_col(curwin); 477 deleted_lines_mark(first, count); 478 rettv->vval.v_number = 0; // OK 479 480 cleanup: 481 if (!is_curbuf) { 482 change_other_buffer_restore(&cob); 483 } 484 } 485 486 /// @return buffer options, variables and other attributes in a dictionary. 487 static dict_T *get_buffer_info(buf_T *buf) 488 { 489 dict_T *const dict = tv_dict_alloc(); 490 491 tv_dict_add_nr(dict, S_LEN("bufnr"), buf->b_fnum); 492 tv_dict_add_str(dict, S_LEN("name"), buf->b_ffname != NULL ? buf->b_ffname : ""); 493 tv_dict_add_nr(dict, S_LEN("lnum"), 494 buf == curbuf ? curwin->w_cursor.lnum : buflist_findlnum(buf)); 495 tv_dict_add_nr(dict, S_LEN("linecount"), buf->b_ml.ml_line_count); 496 tv_dict_add_nr(dict, S_LEN("loaded"), buf->b_ml.ml_mfp != NULL); 497 tv_dict_add_nr(dict, S_LEN("listed"), buf->b_p_bl); 498 tv_dict_add_nr(dict, S_LEN("changed"), bufIsChanged(buf)); 499 tv_dict_add_nr(dict, S_LEN("changedtick"), buf_get_changedtick(buf)); 500 tv_dict_add_nr(dict, S_LEN("hidden"), buf->b_ml.ml_mfp != NULL && buf->b_nwindows == 0); 501 tv_dict_add_nr(dict, S_LEN("command"), buf == cmdwin_buf); 502 503 // Get a reference to buffer variables 504 tv_dict_add_dict(dict, S_LEN("variables"), buf->b_vars); 505 506 // List of windows displaying this buffer 507 list_T *const windows = tv_list_alloc(kListLenMayKnow); 508 FOR_ALL_TAB_WINDOWS(tp, wp) { 509 if (wp->w_buffer == buf) { 510 tv_list_append_number(windows, (varnumber_T)wp->handle); 511 } 512 } 513 tv_dict_add_list(dict, S_LEN("windows"), windows); 514 515 if (buf_has_signs(buf)) { 516 // List of signs placed in this buffer 517 tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf)); 518 } 519 520 tv_dict_add_nr(dict, S_LEN("lastused"), buf->b_last_used); 521 522 return dict; 523 } 524 525 /// "getbufinfo()" function 526 void f_getbufinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 527 { 528 buf_T *argbuf = NULL; 529 bool filtered = false; 530 bool sel_buflisted = false; 531 bool sel_bufloaded = false; 532 bool sel_bufmodified = false; 533 534 tv_list_alloc_ret(rettv, kListLenMayKnow); 535 536 // List of all the buffers or selected buffers 537 if (argvars[0].v_type == VAR_DICT) { 538 dict_T *sel_d = argvars[0].vval.v_dict; 539 540 if (sel_d != NULL) { 541 dictitem_T *di; 542 543 filtered = true; 544 545 di = tv_dict_find(sel_d, S_LEN("buflisted")); 546 if (di != NULL && tv_get_number(&di->di_tv)) { 547 sel_buflisted = true; 548 } 549 550 di = tv_dict_find(sel_d, S_LEN("bufloaded")); 551 if (di != NULL && tv_get_number(&di->di_tv)) { 552 sel_bufloaded = true; 553 } 554 di = tv_dict_find(sel_d, S_LEN("bufmodified")); 555 if (di != NULL && tv_get_number(&di->di_tv)) { 556 sel_bufmodified = true; 557 } 558 } 559 } else if (argvars[0].v_type != VAR_UNKNOWN) { 560 // Information about one buffer. Argument specifies the buffer 561 argbuf = tv_get_buf_from_arg(&argvars[0]); 562 if (argbuf == NULL) { 563 return; 564 } 565 } 566 567 // Return information about all the buffers or a specified buffer 568 FOR_ALL_BUFFERS(buf) { 569 if (argbuf != NULL && argbuf != buf) { 570 continue; 571 } 572 if (filtered && ((sel_bufloaded && buf->b_ml.ml_mfp == NULL) 573 || (sel_buflisted && !buf->b_p_bl) 574 || (sel_bufmodified && !buf->b_changed))) { 575 continue; 576 } 577 578 dict_T *const d = get_buffer_info(buf); 579 tv_list_append_dict(rettv->vval.v_list, d); 580 if (argbuf != NULL) { 581 return; 582 } 583 } 584 } 585 586 /// Get line or list of lines from buffer "buf" into "rettv". 587 /// 588 /// @param retlist if true, then the lines are returned as a Vim List. 589 /// 590 /// @return range (from start to end) of lines in rettv from the specified 591 /// buffer. 592 static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, bool retlist, 593 typval_T *rettv) 594 { 595 rettv->v_type = (retlist ? VAR_LIST : VAR_STRING); 596 rettv->vval.v_string = NULL; 597 598 if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0 || end < start) { 599 if (retlist) { 600 tv_list_alloc_ret(rettv, 0); 601 } 602 return; 603 } 604 605 if (retlist) { 606 if (start < 1) { 607 start = 1; 608 } 609 if (end > buf->b_ml.ml_line_count) { 610 end = buf->b_ml.ml_line_count; 611 } 612 tv_list_alloc_ret(rettv, end - start + 1); 613 while (start <= end) { 614 tv_list_append_string(rettv->vval.v_list, 615 ml_get_buf(buf, start), (int)ml_get_buf_len(buf, start)); 616 start++; 617 } 618 } else { 619 rettv->v_type = VAR_STRING; 620 rettv->vval.v_string = 621 start >= 1 && start <= buf->b_ml.ml_line_count 622 ? xstrnsave(ml_get_buf(buf, start), (size_t)ml_get_buf_len(buf, start)) 623 : NULL; 624 } 625 } 626 627 /// @param retlist true: "getbufline()" function 628 /// false: "getbufoneline()" function 629 static void getbufline(typval_T *argvars, typval_T *rettv, bool retlist) 630 { 631 const int did_emsg_before = did_emsg; 632 buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); 633 const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf); 634 if (did_emsg > did_emsg_before) { 635 return; 636 } 637 const linenr_T end = (argvars[2].v_type == VAR_UNKNOWN 638 ? lnum 639 : tv_get_lnum_buf(&argvars[2], buf)); 640 641 get_buffer_lines(buf, lnum, end, retlist, rettv); 642 } 643 644 /// "getbufline()" function 645 void f_getbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 646 { 647 getbufline(argvars, rettv, true); 648 } 649 650 /// "getbufoneline()" function 651 void f_getbufoneline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 652 { 653 getbufline(argvars, rettv, false); 654 } 655 656 /// "getline(lnum, [end])" function 657 void f_getline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 658 { 659 linenr_T end; 660 bool retlist; 661 662 const linenr_T lnum = tv_get_lnum(argvars); 663 if (argvars[1].v_type == VAR_UNKNOWN) { 664 end = lnum; 665 retlist = false; 666 } else { 667 end = tv_get_lnum(&argvars[1]); 668 retlist = true; 669 } 670 671 get_buffer_lines(curbuf, lnum, end, retlist, rettv); 672 } 673 674 /// "setbufline()" function 675 void f_setbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 676 { 677 buf_set_append_line(argvars, rettv, false); 678 } 679 680 /// "setline()" function 681 void f_setline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 682 { 683 const int did_emsg_before = did_emsg; 684 linenr_T lnum = tv_get_lnum(&argvars[0]); 685 if (did_emsg == did_emsg_before) { 686 set_buffer_lines(curbuf, lnum, false, &argvars[1], rettv); 687 } 688 } 689 690 /// Make "buf" the current buffer. 691 /// 692 /// restore_buffer() MUST be called to undo. 693 /// No autocommands will be executed. Use aucmd_prepbuf() if there are any. 694 void switch_buffer(bufref_T *save_curbuf, buf_T *buf) 695 { 696 block_autocmds(); 697 set_bufref(save_curbuf, curbuf); 698 curbuf->b_nwindows--; 699 curbuf = buf; 700 curwin->w_buffer = buf; 701 curbuf->b_nwindows++; 702 } 703 704 /// Restore the current buffer after using switch_buffer(). 705 void restore_buffer(bufref_T *save_curbuf) 706 { 707 unblock_autocmds(); 708 // Check for valid buffer, just in case. 709 if (bufref_valid(save_curbuf)) { 710 curbuf->b_nwindows--; 711 curwin->w_buffer = save_curbuf->br_buf; 712 curbuf = save_curbuf->br_buf; 713 curbuf->b_nwindows++; 714 } 715 } 716 717 /// "prompt_setcallback({buffer}, {callback})" function 718 void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 719 { 720 Callback prompt_callback = CALLBACK_INIT; 721 722 if (check_secure()) { 723 return; 724 } 725 buf_T *buf = tv_get_buf(&argvars[0], false); 726 if (buf == NULL) { 727 return; 728 } 729 730 if (argvars[1].v_type != VAR_STRING || *argvars[1].vval.v_string != NUL) { 731 if (!callback_from_typval(&prompt_callback, &argvars[1])) { 732 return; 733 } 734 } 735 736 callback_free(&buf->b_prompt_callback); 737 buf->b_prompt_callback = prompt_callback; 738 } 739 740 /// "prompt_setinterrupt({buffer}, {callback})" function 741 void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 742 { 743 Callback interrupt_callback = CALLBACK_INIT; 744 745 if (check_secure()) { 746 return; 747 } 748 buf_T *buf = tv_get_buf(&argvars[0], false); 749 if (buf == NULL) { 750 return; 751 } 752 753 if (argvars[1].v_type != VAR_STRING || *argvars[1].vval.v_string != NUL) { 754 if (!callback_from_typval(&interrupt_callback, &argvars[1])) { 755 return; 756 } 757 } 758 759 callback_free(&buf->b_prompt_interrupt); 760 buf->b_prompt_interrupt = interrupt_callback; 761 } 762 763 /// "prompt_setprompt({buffer}, {text})" function 764 void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 765 { 766 if (check_secure()) { 767 return; 768 } 769 buf_T *buf = tv_get_buf(&argvars[0], false); 770 if (buf == NULL) { 771 return; 772 } 773 774 const char *new_prompt = tv_get_string(&argvars[1]); 775 int new_prompt_len = (int)strlen(new_prompt); 776 777 // Update the prompt-text and prompt-marks if a plugin calls prompt_setprompt() 778 // even while user is editing their input. 779 if (bt_prompt(buf) && buf->b_ml.ml_mfp != NULL) { 780 // In case the mark is set to a nonexistent line. 781 buf->b_prompt_start.mark.lnum = MAX(1, MIN(buf->b_prompt_start.mark.lnum, 782 buf->b_ml.ml_line_count)); 783 784 linenr_T prompt_lno = buf->b_prompt_start.mark.lnum; 785 char *old_prompt = buf_prompt_text(buf); 786 char *old_line = ml_get_buf(buf, prompt_lno); 787 colnr_T old_line_len = ml_get_buf_len(buf, prompt_lno); 788 789 int old_prompt_len = (int)strlen(old_prompt); 790 colnr_T cursor_col = curwin->w_cursor.col; 791 792 if (buf->b_prompt_start.mark.col < old_prompt_len 793 || buf->b_prompt_start.mark.col > old_line_len 794 || !strnequal(old_prompt, old_line + buf->b_prompt_start.mark.col - old_prompt_len, 795 (size_t)old_prompt_len)) { 796 // If for some odd reason the old prompt is missing, 797 // replace prompt line with new-prompt (discards user-input). 798 ml_replace_buf(buf, prompt_lno, (char *)new_prompt, true, false); 799 extmark_splice_cols(buf, prompt_lno - 1, 0, old_line_len, new_prompt_len, kExtmarkNoUndo); 800 cursor_col = new_prompt_len; 801 } else { 802 // Replace prev-prompt + user-input with new-prompt + user-input 803 char *new_line = concat_str(new_prompt, old_line + buf->b_prompt_start.mark.col); 804 if (ml_replace_buf(buf, prompt_lno, new_line, false, false) != OK) { 805 xfree(new_line); 806 } 807 extmark_splice_cols(buf, prompt_lno - 1, 0, buf->b_prompt_start.mark.col, new_prompt_len, 808 kExtmarkNoUndo); 809 cursor_col += new_prompt_len - buf->b_prompt_start.mark.col; 810 } 811 812 if (curwin->w_buffer == buf && curwin->w_cursor.lnum == prompt_lno) { 813 curwin->w_cursor.col = cursor_col; 814 check_cursor_col(curwin); 815 } 816 changed_lines(buf, prompt_lno, 0, prompt_lno + 1, 0, true); 817 // Undo history contains the old prompt. 818 u_clearallandblockfree(buf); 819 } 820 821 // Clear old prompt text and replace with the new one 822 xfree(buf->b_prompt_text); 823 buf->b_prompt_text = xstrdup(new_prompt); 824 buf->b_prompt_start.mark.col = (colnr_T)new_prompt_len; 825 }