diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 21:52:58 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 21:52:58 +0000 |
commit | 931bffbda3668ddc609fc1da8f9eb576b170aa52 (patch) | |
tree | d8c1843a95da5ea0bb4acc09f7e37843d9995c86 /src/nvim/api/buffer.c | |
parent | 142d9041391780ac15b89886a54015fdc5c73995 (diff) | |
parent | 4a8bf24ac690004aedf5540fa440e788459e5e34 (diff) | |
download | rneovim-931bffbda3668ddc609fc1da8f9eb576b170aa52.tar.gz rneovim-931bffbda3668ddc609fc1da8f9eb576b170aa52.tar.bz2 rneovim-931bffbda3668ddc609fc1da8f9eb576b170aa52.zip |
Merge remote-tracking branch 'upstream/master' into userreguserreg
Diffstat (limited to 'src/nvim/api/buffer.c')
-rw-r--r-- | src/nvim/api/buffer.c | 441 |
1 files changed, 234 insertions, 207 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index fe9e6077d6..0df231868d 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1,10 +1,6 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - // Some of this code was adapted from 'if_py_both.h' from the original // vim source -#include <assert.h> #include <lauxlib.h> #include <stdbool.h> #include <stddef.h> @@ -14,9 +10,11 @@ #include "klib/kvec.h" #include "lua.h" #include "nvim/api/buffer.h" +#include "nvim/api/keysets_defs.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/ascii.h" +#include "nvim/api/private/validate.h" +#include "nvim/ascii_defs.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" @@ -26,6 +24,7 @@ #include "nvim/drawscreen.h" #include "nvim/ex_cmds.h" #include "nvim/extmark.h" +#include "nvim/func_attr.h" #include "nvim/globals.h" #include "nvim/lua/executor.h" #include "nvim/mapping.h" @@ -34,10 +33,11 @@ #include "nvim/memory.h" #include "nvim/move.h" #include "nvim/ops.h" -#include "nvim/pos.h" -#include "nvim/types.h" +#include "nvim/pos_defs.h" +#include "nvim/state_defs.h" +#include "nvim/types_defs.h" #include "nvim/undo.h" -#include "nvim/vim.h" +#include "nvim/vim_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/buffer.c.generated.h" @@ -47,7 +47,7 @@ /// /// \brief For more information on buffers, see |buffers| /// -/// Unloaded Buffers:~ +/// Unloaded Buffers: ~ /// /// Buffers may be unloaded by the |:bunload| command or the buffer's /// |'bufhidden'| option. When a buffer is unloaded its file contents are freed @@ -83,12 +83,16 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err) /// Activates buffer-update events on a channel, or as Lua callbacks. /// /// Example (Lua): capture buffer updates in a global `events` variable -/// (use "print(vim.inspect(events))" to see its contents): -/// <pre>lua -/// events = {} -/// vim.api.nvim_buf_attach(0, false, { -/// on_lines=function(...) table.insert(events, {...}) end}) -/// </pre> +/// (use "vim.print(events)" to see its contents): +/// +/// ```lua +/// events = {} +/// vim.api.nvim_buf_attach(0, false, { +/// on_lines = function(...) +/// table.insert(events, {...}) +/// end, +/// }) +/// ``` /// /// @see |nvim_buf_detach()| /// @see |api-buffer-updates-lua| @@ -111,7 +115,7 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err) /// - byte count of previous contents /// - deleted_codepoints (if `utf_sizes` is true) /// - deleted_codeunits (if `utf_sizes` is true) -/// - on_bytes: lua callback invoked on change. +/// - on_bytes: Lua callback invoked on change. /// This callback receives more granular information about the /// change compared to on_lines. /// Return `true` to detach. @@ -179,11 +183,9 @@ Boolean nvim_buf_attach(uint64_t channel_id, Buffer buffer, Boolean send_buffer, if (is_lua) { for (size_t j = 0; cbs[j].name; j++) { if (strequal(cbs[j].name, k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, - "%s is not a function", cbs[j].name); + VALIDATE_T(cbs[j].name, kObjectTypeLuaRef, v->type, { goto error; - } + }); *(cbs[j].dest) = v->data.luaref; v->data.luaref = LUA_NOREF; key_used = true; @@ -194,26 +196,23 @@ Boolean nvim_buf_attach(uint64_t channel_id, Buffer buffer, Boolean send_buffer, if (key_used) { continue; } else if (strequal("utf_sizes", k.data)) { - if (v->type != kObjectTypeBoolean) { - api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean"); + VALIDATE_T("utf_sizes", kObjectTypeBoolean, v->type, { goto error; - } + }); cb.utf_sizes = v->data.boolean; key_used = true; } else if (strequal("preview", k.data)) { - if (v->type != kObjectTypeBoolean) { - api_set_error(err, kErrorTypeValidation, "preview must be boolean"); + VALIDATE_T("preview", kObjectTypeBoolean, v->type, { goto error; - } + }); cb.preview = v->data.boolean; key_used = true; } } - if (!key_used) { - api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); + VALIDATE_S(key_used, "'opts' key", k.data, { goto error; - } + }); } return buf_updates_register(buf, channel_id, cb, send_buffer); @@ -252,6 +251,9 @@ void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last, Error *e if (!buf) { return; } + if (last < 0) { + last = buf->b_ml.ml_line_count; + } redraw_buf_range_later(buf, (linenr_T)first + 1, (linenr_T)last); } @@ -297,10 +299,9 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, start = normalize_index(buf, start, true, &oob); end = normalize_index(buf, end, true, &oob); - if (strict_indexing && oob) { - api_set_error(err, kErrorTypeValidation, "Index out of bounds"); + VALIDATE((!strict_indexing || !oob), "%s", "Index out of bounds", { return rv; - } + }); if (start >= end) { // Return 0-length array @@ -325,28 +326,6 @@ end: return rv; } -static bool check_string_array(Array arr, bool disallow_nl, Error *err) -{ - for (size_t i = 0; i < arr.size; i++) { - if (arr.items[i].type != kObjectTypeString) { - api_set_error(err, - kErrorTypeValidation, - "All items in the replacement array must be strings"); - return false; - } - // Disallow newlines in the middle of the line. - if (disallow_nl) { - const String l = arr.items[i].data.string; - if (memchr(l.data, NL, l.size)) { - api_set_error(err, kErrorTypeValidation, - "String cannot contain newlines"); - return false; - } - } - } - return true; -} - /// Sets (replaces) a line-range in the buffer. /// /// Indexing is zero-based, end-exclusive. Negative indices are interpreted @@ -371,7 +350,7 @@ static bool check_string_array(Array arr, bool disallow_nl, Error *err) void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integer end, Boolean strict_indexing, ArrayOf(String) replacement, Error *err) FUNC_API_SINCE(1) - FUNC_API_CHECK_TEXTLOCK + FUNC_API_TEXTLOCK_ALLOW_CMDWIN { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -379,24 +358,27 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ return; } + // load buffer first if it's not loaded + if (buf->b_ml.ml_mfp == NULL) { + if (!buf_ensure_loaded(buf)) { + api_set_error(err, kErrorTypeException, "Failed to load buffer"); + return; + } + } + bool oob = false; start = normalize_index(buf, start, true, &oob); end = normalize_index(buf, end, true, &oob); - if (strict_indexing && oob) { - api_set_error(err, kErrorTypeValidation, "Index out of bounds"); + VALIDATE((!strict_indexing || !oob), "%s", "Index out of bounds", { return; - } - - if (start > end) { - api_set_error(err, - kErrorTypeValidation, - "Argument \"start\" is higher than \"end\""); + }); + VALIDATE((start <= end), "%s", "'start' is higher than 'end'", { return; - } + }); bool disallow_nl = (channel_id != VIML_INTERNAL_CALL); - if (!check_string_array(replacement, disallow_nl, err)) { + if (!check_string_array(replacement, "replacement string", disallow_nl, err)) { return; } @@ -415,27 +397,25 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ } try_start(); - aco_save_T aco; - aucmd_prepbuf(&aco, buf); if (!MODIFIABLE(buf)) { api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'"); goto end; } - if (u_save((linenr_T)(start - 1), (linenr_T)end) == FAIL) { + if (u_save_buf(buf, (linenr_T)(start - 1), (linenr_T)end) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to save undo information"); goto end; } - bcount_t deleted_bytes = get_region_bytecount(curbuf, (linenr_T)start, (linenr_T)end, 0, 0); + bcount_t deleted_bytes = get_region_bytecount(buf, (linenr_T)start, (linenr_T)end, 0, 0); // If the size of the range is reducing (ie, new_len < old_len) we // need to delete some old_len. We do this at the start, by // repeatedly deleting line "start". - size_t to_delete = (new_len < old_len) ? (size_t)(old_len - new_len) : 0; + size_t to_delete = (new_len < old_len) ? old_len - new_len : 0; for (size_t i = 0; i < to_delete; i++) { - if (ml_delete((linenr_T)start, false) == FAIL) { + if (ml_delete_buf(buf, (linenr_T)start, false) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to delete line"); goto end; } @@ -453,12 +433,11 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ for (size_t i = 0; i < to_replace; i++) { int64_t lnum = start + (int64_t)i; - if (lnum >= MAXLNUM) { - api_set_error(err, kErrorTypeValidation, "Index value is too high"); + VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", { goto end; - } + }); - if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) { + if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to replace line"); goto end; } @@ -473,12 +452,11 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ for (size_t i = to_replace; i < new_len; i++) { int64_t lnum = start + (int64_t)i - 1; - if (lnum >= MAXLNUM) { - api_set_error(err, kErrorTypeValidation, "Index value is too high"); + VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", { goto end; - } + }); - if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) { + if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to insert line"); goto end; } @@ -493,20 +471,21 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ // Adjust marks. Invalidate any which lie in the // changed range, and move any in the remainder of the buffer. - // Only adjust marks if we managed to switch to a window that holds - // the buffer, otherwise line numbers will be invalid. - mark_adjust((linenr_T)start, - (linenr_T)(end - 1), - MAXLNUM, - (linenr_T)extra, - kExtmarkNOOP); - - extmark_splice(curbuf, (int)start - 1, 0, (int)(end - start), 0, + linenr_T adjust = end > start ? MAXLNUM : 0; + mark_adjust_buf(buf, (linenr_T)start, (linenr_T)(end - 1), adjust, (linenr_T)extra, + true, true, kExtmarkNOOP); + + extmark_splice(buf, (int)start - 1, 0, (int)(end - start), 0, deleted_bytes, (int)new_len, 0, inserted_bytes, kExtmarkUndo); - changed_lines((linenr_T)start, 0, (linenr_T)end, (linenr_T)extra, true); - fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra); + changed_lines(buf, (linenr_T)start, 0, (linenr_T)end, (linenr_T)extra, true); + + FOR_ALL_TAB_WINDOWS(tp, win) { + if (win->w_buffer == buf) { + fix_cursor(win, (linenr_T)start, (linenr_T)end, (linenr_T)extra); + } + } end: for (size_t i = 0; i < new_len; i++) { @@ -514,7 +493,6 @@ end: } xfree(lines); - aucmd_restbuf(&aco); try_end(err); } @@ -533,7 +511,10 @@ end: /// /// Prefer |nvim_buf_set_lines()| if you are only adding or deleting entire lines. /// +/// Prefer |nvim_put()| if you want to insert text at the cursor position. +/// /// @see |nvim_buf_set_lines()| +/// @see |nvim_put()| /// /// @param channel_id /// @param buffer Buffer handle, or 0 for current buffer @@ -546,10 +527,11 @@ end: void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, Integer start_col, Integer end_row, Integer end_col, ArrayOf(String) replacement, Error *err) FUNC_API_SINCE(7) + FUNC_API_TEXTLOCK_ALLOW_CMDWIN { MAXSIZE_TEMP_ARRAY(scratch, 1); if (replacement.size == 0) { - ADD_C(scratch, STRING_OBJ(STATIC_CSTR_AS_STRING(""))); + ADD_C(scratch, STATIC_CSTR_AS_OBJ("")); replacement = scratch; } @@ -558,48 +540,54 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In return; } + // load buffer first if it's not loaded + if (buf->b_ml.ml_mfp == NULL) { + if (!buf_ensure_loaded(buf)) { + api_set_error(err, kErrorTypeException, "Failed to load buffer"); + return; + } + } + bool oob = false; // check range is ordered and everything! // start_row, end_row within buffer len (except add text past the end?) start_row = normalize_index(buf, start_row, false, &oob); - if (oob) { - api_set_error(err, kErrorTypeValidation, "start_row out of bounds"); + VALIDATE_RANGE((!oob), "start_row", { return; - } + }); end_row = normalize_index(buf, end_row, false, &oob); - if (oob) { - api_set_error(err, kErrorTypeValidation, "end_row out of bounds"); + VALIDATE_RANGE((!oob), "end_row", { return; - } + }); char *str_at_start = NULL; char *str_at_end = NULL; // Another call to ml_get_buf() may free the line, so make a copy. - str_at_start = xstrdup(ml_get_buf(buf, (linenr_T)start_row, false)); + str_at_start = xstrdup(ml_get_buf(buf, (linenr_T)start_row)); size_t len_at_start = strlen(str_at_start); - if (start_col < 0 || (size_t)start_col > len_at_start) { - api_set_error(err, kErrorTypeValidation, "start_col out of bounds"); + start_col = start_col < 0 ? (int64_t)len_at_start + start_col + 1 : start_col; + VALIDATE_RANGE((start_col >= 0 && (size_t)start_col <= len_at_start), "start_col", { goto early_end; - } + }); // Another call to ml_get_buf() may free the line, so make a copy. - str_at_end = xstrdup(ml_get_buf(buf, (linenr_T)end_row, false)); + str_at_end = xstrdup(ml_get_buf(buf, (linenr_T)end_row)); size_t len_at_end = strlen(str_at_end); - if (end_col < 0 || (size_t)end_col > len_at_end) { - api_set_error(err, kErrorTypeValidation, "end_col out of bounds"); + end_col = end_col < 0 ? (int64_t)len_at_end + end_col + 1 : end_col; + VALIDATE_RANGE((end_col >= 0 && (size_t)end_col <= len_at_end), "end_col", { goto early_end; - } + }); - if (start_row > end_row || (end_row == start_row && start_col > end_col)) { - api_set_error(err, kErrorTypeValidation, "start is higher than end"); + VALIDATE((start_row <= end_row && !(end_row == start_row && start_col > end_col)), + "%s", "'start' is higher than 'end'", { goto early_end; - } + }); bool disallow_nl = (channel_id != VIML_INTERNAL_CALL); - if (!check_string_array(replacement, disallow_nl, err)) { + if (!check_string_array(replacement, "replacement string", disallow_nl, err)) { goto early_end; } @@ -616,7 +604,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In for (int64_t i = 1; i < end_row - start_row; i++) { int64_t lnum = start_row + i; - const char *bufline = ml_get_buf(buf, (linenr_T)lnum, false); + const char *bufline = ml_get_buf(buf, (linenr_T)lnum); old_byte += (bcount_t)(strlen(bufline)) + 1; } old_byte += (bcount_t)end_col + 1; @@ -662,8 +650,6 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In } try_start(); - aco_save_T aco; - aucmd_prepbuf(&aco, buf); if (!MODIFIABLE(buf)) { api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'"); @@ -672,7 +658,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In // Small note about undo states: unlike set_lines, we want to save the // undo state of one past the end_row, since end_row is inclusive. - if (u_save((linenr_T)start_row - 1, (linenr_T)end_row + 1) == FAIL) { + if (u_save_buf(buf, (linenr_T)start_row - 1, (linenr_T)end_row + 1) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to save undo information"); goto end; } @@ -683,9 +669,9 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In // If the size of the range is reducing (ie, new_len < old_len) we // need to delete some old_len. We do this at the start, by // repeatedly deleting line "start". - size_t to_delete = (new_len < old_len) ? (size_t)(old_len - new_len) : 0; + size_t to_delete = (new_len < old_len) ? old_len - new_len : 0; for (size_t i = 0; i < to_delete; i++) { - if (ml_delete((linenr_T)start_row, false) == FAIL) { + if (ml_delete_buf(buf, (linenr_T)start_row, false) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to delete line"); goto end; } @@ -702,12 +688,11 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In for (size_t i = 0; i < to_replace; i++) { int64_t lnum = start_row + (int64_t)i; - if (lnum >= MAXLNUM) { - api_set_error(err, kErrorTypeValidation, "Index value is too high"); + VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", { goto end; - } + }); - if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) { + if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to replace line"); goto end; } @@ -720,12 +705,11 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In for (size_t i = to_replace; i < new_len; i++) { int64_t lnum = start_row + (int64_t)i - 1; - if (lnum >= MAXLNUM) { - api_set_error(err, kErrorTypeValidation, "Index value is too high"); + VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", { goto end; - } + }); - if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) { + if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to insert line"); goto end; } @@ -736,35 +720,39 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In extra++; } + colnr_T col_extent = (colnr_T)(end_col + - ((end_row == start_row) ? start_col : 0)); + // Adjust marks. Invalidate any which lie in the // changed range, and move any in the remainder of the buffer. - mark_adjust((linenr_T)start_row, - (linenr_T)end_row, - MAXLNUM, - (linenr_T)extra, - kExtmarkNOOP); + // Do not adjust any cursors. need to use column-aware logic (below) + linenr_T adjust = end_row >= start_row ? MAXLNUM : 0; + mark_adjust_buf(buf, (linenr_T)start_row, (linenr_T)end_row, adjust, (linenr_T)extra, + true, true, kExtmarkNOOP); - colnr_T col_extent = (colnr_T)(end_col - - ((end_row == start_row) ? start_col : 0)); extmark_splice(buf, (int)start_row - 1, (colnr_T)start_col, (int)(end_row - start_row), col_extent, old_byte, (int)new_len - 1, (colnr_T)last_item.size, new_byte, kExtmarkUndo); - changed_lines((linenr_T)start_row, 0, (linenr_T)end_row + 1, (linenr_T)extra, true); + changed_lines(buf, (linenr_T)start_row, 0, (linenr_T)end_row + 1, (linenr_T)extra, true); - // adjust cursor like an extmark ( i e it was inside last_part_len) - if (curwin->w_cursor.lnum == end_row && curwin->w_cursor.col > end_col) { - curwin->w_cursor.col -= col_extent - (colnr_T)last_item.size; + FOR_ALL_TAB_WINDOWS(tp, win) { + if (win->w_buffer == buf) { + if (win->w_cursor.lnum >= start_row && win->w_cursor.lnum <= end_row) { + fix_cursor_cols(win, (linenr_T)start_row, (colnr_T)start_col, (linenr_T)end_row, + (colnr_T)end_col, (linenr_T)new_len, (colnr_T)last_item.size); + } else { + fix_cursor(win, (linenr_T)start_row, (linenr_T)end_row, (linenr_T)extra); + } + } } - fix_cursor((linenr_T)start_row, (linenr_T)end_row, (linenr_T)extra); end: for (size_t i = 0; i < new_len; i++) { xfree(lines[i]); } xfree(lines); - aucmd_restbuf(&aco); try_end(err); early_end: @@ -800,10 +788,9 @@ ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer, { Array rv = ARRAY_DICT_INIT; - if (opts.size > 0) { - api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); + VALIDATE((opts.size == 0), "%s", "opts dict isn't empty", { return rv; - } + }); buf_T *buf = find_buffer_by_handle(buffer, err); @@ -820,18 +807,16 @@ ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer, start_row = normalize_index(buf, start_row, false, &oob); end_row = normalize_index(buf, end_row, false, &oob); - if (oob) { - api_set_error(err, kErrorTypeValidation, "Index out of bounds"); + VALIDATE((!oob), "%s", "Index out of bounds", { return rv; - } + }); // nvim_buf_get_lines doesn't care if the start row is greater than the end // row (it will just return an empty array), but nvim_buf_get_text does in // order to maintain symmetry with nvim_buf_set_text. - if (start_row > end_row) { - api_set_error(err, kErrorTypeValidation, "start is higher than end"); + VALIDATE((start_row <= end_row), "%s", "'start' is higher than 'end'", { return rv; - } + }); bool replace_nl = (channel_id != VIML_INTERNAL_CALL); @@ -907,10 +892,9 @@ Integer nvim_buf_get_offset(Buffer buffer, Integer index, Error *err) return -1; } - if (index < 0 || index > buf->b_ml.ml_line_count) { - api_set_error(err, kErrorTypeValidation, "Index out of bounds"); + VALIDATE((index >= 0 && index <= buf->b_ml.ml_line_count), "%s", "Index out of bounds", { return 0; - } + }); return ml_find_line_or_offset(buf, (int)index + 1, NULL, true); } @@ -1100,7 +1084,7 @@ Boolean nvim_buf_is_loaded(Buffer buffer) /// - unload: Unloaded only, do not delete. See |:bunload| void nvim_buf_delete(Buffer buffer, Dictionary opts, Error *err) FUNC_API_SINCE(7) - FUNC_API_CHECK_TEXTLOCK + FUNC_API_TEXTLOCK { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -1118,8 +1102,9 @@ void nvim_buf_delete(Buffer buffer, Dictionary opts, Error *err) } else if (strequal("unload", k.data)) { unload = api_object_to_bool(v, "unload", false, err); } else { - api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); - return; + VALIDATE_S(false, "'opts' key", k.data, { + return; + }); } } @@ -1174,20 +1159,16 @@ Boolean nvim_buf_del_mark(Buffer buffer, String name, Error *err) return res; } - if (name.size != 1) { - api_set_error(err, kErrorTypeValidation, - "Mark name must be a single character"); + VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, { return res; - } + }); fmark_T *fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, *name.data); // fm is NULL when there's no mark with the given name - if (fm == NULL) { - api_set_error(err, kErrorTypeValidation, "Invalid mark name: '%c'", - *name.data); + VALIDATE_S((fm != NULL), "mark name", name.data, { return res; - } + }); // mark.lnum is 0 when the mark is not valid in the buffer, or is not set. if (fm->mark.lnum != 0 && fm->fnum == buf->handle) { @@ -1224,19 +1205,18 @@ Boolean nvim_buf_set_mark(Buffer buffer, String name, Integer line, Integer col, return res; } - if (name.size != 1) { - api_set_error(err, kErrorTypeValidation, - "Mark name must be a single character"); + VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, { return res; - } + }); res = set_mark(buf, name, line, col, err); return res; } -/// Returns a tuple (row,col) representing the position of the named mark. See -/// |mark-motions|. +/// Returns a `(row,col)` tuple representing the position of the named mark. +/// "End of line" column position is returned as |v:maxcol| (big number). +/// See |mark-motions|. /// /// Marks are (1,0)-indexed. |api-indexing| /// @@ -1257,21 +1237,18 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) return rv; } - if (name.size != 1) { - api_set_error(err, kErrorTypeValidation, - "Mark name must be a single character"); + VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, { return rv; - } + }); fmark_T *fm; pos_T pos; char mark = *name.data; fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, mark); - if (fm == NULL) { - api_set_error(err, kErrorTypeValidation, "Invalid mark name"); + VALIDATE_S((fm != NULL), "mark name", name.data, { return rv; - } + }); // (0, 0) uppercase/file mark set in another buffer. if (fm->fnum != buf->handle) { pos.lnum = 0; @@ -1295,15 +1272,15 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) /// Otherwise a temporary scratch window (called the "autocmd window" for /// historical reasons) will be used. /// -/// This is useful e.g. to call vimL functions that only work with the current -/// buffer/window currently, like |termopen()|. +/// This is useful e.g. to call Vimscript functions that only work with the +/// current buffer/window currently, like |termopen()|. /// /// @param buffer Buffer handle, or 0 for current buffer -/// @param fun Function to call inside the buffer (currently lua callable +/// @param fun Function to call inside the buffer (currently Lua callable /// only) /// @param[out] err Error details, if any -/// @return Return value of function. NB: will deepcopy lua values -/// currently, use upvalues to send lua references in and out. +/// @return Return value of function. NB: will deepcopy Lua values +/// currently, use upvalues to send Lua references in and out. Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err) FUNC_API_SINCE(7) FUNC_API_LUA_ONLY @@ -1362,42 +1339,92 @@ Dictionary nvim__buf_stats(Buffer buffer, Error *err) // Check if deleting lines made the cursor position invalid. // Changed lines from `lo` to `hi`; added `extra` lines (negative if deleted). -static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra) +static void fix_cursor(win_T *win, linenr_T lo, linenr_T hi, linenr_T extra) { - if (curwin->w_cursor.lnum >= lo) { + if (win->w_cursor.lnum >= lo) { // Adjust cursor position if it's in/after the changed lines. - if (curwin->w_cursor.lnum >= hi) { - curwin->w_cursor.lnum += extra; - check_cursor_col(); + if (win->w_cursor.lnum >= hi) { + win->w_cursor.lnum += extra; } else if (extra < 0) { - check_cursor(); - } else { - check_cursor_col(); + check_cursor_lnum(win); } - changed_cline_bef_curs(); + check_cursor_col_win(win); + changed_cline_bef_curs(win); } - invalidate_botline(); + invalidate_botline(win); } -// Normalizes 0-based indexes to buffer line numbers -static int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bool *oob) +/// Fix cursor position after replacing text +/// between (start_row, start_col) and (end_row, end_col). +/// +/// win->w_cursor.lnum is assumed to be >= start_row and <= end_row. +static void fix_cursor_cols(win_T *win, linenr_T start_row, colnr_T start_col, linenr_T end_row, + colnr_T end_col, linenr_T new_rows, colnr_T new_cols_at_end_row) { - assert(buf->b_ml.ml_line_count > 0); - int64_t max_index = buf->b_ml.ml_line_count + (int)end_exclusive - 1; - // Fix if < 0 - index = index < 0 ? max_index + index + 1 : index; - - // Check for oob - if (index > max_index) { - *oob = true; - index = max_index; - } else if (index < 0) { - *oob = true; - index = 0; - } - // Convert the index to a vim line number - index++; - return index; + colnr_T mode_col_adj = win == curwin && (State & MODE_INSERT) ? 0 : 1; + + colnr_T end_row_change_start = new_rows == 1 ? start_col : 0; + colnr_T end_row_change_end = end_row_change_start + new_cols_at_end_row; + + // check if cursor is after replaced range or not + if (win->w_cursor.lnum == end_row && win->w_cursor.col + mode_col_adj > end_col) { + // if cursor is after replaced range, it's shifted + // to keep it's position the same, relative to end_col + + linenr_T old_rows = end_row - start_row + 1; + win->w_cursor.lnum += new_rows - old_rows; + win->w_cursor.col += end_row_change_end - end_col; + } else { + // if cursor is inside replaced range + // and the new range got smaller, + // it's shifted to keep it inside the new range + // + // if cursor is before range or range did not + // got smaller, position is not changed + + colnr_T old_coladd = win->w_cursor.coladd; + + // it's easier to work with a single value here. + // col and coladd are fixed by a later call + // to check_cursor_col_win when necessary + win->w_cursor.col += win->w_cursor.coladd; + win->w_cursor.coladd = 0; + + linenr_T new_end_row = start_row + new_rows - 1; + + // make sure cursor row is in the new row range + if (win->w_cursor.lnum > new_end_row) { + win->w_cursor.lnum = new_end_row; + + // don't simply move cursor up, but to the end + // of new_end_row, if it's not at or after + // it already (in case virtualedit is active) + // column might be additionally adjusted below + // to keep it inside col range if needed + colnr_T len = (colnr_T)strlen(ml_get_buf(win->w_buffer, new_end_row)); + if (win->w_cursor.col < len) { + win->w_cursor.col = len; + } + } + + // if cursor is at the last row and + // it wasn't after eol before, move it exactly + // to end_row_change_end + if (win->w_cursor.lnum == new_end_row + && win->w_cursor.col > end_row_change_end && old_coladd == 0) { + win->w_cursor.col = end_row_change_end; + + // make sure cursor is inside range, not after it, + // except when doing so would move it before new range + if (win->w_cursor.col - mode_col_adj >= end_row_change_start) { + win->w_cursor.col -= mode_col_adj; + } + } + } + + check_cursor_col_win(win); + changed_cline_bef_curs(win); + invalidate_botline(win); } /// Initialise a string array either: @@ -1481,7 +1508,7 @@ bool buf_collect_lines(buf_T *buf, size_t n, linenr_T start, int start_idx, bool return false; } - char *bufstr = ml_get_buf(buf, lnum, false); + char *bufstr = ml_get_buf(buf, lnum); push_linestr(lstate, l, bufstr, strlen(bufstr), start_idx + (int)i, replace_nl); } |