buffer.c (46605B)
1 // Some of this code was adapted from 'if_py_both.h' from the original 2 // vim source 3 4 #include <lauxlib.h> 5 #include <stdbool.h> 6 #include <stddef.h> 7 #include <stdint.h> 8 #include <string.h> 9 10 #include "klib/kvec.h" 11 #include "lua.h" 12 #include "nvim/api/buffer.h" 13 #include "nvim/api/keysets_defs.h" 14 #include "nvim/api/private/defs.h" 15 #include "nvim/api/private/dispatch.h" 16 #include "nvim/api/private/helpers.h" 17 #include "nvim/api/private/validate.h" 18 #include "nvim/ascii_defs.h" 19 #include "nvim/autocmd.h" 20 #include "nvim/autocmd_defs.h" 21 #include "nvim/buffer.h" 22 #include "nvim/buffer_defs.h" 23 #include "nvim/buffer_updates.h" 24 #include "nvim/change.h" 25 #include "nvim/cursor.h" 26 #include "nvim/ex_cmds.h" 27 #include "nvim/extmark.h" 28 #include "nvim/extmark_defs.h" 29 #include "nvim/globals.h" 30 #include "nvim/lua/executor.h" 31 #include "nvim/mapping.h" 32 #include "nvim/mark.h" 33 #include "nvim/mark_defs.h" 34 #include "nvim/marktree_defs.h" 35 #include "nvim/memline.h" 36 #include "nvim/memline_defs.h" 37 #include "nvim/memory.h" 38 #include "nvim/memory_defs.h" 39 #include "nvim/move.h" 40 #include "nvim/ops.h" 41 #include "nvim/option_vars.h" 42 #include "nvim/pos_defs.h" 43 #include "nvim/state_defs.h" 44 #include "nvim/types_defs.h" 45 #include "nvim/undo.h" 46 #include "nvim/undo_defs.h" 47 #include "nvim/vim_defs.h" 48 49 #include "api/buffer.c.generated.h" 50 51 /// Ensures that a buffer is loaded. 52 buf_T *api_buf_ensure_loaded(Buffer buffer, Error *err) 53 { 54 buf_T *buf = find_buffer_by_handle(buffer, err); 55 if (!buf) { 56 return NULL; 57 } 58 59 if (buf->b_ml.ml_mfp == NULL && !buf_ensure_loaded(buf)) { 60 api_set_error(err, kErrorTypeException, "Failed to load buffer"); 61 return NULL; 62 } 63 64 return buf; 65 } 66 67 /// @brief <pre>help 68 /// For more information on buffers, see |buffers|. 69 /// 70 /// Unloaded Buffers: ~ 71 /// 72 /// Buffers may be unloaded by the |:bunload| command or the buffer's 73 /// 'bufhidden' option. When a buffer is unloaded its file contents are freed 74 /// from memory and vim cannot operate on the buffer lines until it is reloaded 75 /// (usually by opening the buffer again in a new window). API methods such as 76 /// |nvim_buf_get_lines()| and |nvim_buf_line_count()| will be affected. 77 /// 78 /// You can use |nvim_buf_is_loaded()| or |nvim_buf_line_count()| to check 79 /// whether a buffer is loaded. 80 /// </pre> 81 82 /// Returns the number of lines in the given buffer. 83 /// 84 /// @param buffer Buffer id, or 0 for current buffer 85 /// @param[out] err Error details, if any 86 /// @return Line count, or 0 for unloaded buffer. |api-buffer| 87 Integer nvim_buf_line_count(Buffer buffer, Error *err) 88 FUNC_API_SINCE(1) 89 { 90 buf_T *buf = find_buffer_by_handle(buffer, err); 91 92 if (!buf) { 93 return 0; 94 } 95 96 // return sentinel value if the buffer isn't loaded 97 if (buf->b_ml.ml_mfp == NULL) { 98 return 0; 99 } 100 101 return buf->b_ml.ml_line_count; 102 } 103 104 /// Activates |api-buffer-updates| events on a channel, or as Lua callbacks. 105 /// 106 /// Example (Lua): capture buffer updates in a global `events` variable 107 /// (use "vim.print(events)" to see its contents): 108 /// 109 /// ```lua 110 /// events = {} 111 /// vim.api.nvim_buf_attach(0, false, { 112 /// on_lines = function(...) 113 /// table.insert(events, {...}) 114 /// end, 115 /// }) 116 /// ``` 117 /// 118 /// @see |nvim_buf_detach()| 119 /// @see |api-buffer-updates-lua| 120 /// 121 /// @param channel_id 122 /// @param buffer Buffer id, or 0 for current buffer 123 /// @param send_buffer True if the initial notification should contain the 124 /// whole buffer: first notification will be `nvim_buf_lines_event`. 125 /// Else the first notification will be `nvim_buf_changedtick_event`. 126 /// Not for Lua callbacks. 127 /// @param opts Optional parameters. 128 /// - on_lines: Called on linewise changes. Not called on buffer reload (`:checktime`, 129 /// `:edit`, …), see `on_reload:`. Return a [lua-truthy] value to detach. Args: 130 /// - the string "lines" 131 /// - buffer id 132 /// - b:changedtick 133 /// - first line that changed (zero-indexed) 134 /// - last line that was changed 135 /// - last line in the updated range 136 /// - byte count of previous contents 137 /// - deleted_codepoints (if `utf_sizes` is true) 138 /// - deleted_codeunits (if `utf_sizes` is true) 139 /// - on_bytes: Called on granular changes (compared to on_lines). Not called on buffer 140 /// reload (`:checktime`, `:edit`, …), see `on_reload:`. Return a [lua-truthy] value 141 /// to detach. Args: 142 /// - the string "bytes" 143 /// - buffer id 144 /// - b:changedtick 145 /// - start row of the changed text (zero-indexed) 146 /// - start column of the changed text 147 /// - byte offset of the changed text (from the start of 148 /// the buffer) 149 /// - old end row of the changed text (offset from start row) 150 /// - old end column of the changed text 151 /// (if old end row = 0, offset from start column) 152 /// - old end byte length of the changed text 153 /// - new end row of the changed text (offset from start row) 154 /// - new end column of the changed text 155 /// (if new end row = 0, offset from start column) 156 /// - new end byte length of the changed text 157 /// - on_changedtick: Called on [changetick] increment without text change. Args: 158 /// - the string "changedtick" 159 /// - buffer id 160 /// - b:changedtick 161 /// - on_detach: Called on detach. Args: 162 /// - the string "detach" 163 /// - buffer id 164 /// - on_reload: Called on whole-buffer load (`:checktime`, `:edit`, …). Clients should 165 /// typically re-fetch the entire buffer contents. Args: 166 /// - the string "reload" 167 /// - buffer id 168 /// - utf_sizes: include UTF-32 and UTF-16 size of the replaced 169 /// region, as args to `on_lines`. 170 /// - preview: also attach to command preview (i.e. 'inccommand') 171 /// events. 172 /// @param[out] err Error details, if any 173 /// @return False if attach failed (invalid parameter, or buffer isn't loaded); 174 /// otherwise True. 175 Boolean nvim_buf_attach(uint64_t channel_id, Buffer buffer, Boolean send_buffer, 176 Dict(buf_attach) *opts, Error *err) 177 FUNC_API_SINCE(4) 178 { 179 buf_T *buf = find_buffer_by_handle(buffer, err); 180 181 if (!buf) { 182 return false; 183 } 184 185 BufUpdateCallbacks cb = BUF_UPDATE_CALLBACKS_INIT; 186 187 if (channel_id == LUA_INTERNAL_CALL) { 188 if (HAS_KEY(opts, buf_attach, on_lines)) { 189 cb.on_lines = opts->on_lines; 190 opts->on_lines = LUA_NOREF; 191 } 192 193 if (HAS_KEY(opts, buf_attach, on_bytes)) { 194 cb.on_bytes = opts->on_bytes; 195 opts->on_bytes = LUA_NOREF; 196 } 197 198 if (HAS_KEY(opts, buf_attach, on_changedtick)) { 199 cb.on_changedtick = opts->on_changedtick; 200 opts->on_changedtick = LUA_NOREF; 201 } 202 203 if (HAS_KEY(opts, buf_attach, on_detach)) { 204 cb.on_detach = opts->on_detach; 205 opts->on_detach = LUA_NOREF; 206 } 207 208 if (HAS_KEY(opts, buf_attach, on_reload)) { 209 cb.on_reload = opts->on_reload; 210 opts->on_reload = LUA_NOREF; 211 } 212 213 cb.utf_sizes = opts->utf_sizes; 214 215 cb.preview = opts->preview; 216 } 217 218 return buf_updates_register(buf, channel_id, cb, send_buffer); 219 } 220 221 /// Deactivates buffer-update events on the channel. 222 /// 223 /// @see |nvim_buf_attach()| 224 /// @see |api-lua-detach| for detaching Lua callbacks 225 /// 226 /// @param channel_id 227 /// @param buffer Buffer id, or 0 for current buffer 228 /// @param[out] err Error details, if any 229 /// @return False if detach failed (because the buffer isn't loaded); 230 /// otherwise True. 231 Boolean nvim_buf_detach(uint64_t channel_id, Buffer buffer, Error *err) 232 FUNC_API_SINCE(4) FUNC_API_REMOTE_ONLY 233 { 234 buf_T *buf = find_buffer_by_handle(buffer, err); 235 236 if (!buf) { 237 return false; 238 } 239 240 buf_updates_unregister(buf, channel_id); 241 return true; 242 } 243 244 /// Gets a line-range from the buffer. 245 /// 246 /// Indexing is zero-based, end-exclusive. Negative indices are interpreted 247 /// as length+1+index: -1 refers to the index past the end. So to get the 248 /// last element use start=-2 and end=-1. 249 /// 250 /// Out-of-bounds indices are clamped to the nearest valid value, unless 251 /// `strict_indexing` is set. 252 /// 253 /// @see |nvim_buf_get_text()| 254 /// 255 /// @param channel_id 256 /// @param buffer Buffer id, or 0 for current buffer 257 /// @param start First line index 258 /// @param end Last line index, exclusive 259 /// @param strict_indexing Whether out-of-bounds should be an error. 260 /// @param[out] err Error details, if any 261 /// @return Array of lines, or empty array for unloaded buffer. 262 ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, 263 Buffer buffer, 264 Integer start, 265 Integer end, 266 Boolean strict_indexing, 267 Arena *arena, 268 lua_State *lstate, 269 Error *err) 270 FUNC_API_SINCE(1) 271 { 272 Array rv = ARRAY_DICT_INIT; 273 buf_T *buf = find_buffer_by_handle(buffer, err); 274 275 if (!buf) { 276 return rv; 277 } 278 279 // return sentinel value if the buffer isn't loaded 280 if (buf->b_ml.ml_mfp == NULL) { 281 return rv; 282 } 283 284 bool oob = false; 285 start = normalize_index(buf, start, true, &oob); 286 end = normalize_index(buf, end, true, &oob); 287 288 VALIDATE((!strict_indexing || !oob), "%s", "Index out of bounds", { 289 return rv; 290 }); 291 292 if (start >= end) { 293 // Return 0-length array 294 return rv; 295 } 296 297 size_t size = (size_t)(end - start); 298 299 init_line_array(lstate, &rv, size, arena); 300 301 buf_collect_lines(buf, size, (linenr_T)start, 0, (channel_id != VIML_INTERNAL_CALL), &rv, 302 lstate, arena); 303 304 return rv; 305 } 306 307 /// Sets (replaces) a line-range in the buffer. 308 /// 309 /// Indexing is zero-based, end-exclusive. Negative indices are interpreted 310 /// as length+1+index: -1 refers to the index past the end. So to change 311 /// or delete the last line use start=-2 and end=-1. 312 /// 313 /// To insert lines at a given index, set `start` and `end` to the same index. 314 /// To delete a range of lines, set `replacement` to an empty array. 315 /// 316 /// Out-of-bounds indices are clamped to the nearest valid value, unless 317 /// `strict_indexing` is set. 318 /// 319 /// @see |nvim_buf_set_text()| 320 /// 321 /// @param channel_id 322 /// @param buffer Buffer id, or 0 for current buffer 323 /// @param start First line index 324 /// @param end Last line index, exclusive 325 /// @param strict_indexing Whether out-of-bounds should be an error. 326 /// @param replacement Array of lines to use as replacement 327 /// @param[out] err Error details, if any 328 void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integer end, 329 Boolean strict_indexing, ArrayOf(String) replacement, Arena *arena, 330 Error *err) 331 FUNC_API_SINCE(1) 332 FUNC_API_TEXTLOCK_ALLOW_CMDWIN 333 { 334 buf_T *buf = api_buf_ensure_loaded(buffer, err); 335 336 if (!buf) { 337 return; 338 } 339 340 bool oob = false; 341 start = normalize_index(buf, start, true, &oob); 342 end = normalize_index(buf, end, true, &oob); 343 344 VALIDATE((!strict_indexing || !oob), "%s", "Index out of bounds", { 345 return; 346 }); 347 VALIDATE((start <= end), "%s", "'start' is higher than 'end'", { 348 return; 349 }); 350 351 bool disallow_nl = (channel_id != VIML_INTERNAL_CALL); 352 if (!check_string_array(replacement, "replacement string", disallow_nl, err)) { 353 return; 354 } 355 356 size_t new_len = replacement.size; 357 size_t old_len = (size_t)(end - start); 358 ptrdiff_t extra = 0; // lines added to text, can be negative 359 char **lines = (new_len != 0) ? arena_alloc(arena, new_len * sizeof(char *), true) : NULL; 360 361 for (size_t i = 0; i < new_len; i++) { 362 const String l = replacement.items[i].data.string; 363 364 // Fill lines[i] with l's contents. Convert NULs to newlines as required by 365 // NL-used-for-NUL. 366 lines[i] = arena_memdupz(arena, l.data, l.size); 367 memchrsub(lines[i], NUL, NL, l.size); 368 } 369 370 TRY_WRAP(err, { 371 if (!MODIFIABLE(buf)) { 372 api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'"); 373 goto end; 374 } 375 376 if (u_save_buf(buf, (linenr_T)(start - 1), (linenr_T)end) == FAIL) { 377 api_set_error(err, kErrorTypeException, "Failed to save undo information"); 378 goto end; 379 } 380 381 bcount_t deleted_bytes = get_region_bytecount(buf, (linenr_T)start, (linenr_T)end, 0, 0); 382 383 // If the size of the range is reducing (ie, new_len < old_len) we 384 // need to delete some old_len. We do this at the start, by 385 // repeatedly deleting line "start". 386 size_t to_delete = (new_len < old_len) ? old_len - new_len : 0; 387 for (size_t i = 0; i < to_delete; i++) { 388 if (ml_delete_buf(buf, (linenr_T)start, false) == FAIL) { 389 api_set_error(err, kErrorTypeException, "Failed to delete line"); 390 goto end; 391 } 392 } 393 394 if (to_delete > 0) { 395 extra -= (ptrdiff_t)to_delete; 396 } 397 398 // For as long as possible, replace the existing old_len with the 399 // new old_len. This is a more efficient operation, as it requires 400 // less memory allocation and freeing. 401 size_t to_replace = old_len < new_len ? old_len : new_len; 402 bcount_t inserted_bytes = 0; 403 for (size_t i = 0; i < to_replace; i++) { 404 int64_t lnum = start + (int64_t)i; 405 406 VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", { 407 goto end; 408 }); 409 410 if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false, true) == FAIL) { 411 api_set_error(err, kErrorTypeException, "Failed to replace line"); 412 goto end; 413 } 414 415 inserted_bytes += (bcount_t)strlen(lines[i]) + 1; 416 } 417 418 // Now we may need to insert the remaining new old_len 419 for (size_t i = to_replace; i < new_len; i++) { 420 int64_t lnum = start + (int64_t)i - 1; 421 422 VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", { 423 goto end; 424 }); 425 426 if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) { 427 api_set_error(err, kErrorTypeException, "Failed to insert line"); 428 goto end; 429 } 430 431 inserted_bytes += (bcount_t)strlen(lines[i]) + 1; 432 433 extra++; 434 } 435 436 // Adjust marks. Invalidate any which lie in the 437 // changed range, and move any in the remainder of the buffer. 438 linenr_T adjust = end > start ? MAXLNUM : 0; 439 mark_adjust_buf(buf, (linenr_T)start, (linenr_T)(end - 1), adjust, (linenr_T)extra, 440 true, kMarkAdjustApi, kExtmarkNOOP); 441 442 extmark_splice(buf, (int)start - 1, 0, (int)(end - start), 0, 443 deleted_bytes, (int)new_len, 0, inserted_bytes, 444 kExtmarkUndo); 445 446 changed_lines(buf, (linenr_T)start, 0, (linenr_T)end, (linenr_T)extra, true); 447 448 FOR_ALL_TAB_WINDOWS(tp, win) { 449 if (win->w_buffer == buf) { 450 fix_cursor(win, (linenr_T)start, (linenr_T)end, (linenr_T)extra); 451 } 452 } 453 end:; 454 }); 455 } 456 457 /// Sets (replaces) a range in the buffer 458 /// 459 /// This is recommended over |nvim_buf_set_lines()| when only modifying parts of 460 /// a line, as extmarks will be preserved on non-modified parts of the touched 461 /// lines. 462 /// 463 /// Indexing is zero-based. Row indices are end-inclusive, and column indices 464 /// are end-exclusive. 465 /// 466 /// To insert text at a given `(row, column)` location, use `start_row = end_row 467 /// = row` and `start_col = end_col = col`. To delete the text in a range, use 468 /// `replacement = {}`. 469 /// 470 /// @note Prefer |nvim_buf_set_lines()| (for performance) to add or delete entire lines. 471 /// @note Prefer |nvim_paste()| or |nvim_put()| to insert (instead of replace) text at cursor. 472 /// 473 /// @param channel_id 474 /// @param buffer Buffer id, or 0 for current buffer 475 /// @param start_row First line index 476 /// @param start_col Starting column (byte offset) on first line 477 /// @param end_row Last line index, inclusive 478 /// @param end_col Ending column (byte offset) on last line, exclusive 479 /// @param replacement Array of lines to use as replacement 480 /// @param[out] err Error details, if any 481 void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, Integer start_col, 482 Integer end_row, Integer end_col, ArrayOf(String) replacement, Arena *arena, 483 Error *err) 484 FUNC_API_SINCE(7) 485 FUNC_API_TEXTLOCK_ALLOW_CMDWIN 486 { 487 MAXSIZE_TEMP_ARRAY(scratch, 1); 488 if (replacement.size == 0) { 489 ADD_C(scratch, STATIC_CSTR_AS_OBJ("")); 490 replacement = scratch; 491 } 492 493 buf_T *buf = api_buf_ensure_loaded(buffer, err); 494 if (!buf) { 495 return; 496 } 497 498 bool oob = false; 499 500 // check range is ordered and everything! 501 // start_row, end_row within buffer len (except add text past the end?) 502 start_row = normalize_index(buf, start_row, false, &oob); 503 VALIDATE_RANGE((!oob), "start_row", { 504 return; 505 }); 506 507 end_row = normalize_index(buf, end_row, false, &oob); 508 VALIDATE_RANGE((!oob), "end_row", { 509 return; 510 }); 511 512 // Another call to ml_get_buf() may free the lines, so we make copies 513 char *str_at_start = ml_get_buf(buf, (linenr_T)start_row); 514 colnr_T len_at_start = ml_get_buf_len(buf, (linenr_T)start_row); 515 str_at_start = arena_memdupz(arena, str_at_start, (size_t)len_at_start); 516 start_col = start_col < 0 ? len_at_start + start_col + 1 : start_col; 517 VALIDATE_RANGE((start_col >= 0 && start_col <= len_at_start), "start_col", { 518 return; 519 }); 520 521 char *str_at_end = ml_get_buf(buf, (linenr_T)end_row); 522 colnr_T len_at_end = ml_get_buf_len(buf, (linenr_T)end_row); 523 str_at_end = arena_memdupz(arena, str_at_end, (size_t)len_at_end); 524 end_col = end_col < 0 ? len_at_end + end_col + 1 : end_col; 525 VALIDATE_RANGE((end_col >= 0 && end_col <= len_at_end), "end_col", { 526 return; 527 }); 528 529 VALIDATE((start_row <= end_row && !(end_row == start_row && start_col > end_col)), 530 "%s", "'start' is higher than 'end'", { 531 return; 532 }); 533 534 bool disallow_nl = (channel_id != VIML_INTERNAL_CALL); 535 if (!check_string_array(replacement, "replacement string", disallow_nl, err)) { 536 return; 537 } 538 539 size_t new_len = replacement.size; 540 541 bcount_t new_byte = 0; 542 bcount_t old_byte = 0; 543 544 // calculate byte size of old region before it gets modified/deleted 545 if (start_row == end_row) { 546 old_byte = (bcount_t)end_col - start_col; 547 } else { 548 old_byte += len_at_start - start_col; 549 for (int64_t i = 1; i < end_row - start_row; i++) { 550 int64_t lnum = start_row + i; 551 old_byte += ml_get_buf_len(buf, (linenr_T)lnum) + 1; 552 } 553 old_byte += (bcount_t)end_col + 1; 554 } 555 556 String first_item = replacement.items[0].data.string; 557 String last_item = replacement.items[replacement.size - 1].data.string; 558 559 size_t firstlen = (size_t)start_col + first_item.size; 560 size_t last_part_len = (size_t)len_at_end - (size_t)end_col; 561 if (replacement.size == 1) { 562 firstlen += last_part_len; 563 } 564 char *first = arena_allocz(arena, firstlen); 565 char *last = NULL; 566 memcpy(first, str_at_start, (size_t)start_col); 567 memcpy(first + start_col, first_item.data, first_item.size); 568 memchrsub(first + start_col, NUL, NL, first_item.size); 569 if (replacement.size == 1) { 570 memcpy(first + start_col + first_item.size, str_at_end + end_col, last_part_len); 571 } else { 572 last = arena_allocz(arena, last_item.size + last_part_len); 573 memcpy(last, last_item.data, last_item.size); 574 memchrsub(last, NUL, NL, last_item.size); 575 memcpy(last + last_item.size, str_at_end + end_col, last_part_len); 576 } 577 578 char **lines = arena_alloc(arena, new_len * sizeof(char *), true); 579 lines[0] = first; 580 new_byte += (bcount_t)(first_item.size); 581 for (size_t i = 1; i < new_len - 1; i++) { 582 const String l = replacement.items[i].data.string; 583 584 // Fill lines[i] with l's contents. Convert NULs to newlines as required by 585 // NL-used-for-NUL. 586 lines[i] = arena_memdupz(arena, l.data, l.size); 587 memchrsub(lines[i], NUL, NL, l.size); 588 new_byte += (bcount_t)(l.size) + 1; 589 } 590 if (replacement.size > 1) { 591 lines[replacement.size - 1] = last; 592 new_byte += (bcount_t)(last_item.size) + 1; 593 } 594 595 TRY_WRAP(err, { 596 if (!MODIFIABLE(buf)) { 597 api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'"); 598 goto end; 599 } 600 601 // Small note about undo states: unlike set_lines, we want to save the 602 // undo state of one past the end_row, since end_row is inclusive. 603 if (u_save_buf(buf, (linenr_T)start_row - 1, (linenr_T)end_row + 1) == FAIL) { 604 api_set_error(err, kErrorTypeException, "Failed to save undo information"); 605 goto end; 606 } 607 608 ptrdiff_t extra = 0; // lines added to text, can be negative 609 size_t old_len = (size_t)(end_row - start_row + 1); 610 611 // If the size of the range is reducing (ie, new_len < old_len) we 612 // need to delete some old_len. We do this at the start, by 613 // repeatedly deleting line "start". 614 size_t to_delete = (new_len < old_len) ? old_len - new_len : 0; 615 for (size_t i = 0; i < to_delete; i++) { 616 if (ml_delete_buf(buf, (linenr_T)start_row, false) == FAIL) { 617 api_set_error(err, kErrorTypeException, "Failed to delete line"); 618 goto end; 619 } 620 } 621 622 if (to_delete > 0) { 623 extra -= (ptrdiff_t)to_delete; 624 } 625 626 // For as long as possible, replace the existing old_len with the 627 // new old_len. This is a more efficient operation, as it requires 628 // less memory allocation and freeing. 629 size_t to_replace = old_len < new_len ? old_len : new_len; 630 for (size_t i = 0; i < to_replace; i++) { 631 int64_t lnum = start_row + (int64_t)i; 632 633 VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", { 634 goto end; 635 }); 636 637 if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false, true) == FAIL) { 638 api_set_error(err, kErrorTypeException, "Failed to replace line"); 639 goto end; 640 } 641 } 642 643 // Now we may need to insert the remaining new old_len 644 for (size_t i = to_replace; i < new_len; i++) { 645 int64_t lnum = start_row + (int64_t)i - 1; 646 647 VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", { 648 goto end; 649 }); 650 651 if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) { 652 api_set_error(err, kErrorTypeException, "Failed to insert line"); 653 goto end; 654 } 655 656 extra++; 657 } 658 659 colnr_T col_extent = (colnr_T)(end_col 660 - ((end_row == start_row) ? start_col : 0)); 661 662 // Adjust marks. Invalidate any which lie in the 663 // changed range, and move any in the remainder of the buffer. 664 // Do not adjust any cursors. need to use column-aware logic (below) 665 linenr_T adjust = end_row >= start_row ? MAXLNUM : 0; 666 mark_adjust_buf(buf, (linenr_T)start_row, (linenr_T)end_row - 1, adjust, (linenr_T)extra, 667 true, kMarkAdjustApi, kExtmarkNOOP); 668 669 extmark_splice(buf, (int)start_row - 1, (colnr_T)start_col, 670 (int)(end_row - start_row), col_extent, old_byte, 671 (int)new_len - 1, (colnr_T)last_item.size, new_byte, 672 kExtmarkUndo); 673 674 changed_lines(buf, (linenr_T)start_row, 0, (linenr_T)end_row + 1, (linenr_T)extra, true); 675 676 FOR_ALL_TAB_WINDOWS(tp, win) { 677 if (win->w_buffer == buf) { 678 if (win->w_cursor.lnum >= start_row && win->w_cursor.lnum <= end_row) { 679 fix_cursor_cols(win, (linenr_T)start_row, (colnr_T)start_col, (linenr_T)end_row, 680 (colnr_T)end_col, (linenr_T)new_len, (colnr_T)last_item.size); 681 } else { 682 fix_cursor(win, (linenr_T)start_row, (linenr_T)end_row, (linenr_T)extra); 683 } 684 } 685 } 686 end:; 687 }); 688 } 689 690 /// Gets a range from the buffer (may be partial lines, unlike |nvim_buf_get_lines()|). 691 /// 692 /// Indexing is zero-based. Row indices are end-inclusive, and column indices 693 /// are end-exclusive. 694 /// 695 /// Prefer |nvim_buf_get_lines()| when retrieving entire lines. 696 /// 697 /// @param channel_id 698 /// @param buffer Buffer id, or 0 for current buffer 699 /// @param start_row First line index 700 /// @param start_col Starting column (byte offset) on first line 701 /// @param end_row Last line index, inclusive 702 /// @param end_col Ending column (byte offset) on last line, exclusive 703 /// @param opts Optional parameters. Currently unused. 704 /// @param[out] err Error details, if any 705 /// @return Array of lines, or empty array for unloaded buffer. 706 ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer, 707 Integer start_row, Integer start_col, 708 Integer end_row, Integer end_col, 709 Dict(empty) *opts, 710 Arena *arena, lua_State *lstate, Error *err) 711 FUNC_API_SINCE(9) 712 { 713 Array rv = ARRAY_DICT_INIT; 714 715 buf_T *buf = find_buffer_by_handle(buffer, err); 716 717 if (!buf) { 718 return rv; 719 } 720 721 // return sentinel value if the buffer isn't loaded 722 if (buf->b_ml.ml_mfp == NULL) { 723 return rv; 724 } 725 726 bool oob = false; 727 start_row = normalize_index(buf, start_row, false, &oob); 728 end_row = normalize_index(buf, end_row, false, &oob); 729 730 VALIDATE((!oob), "%s", "Index out of bounds", { 731 return rv; 732 }); 733 734 // nvim_buf_get_lines doesn't care if the start row is greater than the end 735 // row (it will just return an empty array), but nvim_buf_get_text does in 736 // order to maintain symmetry with nvim_buf_set_text. 737 VALIDATE((start_row <= end_row), "%s", "'start' is higher than 'end'", { 738 return rv; 739 }); 740 741 bool replace_nl = (channel_id != VIML_INTERNAL_CALL); 742 743 size_t size = (size_t)(end_row - start_row) + 1; 744 745 init_line_array(lstate, &rv, size, arena); 746 747 if (start_row == end_row) { 748 String line = buf_get_text(buf, start_row, start_col, end_col, err); 749 if (ERROR_SET(err)) { 750 goto end; 751 } 752 push_linestr(lstate, &rv, line.data, line.size, 0, replace_nl, arena); 753 return rv; 754 } 755 756 String str = buf_get_text(buf, start_row, start_col, MAXCOL - 1, err); 757 if (ERROR_SET(err)) { 758 goto end; 759 } 760 761 push_linestr(lstate, &rv, str.data, str.size, 0, replace_nl, arena); 762 763 if (size > 2) { 764 buf_collect_lines(buf, size - 2, (linenr_T)start_row + 1, 1, replace_nl, &rv, lstate, arena); 765 } 766 767 str = buf_get_text(buf, end_row, 0, end_col, err); 768 if (ERROR_SET(err)) { 769 goto end; 770 } 771 772 push_linestr(lstate, &rv, str.data, str.size, (int)(size - 1), replace_nl, arena); 773 774 end: 775 if (ERROR_SET(err)) { 776 return (Array)ARRAY_DICT_INIT; 777 } 778 779 return rv; 780 } 781 782 /// Returns the byte offset of a line (0-indexed). |api-indexing| 783 /// 784 /// Line 1 (index=0) has offset 0. UTF-8 bytes are counted. EOL is one byte. 785 /// 'fileformat' and 'fileencoding' are ignored. The line index just after the 786 /// last line gives the total byte-count of the buffer. A final EOL byte is 787 /// counted if it would be written, see 'eol'. 788 /// 789 /// Unlike |line2byte()|, throws error for out-of-bounds indexing. 790 /// Returns -1 for unloaded buffer. 791 /// 792 /// @param buffer Buffer id, or 0 for current buffer 793 /// @param index Line index 794 /// @param[out] err Error details, if any 795 /// @return Integer byte offset, or -1 for unloaded buffer. 796 Integer nvim_buf_get_offset(Buffer buffer, Integer index, Error *err) 797 FUNC_API_SINCE(5) 798 { 799 buf_T *buf = find_buffer_by_handle(buffer, err); 800 if (!buf) { 801 return 0; 802 } 803 804 // return sentinel value if the buffer isn't loaded 805 if (buf->b_ml.ml_mfp == NULL) { 806 return -1; 807 } 808 809 VALIDATE((index >= 0 && index <= buf->b_ml.ml_line_count), "%s", "Index out of bounds", { 810 return 0; 811 }); 812 813 return ml_find_line_or_offset(buf, (int)index + 1, NULL, true); 814 } 815 816 /// Gets a buffer-scoped (b:) variable. 817 /// 818 /// @param buffer Buffer id, or 0 for current buffer 819 /// @param name Variable name 820 /// @param[out] err Error details, if any 821 /// @return Variable value 822 Object nvim_buf_get_var(Buffer buffer, String name, Arena *arena, Error *err) 823 FUNC_API_SINCE(1) 824 { 825 buf_T *buf = find_buffer_by_handle(buffer, err); 826 827 if (!buf) { 828 return (Object)OBJECT_INIT; 829 } 830 831 return dict_get_value(buf->b_vars, name, arena, err); 832 } 833 834 /// Gets a changed tick of a buffer 835 /// 836 /// @param[in] buffer Buffer id, or 0 for current buffer 837 /// @param[out] err Error details, if any 838 /// 839 /// @return `b:changedtick` value. 840 Integer nvim_buf_get_changedtick(Buffer buffer, Error *err) 841 FUNC_API_SINCE(2) 842 { 843 const buf_T *const buf = find_buffer_by_handle(buffer, err); 844 845 if (!buf) { 846 return -1; 847 } 848 849 return buf_get_changedtick(buf); 850 } 851 852 /// Gets a list of buffer-local |mapping| definitions. 853 /// 854 /// @param buffer Buffer id, or 0 for current buffer 855 /// @param mode Mode short-name ("n", "i", "v", ...) 856 /// @param[out] err Error details, if any 857 /// @returns Array of |maparg()|-like dictionaries describing mappings. 858 /// The "buffer" key holds the associated buffer id. 859 ArrayOf(DictAs(get_keymap)) nvim_buf_get_keymap(Buffer buffer, String mode, Arena *arena, 860 Error *err) 861 FUNC_API_SINCE(3) 862 { 863 buf_T *buf = find_buffer_by_handle(buffer, err); 864 865 if (!buf) { 866 return (Array)ARRAY_DICT_INIT; 867 } 868 869 return keymap_array(mode, buf, arena); 870 } 871 872 /// Sets a buffer-local |mapping| for the given mode. 873 /// 874 /// @see |nvim_set_keymap()| 875 /// 876 /// @param buffer Buffer id, or 0 for current buffer 877 void nvim_buf_set_keymap(uint64_t channel_id, Buffer buffer, String mode, String lhs, String rhs, 878 Dict(keymap) *opts, Error *err) 879 FUNC_API_SINCE(6) 880 { 881 modify_keymap(channel_id, buffer, false, mode, lhs, rhs, opts, err); 882 } 883 884 /// Unmaps a buffer-local |mapping| for the given mode. 885 /// 886 /// @see |nvim_del_keymap()| 887 /// 888 /// @param buffer Buffer id, or 0 for current buffer 889 void nvim_buf_del_keymap(uint64_t channel_id, Buffer buffer, String mode, String lhs, Error *err) 890 FUNC_API_SINCE(6) 891 { 892 String rhs = { .data = "", .size = 0 }; 893 modify_keymap(channel_id, buffer, true, mode, lhs, rhs, NULL, err); 894 } 895 896 /// Sets a buffer-scoped (b:) variable 897 /// 898 /// @param buffer Buffer id, or 0 for current buffer 899 /// @param name Variable name 900 /// @param value Variable value 901 /// @param[out] err Error details, if any 902 void nvim_buf_set_var(Buffer buffer, String name, Object value, Error *err) 903 FUNC_API_SINCE(1) 904 { 905 buf_T *buf = find_buffer_by_handle(buffer, err); 906 907 if (!buf) { 908 return; 909 } 910 911 dict_set_var(buf->b_vars, name, value, false, false, NULL, err); 912 } 913 914 /// Removes a buffer-scoped (b:) variable 915 /// 916 /// @param buffer Buffer id, or 0 for current buffer 917 /// @param name Variable name 918 /// @param[out] err Error details, if any 919 void nvim_buf_del_var(Buffer buffer, String name, Error *err) 920 FUNC_API_SINCE(1) 921 { 922 buf_T *buf = find_buffer_by_handle(buffer, err); 923 924 if (!buf) { 925 return; 926 } 927 928 dict_set_var(buf->b_vars, name, NIL, true, false, NULL, err); 929 } 930 931 /// Gets the full file name for the buffer 932 /// 933 /// @param buffer Buffer id, or 0 for current buffer 934 /// @param[out] err Error details, if any 935 /// @return Buffer name 936 String nvim_buf_get_name(Buffer buffer, Error *err) 937 FUNC_API_SINCE(1) 938 { 939 String rv = STRING_INIT; 940 buf_T *buf = find_buffer_by_handle(buffer, err); 941 942 if (!buf || buf->b_ffname == NULL) { 943 return rv; 944 } 945 946 return cstr_as_string(buf->b_ffname); 947 } 948 949 /// Sets the full file name for a buffer, like |:file_f| 950 /// 951 /// @param buffer Buffer id, or 0 for current buffer 952 /// @param name Buffer name 953 /// @param[out] err Error details, if any 954 void nvim_buf_set_name(Buffer buffer, String name, Error *err) 955 FUNC_API_SINCE(1) 956 { 957 buf_T *buf = find_buffer_by_handle(buffer, err); 958 959 if (!buf) { 960 return; 961 } 962 963 int ren_ret = OK; 964 TRY_WRAP(err, { 965 const bool is_curbuf = buf == curbuf; 966 const int save_acd = p_acd; 967 if (!is_curbuf) { 968 // Temporarily disable 'autochdir' when setting file name for another buffer. 969 p_acd = false; 970 } 971 972 // Using aucmd_*: autocommands will be executed by rename_buffer 973 aco_save_T aco; 974 aucmd_prepbuf(&aco, buf); 975 ren_ret = rename_buffer(name.data); 976 aucmd_restbuf(&aco); 977 978 if (!is_curbuf) { 979 p_acd = save_acd; 980 } 981 }); 982 983 if (ERROR_SET(err)) { 984 return; 985 } 986 987 if (ren_ret == FAIL) { 988 api_set_error(err, kErrorTypeException, "Failed to rename buffer"); 989 } 990 } 991 992 /// Checks if a buffer is valid and loaded. See |api-buffer| for more info 993 /// about unloaded buffers. 994 /// 995 /// @param buffer Buffer id, or 0 for current buffer 996 /// @return true if the buffer is valid and loaded, false otherwise. 997 Boolean nvim_buf_is_loaded(Buffer buffer) 998 FUNC_API_SINCE(5) 999 { 1000 Error stub = ERROR_INIT; 1001 buf_T *buf = find_buffer_by_handle(buffer, &stub); 1002 api_clear_error(&stub); 1003 return buf && buf->b_ml.ml_mfp != NULL; 1004 } 1005 1006 /// Deletes a buffer and its metadata (like |:bwipeout|). 1007 /// 1008 /// To get |:bdelete| behavior, reset 'buflisted' and pass `unload=true`: 1009 /// ```lua 1010 /// vim.bo.buflisted = false 1011 /// vim.api.nvim_buf_delete(0, { unload = true }) 1012 /// ``` 1013 /// 1014 /// @param buffer Buffer id, or 0 for current buffer 1015 /// @param opts Optional parameters. Keys: 1016 /// - force: Force deletion, ignore unsaved changes. 1017 /// - unload: Unloaded only (|:bunload|), do not delete. 1018 void nvim_buf_delete(Buffer buffer, Dict(buf_delete) *opts, Error *err) 1019 FUNC_API_SINCE(7) 1020 FUNC_API_TEXTLOCK 1021 { 1022 buf_T *buf = find_buffer_by_handle(buffer, err); 1023 1024 if (ERROR_SET(err)) { 1025 return; 1026 } 1027 1028 bool force = opts->force; 1029 1030 bool unload = opts->unload; 1031 1032 int result = do_buffer(unload ? DOBUF_UNLOAD : DOBUF_WIPE, 1033 DOBUF_FIRST, 1034 FORWARD, 1035 buf->handle, 1036 force); 1037 1038 if (result == FAIL) { 1039 api_set_error(err, kErrorTypeException, "Failed to unload buffer."); 1040 return; 1041 } 1042 } 1043 1044 /// Checks if a buffer is valid. 1045 /// 1046 /// @note Even if a buffer is valid it may have been unloaded. See |api-buffer| 1047 /// for more info about unloaded buffers. 1048 /// 1049 /// @param buffer Buffer id, or 0 for current buffer 1050 /// @return true if the buffer is valid, false otherwise. 1051 Boolean nvim_buf_is_valid(Buffer buffer) 1052 FUNC_API_SINCE(1) 1053 { 1054 Error stub = ERROR_INIT; 1055 Boolean ret = find_buffer_by_handle(buffer, &stub) != NULL; 1056 api_clear_error(&stub); 1057 return ret; 1058 } 1059 1060 /// Deletes a named mark in the buffer. See |mark-motions|. 1061 /// 1062 /// @note only deletes marks set in the buffer, if the mark is not set 1063 /// in the buffer it will return false. 1064 /// @param buffer Buffer to set the mark on 1065 /// @param name Mark name 1066 /// @return true if the mark was deleted, else false. 1067 /// @see |nvim_buf_set_mark()| 1068 /// @see |nvim_del_mark()| 1069 Boolean nvim_buf_del_mark(Buffer buffer, String name, Error *err) 1070 FUNC_API_SINCE(8) 1071 { 1072 bool res = false; 1073 buf_T *buf = find_buffer_by_handle(buffer, err); 1074 1075 if (!buf) { 1076 return res; 1077 } 1078 1079 VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, { 1080 return res; 1081 }); 1082 1083 fmark_T *fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, *name.data); 1084 1085 // fm is NULL when there's no mark with the given name 1086 VALIDATE_S((fm != NULL), "mark name", name.data, { 1087 return res; 1088 }); 1089 1090 // mark.lnum is 0 when the mark is not valid in the buffer, or is not set. 1091 if (fm->mark.lnum != 0 && fm->fnum == buf->handle) { 1092 // since the mark belongs to the buffer delete it. 1093 res = set_mark(buf, name, 0, 0, err); 1094 } 1095 1096 return res; 1097 } 1098 1099 /// Sets a named mark in the given buffer, all marks are allowed 1100 /// file/uppercase, visual, last change, etc. See |mark-motions|. 1101 /// 1102 /// Marks are (1,0)-indexed. |api-indexing| 1103 /// 1104 /// @note Passing 0 as line deletes the mark 1105 /// 1106 /// @param buffer Buffer to set the mark on 1107 /// @param name Mark name 1108 /// @param line Line number 1109 /// @param col Column/row number 1110 /// @param opts Optional parameters. Reserved for future use. 1111 /// @return true if the mark was set, else false. 1112 /// @see |nvim_buf_del_mark()| 1113 /// @see |nvim_buf_get_mark()| 1114 Boolean nvim_buf_set_mark(Buffer buffer, String name, Integer line, Integer col, Dict(empty) *opts, 1115 Error *err) 1116 FUNC_API_SINCE(8) 1117 { 1118 bool res = false; 1119 buf_T *buf = api_buf_ensure_loaded(buffer, err); 1120 1121 if (!buf) { 1122 return res; 1123 } 1124 1125 VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, { 1126 return res; 1127 }); 1128 1129 res = set_mark(buf, name, line, col, err); 1130 1131 return res; 1132 } 1133 1134 /// Returns a `(row,col)` tuple representing the position of the named mark. 1135 /// "End of line" column position is returned as |v:maxcol| (big number). 1136 /// See |mark-motions|. 1137 /// 1138 /// Marks are (1,0)-indexed. |api-indexing| 1139 /// 1140 /// @param buffer Buffer id, or 0 for current buffer 1141 /// @param name Mark name 1142 /// @param[out] err Error details, if any 1143 /// @return (row, col) tuple, (0, 0) if the mark is not set, or is an 1144 /// uppercase/file mark set in another buffer. 1145 /// @see |nvim_buf_set_mark()| 1146 /// @see |nvim_buf_del_mark()| 1147 ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Arena *arena, Error *err) 1148 FUNC_API_SINCE(1) 1149 { 1150 Array rv = ARRAY_DICT_INIT; 1151 buf_T *buf = find_buffer_by_handle(buffer, err); 1152 1153 if (!buf) { 1154 return rv; 1155 } 1156 1157 VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, { 1158 return rv; 1159 }); 1160 1161 fmark_T *fm; 1162 pos_T pos; 1163 char mark = *name.data; 1164 1165 fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, mark); 1166 VALIDATE_S((fm != NULL), "mark name", name.data, { 1167 return rv; 1168 }); 1169 // (0, 0) uppercase/file mark set in another buffer. 1170 if (fm->fnum != buf->handle) { 1171 pos.lnum = 0; 1172 pos.col = 0; 1173 } else { 1174 pos = fm->mark; 1175 } 1176 1177 rv = arena_array(arena, 2); 1178 ADD_C(rv, INTEGER_OBJ(pos.lnum)); 1179 ADD_C(rv, INTEGER_OBJ(pos.col)); 1180 1181 return rv; 1182 } 1183 1184 /// Call a function with buffer as temporary current buffer. 1185 /// 1186 /// This temporarily switches current buffer to "buffer". 1187 /// If the current window already shows "buffer", the window is not switched. 1188 /// If a window inside the current tabpage (including a float) already shows the 1189 /// buffer, then one of those windows will be set as current window temporarily. 1190 /// Otherwise a temporary scratch window (called the "autocmd window" for 1191 /// historical reasons) will be used. 1192 /// 1193 /// This is useful e.g. to call Vimscript functions that only work with the 1194 /// current buffer/window currently, like `jobstart(…, {'term': v:true})`. 1195 /// 1196 /// @param buffer Buffer id, or 0 for current buffer 1197 /// @param fun Function to call inside the buffer (currently Lua callable 1198 /// only) 1199 /// @param[out] err Error details, if any 1200 /// @return Return value of function. 1201 Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err) 1202 FUNC_API_SINCE(7) 1203 FUNC_API_LUA_ONLY 1204 { 1205 buf_T *buf = find_buffer_by_handle(buffer, err); 1206 if (!buf) { 1207 return NIL; 1208 } 1209 1210 Object res = OBJECT_INIT; 1211 TRY_WRAP(err, { 1212 aco_save_T aco; 1213 aucmd_prepbuf(&aco, buf); 1214 1215 Array args = ARRAY_DICT_INIT; 1216 res = nlua_call_ref(fun, NULL, args, kRetLuaref, NULL, err); 1217 1218 aucmd_restbuf(&aco); 1219 }); 1220 1221 return res; 1222 } 1223 1224 /// @nodoc 1225 Dict nvim__buf_stats(Buffer buffer, Arena *arena, Error *err) 1226 { 1227 buf_T *buf = find_buffer_by_handle(buffer, err); 1228 if (!buf) { 1229 return (Dict)ARRAY_DICT_INIT; 1230 } 1231 1232 Dict rv = arena_dict(arena, 7); 1233 // Number of times the cached line was flushed. 1234 // This should generally not increase while editing the same 1235 // line in the same mode. 1236 PUT_C(rv, "flush_count", INTEGER_OBJ(buf->flush_count)); 1237 // lnum of current line 1238 PUT_C(rv, "current_lnum", INTEGER_OBJ(buf->b_ml.ml_line_lnum)); 1239 // whether the line has unflushed changes. 1240 PUT_C(rv, "line_dirty", BOOLEAN_OBJ(buf->b_ml.ml_flags & ML_LINE_DIRTY)); 1241 // NB: this should be zero at any time API functions are called, 1242 // this exists to debug issues 1243 PUT_C(rv, "dirty_bytes", INTEGER_OBJ((Integer)buf->deleted_bytes)); 1244 PUT_C(rv, "dirty_bytes2", INTEGER_OBJ((Integer)buf->deleted_bytes2)); 1245 PUT_C(rv, "virt_blocks", INTEGER_OBJ((Integer)buf_meta_total(buf, kMTMetaLines))); 1246 1247 u_header_T *uhp = NULL; 1248 if (buf->b_u_curhead != NULL) { 1249 uhp = buf->b_u_curhead; 1250 } else if (buf->b_u_newhead) { 1251 uhp = buf->b_u_newhead; 1252 } 1253 if (uhp) { 1254 PUT_C(rv, "uhp_extmark_size", INTEGER_OBJ((Integer)kv_size(uhp->uh_extmark))); 1255 } 1256 1257 return rv; 1258 } 1259 1260 // Check if deleting lines made the cursor position invalid. 1261 // Changed lines from `lo` to `hi`; added `extra` lines (negative if deleted). 1262 static void fix_cursor(win_T *win, linenr_T lo, linenr_T hi, linenr_T extra) 1263 { 1264 if (win->w_cursor.lnum >= lo) { 1265 // Adjust cursor position if it's in/after the changed lines. 1266 if (win->w_cursor.lnum >= hi) { 1267 win->w_cursor.lnum += extra; 1268 } else if (extra < 0) { 1269 check_cursor_lnum(win); 1270 } 1271 check_cursor_col(win); 1272 changed_cline_bef_curs(win); 1273 win->w_valid &= ~(VALID_BOTLINE_AP); 1274 update_topline(win); 1275 } else { 1276 invalidate_botline_win(win); 1277 } 1278 } 1279 1280 /// Fix cursor position after replacing text 1281 /// between (start_row, start_col) and (end_row, end_col). 1282 /// 1283 /// win->w_cursor.lnum is assumed to be >= start_row and <= end_row. 1284 static void fix_cursor_cols(win_T *win, linenr_T start_row, colnr_T start_col, linenr_T end_row, 1285 colnr_T end_col, linenr_T new_rows, colnr_T new_cols_at_end_row) 1286 { 1287 colnr_T mode_col_adj = win == curwin && (State & MODE_INSERT) ? 0 : 1; 1288 1289 colnr_T end_row_change_start = new_rows == 1 ? start_col : 0; 1290 colnr_T end_row_change_end = end_row_change_start + new_cols_at_end_row; 1291 1292 // check if cursor is after replaced range or not 1293 if (win->w_cursor.lnum == end_row && win->w_cursor.col + mode_col_adj > end_col) { 1294 // if cursor is after replaced range, it's shifted 1295 // to keep it's position the same, relative to end_col 1296 1297 linenr_T old_rows = end_row - start_row + 1; 1298 win->w_cursor.lnum += new_rows - old_rows; 1299 win->w_cursor.col += end_row_change_end - end_col; 1300 } else { 1301 // if cursor is inside replaced range 1302 // and the new range got smaller, 1303 // it's shifted to keep it inside the new range 1304 // 1305 // if cursor is before range or range did not 1306 // got smaller, position is not changed 1307 1308 colnr_T old_coladd = win->w_cursor.coladd; 1309 1310 // it's easier to work with a single value here. 1311 // col and coladd are fixed by a later call 1312 // to check_cursor_col when necessary 1313 win->w_cursor.col += win->w_cursor.coladd; 1314 win->w_cursor.coladd = 0; 1315 1316 linenr_T new_end_row = start_row + new_rows - 1; 1317 1318 // make sure cursor row is in the new row range 1319 if (win->w_cursor.lnum > new_end_row) { 1320 win->w_cursor.lnum = new_end_row; 1321 1322 // don't simply move cursor up, but to the end 1323 // of new_end_row, if it's not at or after 1324 // it already (in case virtualedit is active) 1325 // column might be additionally adjusted below 1326 // to keep it inside col range if needed 1327 colnr_T len = ml_get_buf_len(win->w_buffer, new_end_row); 1328 if (win->w_cursor.col < len) { 1329 win->w_cursor.col = len; 1330 } 1331 } 1332 1333 // if cursor is at the last row and 1334 // it wasn't after eol before, move it exactly 1335 // to end_row_change_end 1336 if (win->w_cursor.lnum == new_end_row 1337 && win->w_cursor.col > end_row_change_end && old_coladd == 0) { 1338 win->w_cursor.col = end_row_change_end; 1339 1340 // make sure cursor is inside range, not after it, 1341 // except when doing so would move it before new range 1342 if (win->w_cursor.col - mode_col_adj >= end_row_change_start) { 1343 win->w_cursor.col -= mode_col_adj; 1344 } 1345 } 1346 } 1347 1348 check_cursor_col(win); 1349 changed_cline_bef_curs(win); 1350 invalidate_botline_win(win); 1351 } 1352 1353 /// Initialise a string array either: 1354 /// - on the Lua stack (as a table) (if lstate is not NULL) 1355 /// - as an API array object (if lstate is NULL). 1356 /// 1357 /// @param lstate Lua state. When NULL the Array is initialized instead. 1358 /// @param a Array to initialize 1359 /// @param size Size of array 1360 static inline void init_line_array(lua_State *lstate, Array *a, size_t size, Arena *arena) 1361 { 1362 if (lstate) { 1363 lua_createtable(lstate, (int)size, 0); 1364 } else { 1365 *a = arena_array(arena, size); 1366 } 1367 } 1368 1369 /// Push a string onto either the Lua stack (as a table element) or an API array object. 1370 /// 1371 /// For Lua, a table of the correct size must be created first. 1372 /// API array objects must be pre allocated. 1373 /// 1374 /// @param lstate Lua state. When NULL the Array is pushed to instead. 1375 /// @param a Array to push onto when not using Lua 1376 /// @param s String to push 1377 /// @param len Size of string 1378 /// @param idx 0-based index to place s (only used for Lua) 1379 /// @param replace_nl Replace newlines ('\n') with null (NUL) 1380 static void push_linestr(lua_State *lstate, Array *a, const char *s, size_t len, int idx, 1381 bool replace_nl, Arena *arena) 1382 { 1383 if (lstate) { 1384 // Vim represents NULs as NLs 1385 if (s && replace_nl && strchr(s, '\n')) { 1386 // TODO(bfredl): could manage scratch space in the arena, for the NUL case 1387 char *tmp = xmemdupz(s, len); 1388 strchrsub(tmp, '\n', NUL); 1389 lua_pushlstring(lstate, tmp, len); 1390 xfree(tmp); 1391 } else { 1392 lua_pushlstring(lstate, s, len); 1393 } 1394 lua_rawseti(lstate, -2, idx + 1); 1395 } else { 1396 String str = STRING_INIT; 1397 if (len > 0) { 1398 str = CBUF_TO_ARENA_STR(arena, s, len); 1399 if (replace_nl) { 1400 // Vim represents NULs as NLs, but this may confuse clients. 1401 strchrsub(str.data, '\n', NUL); 1402 } 1403 } 1404 1405 ADD_C(*a, STRING_OBJ(str)); 1406 } 1407 } 1408 1409 /// Collects `n` buffer lines into array `l` and/or lua_State `lstate`, optionally replacing 1410 /// newlines with NUL. 1411 /// 1412 /// @param buf Buffer to get lines from 1413 /// @param n Number of lines to collect 1414 /// @param replace_nl Replace newlines ("\n") with NUL 1415 /// @param start Line number to start from 1416 /// @param start_idx First index to push to (only used for Lua) 1417 /// @param[out] l If not NULL, Lines are copied here 1418 /// @param[out] lstate If not NULL, Lines are pushed into a table onto the stack 1419 /// @param err[out] Error, if any 1420 /// @return true unless `err` was set 1421 void buf_collect_lines(buf_T *buf, size_t n, linenr_T start, int start_idx, bool replace_nl, 1422 Array *l, lua_State *lstate, Arena *arena) 1423 { 1424 for (size_t i = 0; i < n; i++) { 1425 linenr_T lnum = start + (linenr_T)i; 1426 char *bufstr = ml_get_buf(buf, lnum); 1427 size_t bufstrlen = (size_t)ml_get_buf_len(buf, lnum); 1428 push_linestr(lstate, l, bufstr, bufstrlen, start_idx + (int)i, replace_nl, arena); 1429 } 1430 }