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| |
59 | Integer 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 |
88 | String 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 |
123 | Boolean 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 | |
176 | error: |
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. |
193 | Boolean 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 |
220 | void 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 |
238 | void 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 |
258 | ArrayOf(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. |
286 | ArrayOf(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 | |
328 | end: |
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 |
357 | void 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 |
390 | void 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 = 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 | |
537 | end: |
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. |
561 | Integer 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 |
588 | Object 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. |
606 | Integer 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. |
625 | ArrayOf(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 |
642 | void 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 |
654 | void 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. |
669 | Dictionary 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 |
708 | void 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 |
725 | void 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`. |
749 | Object 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 |
768 | Object 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 |
786 | Object 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 |
806 | void 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 |
827 | Integer 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 |
846 | String 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 |
864 | void 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. |
895 | Boolean 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. |
911 | Boolean 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 |
929 | void 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 |
947 | ArrayOf(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 |
1017 | Integer 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 |
1064 | void 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 |
1097 | void 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 |
1135 | Integer 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 | |
1190 | free_exit: |
1191 | kv_destroy(virt_text); |
1192 | return 0; |
1193 | } |
1194 | |
1195 | Dictionary 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). |
1221 | static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T ) |
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 |
1240 | static 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 | |
1259 | static int64_t convert_index(int64_t index) |
1260 | { |
1261 | return index < 0 ? index - 1 : index; |
1262 | } |
1263 | |