aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/buffer.c34
-rw-r--r--src/nvim/buffer_defs.h20
-rw-r--r--src/nvim/buffer_updates.c18
-rw-r--r--src/nvim/eval.c47
-rw-r--r--src/nvim/eval.lua3
-rw-r--r--src/nvim/fileio.c3
-rw-r--r--src/nvim/garray.c8
-rw-r--r--src/nvim/globals.h2
-rw-r--r--src/nvim/mbyte.c33
-rw-r--r--src/nvim/memline.c53
-rw-r--r--src/nvim/misc1.c3
-rw-r--r--src/nvim/os/env.c1
-rw-r--r--src/nvim/screen.c5
-rw-r--r--src/nvim/testdir/test_environ.vim44
-rw-r--r--src/nvim/version.c17
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