diff options
Diffstat (limited to 'src/nvim/api/buffer.c')
-rw-r--r-- | src/nvim/api/buffer.c | 407 |
1 files changed, 189 insertions, 218 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 2d5403d4b8..806b649ce6 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -22,6 +22,7 @@ #include "nvim/ex_docmd.h" #include "nvim/extmark.h" #include "nvim/lua/executor.h" +#include "nvim/mapping.h" #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" @@ -35,7 +36,6 @@ # include "api/buffer.c.generated.h" #endif - /// \defgroup api-buffer /// /// \brief For more information on buffers, see |buffers| @@ -51,8 +51,7 @@ /// You can use |nvim_buf_is_loaded()| or |nvim_buf_line_count()| to check /// whether a buffer is loaded. - -/// Gets the buffer line count +/// Returns the number of lines in the given buffer. /// /// @param buffer Buffer handle, or 0 for current buffer /// @param[out] err Error details, if any @@ -133,7 +132,7 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err) /// - buffer handle /// - on_reload: Lua callback invoked on reload. The entire buffer /// content should be considered changed. Args: -/// - the string "detach" +/// - the string "reload" /// - buffer handle /// - utf_sizes: include UTF-32 and UTF-16 size of the replaced /// region, as args to `on_lines`. @@ -247,7 +246,7 @@ void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last, Error *e return; } - redraw_buf_range_later(buf, (linenr_T)first+1, (linenr_T)last); + redraw_buf_range_later(buf, (linenr_T)first + 1, (linenr_T)last); } /// Gets a line-range from the buffer. @@ -262,7 +261,7 @@ void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last, Error *e /// @param channel_id /// @param buffer Buffer handle, or 0 for current buffer /// @param start First line index -/// @param end Last line index (exclusive) +/// @param end Last line index, exclusive /// @param strict_indexing Whether out-of-bounds should be an error. /// @param[out] err Error details, if any /// @return Array of lines, or empty array for unloaded buffer. @@ -287,8 +286,8 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, } bool oob = false; - start = normalize_index(buf, start, &oob); - end = normalize_index(buf, end, &oob); + 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"); @@ -358,7 +357,7 @@ static bool check_string_array(Array arr, bool disallow_nl, Error *err) /// @param channel_id /// @param buffer Buffer handle, or 0 for current buffer /// @param start First line index -/// @param end Last line index (exclusive) +/// @param end Last line index, exclusive /// @param strict_indexing Whether out-of-bounds should be an error. /// @param replacement Array of lines to use as replacement /// @param[out] err Error details, if any @@ -374,15 +373,14 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ } bool oob = false; - start = normalize_index(buf, start, &oob); - end = normalize_index(buf, end, &oob); + 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"); return; } - if (start > end) { api_set_error(err, kErrorTypeValidation, @@ -423,7 +421,7 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ goto end; } - bcount_t deleted_bytes = get_region_bytecount(curbuf, start, end, 0, 0); + bcount_t deleted_bytes = get_region_bytecount(curbuf, (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 @@ -453,7 +451,7 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ goto end; } - if (ml_replace((linenr_T)lnum, (char_u *)lines[i], false) == FAIL) { + if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to replace line"); goto end; } @@ -473,7 +471,7 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ goto end; } - if (ml_append((linenr_T)lnum, (char_u *)lines[i], 0, false) == FAIL) { + if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to insert line"); goto end; } @@ -493,14 +491,14 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ mark_adjust((linenr_T)start, (linenr_T)(end - 1), MAXLNUM, - (long)extra, + (linenr_T)extra, kExtmarkNOOP); - extmark_splice(curbuf, (int)start-1, 0, (int)(end-start), 0, + extmark_splice(curbuf, (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, (long)extra, true); + changed_lines((linenr_T)start, 0, (linenr_T)end, (linenr_T)extra, true); fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra); end: @@ -515,24 +513,25 @@ end: /// Sets (replaces) a range in the buffer /// -/// This is recommended over nvim_buf_set_lines when only modifying parts of a -/// line, as extmarks will be preserved on non-modified parts of the touched +/// This is recommended over |nvim_buf_set_lines()| when only modifying parts of +/// a line, as extmarks will be preserved on non-modified parts of the touched /// lines. /// -/// Indexing is zero-based and end-exclusive. +/// Indexing is zero-based. Row indices are end-inclusive, and column indices +/// are end-exclusive. /// -/// To insert text at a given index, set `start` and `end` ranges to the same -/// index. To delete a range, set `replacement` to an array containing -/// an empty string, or simply an empty array. +/// To insert text at a given `(row, column)` location, use `start_row = end_row +/// = row` and `start_col = end_col = col`. To delete the text in a range, use +/// `replacement = {}`. /// -/// Prefer nvim_buf_set_lines when adding or deleting entire lines only. +/// Prefer |nvim_buf_set_lines()| if you are only adding or deleting entire lines. /// /// @param channel_id /// @param buffer Buffer handle, or 0 for current buffer /// @param start_row First line index -/// @param start_column First column -/// @param end_row Last line index -/// @param end_column Last column +/// @param start_col Starting column (byte offset) on first line +/// @param end_row Last line index, inclusive +/// @param end_col Ending column (byte offset) on last line, exclusive /// @param replacement Array of lines to use as replacement /// @param[out] err Error details, if any void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, Integer start_col, @@ -554,25 +553,25 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In // 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, &oob); - if (oob || start_row == buf->b_ml.ml_line_count + 1) { + start_row = normalize_index(buf, start_row, false, &oob); + if (oob) { api_set_error(err, kErrorTypeValidation, "start_row out of bounds"); return; } - end_row = normalize_index(buf, end_row, &oob); - if (oob || end_row == buf->b_ml.ml_line_count + 1) { + end_row = normalize_index(buf, end_row, false, &oob); + if (oob) { api_set_error(err, kErrorTypeValidation, "end_row out of bounds"); return; } - char *str_at_start = (char *)ml_get_buf(buf, start_row, false); + char *str_at_start = (char *)ml_get_buf(buf, (linenr_T)start_row, false); if (start_col < 0 || (size_t)start_col > strlen(str_at_start)) { api_set_error(err, kErrorTypeValidation, "start_col out of bounds"); return; } - char *str_at_end = (char *)ml_get_buf(buf, end_row, false); + char *str_at_end = (char *)ml_get_buf(buf, (linenr_T)end_row, false); 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"); @@ -598,21 +597,20 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In if (start_row == end_row) { old_byte = (bcount_t)end_col - start_col; } else { - const char *bufline; old_byte += (bcount_t)strlen(str_at_start) - start_col; for (int64_t i = 1; i < end_row - start_row; i++) { int64_t lnum = start_row + i; - bufline = (char *)ml_get_buf(buf, lnum, false); - old_byte += (bcount_t)(strlen(bufline))+1; + const char *bufline = (char *)ml_get_buf(buf, (linenr_T)lnum, false); + old_byte += (bcount_t)(strlen(bufline)) + 1; } - old_byte += (bcount_t)end_col+1; + old_byte += (bcount_t)end_col + 1; } String first_item = replacement.items[0].data.string; - String last_item = replacement.items[replacement.size-1].data.string; + String last_item = replacement.items[replacement.size - 1].data.string; - size_t firstlen = (size_t)start_col+first_item.size; + size_t firstlen = (size_t)start_col + first_item.size; size_t last_part_len = strlen(str_at_end) - (size_t)end_col; if (replacement.size == 1) { firstlen += last_part_len; @@ -620,32 +618,32 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In char *first = xmallocz(firstlen); char *last = NULL; memcpy(first, str_at_start, (size_t)start_col); - memcpy(first+start_col, first_item.data, first_item.size); - memchrsub(first+start_col, NUL, NL, first_item.size); + memcpy(first + start_col, first_item.data, first_item.size); + memchrsub(first + start_col, NUL, NL, first_item.size); if (replacement.size == 1) { - memcpy(first+start_col+first_item.size, str_at_end+end_col, last_part_len); + memcpy(first + start_col + first_item.size, str_at_end + end_col, last_part_len); } else { - last = xmallocz(last_item.size+last_part_len); + last = xmallocz(last_item.size + last_part_len); memcpy(last, last_item.data, last_item.size); memchrsub(last, NUL, NL, last_item.size); - memcpy(last+last_item.size, str_at_end+end_col, last_part_len); + memcpy(last + last_item.size, str_at_end + end_col, last_part_len); } char **lines = xcalloc(new_len, sizeof(char *)); lines[0] = first; new_byte += (bcount_t)(first_item.size); - for (size_t i = 1; i < new_len-1; i++) { + for (size_t i = 1; i < new_len - 1; i++) { const String l = replacement.items[i].data.string; // Fill lines[i] with l's contents. Convert NULs to newlines as required by // NL-used-for-NUL. lines[i] = xmemdupz(l.data, l.size); memchrsub(lines[i], NUL, NL, l.size); - new_byte += (bcount_t)(l.size)+1; + new_byte += (bcount_t)(l.size) + 1; } if (replacement.size > 1) { - lines[replacement.size-1] = last; - new_byte += (bcount_t)(last_item.size)+1; + lines[replacement.size - 1] = last; + new_byte += (bcount_t)(last_item.size) + 1; } try_start(); @@ -665,7 +663,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In } ptrdiff_t extra = 0; // lines added to text, can be negative - size_t old_len = (size_t)(end_row-start_row+1); + size_t old_len = (size_t)(end_row - start_row + 1); // 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 @@ -694,7 +692,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In goto end; } - if (ml_replace((linenr_T)lnum, (char_u *)lines[i], false) == FAIL) { + if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to replace line"); goto end; } @@ -712,7 +710,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In goto end; } - if (ml_append((linenr_T)lnum, (char_u *)lines[i], 0, false) == FAIL) { + if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to insert line"); goto end; } @@ -728,19 +726,17 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In mark_adjust((linenr_T)start_row, (linenr_T)end_row, MAXLNUM, - (long)extra, + (linenr_T)extra, 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, + 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, - (long)extra, true); + changed_lines((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) { @@ -757,6 +753,109 @@ end: try_end(err); } +/// Gets a range from the buffer. +/// +/// This differs from |nvim_buf_get_lines()| in that it allows retrieving only +/// portions of a line. +/// +/// Indexing is zero-based. Row indices are end-inclusive, and column indices +/// are end-exclusive. +/// +/// Prefer |nvim_buf_get_lines()| when retrieving entire lines. +/// +/// @param channel_id +/// @param buffer Buffer handle, or 0 for current buffer +/// @param start_row First line index +/// @param start_col Starting column (byte offset) on first line +/// @param end_row Last line index, inclusive +/// @param end_col Ending column (byte offset) on last line, exclusive +/// @param opts Optional parameters. Currently unused. +/// @param[out] err Error details, if any +/// @return Array of lines, or empty array for unloaded buffer. +ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer, + Integer start_row, Integer start_col, + Integer end_row, Integer end_col, + Dictionary opts, Error *err) + FUNC_API_SINCE(9) +{ + Array rv = ARRAY_DICT_INIT; + + if (opts.size > 0) { + api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); + return rv; + } + + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return rv; + } + + // return sentinel value if the buffer isn't loaded + if (buf->b_ml.ml_mfp == NULL) { + return rv; + } + + bool oob = false; + 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"); + 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"); + return rv; + } + + bool replace_nl = (channel_id != VIML_INTERNAL_CALL); + + if (start_row == end_row) { + String line = buf_get_text(buf, start_row, start_col, end_col, replace_nl, err); + if (ERROR_SET(err)) { + return rv; + } + + ADD(rv, STRING_OBJ(line)); + return rv; + } + + rv.size = (size_t)(end_row - start_row) + 1; + rv.items = xcalloc(rv.size, sizeof(Object)); + + rv.items[0] = STRING_OBJ(buf_get_text(buf, start_row, start_col, MAXCOL - 1, replace_nl, err)); + if (ERROR_SET(err)) { + goto end; + } + + if (rv.size > 2) { + Array tmp = ARRAY_DICT_INIT; + tmp.items = &rv.items[1]; + if (!buf_collect_lines(buf, rv.size - 2, start_row + 1, replace_nl, &tmp, err)) { + goto end; + } + } + + rv.items[rv.size - 1] = STRING_OBJ(buf_get_text(buf, end_row, 0, end_col, replace_nl, err)); + if (ERROR_SET(err)) { + goto end; + } + +end: + if (ERROR_SET(err)) { + api_free_array(rv); + rv.size = 0; + rv.items = NULL; + } + + return rv; +} + /// Returns the byte offset of a line (0-indexed). |api-indexing| /// /// Line 1 (index=0) has offset 0. UTF-8 bytes are counted. EOL is one byte. @@ -789,7 +888,7 @@ Integer nvim_buf_get_offset(Buffer buffer, Integer index, Error *err) return 0; } - return ml_find_line_or_offset(buf, (int)index+1, NULL, true); + return ml_find_line_or_offset(buf, (int)index + 1, NULL, true); } /// Gets a buffer-scoped (b:) variable. @@ -852,11 +951,11 @@ ArrayOf(Dictionary) nvim_buf_get_keymap(uint64_t channel_id, Buffer buffer, Stri /// @see |nvim_set_keymap()| /// /// @param buffer Buffer handle, or 0 for current buffer -void nvim_buf_set_keymap(Buffer buffer, String mode, String lhs, String rhs, Dict(keymap) *opts, - Error *err) +void nvim_buf_set_keymap(uint64_t channel_id, Buffer buffer, String mode, String lhs, String rhs, + Dict(keymap) *opts, Error *err) FUNC_API_SINCE(6) { - modify_keymap(buffer, false, mode, lhs, rhs, opts, err); + modify_keymap(channel_id, buffer, false, mode, lhs, rhs, opts, err); } /// Unmaps a buffer-local |mapping| for the given mode. @@ -864,42 +963,11 @@ void nvim_buf_set_keymap(Buffer buffer, String mode, String lhs, String rhs, Dic /// @see |nvim_del_keymap()| /// /// @param buffer Buffer handle, or 0 for current buffer -void nvim_buf_del_keymap(Buffer buffer, String mode, String lhs, Error *err) +void nvim_buf_del_keymap(uint64_t channel_id, Buffer buffer, String mode, String lhs, Error *err) FUNC_API_SINCE(6) { String rhs = { .data = "", .size = 0 }; - modify_keymap(buffer, true, mode, lhs, rhs, NULL, err); -} - -/// Gets a map of buffer-local |user-commands|. -/// -/// @param buffer Buffer handle, or 0 for current buffer -/// @param opts Optional parameters. Currently not used. -/// @param[out] err Error details, if any. -/// -/// @returns Map of maps describing commands. -Dictionary nvim_buf_get_commands(Buffer buffer, Dict(get_commands) *opts, Error *err) - FUNC_API_SINCE(4) -{ - bool global = (buffer == -1); - bool builtin = api_object_to_bool(opts->builtin, "builtin", false, err); - if (ERROR_SET(err)) { - return (Dictionary)ARRAY_DICT_INIT; - } - - if (global) { - if (builtin) { - api_set_error(err, kErrorTypeValidation, "builtin=true not implemented"); - return (Dictionary)ARRAY_DICT_INIT; - } - return commands_array(NULL); - } - - buf_T *buf = find_buffer_by_handle(buffer, err); - if (builtin || !buf) { - return (Dictionary)ARRAY_DICT_INIT; - } - return commands_array(buf); + modify_keymap(channel_id, buffer, true, mode, lhs, rhs, NULL, err); } /// Sets a buffer-scoped (b:) variable @@ -937,45 +1005,6 @@ void nvim_buf_del_var(Buffer buffer, String name, Error *err) dict_set_var(buf->b_vars, name, NIL, true, false, err); } - -/// Gets a buffer option value -/// -/// @param buffer Buffer handle, or 0 for current buffer -/// @param name Option name -/// @param[out] err Error details, if any -/// @return Option value -Object nvim_buf_get_option(Buffer buffer, String name, Error *err) - FUNC_API_SINCE(1) -{ - buf_T *buf = find_buffer_by_handle(buffer, err); - - if (!buf) { - return (Object)OBJECT_INIT; - } - - return get_option_from(buf, SREQ_BUF, name, err); -} - -/// Sets a buffer option value. Passing 'nil' as value deletes the option (only -/// works if there's a global fallback) -/// -/// @param channel_id -/// @param buffer Buffer handle, or 0 for current buffer -/// @param name Option name -/// @param value Option value -/// @param[out] err Error details, if any -void nvim_buf_set_option(uint64_t channel_id, Buffer buffer, String name, Object value, Error *err) - FUNC_API_SINCE(1) -{ - buf_T *buf = find_buffer_by_handle(buffer, err); - - if (!buf) { - return; - } - - set_option_to(channel_id, buf, SREQ_BUF, name, value, err); -} - /// Gets the full file name for the buffer /// /// @param buffer Buffer handle, or 0 for current buffer @@ -1013,7 +1042,7 @@ void nvim_buf_set_name(Buffer buffer, String name, Error *err) // Using aucmd_*: autocommands will be executed by rename_buffer aco_save_T aco; aucmd_prepbuf(&aco, buf); - int ren_ret = rename_buffer((char_u *)name.data); + int ren_ret = rename_buffer(name.data); aucmd_restbuf(&aco); if (try_end(err)) { @@ -1127,17 +1156,17 @@ Boolean nvim_buf_del_mark(Buffer buffer, String name, Error *err) return res; } - pos_T *pos = getmark_buf(buf, *name.data, false); + fmark_T *fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, *name.data); - // pos point to NULL when there's no mark with name - if (pos == NULL) { + // 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); return res; } - // pos->lnum is 0 when the mark is not valid in the buffer, or is not set. - if (pos->lnum != 0) { + // 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) { // since the mark belongs to the buffer delete it. res = set_mark(buf, name, 0, 0, err); } @@ -1210,31 +1239,29 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) return rv; } - pos_T *posp; + fmark_T *fm; + pos_T pos; char mark = *name.data; - try_start(); - bufref_T save_buf; - switch_buffer(&save_buf, buf); - posp = getmark(mark, false); - restore_buffer(&save_buf); - - if (try_end(err)) { - return rv; - } - - if (posp == NULL) { + fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, mark); + if (fm == NULL) { api_set_error(err, kErrorTypeValidation, "Invalid mark name"); return rv; } + // (0, 0) uppercase/file mark set in another buffer. + if (fm->fnum != buf->handle) { + pos.lnum = 0; + pos.col = 0; + } else { + pos = fm->mark; + } - ADD(rv, INTEGER_OBJ(posp->lnum)); - ADD(rv, INTEGER_OBJ(posp->col)); + ADD(rv, INTEGER_OBJ(pos.lnum)); + ADD(rv, INTEGER_OBJ(pos.col)); return rv; } - /// call a function with buffer as temporary current buffer /// /// This temporarily switches current buffer to "buffer". @@ -1273,63 +1300,6 @@ Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err) return res; } -/// Create a new user command |user-commands| in the given buffer. -/// -/// @param buffer Buffer handle, or 0 for current buffer. -/// @param[out] err Error details, if any. -/// @see nvim_add_user_command -void nvim_buf_add_user_command(Buffer buffer, String name, Object command, - Dict(user_command) *opts, Error *err) - FUNC_API_SINCE(9) -{ - buf_T *target_buf = find_buffer_by_handle(buffer, err); - if (ERROR_SET(err)) { - return; - } - - buf_T *save_curbuf = curbuf; - curbuf = target_buf; - add_user_command(name, command, opts, UC_BUFFER, err); - curbuf = save_curbuf; -} - -/// Delete a buffer-local user-defined command. -/// -/// Only commands created with |:command-buffer| or -/// |nvim_buf_add_user_command()| can be deleted with this function. -/// -/// @param buffer Buffer handle, or 0 for current buffer. -/// @param name Name of the command to delete. -/// @param[out] err Error details, if any. -void nvim_buf_del_user_command(Buffer buffer, String name, Error *err) - FUNC_API_SINCE(9) -{ - garray_T *gap; - if (buffer == -1) { - gap = &ucmds; - } else { - buf_T *buf = find_buffer_by_handle(buffer, err); - gap = &buf->b_ucmds; - } - - for (int i = 0; i < gap->ga_len; i++) { - ucmd_T *cmd = USER_CMD_GA(gap, i); - if (!STRCMP(name.data, cmd->uc_name)) { - free_ucmd(cmd); - - gap->ga_len -= 1; - - if (i < gap->ga_len) { - memmove(cmd, cmd + 1, (size_t)(gap->ga_len - i) * sizeof(ucmd_T)); - } - - return; - } - } - - api_set_error(err, kErrorTypeException, "No such user-defined command: %s", name.data); -} - Dictionary nvim__buf_stats(Buffer buffer, Error *err) { Dictionary rv = ARRAY_DICT_INIT; @@ -1386,16 +1356,17 @@ static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra) } // Normalizes 0-based indexes to buffer line numbers -static int64_t normalize_index(buf_T *buf, int64_t index, bool *oob) +static int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bool *oob) { - int64_t line_count = buf->b_ml.ml_line_count; + 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 ? line_count + index +1 : index; + index = index < 0 ? max_index + index + 1 : index; // Check for oob - if (index > line_count) { + if (index > max_index) { *oob = true; - index = line_count; + index = max_index; } else if (index < 0) { *oob = true; index = 0; |