diff options
Diffstat (limited to 'src')
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(¶ms); -  } -    /* 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(¶ms); +  } +    /*     * 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(®name); -      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(®name);        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(®name);        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(®name);    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, ®name, 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, ®type, 1); + +  char_u regname = name; +  list_append_string(args, ®name, 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(¤t_state); +# define UNREF_STATEITEM_EXTMATCH(si) unref_extmatch((si)->si_extmatch) +  GA_DEEP_CLEAR(¤t_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 Binary files differindex 63b9156442..061e0cfd2f 100644 --- a/src/nvim/testdir/test_eval.ok +++ b/src/nvim/testdir/test_eval.ok 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, ¤t_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, ¤t_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,  | 
