diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/api/buffer.c | 34 | ||||
-rw-r--r-- | src/nvim/buffer_defs.h | 20 | ||||
-rw-r--r-- | src/nvim/buffer_updates.c | 18 | ||||
-rw-r--r-- | src/nvim/eval.c | 47 | ||||
-rw-r--r-- | src/nvim/eval.lua | 3 | ||||
-rw-r--r-- | src/nvim/fileio.c | 3 | ||||
-rw-r--r-- | src/nvim/garray.c | 8 | ||||
-rw-r--r-- | src/nvim/globals.h | 2 | ||||
-rw-r--r-- | src/nvim/mbyte.c | 33 | ||||
-rw-r--r-- | src/nvim/memline.c | 53 | ||||
-rw-r--r-- | src/nvim/misc1.c | 3 | ||||
-rw-r--r-- | src/nvim/os/env.c | 1 | ||||
-rw-r--r-- | src/nvim/screen.c | 5 | ||||
-rw-r--r-- | src/nvim/testdir/test_environ.vim | 44 | ||||
-rw-r--r-- | src/nvim/version.c | 17 |
15 files changed, 273 insertions, 18 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index b0b65545ab..c6f82e9d85 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -109,9 +109,11 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) /// `nvim_buf_lines_event`. Otherwise, the first notification will be /// a `nvim_buf_changedtick_event`. Not used for lua callbacks. /// @param opts Optional parameters. -/// `on_lines`: lua callback received on change. +/// `on_lines`: lua callback received on change. /// `on_changedtick`: lua callback received on changedtick /// increment without text change. +/// `utf_sizes`: include UTF-32 and UTF-16 size of +/// the replaced region. /// See |api-buffer-updates-lua| for more information /// @param[out] err Error details, if any /// @return False when updates couldn't be enabled because the buffer isn't @@ -156,6 +158,12 @@ Boolean nvim_buf_attach(uint64_t channel_id, } cb.on_detach = v->data.luaref; v->data.integer = LUA_NOREF; + } else if (is_lua && strequal("utf_sizes", k.data)) { + if (v->type != kObjectTypeBoolean) { + api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean"); + goto error; + } + cb.utf_sizes = v->data.boolean; } else { api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); goto error; @@ -1176,6 +1184,30 @@ free_exit: return 0; } +Dictionary nvim__buf_stats(Buffer buffer, Error *err) +{ + Dictionary rv = ARRAY_DICT_INIT; + + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return rv; + } + + // Number of times the cached line was flushed. + // This should generally not increase while editing the same + // line in the same mode. + PUT(rv, "flush_count", INTEGER_OBJ(buf->flush_count)); + // lnum of current line + PUT(rv, "current_lnum", INTEGER_OBJ(buf->b_ml.ml_line_lnum)); + // whether the line has unflushed changes. + PUT(rv, "line_dirty", BOOLEAN_OBJ(buf->b_ml.ml_flags & ML_LINE_DIRTY)); + // NB: this should be zero at any time API functions are called, + // this exists to debug issues + PUT(rv, "dirty_bytes", INTEGER_OBJ((Integer)buf->deleted_bytes)); + + return rv; +} + // Check if deleting lines made the cursor position invalid. // Changed lines from `lo` to `hi`; added `extra` lines (negative if deleted). static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra) diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 143737b478..b11eaefdd0 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -459,8 +459,9 @@ typedef struct { LuaRef on_lines; LuaRef on_changedtick; LuaRef on_detach; + bool utf_sizes; } BufUpdateCallbacks; -#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF } +#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF, false } #define BUF_HAS_QF_ENTRY 1 #define BUF_HAS_LL_ENTRY 2 @@ -802,11 +803,26 @@ struct file_buffer { kvec_t(BufhlLine *) b_bufhl_move_space; // temporary space for highlights - // array of channelids which have asked to receive updates for this + // array of channel_id:s which have asked to receive updates for this // buffer. kvec_t(uint64_t) update_channels; + // array of lua callbacks for buffer updates. kvec_t(BufUpdateCallbacks) update_callbacks; + // whether an update callback has requested codepoint size of deleted regions. + bool update_need_codepoints; + + // Measurements of the deleted or replaced region since the last update + // event. Some consumers of buffer changes need to know the byte size (like + // tree-sitter) or the corresponding UTF-32/UTF-16 size (like LSP) of the + // deleted text. + size_t deleted_bytes; + size_t deleted_codepoints; + size_t deleted_codeunits; + + // The number for times the current line has been flushed in the memline. + int flush_count; + int b_diff_failed; // internal diff failed for this buffer }; diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c index 21efda9fd9..3604578b50 100644 --- a/src/nvim/buffer_updates.c +++ b/src/nvim/buffer_updates.c @@ -26,6 +26,9 @@ bool buf_updates_register(buf_T *buf, uint64_t channel_id, if (channel_id == LUA_INTERNAL_CALL) { kv_push(buf->update_callbacks, cb); + if (cb.utf_sizes) { + buf->update_need_codepoints = true; + } return true; } @@ -169,6 +172,10 @@ void buf_updates_send_changes(buf_T *buf, int64_t num_removed, bool send_tick) { + size_t deleted_codepoints, deleted_codeunits; + size_t deleted_bytes = ml_flush_deleted_bytes(buf, &deleted_codepoints, + &deleted_codeunits); + if (!buf_updates_active(buf)) { return; } @@ -231,8 +238,8 @@ void buf_updates_send_changes(buf_T *buf, bool keep = true; if (cb.on_lines != LUA_NOREF) { Array args = ARRAY_DICT_INIT; - Object items[5]; - args.size = 5; + Object items[8]; + args.size = 6; // may be increased to 8 below args.items = items; // the first argument is always the buffer handle @@ -250,6 +257,13 @@ void buf_updates_send_changes(buf_T *buf, // the last line in the updated range args.items[4] = INTEGER_OBJ(firstline - 1 + num_added); + // byte count of previous contents + args.items[5] = INTEGER_OBJ((Integer)deleted_bytes); + if (cb.utf_sizes) { + args.size = 8; + args.items[6] = INTEGER_OBJ((Integer)deleted_codepoints); + args.items[7] = INTEGER_OBJ((Integer)deleted_codeunits); + } textlock++; Object res = executor_exec_lua_cb(cb.on_lines, "lines", args, true); textlock--; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index d82a081c27..7ffa59f298 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -8495,6 +8495,25 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = n; } +/// "environ()" function +static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_alloc_ret(rettv); + + for (int i = 0; ; i++) { + // TODO(justinmk): use os_copyfullenv from #7202 ? + char *envname = os_getenvname_at_index((size_t)i); + if (envname == NULL) { + break; + } + const char *value = os_getenv(envname); + tv_dict_add_str(rettv->vval.v_dict, + (char *)envname, STRLEN((char *)envname), + value == NULL ? "" : value); + xfree(envname); + } +} + /* * "escape({string}, {chars})" function */ @@ -8508,6 +8527,20 @@ static void f_escape(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_STRING; } +/// "getenv()" function +static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char_u *p = (char_u *)vim_getenv(tv_get_string(&argvars[0])); + + if (p == NULL) { + rettv->v_type = VAR_SPECIAL; + rettv->vval.v_number = kSpecialVarNull; + return; + } + rettv->vval.v_string = p; + rettv->v_type = VAR_STRING; +} + /* * "eval()" function */ @@ -15319,6 +15352,20 @@ static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +/// "setenv()" function +static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char namebuf[NUMBUFLEN]; + char valbuf[NUMBUFLEN]; + const char *name = tv_get_string_buf(&argvars[0], namebuf); + + if (argvars[1].v_type == VAR_SPECIAL + && argvars[1].vval.v_number == kSpecialVarNull) { + os_unsetenv(name); + } else { + vim_setenv(name, tv_get_string_buf(&argvars[1], valbuf)); + } +} /// "setfperm({fname}, {mode})" function static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 0b77a24f7a..db45409e77 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -89,6 +89,7 @@ return { diff_filler={args=1}, diff_hlID={args=2}, empty={args=1}, + environ={}, escape={args=2}, eval={args=1}, eventhandler={}, @@ -135,6 +136,7 @@ return { getcompletion={args={2, 3}}, getcurpos={}, getcwd={args={0,2}}, + getenv={args={1}}, getfontname={args={0, 1}}, getfperm={args=1}, getfsize={args=1}, @@ -274,6 +276,7 @@ return { setbufvar={args=3}, setcharsearch={args=1}, setcmdpos={args=1}, + setenv={args=2}, setfperm={args=2}, setline={args=2}, setloclist={args={2, 4}}, diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index a164cf47d5..d03b9138d0 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -1755,6 +1755,9 @@ failed: ml_delete(curbuf->b_ml.ml_line_count, false); linecnt--; } + curbuf->deleted_bytes = 0; + curbuf->deleted_codepoints = 0; + curbuf->deleted_codeunits = 0; linecnt = curbuf->b_ml.ml_line_count - linecnt; if (filesize == 0) linecnt = 0; diff --git a/src/nvim/garray.c b/src/nvim/garray.c index 74fd9d89cb..1cfc2b6176 100644 --- a/src/nvim/garray.c +++ b/src/nvim/garray.c @@ -89,6 +89,14 @@ void ga_grow(garray_T *gap, int n) if (n < gap->ga_growsize) { n = gap->ga_growsize; } + + // A linear growth is very inefficient when the array grows big. This + // is a compromise between allocating memory that won't be used and too + // many copy operations. A factor of 1.5 seems reasonable. + if (n < gap->ga_len / 2) { + n = gap->ga_len / 2; + } + int new_maxlen = gap->ga_len + n; size_t new_size = (size_t)gap->ga_itemsize * (size_t)new_maxlen; diff --git a/src/nvim/globals.h b/src/nvim/globals.h index de6f59b3f1..4524c4b2c0 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -627,6 +627,8 @@ EXTERN pos_T Insstart_orig; EXTERN int orig_line_count INIT(= 0); /* Line count when "gR" started */ EXTERN int vr_lines_changed INIT(= 0); /* #Lines changed by "gR" so far */ +// increase around internal delete/replace +EXTERN int inhibit_delete_count INIT(= 0); /* * These flags are set based upon 'fileencoding'. diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index e7579399f3..bf8ce46113 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -1438,6 +1438,39 @@ int utf16_to_utf8(const wchar_t *strw, char **str) #endif +/// Measure the length of a string in corresponding UTF-32 and UTF-16 units. +/// +/// Invalid UTF-8 bytes, or embedded surrogates, count as one code point/unit +/// each. +/// +/// The out parameters are incremented. This is used to measure the size of +/// a buffer region consisting of multiple line segments. +/// +/// @param s the string +/// @param len maximum length (an earlier NUL terminates) +/// @param[out] codepoints incremented with UTF-32 code point size +/// @param[out] codeunits incremented with UTF-16 code unit size +void mb_utflen(const char_u *s, size_t len, size_t *codepoints, + size_t *codeunits) + FUNC_ATTR_NONNULL_ALL +{ + size_t count = 0, extra = 0; + size_t clen; + for (size_t i = 0; i < len && s[i] != NUL; i += clen) { + clen = utf_ptr2len_len(s+i, len-i); + // NB: gets the byte value of invalid sequence bytes. + // we only care whether the char fits in the BMP or not + int c = (clen > 1) ? utf_ptr2char(s+i) : s[i]; + count++; + if (c > 0xFFFF) { + extra++; + } + } + *codepoints += count; + *codeunits += count + extra; +} + + /* * Version of strnicmp() that handles multi-byte characters. * Needed for Big5, Shift-JIS and UTF-8 encoding. Other DBCS encodings can diff --git a/src/nvim/memline.c b/src/nvim/memline.c index b027459706..3220c7d9b8 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -2383,6 +2383,23 @@ static int ml_append_int( return OK; } +void ml_add_deleted_len(char_u *ptr, ssize_t len) +{ + if (inhibit_delete_count) { + return; + } + if (len == -1) { + len = STRLEN(ptr); + } + curbuf->deleted_bytes += len+1; + if (curbuf->update_need_codepoints) { + mb_utflen(ptr, len, &curbuf->deleted_codepoints, + &curbuf->deleted_codeunits); + curbuf->deleted_codepoints++; // NL char + curbuf->deleted_codeunits++; + } +} + /* * Replace line lnum, with buffering, in current buffer. * @@ -2403,13 +2420,24 @@ int ml_replace(linenr_T lnum, char_u *line, bool copy) if (curbuf->b_ml.ml_mfp == NULL && open_buffer(FALSE, NULL, 0) == FAIL) return FAIL; + bool readlen = true; + if (copy) { line = vim_strsave(line); } - if (curbuf->b_ml.ml_line_lnum != lnum) /* other line buffered */ - ml_flush_line(curbuf); /* flush it */ - else if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) /* same line allocated */ - xfree(curbuf->b_ml.ml_line_ptr); /* free it */ + if (curbuf->b_ml.ml_line_lnum != lnum) { // other line buffered + ml_flush_line(curbuf); // flush it + } else if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) { // same line allocated + ml_add_deleted_len(curbuf->b_ml.ml_line_ptr, -1); + readlen = false; // already added the length + + xfree(curbuf->b_ml.ml_line_ptr); // free it + } + + if (readlen && kv_size(curbuf->update_callbacks)) { + ml_add_deleted_len(ml_get_buf(curbuf, lnum, false), -1); + } + curbuf->b_ml.ml_line_ptr = line; curbuf->b_ml.ml_line_lnum = lnum; curbuf->b_ml.ml_flags = (curbuf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY; @@ -2491,6 +2519,10 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) else line_size = ((dp->db_index[idx - 1]) & DB_INDEX_MASK) - line_start; + // Line should always have an NL char internally (represented as NUL), + // even if 'noeol' is set. + assert(line_size >= 1); + ml_add_deleted_len((char_u *)dp + line_start, line_size-1); /* * special case: If there is only one line in the data block it becomes empty. @@ -2676,6 +2708,17 @@ void ml_clearmarked(void) return; } +size_t ml_flush_deleted_bytes(buf_T *buf, size_t *codepoints, size_t *codeunits) +{ + size_t ret = buf->deleted_bytes; + *codepoints = buf->deleted_codepoints; + *codeunits = buf->deleted_codeunits; + buf->deleted_bytes = 0; + buf->deleted_codepoints = 0; + buf->deleted_codeunits = 0; + return ret; +} + /* * flush ml_line if necessary */ @@ -2704,6 +2747,8 @@ static void ml_flush_line(buf_T *buf) return; entered = TRUE; + buf->flush_count++; + lnum = buf->b_ml.ml_line_lnum; new_line = buf->b_ml.ml_line_ptr; diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index db0d56b5fd..a62fa6d585 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -780,6 +780,7 @@ open_line ( did_append = FALSE; } + inhibit_delete_count++; if (newindent || did_si ) { @@ -821,6 +822,7 @@ open_line ( did_si = false; } } + inhibit_delete_count--; /* * In REPLACE mode, for each character in the extra leader, there must be @@ -1685,6 +1687,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) bool was_alloced = ml_line_alloced(); // check if oldp was allocated char_u *newp; if (was_alloced) { + ml_add_deleted_len(curbuf->b_ml.ml_line_ptr, oldlen); newp = oldp; // use same allocated memory } else { // need to allocate a new line newp = xmalloc((size_t)(oldlen + 1 - count)); diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 2278c325ea..bef78d8cc8 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -45,6 +45,7 @@ void env_init(void) } /// Like getenv(), but returns NULL if the variable is empty. +/// @see os_env_exists const char *os_getenv(const char *name) FUNC_ATTR_NONNULL_ALL { diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 06f886d411..fb019a4d2d 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -3143,6 +3143,7 @@ win_line ( c = '>'; mb_c = c; mb_l = 1; + (void)mb_l; multi_attr = win_hl_attr(wp, HLF_AT); // put the pointer back to output the double-width @@ -3153,9 +3154,9 @@ win_line ( n_extra -= mb_l - 1; p_extra += mb_l - 1; } - ++p_extra; + p_extra++; } - --n_extra; + n_extra--; } else { int c0; diff --git a/src/nvim/testdir/test_environ.vim b/src/nvim/testdir/test_environ.vim new file mode 100644 index 0000000000..094c4ce36f --- /dev/null +++ b/src/nvim/testdir/test_environ.vim @@ -0,0 +1,44 @@ +scriptencoding utf-8 + +func Test_environ() + unlet! $TESTENV + call assert_equal(0, has_key(environ(), 'TESTENV')) + let $TESTENV = 'foo' + call assert_equal(1, has_key(environ(), 'TESTENV')) + let $TESTENV = 'こんにちわ' + call assert_equal('こんにちわ', environ()['TESTENV']) +endfunc + +func Test_getenv() + unlet! $TESTENV + call assert_equal(v:null, getenv('TESTENV')) + let $TESTENV = 'foo' + call assert_equal('foo', getenv('TESTENV')) +endfunc + +func Test_setenv() + unlet! $TESTENV + call setenv('TEST ENV', 'foo') + call assert_equal('foo', getenv('TEST ENV')) + call setenv('TEST ENV', v:null) + call assert_equal(v:null, getenv('TEST ENV')) +endfunc + +func Test_external_env() + call setenv('FOO', 'HelloWorld') + if has('win32') + let result = system('echo %FOO%') + else + let result = system('echo $FOO') + endif + let result = substitute(result, '[ \r\n]', '', 'g') + call assert_equal('HelloWorld', result) + + call setenv('FOO', v:null) + if has('win32') + let result = system('set | grep ^FOO=') + else + let result = system('env | grep ^FOO=') + endif + call assert_equal('', result) +endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index 33b2a05cbb..e07865a410 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -2034,14 +2034,19 @@ static void version_msg(char *s) version_msg_wrap((char_u *)s, false); } -/// List all features aligned in columns, dictionary style. +/// List all features. +/// This does not use list_in_columns (as in Vim), because there are only a +/// few, and we do not start at a new line. static void list_features(void) { - list_in_columns((char_u **)features, -1, -1); - if (msg_col > 0) { - msg_putchar('\n'); + version_msg(_("\n\nFeatures: ")); + for (int i = 0; features[i] != NULL; i++) { + version_msg(features[i]); + if (features[i+1] != NULL) { + version_msg(" "); + } } - MSG_PUTS("See \":help feature-compile\"\n\n"); + version_msg("\nSee \":help feature-compile\"\n\n"); } /// List string items nicely aligned in columns. @@ -2150,8 +2155,6 @@ void list_version(void) } #endif // ifdef HAVE_PATHDEF - version_msg(_("\n\nFeatures: ")); - list_features(); #ifdef SYS_VIMRC_FILE |