aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/buffer.c20
-rw-r--r--src/nvim/api/vim.c6
-rw-r--r--src/nvim/edit.c27
-rw-r--r--src/nvim/eval.c132
-rw-r--r--src/nvim/ex_cmds.c4
-rw-r--r--src/nvim/ex_cmds2.c8
-rw-r--r--src/nvim/ex_docmd.c42
-rw-r--r--src/nvim/ex_getln.c4
-rw-r--r--src/nvim/fold.c5
-rw-r--r--src/nvim/garray.c5
-rw-r--r--src/nvim/garray.h26
-rw-r--r--src/nvim/getchar.c7
-rw-r--r--src/nvim/globals.h4
-rw-r--r--src/nvim/main.c36
-rw-r--r--src/nvim/memory.c37
-rw-r--r--src/nvim/menu.c15
-rw-r--r--src/nvim/message.c13
-rw-r--r--src/nvim/mouse.c11
-rw-r--r--src/nvim/msgpack_rpc/channel.c25
-rw-r--r--src/nvim/msgpack_rpc/defs.h4
-rw-r--r--src/nvim/msgpack_rpc/remote_ui.c280
-rw-r--r--src/nvim/msgpack_rpc/remote_ui.h9
-rw-r--r--src/nvim/normal.c42
-rw-r--r--src/nvim/ops.c485
-rw-r--r--src/nvim/ops.h7
-rw-r--r--src/nvim/option.c13
-rw-r--r--src/nvim/option_defs.h8
-rw-r--r--src/nvim/os/input.c113
-rw-r--r--src/nvim/os/shell.c52
-rw-r--r--src/nvim/os/signal.c2
-rw-r--r--src/nvim/os_unix.c1
-rw-r--r--src/nvim/screen.c43
-rw-r--r--src/nvim/spell.c61
-rw-r--r--src/nvim/strings.c10
-rw-r--r--src/nvim/syntax.c306
-rw-r--r--src/nvim/syntax.h2
-rw-r--r--src/nvim/syntax_defs.h3
-rw-r--r--src/nvim/term.c131
-rw-r--r--src/nvim/testdir/dotest.in2
-rw-r--r--src/nvim/testdir/test14.in6
-rw-r--r--src/nvim/testdir/test17.in6
-rw-r--r--src/nvim/testdir/test40.in1
-rw-r--r--src/nvim/testdir/test48.in2
-rw-r--r--src/nvim/testdir/test60.in1
-rw-r--r--src/nvim/testdir/test68.in4
-rw-r--r--src/nvim/testdir/test69.in8
-rw-r--r--src/nvim/testdir/test90.in2
-rw-r--r--src/nvim/testdir/test_breakindent.in1
-rw-r--r--src/nvim/testdir/test_breakindent.ok8
-rw-r--r--src/nvim/testdir/test_eval.in147
-rw-r--r--src/nvim/testdir/test_eval.okbin432 -> 10578 bytes
-rw-r--r--src/nvim/testdir/test_listlbr.in1
-rw-r--r--src/nvim/testdir/test_listlbr_utf8.in1
-rw-r--r--src/nvim/ui.c445
-rw-r--r--src/nvim/ui.h32
-rw-r--r--src/nvim/version.c6
56 files changed, 2073 insertions, 599 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 982003a31a..0292e82038 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -132,7 +132,12 @@ ArrayOf(String) buffer_get_line_slice(Buffer buffer,
}
const char *bufstr = (char *) ml_get_buf(buf, (linenr_T) lnum, false);
- rv.items[i] = STRING_OBJ(cstr_to_string(bufstr));
+ Object str = STRING_OBJ(cstr_to_string(bufstr));
+
+ // Vim represents NULs as NLs, but this may confuse clients.
+ strchrsub(str.data.string.data, '\n', '\0');
+
+ rv.items[i] = str;
}
end:
@@ -201,7 +206,18 @@ void buffer_set_line_slice(Buffer buffer,
}
String l = replacement.items[i].data.string;
- lines[i] = xmemdupz(l.data, l.size);
+
+ // Fill lines[i] with l's contents. Disallow newlines in the middle of a
+ // line and convert NULs to newlines to avoid truncation.
+ lines[i] = xmallocz(l.size);
+ for (size_t j = 0; j < l.size; j++) {
+ if (l.data[j] == '\n') {
+ api_set_error(err, Exception, _("string cannot contain newlines"));
+ new_len = i + 1;
+ goto end;
+ }
+ lines[i][j] = (char) (l.data[j] == '\0' ? '\n' : l.data[j]);
+ }
}
try_start();
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index fe5fa6274b..eab79d970e 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -21,6 +21,7 @@
#include "nvim/message.h"
#include "nvim/eval.h"
#include "nvim/misc2.h"
+#include "nvim/syntax.h"
#include "nvim/term.h"
#include "nvim/getchar.h"
#include "nvim/os/input.h"
@@ -546,6 +547,11 @@ void vim_unsubscribe(uint64_t channel_id, String event)
channel_unsubscribe(channel_id, e);
}
+Integer vim_name_to_color(String name)
+{
+ return name_to_color((uint8_t *)name.data);
+}
+
Array vim_get_api_info(uint64_t channel_id)
{
Array rv = ARRAY_DICT_INIT;
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index c7f20783a9..d5ef84ff7b 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -3439,6 +3439,7 @@ static int ins_compl_get_exp(pos_T *ini)
int dict_f = 0;
compl_T *old_match;
int set_match_pos;
+ int l_ctrl_x_mode = ctrl_x_mode;
if (!compl_started) {
FOR_ALL_BUFFERS(buf) {
@@ -3458,10 +3459,12 @@ static int ins_compl_get_exp(pos_T *ini)
found_new_match = FAIL;
set_match_pos = FALSE;
+ assert(l_ctrl_x_mode == ctrl_x_mode);
+
/* For ^N/^P pick a new entry from e_cpt if compl_started is off,
* or if found_all says this entry is done. For ^X^L only use the
* entries from 'complete' that look in loaded buffers. */
- if ((ctrl_x_mode == 0 || ctrl_x_mode == CTRL_X_WHOLE_LINE)
+ if ((l_ctrl_x_mode == 0 || l_ctrl_x_mode == CTRL_X_WHOLE_LINE)
&& (!compl_started || found_all)) {
found_all = FALSE;
while (*e_cpt == ',' || *e_cpt == ' ')
@@ -3470,7 +3473,7 @@ static int ins_compl_get_exp(pos_T *ini)
ins_buf = curbuf;
first_match_pos = *ini;
/* So that ^N can match word immediately after cursor */
- if (ctrl_x_mode == 0)
+ if (l_ctrl_x_mode == 0)
dec(&first_match_pos);
last_match_pos = first_match_pos;
type = 0;
@@ -3506,7 +3509,7 @@ static int ins_compl_get_exp(pos_T *ini)
} else if (*e_cpt == NUL)
break;
else {
- if (ctrl_x_mode == CTRL_X_WHOLE_LINE)
+ if (l_ctrl_x_mode == CTRL_X_WHOLE_LINE)
type = -1;
else if (*e_cpt == 'k' || *e_cpt == 's') {
if (*e_cpt == 'k')
@@ -3576,7 +3579,7 @@ static int ins_compl_get_exp(pos_T *ini)
* of matches is found when compl_pattern is empty */
if (find_tags(compl_pattern, &num_matches, &matches,
TAG_REGEXP | TAG_NAMES | TAG_NOIC |
- TAG_INS_COMP | (ctrl_x_mode ? TAG_VERBOSE : 0),
+ TAG_INS_COMP | (l_ctrl_x_mode ? TAG_VERBOSE : 0),
TAG_MANY, curbuf->b_ffname) == OK && num_matches > 0) {
ins_compl_add_matches(num_matches, matches, p_ic);
}
@@ -3635,9 +3638,9 @@ static int ins_compl_get_exp(pos_T *ini)
++msg_silent; /* Don't want messages for wrapscan. */
- /* ctrl_x_mode == CTRL_X_WHOLE_LINE || word-wise search that
+ /* l_ctrl_x_mode == CTRL_X_WHOLE_LINE || word-wise search that
* has added a word that was at the beginning of the line */
- if ( ctrl_x_mode == CTRL_X_WHOLE_LINE
+ if ( l_ctrl_x_mode == CTRL_X_WHOLE_LINE
|| (compl_cont_status & CONT_SOL))
found_new_match = search_for_exact_line(ins_buf, pos,
compl_direction, compl_pattern);
@@ -3668,7 +3671,7 @@ static int ins_compl_get_exp(pos_T *ini)
&& ini->col == pos->col)
continue;
ptr = ml_get_buf(ins_buf, pos->lnum, FALSE) + pos->col;
- if (ctrl_x_mode == CTRL_X_WHOLE_LINE) {
+ if (l_ctrl_x_mode == CTRL_X_WHOLE_LINE) {
if (compl_cont_status & CONT_ADDING) {
if (pos->lnum >= ins_buf->b_ml.ml_line_count)
continue;
@@ -3751,8 +3754,8 @@ static int ins_compl_get_exp(pos_T *ini)
found_new_match = OK;
/* break the loop for specialized modes (use 'complete' just for the
- * generic ctrl_x_mode == 0) or when we've found a new match */
- if ((ctrl_x_mode != 0 && ctrl_x_mode != CTRL_X_WHOLE_LINE)
+ * generic l_ctrl_x_mode == 0) or when we've found a new match */
+ if ((l_ctrl_x_mode != 0 && l_ctrl_x_mode != CTRL_X_WHOLE_LINE)
|| found_new_match != FAIL) {
if (got_int)
break;
@@ -3760,7 +3763,7 @@ static int ins_compl_get_exp(pos_T *ini)
if (type != -1)
ins_compl_check_keys(0);
- if ((ctrl_x_mode != 0 && ctrl_x_mode != CTRL_X_WHOLE_LINE)
+ if ((l_ctrl_x_mode != 0 && l_ctrl_x_mode != CTRL_X_WHOLE_LINE)
|| compl_interrupted)
break;
compl_started = TRUE;
@@ -3776,13 +3779,13 @@ static int ins_compl_get_exp(pos_T *ini)
}
compl_started = TRUE;
- if ((ctrl_x_mode == 0 || ctrl_x_mode == CTRL_X_WHOLE_LINE)
+ if ((l_ctrl_x_mode == 0 || l_ctrl_x_mode == CTRL_X_WHOLE_LINE)
&& *e_cpt == NUL) /* Got to end of 'complete' */
found_new_match = FAIL;
i = -1; /* total of matches, unknown */
if (found_new_match == FAIL
- || (ctrl_x_mode != 0 && ctrl_x_mode != CTRL_X_WHOLE_LINE))
+ || (l_ctrl_x_mode != 0 && l_ctrl_x_mode != CTRL_X_WHOLE_LINE))
i = ins_compl_make_cyclic();
/* If several matches were added (FORWARD) or the search failed and has
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index be69bdbe61..45ab901398 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -1835,7 +1835,7 @@ ex_let_one (
p = get_tv_string_chk(tv);
if (p != NULL && op != NULL && *op == '.') {
- s = get_reg_contents(*arg == '@' ? '"' : *arg, TRUE, TRUE);
+ s = get_reg_contents(*arg == '@' ? '"' : *arg, kGRegExprSrc);
if (s != NULL) {
p = ptofree = concat_str(s, p);
free(s);
@@ -4076,7 +4076,7 @@ eval7 (
case '@': ++*arg;
if (evaluate) {
rettv->v_type = VAR_STRING;
- rettv->vval.v_string = get_reg_contents(**arg, TRUE, TRUE);
+ rettv->vval.v_string = get_reg_contents(**arg, kGRegExprSrc);
}
if (**arg != NUL)
++*arg;
@@ -5119,6 +5119,20 @@ void list_append_tv(list_T *l, typval_T *tv)
}
/*
+ * Add a list to a list.
+ */
+void list_append_list(list_T *list, list_T *itemlist)
+{
+ listitem_T *li = listitem_alloc();
+
+ li->li_tv.v_type = VAR_LIST;
+ li->li_tv.v_lock = 0;
+ li->li_tv.vval.v_list = itemlist;
+ list_append(list, li);
+ ++list->lv_refcount;
+}
+
+/*
* Add a dictionary to a list. Used by getqflist().
*/
void list_append_dict(list_T *list, dict_T *dict)
@@ -5406,20 +5420,12 @@ static int list_join(garray_T *gap, list_T *l, char_u *sep, int echo_style, int
{
garray_T join_ga;
int retval;
- join_T *p;
ga_init(&join_ga, (int)sizeof(join_T), l->lv_len);
retval = list_join_inner(gap, l, sep, echo_style, copyID, &join_ga);
- /* Dispose each item in join_ga. */
- if (join_ga.ga_data != NULL) {
- p = (join_T *)join_ga.ga_data;
- for (int i = 0; i < join_ga.ga_len; ++i) {
- free(p->tofree);
- ++p;
- }
- ga_clear(&join_ga);
- }
+# define FREE_JOIN_TOFREE(join) free((join)->tofree)
+ GA_DEEP_CLEAR(&join_ga, join_T, FREE_JOIN_TOFREE);
return retval;
}
@@ -6452,7 +6458,7 @@ static struct fst {
{"getpid", 0, 0, f_getpid},
{"getpos", 1, 1, f_getpos},
{"getqflist", 0, 0, f_getqflist},
- {"getreg", 0, 2, f_getreg},
+ {"getreg", 0, 3, f_getreg},
{"getregtype", 0, 1, f_getregtype},
{"gettabvar", 2, 3, f_gettabvar},
{"gettabwinvar", 3, 4, f_gettabwinvar},
@@ -9512,30 +9518,44 @@ static void f_getqflist(typval_T *argvars, typval_T *rettv)
(void)get_errorlist(wp, rettv->vval.v_list);
}
-/*
- * "getreg()" function
- */
+/// "getreg()" function
static void f_getreg(typval_T *argvars, typval_T *rettv)
{
char_u *strregname;
int regname;
- int arg2 = FALSE;
- int error = FALSE;
+ int arg2 = false;
+ bool return_list = false;
+ int error = false;
if (argvars[0].v_type != VAR_UNKNOWN) {
strregname = get_tv_string_chk(&argvars[0]);
error = strregname == NULL;
- if (argvars[1].v_type != VAR_UNKNOWN)
+ if (argvars[1].v_type != VAR_UNKNOWN) {
arg2 = get_tv_number_chk(&argvars[1], &error);
- } else
+ if (!error && argvars[2].v_type != VAR_UNKNOWN) {
+ return_list = get_tv_number_chk(&argvars[2], &error);
+ }
+ }
+ } else {
strregname = vimvars[VV_REG].vv_str;
+ }
+
+ if (error) {
+ return;
+ }
+
regname = (strregname == NULL ? '"' : *strregname);
if (regname == 0)
regname = '"';
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = error ? NULL :
- get_reg_contents(regname, TRUE, arg2);
+ if (return_list) {
+ rettv->v_type = VAR_LIST;
+ rettv->vval.v_list =
+ get_reg_contents(regname, (arg2 ? kGRegExprSrc : 0) | kGRegList);
+ } else {
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = get_reg_contents(regname, arg2 ? kGRegExprSrc : 0);
+ }
}
/*
@@ -13300,7 +13320,6 @@ static void f_setreg(typval_T *argvars, typval_T *rettv)
int regname;
char_u *strregname;
char_u *stropt;
- char_u *strval;
int append;
char_u yank_type;
long block_len;
@@ -13317,8 +13336,6 @@ static void f_setreg(typval_T *argvars, typval_T *rettv)
regname = *strregname;
if (regname == 0 || regname == '@')
regname = '"';
- else if (regname == '=')
- return;
if (argvars[2].v_type != VAR_UNKNOWN) {
stropt = get_tv_string_chk(&argvars[2]);
@@ -13346,10 +13363,46 @@ static void f_setreg(typval_T *argvars, typval_T *rettv)
}
}
- strval = get_tv_string_chk(&argvars[1]);
- if (strval != NULL)
- write_reg_contents_ex(regname, strval, -1,
- append, yank_type, block_len);
+ if (argvars[1].v_type == VAR_LIST) {
+ int len = argvars[1].vval.v_list->lv_len;
+ // First half: use for pointers to result lines; second half: use for
+ // pointers to allocated copies.
+ char_u **lstval = xmalloc(sizeof(char_u *) * ((len + 1) * 2));
+ char_u **curval = lstval;
+ char_u **allocval = lstval + len + 2;
+ char_u **curallocval = allocval;
+
+ char_u buf[NUMBUFLEN];
+ for (listitem_T *li = argvars[1].vval.v_list->lv_first;
+ li != NULL;
+ li = li->li_next) {
+ char_u *strval = get_tv_string_buf_chk(&li->li_tv, buf);
+ if (strval == NULL) {
+ goto free_lstval;
+ }
+ if (strval == buf) {
+ // Need to make a copy,
+ // next get_tv_string_buf_chk() will overwrite the string.
+ strval = vim_strsave(buf);
+ *curallocval++ = strval;
+ }
+ *curval++ = strval;
+ }
+ *curval++ = NULL;
+
+ write_reg_contents_lst(regname, lstval, -1, append, yank_type, block_len);
+
+free_lstval:
+ while (curallocval > allocval)
+ free(*--curallocval);
+ free(lstval);
+ } else {
+ char_u *strval = get_tv_string_chk(&argvars[1]);
+ if (strval == NULL) {
+ return;
+ }
+ write_reg_contents_ex(regname, strval, -1, append, yank_type, block_len);
+ }
rettv->vval.v_number = 0;
}
@@ -16273,28 +16326,33 @@ static linenr_T get_tv_lnum_buf(typval_T *argvars, buf_T *buf)
* get_tv_string_chk() and get_tv_string_buf_chk() are similar, but return
* NULL on error.
*/
-static char_u *get_tv_string(typval_T *varp)
+static char_u *get_tv_string(const typval_T *varp)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
static char_u mybuf[NUMBUFLEN];
return get_tv_string_buf(varp, mybuf);
}
-static char_u *get_tv_string_buf(typval_T *varp, char_u *buf)
+static char_u *get_tv_string_buf(const typval_T *varp, char_u *buf)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
char_u *res = get_tv_string_buf_chk(varp, buf);
return res != NULL ? res : (char_u *)"";
}
-char_u *get_tv_string_chk(typval_T *varp)
+/// Careful: This uses a single, static buffer. YOU CAN ONLY USE IT ONCE!
+char_u *get_tv_string_chk(const typval_T *varp)
+ FUNC_ATTR_NONNULL_ALL
{
static char_u mybuf[NUMBUFLEN];
return get_tv_string_buf_chk(varp, mybuf);
}
-static char_u *get_tv_string_buf_chk(typval_T *varp, char_u *buf)
+static char_u *get_tv_string_buf_chk(const typval_T *varp, char_u *buf)
+ FUNC_ATTR_NONNULL_ALL
{
switch (varp->v_type) {
case VAR_NUMBER:
@@ -19815,16 +19873,12 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
bool eval_has_provider(char *name)
{
-#define source_provider(name) \
- do_source((uint8_t *)"$VIMRUNTIME/autoload/provider/" name ".vim", \
- false, \
- false)
#define check_provider(name) \
if (has_##name == -1) { \
has_##name = !!find_func((uint8_t *)"provider#" #name "#Call"); \
if (!has_##name) { \
- source_provider(#name); \
+ script_autoload((uint8_t *)"provider#" #name "#Call", false); \
has_##name = !!find_func((uint8_t *)"provider#" #name "#Call"); \
} \
}
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 5ae03c6be3..701e969393 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -5625,9 +5625,7 @@ helptags_one (
if (mix)
got_int = FALSE; /* continue with other languages */
- for (int i = 0; i < ga.ga_len; ++i)
- free(((char_u **)ga.ga_data)[i]);
- ga_clear(&ga);
+ GA_DEEP_CLEAR_PTR(&ga);
fclose(fd_tags); /* there is no check for an error... */
}
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 794e9930b9..fa78047a46 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -2580,13 +2580,11 @@ char_u *get_scriptname(scid_T id)
}
# if defined(EXITFREE) || defined(PROTO)
-void free_scriptnames(void)
+void free_scriptnames()
{
- for (int i = script_items.ga_len; i > 0; --i)
- free(SCRIPT_ITEM(i).sn_name);
- ga_clear(&script_items);
+# define FREE_SCRIPTNAME(item) free((item)->sn_name)
+ GA_DEEP_CLEAR(&script_items, scriptitem_T, FREE_SCRIPTNAME);
}
-
# endif
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index e180be4421..6bca1ff34d 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -97,6 +97,8 @@ typedef struct {
linenr_T lnum; /* sourcing_lnum of the line */
} wcmd_T;
+#define FREE_WCMD(wcmd) free((wcmd)->line)
+
/*
* Structure used to store info for line position in a while or for loop.
* This is required, because do_one_cmd() may invoke ex_function(), which
@@ -708,9 +710,8 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
*/
if (cstack.cs_looplevel == 0) {
if (!GA_EMPTY(&lines_ga)) {
- sourcing_lnum =
- ((wcmd_T *)lines_ga.ga_data)[lines_ga.ga_len - 1].lnum;
- free_cmdlines(&lines_ga);
+ sourcing_lnum = ((wcmd_T *)lines_ga.ga_data)[lines_ga.ga_len - 1].lnum;
+ GA_DEEP_CLEAR(&lines_ga, wcmd_T, FREE_WCMD);
}
current_line = 0;
}
@@ -777,8 +778,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
free(cmdline_copy);
did_emsg_syntax = FALSE;
- free_cmdlines(&lines_ga);
- ga_clear(&lines_ga);
+ GA_DEEP_CLEAR(&lines_ga, wcmd_T, FREE_WCMD);
if (cstack.cs_idx >= 0) {
/*
@@ -1018,17 +1018,6 @@ static void store_loop_line(garray_T *gap, char_u *line)
}
/*
- * Free the lines stored for a ":while" or ":for" loop.
- */
-static void free_cmdlines(garray_T *gap)
-{
- while (!GA_EMPTY(gap)) {
- free(((wcmd_T *)(gap->ga_data))[gap->ga_len - 1].line);
- --gap->ga_len;
- }
-}
-
-/*
* If "fgetline" is get_loop_line(), return TRUE if the getline it uses equals
* "func". * Otherwise return TRUE when "fgetline" equals "func".
*/
@@ -4546,20 +4535,18 @@ void ex_comclear(exarg_T *eap)
uc_clear(&curbuf->b_ucmds);
}
+static void free_ucmd(ucmd_T* cmd) {
+ free(cmd->uc_name);
+ free(cmd->uc_rep);
+ free(cmd->uc_compl_arg);
+}
+
/*
* Clear all user commands for "gap".
*/
void uc_clear(garray_T *gap)
{
- ucmd_T *cmd;
-
- for (int i = 0; i < gap->ga_len; ++i) {
- cmd = USER_CMD_GA(gap, i);
- free(cmd->uc_name);
- free(cmd->uc_rep);
- free(cmd->uc_compl_arg);
- }
- ga_clear(gap);
+ GA_DEEP_CLEAR(gap, ucmd_T, free_ucmd);
}
static void ex_delcommand(exarg_T *eap)
@@ -5488,9 +5475,8 @@ static void ex_goto(exarg_T *eap)
*/
void alist_clear(alist_T *al)
{
- while (--al->al_ga.ga_len >= 0)
- free(AARGLIST(al)[al->al_ga.ga_len].ae_fname);
- ga_clear(&al->al_ga);
+# define FREE_AENTRY_FNAME(arg) free(arg->ae_fname)
+ GA_DEEP_CLEAR(&al->al_ga, aentry_T, FREE_AENTRY_FNAME);
}
/*
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index e56592923d..d3051c5202 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -1984,10 +1984,6 @@ void free_cmdline_buf(void)
*/
static void draw_cmdline(int start, int len)
{
- if (embedded_mode) {
- return;
- }
-
int i;
if (cmdline_star > 0)
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index e76aacbadc..505ac8da0d 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -1336,9 +1336,8 @@ static void deleteFoldEntry(garray_T *gap, int idx, int recursive)
*/
void deleteFoldRecurse(garray_T *gap)
{
- for (int i = 0; i < gap->ga_len; ++i)
- deleteFoldRecurse(&(((fold_T *)(gap->ga_data))[i].fd_nested));
- ga_clear(gap);
+# define DELETE_FOLD_NESTED(fd) deleteFoldRecurse(&((fd)->fd_nested))
+ GA_DEEP_CLEAR(gap, fold_T, DELETE_FOLD_NESTED);
}
/* foldMarkAdjust() {{{2 */
diff --git a/src/nvim/garray.c b/src/nvim/garray.c
index 08a38493bf..c4f8f66bfe 100644
--- a/src/nvim/garray.c
+++ b/src/nvim/garray.c
@@ -37,10 +37,7 @@ void ga_clear(garray_T *gap)
/// @param gap
void ga_clear_strings(garray_T *gap)
{
- for (int i = 0; i < gap->ga_len; ++i) {
- free(((char_u **)(gap->ga_data))[i]);
- }
- ga_clear(gap);
+ GA_DEEP_CLEAR_PTR(gap);
}
/// Initialize a growing array.
diff --git a/src/nvim/garray.h b/src/nvim/garray.h
index b32bab52f7..b758fce5da 100644
--- a/src/nvim/garray.h
+++ b/src/nvim/garray.h
@@ -43,4 +43,30 @@ static inline void *ga_append_via_ptr(garray_T *gap, size_t item_size)
return ((char *)gap->ga_data) + (item_size * (size_t)gap->ga_len++);
}
+/// Deep free a garray of specific type using a custom free function.
+/// Items in the array as well as the array itself are freed.
+///
+/// @param gap the garray to be freed
+/// @param item_type type of the item in the garray
+/// @param free_item_fn free function that takes (*item_type) as parameter
+#define GA_DEEP_CLEAR(gap, item_type, free_item_fn) \
+ do { \
+ garray_T *_gap = (gap); \
+ if (_gap->ga_data != NULL) { \
+ for (int i = 0; i < _gap->ga_len; i++) { \
+ item_type *_item = &(((item_type *)_gap->ga_data)[i]); \
+ free_item_fn(_item); \
+ } \
+ } \
+ ga_clear(_gap); \
+ } while (false)
+
+#define FREE_PTR_PTR(ptr) free(*(ptr))
+
+/// Call `free` for every pointer stored in the garray and then frees the
+/// garray.
+///
+/// @param gap the garray to be freed
+#define GA_DEEP_CLEAR_PTR(gap) GA_DEEP_CLEAR(gap, void*, FREE_PTR_PTR)
+
#endif // NVIM_GARRAY_H
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index d0bdcde9e8..5dec7e38fd 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -2513,6 +2513,13 @@ fix_input_buffer (
int script /* TRUE when reading from a script */
)
{
+ if (abstract_ui) {
+ // Should not escape K_SPECIAL/CSI while in embedded mode because vim key
+ // codes keys are processed in input.c/input_enqueue.
+ buf[len] = NUL;
+ return len;
+ }
+
int i;
char_u *p = buf;
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index ea91135194..233d326a40 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -465,6 +465,8 @@ EXTERN int highlight_stlnc[9]; /* On top of user */
EXTERN int cterm_normal_fg_color INIT(= 0);
EXTERN int cterm_normal_fg_bold INIT(= 0);
EXTERN int cterm_normal_bg_color INIT(= 0);
+EXTERN RgbValue normal_fg INIT(= -1);
+EXTERN RgbValue normal_bg INIT(= -1);
EXTERN int autocmd_busy INIT(= FALSE); /* Is apply_autocmds() busy? */
EXTERN int autocmd_no_enter INIT(= FALSE); /* *Enter autocmds disabled */
@@ -1251,6 +1253,8 @@ EXTERN int curr_tmode INIT(= TMODE_COOK); /* contains current terminal mode */
// If a msgpack-rpc channel should be started over stdin/stdout
EXTERN bool embedded_mode INIT(= false);
+// Using the "abstract_ui" termcap
+EXTERN bool abstract_ui INIT(= false);
/// Used to track the status of external functions.
/// Currently only used for iconv().
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 8e19cf3686..c806431872 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -265,13 +265,6 @@ int main(int argc, char **argv)
term_init();
TIME_MSG("shell init");
- event_init();
-
- if (!embedded_mode) {
- // Print a warning if stdout is not a terminal.
- check_tty(&params);
- }
-
/* This message comes before term inits, but after setting "silent_mode"
* when the input is not a tty. */
if (GARGCOUNT > 1 && !silent_mode)
@@ -283,6 +276,7 @@ int main(int argc, char **argv)
// initial screen size of 80x20
full_screen = true;
screen_resize(80, 20, false);
+ termcapinit((uint8_t *)"abstract_ui");
} else {
// set terminal name and get terminal capabilities (will set full_screen)
// Do some initialization of the screen
@@ -292,6 +286,16 @@ int main(int argc, char **argv)
TIME_MSG("Termcap init");
}
+ event_init();
+
+ if (abstract_ui) {
+ t_colors = 256;
+ } else {
+ // Print a warning if stdout is not a terminal TODO(tarruda): Remove this
+ // check once the new terminal UI is implemented
+ check_tty(&params);
+ }
+
/*
* Set the default values for the options that use Rows and Columns.
*/
@@ -424,19 +428,17 @@ int main(int argc, char **argv)
TIME_MSG("waiting for return");
}
- if (!embedded_mode) {
- starttermcap(); // start termcap if not done by wait_return()
- TIME_MSG("start termcap");
- may_req_ambiguous_char_width();
- setmouse(); // may start using the mouse
+ starttermcap(); // start termcap if not done by wait_return()
+ TIME_MSG("start termcap");
+ may_req_ambiguous_char_width();
+ setmouse(); // may start using the mouse
- if (scroll_region) {
- scroll_region_reset(); // In case Rows changed
- }
-
- scroll_start(); // may scroll the screen to the right position
+ if (scroll_region) {
+ scroll_region_reset(); // In case Rows changed
}
+ scroll_start(); // may scroll the screen to the right position
+
/*
* Don't clear the screen when starting in Ex mode, unless using the GUI.
*/
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index f959ea55e4..c221b13f8b 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -234,6 +234,43 @@ void memchrsub(void *data, char c, char x, size_t len)
}
}
+/// Counts the number of occurrences of `c` in `str`.
+///
+/// @warning Unsafe if `c == NUL`.
+///
+/// @param str Pointer to the string to search.
+/// @param c The byte to search for.
+/// @returns the number of occurrences of `c` in `str`.
+size_t strcnt(const char *str, char c, size_t len)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
+{
+ assert(c != 0);
+ size_t cnt = 0;
+ while ((str = strchr(str, c))) {
+ cnt++;
+ str++; // Skip the instance of c.
+ }
+ return cnt;
+}
+
+/// Counts the number of occurrences of byte `c` in `data[len]`.
+///
+/// @param data Pointer to the data to search.
+/// @param c The byte to search for.
+/// @param len The length of `data`.
+/// @returns the number of occurrences of `c` in `data[len]`.
+size_t memcnt(const void *data, char c, size_t len)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
+{
+ size_t cnt = 0;
+ const char *ptr = data, *end = ptr + len;
+ while ((ptr = memchr(ptr, c, (size_t)(end - ptr))) != NULL) {
+ cnt++;
+ ptr++; // Skip the instance of c.
+ }
+ return cnt;
+}
+
/// The xstpcpy() function shall copy the string pointed to by src (including
/// the terminating NUL character) into the array pointed to by dst.
///
diff --git a/src/nvim/menu.c b/src/nvim/menu.c
index 1573aaae84..b31b6c1cec 100644
--- a/src/nvim/menu.c
+++ b/src/nvim/menu.c
@@ -1424,6 +1424,12 @@ typedef struct {
static garray_T menutrans_ga = GA_EMPTY_INIT_VALUE;
+#define FREE_MENUTRANS(mt) \
+ menutrans_T* _mt = (mt); \
+ free(_mt->from); \
+ free(_mt->from_noamp); \
+ free(_mt->to)
+
/*
* ":menutrans".
* This function is also defined without the +multi_lang feature, in which
@@ -1441,13 +1447,8 @@ void ex_menutranslate(exarg_T *eap)
* ":menutrans clear": clear all translations.
*/
if (STRNCMP(arg, "clear", 5) == 0 && ends_excmd(*skipwhite(arg + 5))) {
- menutrans_T *tp = (menutrans_T *)menutrans_ga.ga_data;
- for (int i = 0; i < menutrans_ga.ga_len; ++i) {
- free(tp[i].from);
- free(tp[i].from_noamp);
- free(tp[i].to);
- }
- ga_clear(&menutrans_ga);
+ GA_DEEP_CLEAR(&menutrans_ga, menutrans_T, FREE_MENUTRANS);
+
/* Delete all "menutrans_" global variables. */
del_menutrans_vars();
} else {
diff --git a/src/nvim/message.c b/src/nvim/message.c
index cd0c548fb4..808253d33c 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -145,7 +145,7 @@ int verb_msg(char_u *s)
return n;
}
-int msg_attr(char_u *s, int attr)
+int msg_attr(char_u *s, int attr) FUNC_ATTR_NONNULL_ARG(1)
{
return msg_attr_keep(s, attr, FALSE);
}
@@ -156,6 +156,7 @@ msg_attr_keep (
int attr,
int keep /* TRUE: set keep_msg if it doesn't scroll */
)
+ FUNC_ATTR_NONNULL_ARG(1)
{
static int entered = 0;
int retval;
@@ -2623,7 +2624,7 @@ int verbose_open(void)
* Give a warning message (for searching).
* Use 'w' highlighting and may repeat the message after redrawing
*/
-void give_warning(char_u *message, bool hl)
+void give_warning(char_u *message, bool hl) FUNC_ATTR_NONNULL_ARG(1)
{
/* Don't do this for ":silent". */
if (msg_silent != 0)
@@ -3623,13 +3624,7 @@ int vim_vsnprintf(char *str, size_t str_m, char *fmt, va_list ap, typval_T *tvs)
remove_trailing_zeroes = TRUE;
}
- if (fmt_spec == 'f' &&
-#ifdef VAX
- abs_f > 1.0e38
-#else
- abs_f > 1.0e307
-#endif
- ) {
+ if (fmt_spec == 'f' && abs_f > 1.0e307) {
/* Avoid a buffer overflow */
strcpy(tmp, "inf");
str_arg_l = 3;
diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c
index 439cdbd5c8..9f67bd1760 100644
--- a/src/nvim/mouse.c
+++ b/src/nvim/mouse.c
@@ -452,7 +452,7 @@ void setmouse(void)
return;
/* don't switch mouse on when not in raw mode (Ex mode) */
- if (cur_tmode != TMODE_RAW) {
+ if (!abstract_ui && cur_tmode != TMODE_RAW) {
mch_setmouse(false);
return;
}
@@ -470,10 +470,11 @@ void setmouse(void)
else
checkfor = MOUSE_NORMAL; /* assume normal mode */
- if (mouse_has(checkfor))
- mch_setmouse(true);
- else
- mch_setmouse(false);
+ if (mouse_has(checkfor)) {
+ ui_mouse_on();
+ } else {
+ ui_mouse_off();
+ }
}
/*
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index 0c04a7b23e..4c35cce09a 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -10,6 +10,7 @@
#include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h"
#include "nvim/msgpack_rpc/channel.h"
+#include "nvim/msgpack_rpc/remote_ui.h"
#include "nvim/os/event.h"
#include "nvim/os/rstream.h"
#include "nvim/os/rstream_defs.h"
@@ -100,6 +101,17 @@ void channel_init(void)
if (embedded_mode) {
channel_from_stdio();
}
+
+ if (abstract_ui) {
+ // Add handler for "attach_ui"
+ remote_ui_init();
+ String method = cstr_as_string("attach_ui");
+ MsgpackRpcRequestHandler handler = {.fn = remote_ui_attach, .defer = true};
+ msgpack_rpc_add_method_handler(method, handler);
+ method = cstr_as_string("detach_ui");
+ handler.fn = remote_ui_detach;
+ msgpack_rpc_add_method_handler(method, handler);
+ }
}
/// Teardown the module
@@ -241,6 +253,7 @@ Object channel_send_call(uint64_t id,
if (frame.errored) {
api_set_error(err, Exception, "%s", frame.result.data.string.data);
+ api_free_object(frame.result);
return NIL;
}
@@ -347,7 +360,13 @@ static void job_err(RStream *rstream, void *data, bool eof)
static void job_exit(Job *job, void *data)
{
- free_channel((Channel *)data);
+ Channel *channel = data;
+ // ensure the channel is flagged as closed so channel_send_call frees it
+ // later
+ channel->closed = true;
+ if (!kv_size(channel->call_stack)) {
+ free_channel(channel);
+ }
}
static void parse_msgpack(RStream *rstream, void *data, bool eof)
@@ -638,6 +657,10 @@ static void on_stdio_close(Event e)
static void free_channel(Channel *channel)
{
+ if (abstract_ui) {
+ remote_ui_disconnect(channel->id);
+ }
+
pmap_del(uint64_t)(channels, channel->id);
msgpack_unpacker_free(channel->unpacker);
diff --git a/src/nvim/msgpack_rpc/defs.h b/src/nvim/msgpack_rpc/defs.h
index 13067fb7b4..0492a65290 100644
--- a/src/nvim/msgpack_rpc/defs.h
+++ b/src/nvim/msgpack_rpc/defs.h
@@ -19,6 +19,10 @@ typedef struct {
/// Initializes the msgpack-rpc method table
void msgpack_rpc_init_method_table(void);
+// Add a handler to the method table
+void msgpack_rpc_add_method_handler(String method,
+ MsgpackRpcRequestHandler handler);
+
void msgpack_rpc_init_function_metadata(Dictionary *metadata);
/// Dispatches to the actual API function after basic payload validation by
diff --git a/src/nvim/msgpack_rpc/remote_ui.c b/src/nvim/msgpack_rpc/remote_ui.c
new file mode 100644
index 0000000000..f980a77b4c
--- /dev/null
+++ b/src/nvim/msgpack_rpc/remote_ui.c
@@ -0,0 +1,280 @@
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "nvim/vim.h"
+#include "nvim/ui.h"
+#include "nvim/memory.h"
+#include "nvim/map.h"
+#include "nvim/msgpack_rpc/remote_ui.h"
+#include "nvim/msgpack_rpc/channel.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "msgpack_rpc/remote_ui.c.generated.h"
+#endif
+
+typedef struct {
+ uint64_t channel_id;
+ Array buffer;
+} UIData;
+
+static PMap(uint64_t) *connected_uis = NULL;
+
+void remote_ui_init(void)
+{
+ connected_uis = pmap_new(uint64_t)();
+}
+
+Object remote_ui_attach(uint64_t channel_id, uint64_t request_id, Array args,
+ Error *error)
+{
+ if (pmap_has(uint64_t)(connected_uis, channel_id)) {
+ api_set_error(error, Exception, _("UI already attached for channel"));
+ return NIL;
+ }
+
+ if (args.size != 2 || args.items[0].type != kObjectTypeInteger
+ || args.items[1].type != kObjectTypeInteger
+ || args.items[0].data.integer <= 0 || args.items[1].data.integer <= 0) {
+ api_set_error(error, Validation,
+ _("Arguments must be a pair of positive integers "
+ "representing the remote screen width/height"));
+ return NIL;
+ }
+ UIData *data = xmalloc(sizeof(UIData));
+ data->channel_id = channel_id;
+ data->buffer = (Array)ARRAY_DICT_INIT;
+ UI *ui = xcalloc(1, sizeof(UI));
+ ui->width = (int)args.items[0].data.integer;
+ ui->height = (int)args.items[1].data.integer;
+ ui->data = data;
+ ui->resize = remote_ui_resize;
+ ui->clear = remote_ui_clear;
+ ui->eol_clear = remote_ui_eol_clear;
+ ui->cursor_goto = remote_ui_cursor_goto;
+ ui->cursor_on = remote_ui_cursor_on;
+ ui->cursor_off = remote_ui_cursor_off;
+ ui->mouse_on = remote_ui_mouse_on;
+ ui->mouse_off = remote_ui_mouse_off;
+ ui->insert_mode = remote_ui_insert_mode;
+ ui->normal_mode = remote_ui_normal_mode;
+ ui->set_scroll_region = remote_ui_set_scroll_region;
+ ui->scroll = remote_ui_scroll;
+ ui->highlight_set = remote_ui_highlight_set;
+ ui->put = remote_ui_put;
+ ui->bell = remote_ui_bell;
+ ui->visual_bell = remote_ui_visual_bell;
+ ui->flush = remote_ui_flush;
+ ui->suspend = remote_ui_suspend;
+ pmap_put(uint64_t)(connected_uis, channel_id, ui);
+ ui_attach(ui);
+
+ return NIL;
+}
+
+Object remote_ui_detach(uint64_t channel_id, uint64_t request_id, Array args,
+ Error *error)
+{
+ if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
+ api_set_error(error, Exception, _("UI is not attached for channel"));
+ }
+ remote_ui_disconnect(channel_id);
+
+ return NIL;
+}
+
+void remote_ui_disconnect(uint64_t channel_id)
+{
+ UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
+ if (!ui) {
+ return;
+ }
+ UIData *data = ui->data;
+ // destroy pending screen updates
+ api_free_array(data->buffer);
+ pmap_del(uint64_t)(connected_uis, channel_id);
+ free(ui->data);
+ ui_detach(ui);
+ free(ui);
+}
+
+static void push_call(UI *ui, char *name, Array args)
+{
+ Array call = ARRAY_DICT_INIT;
+ UIData *data = ui->data;
+
+ // To optimize data transfer(especially for "put"), we bundle adjacent
+ // calls to same method together, so only add a new call entry if the last
+ // method call is different from "name"
+ if (kv_size(data->buffer)) {
+ call = kv_A(data->buffer, kv_size(data->buffer) - 1).data.array;
+ }
+
+ if (!kv_size(call) || strcmp(kv_A(call, 0).data.string.data, name)) {
+ call = (Array)ARRAY_DICT_INIT;
+ ADD(data->buffer, ARRAY_OBJ(call));
+ ADD(call, STRING_OBJ(cstr_to_string(name)));
+ }
+
+ ADD(call, ARRAY_OBJ(args));
+ kv_A(data->buffer, kv_size(data->buffer) - 1).data.array = call;
+}
+
+static void remote_ui_resize(UI *ui, int width, int height)
+{
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(width));
+ ADD(args, INTEGER_OBJ(height));
+ push_call(ui, "resize", args);
+}
+
+static void remote_ui_clear(UI *ui)
+{
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "clear", args);
+}
+
+static void remote_ui_eol_clear(UI *ui)
+{
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "eol_clear", args);
+}
+
+static void remote_ui_cursor_goto(UI *ui, int row, int col)
+{
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(row));
+ ADD(args, INTEGER_OBJ(col));
+ push_call(ui, "cursor_goto", args);
+}
+
+static void remote_ui_cursor_on(UI *ui)
+{
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "cursor_on", args);
+}
+
+static void remote_ui_cursor_off(UI *ui)
+{
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "cursor_off", args);
+}
+
+static void remote_ui_mouse_on(UI *ui)
+{
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "mouse_on", args);
+}
+
+static void remote_ui_mouse_off(UI *ui)
+{
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "mouse_off", args);
+}
+
+static void remote_ui_insert_mode(UI *ui)
+{
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "insert_mode", args);
+}
+
+static void remote_ui_normal_mode(UI *ui)
+{
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "normal_mode", args);
+}
+
+static void remote_ui_set_scroll_region(UI *ui, int top, int bot, int left,
+ int right)
+{
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(top));
+ ADD(args, INTEGER_OBJ(bot));
+ ADD(args, INTEGER_OBJ(left));
+ ADD(args, INTEGER_OBJ(right));
+ push_call(ui, "set_scroll_region", args);
+}
+
+static void remote_ui_scroll(UI *ui, int count)
+{
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(count));
+ push_call(ui, "scroll", args);
+}
+
+static void remote_ui_highlight_set(UI *ui, HlAttrs attrs)
+{
+ Array args = ARRAY_DICT_INIT;
+ Dictionary hl = ARRAY_DICT_INIT;
+
+ if (attrs.bold) {
+ PUT(hl, "bold", BOOLEAN_OBJ(true));
+ }
+
+ if (attrs.standout) {
+ PUT(hl, "standout", BOOLEAN_OBJ(true));
+ }
+
+ if (attrs.underline) {
+ PUT(hl, "underline", BOOLEAN_OBJ(true));
+ }
+
+ if (attrs.undercurl) {
+ PUT(hl, "undercurl", BOOLEAN_OBJ(true));
+ }
+
+ if (attrs.italic) {
+ PUT(hl, "italic", BOOLEAN_OBJ(true));
+ }
+
+ if (attrs.reverse) {
+ PUT(hl, "reverse", BOOLEAN_OBJ(true));
+ }
+
+ if (attrs.foreground != -1) {
+ PUT(hl, "foreground", INTEGER_OBJ(attrs.foreground));
+ }
+
+ if (attrs.background != -1) {
+ PUT(hl, "background", INTEGER_OBJ(attrs.background));
+ }
+
+ ADD(args, DICTIONARY_OBJ(hl));
+ push_call(ui, "highlight_set", args);
+}
+
+static void remote_ui_put(UI *ui, uint8_t *data, size_t size)
+{
+ Array args = ARRAY_DICT_INIT;
+ String str = {.data = xmemdupz(data, size), .size = size};
+ ADD(args, STRING_OBJ(str));
+ push_call(ui, "put", args);
+}
+
+static void remote_ui_bell(UI *ui)
+{
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "bell", args);
+}
+
+static void remote_ui_visual_bell(UI *ui)
+{
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "visual_bell", args);
+}
+
+static void remote_ui_flush(UI *ui)
+{
+ UIData *data = ui->data;
+ channel_send_event(data->channel_id, "redraw", data->buffer);
+ data->buffer = (Array)ARRAY_DICT_INIT;
+}
+
+static void remote_ui_suspend(UI *ui)
+{
+ UIData *data = ui->data;
+ remote_ui_disconnect(data->channel_id);
+}
diff --git a/src/nvim/msgpack_rpc/remote_ui.h b/src/nvim/msgpack_rpc/remote_ui.h
new file mode 100644
index 0000000000..8af86dc1b8
--- /dev/null
+++ b/src/nvim/msgpack_rpc/remote_ui.h
@@ -0,0 +1,9 @@
+#ifndef NVIM_MSGPACK_RPC_REMOTE_UI_H
+#define NVIM_MSGPACK_RPC_REMOTE_UI_H
+
+#include "nvim/ui.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "msgpack_rpc/remote_ui.h.generated.h"
+#endif
+#endif // NVIM_MSGPACK_RPC_REMOTE_UI_H
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 1b21100933..3b4d4047db 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -915,14 +915,7 @@ getcount:
&& !oap->op_type
&& (idx < 0 || !(nv_cmds[idx].cmd_flags & NV_KEEPREG))) {
clearop(oap);
- {
- int regname = 0;
-
- /* Adjust the register according to 'clipboard', so that when
- * "unnamed" is present it becomes '*' or '+' instead of '"'. */
- adjust_clipboard_register(&regname);
- set_reg_var(regname);
- }
+ set_reg_var(0);
}
/* Get the length of mapped chars again after typing a count, second
@@ -1863,21 +1856,21 @@ do_mouse (
save_cursor = curwin->w_cursor;
- /*
- * When GUI is active, always recognize mouse events, otherwise:
- * - Ignore mouse event in normal mode if 'mouse' doesn't include 'n'.
- * - Ignore mouse event in visual mode if 'mouse' doesn't include 'v'.
- * - For command line and insert mode 'mouse' is checked before calling
- * do_mouse().
- */
- if (do_always)
- do_always = false;
- else {
- if (VIsual_active) {
- if (!mouse_has(MOUSE_VISUAL))
+ // When "abstract_ui" is active, always recognize mouse events, otherwise:
+ // - Ignore mouse event in normal mode if 'mouse' doesn't include 'n'.
+ // - Ignore mouse event in visual mode if 'mouse' doesn't include 'v'.
+ // - For command line and insert mode 'mouse' is checked before calling
+ // do_mouse().
+ if (!abstract_ui) {
+ if (do_always)
+ do_always = false;
+ else {
+ if (VIsual_active) {
+ if (!mouse_has(MOUSE_VISUAL))
+ return false;
+ } else if (State == NORMAL && !mouse_has(MOUSE_NORMAL))
return false;
- } else if (State == NORMAL && !mouse_has(MOUSE_NORMAL))
- return false;
+ }
}
for (;; ) {
@@ -2109,7 +2102,7 @@ do_mouse (
* Windows only shows the popup menu on the button up event.
*/
#if defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_GTK) \
- || defined(FEAT_GUI_PHOTON) || defined(FEAT_GUI_MAC)
+ || defined(FEAT_GUI_MAC)
if (!is_click)
return false;
#endif
@@ -5105,7 +5098,6 @@ static void nv_brackets(cmdarg_T *cap)
end = equalpos(start, VIsual) ? curwin->w_cursor : VIsual;
curwin->w_cursor = (dir == BACKWARD ? start : end);
}
- adjust_clipboard_register(&regname);
prep_redo_cmd(cap);
do_put(regname, dir, cap->count1, PUT_FIXINDENT);
if (was_visual) {
@@ -7272,10 +7264,8 @@ static void nv_put(cmdarg_T *cap)
*/
was_visual = true;
regname = cap->oap->regname;
- bool adjusted = adjust_clipboard_register(&regname);
if (regname == 0 || regname == '"'
|| VIM_ISDIGIT(regname) || regname == '-'
- || adjusted
) {
/* The delete is going to overwrite the register we want to
* put, save it first. */
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 6bf3f6036f..32b37b6458 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -60,6 +60,7 @@
#define DELETION_REGISTER 36
#define CLIP_REGISTER 37
+# define CB_UNNAMEDMASK (CB_UNNAMED | CB_UNNAMEDPLUS)
/*
* Each yank register is an array of pointers to lines.
*/
@@ -74,6 +75,7 @@ static struct yankreg *y_current; /* ptr to current yankreg */
static int y_append; /* TRUE when appending */
static struct yankreg *y_previous = NULL; /* ptr to last written yankreg */
+static bool clipboard_didwarn_unnamed = false;
/*
* structure used by block_prep, op_delete and op_yank for blockwise operators
* also op_change, op_shift, op_insert, op_replace - AKelly
@@ -751,7 +753,8 @@ void get_yank_register(int regname, int writing)
int i;
y_append = FALSE;
- if ((regname == 0 || regname == '"') && !writing && y_previous != NULL) {
+ int unnamedclip = cb_flags & CB_UNNAMEDMASK;
+ if ((regname == 0 || regname == '"') && !unnamedclip && !writing && y_previous != NULL) {
y_current = y_previous;
return;
}
@@ -1302,18 +1305,6 @@ cmdline_paste_reg (
return OK;
}
-bool adjust_clipboard_register(int *rp)
-{
- // If no reg. specified and 'unnamedclip' is set, use the
- // clipboard register.
- if (*rp == 0 && p_unc && eval_has_provider("clipboard")) {
- *rp = '+';
- return true;
- }
-
- return false;
-}
-
/*
* Handle a delete operation.
*
@@ -1328,7 +1319,6 @@ int op_delete(oparg_T *oap)
struct block_def bd;
linenr_T old_lcount = curbuf->b_ml.ml_line_count;
int did_yank = FALSE;
- int orig_regname = oap->regname;
if (curbuf->b_ml.ml_flags & ML_EMPTY) /* nothing to do */
return OK;
@@ -1342,8 +1332,6 @@ int op_delete(oparg_T *oap)
return FAIL;
}
- bool adjusted = adjust_clipboard_register(&oap->regname);
-
if (has_mbyte)
mb_adjust_opend(oap);
@@ -1393,9 +1381,10 @@ int op_delete(oparg_T *oap)
* register. For the black hole register '_' don't yank anything.
*/
if (oap->regname != '_') {
- if (oap->regname != 0) {
+ bool unnamedclip = oap->regname == 0 && (cb_flags & CB_UNNAMEDMASK);
+ if (oap->regname != 0 || unnamedclip) {
/* check for read-only register */
- if (!valid_yank_reg(oap->regname, TRUE)) {
+ if (!( valid_yank_reg(oap->regname, TRUE) || unnamedclip )) {
beep_flush();
return OK;
}
@@ -1407,10 +1396,8 @@ int op_delete(oparg_T *oap)
/*
* Put deleted text into register 1 and shift number registers if the
* delete contains a line break, or when a regname has been specified.
- * Use the register name from before adjust_clip_reg() may have
- * changed it.
*/
- if (orig_regname != 0 || oap->motion_type == MLINE
+ if (oap->regname != 0 || oap->motion_type == MLINE
|| oap->line_count > 1 || oap->use_reg_one) {
y_current = &y_regs[9];
free_yank_all(); /* free register nine */
@@ -1424,9 +1411,7 @@ int op_delete(oparg_T *oap)
/* Yank into small delete register when no named register specified
* and the delete is within one line. */
- if ((
- adjusted ||
- oap->regname == 0) && oap->motion_type != MLINE
+ if (oap->regname == 0 && oap->motion_type != MLINE
&& oap->line_count == 1) {
oap->regname = '-';
get_yank_register(oap->regname, TRUE);
@@ -2623,7 +2608,6 @@ do_put (
int allocated = FALSE;
long cnt;
- adjust_clipboard_register(&regname);
get_clipboard(regname);
if (flags & PUT_FIXINDENT)
@@ -3215,7 +3199,6 @@ void ex_display(exarg_T *eap)
)
continue; /* did not ask for this register */
- adjust_clipboard_register(&name);
get_clipboard(name);
if (i == -1) {
@@ -4671,30 +4654,43 @@ char_u get_reg_type(int regname, long *reglen)
return MAUTO;
}
-/*
- * Return the contents of a register as a single allocated string.
- * Used for "@r" in expressions and for getreg().
- * Returns NULL for error.
- */
-char_u *
-get_reg_contents (
- int regname,
- int allowexpr, /* allow "=" register */
- int expr_src /* get expression for "=" register */
-)
+/// When `flags` has `kGRegList` return a list with text `s`.
+/// Otherwise just return `s`.
+///
+/// Returns a void * for use in get_reg_contents().
+static void *get_reg_wrap_one_line(char_u *s, int flags)
+{
+ if (!(flags & kGRegList)) {
+ return s;
+ }
+ list_T *list = list_alloc();
+ list_append_string(list, NULL, -1);
+ list->lv_first->li_tv.vval.v_string = s;
+ return list;
+}
+
+/// Gets the contents of a register.
+/// @remark Used for `@r` in expressions and for `getreg()`.
+///
+/// @param regname The register.
+/// @param flags see @ref GRegFlags
+///
+/// @returns The contents of the register as an allocated string.
+/// @returns A linked list when `flags` contains @ref kGRegList.
+/// @returns NULL for error.
+void *get_reg_contents(int regname, int flags)
{
long i;
- char_u *retval;
- int allocated;
- /* Don't allow using an expression register inside an expression */
+ // Don't allow using an expression register inside an expression.
if (regname == '=') {
- if (allowexpr) {
- if (expr_src)
- return get_expr_line_src();
- return get_expr_line();
+ if (flags & kGRegNoExpr) {
+ return NULL;
}
- return NULL;
+ if (flags & kGRegExprSrc) {
+ return get_reg_wrap_one_line(get_expr_line_src(), flags);
+ }
+ return get_reg_wrap_one_line(get_expr_line(), flags);
}
if (regname == '@') /* "@@" is used for unnamed register */
@@ -4706,18 +4702,30 @@ get_reg_contents (
get_clipboard(regname);
+ char_u *retval;
+ int allocated;
if (get_spec_reg(regname, &retval, &allocated, FALSE)) {
if (retval == NULL)
return NULL;
- if (!allocated)
- retval = vim_strsave(retval);
- return retval;
+ if (allocated) {
+ return get_reg_wrap_one_line(retval, flags);
+ }
+ return get_reg_wrap_one_line(vim_strsave(retval), flags);
}
get_yank_register(regname, FALSE);
if (y_current->y_array == NULL)
return NULL;
+ if (flags & kGRegList) {
+ list_T *list = list_alloc();
+ for (int i = 0; i < y_current->y_size; ++i) {
+ list_append_string(list, y_current->y_array[i], -1);
+ }
+
+ return list;
+ }
+
/*
* Compute length of resulting string.
*/
@@ -4754,17 +4762,77 @@ get_reg_contents (
return retval;
}
+static bool init_write_reg(int name, struct yankreg **old_y_previous,
+ struct yankreg **old_y_current, int must_append)
+{
+ if (!valid_yank_reg(name, true)) { // check for valid reg name
+ emsg_invreg(name);
+ return false;
+ }
+
+ // Don't want to change the current (unnamed) register.
+ *old_y_previous = y_previous;
+ *old_y_current = y_current;
+
+ get_yank_register(name, true);
+ if (!y_append && !must_append) {
+ free_yank_all();
+ }
+ return true;
+}
+
+static void finish_write_reg(int name, struct yankreg *old_y_previous,
+ struct yankreg *old_y_current)
+{
+ // Send text of clipboard register to the clipboard.
+ set_clipboard(name);
+
+ // ':let @" = "val"' should change the meaning of the "" register
+ if (name != '"') {
+ y_previous = old_y_previous;
+ }
+ y_current = old_y_current;
+}
+
/// write_reg_contents - store `str` in register `name`
///
/// @see write_reg_contents_ex
-void write_reg_contents(int name,
- const char_u *str,
- ssize_t len,
+void write_reg_contents(int name, const char_u *str, ssize_t len,
int must_append)
{
write_reg_contents_ex(name, str, len, must_append, MAUTO, 0L);
}
+void write_reg_contents_lst(int name, char_u **strings, int maxlen,
+ int must_append, int yank_type, long block_len)
+{
+ if (name == '/' || name == '=') {
+ char_u *s = strings[0];
+ if (strings[0] == NULL) {
+ s = (char_u *)"";
+ } else if (strings[1] != NULL) {
+ EMSG(_("E883: search pattern and expression register may not "
+ "contain two or more lines"));
+ return;
+ }
+ write_reg_contents_ex(name, s, -1, must_append, yank_type, block_len);
+ return;
+ }
+
+ // black hole: nothing to do
+ if (name == '_') {
+ return;
+ }
+
+ struct yankreg *old_y_previous, *old_y_current;
+ if (!init_write_reg(name, &old_y_previous, &old_y_current, must_append)) {
+ return;
+ }
+
+ str_to_reg(y_current, yank_type, (char_u *) strings, -1, block_len, true);
+ finish_write_reg(name, old_y_previous, old_y_current);
+}
+
/// write_reg_contents_ex - store `str` in register `name`
///
/// If `str` ends in '\n' or '\r', use linewise, otherwise use
@@ -4791,8 +4859,6 @@ void write_reg_contents_ex(int name,
int yank_type,
long block_len)
{
- struct yankreg *old_y_previous, *old_y_current;
-
if (len < 0) {
len = (ssize_t) STRLEN(str);
}
@@ -4826,28 +4892,16 @@ void write_reg_contents_ex(int name,
return;
}
- if (!valid_yank_reg(name, TRUE)) { /* check for valid reg name */
- emsg_invreg(name);
+ if (name == '_') { // black hole: nothing to do
return;
}
- if (name == '_') /* black hole: nothing to do */
- return;
-
- /* Don't want to change the current (unnamed) register */
- old_y_previous = y_previous;
- old_y_current = y_current;
-
- get_yank_register(name, TRUE);
- if (!y_append && !must_append)
- free_yank_all();
- str_to_reg(y_current, yank_type, str, len, block_len);
-
-
- /* ':let @" = "val"' should change the meaning of the "" register */
- if (name != '"')
- y_previous = old_y_previous;
- y_current = old_y_current;
+ struct yankreg *old_y_previous, *old_y_current;
+ if (!init_write_reg(name, &old_y_previous, &old_y_current, must_append)) {
+ return;
+ }
+ str_to_reg(y_current, yank_type, str, len, block_len, false);
+ finish_write_reg(name, old_y_previous, old_y_current);
}
/// str_to_reg - Put a string into a register.
@@ -4856,100 +4910,100 @@ void write_reg_contents_ex(int name,
///
/// @param y_ptr pointer to yank register
/// @param yank_type MCHAR, MLINE, MBLOCK or MAUTO
-/// @param str string to put in register
-/// @param len length of the string
-/// @param blocklen width of visual block
-static void str_to_reg(struct yankreg *y_ptr,
- int yank_type,
- const char_u *str,
- long len,
- long blocklen)
+/// @param str string or list of strings to put in register
+/// @param len length of the string (Ignored when str_list=true.)
+/// @param blocklen width of visual block, or -1 for "I don't know."
+/// @param str_list True if str is `char_u **`.
+static void str_to_reg(struct yankreg *y_ptr, int yank_type, const char_u *str,
+ size_t len, colnr_T blocklen, bool str_list)
+ FUNC_ATTR_NONNULL_ALL
{
- int type; /* MCHAR, MLINE or MBLOCK */
- int lnum;
- long start;
- long i;
- int extra;
- size_t newlines; /* number of lines added */
- int extraline = 0; /* extra line at the end */
- int append = FALSE; /* append to last line in register */
- char_u *s;
- char_u **pp;
- long maxlen;
-
- if (y_ptr->y_array == NULL) /* NULL means empty register */
+ if (y_ptr->y_array == NULL) { // NULL means empty register
y_ptr->y_size = 0;
+ }
- if (yank_type == MAUTO)
- type = ((len > 0 && (str[len - 1] == NL || str[len - 1] == CAR))
+ int type = yank_type; // MCHAR, MLINE or MBLOCK
+ if (yank_type == MAUTO) {
+ type = ((str_list ||
+ (len > 0 && (str[len - 1] == NL || str[len - 1] == CAR)))
? MLINE : MCHAR);
- else
- type = yank_type;
-
- /*
- * Count the number of lines within the string
- */
- newlines = 0;
- for (i = 0; i < len; i++)
- if (str[i] == '\n')
- ++newlines;
- if (type == MCHAR || len == 0 || str[len - 1] != '\n') {
- extraline = 1;
- ++newlines; /* count extra newline at the end */
}
- if (y_ptr->y_size > 0 && y_ptr->y_type == MCHAR) {
- append = TRUE;
- --newlines; /* uncount newline when appending first line */
+
+ size_t newlines = 0;
+ bool extraline = false; // extra line at the end
+ bool append = false; // append to last line in register
+
+ // Count the number of lines within the string
+ if (str_list) {
+ for (char_u **ss = (char_u **) str; *ss != NULL; ++ss) {
+ newlines++;
+ }
+ } else {
+ newlines = memcnt(str, '\n', len);
+ if (type == MCHAR || len == 0 || str[len - 1] != '\n') {
+ extraline = 1;
+ ++newlines; // count extra newline at the end
+ }
+ if (y_ptr->y_size > 0 && y_ptr->y_type == MCHAR) {
+ append = true;
+ --newlines; // uncount newline when appending first line
+ }
}
- /*
- * Allocate an array to hold the pointers to the new register lines.
- * If the register was not empty, move the existing lines to the new array.
- */
- pp = xcalloc((y_ptr->y_size + newlines), sizeof(char_u *));
- for (lnum = 0; lnum < y_ptr->y_size; ++lnum)
- pp[lnum] = y_ptr->y_array[lnum];
- free(y_ptr->y_array);
+
+ // Grow the register array to hold the pointers to the new lines.
+ char_u **pp = xrealloc(y_ptr->y_array,
+ (y_ptr->y_size + newlines) * sizeof(char_u *));
y_ptr->y_array = pp;
- maxlen = 0;
- /*
- * Find the end of each line and save it into the array.
- */
- for (start = 0; start < len + extraline; start += i + 1) {
- // Let i represent the length of one line.
- const char_u *p = str + start;
- i = (char_u *)xmemscan(p, '\n', len - start) - p;
- if (i > maxlen)
- maxlen = i;
- if (append) {
- --lnum;
- extra = (int)STRLEN(y_ptr->y_array[lnum]);
- } else
- extra = 0;
- s = xmalloc(i + extra + 1);
- if (extra)
- memmove(s, y_ptr->y_array[lnum], (size_t)extra);
- if (append)
- free(y_ptr->y_array[lnum]);
- if (i)
- memmove(s + extra, str + start, (size_t)i);
- extra += i;
- s[extra] = NUL;
- y_ptr->y_array[lnum++] = s;
- while (--extra >= 0) {
- if (*s == NUL)
- *s = '\n'; /* replace NUL with newline */
- ++s;
+ linenr_T lnum = y_ptr->y_size; // The current line number.
+
+ // If called with `blocklen < 0`, we have to update the yank reg's width.
+ size_t maxlen = 0;
+
+ // Find the end of each line and save it into the array.
+ if (str_list) {
+ for (char_u **ss = (char_u **) str; *ss != NULL; ++ss, ++lnum) {
+ size_t ss_len = STRLEN(*ss);
+ pp[lnum] = xmemdupz(*ss, ss_len);
+ if (ss_len > maxlen) {
+ maxlen = ss_len;
+ }
+ }
+ } else {
+ size_t line_len;
+ for (const char_u *start = str, *end = str + len;
+ start < end + extraline;
+ start += line_len + 1, lnum++) {
+ line_len = (const char_u *) xmemscan(start, '\n', end - start) - start;
+ if (line_len > maxlen) {
+ maxlen = line_len;
+ }
+
+ // When appending, copy the previous line and free it after.
+ size_t extra = append ? STRLEN(pp[--lnum]) : 0;
+ char_u *s = xmallocz(line_len + extra);
+ memcpy(s, pp[lnum], extra);
+ memcpy(s + extra, start, line_len);
+ ssize_t s_len = extra + line_len;
+
+ if (append) {
+ free(pp[lnum]);
+ append = false; // only first line is appended
+ }
+ pp[lnum] = s;
+
+ // Convert NULs to '\n' to prevent truncation.
+ memchrsub(pp[lnum], NUL, '\n', s_len);
}
- append = FALSE; /* only first line is appended */
}
y_ptr->y_type = type;
y_ptr->y_size = lnum;
- if (type == MBLOCK)
- y_ptr->y_width = (blocklen < 0 ? maxlen - 1 : blocklen);
- else
+ if (type == MBLOCK) {
+ y_ptr->y_width = (blocklen == -1 ? (colnr_T) maxlen - 1 : blocklen);
+ } else {
y_ptr->y_width = 0;
+ }
}
void clear_oparg(oparg_T *oap)
@@ -5225,33 +5279,82 @@ static void free_register(struct yankreg *reg)
y_current = curr;
}
-static void copy_register(struct yankreg *dest, struct yankreg *src)
-{
- free_register(dest);
- *dest = *src;
- dest->y_array = xcalloc(src->y_size, sizeof(uint8_t *));
- for (int j = 0; j < src->y_size; ++j) {
- dest->y_array[j] = (uint8_t *)xstrdup((char *)src->y_array[j]);
+// return target register
+static int adjust_clipboard_name(int *name) {
+ if (*name == '*' || *name == '+') {
+ if(!eval_has_provider("clipboard")) {
+ EMSG("clipboard: provider is not available");
+ return -1;
+ }
+ return CLIP_REGISTER;
+ } else if (*name == NUL && (cb_flags & (CB_UNNAMED | CB_UNNAMEDPLUS))) {
+ if(!eval_has_provider("clipboard")) {
+ if (!clipboard_didwarn_unnamed) {
+ msg((char_u*)"clipboard: provider not available, ignoring clipboard=unnamed[plus]");
+ clipboard_didwarn_unnamed = true;
+ }
+ return -1;
+ }
+ if (cb_flags & CB_UNNAMEDPLUS) {
+ *name = '+';
+ } else {
+ *name = '*';
+ }
+ return 0; //unnamed
}
+ // don't do anything for other register names
+ return -1;
}
static void get_clipboard(int name)
{
- if (!(name == '*' || name == '+'
- || (p_unc && !name && eval_has_provider("clipboard")))) {
+ int ireg = adjust_clipboard_name(&name);
+ if (ireg < 0) {
return;
}
- struct yankreg *reg = &y_regs[CLIP_REGISTER];
+ struct yankreg *reg = &y_regs[ireg];
free_register(reg);
+
list_T *args = list_alloc();
+ char_u regname = name;
+ list_append_string(args, &regname, 1);
+
typval_T result = eval_call_provider("clipboard", "get", args);
if (result.v_type != VAR_LIST) {
goto err;
}
- list_T *lines = result.vval.v_list;
+ list_T *res = result.vval.v_list, *lines = NULL;
+ if (res->lv_len == 2 && res->lv_first->li_tv.v_type == VAR_LIST) {
+ lines = res->lv_first->li_tv.vval.v_list;
+ if (res->lv_last->li_tv.v_type != VAR_STRING) {
+ goto err;
+ }
+ char_u* regtype = res->lv_last->li_tv.vval.v_string;
+ if (regtype == NULL || strlen((char*)regtype) != 1) {
+ goto err;
+ }
+ switch (regtype[0]) {
+ case 'v': case 'c':
+ reg->y_type = MCHAR;
+ break;
+ case 'V': case 'l':
+ reg->y_type = MLINE;
+ break;
+ case 'b': case Ctrl_V:
+ reg->y_type = MBLOCK;
+ break;
+ default:
+ goto err;
+ }
+ } else {
+ lines = res;
+ // provider did not specify regtype, calculate it below
+ reg->y_type = MAUTO;
+ }
+
reg->y_array = xcalloc(lines->lv_len, sizeof(uint8_t *));
reg->y_size = lines->lv_len;
@@ -5263,9 +5366,23 @@ static void get_clipboard(int name)
reg->y_array[i++] = (uint8_t *)xstrdup((char *)li->li_tv.vval.v_string);
}
- if (!name && p_unc) {
- // copy to the unnamed register
- copy_register(&y_regs[0], reg);
+ if (reg->y_type == MAUTO) {
+ if (reg->y_size > 0 && strlen((char*)reg->y_array[reg->y_size-1]) == 0) {
+ reg->y_type = MLINE;
+ free(reg->y_array[reg->y_size-1]);
+ reg->y_size--;
+ } else {
+ reg->y_type = MCHAR;
+ }
+ } else if (reg->y_type == MBLOCK) {
+ int maxlen = 0;
+ for (int i = 0; i < reg->y_size; i++) {
+ int rowlen = STRLEN(reg->y_array[i]);
+ if (rowlen > maxlen) {
+ maxlen = rowlen;
+ }
+ }
+ reg->y_width = maxlen-1;
}
return;
@@ -5279,22 +5396,17 @@ err:
}
reg->y_array = NULL;
reg->y_size = 0;
- EMSG("Clipboard provider returned invalid data");
+ EMSG("clipboard: provider returned invalid data");
}
static void set_clipboard(int name)
{
- if (!(name == '*' || name == '+'
- || (p_unc && !name && eval_has_provider("clipboard")))) {
+ int ireg = adjust_clipboard_name(&name);
+ if (ireg < 0) {
return;
}
- struct yankreg *reg = &y_regs[CLIP_REGISTER];
-
- if (!name && p_unc) {
- // copy from the unnamed register
- copy_register(reg, &y_regs[0]);
- }
+ struct yankreg *reg = &y_regs[ireg];
list_T *lines = list_alloc();
@@ -5302,5 +5414,26 @@ static void set_clipboard(int name)
list_append_string(lines, reg->y_array[i], -1);
}
- (void)eval_call_provider("clipboard", "set", lines);
+ list_T *args = list_alloc();
+ list_append_list(args, lines);
+
+ char_u regtype;
+ switch (reg->y_type) {
+ case MLINE:
+ regtype = 'V';
+ list_append_string(lines, (char_u*)"", 0);
+ break;
+ case MCHAR:
+ regtype = 'v';
+ break;
+ case MBLOCK:
+ regtype = 'b';
+ break;
+ }
+ list_append_string(args, &regtype, 1);
+
+ char_u regname = name;
+ list_append_string(args, &regname, 1);
+
+ (void)eval_call_provider("clipboard", "set", args);
}
diff --git a/src/nvim/ops.h b/src/nvim/ops.h
index ca70684ab8..3251042c8f 100644
--- a/src/nvim/ops.h
+++ b/src/nvim/ops.h
@@ -47,6 +47,13 @@ typedef int (*Indenter)(void);
#define OP_FORMAT2 26 /* "gw" format operator, keeps cursor pos */
#define OP_FUNCTION 27 /* "g@" call 'operatorfunc' */
+/// Flags for get_reg_contents().
+enum GRegFlags {
+ kGRegNoExpr = 1, ///< Do not allow expression register.
+ kGRegExprSrc = 2, ///< Return expression itself for "=" register.
+ kGRegList = 4 ///< Return list.
+};
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ops.h.generated.h"
#endif
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 2882d7a511..b3a883c79e 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -527,7 +527,7 @@ static struct vimoption
(char_u *)0L}
SCRIPTID_INIT},
{"clipboard", "cb", P_STRING|P_VI_DEF|P_COMMA|P_NODUP,
- (char_u *)NULL, PV_NONE,
+ (char_u *)&p_cb, PV_NONE,
{(char_u *)"", (char_u *)0L}
SCRIPTID_INIT},
{"cmdheight", "ch", P_NUM|P_VI_DEF|P_RALL,
@@ -1179,9 +1179,6 @@ static struct vimoption
{"optimize", "opt", P_BOOL|P_VI_DEF,
(char_u *)NULL, PV_NONE,
{(char_u *)FALSE, (char_u *)0L} SCRIPTID_INIT},
- {"osfiletype", "oft", P_STRING|P_ALLOCED|P_VI_DEF,
- (char_u *)NULL, PV_NONE,
- {(char_u *)0L, (char_u *)0L} SCRIPTID_INIT},
{"paragraphs", "para", P_STRING|P_VI_DEF,
(char_u *)&p_para, PV_NONE,
{(char_u *)"IPLPPPQPP TPHPLIPpLpItpplpipbp",
@@ -1620,9 +1617,6 @@ static struct vimoption
{"undoreload", "ur", P_NUM|P_VI_DEF,
(char_u *)&p_ur, PV_NONE,
{ (char_u *)10000L, (char_u *)0L} SCRIPTID_INIT},
- {"unnamedclip", "ucp", P_BOOL|P_VI_DEF|P_VIM,
- (char_u *)&p_unc, PV_NONE,
- {(char_u *)FALSE, (char_u *)FALSE} SCRIPTID_INIT},
{"updatecount", "uc", P_NUM|P_VI_DEF,
(char_u *)&p_uc, PV_NONE,
{(char_u *)200L, (char_u *)0L} SCRIPTID_INIT},
@@ -4279,6 +4273,10 @@ did_set_string_option (
if (check_opt_strings(p_ead, p_ead_values, FALSE) != OK)
errmsg = e_invarg;
}
+ else if (varp == &p_cb) {
+ if (opt_strings_flags(p_cb, p_cb_values, &cb_flags, TRUE) != OK)
+ errmsg = e_invarg;
+ }
/* When 'spelllang' or 'spellfile' is set and there is a window for this
* buffer in which 'spell' is set load the wordlists. */
else if (varp == &(curbuf->b_s.b_p_spl) || varp == &(curbuf->b_s.b_p_spf)) {
@@ -4846,7 +4844,6 @@ char_u *check_stl_option(char_u *s)
return NULL;
}
-
/*
* Set curbuf->b_cap_prog to the regexp program for 'spellcapcheck'.
* Return error message when failed, NULL when OK.
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index 89264f8982..39dfbe8b88 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -317,6 +317,13 @@ EXTERN char_u *p_enc; /* 'encoding' */
EXTERN int p_deco; /* 'delcombine' */
EXTERN char_u *p_ccv; /* 'charconvert' */
EXTERN char_u *p_cedit; /* 'cedit' */
+EXTERN char_u *p_cb; /* 'clipboard' */
+EXTERN unsigned cb_flags;
+#ifdef IN_OPTION_C
+static char *(p_cb_values[]) = {"unnamed", "unnamedplus", NULL};
+#endif
+# define CB_UNNAMED 0x001
+# define CB_UNNAMEDPLUS 0x002
EXTERN long p_cwh; /* 'cmdwinheight' */
EXTERN long p_ch; /* 'cmdheight' */
EXTERN int p_confirm; /* 'confirm' */
@@ -582,7 +589,6 @@ static char *(p_ttym_values[]) =
EXTERN char_u *p_udir; /* 'undodir' */
EXTERN long p_ul; /* 'undolevels' */
EXTERN long p_ur; /* 'undoreload' */
-EXTERN int p_unc; /* 'unnamedclip' */
EXTERN long p_uc; /* 'updatecount' */
EXTERN long p_ut; /* 'updatetime' */
EXTERN char_u *p_fcs; /* 'fillchar' */
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index 686fe1f06d..cddc28fac9 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -46,7 +46,7 @@ void input_init(void)
{
input_buffer = rbuffer_new(INPUT_BUFFER_SIZE + MAX_KEY_CODE_LEN);
- if (embedded_mode) {
+ if (abstract_ui) {
return;
}
@@ -57,7 +57,7 @@ void input_init(void)
void input_teardown(void)
{
- if (embedded_mode) {
+ if (abstract_ui) {
return;
}
@@ -67,7 +67,7 @@ void input_teardown(void)
// Listen for input
void input_start(void)
{
- if (embedded_mode) {
+ if (abstract_ui) {
return;
}
@@ -77,7 +77,7 @@ void input_start(void)
// Stop listening for input
void input_stop(void)
{
- if (embedded_mode) {
+ if (abstract_ui) {
return;
}
@@ -180,11 +180,110 @@ void input_buffer_restore(String str)
size_t input_enqueue(String keys)
{
- size_t rv = rbuffer_write(input_buffer, keys.data, keys.size);
+ char *ptr = keys.data, *end = ptr + keys.size;
+
+ while (rbuffer_available(input_buffer) >= 6 && ptr < end) {
+ uint8_t buf[6] = {0};
+ int new_size = trans_special((uint8_t **)&ptr, buf, false);
+
+ if (!new_size) {
+ // copy the character unmodified
+ *buf = (uint8_t)*ptr++;
+ new_size = 1;
+ }
+
+ new_size = handle_mouse_event(&ptr, buf, new_size);
+ // TODO(tarruda): Don't produce past unclosed '<' characters, except if
+ // there's a lot of characters after the '<'
+ rbuffer_write(input_buffer, (char *)buf, (size_t)new_size);
+ }
+
+ size_t rv = (size_t)(ptr - keys.data);
process_interrupts();
return rv;
}
+// Mouse event handling code(Extract row/col if available and detect multiple
+// clicks)
+static int handle_mouse_event(char **ptr, uint8_t *buf, int bufsize)
+{
+ int mouse_code = 0;
+
+ if (bufsize == 3) {
+ mouse_code = buf[2];
+ } else if (bufsize == 6) {
+ // prefixed with K_SPECIAL KS_MODIFIER mod
+ mouse_code = buf[5];
+ }
+
+ if (mouse_code < KE_LEFTMOUSE || mouse_code > KE_RIGHTRELEASE) {
+ return bufsize;
+ }
+
+ // a <[COL],[ROW]> sequence can follow and will set the mouse_row/mouse_col
+ // global variables. This is ugly but its how the rest of the code expects to
+ // find mouse coordinates, and it would be too expensive to refactor this
+ // now.
+ int col, row, advance;
+ if (sscanf(*ptr, "<%d,%d>%n", &col, &row, &advance)) {
+ if (col >= 0 && row >= 0) {
+ mouse_row = row;
+ mouse_col = col;
+ }
+ *ptr += advance;
+ }
+
+ static int orig_num_clicks = 0;
+ static int orig_mouse_code = 0;
+ static int orig_mouse_col = 0;
+ static int orig_mouse_row = 0;
+ static uint64_t orig_mouse_time = 0; // time of previous mouse click
+ uint64_t mouse_time = os_hrtime(); // time of current mouse click
+
+ // compute the time elapsed since the previous mouse click and
+ // convert p_mouse from ms to ns
+ uint64_t timediff = mouse_time - orig_mouse_time;
+ uint64_t mouset = (uint64_t)p_mouset * 1000000;
+ if (mouse_code == orig_mouse_code
+ && timediff < mouset
+ && orig_num_clicks != 4
+ && orig_mouse_col == mouse_col
+ && orig_mouse_row == mouse_row) {
+ orig_num_clicks++;
+ } else {
+ orig_num_clicks = 1;
+ }
+ orig_mouse_code = mouse_code;
+ orig_mouse_col = mouse_col;
+ orig_mouse_row = mouse_row;
+ orig_mouse_time = mouse_time;
+
+ int modifiers = 0;
+ if (orig_num_clicks == 2) {
+ modifiers |= MOD_MASK_2CLICK;
+ } else if (orig_num_clicks == 3) {
+ modifiers |= MOD_MASK_3CLICK;
+ } else if (orig_num_clicks == 4) {
+ modifiers |= MOD_MASK_4CLICK;
+ }
+
+ if (modifiers) {
+ if (buf[1] != KS_MODIFIER) {
+ // no modifiers in the buffer yet, shift the bytes 3 positions
+ memcpy(buf + 3, buf, 3);
+ // add the modifier sequence
+ buf[0] = K_SPECIAL;
+ buf[1] = KS_MODIFIER;
+ buf[2] = (uint8_t)modifiers;
+ bufsize += 3;
+ } else {
+ buf[2] |= (uint8_t)modifiers;
+ }
+ }
+
+ return bufsize;
+}
+
static bool input_poll(int ms)
{
if (do_profiling == PROF_YES && ms) {
@@ -255,7 +354,7 @@ static void read_cb(RStream *rstream, void *data, bool at_eof)
static void convert_input(void)
{
- if (embedded_mode || !rbuffer_available(input_buffer)) {
+ if (abstract_ui || !rbuffer_available(input_buffer)) {
// No input buffer space
return;
}
@@ -335,7 +434,7 @@ static bool input_ready(void)
return typebuf_was_filled || // API call filled typeahead
rbuffer_pending(input_buffer) > 0 || // Stdin input
event_has_deferred() || // Events must be processed
- (!embedded_mode && eof); // Stdin closed
+ (!abstract_ui && eof); // Stdin closed
}
// Exit because of an input read error.
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
index cdd85e4e96..88b7f5c73d 100644
--- a/src/nvim/os/shell.c
+++ b/src/nvim/os/shell.c
@@ -141,7 +141,7 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_arg)
}
if (output) {
- write_output(output, nread);
+ (void)write_output(output, nread, true, true);
free(output);
}
@@ -197,6 +197,9 @@ static int shell(const char *cmd,
// the output buffer
DynamicBuffer buf = DYNAMIC_BUFFER_INIT;
rstream_cb data_cb = system_data_cb;
+ if (nread) {
+ *nread = 0;
+ }
if (forward_output) {
data_cb = out_data_cb;
@@ -296,9 +299,9 @@ static void system_data_cb(RStream *rstream, void *data, bool eof)
static void out_data_cb(RStream *rstream, void *data, bool eof)
{
RBuffer *rbuffer = rstream_buffer(rstream);
- size_t len = rbuffer_pending(rbuffer);
- ui_write((char_u *)rbuffer_read_ptr(rbuffer), (int)len);
- rbuffer_consumed(rbuffer, len);
+ size_t written = write_output(rbuffer_read_ptr(rbuffer),
+ rbuffer_pending(rbuffer), false, eof);
+ rbuffer_consumed(rbuffer, written);
}
/// Parses a command string into a sequence of words, taking quotes into
@@ -407,18 +410,27 @@ static void read_input(DynamicBuffer *buf)
}
}
-static void write_output(char *output, size_t remaining)
+static size_t write_output(char *output, size_t remaining, bool to_buffer,
+ bool eof)
{
if (!output) {
- return;
+ return 0;
}
+ char *start = output;
size_t off = 0;
while (off < remaining) {
if (output[off] == NL) {
// Insert the line
output[off] = NUL;
- ml_append(curwin->w_cursor.lnum++, (char_u *)output, 0, false);
+ if (to_buffer) {
+ ml_append(curwin->w_cursor.lnum++, (char_u *)output, 0, false);
+ } else {
+ // pending data from the output buffer has been flushed to the screen,
+ // safe to call ui_write directly
+ ui_write((char_u *)output, (int)off);
+ ui_write((char_u *)"\r\n", 2);
+ }
size_t skip = off + 1;
output += skip;
remaining -= skip;
@@ -433,14 +445,26 @@ static void write_output(char *output, size_t remaining)
off++;
}
- if (remaining) {
- // append unfinished line
- ml_append(curwin->w_cursor.lnum++, (char_u *)output, 0, false);
- // remember that the NL was missing
- curbuf->b_no_eol_lnum = curwin->w_cursor.lnum;
- } else {
- curbuf->b_no_eol_lnum = 0;
+ if (eof) {
+ if (remaining) {
+ if (to_buffer) {
+ // append unfinished line
+ ml_append(curwin->w_cursor.lnum++, (char_u *)output, 0, false);
+ // remember that the NL was missing
+ curbuf->b_no_eol_lnum = curwin->w_cursor.lnum;
+ } else {
+ ui_write((char_u *)output, (int)remaining);
+ ui_write((char_u *)"\r\n", 2);
+ }
+ output += remaining;
+ } else if (to_buffer) {
+ curbuf->b_no_eol_lnum = 0;
+ }
}
+
+ out_flush();
+
+ return (size_t)(output - start);
}
static void shell_write_cb(WStream *wstream, void *data, int status)
diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c
index cf8ba85ed5..ca3ba052d7 100644
--- a/src/nvim/os/signal.c
+++ b/src/nvim/os/signal.c
@@ -45,7 +45,7 @@ void signal_init(void)
uv_signal_start(&shup, signal_cb, SIGHUP);
uv_signal_start(&squit, signal_cb, SIGQUIT);
uv_signal_start(&sterm, signal_cb, SIGTERM);
- if (!embedded_mode) {
+ if (!abstract_ui) {
// TODO(tarruda): There must be an API function for resizing window
uv_signal_start(&swinch, signal_cb, SIGWINCH);
}
diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c
index 677976e3e1..a9c1fec0b4 100644
--- a/src/nvim/os_unix.c
+++ b/src/nvim/os_unix.c
@@ -9,7 +9,6 @@
/*
* os_unix.c -- code for all flavors of Unix (BSD, SYSV, SVR4, POSIX, ...)
- * Also for BeOS
*
* A lot of this file was originally written by Juergen Weigert and later
* changed beyond recognition.
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 0225eb72c1..c0a909f147 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -5824,9 +5824,12 @@ static void screen_start_highlight(int attr)
attrentry_T *aep = NULL;
screen_attr = attr;
- if (full_screen
- ) {
- {
+ if (full_screen) {
+ if (abstract_ui) {
+ char buf[20];
+ sprintf(buf, "\033|%dh", attr);
+ OUT_STR(buf);
+ } else {
if (attr > HL_ALL) { /* special HL attr. */
if (t_colors > 1)
aep = syn_cterm_attr2entry(attr);
@@ -5877,9 +5880,13 @@ void screen_stop_highlight(void)
{
int do_ME = FALSE; /* output T_ME code */
- if (screen_attr != 0
- ) {
- {
+ if (screen_attr != 0) {
+ if (abstract_ui) {
+ // Handled in ui.c
+ char buf[20];
+ sprintf(buf, "\033|%dH", screen_attr);
+ OUT_STR(buf);
+ } else {
if (screen_attr > HL_ALL) { /* special HL attr. */
attrentry_T *aep;
@@ -6558,11 +6565,14 @@ static void screenclear2(void)
{
int i;
- if (starting == NO_SCREEN || ScreenLines == NULL
- )
+ if (starting == NO_SCREEN || ScreenLines == NULL) {
return;
+ }
+
+ if (!abstract_ui) {
+ screen_attr = -1; /* force setting the Normal colors */
+ }
- screen_attr = -1; /* force setting the Normal colors */
screen_stop_highlight(); /* don't want highlighting here */
@@ -8156,14 +8166,19 @@ void screen_resize(int width, int height, int mustset)
++busy;
-
- if (mustset || (ui_get_shellsize() == FAIL && height != 0)) {
+ // TODO(tarruda): "mustset" is still used in the old tests, which don't use
+ // "abstract_ui" yet. This will change when a new TUI is merged.
+ if (abstract_ui || mustset || (ui_get_shellsize() == FAIL && height != 0)) {
Rows = height;
Columns = width;
- check_shellsize();
+ }
+ check_shellsize();
+
+ if (abstract_ui) {
+ ui_resize(width, height);
+ } else {
mch_set_shellsize();
- } else
- check_shellsize();
+ }
/* The window layout used to be adjusted here, but it now happens in
* screenalloc() (also invoked from screenclear()). That is because the
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index fa786fdd74..83dcddecd6 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -2379,13 +2379,26 @@ static void slang_free(slang_T *lp)
free(lp);
}
+/// Frees a salitem_T
+static void free_salitem(salitem_T *smp) {
+ free(smp->sm_lead);
+ // Don't free sm_oneof and sm_rules, they point into sm_lead.
+ free(smp->sm_to);
+ free(smp->sm_lead_w);
+ free(smp->sm_oneof_w);
+ free(smp->sm_to_w);
+}
+
+/// Frees a fromto_T
+static void free_fromto(fromto_T *ftp) {
+ free(ftp->ft_from);
+ free(ftp->ft_to);
+}
+
// Clear an slang_T so that the file can be reloaded.
static void slang_clear(slang_T *lp)
{
garray_T *gap;
- fromto_T *ftp;
- salitem_T *smp;
- int round;
free(lp->sl_fbyts);
lp->sl_fbyts = NULL;
@@ -2401,36 +2414,17 @@ static void slang_clear(slang_T *lp)
free(lp->sl_pidxs);
lp->sl_pidxs = NULL;
- for (round = 1; round <= 2; ++round) {
- gap = round == 1 ? &lp->sl_rep : &lp->sl_repsal;
- while (!GA_EMPTY(gap)) {
- ftp = &((fromto_T *)gap->ga_data)[--gap->ga_len];
- free(ftp->ft_from);
- free(ftp->ft_to);
- }
- ga_clear(gap);
- }
+ GA_DEEP_CLEAR(&lp->sl_rep, fromto_T, free_fromto);
+ GA_DEEP_CLEAR(&lp->sl_repsal, fromto_T, free_fromto);
gap = &lp->sl_sal;
if (lp->sl_sofo) {
// "ga_len" is set to 1 without adding an item for latin1
- if (gap->ga_data != NULL)
- // SOFOFROM and SOFOTO items: free lists of wide characters.
- for (int i = 0; i < gap->ga_len; ++i) {
- free(((int **)gap->ga_data)[i]);
- }
- } else
+ GA_DEEP_CLEAR_PTR(gap);
+ } else {
// SAL items: free salitem_T items
- while (!GA_EMPTY(gap)) {
- smp = &((salitem_T *)gap->ga_data)[--gap->ga_len];
- free(smp->sm_lead);
- // Don't free sm_oneof and sm_rules, they point into sm_lead.
- free(smp->sm_to);
- free(smp->sm_lead_w);
- free(smp->sm_oneof_w);
- free(smp->sm_to_w);
- }
- ga_clear(gap);
+ GA_DEEP_CLEAR(gap, salitem_T, free_salitem);
+ }
for (int i = 0; i < lp->sl_prefixcnt; ++i) {
vim_regfree(lp->sl_prefprog[i]);
@@ -9206,15 +9200,10 @@ static void tree_count_words(char_u *byts, idx_T *idxs)
// Free the info put in "*su" by spell_find_suggest().
static void spell_find_cleanup(suginfo_T *su)
{
+# define FREE_SUG_WORD(sug) free(sug->st_word)
// Free the suggestions.
- for (int i = 0; i < su->su_ga.ga_len; ++i) {
- free(SUG(su->su_ga, i).st_word);
- }
- ga_clear(&su->su_ga);
- for (int i = 0; i < su->su_sga.ga_len; ++i) {
- free(SUG(su->su_sga, i).st_word);
- }
- ga_clear(&su->su_sga);
+ GA_DEEP_CLEAR(&su->su_ga, suggest_T, FREE_SUG_WORD);
+ GA_DEEP_CLEAR(&su->su_sga, suggest_T, FREE_SUG_WORD);
// Free the banned words.
hash_clear_all(&su->su_banned, 0);
diff --git a/src/nvim/strings.c b/src/nvim/strings.c
index 20008bca16..1e619b1c6e 100644
--- a/src/nvim/strings.c
+++ b/src/nvim/strings.c
@@ -237,13 +237,9 @@ char_u *vim_strnsave_up(const char_u *string, size_t len)
void vim_strup(char_u *p)
FUNC_ATTR_NONNULL_ALL
{
- char_u *p2;
char_u c;
-
- if (p != NULL) {
- p2 = p;
- while ((c = *p2) != NUL)
- *p2++ = (char_u)((c < 'a' || c > 'z') ? c : c - 0x20);
+ while ((c = *p) != NUL) {
+ *p++ = (char_u)(c < 'a' || c > 'z' ? c : c - 0x20);
}
}
@@ -525,7 +521,7 @@ void sort_strings(char_u **files, int count)
* When "s" is NULL false is returned.
*/
bool has_non_ascii(const char_u *s)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
+ FUNC_ATTR_PURE
{
const char_u *p;
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index 69d6479cf3..896f27f9e4 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -11,6 +11,7 @@
*/
#include <assert.h>
+#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
@@ -68,9 +69,10 @@ struct hl_group {
int sg_cterm_attr; /* Screen attr for color term mode */
/* Store the sp color name for the GUI or synIDattr() */
int sg_gui; /* "gui=" highlighting attributes */
- char_u *sg_gui_fg_name; /* GUI foreground color name */
- char_u *sg_gui_bg_name; /* GUI background color name */
- char_u *sg_gui_sp_name; /* GUI special color name */
+ RgbValue sg_rgb_fg; // RGB foreground color
+ RgbValue sg_rgb_bg; // RGB background color
+ uint8_t *sg_rgb_fg_name; // RGB foreground color name
+ uint8_t *sg_rgb_bg_name; // RGB background color name
int sg_link; /* link to this highlight group ID */
int sg_set; /* combination of SG_* flags */
scid_T sg_scriptID; /* script in which the group was last set */
@@ -548,14 +550,9 @@ void syntax_start(win_T *wp, linenr_T lnum)
*/
static void clear_syn_state(synstate_T *p)
{
- garray_T *gap;
-
if (p->sst_stacksize > SST_FIX_STATES) {
- gap = &(p->sst_union.sst_ga);
- for (int i = 0; i < gap->ga_len; i++) {
- unref_extmatch(SYN_STATE_P(gap)[i].bs_extmatch);
- }
- ga_clear(gap);
+# define UNREF_BUFSTATE_EXTMATCH(bs) unref_extmatch((bs)->bs_extmatch)
+ GA_DEEP_CLEAR(&(p->sst_union.sst_ga), bufstate_T, UNREF_BUFSTATE_EXTMATCH);
} else {
for (int i = 0; i < p->sst_stacksize; i++) {
unref_extmatch(p->sst_union.sst_stack[i].bs_extmatch);
@@ -568,11 +565,8 @@ static void clear_syn_state(synstate_T *p)
*/
static void clear_current_state(void)
{
- stateitem_T *sip = (stateitem_T *)(current_state.ga_data);
- for (int i = 0; i < current_state.ga_len; i++) {
- unref_extmatch(sip[i].si_extmatch);
- }
- ga_clear(&current_state);
+# define UNREF_STATEITEM_EXTMATCH(si) unref_extmatch((si)->si_extmatch)
+ GA_DEEP_CLEAR(&current_state, stateitem_T, UNREF_STATEITEM_EXTMATCH);
}
/*
@@ -6518,34 +6512,39 @@ do_highlight (
if (!init)
HL_TABLE()[idx].sg_set |= SG_GUI;
- free(HL_TABLE()[idx].sg_gui_fg_name);
- if (STRCMP(arg, "NONE"))
- HL_TABLE()[idx].sg_gui_fg_name = vim_strsave(arg);
- else
- HL_TABLE()[idx].sg_gui_fg_name = NULL;
+ free(HL_TABLE()[idx].sg_rgb_fg_name);
+ if (STRCMP(arg, "NONE")) {
+ HL_TABLE()[idx].sg_rgb_fg_name = (uint8_t *)xstrdup((char *)arg);
+ HL_TABLE()[idx].sg_rgb_fg = name_to_color(arg);
+ } else {
+ HL_TABLE()[idx].sg_rgb_fg_name = NULL;
+ HL_TABLE()[idx].sg_rgb_fg = -1;
+ }
+ }
+
+ if (is_normal_group) {
+ normal_fg = HL_TABLE()[idx].sg_rgb_fg;
}
} else if (STRCMP(key, "GUIBG") == 0) {
if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) {
if (!init)
HL_TABLE()[idx].sg_set |= SG_GUI;
- free(HL_TABLE()[idx].sg_gui_bg_name);
- if (STRCMP(arg, "NONE") != 0)
- HL_TABLE()[idx].sg_gui_bg_name = vim_strsave(arg);
- else
- HL_TABLE()[idx].sg_gui_bg_name = NULL;
+ free(HL_TABLE()[idx].sg_rgb_bg_name);
+ if (STRCMP(arg, "NONE") != 0) {
+ HL_TABLE()[idx].sg_rgb_bg_name = (uint8_t *)xstrdup((char *)arg);
+ HL_TABLE()[idx].sg_rgb_bg = name_to_color(arg);
+ } else {
+ HL_TABLE()[idx].sg_rgb_bg_name = NULL;
+ HL_TABLE()[idx].sg_rgb_bg = -1;
+ }
}
- } else if (STRCMP(key, "GUISP") == 0) {
- if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) {
- if (!init)
- HL_TABLE()[idx].sg_set |= SG_GUI;
- free(HL_TABLE()[idx].sg_gui_sp_name);
- if (STRCMP(arg, "NONE") != 0)
- HL_TABLE()[idx].sg_gui_sp_name = vim_strsave(arg);
- else
- HL_TABLE()[idx].sg_gui_sp_name = NULL;
+ if (is_normal_group) {
+ normal_bg = HL_TABLE()[idx].sg_rgb_bg;
}
+ } else if (STRCMP(key, "GUISP") == 0) {
+ // Ignored
} else if (STRCMP(key, "START") == 0 || STRCMP(key, "STOP") == 0) {
char_u buf[100];
char_u *tname;
@@ -6670,6 +6669,8 @@ void free_highlight(void)
*/
void restore_cterm_colors(void)
{
+ normal_fg = -1;
+ normal_bg = -1;
cterm_normal_fg_color = 0;
cterm_normal_fg_bold = 0;
cterm_normal_bg_color = 0;
@@ -6705,12 +6706,12 @@ static void highlight_clear(int idx)
HL_TABLE()[idx].sg_cterm_bg = 0;
HL_TABLE()[idx].sg_cterm_attr = 0;
HL_TABLE()[idx].sg_gui = 0;
- free(HL_TABLE()[idx].sg_gui_fg_name);
- HL_TABLE()[idx].sg_gui_fg_name = NULL;
- free(HL_TABLE()[idx].sg_gui_bg_name);
- HL_TABLE()[idx].sg_gui_bg_name = NULL;
- free(HL_TABLE()[idx].sg_gui_sp_name);
- HL_TABLE()[idx].sg_gui_sp_name = NULL;
+ HL_TABLE()[idx].sg_rgb_fg = -1;
+ HL_TABLE()[idx].sg_rgb_bg = -1;
+ free(HL_TABLE()[idx].sg_rgb_fg_name);
+ HL_TABLE()[idx].sg_rgb_fg_name = NULL;
+ free(HL_TABLE()[idx].sg_rgb_bg_name);
+ HL_TABLE()[idx].sg_rgb_bg_name = NULL;
/* Clear the script ID only when there is no link, since that is not
* cleared. */
if (HL_TABLE()[idx].sg_link == 0)
@@ -6771,7 +6772,11 @@ static int get_attr_entry(garray_T *table, attrentry_T *aep)
&& aep->ae_u.cterm.fg_color
== taep->ae_u.cterm.fg_color
&& aep->ae_u.cterm.bg_color
- == taep->ae_u.cterm.bg_color)
+ == taep->ae_u.cterm.bg_color
+ && aep->fg_color
+ == taep->fg_color
+ && aep->bg_color
+ == taep->bg_color)
))
return i + ATTR_OFF;
@@ -6818,6 +6823,8 @@ static int get_attr_entry(garray_T *table, attrentry_T *aep)
} else if (table == &cterm_attr_table) {
taep->ae_u.cterm.fg_color = aep->ae_u.cterm.fg_color;
taep->ae_u.cterm.bg_color = aep->ae_u.cterm.bg_color;
+ taep->fg_color = aep->fg_color;
+ taep->bg_color = aep->bg_color;
}
return table->ga_len - 1 + ATTR_OFF;
@@ -6880,6 +6887,10 @@ int hl_combine_attr(int char_attr, int prim_attr)
new_en.ae_u.cterm.fg_color = spell_aep->ae_u.cterm.fg_color;
if (spell_aep->ae_u.cterm.bg_color > 0)
new_en.ae_u.cterm.bg_color = spell_aep->ae_u.cterm.bg_color;
+ if (spell_aep->fg_color >= 0)
+ new_en.fg_color = spell_aep->fg_color;
+ if (spell_aep->bg_color >= 0)
+ new_en.bg_color = spell_aep->bg_color;
}
}
return get_attr_entry(&cterm_attr_table, &new_en);
@@ -6974,11 +6985,11 @@ static void highlight_list_one(int id)
didh = highlight_list_arg(id, didh, LIST_ATTR,
sgp->sg_gui, NULL, "gui");
didh = highlight_list_arg(id, didh, LIST_STRING,
- 0, sgp->sg_gui_fg_name, "guifg");
+ 0, sgp->sg_rgb_fg_name, "guifg");
didh = highlight_list_arg(id, didh, LIST_STRING,
- 0, sgp->sg_gui_bg_name, "guibg");
+ 0, sgp->sg_rgb_bg_name, "guibg");
didh = highlight_list_arg(id, didh, LIST_STRING,
- 0, sgp->sg_gui_sp_name, "guisp");
+ 0, NULL, "guisp");
if (sgp->sg_link && !got_int) {
(void)syn_list_header(didh, 9999, id);
@@ -7092,10 +7103,10 @@ highlight_color (
return NULL;
if (modec == 'g') {
if (fg)
- return HL_TABLE()[id - 1].sg_gui_fg_name;
+ return HL_TABLE()[id - 1].sg_rgb_fg_name;
if (sp)
- return HL_TABLE()[id - 1].sg_gui_sp_name;
- return HL_TABLE()[id - 1].sg_gui_bg_name;
+ return NULL;
+ return HL_TABLE()[id - 1].sg_rgb_bg_name;
}
if (font || sp)
return NULL;
@@ -7192,9 +7203,14 @@ set_hl_attr (
if (sgp->sg_cterm_fg == 0 && sgp->sg_cterm_bg == 0)
sgp->sg_cterm_attr = sgp->sg_cterm;
else {
- at_en.ae_attr = sgp->sg_cterm;
+ at_en.ae_attr = abstract_ui ? sgp->sg_gui : sgp->sg_cterm;
at_en.ae_u.cterm.fg_color = sgp->sg_cterm_fg;
at_en.ae_u.cterm.bg_color = sgp->sg_cterm_bg;
+ // FIXME(tarruda): The "unset value" for rgb is -1, but since hlgroup is
+ // initialized with 0(by garray functions), check for sg_rgb_{f,b}g_name
+ // before setting attr_entry->{f,g}g_color to a other than -1
+ at_en.fg_color = sgp->sg_rgb_fg_name ? sgp->sg_rgb_fg : -1;
+ at_en.bg_color = sgp->sg_rgb_bg_name ? sgp->sg_rgb_bg : -1;
sgp->sg_cterm_attr = get_attr_entry(&cterm_attr_table, &at_en);
}
}
@@ -7633,6 +7649,200 @@ char_u *get_highlight_name(expand_T *xp, int idx)
}
+RgbValue name_to_color(uint8_t *name)
+{
+#define RGB(r, g, b) ((r << 16) | (g << 8) | b)
+ static struct {
+ char *name;
+ RgbValue color;
+ } color_name_table[] = {
+ // Color names taken from
+ // http://www.rapidtables.com/web/color/RGB_Color.htm
+ {"Maroon", RGB(0x80, 0x00, 0x00)},
+ {"DarkRed", RGB(0x8b, 0x00, 0x00)},
+ {"Brown", RGB(0xa5, 0x2a, 0x2a)},
+ {"Firebrick", RGB(0xb2, 0x22, 0x22)},
+ {"Crimson", RGB(0xdc, 0x14, 0x3c)},
+ {"Red", RGB(0xff, 0x00, 0x00)},
+ {"Tomato", RGB(0xff, 0x63, 0x47)},
+ {"Coral", RGB(0xff, 0x7f, 0x50)},
+ {"IndianRed", RGB(0xcd, 0x5c, 0x5c)},
+ {"LightCoral", RGB(0xf0, 0x80, 0x80)},
+ {"DarkSalmon", RGB(0xe9, 0x96, 0x7a)},
+ {"Salmon", RGB(0xfa, 0x80, 0x72)},
+ {"LightSalmon", RGB(0xff, 0xa0, 0x7a)},
+ {"OrangeRed", RGB(0xff, 0x45, 0x00)},
+ {"DarkOrange", RGB(0xff, 0x8c, 0x00)},
+ {"Orange", RGB(0xff, 0xa5, 0x00)},
+ {"Gold", RGB(0xff, 0xd7, 0x00)},
+ {"DarkGoldenRod", RGB(0xb8, 0x86, 0x0b)},
+ {"GoldenRod", RGB(0xda, 0xa5, 0x20)},
+ {"PaleGoldenRod", RGB(0xee, 0xe8, 0xaa)},
+ {"DarkKhaki", RGB(0xbd, 0xb7, 0x6b)},
+ {"Khaki", RGB(0xf0, 0xe6, 0x8c)},
+ {"Olive", RGB(0x80, 0x80, 0x00)},
+ {"Yellow", RGB(0xff, 0xff, 0x00)},
+ {"YellowGreen", RGB(0x9a, 0xcd, 0x32)},
+ {"DarkOliveGreen", RGB(0x55, 0x6b, 0x2f)},
+ {"OliveDrab", RGB(0x6b, 0x8e, 0x23)},
+ {"LawnGreen", RGB(0x7c, 0xfc, 0x00)},
+ {"ChartReuse", RGB(0x7f, 0xff, 0x00)},
+ {"GreenYellow", RGB(0xad, 0xff, 0x2f)},
+ {"DarkGreen", RGB(0x00, 0x64, 0x00)},
+ {"Green", RGB(0x00, 0x80, 0x00)},
+ {"ForestGreen", RGB(0x22, 0x8b, 0x22)},
+ {"Lime", RGB(0x00, 0xff, 0x00)},
+ {"LimeGreen", RGB(0x32, 0xcd, 0x32)},
+ {"LightGreen", RGB(0x90, 0xee, 0x90)},
+ {"PaleGreen", RGB(0x98, 0xfb, 0x98)},
+ {"DarkSeaGreen", RGB(0x8f, 0xbc, 0x8f)},
+ {"MediumSpringGreen", RGB(0x00, 0xfa, 0x9a)},
+ {"SpringGreen", RGB(0x00, 0xff, 0x7f)},
+ {"SeaGreen", RGB(0x2e, 0x8b, 0x57)},
+ {"MediumAquamarine", RGB(0x66, 0xcd, 0xaa)},
+ {"MediumSeaGreen", RGB(0x3c, 0xb3, 0x71)},
+ {"LightSeaGreen", RGB(0x20, 0xb2, 0xaa)},
+ {"DarkSlateGray", RGB(0x2f, 0x4f, 0x4f)},
+ {"Teal", RGB(0x00, 0x80, 0x80)},
+ {"DarkCyan", RGB(0x00, 0x8b, 0x8b)},
+ {"Aqua", RGB(0x00, 0xff, 0xff)},
+ {"Cyan", RGB(0x00, 0xff, 0xff)},
+ {"LightCyan", RGB(0xe0, 0xff, 0xff)},
+ {"DarkTurquoise", RGB(0x00, 0xce, 0xd1)},
+ {"Turquoise", RGB(0x40, 0xe0, 0xd0)},
+ {"MediumTurquoise", RGB(0x48, 0xd1, 0xcc)},
+ {"PaleTurquoise", RGB(0xaf, 0xee, 0xee)},
+ {"Aquamarine", RGB(0x7f, 0xff, 0xd4)},
+ {"PowderBlue", RGB(0xb0, 0xe0, 0xe6)},
+ {"CadetBlue", RGB(0x5f, 0x9e, 0xa0)},
+ {"SteelBlue", RGB(0x46, 0x82, 0xb4)},
+ {"CornFlowerBlue", RGB(0x64, 0x95, 0xed)},
+ {"DeepSkyBlue", RGB(0x00, 0xbf, 0xff)},
+ {"DodgerBlue", RGB(0x1e, 0x90, 0xff)},
+ {"LightBlue", RGB(0xad, 0xd8, 0xe6)},
+ {"SkyBlue", RGB(0x87, 0xce, 0xeb)},
+ {"LightSkyBlue", RGB(0x87, 0xce, 0xfa)},
+ {"MidnightBlue", RGB(0x19, 0x19, 0x70)},
+ {"Navy", RGB(0x00, 0x00, 0x80)},
+ {"DarkBlue", RGB(0x00, 0x00, 0x8b)},
+ {"MediumBlue", RGB(0x00, 0x00, 0xcd)},
+ {"Blue", RGB(0x00, 0x00, 0xff)},
+ {"RoyalBlue", RGB(0x41, 0x69, 0xe1)},
+ {"BlueViolet", RGB(0x8a, 0x2b, 0xe2)},
+ {"Indigo", RGB(0x4b, 0x00, 0x82)},
+ {"DarkSlateBlue", RGB(0x48, 0x3d, 0x8b)},
+ {"SlateBlue", RGB(0x6a, 0x5a, 0xcd)},
+ {"MediumSlateBlue", RGB(0x7b, 0x68, 0xee)},
+ {"MediumPurple", RGB(0x93, 0x70, 0xdb)},
+ {"DarkMagenta", RGB(0x8b, 0x00, 0x8b)},
+ {"DarkViolet", RGB(0x94, 0x00, 0xd3)},
+ {"DarkOrchid", RGB(0x99, 0x32, 0xcc)},
+ {"MediumOrchid", RGB(0xba, 0x55, 0xd3)},
+ {"Purple", RGB(0x80, 0x00, 0x80)},
+ {"Thistle", RGB(0xd8, 0xbf, 0xd8)},
+ {"Plum", RGB(0xdd, 0xa0, 0xdd)},
+ {"Violet", RGB(0xee, 0x82, 0xee)},
+ {"Magenta", RGB(0xff, 0x00, 0xff)},
+ {"Fuchsia", RGB(0xff, 0x00, 0xff)},
+ {"Orchid", RGB(0xda, 0x70, 0xd6)},
+ {"MediumVioletRed", RGB(0xc7, 0x15, 0x85)},
+ {"PaleVioletRed", RGB(0xdb, 0x70, 0x93)},
+ {"DeepPink", RGB(0xff, 0x14, 0x93)},
+ {"HotPink", RGB(0xff, 0x69, 0xb4)},
+ {"LightPink", RGB(0xff, 0xb6, 0xc1)},
+ {"Pink", RGB(0xff, 0xc0, 0xcb)},
+ {"AntiqueWhite", RGB(0xfa, 0xeb, 0xd7)},
+ {"Beige", RGB(0xf5, 0xf5, 0xdc)},
+ {"Bisque", RGB(0xff, 0xe4, 0xc4)},
+ {"BlanchedAlmond", RGB(0xff, 0xeb, 0xcd)},
+ {"Wheat", RGB(0xf5, 0xde, 0xb3)},
+ {"Cornsilk", RGB(0xff, 0xf8, 0xdc)},
+ {"LemonChiffon", RGB(0xff, 0xfa, 0xcd)},
+ {"LightGoldenRodYellow", RGB(0xfa, 0xfa, 0xd2)},
+ {"LightYellow", RGB(0xff, 0xff, 0xe0)},
+ {"SaddleBrown", RGB(0x8b, 0x45, 0x13)},
+ {"Sienna", RGB(0xa0, 0x52, 0x2d)},
+ {"Chocolate", RGB(0xd2, 0x69, 0x1e)},
+ {"Peru", RGB(0xcd, 0x85, 0x3f)},
+ {"SandyBrown", RGB(0xf4, 0xa4, 0x60)},
+ {"BurlyWood", RGB(0xde, 0xb8, 0x87)},
+ {"Tan", RGB(0xd2, 0xb4, 0x8c)},
+ {"RosyBrown", RGB(0xbc, 0x8f, 0x8f)},
+ {"Moccasin", RGB(0xff, 0xe4, 0xb5)},
+ {"NavajoWhite", RGB(0xff, 0xde, 0xad)},
+ {"PeachPuff", RGB(0xff, 0xda, 0xb9)},
+ {"MistyRose", RGB(0xff, 0xe4, 0xe1)},
+ {"LavenderBlush", RGB(0xff, 0xf0, 0xf5)},
+ {"Linen", RGB(0xfa, 0xf0, 0xe6)},
+ {"Oldlace", RGB(0xfd, 0xf5, 0xe6)},
+ {"PapayaWhip", RGB(0xff, 0xef, 0xd5)},
+ {"SeaShell", RGB(0xff, 0xf5, 0xee)},
+ {"MintCream", RGB(0xf5, 0xff, 0xfa)},
+ {"SlateGray", RGB(0x70, 0x80, 0x90)},
+ {"LightSlateGray", RGB(0x77, 0x88, 0x99)},
+ {"LightSteelBlue", RGB(0xb0, 0xc4, 0xde)},
+ {"Lavender", RGB(0xe6, 0xe6, 0xfa)},
+ {"FloralWhite", RGB(0xff, 0xfa, 0xf0)},
+ {"AliceBlue", RGB(0xf0, 0xf8, 0xff)},
+ {"GhostWhite", RGB(0xf8, 0xf8, 0xff)},
+ {"Honeydew", RGB(0xf0, 0xff, 0xf0)},
+ {"Ivory", RGB(0xff, 0xff, 0xf0)},
+ {"Azure", RGB(0xf0, 0xff, 0xff)},
+ {"Snow", RGB(0xff, 0xfa, 0xfa)},
+ {"Black", RGB(0x00, 0x00, 0x00)},
+ {"DimGray", RGB(0x69, 0x69, 0x69)},
+ {"DimGrey", RGB(0x69, 0x69, 0x69)},
+ {"Gray", RGB(0x80, 0x80, 0x80)},
+ {"Grey", RGB(0x80, 0x80, 0x80)},
+ {"DarkGray", RGB(0xa9, 0xa9, 0xa9)},
+ {"DarkGrey", RGB(0xa9, 0xa9, 0xa9)},
+ {"Silver", RGB(0xc0, 0xc0, 0xc0)},
+ {"LightGray", RGB(0xd3, 0xd3, 0xd3)},
+ {"LightGrey", RGB(0xd3, 0xd3, 0xd3)},
+ {"Gainsboro", RGB(0xdc, 0xdc, 0xdc)},
+ {"WhiteSmoke", RGB(0xf5, 0xf5, 0xf5)},
+ {"White", RGB(0xff, 0xff, 0xff)},
+ // The color names below were taken from gui_x11.c in vim source
+ {"LightRed", RGB(0xff, 0xbb, 0xbb)},
+ {"LightMagenta",RGB(0xff, 0xbb, 0xff)},
+ {"DarkYellow", RGB(0xbb, 0xbb, 0x00)},
+ {"Gray10", RGB(0x1a, 0x1a, 0x1a)},
+ {"Grey10", RGB(0x1a, 0x1a, 0x1a)},
+ {"Gray20", RGB(0x33, 0x33, 0x33)},
+ {"Grey20", RGB(0x33, 0x33, 0x33)},
+ {"Gray30", RGB(0x4d, 0x4d, 0x4d)},
+ {"Grey30", RGB(0x4d, 0x4d, 0x4d)},
+ {"Gray40", RGB(0x66, 0x66, 0x66)},
+ {"Grey40", RGB(0x66, 0x66, 0x66)},
+ {"Gray50", RGB(0x7f, 0x7f, 0x7f)},
+ {"Grey50", RGB(0x7f, 0x7f, 0x7f)},
+ {"Gray60", RGB(0x99, 0x99, 0x99)},
+ {"Grey60", RGB(0x99, 0x99, 0x99)},
+ {"Gray70", RGB(0xb3, 0xb3, 0xb3)},
+ {"Grey70", RGB(0xb3, 0xb3, 0xb3)},
+ {"Gray80", RGB(0xcc, 0xcc, 0xcc)},
+ {"Grey80", RGB(0xcc, 0xcc, 0xcc)},
+ {"Gray90", RGB(0xe5, 0xe5, 0xe5)},
+ {"Grey90", RGB(0xe5, 0xe5, 0xe5)},
+ {NULL, 0},
+ };
+
+ if (name[0] == '#' && isxdigit(name[1]) && isxdigit(name[2])
+ && isxdigit(name[3]) && isxdigit(name[4]) && isxdigit(name[5])
+ && isxdigit(name[6]) && name[7] == NUL) {
+ // rgb hex string
+ return strtol((char *)(name + 1), NULL, 16);
+ }
+
+ for (int i = 0; color_name_table[i].name != NULL; i++) {
+ if (!STRICMP(name, color_name_table[i].name)) {
+ return color_name_table[i].color;
+ }
+ }
+
+ return -1;
+}
+
/**************************************
* End of Highlighting stuff *
**************************************/
diff --git a/src/nvim/syntax.h b/src/nvim/syntax.h
index a03bd1e604..9a284c8a8d 100644
--- a/src/nvim/syntax.h
+++ b/src/nvim/syntax.h
@@ -5,8 +5,6 @@
#include "nvim/buffer_defs.h"
-typedef int guicolor_T;
-
/*
* Terminal highlighting attribute bits.
* Attributes above HL_ALL are used for syntax highlighting.
diff --git a/src/nvim/syntax_defs.h b/src/nvim/syntax_defs.h
index 11e342f870..abf7ea5a7d 100644
--- a/src/nvim/syntax_defs.h
+++ b/src/nvim/syntax_defs.h
@@ -3,6 +3,8 @@
#include "nvim/regexp_defs.h"
+typedef int32_t RgbValue;
+
# define SST_MIN_ENTRIES 150 /* minimal size for state stack array */
# define SST_MAX_ENTRIES 1000 /* maximal size for state stack array */
# define SST_FIX_STATES 7 /* size of sst_stack[]. */
@@ -70,6 +72,7 @@ struct syn_state {
*/
typedef struct attr_entry {
short ae_attr; /* HL_BOLD, etc. */
+ RgbValue fg_color, bg_color;
union {
struct {
char_u *start; /* start escape sequence */
diff --git a/src/nvim/term.c b/src/nvim/term.c
index 54508b1daa..24969bf90f 100644
--- a/src/nvim/term.c
+++ b/src/nvim/term.c
@@ -161,6 +161,33 @@ static bool detected_8bit = false; // detected 8-bit terminal
static struct builtin_term builtin_termcaps[] =
{
+ // abstract UI pseudo termcap, based on vim's "builtin_gui" termcap
+ {(int)KS_NAME, "abstract_ui"},
+ {(int)KS_CE, "\033|$"},
+ {(int)KS_AL, "\033|i"},
+ {(int)KS_CAL, "\033|%p1%dI"},
+ {(int)KS_DL, "\033|d"},
+ {(int)KS_CDL, "\033|%p1%dD"},
+ {(int)KS_CS, "\033|%p1%d;%p2%dR"},
+ {(int)KS_CL, "\033|C"},
+ // attributes switched on with 'h', off with * 'H'
+ {(int)KS_ME, "\033|31H"}, // HL_ALL
+ {(int)KS_MR, "\033|1h"}, // HL_INVERSE
+ {(int)KS_MD, "\033|2h"}, // HL_BOLD
+ {(int)KS_SE, "\033|16H"}, // HL_STANDOUT
+ {(int)KS_SO, "\033|16h"}, // HL_STANDOUT
+ {(int)KS_UE, "\033|8H"}, // HL_UNDERLINE
+ {(int)KS_US, "\033|8h"}, // HL_UNDERLINE
+ {(int)KS_CZR, "\033|4H"}, // HL_ITALIC
+ {(int)KS_CZH, "\033|4h"}, // HL_ITALIC
+ {(int)KS_VB, "\033|f"},
+ {(int)KS_MS, "y"},
+ {(int)KS_UT, "y"},
+ {(int)KS_LE, "\b"}, // cursor-left = BS
+ {(int)KS_ND, "\014"}, // cursor-right = CTRL-L
+ {(int)KS_CM, "\033|%p1%d;%p2%dM"},
+ // there are no key sequences here, for "abstract_ui" vim key codes are
+ // parsed directly in input_enqueue()
#ifndef NO_BUILTIN_TCAPS
@@ -251,69 +278,6 @@ static struct builtin_term builtin_termcaps[] =
{TERMCAP2KEY('*', '7'), "\233\065\065~"}, /* shifted end key */
# endif
-# if defined(__BEOS__) || defined(ALL_BUILTIN_TCAPS)
- /*
- * almost standard ANSI terminal, default for bebox
- */
- {(int)KS_NAME, "beos-ansi"},
- {(int)KS_CE, "\033[K"},
- {(int)KS_CD, "\033[J"},
- {(int)KS_AL, "\033[L"},
-# ifdef TERMINFO
- {(int)KS_CAL, "\033[%p1%dL"},
-# else
- {(int)KS_CAL, "\033[%dL"},
-# endif
- {(int)KS_DL, "\033[M"},
-# ifdef TERMINFO
- {(int)KS_CDL, "\033[%p1%dM"},
-# else
- {(int)KS_CDL, "\033[%dM"},
-# endif
- {(int)KS_CL, "\033[H\033[2J"},
-#ifdef notyet
- {(int)KS_VI, "[VI]"}, /* cursor invisible, VT320: CSI ? 25 l */
- {(int)KS_VE, "[VE]"}, /* cursor visible, VT320: CSI ? 25 h */
-#endif
- {(int)KS_ME, "\033[m"}, /* normal mode */
- {(int)KS_MR, "\033[7m"}, /* reverse */
- {(int)KS_MD, "\033[1m"}, /* bold */
- {(int)KS_SO, "\033[31m"}, /* standout mode: red */
- {(int)KS_SE, "\033[m"}, /* standout end */
- {(int)KS_CZH, "\033[35m"}, /* italic: purple */
- {(int)KS_CZR, "\033[m"}, /* italic end */
- {(int)KS_US, "\033[4m"}, /* underscore mode */
- {(int)KS_UE, "\033[m"}, /* underscore end */
- {(int)KS_CCO, "8"}, /* allow 8 colors */
-# ifdef TERMINFO
- {(int)KS_CAB, "\033[4%p1%dm"}, /* set background color */
- {(int)KS_CAF, "\033[3%p1%dm"}, /* set foreground color */
-# else
- {(int)KS_CAB, "\033[4%dm"}, /* set background color */
- {(int)KS_CAF, "\033[3%dm"}, /* set foreground color */
-# endif
- {(int)KS_OP, "\033[m"}, /* reset colors */
- {(int)KS_MS, "y"}, /* safe to move cur in reverse mode */
- {(int)KS_UT, "y"}, /* guessed */
- {(int)KS_LE, "\b"},
-# ifdef TERMINFO
- {(int)KS_CM, "\033[%i%p1%d;%p2%dH"},
-# else
- {(int)KS_CM, "\033[%i%d;%dH"},
-# endif
- {(int)KS_SR, "\033M"},
-# ifdef TERMINFO
- {(int)KS_CRI, "\033[%p1%dC"},
-# else
- {(int)KS_CRI, "\033[%dC"},
-# endif
-
- {K_UP, "\033[A"},
- {K_DOWN, "\033[B"},
- {K_LEFT, "\033[D"},
- {K_RIGHT, "\033[C"},
-# endif
-
# if defined(UNIX) || defined(ALL_BUILTIN_TCAPS) || defined(SOME_BUILTIN_TCAPS)
/*
* standard ANSI terminal, default for unix
@@ -1162,6 +1126,10 @@ int set_termname(char_u *term)
if (silent_mode)
return OK;
+ if (!STRCMP(term, "abstract_ui")) {
+ abstract_ui = true;
+ }
+
detected_8bit = false; // reset 8-bit detection
if (term_is_builtin(term)) {
@@ -1829,18 +1797,6 @@ void termcapinit(char_u *name)
/// Write s[len] to the screen.
void term_write(char_u *s, size_t len)
{
- if (embedded_mode) {
- // TODO(tarruda): This is a temporary hack to stop Neovim from writing
- // messages to stdout in embedded mode. In the future, embedded mode will
- // be the only possibility(GUIs will always start neovim with a msgpack-rpc
- // over stdio) and this function won't exist.
- //
- // The reason for this is because before Neovim fully migrates to a
- // msgpack-rpc-driven architecture, we must have a fully functional
- // UI working
- return;
- }
-
(void) fwrite(s, len, 1, stdout);
#ifdef UNIX
@@ -2296,7 +2252,7 @@ void shell_resized_check(void)
*/
void settmode(int tmode)
{
- if (embedded_mode) {
+ if (abstract_ui) {
return;
}
@@ -2340,7 +2296,7 @@ void starttermcap(void)
out_flush();
termcap_active = TRUE;
screen_start(); /* don't know where cursor is now */
- {
+ if (!abstract_ui) {
may_req_termresponse();
/* Immediately check for a response. If t_Co changes, we don't
* want to redraw with wrong colors first. */
@@ -2356,7 +2312,7 @@ void stoptermcap(void)
screen_stop_highlight();
reset_cterm_colors();
if (termcap_active) {
- {
+ if (!abstract_ui) {
/* May need to discard T_CRV or T_U7 response. */
if (crv_status == CRV_SENT || u7_status == U7_SENT) {
# ifdef UNIX
@@ -2545,6 +2501,11 @@ static int cursor_is_off = FALSE;
*/
void cursor_on(void)
{
+ if (abstract_ui) {
+ ui_cursor_on();
+ return;
+ }
+
if (cursor_is_off) {
out_str(T_VE);
cursor_is_off = FALSE;
@@ -2556,6 +2517,11 @@ void cursor_on(void)
*/
void cursor_off(void)
{
+ if (abstract_ui) {
+ ui_cursor_off();
+ return;
+ }
+
if (full_screen) {
if (!cursor_is_off)
out_str(T_VI); /* disable cursor */
@@ -2852,6 +2818,11 @@ void set_mouse_topline(win_T *wp)
*/
int check_termcode(int max_offset, char_u *buf, int bufsize, int *buflen)
{
+ if (abstract_ui) {
+ // codes are parsed by input.c/input_enqueue
+ return 0;
+ }
+
char_u *tp;
char_u *p;
int slen = 0; /* init for GCC */
@@ -3883,6 +3854,10 @@ int find_term_bykeys(char_u *src)
*/
static void gather_termleader(void)
{
+ if (abstract_ui) {
+ return;
+ }
+
int len = 0;
if (check_for_codes)
diff --git a/src/nvim/testdir/dotest.in b/src/nvim/testdir/dotest.in
index b2a0e1a68e..b495f674f8 100644
--- a/src/nvim/testdir/dotest.in
+++ b/src/nvim/testdir/dotest.in
@@ -1,3 +1,3 @@
-:set cp
+:set nocp nomore
:map dotest /^STARTTEST j:set ff=unix cpo-=A :.,/ENDTEST/-1w! Xdotest :set ff& cpo+=A nj0:so! Xdotest dotest
dotest
diff --git a/src/nvim/testdir/test14.in b/src/nvim/testdir/test14.in
index fb987ebc88..6ebec99af6 100644
--- a/src/nvim/testdir/test14.in
+++ b/src/nvim/testdir/test14.in
@@ -13,11 +13,7 @@ vaBiBD:?Bug?,/Piece/-2w! test.out
:s/i/~u~/
:s/o/~~~/
:.w >>test.out
-:if has("ebcdic")
-: let tt = "o\<C-V>193\<C-V>xc2\<C-V>o303 \<C-V>90a\<C-V>xfg\<C-V>o578\<Esc>"
-:else
-: let tt = "o\<C-V>65\<C-V>x42\<C-V>o103 \<C-V>33a\<C-V>xfg\<C-V>o78\<Esc>"
-:endif
+:let tt = "o\<C-V>65\<C-V>x42\<C-V>o103 \<C-V>33a\<C-V>xfg\<C-V>o78\<Esc>"
:exe "normal " . tt
:unlet tt
:.w >>test.out
diff --git a/src/nvim/testdir/test17.in b/src/nvim/testdir/test17.in
index bc542c7625..64534ec77c 100644
--- a/src/nvim/testdir/test17.in
+++ b/src/nvim/testdir/test17.in
@@ -4,11 +4,7 @@ Tests for:
STARTTEST
:so small.vim
-:if has("ebcdic")
-: set isfname=@,240-249,/,.,-,_,+,,,$,:,~,{,}
-:else
-: set isfname=@,48-57,/,.,-,_,+,,,$,:,~,{,}
-:endif
+:set isfname=@,48-57,/,.,-,_,+,,,$,:,~,{,}
:function! DeleteDirectory(dir)
: if has("win16") || has("win32") || has("win64") || has("dos16") || has("dos32")
: exec "silent !rmdir /Q /S " . a:dir
diff --git a/src/nvim/testdir/test40.in b/src/nvim/testdir/test40.in
index d92a18f3d0..ced4572fb8 100644
--- a/src/nvim/testdir/test40.in
+++ b/src/nvim/testdir/test40.in
@@ -2,6 +2,7 @@ Test for "*Cmd" autocommands
STARTTEST
:so small.vim
+:set wildchar=^E
:/^start/,$w! Xxx " write lines below to Xxx
:au BufReadCmd XtestA 0r Xxx|$del
:e XtestA " will read text of Xxd instead
diff --git a/src/nvim/testdir/test48.in b/src/nvim/testdir/test48.in
index 48f4abbf75..25ea2fa154 100644
--- a/src/nvim/testdir/test48.in
+++ b/src/nvim/testdir/test48.in
@@ -4,7 +4,7 @@ STARTTEST
:so small.vim
:set noswf
:set ve=all
--dgg
+j-dgg
:"
:" Insert "keyword keyw", ESC, C CTRL-N, shows "keyword ykeyword".
:" Repeating CTRL-N fixes it. (Mary Ellen Foster)
diff --git a/src/nvim/testdir/test60.in b/src/nvim/testdir/test60.in
index 8835df9e0c..f0f1aecedd 100644
--- a/src/nvim/testdir/test60.in
+++ b/src/nvim/testdir/test60.in
@@ -2,6 +2,7 @@ Tests for the exists() and has() functions. vim: set ft=vim ts=8 sw=2 :
STARTTEST
:so small.vim
+:set wildchar=^E
:function! RunTest(str, result)
if exists(a:str) == a:result
echo "OK"
diff --git a/src/nvim/testdir/test68.in b/src/nvim/testdir/test68.in
index ceaf9af1ab..ca54e942b5 100644
--- a/src/nvim/testdir/test68.in
+++ b/src/nvim/testdir/test68.in
@@ -30,7 +30,7 @@ STARTTEST
/^{/+1
:set tw=3 fo=t
gqgqo
-a 
+a 
ENDTEST
{
@@ -99,7 +99,7 @@ ENDTEST
STARTTEST
/^{/+2
:set tw& fo=a
-I^^
+I^^
ENDTEST
{
diff --git a/src/nvim/testdir/test69.in b/src/nvim/testdir/test69.in
index 674dc32812..26f41e8a29 100644
--- a/src/nvim/testdir/test69.in
+++ b/src/nvim/testdir/test69.in
@@ -15,7 +15,7 @@ STARTTEST
:set tw=2 fo=t
gqgqjgqgqo
XYZ
-abc XYZ
+abc XYZ
ENDTEST
{
@@ -31,7 +31,7 @@ gqgqjgqgqjgqgqjgqgqjgqgqo
Xa
X a
XY
-X Y
+X Y
ENDTEST
{
@@ -55,7 +55,7 @@ aX
abX
abcX
abX c
-abXY
+abXY
ENDTEST
{
@@ -110,7 +110,7 @@ gqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqo
X YZ
XX
XXa
-XXY
+XXY
ENDTEST
{
diff --git a/src/nvim/testdir/test90.in b/src/nvim/testdir/test90.in
index 6bac414f31..3c0d8c030c 100644
--- a/src/nvim/testdir/test90.in
+++ b/src/nvim/testdir/test90.in
@@ -2,7 +2,7 @@ Tests for sha256() function. vim: set ft=vim et ts=2 sw=2 :
STARTTEST
:so small.vim
-:if !has('cryptv') || !exists('*sha256')
+:if !exists('*sha256')
e! test.ok
wq! test.out
:endif
diff --git a/src/nvim/testdir/test_breakindent.in b/src/nvim/testdir/test_breakindent.in
index 150c9430db..0b00c95a85 100644
--- a/src/nvim/testdir/test_breakindent.in
+++ b/src/nvim/testdir/test_breakindent.in
@@ -3,6 +3,7 @@ Test for breakindent
STARTTEST
:so small.vim
:if !exists("+breakindent") | e! test.ok | w! test.out | qa! | endif
+:set wildchar=^E
:10new|:vsp|:vert resize 20
:put =\"\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP\"
:set ts=4 sw=4 sts=4 breakindent
diff --git a/src/nvim/testdir/test_breakindent.ok b/src/nvim/testdir/test_breakindent.ok
index d89d424fb3..a530c18fd3 100644
--- a/src/nvim/testdir/test_breakindent.ok
+++ b/src/nvim/testdir/test_breakindent.ok
@@ -33,13 +33,13 @@ Test 4: Simple breakindent + min width: 18
Test 7: breakindent + shift by +1 + nu + sbr=? briopt:sbr
2 ab
-? m
-? x
+ ? m
+ ? x
Test 8: breakindent + shift:1 + nu + sbr=# list briopt:sbr
2 ^Iabcd
-# opq
-# BCD
+ # opq
+ # BCD
Test 9: breakindent + shift by +1 + 'nu' + sbr=# list
2 ^Iabcd
diff --git a/src/nvim/testdir/test_eval.in b/src/nvim/testdir/test_eval.in
index a29fefc3b6..95a59ee42a 100644
--- a/src/nvim/testdir/test_eval.in
+++ b/src/nvim/testdir/test_eval.in
@@ -1,7 +1,154 @@
+Test for various eval features. vim: set ft=vim :
+
+Note: system clipboard support is not tested. I do not think anybody will thank
+me for messing with clipboard.
+
STARTTEST
:so small.vim
:set encoding=latin1
:set noswapfile
+:lang C
+:fun AppendRegContents(reg)
+ call append('$', printf('%s: type %s; value: %s (%s), expr: %s (%s)', a:reg, getregtype(a:reg), getreg(a:reg), string(getreg(a:reg, 0, 1)), getreg(a:reg, 1), string(getreg(a:reg, 1, 1))))
+endfun
+:command -nargs=? AR :call AppendRegContents(<q-args>)
+:fun SetReg(...)
+ call call('setreg', a:000)
+ call append('$', printf('{{{2 setreg(%s)', string(a:000)[1:-2]))
+ call AppendRegContents(a:1)
+ if a:1 isnot# '='
+ execute "silent normal! Go==\n==\e\"".a:1."P"
+ endif
+endfun
+:fun ErrExe(str)
+ call append('$', 'Executing '.a:str)
+ try
+ execute a:str
+ catch
+ $put =v:exception
+ endtry
+endfun
+:fun Test()
+$put ='{{{1 let tests'
+let @" = 'abc'
+AR "
+let @" = "abc\n"
+AR "
+let @" = "abc\<C-m>"
+AR "
+let @= = '"abc"'
+AR =
+
+$put ='{{{1 Basic setreg tests'
+call SetReg('a', 'abcA', 'c')
+call SetReg('b', 'abcB', 'v')
+call SetReg('c', 'abcC', 'l')
+call SetReg('d', 'abcD', 'V')
+call SetReg('e', 'abcE', 'b')
+call SetReg('f', 'abcF', "\<C-v>")
+call SetReg('g', 'abcG', 'b10')
+call SetReg('h', 'abcH', "\<C-v>10")
+call SetReg('I', 'abcI')
+
+$put ='{{{1 Appending single lines with setreg()'
+call SetReg('A', 'abcAc', 'c')
+call SetReg('A', 'abcAl', 'l')
+call SetReg('A', 'abcAc2','c')
+call SetReg('b', 'abcBc', 'ca')
+call SetReg('b', 'abcBb', 'ba')
+call SetReg('b', 'abcBc2','ca')
+call SetReg('b', 'abcBb2','b50a')
+
+call SetReg('C', 'abcCl', 'l')
+call SetReg('C', 'abcCc', 'c')
+call SetReg('D', 'abcDb', 'b')
+
+call SetReg('E', 'abcEb', 'b')
+call SetReg('E', 'abcEl', 'l')
+call SetReg('F', 'abcFc', 'c')
+
+$put ='{{{1 Appending NL with setreg()'
+call setreg('a', 'abcA2', 'c')
+call setreg('b', 'abcB2', 'v')
+call setreg('c', 'abcC2', 'l')
+call setreg('d', 'abcD2', 'V')
+call setreg('e', 'abcE2', 'b')
+call setreg('f', 'abcF2', "\<C-v>")
+call setreg('g', 'abcG2', 'b10')
+call setreg('h', 'abcH2', "\<C-v>10")
+call setreg('I', 'abcI2')
+
+call SetReg('A', "\n")
+call SetReg('B', "\n", 'c')
+call SetReg('C', "\n")
+call SetReg('D', "\n", 'l')
+call SetReg('E', "\n")
+call SetReg('F', "\n", 'b')
+
+$put ='{{{1 Setting lists with setreg()'
+call SetReg('a', ['abcA3'], 'c')
+call SetReg('b', ['abcB3'], 'l')
+call SetReg('c', ['abcC3'], 'b')
+call SetReg('d', ['abcD3'])
+call SetReg('e', [1, 2, 'abc', 3])
+call SetReg('f', [1, 2, 3])
+
+$put ='{{{1 Appending lists with setreg()'
+call SetReg('A', ['abcA3c'], 'c')
+call SetReg('b', ['abcB3l'], 'la')
+call SetReg('C', ['abcC3b'], 'lb')
+call SetReg('D', ['abcD32'])
+
+call SetReg('A', ['abcA32'])
+call SetReg('B', ['abcB3c'], 'c')
+call SetReg('C', ['abcC3l'], 'l')
+call SetReg('D', ['abcD3b'], 'b')
+
+$put ='{{{1 Appending lists with NL with setreg()'
+call SetReg('A', ["\n", 'abcA3l2'], 'l')
+call SetReg('B', ["\n", 'abcB3c2'], 'c')
+call SetReg('C', ["\n", 'abcC3b2'], 'b')
+call SetReg('D', ["\n", 'abcD3b50'],'b50')
+
+$put ='{{{1 Setting lists with NLs with setreg()'
+call SetReg('a', ['abcA4-0', "\n", "abcA4-2\n", "\nabcA4-3", "abcA4-4\nabcA4-4-2"])
+call SetReg('b', ['abcB4c-0', "\n", "abcB4c-2\n", "\nabcB4c-3", "abcB4c-4\nabcB4c-4-2"], 'c')
+call SetReg('c', ['abcC4l-0', "\n", "abcC4l-2\n", "\nabcC4l-3", "abcC4l-4\nabcC4l-4-2"], 'l')
+call SetReg('d', ['abcD4b-0', "\n", "abcD4b-2\n", "\nabcD4b-3", "abcD4b-4\nabcD4b-4-2"], 'b')
+call SetReg('e', ['abcE4b10-0', "\n", "abcE4b10-2\n", "\nabcE4b10-3", "abcE4b10-4\nabcE4b10-4-2"], 'b10')
+
+$put ='{{{1 Search and expressions'
+call SetReg('/', ['abc/'])
+call SetReg('/', ["abc/\n"])
+call SetReg('=', ['"abc/"'])
+call SetReg('=', ["\"abc/\n\""])
+
+$put ='{{{1 Errors'
+call ErrExe('call setreg()')
+call ErrExe('call setreg(1)')
+call ErrExe('call setreg(1, 2, 3, 4)')
+call ErrExe('call setreg([], 2)')
+call ErrExe('call setreg(1, {})')
+call ErrExe('call setreg(1, 2, [])')
+call ErrExe('call setreg("/", ["1", "2"])')
+call ErrExe('call setreg("=", ["1", "2"])')
+call ErrExe('call setreg(1, ["", "", [], ""])')
+endfun
+:"
+:call Test()
+:"
+:delfunction SetReg
+:delfunction AppendRegContents
+:delfunction ErrExe
+:delfunction Test
+:delcommand AR
+:call garbagecollect(1)
+:"
+:/^start:/+1,$wq! test.out
+:" vim: et ts=4 isk-=\: fmr=???,???
+:call getchar()
+:e test.out
+:%d
:" function name not starting with a capital
:try
diff --git a/src/nvim/testdir/test_eval.ok b/src/nvim/testdir/test_eval.ok
index 63b9156442..061e0cfd2f 100644
--- a/src/nvim/testdir/test_eval.ok
+++ b/src/nvim/testdir/test_eval.ok
Binary files differ
diff --git a/src/nvim/testdir/test_listlbr.in b/src/nvim/testdir/test_listlbr.in
index 75b06b4cc7..36235ea915 100644
--- a/src/nvim/testdir/test_listlbr.in
+++ b/src/nvim/testdir/test_listlbr.in
@@ -3,6 +3,7 @@ Test for linebreak and list option (non-utf8)
STARTTEST
:so small.vim
:if !exists("+linebreak") | e! test.ok | w! test.out | qa! | endif
+:set wildchar=^E
:10new|:vsp|:vert resize 20
:put =\"\tabcdef hijklmn\tpqrstuvwxyz_1060ABCDEFGHIJKLMNOP \"
:norm! zt
diff --git a/src/nvim/testdir/test_listlbr_utf8.in b/src/nvim/testdir/test_listlbr_utf8.in
index ba12adae05..23b3098786 100644
--- a/src/nvim/testdir/test_listlbr_utf8.in
+++ b/src/nvim/testdir/test_listlbr_utf8.in
@@ -3,6 +3,7 @@ Test for linebreak and list option in utf-8 mode
STARTTEST
:so small.vim
:if !exists("+linebreak") | e! test.ok | w! test.out | qa! | endif
+:set wildchar=^E
:so mbyte.vim
:if &enc !=? 'utf-8'|:e! test.ok|:w! test.out|qa!|endif
:10new|:vsp|:vert resize 20
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index eab6251288..9c58193e8c 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -15,20 +15,24 @@
* 3. Input buffer stuff.
*/
+#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
#include "nvim/vim.h"
#include "nvim/ui.h"
+#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
#include "nvim/ex_cmds2.h"
#include "nvim/fold.h"
#include "nvim/main.h"
#include "nvim/mbyte.h"
+#include "nvim/ascii.h"
#include "nvim/misc1.h"
#include "nvim/misc2.h"
+#include "nvim/mbyte.h"
#include "nvim/garray.h"
#include "nvim/memory.h"
#include "nvim/move.h"
@@ -39,27 +43,74 @@
#include "nvim/os/input.h"
#include "nvim/os/signal.h"
#include "nvim/screen.h"
+#include "nvim/syntax.h"
#include "nvim/term.h"
#include "nvim/window.h"
-void ui_write(char_u *s, int len)
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "ui.c.generated.h"
+#endif
+
+#define MAX_UI_COUNT 16
+
+static UI *uis[MAX_UI_COUNT];
+static size_t ui_count = 0;
+static int row, col;
+static struct {
+ int top, bot, left, right;
+} sr;
+static int current_highlight_mask = 0;
+static HlAttrs current_attrs = {
+ false, false, false, false, false, false, -1, -1
+};
+static bool cursor_enabled = true;
+static int height = INT_MAX, width = INT_MAX;
+
+// This set of macros allow us to use UI_CALL to invoke any function on
+// registered UI instances. The functions can have 0-5 arguments(configurable
+// by SELECT_NTH)
+//
+// See http://stackoverflow.com/a/11172679 for a better explanation of how it
+// works.
+#define UI_CALL(...) \
+ do { \
+ for (size_t i = 0; i < ui_count; i++) { \
+ UI *ui = uis[i]; \
+ UI_CALL_HELPER(CNT(__VA_ARGS__), __VA_ARGS__); \
+ } \
+ } while (0)
+#define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, MORE, ZERO, ignore)
+#define SELECT_NTH(a1, a2, a3, a4, a5, a6, ...) a6
+#define UI_CALL_HELPER(c, ...) UI_CALL_HELPER2(c, __VA_ARGS__)
+#define UI_CALL_HELPER2(c, ...) UI_CALL_##c(__VA_ARGS__)
+#define UI_CALL_MORE(method, ...) ui->method(ui, __VA_ARGS__)
+#define UI_CALL_ZERO(method) ui->method(ui)
+
+void ui_write(uint8_t *s, int len)
{
- /* Don't output anything in silent mode ("ex -s") unless 'verbose' set */
- if (!(silent_mode && p_verbose == 0)) {
- char_u *tofree = NULL;
+ if (silent_mode && !p_verbose) {
+ // Don't output anything in silent mode ("ex -s") unless 'verbose' set
+ return;
+ }
- if (output_conv.vc_type != CONV_NONE) {
- /* Convert characters from 'encoding' to 'termencoding'. */
- tofree = string_convert(&output_conv, s, &len);
- if (tofree != NULL)
- s = tofree;
- }
+ if (abstract_ui) {
+ parse_abstract_ui_codes(s, len);
+ return;
+ }
- term_write(s, len);
+ char_u *tofree = NULL;
- if (output_conv.vc_type != CONV_NONE)
- free(tofree);
+ if (output_conv.vc_type != CONV_NONE) {
+ /* Convert characters from 'encoding' to 'termencoding'. */
+ tofree = string_convert(&output_conv, s, &len);
+ if (tofree != NULL)
+ s = tofree;
}
+
+ term_write(s, len);
+
+ if (output_conv.vc_type != CONV_NONE)
+ free(tofree);
}
/*
@@ -69,7 +120,11 @@ void ui_write(char_u *s, int len)
*/
void ui_suspend(void)
{
- mch_suspend();
+ if (abstract_ui) {
+ UI_CALL(suspend);
+ } else {
+ mch_suspend();
+ }
}
/*
@@ -79,6 +134,10 @@ void ui_suspend(void)
*/
int ui_get_shellsize(void)
{
+ if (abstract_ui) {
+ return FAIL;
+ }
+
int retval;
retval = mch_get_shellsize();
@@ -98,7 +157,363 @@ int ui_get_shellsize(void)
*/
void ui_cursor_shape(void)
{
- term_cursor_shape();
+ if (abstract_ui) {
+ ui_change_mode();
+ } else {
+ term_cursor_shape();
+ conceal_check_cursur_line();
+ }
+}
+
+void ui_resize(int width, int height)
+{
+ sr.top = 0;
+ sr.bot = height - 1;
+ sr.left = 0;
+ sr.right = width - 1;
+ UI_CALL(resize, width, height);
+}
+
+void ui_cursor_on(void)
+{
+ if (!cursor_enabled) {
+ UI_CALL(cursor_on);
+ cursor_enabled = true;
+ }
+}
+
+void ui_cursor_off(void)
+{
+ if (full_screen) {
+ if (cursor_enabled) {
+ UI_CALL(cursor_off);
+ }
+ cursor_enabled = false;
+ }
+}
+
+void ui_mouse_on(void)
+{
+ if (abstract_ui) {
+ UI_CALL(mouse_on);
+ } else {
+ mch_setmouse(true);
+ }
+}
+
+void ui_mouse_off(void)
+{
+ if (abstract_ui) {
+ UI_CALL(mouse_off);
+ } else {
+ mch_setmouse(false);
+ }
+}
+
+// Notify that the current mode has changed. Can be used to change cursor
+// shape, for example.
+void ui_change_mode(void)
+{
+ static int showing_insert_mode = MAYBE;
+
+ if (!full_screen)
+ return;
+
+ if (State & INSERT) {
+ if (showing_insert_mode != TRUE) {
+ UI_CALL(insert_mode);
+ }
+ showing_insert_mode = TRUE;
+ } else {
+ if (showing_insert_mode != FALSE) {
+ UI_CALL(normal_mode);
+ }
+ showing_insert_mode = FALSE;
+ }
conceal_check_cursur_line();
}
+void ui_attach(UI *ui)
+{
+ if (ui_count == MAX_UI_COUNT) {
+ abort();
+ }
+
+ uis[ui_count++] = ui;
+ resized(ui);
+}
+
+void ui_detach(UI *ui)
+{
+ size_t shift_index = MAX_UI_COUNT;
+
+ // Find the index that will be removed
+ for (size_t i = 0; i < ui_count; i++) {
+ if (uis[i] == ui) {
+ shift_index = i;
+ break;
+ }
+ }
+
+ if (shift_index == MAX_UI_COUNT) {
+ abort();
+ }
+
+ // Shift UIs at "shift_index"
+ while (shift_index < ui_count - 1) {
+ uis[shift_index] = uis[shift_index + 1];
+ shift_index++;
+ }
+
+ ui_count--;
+
+ if (ui->width == width || ui->height == height) {
+ // It is possible that the UI being detached had the smallest screen,
+ // so check for the new minimum dimensions
+ width = height = INT_MAX;
+ for (size_t i = 0; i < ui_count; i++) {
+ check_dimensions(uis[i]);
+ }
+ }
+
+ if (ui_count) {
+ screen_resize(width, height, true);
+ }
+}
+
+static void highlight_start(int mask)
+{
+ if (mask > HL_ALL) {
+ // attribute code
+ current_highlight_mask = mask;
+ } else {
+ // attribute mask
+ current_highlight_mask |= mask;
+ }
+
+ if (!ui_count) {
+ return;
+ }
+
+ set_highlight_args(current_highlight_mask, &current_attrs);
+ UI_CALL(highlight_set, current_attrs);
+}
+
+static void highlight_stop(int mask)
+{
+ if (mask > HL_ALL) {
+ // attribute code
+ current_highlight_mask = HL_NORMAL;
+ } else {
+ // attribute mask
+ current_highlight_mask &= ~mask;
+ }
+
+ set_highlight_args(current_highlight_mask, &current_attrs);
+ UI_CALL(highlight_set, current_attrs);
+}
+
+static void set_highlight_args(int mask, HlAttrs *attrs)
+{
+ attrentry_T *aep = NULL;
+
+ if (mask > HL_ALL) {
+ aep = syn_cterm_attr2entry(mask);
+ mask = aep ? aep->ae_attr : 0;
+ }
+
+ attrs->bold = mask & HL_BOLD;
+ attrs->standout = mask & HL_STANDOUT;
+ attrs->underline = mask & HL_UNDERLINE;
+ attrs->undercurl = mask & HL_UNDERCURL;
+ attrs->italic = mask & HL_ITALIC;
+ attrs->reverse = mask & HL_INVERSE;
+ attrs->foreground = aep && aep->fg_color >= 0 ? aep->fg_color : normal_fg;
+ attrs->background = aep && aep->bg_color >= 0 ? aep->bg_color : normal_bg;
+}
+
+static void parse_abstract_ui_codes(uint8_t *ptr, int len)
+{
+ int arg1 = 0, arg2 = 0;
+ uint8_t *end = ptr + len, *p, c;
+ bool update_cursor = false;
+
+ while (ptr < end) {
+ if (ptr < end - 1 && ptr[0] == ESC && ptr[1] == '|') {
+ p = ptr + 2;
+ assert(p != end);
+
+ if (VIM_ISDIGIT(*p)) {
+ arg1 = (int)getdigits(&p);
+ if (p >= end) {
+ break;
+ }
+
+ if (*p == ';') {
+ p++;
+ arg2 = (int)getdigits(&p);
+ if (p >= end)
+ break;
+ }
+ }
+
+ switch (*p) {
+ case 'C':
+ UI_CALL(clear);
+ break;
+ case 'M':
+ ui_cursor_goto(arg1, arg2);
+ break;
+ case 's':
+ update_cursor = true;
+ break;
+ case 'R':
+ if (arg1 < arg2) {
+ sr.top = arg1;
+ sr.bot = arg2;
+ UI_CALL(set_scroll_region, sr.top, sr.bot, sr.left, sr.right);
+ } else {
+ sr.top = arg2;
+ sr.bot = arg1;
+ UI_CALL(set_scroll_region, sr.top, sr.bot, sr.left, sr.right);
+ }
+ break;
+ case 'V':
+ if (arg1 < arg2) {
+ sr.left = arg1;
+ sr.right = arg2;
+ UI_CALL(set_scroll_region, sr.top, sr.bot, sr.left, sr.right);
+ } else {
+ sr.left = arg2;
+ sr.right = arg1;
+ UI_CALL(set_scroll_region, sr.top, sr.bot, sr.left, sr.right);
+ }
+ break;
+ case 'd':
+ UI_CALL(scroll, 1);
+ break;
+ case 'D':
+ UI_CALL(scroll, arg1);
+ break;
+ case 'i':
+ UI_CALL(scroll, -1);
+ break;
+ case 'I':
+ UI_CALL(scroll, -arg1);
+ break;
+ case '$':
+ UI_CALL(eol_clear);
+ break;
+ case 'h':
+ highlight_start(arg1);
+ break;
+ case 'H':
+ highlight_stop(arg1);
+ break;
+ case 'f':
+ UI_CALL(visual_bell);
+ break;
+ default:
+ // Skip the ESC
+ p = ptr + 1;
+ break;
+ }
+ ptr = ++p;
+ } else if ((c = *ptr) < 0x20) {
+ // Ctrl character
+ if (c == '\n') {
+ ui_linefeed();
+ } else if (c == '\r') {
+ ui_carriage_return();
+ } else if (c == '\b') {
+ ui_cursor_left();
+ } else if (c == Ctrl_L) { // cursor right
+ ui_cursor_right();
+ } else if (c == Ctrl_G) {
+ UI_CALL(bell);
+ }
+ ptr++;
+ } else {
+ p = ptr;
+ while (p < end && (*p >= 0x20)) {
+ size_t clen = (size_t)mb_ptr2len(p);
+ UI_CALL(put, p, (size_t)clen);
+ col++;
+ if (mb_ptr2cells(p) > 1) {
+ // double cell character, blank the next cell
+ UI_CALL(put, NULL, 0);
+ col++;
+ }
+ p += clen;
+ }
+ ptr = p;
+ }
+ }
+
+ if (update_cursor) {
+ ui_cursor_shape();
+ }
+
+ UI_CALL(flush);
+}
+
+static void resized(UI *ui)
+{
+ check_dimensions(ui);
+ screen_resize(width, height, true);
+}
+
+static void check_dimensions(UI *ui)
+{
+ // The internal screen dimensions are always the minimum required to fit on
+ // all connected screens
+ if (ui->width < width) {
+ width = ui->width;
+ }
+
+ if (ui->height < height) {
+ height = ui->height;
+ }
+}
+
+static void ui_linefeed(void)
+{
+ int new_col = 0;
+ int new_row = row;
+ if (new_row < sr.bot) {
+ new_row++;
+ } else {
+ UI_CALL(scroll, 1);
+ }
+ ui_cursor_goto(new_row, new_col);
+}
+
+static void ui_carriage_return(void)
+{
+ int new_col = 0;
+ ui_cursor_goto(row, new_col);
+}
+
+static void ui_cursor_left(void)
+{
+ int new_col = col - 1;
+ assert(new_col >= 0);
+ ui_cursor_goto(row, new_col);
+}
+
+static void ui_cursor_right(void)
+{
+ int new_col = col + 1;
+ assert(new_col < width);
+ ui_cursor_goto(row, new_col);
+}
+
+static void ui_cursor_goto(int new_row, int new_col)
+{
+ if (new_row == row && new_col == col) {
+ return;
+ }
+ row = new_row;
+ col = new_col;
+ UI_CALL(cursor_goto, row, col);
+}
diff --git a/src/nvim/ui.h b/src/nvim/ui.h
index b174af9abe..d0933055cc 100644
--- a/src/nvim/ui.h
+++ b/src/nvim/ui.h
@@ -1,7 +1,39 @@
#ifndef NVIM_UI_H
#define NVIM_UI_H
+#include <stddef.h>
#include <stdbool.h>
+#include <stdint.h>
+
+typedef struct {
+ bool bold, standout, underline, undercurl, italic, reverse;
+ int foreground, background;
+} HlAttrs;
+
+typedef struct ui_t UI;
+
+struct ui_t {
+ int width, height;
+ void *data;
+ void (*resize)(UI *ui, int rows, int columns);
+ void (*clear)(UI *ui);
+ void (*eol_clear)(UI *ui);
+ void (*cursor_goto)(UI *ui, int row, int col);
+ void (*cursor_on)(UI *ui);
+ void (*cursor_off)(UI *ui);
+ void (*mouse_on)(UI *ui);
+ void (*mouse_off)(UI *ui);
+ void (*insert_mode)(UI *ui);
+ void (*normal_mode)(UI *ui);
+ void (*set_scroll_region)(UI *ui, int top, int bot, int left, int right);
+ void (*scroll)(UI *ui, int count);
+ void (*highlight_set)(UI *ui, HlAttrs attrs);
+ void (*put)(UI *ui, uint8_t *str, size_t len);
+ void (*bell)(UI *ui);
+ void (*visual_bell)(UI *ui);
+ void (*flush)(UI *ui);
+ void (*suspend)(UI *ui);
+};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ui.h.generated.h"
diff --git a/src/nvim/version.c b/src/nvim/version.c
index f73e5c8cae..6f37982f4d 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -416,14 +416,14 @@ static int included_patches[] = {
//252 NA
251,
//250 NA
- //249,
+ 249,
248,
247,
//246,
245,
//244,
- //243,
- //242,
+ 243,
+ 242,
241,
240,
239,