1// This is an open source non-commercial project. Dear PVS-Studio, please check
2// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3
4// Much of this code was adapted from 'if_py_both.h' from the original
5// vim source
6#include <stdbool.h>
7#include <stdint.h>
8#include <stdlib.h>
9#include <limits.h>
10#include <lauxlib.h>
11
12#include "nvim/api/buffer.h"
13#include "nvim/api/private/helpers.h"
14#include "nvim/api/private/defs.h"
15#include "nvim/lua/executor.h"
16#include "nvim/vim.h"
17#include "nvim/buffer.h"
18#include "nvim/change.h"
19#include "nvim/charset.h"
20#include "nvim/cursor.h"
21#include "nvim/getchar.h"
22#include "nvim/memline.h"
23#include "nvim/memory.h"
24#include "nvim/misc1.h"
25#include "nvim/ex_cmds.h"
26#include "nvim/mark.h"
27#include "nvim/fileio.h"
28#include "nvim/move.h"
29#include "nvim/syntax.h"
30#include "nvim/window.h"
31#include "nvim/undo.h"
32#include "nvim/ex_docmd.h"
33#include "nvim/buffer_updates.h"
34
35#ifdef INCLUDE_GENERATED_DECLARATIONS
36# include "api/buffer.c.generated.h"
37#endif
38
39
40/// \defgroup api-buffer
41///
42/// Unloaded Buffers:~
43///
44/// Buffers may be unloaded by the |:bunload| command or the buffer's
45/// |'bufhidden'| option. When a buffer is unloaded its file contents are freed
46/// from memory and vim cannot operate on the buffer lines until it is reloaded
47/// (usually by opening the buffer again in a new window). API methods such as
48/// |nvim_buf_get_lines()| and |nvim_buf_line_count()| will be affected.
49///
50/// You can use |nvim_buf_is_loaded()| or |nvim_buf_line_count()| to check
51/// whether a buffer is loaded.
52
53
54/// Gets the buffer line count
55///
56/// @param buffer Buffer handle, or 0 for current buffer
57/// @param[out] err Error details, if any
58/// @return Line count, or 0 for unloaded buffer. |api-buffer|
59Integer nvim_buf_line_count(Buffer buffer, Error *err)
60 FUNC_API_SINCE(1)
61{
62 buf_T *buf = find_buffer_by_handle(buffer, err);
63
64 if (!buf) {
65 return 0;
66 }
67
68 // return sentinel value if the buffer isn't loaded
69 if (buf->b_ml.ml_mfp == NULL) {
70 return 0;
71 }
72
73 return buf->b_ml.ml_line_count;
74}
75
76/// Gets a buffer line
77///
78/// @deprecated use nvim_buf_get_lines instead.
79/// for positive indices (including 0) use
80/// "nvim_buf_get_lines(buffer, index, index+1, true)"
81/// for negative indices use
82/// "nvim_buf_get_lines(buffer, index-1, index, true)"
83///
84/// @param buffer Buffer handle
85/// @param index Line index
86/// @param[out] err Error details, if any
87/// @return Line string
88String buffer_get_line(Buffer buffer, Integer index, Error *err)
89{
90 String rv = { .size = 0 };
91
92 index = convert_index(index);
93 Array slice = nvim_buf_get_lines(0, buffer, index, index+1, true, err);
94
95 if (!ERROR_SET(err) && slice.size) {
96 rv = slice.items[0].data.string;
97 }
98
99 xfree(slice.items);
100
101 return rv;
102}
103
104/// Activates buffer-update events on a channel, or as lua callbacks.
105///
106/// @param channel_id
107/// @param buffer Buffer handle, or 0 for current buffer
108/// @param send_buffer Set to true if the initial notification should contain
109/// the whole buffer. If so, the first notification will be a
110/// `nvim_buf_lines_event`. Otherwise, the first notification will be
111/// a `nvim_buf_changedtick_event`. Not used for lua callbacks.
112/// @param opts Optional parameters.
113/// - `on_lines`: lua callback received on change.
114/// - `on_changedtick`: lua callback received on changedtick
115/// increment without text change.
116/// - `utf_sizes`: include UTF-32 and UTF-16 size of
117/// the replaced region.
118/// See |api-buffer-updates-lua| for more information
119/// @param[out] err Error details, if any
120/// @return False when updates couldn't be enabled because the buffer isn't
121/// loaded or `opts` contained an invalid key; otherwise True.
122/// TODO: LUA_API_NO_EVAL
123Boolean nvim_buf_attach(uint64_t channel_id,
124 Buffer buffer,
125 Boolean send_buffer,
126 DictionaryOf(LuaRef) opts,
127 Error *err)
128 FUNC_API_SINCE(4)
129{
130 buf_T *buf = find_buffer_by_handle(buffer, err);
131
132 if (!buf) {
133 return false;
134 }
135
136 bool is_lua = (channel_id == LUA_INTERNAL_CALL);
137 BufUpdateCallbacks cb = BUF_UPDATE_CALLBACKS_INIT;
138 for (size_t i = 0; i < opts.size; i++) {
139 String k = opts.items[i].key;
140 Object *v = &opts.items[i].value;
141 if (is_lua && strequal("on_lines", k.data)) {
142 if (v->type != kObjectTypeLuaRef) {
143 api_set_error(err, kErrorTypeValidation, "callback is not a function");
144 goto error;
145 }
146 cb.on_lines = v->data.luaref;
147 v->data.integer = LUA_NOREF;
148 } else if (is_lua && strequal("on_changedtick", k.data)) {
149 if (v->type != kObjectTypeLuaRef) {
150 api_set_error(err, kErrorTypeValidation, "callback is not a function");
151 goto error;
152 }
153 cb.on_changedtick = v->data.luaref;
154 v->data.integer = LUA_NOREF;
155 } else if (is_lua && strequal("on_detach", k.data)) {
156 if (v->type != kObjectTypeLuaRef) {
157 api_set_error(err, kErrorTypeValidation, "callback is not a function");
158 goto error;
159 }
160 cb.on_detach = v->data.luaref;
161 v->data.integer = LUA_NOREF;
162 } else if (is_lua && strequal("utf_sizes", k.data)) {
163 if (v->type != kObjectTypeBoolean) {
164 api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean");
165 goto error;
166 }
167 cb.utf_sizes = v->data.boolean;
168 } else {
169 api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
170 goto error;
171 }
172 }
173
174 return buf_updates_register(buf, channel_id, cb, send_buffer);
175
176error:
177 // TODO(bfredl): ASAN build should check that the ref table is empty?
178 executor_free_luaref(cb.on_lines);
179 executor_free_luaref(cb.on_changedtick);
180 executor_free_luaref(cb.on_detach);
181 return false;
182}
183
184/// Deactivates buffer-update events on the channel.
185///
186/// For Lua callbacks see |api-lua-detach|.
187///
188/// @param channel_id
189/// @param buffer Buffer handle, or 0 for current buffer
190/// @param[out] err Error details, if any
191/// @return False when updates couldn't be disabled because the buffer
192/// isn't loaded; otherwise True.
193Boolean nvim_buf_detach(uint64_t channel_id,
194 Buffer buffer,
195 Error *err)
196 FUNC_API_SINCE(4) FUNC_API_REMOTE_ONLY
197{
198 buf_T *buf = find_buffer_by_handle(buffer, err);
199
200 if (!buf) {
201 return false;
202 }
203
204 buf_updates_unregister(buf, channel_id);
205 return true;
206}
207
208/// Sets a buffer line
209///
210/// @deprecated use nvim_buf_set_lines instead.
211/// for positive indices use
212/// "nvim_buf_set_lines(buffer, index, index+1, true, [line])"
213/// for negative indices use
214/// "nvim_buf_set_lines(buffer, index-1, index, true, [line])"
215///
216/// @param buffer Buffer handle
217/// @param index Line index
218/// @param line Contents of the new line
219/// @param[out] err Error details, if any
220void buffer_set_line(Buffer buffer, Integer index, String line, Error *err)
221{
222 Object l = STRING_OBJ(line);
223 Array array = { .items = &l, .size = 1 };
224 index = convert_index(index);
225 nvim_buf_set_lines(0, buffer, index, index+1, true, array, err);
226}
227
228/// Deletes a buffer line
229///
230/// @deprecated use nvim_buf_set_lines instead.
231/// for positive indices use
232/// "nvim_buf_set_lines(buffer, index, index+1, true, [])"
233/// for negative indices use
234/// "nvim_buf_set_lines(buffer, index-1, index, true, [])"
235/// @param buffer buffer handle
236/// @param index line index
237/// @param[out] err Error details, if any
238void buffer_del_line(Buffer buffer, Integer index, Error *err)
239{
240 Array array = ARRAY_DICT_INIT;
241 index = convert_index(index);
242 nvim_buf_set_lines(0, buffer, index, index+1, true, array, err);
243}
244
245/// Retrieves a line range from the buffer
246///
247/// @deprecated use nvim_buf_get_lines(buffer, newstart, newend, false)
248/// where newstart = start + int(not include_start) - int(start < 0)
249/// newend = end + int(include_end) - int(end < 0)
250/// int(bool) = 1 if bool is true else 0
251/// @param buffer Buffer handle
252/// @param start First line index
253/// @param end Last line index
254/// @param include_start True if the slice includes the `start` parameter
255/// @param include_end True if the slice includes the `end` parameter
256/// @param[out] err Error details, if any
257/// @return Array of lines
258ArrayOf(String) buffer_get_line_slice(Buffer buffer,
259 Integer start,
260 Integer end,
261 Boolean include_start,
262 Boolean include_end,
263 Error *err)
264{
265 start = convert_index(start) + !include_start;
266 end = convert_index(end) + include_end;
267 return nvim_buf_get_lines(0, buffer, start , end, false, err);
268}
269
270/// Gets a line-range from the buffer.
271///
272/// Indexing is zero-based, end-exclusive. Negative indices are interpreted
273/// as length+1+index: -1 refers to the index past the end. So to get the
274/// last element use start=-2 and end=-1.
275///
276/// Out-of-bounds indices are clamped to the nearest valid value, unless
277/// `strict_indexing` is set.
278///
279/// @param channel_id
280/// @param buffer Buffer handle, or 0 for current buffer
281/// @param start First line index
282/// @param end Last line index (exclusive)
283/// @param strict_indexing Whether out-of-bounds should be an error.
284/// @param[out] err Error details, if any
285/// @return Array of lines, or empty array for unloaded buffer.
286ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id,
287 Buffer buffer,
288 Integer start,
289 Integer end,
290 Boolean strict_indexing,
291 Error *err)
292 FUNC_API_SINCE(1)
293{
294 Array rv = ARRAY_DICT_INIT;
295 buf_T *buf = find_buffer_by_handle(buffer, err);
296
297 if (!buf) {
298 return rv;
299 }
300
301 // return sentinel value if the buffer isn't loaded
302 if (buf->b_ml.ml_mfp == NULL) {
303 return rv;
304 }
305
306 bool oob = false;
307 start = normalize_index(buf, start, &oob);
308 end = normalize_index(buf, end, &oob);
309
310 if (strict_indexing && oob) {
311 api_set_error(err, kErrorTypeValidation, "Index out of bounds");
312 return rv;
313 }
314
315 if (start >= end) {
316 // Return 0-length array
317 return rv;
318 }
319
320 rv.size = (size_t)(end - start);
321 rv.items = xcalloc(sizeof(Object), rv.size);
322
323 if (!buf_collect_lines(buf, rv.size, start,
324 (channel_id != VIML_INTERNAL_CALL), &rv, err)) {
325 goto end;
326 }
327
328end:
329 if (ERROR_SET(err)) {
330 for (size_t i = 0; i < rv.size; i++) {
331 xfree(rv.items[i].data.string.data);
332 }
333
334 xfree(rv.items);
335 rv.items = NULL;
336 }
337
338 return rv;
339}
340
341
342/// Replaces a line range on the buffer
343///
344/// @deprecated use nvim_buf_set_lines(buffer, newstart, newend, false, lines)
345/// where newstart = start + int(not include_start) + int(start < 0)
346/// newend = end + int(include_end) + int(end < 0)
347/// int(bool) = 1 if bool is true else 0
348///
349/// @param buffer Buffer handle, or 0 for current buffer
350/// @param start First line index
351/// @param end Last line index
352/// @param include_start True if the slice includes the `start` parameter
353/// @param include_end True if the slice includes the `end` parameter
354/// @param replacement Array of lines to use as replacement (0-length
355// array will delete the line range)
356/// @param[out] err Error details, if any
357void buffer_set_line_slice(Buffer buffer,
358 Integer start,
359 Integer end,
360 Boolean include_start,
361 Boolean include_end,
362 ArrayOf(String) replacement,
363 Error *err)
364{
365 start = convert_index(start) + !include_start;
366 end = convert_index(end) + include_end;
367 nvim_buf_set_lines(0, buffer, start, end, false, replacement, err);
368}
369
370
371/// Sets (replaces) a line-range in the buffer.
372///
373/// Indexing is zero-based, end-exclusive. Negative indices are interpreted
374/// as length+1+index: -1 refers to the index past the end. So to change
375/// or delete the last element use start=-2 and end=-1.
376///
377/// To insert lines at a given index, set `start` and `end` to the same index.
378/// To delete a range of lines, set `replacement` to an empty array.
379///
380/// Out-of-bounds indices are clamped to the nearest valid value, unless
381/// `strict_indexing` is set.
382///
383/// @param channel_id
384/// @param buffer Buffer handle, or 0 for current buffer
385/// @param start First line index
386/// @param end Last line index (exclusive)
387/// @param strict_indexing Whether out-of-bounds should be an error.
388/// @param replacement Array of lines to use as replacement
389/// @param[out] err Error details, if any
390void nvim_buf_set_lines(uint64_t channel_id,
391 Buffer buffer,
392 Integer start,
393 Integer end,
394 Boolean strict_indexing,
395 ArrayOf(String) replacement,
396 Error *err)
397 FUNC_API_SINCE(1)
398{
399 buf_T *buf = find_buffer_by_handle(buffer, err);
400
401 if (!buf) {
402 return;
403 }
404
405 bool oob = false;
406 start = normalize_index(buf, start, &oob);
407 end = normalize_index(buf, end, &oob);
408
409 if (strict_indexing && oob) {
410 api_set_error(err, kErrorTypeValidation, "Index out of bounds");
411 return;
412 }
413
414
415 if (start > end) {
416 api_set_error(err,
417 kErrorTypeValidation,
418 "Argument \"start\" is higher than \"end\"");
419 return;
420 }
421
422 for (size_t i = 0; i < replacement.size; i++) {
423 if (replacement.items[i].type != kObjectTypeString) {
424 api_set_error(err,
425 kErrorTypeValidation,
426 "All items in the replacement array must be strings");
427 return;
428 }
429 // Disallow newlines in the middle of the line.
430 if (channel_id != VIML_INTERNAL_CALL) {
431 const String l = replacement.items[i].data.string;
432 if (memchr(l.data, NL, l.size)) {
433 api_set_error(err, kErrorTypeValidation,
434 "String cannot contain newlines");
435 return;
436 }
437 }
438 }
439
440 size_t new_len = replacement.size;
441 size_t old_len = (size_t)(end - start);
442 ptrdiff_t extra = 0; // lines added to text, can be negative
443 char **lines = (new_len != 0) ? xcalloc(new_len, sizeof(char *)) : NULL;
444
445 for (size_t i = 0; i < new_len; i++) {
446 const String l = replacement.items[i].data.string;
447
448 // Fill lines[i] with l's contents. Convert NULs to newlines as required by
449 // NL-used-for-NUL.
450 lines[i] = xmemdupz(l.data, l.size);
451 memchrsub(lines[i], NUL, NL, l.size);
452 }
453
454 try_start();
455 aco_save_T aco;
456 aucmd_prepbuf(&aco, (buf_T *)buf);
457
458 if (!MODIFIABLE(buf)) {
459 api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'");
460 goto end;
461 }
462
463 if (u_save((linenr_T)(start - 1), (linenr_T)end) == FAIL) {
464 api_set_error(err, kErrorTypeException, "Failed to save undo information");
465 goto end;
466 }
467
468 // If the size of the range is reducing (ie, new_len < old_len) we
469 // need to delete some old_len. We do this at the start, by
470 // repeatedly deleting line "start".
471 size_t to_delete = (new_len < old_len) ? (size_t)(old_len - new_len) : 0;
472 for (size_t i = 0; i < to_delete; i++) {
473 if (ml_delete((linenr_T)start, false) == FAIL) {
474 api_set_error(err, kErrorTypeException, "Failed to delete line");
475 goto end;
476 }
477 }
478
479 if (to_delete > 0) {
480 extra -= (ptrdiff_t)to_delete;
481 }
482
483 // For as long as possible, replace the existing old_len with the
484 // new old_len. This is a more efficient operation, as it requires
485 // less memory allocation and freeing.
486 size_t to_replace = old_len < new_len ? old_len : new_len;
487 for (size_t i = 0; i < to_replace; i++) {
488 int64_t lnum = start + (int64_t)i;
489
490 if (lnum >= MAXLNUM) {
491 api_set_error(err, kErrorTypeValidation, "Index value is too high");
492 goto end;
493 }
494
495 if (ml_replace((linenr_T)lnum, (char_u *)lines[i], false) == FAIL) {
496 api_set_error(err, kErrorTypeException, "Failed to replace line");
497 goto end;
498 }
499 // Mark lines that haven't been passed to the buffer as they need
500 // to be freed later
501 lines[i] = NULL;
502 }
503
504 // Now we may need to insert the remaining new old_len
505 for (size_t i = to_replace; i < new_len; i++) {
506 int64_t lnum = start + (int64_t)i - 1;
507
508 if (lnum >= MAXLNUM) {
509 api_set_error(err, kErrorTypeValidation, "Index value is too high");
510 goto end;
511 }
512
513 if (ml_append((linenr_T)lnum, (char_u *)lines[i], 0, false) == FAIL) {
514 api_set_error(err, kErrorTypeException, "Failed to insert line");
515 goto end;
516 }
517
518 // Same as with replacing, but we also need to free lines
519 xfree(lines[i]);
520 lines[i] = NULL;
521 extra++;
522 }
523
524 // Adjust marks. Invalidate any which lie in the
525 // changed range, and move any in the remainder of the buffer.
526 // Only adjust marks if we managed to switch to a window that holds
527 // the buffer, otherwise line numbers will be invalid.
528 mark_adjust((linenr_T)start,
529 (linenr_T)(end - 1),
530 MAXLNUM,
531 (long)extra,
532 false);
533
534 changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true);
535 fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra);
536
537end:
538 for (size_t i = 0; i < new_len; i++) {
539 xfree(lines[i]);
540 }
541
542 xfree(lines);
543 aucmd_restbuf(&aco);
544 try_end(err);
545}
546
547/// Returns the byte offset of a line (0-indexed). |api-indexing|
548///
549/// Line 1 (index=0) has offset 0. UTF-8 bytes are counted. EOL is one byte.
550/// 'fileformat' and 'fileencoding' are ignored. The line index just after the
551/// last line gives the total byte-count of the buffer. A final EOL byte is
552/// counted if it would be written, see 'eol'.
553///
554/// Unlike |line2byte()|, throws error for out-of-bounds indexing.
555/// Returns -1 for unloaded buffer.
556///
557/// @param buffer Buffer handle, or 0 for current buffer
558/// @param index Line index
559/// @param[out] err Error details, if any
560/// @return Integer byte offset, or -1 for unloaded buffer.
561Integer nvim_buf_get_offset(Buffer buffer, Integer index, Error *err)
562 FUNC_API_SINCE(5)
563{
564 buf_T *buf = find_buffer_by_handle(buffer, err);
565 if (!buf) {
566 return 0;
567 }
568
569 // return sentinel value if the buffer isn't loaded
570 if (buf->b_ml.ml_mfp == NULL) {
571 return -1;
572 }
573
574 if (index < 0 || index > buf->b_ml.ml_line_count) {
575 api_set_error(err, kErrorTypeValidation, "Index out of bounds");
576 return 0;
577 }
578
579 return ml_find_line_or_offset(buf, (int)index+1, NULL, true);
580}
581
582/// Gets a buffer-scoped (b:) variable.
583///
584/// @param buffer Buffer handle, or 0 for current buffer
585/// @param name Variable name
586/// @param[out] err Error details, if any
587/// @return Variable value
588Object nvim_buf_get_var(Buffer buffer, String name, Error *err)
589 FUNC_API_SINCE(1)
590{
591 buf_T *buf = find_buffer_by_handle(buffer, err);
592
593 if (!buf) {
594 return (Object) OBJECT_INIT;
595 }
596
597 return dict_get_value(buf->b_vars, name, err);
598}
599
600/// Gets a changed tick of a buffer
601///
602/// @param[in] buffer Buffer handle, or 0 for current buffer
603/// @param[out] err Error details, if any
604///
605/// @return `b:changedtick` value.
606Integer nvim_buf_get_changedtick(Buffer buffer, Error *err)
607 FUNC_API_SINCE(2)
608{
609 const buf_T *const buf = find_buffer_by_handle(buffer, err);
610
611 if (!buf) {
612 return -1;
613 }
614
615 return buf_get_changedtick(buf);
616}
617
618/// Gets a list of buffer-local |mapping| definitions.
619///
620/// @param mode Mode short-name ("n", "i", "v", ...)
621/// @param buffer Buffer handle, or 0 for current buffer
622/// @param[out] err Error details, if any
623/// @returns Array of maparg()-like dictionaries describing mappings.
624/// The "buffer" key holds the associated buffer handle.
625ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err)
626 FUNC_API_SINCE(3)
627{
628 buf_T *buf = find_buffer_by_handle(buffer, err);
629
630 if (!buf) {
631 return (Array)ARRAY_DICT_INIT;
632 }
633
634 return keymap_array(mode, buf);
635}
636
637/// Sets a buffer-local |mapping| for the given mode.
638///
639/// @see |nvim_set_keymap()|
640///
641/// @param buffer Buffer handle, or 0 for current buffer
642void nvim_buf_set_keymap(Buffer buffer, String mode, String lhs, String rhs,
643 Dictionary opts, Error *err)
644 FUNC_API_SINCE(6)
645{
646 modify_keymap(buffer, false, mode, lhs, rhs, opts, err);
647}
648
649/// Unmaps a buffer-local |mapping| for the given mode.
650///
651/// @see |nvim_del_keymap()|
652///
653/// @param buffer Buffer handle, or 0 for current buffer
654void nvim_buf_del_keymap(Buffer buffer, String mode, String lhs, Error *err)
655 FUNC_API_SINCE(6)
656{
657 String rhs = { .data = "", .size = 0 };
658 Dictionary opts = ARRAY_DICT_INIT;
659 modify_keymap(buffer, true, mode, lhs, rhs, opts, err);
660}
661
662/// Gets a map of buffer-local |user-commands|.
663///
664/// @param buffer Buffer handle, or 0 for current buffer
665/// @param opts Optional parameters. Currently not used.
666/// @param[out] err Error details, if any.
667///
668/// @returns Map of maps describing commands.
669Dictionary nvim_buf_get_commands(Buffer buffer, Dictionary opts, Error *err)
670 FUNC_API_SINCE(4)
671{
672 bool global = (buffer == -1);
673 bool builtin = false;
674
675 for (size_t i = 0; i < opts.size; i++) {
676 String k = opts.items[i].key;
677 Object v = opts.items[i].value;
678 if (!strequal("builtin", k.data)) {
679 api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
680 return (Dictionary)ARRAY_DICT_INIT;
681 }
682 if (strequal("builtin", k.data)) {
683 builtin = v.data.boolean;
684 }
685 }
686
687 if (global) {
688 if (builtin) {
689 api_set_error(err, kErrorTypeValidation, "builtin=true not implemented");
690 return (Dictionary)ARRAY_DICT_INIT;
691 }
692 return commands_array(NULL);
693 }
694
695 buf_T *buf = find_buffer_by_handle(buffer, err);
696 if (builtin || !buf) {
697 return (Dictionary)ARRAY_DICT_INIT;
698 }
699 return commands_array(buf);
700}
701
702/// Sets a buffer-scoped (b:) variable
703///
704/// @param buffer Buffer handle, or 0 for current buffer
705/// @param name Variable name
706/// @param value Variable value
707/// @param[out] err Error details, if any
708void nvim_buf_set_var(Buffer buffer, String name, Object value, Error *err)
709 FUNC_API_SINCE(1)
710{
711 buf_T *buf = find_buffer_by_handle(buffer, err);
712
713 if (!buf) {
714 return;
715 }
716
717 dict_set_var(buf->b_vars, name, value, false, false, err);
718}
719
720/// Removes a buffer-scoped (b:) variable
721///
722/// @param buffer Buffer handle, or 0 for current buffer
723/// @param name Variable name
724/// @param[out] err Error details, if any
725void nvim_buf_del_var(Buffer buffer, String name, Error *err)
726 FUNC_API_SINCE(1)
727{
728 buf_T *buf = find_buffer_by_handle(buffer, err);
729
730 if (!buf) {
731 return;
732 }
733
734 dict_set_var(buf->b_vars, name, NIL, true, false, err);
735}
736
737/// Sets a buffer-scoped (b:) variable
738///
739/// @deprecated
740///
741/// @param buffer Buffer handle, or 0 for current buffer
742/// @param name Variable name
743/// @param value Variable value
744/// @param[out] err Error details, if any
745/// @return Old value or nil if there was no previous value.
746///
747/// @warning It may return nil if there was no previous value
748/// or if previous value was `v:null`.
749Object buffer_set_var(Buffer buffer, String name, Object value, Error *err)
750{
751 buf_T *buf = find_buffer_by_handle(buffer, err);
752
753 if (!buf) {
754 return (Object) OBJECT_INIT;
755 }
756
757 return dict_set_var(buf->b_vars, name, value, false, true, err);
758}
759
760/// Removes a buffer-scoped (b:) variable
761///
762/// @deprecated
763///
764/// @param buffer Buffer handle, or 0 for current buffer
765/// @param name Variable name
766/// @param[out] err Error details, if any
767/// @return Old value
768Object buffer_del_var(Buffer buffer, String name, Error *err)
769{
770 buf_T *buf = find_buffer_by_handle(buffer, err);
771
772 if (!buf) {
773 return (Object) OBJECT_INIT;
774 }
775
776 return dict_set_var(buf->b_vars, name, NIL, true, true, err);
777}
778
779
780/// Gets a buffer option value
781///
782/// @param buffer Buffer handle, or 0 for current buffer
783/// @param name Option name
784/// @param[out] err Error details, if any
785/// @return Option value
786Object nvim_buf_get_option(Buffer buffer, String name, Error *err)
787 FUNC_API_SINCE(1)
788{
789 buf_T *buf = find_buffer_by_handle(buffer, err);
790
791 if (!buf) {
792 return (Object) OBJECT_INIT;
793 }
794
795 return get_option_from(buf, SREQ_BUF, name, err);
796}
797
798/// Sets a buffer option value. Passing 'nil' as value deletes the option (only
799/// works if there's a global fallback)
800///
801/// @param channel_id
802/// @param buffer Buffer handle, or 0 for current buffer
803/// @param name Option name
804/// @param value Option value
805/// @param[out] err Error details, if any
806void nvim_buf_set_option(uint64_t channel_id, Buffer buffer,
807 String name, Object value, Error *err)
808 FUNC_API_SINCE(1)
809{
810 buf_T *buf = find_buffer_by_handle(buffer, err);
811
812 if (!buf) {
813 return;
814 }
815
816 set_option_to(channel_id, buf, SREQ_BUF, name, value, err);
817}
818
819/// Gets the buffer number
820///
821/// @deprecated The buffer number now is equal to the object id,
822/// so there is no need to use this function.
823///
824/// @param buffer Buffer handle, or 0 for current buffer
825/// @param[out] err Error details, if any
826/// @return Buffer number
827Integer nvim_buf_get_number(Buffer buffer, Error *err)
828 FUNC_API_SINCE(1)
829 FUNC_API_DEPRECATED_SINCE(2)
830{
831 Integer rv = 0;
832 buf_T *buf = find_buffer_by_handle(buffer, err);
833
834 if (!buf) {
835 return rv;
836 }
837
838 return buf->b_fnum;
839}
840
841/// Gets the full file name for the buffer
842///
843/// @param buffer Buffer handle, or 0 for current buffer
844/// @param[out] err Error details, if any
845/// @return Buffer name
846String nvim_buf_get_name(Buffer buffer, Error *err)
847 FUNC_API_SINCE(1)
848{
849 String rv = STRING_INIT;
850 buf_T *buf = find_buffer_by_handle(buffer, err);
851
852 if (!buf || buf->b_ffname == NULL) {
853 return rv;
854 }
855
856 return cstr_to_string((char *)buf->b_ffname);
857}
858
859/// Sets the full file name for a buffer
860///
861/// @param buffer Buffer handle, or 0 for current buffer
862/// @param name Buffer name
863/// @param[out] err Error details, if any
864void nvim_buf_set_name(Buffer buffer, String name, Error *err)
865 FUNC_API_SINCE(1)
866{
867 buf_T *buf = find_buffer_by_handle(buffer, err);
868
869 if (!buf) {
870 return;
871 }
872
873 try_start();
874
875 // Using aucmd_*: autocommands will be executed by rename_buffer
876 aco_save_T aco;
877 aucmd_prepbuf(&aco, buf);
878 int ren_ret = rename_buffer((char_u *) name.data);
879 aucmd_restbuf(&aco);
880
881 if (try_end(err)) {
882 return;
883 }
884
885 if (ren_ret == FAIL) {
886 api_set_error(err, kErrorTypeException, "Failed to rename buffer");
887 }
888}
889
890/// Checks if a buffer is valid and loaded. See |api-buffer| for more info
891/// about unloaded buffers.
892///
893/// @param buffer Buffer handle, or 0 for current buffer
894/// @return true if the buffer is valid and loaded, false otherwise.
895Boolean nvim_buf_is_loaded(Buffer buffer)
896 FUNC_API_SINCE(5)
897{
898 Error stub = ERROR_INIT;
899 buf_T *buf = find_buffer_by_handle(buffer, &stub);
900 api_clear_error(&stub);
901 return buf && buf->b_ml.ml_mfp != NULL;
902}
903
904/// Checks if a buffer is valid.
905///
906/// @note Even if a buffer is valid it may have been unloaded. See |api-buffer|
907/// for more info about unloaded buffers.
908///
909/// @param buffer Buffer handle, or 0 for current buffer
910/// @return true if the buffer is valid, false otherwise.
911Boolean nvim_buf_is_valid(Buffer buffer)
912 FUNC_API_SINCE(1)
913{
914 Error stub = ERROR_INIT;
915 Boolean ret = find_buffer_by_handle(buffer, &stub) != NULL;
916 api_clear_error(&stub);
917 return ret;
918}
919
920/// Inserts a sequence of lines to a buffer at a certain index
921///
922/// @deprecated use nvim_buf_set_lines(buffer, lnum, lnum, true, lines)
923///
924/// @param buffer Buffer handle
925/// @param lnum Insert the lines after `lnum`. If negative, appends to
926/// the end of the buffer.
927/// @param lines Array of lines
928/// @param[out] err Error details, if any
929void buffer_insert(Buffer buffer,
930 Integer lnum,
931 ArrayOf(String) lines,
932 Error *err)
933{
934 // "lnum" will be the index of the line after inserting,
935 // no matter if it is negative or not
936 nvim_buf_set_lines(0, buffer, lnum, lnum, true, lines, err);
937}
938
939/// Return a tuple (row,col) representing the position of the named mark.
940///
941/// Marks are (1,0)-indexed. |api-indexing|
942///
943/// @param buffer Buffer handle, or 0 for current buffer
944/// @param name Mark name
945/// @param[out] err Error details, if any
946/// @return (row, col) tuple
947ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
948 FUNC_API_SINCE(1)
949{
950 Array rv = ARRAY_DICT_INIT;
951 buf_T *buf = find_buffer_by_handle(buffer, err);
952
953 if (!buf) {
954 return rv;
955 }
956
957 if (name.size != 1) {
958 api_set_error(err, kErrorTypeValidation,
959 "Mark name must be a single character");
960 return rv;
961 }
962
963 pos_T *posp;
964 char mark = *name.data;
965
966 try_start();
967 bufref_T save_buf;
968 switch_buffer(&save_buf, buf);
969 posp = getmark(mark, false);
970 restore_buffer(&save_buf);
971
972 if (try_end(err)) {
973 return rv;
974 }
975
976 if (posp == NULL) {
977 api_set_error(err, kErrorTypeValidation, "Invalid mark name");
978 return rv;
979 }
980
981 ADD(rv, INTEGER_OBJ(posp->lnum));
982 ADD(rv, INTEGER_OBJ(posp->col));
983
984 return rv;
985}
986
987/// Adds a highlight to buffer.
988///
989/// Useful for plugins that dynamically generate highlights to a buffer
990/// (like a semantic highlighter or linter). The function adds a single
991/// highlight to a buffer. Unlike |matchaddpos()| highlights follow changes to
992/// line numbering (as lines are inserted/removed above the highlighted line),
993/// like signs and marks do.
994///
995/// Namespaces are used for batch deletion/updating of a set of highlights. To
996/// create a namespace, use |nvim_create_namespace| which returns a namespace
997/// id. Pass it in to this function as `ns_id` to add highlights to the
998/// namespace. All highlights in the same namespace can then be cleared with
999/// single call to |nvim_buf_clear_namespace|. If the highlight never will be
1000/// deleted by an API call, pass `ns_id = -1`.
1001///
1002/// As a shorthand, `ns_id = 0` can be used to create a new namespace for the
1003/// highlight, the allocated id is then returned. If `hl_group` is the empty
1004/// string no highlight is added, but a new `ns_id` is still returned. This is
1005/// supported for backwards compatibility, new code should use
1006/// |nvim_create_namespace| to create a new empty namespace.
1007///
1008/// @param buffer Buffer handle, or 0 for current buffer
1009/// @param ns_id namespace to use or -1 for ungrouped highlight
1010/// @param hl_group Name of the highlight group to use
1011/// @param line Line to highlight (zero-indexed)
1012/// @param col_start Start of (byte-indexed) column range to highlight
1013/// @param col_end End of (byte-indexed) column range to highlight,
1014/// or -1 to highlight to end of line
1015/// @param[out] err Error details, if any
1016/// @return The ns_id that was used
1017Integer nvim_buf_add_highlight(Buffer buffer,
1018 Integer ns_id,
1019 String hl_group,
1020 Integer line,
1021 Integer col_start,
1022 Integer col_end,
1023 Error *err)
1024 FUNC_API_SINCE(1)
1025{
1026 buf_T *buf = find_buffer_by_handle(buffer, err);
1027 if (!buf) {
1028 return 0;
1029 }
1030
1031 if (line < 0 || line >= MAXLNUM) {
1032 api_set_error(err, kErrorTypeValidation, "Line number outside range");
1033 return 0;
1034 }
1035 if (col_start < 0 || col_start > MAXCOL) {
1036 api_set_error(err, kErrorTypeValidation, "Column value outside range");
1037 return 0;
1038 }
1039 if (col_end < 0 || col_end > MAXCOL) {
1040 col_end = MAXCOL;
1041 }
1042
1043 int hlg_id = 0;
1044 if (hl_group.size > 0) {
1045 hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size);
1046 }
1047
1048 ns_id = bufhl_add_hl(buf, (int)ns_id, hlg_id, (linenr_T)line+1,
1049 (colnr_T)col_start+1, (colnr_T)col_end);
1050 return ns_id;
1051}
1052
1053/// Clears namespaced objects, highlights and virtual text, from a line range
1054///
1055/// Lines are 0-indexed. |api-indexing| To clear the namespace in the entire
1056/// buffer, specify line_start=0 and line_end=-1.
1057///
1058/// @param buffer Buffer handle, or 0 for current buffer
1059/// @param ns_id Namespace to clear, or -1 to clear all namespaces.
1060/// @param line_start Start of range of lines to clear
1061/// @param line_end End of range of lines to clear (exclusive) or -1 to clear
1062/// to end of buffer.
1063/// @param[out] err Error details, if any
1064void nvim_buf_clear_namespace(Buffer buffer,
1065 Integer ns_id,
1066 Integer line_start,
1067 Integer line_end,
1068 Error *err)
1069 FUNC_API_SINCE(5)
1070{
1071 buf_T *buf = find_buffer_by_handle(buffer, err);
1072 if (!buf) {
1073 return;
1074 }
1075
1076 if (line_start < 0 || line_start >= MAXLNUM) {
1077 api_set_error(err, kErrorTypeValidation, "Line number outside range");
1078 return;
1079 }
1080 if (line_end < 0 || line_end > MAXLNUM) {
1081 line_end = MAXLNUM;
1082 }
1083
1084 bufhl_clear_line_range(buf, (int)ns_id, (int)line_start+1, (int)line_end);
1085}
1086
1087/// Clears highlights and virtual text from namespace and range of lines
1088///
1089/// @deprecated use |nvim_buf_clear_namespace|.
1090///
1091/// @param buffer Buffer handle, or 0 for current buffer
1092/// @param ns_id Namespace to clear, or -1 to clear all.
1093/// @param line_start Start of range of lines to clear
1094/// @param line_end End of range of lines to clear (exclusive) or -1 to clear
1095/// to end of file.
1096/// @param[out] err Error details, if any
1097void nvim_buf_clear_highlight(Buffer buffer,
1098 Integer ns_id,
1099 Integer line_start,
1100 Integer line_end,
1101 Error *err)
1102 FUNC_API_SINCE(1)
1103{
1104 nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end, err);
1105}
1106
1107
1108/// Set the virtual text (annotation) for a buffer line.
1109///
1110/// By default (and currently the only option) the text will be placed after
1111/// the buffer text. Virtual text will never cause reflow, rather virtual
1112/// text will be truncated at the end of the screen line. The virtual text will
1113/// begin one cell (|lcs-eol| or space) after the ordinary text.
1114///
1115/// Namespaces are used to support batch deletion/updating of virtual text.
1116/// To create a namespace, use |nvim_create_namespace|. Virtual text is
1117/// cleared using |nvim_buf_clear_namespace|. The same `ns_id` can be used for
1118/// both virtual text and highlights added by |nvim_buf_add_highlight|, both
1119/// can then be cleared with a single call to |nvim_buf_clear_namespace|. If the
1120/// virtual text never will be cleared by an API call, pass `ns_id = -1`.
1121///
1122/// As a shorthand, `ns_id = 0` can be used to create a new namespace for the
1123/// virtual text, the allocated id is then returned.
1124///
1125/// @param buffer Buffer handle, or 0 for current buffer
1126/// @param ns_id Namespace to use or 0 to create a namespace,
1127/// or -1 for a ungrouped annotation
1128/// @param line Line to annotate with virtual text (zero-indexed)
1129/// @param chunks A list of [text, hl_group] arrays, each representing a
1130/// text chunk with specified highlight. `hl_group` element
1131/// can be omitted for no highlight.
1132/// @param opts Optional parameters. Currently not used.
1133/// @param[out] err Error details, if any
1134/// @return The ns_id that was used
1135Integer nvim_buf_set_virtual_text(Buffer buffer,
1136 Integer ns_id,
1137 Integer line,
1138 Array chunks,
1139 Dictionary opts,
1140 Error *err)
1141 FUNC_API_SINCE(5)
1142{
1143 buf_T *buf = find_buffer_by_handle(buffer, err);
1144 if (!buf) {
1145 return 0;
1146 }
1147
1148 if (line < 0 || line >= MAXLNUM) {
1149 api_set_error(err, kErrorTypeValidation, "Line number outside range");
1150 return 0;
1151 }
1152
1153 if (opts.size > 0) {
1154 api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
1155 return 0;
1156 }
1157
1158 VirtText virt_text = KV_INITIAL_VALUE;
1159 for (size_t i = 0; i < chunks.size; i++) {
1160 if (chunks.items[i].type != kObjectTypeArray) {
1161 api_set_error(err, kErrorTypeValidation, "Chunk is not an array");
1162 goto free_exit;
1163 }
1164 Array chunk = chunks.items[i].data.array;
1165 if (chunk.size == 0 || chunk.size > 2
1166 || chunk.items[0].type != kObjectTypeString
1167 || (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) {
1168 api_set_error(err, kErrorTypeValidation,
1169 "Chunk is not an array with one or two strings");
1170 goto free_exit;
1171 }
1172
1173 String str = chunk.items[0].data.string;
1174 char *text = transstr(str.size > 0 ? str.data : ""); // allocates
1175
1176 int hl_id = 0;
1177 if (chunk.size == 2) {
1178 String hl = chunk.items[1].data.string;
1179 if (hl.size > 0) {
1180 hl_id = syn_check_group((char_u *)hl.data, (int)hl.size);
1181 }
1182 }
1183 kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
1184 }
1185
1186 ns_id = bufhl_add_virt_text(buf, (int)ns_id, (linenr_T)line+1,
1187 virt_text);
1188 return ns_id;
1189
1190free_exit:
1191 kv_destroy(virt_text);
1192 return 0;
1193}
1194
1195Dictionary nvim__buf_stats(Buffer buffer, Error *err)
1196{
1197 Dictionary rv = ARRAY_DICT_INIT;
1198
1199 buf_T *buf = find_buffer_by_handle(buffer, err);
1200 if (!buf) {
1201 return rv;
1202 }
1203
1204 // Number of times the cached line was flushed.
1205 // This should generally not increase while editing the same
1206 // line in the same mode.
1207 PUT(rv, "flush_count", INTEGER_OBJ(buf->flush_count));
1208 // lnum of current line
1209 PUT(rv, "current_lnum", INTEGER_OBJ(buf->b_ml.ml_line_lnum));
1210 // whether the line has unflushed changes.
1211 PUT(rv, "line_dirty", BOOLEAN_OBJ(buf->b_ml.ml_flags & ML_LINE_DIRTY));
1212 // NB: this should be zero at any time API functions are called,
1213 // this exists to debug issues
1214 PUT(rv, "dirty_bytes", INTEGER_OBJ((Integer)buf->deleted_bytes));
1215
1216 return rv;
1217}
1218
1219// Check if deleting lines made the cursor position invalid.
1220// Changed lines from `lo` to `hi`; added `extra` lines (negative if deleted).
1221static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra)
1222{
1223 if (curwin->w_cursor.lnum >= lo) {
1224 // Adjust cursor position if it's in/after the changed lines.
1225 if (curwin->w_cursor.lnum >= hi) {
1226 curwin->w_cursor.lnum += extra;
1227 check_cursor_col();
1228 } else if (extra < 0) {
1229 curwin->w_cursor.lnum = lo;
1230 check_cursor();
1231 } else {
1232 check_cursor_col();
1233 }
1234 changed_cline_bef_curs();
1235 }
1236 invalidate_botline();
1237}
1238
1239// Normalizes 0-based indexes to buffer line numbers
1240static int64_t normalize_index(buf_T *buf, int64_t index, bool *oob)
1241{
1242 int64_t line_count = buf->b_ml.ml_line_count;
1243 // Fix if < 0
1244 index = index < 0 ? line_count + index +1 : index;
1245
1246 // Check for oob
1247 if (index > line_count) {
1248 *oob = true;
1249 index = line_count;
1250 } else if (index < 0) {
1251 *oob = true;
1252 index = 0;
1253 }
1254 // Convert the index to a vim line number
1255 index++;
1256 return index;
1257}
1258
1259static int64_t convert_index(int64_t index)
1260{
1261 return index < 0 ? index - 1 : index;
1262}
1263