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, |