diff options
Diffstat (limited to 'src')
33 files changed, 828 insertions, 261 deletions
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 76e3927820..509032892b 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -424,7 +424,8 @@ static void remote_ui_put(UI *ui, const char *cell) static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol, Integer clearcol, Integer clearattr, - const schar_T *chunk, const sattr_T *attrs) + Boolean wrap, const schar_T *chunk, + const sattr_T *attrs) { UIData *data = ui->data; if (ui->ui_ext[kUINewgrid]) { diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index 54508018b5..eb28280457 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -1359,6 +1359,12 @@ static digr_T digraphdefault[] = { 'f', 't', 0xfb05 }, { 's', 't', 0xfb06 }, + // extra alternatives, easier to remember + { 'W', '`', 0x1e80 }, + { 'w', '`', 0x1e81 }, + { 'Y', '`', 0x1ef2 }, + { 'y', '`', 0x1ef3 }, + // Vim 5.x compatible digraphs that don't conflict with the above { '~', '!', 161 }, // ¡ { 'c', '|', 162 }, // ¢ @@ -1520,34 +1526,6 @@ static int getexactdigraph(int char1, int char2, int meta_char) } } - if ((retval != 0) && !enc_utf8) { - char_u buf[6], *to; - vimconv_T vc; - - // Convert the Unicode digraph to 'encoding'. - int i = utf_char2bytes(retval, buf); - retval = 0; - vc.vc_type = CONV_NONE; - - if (convert_setup(&vc, (char_u *)"utf-8", p_enc) == OK) { - vc.vc_fail = true; - assert(i >= 0); - size_t len = (size_t)i; - to = string_convert(&vc, buf, &len); - - if (to != NULL) { - retval = utf_ptr2char(to); - xfree(to); - } - (void)convert_setup(&vc, NULL, NULL); - } - } - - // Ignore multi-byte characters when not in multi-byte mode. - if (!has_mbyte && (retval > 0xff)) { - retval = 0; - } - if (retval == 0) { // digraph deleted or not found if ((char1 == ' ') && meta_char) { @@ -1654,8 +1632,7 @@ void listdigraphs(void) tmp.result = getexactdigraph(tmp.char1, tmp.char2, FALSE); if ((tmp.result != 0) - && (tmp.result != tmp.char2) - && (has_mbyte || (tmp.result <= 255))) { + && (tmp.result != tmp.char2)) { printdigraph(&tmp); } dp++; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 38a080b1ef..b7f5d93bf3 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2076,7 +2076,11 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, return p; } - v = find_var(lp->ll_name, lp->ll_name_len, &ht, flags & GLV_NO_AUTOLOAD); + // Only pass &ht when we would write to the variable, it prevents autoload + // as well. + v = find_var(lp->ll_name, lp->ll_name_len, + (flags & GLV_READ_ONLY) ? NULL : &ht, + flags & GLV_NO_AUTOLOAD); if (v == NULL && !quiet) { emsgf(_("E121: Undefined variable: %.*s"), (int)lp->ll_name_len, lp->ll_name); @@ -8873,9 +8877,14 @@ static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr) char_u buf[FOLD_TEXT_LEN]; foldinfo_T foldinfo; int fold_count; + static bool entered = false; rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; + if (entered) { + return; // reject recursive use + } + entered = true; linenr_T lnum = tv_get_lnum(argvars); // Treat illegal types and illegal string values for {lnum} the same. if (lnum < 0) { @@ -8889,6 +8898,8 @@ static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr) } rettv->vval.v_string = text; } + + entered = false; } /* @@ -9943,7 +9954,7 @@ static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, if (what_arg->v_type == VAR_UNKNOWN) { tv_list_alloc_ret(rettv, kListLenMayKnow); if (is_qf || wp != NULL) { - (void)get_errorlist(wp, -1, rettv->vval.v_list); + (void)get_errorlist(NULL, wp, -1, rettv->vval.v_list); } } else { tv_dict_alloc_ret(rettv); @@ -12138,8 +12149,12 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) if (!get_dict) { // Return a string. if (rhs != NULL) { - rettv->vval.v_string = (char_u *)str2special_save( - (const char *)rhs, false, false); + if (*rhs == NUL) { + rettv->vval.v_string = vim_strsave((char_u *)"<Nop>"); + } else { + rettv->vval.v_string = (char_u *)str2special_save( + (char *)rhs, false, false); + } } } else { @@ -18315,9 +18330,9 @@ varnumber_T get_vim_var_nr(int idx) FUNC_ATTR_PURE return vimvars[idx].vv_nr; } -/* - * Get string v: variable value. Uses a static buffer, can only be used once. - */ +// Get string v: variable value. Uses a static buffer, can only be used once. +// If the String variable has never been set, return an empty string. +// Never returns NULL; char_u *get_vim_var_str(int idx) FUNC_ATTR_PURE FUNC_ATTR_NONNULL_RET { return (char_u *)tv_get_string(&vimvars[idx].vv_tv); @@ -19804,7 +19819,7 @@ void ex_function(exarg_T *eap) // s:func script-local function name // g:func global function name, same as "func" p = eap->arg; - name = trans_function_name(&p, eap->skip, 0, &fudi, NULL); + name = trans_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL); paren = (vim_strchr(p, '(') != NULL); if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { /* @@ -20348,7 +20363,7 @@ trans_function_name( } // Note that TFN_ flags use the same values as GLV_ flags. - end = get_lval((char_u *)start, NULL, &lv, false, skip, flags, + end = get_lval((char_u *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY, lead > 2 ? 0 : FNE_CHECK_START); if (end == start) { if (!skip) diff --git a/src/nvim/event/defs.h b/src/nvim/event/defs.h index 55b2d277bb..fdd4f17d5c 100644 --- a/src/nvim/event/defs.h +++ b/src/nvim/event/defs.h @@ -4,7 +4,7 @@ #include <assert.h> #include <stdarg.h> -#define EVENT_HANDLER_MAX_ARGC 9 +#define EVENT_HANDLER_MAX_ARGC 10 typedef void (*argv_callback)(void **argv); typedef struct message { diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 8b650d0d5b..290de034d7 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -4311,38 +4311,46 @@ static int make_bom(char_u *buf, char_u *name) return (int)(p - buf); } +/// Shorten filename of a buffer. +/// When "force" is TRUE: Use full path from now on for files currently being +/// edited, both for file name and swap file name. Try to shorten the file +/// names a bit, if safe to do so. +/// When "force" is FALSE: Only try to shorten absolute file names. +/// For buffers that have buftype "nofile" or "scratch": never change the file +/// name. +void shorten_buf_fname(buf_T *buf, char_u *dirname, int force) +{ + char_u *p; + + if (buf->b_fname != NULL + && !bt_nofile(buf) + && !path_with_url((char *)buf->b_fname) + && (force + || buf->b_sfname == NULL + || path_is_absolute(buf->b_sfname))) { + xfree(buf->b_sfname); + buf->b_sfname = NULL; + p = path_shorten_fname(buf->b_ffname, dirname); + if (p != NULL) { + buf->b_sfname = vim_strsave(p); + buf->b_fname = buf->b_sfname; + } + if (p == NULL || buf->b_fname == NULL) { + buf->b_fname = buf->b_ffname; + } + } +} + /* * Shorten filenames for all buffers. - * When "force" is TRUE: Use full path from now on for files currently being - * edited, both for file name and swap file name. Try to shorten the file - * names a bit, if safe to do so. - * When "force" is FALSE: Only try to shorten absolute file names. - * For buffers that have buftype "nofile" or "scratch": never change the file - * name. */ void shorten_fnames(int force) { char_u dirname[MAXPATHL]; - char_u *p; os_dirname(dirname, MAXPATHL); FOR_ALL_BUFFERS(buf) { - if (buf->b_fname != NULL - && !bt_nofile(buf) - && !path_with_url((char *)buf->b_fname) - && (force - || buf->b_sfname == NULL - || path_is_absolute(buf->b_sfname))) { - xfree(buf->b_sfname); - buf->b_sfname = NULL; - p = path_shorten_fname(buf->b_ffname, dirname); - if (p != NULL) { - buf->b_sfname = vim_strsave(p); - buf->b_fname = buf->b_sfname; - } - if (p == NULL || buf->b_fname == NULL) - buf->b_fname = buf->b_ffname; - } + shorten_buf_fname(buf, dirname, force); /* Always make the swap file name a full path, a "nofile" buffer may * also have a swap file. */ diff --git a/src/nvim/move.c b/src/nvim/move.c index 41859a489f..1b84628ebc 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -16,7 +16,6 @@ #include <inttypes.h> #include <stdbool.h> -#include "nvim/vim.h" #include "nvim/ascii.h" #include "nvim/move.h" #include "nvim/charset.h" @@ -1726,7 +1725,7 @@ void cursor_correct(void) * * return FAIL for failure, OK otherwise */ -int onepage(int dir, long count) +int onepage(Direction dir, long count) { long n; int retval = OK; @@ -1884,16 +1883,18 @@ int onepage(int dir, long count) } curwin->w_valid &= ~(VALID_WCOL|VALID_WROW|VALID_VIRTCOL); - /* - * Avoid the screen jumping up and down when 'scrolloff' is non-zero. - * But make sure we scroll at least one line (happens with mix of long - * wrapping lines and non-wrapping line). - */ - if (retval == OK && dir == FORWARD && check_top_offset()) { - scroll_cursor_top(1, false); - if (curwin->w_topline <= old_topline - && old_topline < curbuf->b_ml.ml_line_count) { - curwin->w_topline = old_topline + 1; + if (retval == OK && dir == FORWARD) { + // Avoid the screen jumping up and down when 'scrolloff' is non-zero. + // But make sure we scroll at least one line (happens with mix of long + // wrapping lines and non-wrapping line). + if (check_top_offset()) { + scroll_cursor_top(1, false); + if (curwin->w_topline <= old_topline + && old_topline < curbuf->b_ml.ml_line_count) { + curwin->w_topline = old_topline + 1; + (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL); + } + } else if (curwin->w_botline > curbuf->b_ml.ml_line_count) { (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL); } } @@ -2166,9 +2167,7 @@ void do_check_cursorbind(void) restart_edit = restart_edit_save; } // Correct cursor for multi-byte character. - if (has_mbyte) { - mb_adjust_cursor(); - } + mb_adjust_cursor(); redraw_later(VALID); // Only scroll when 'scrollbind' hasn't done this. diff --git a/src/nvim/move.h b/src/nvim/move.h index 00fbcc580f..3670dc9086 100644 --- a/src/nvim/move.h +++ b/src/nvim/move.h @@ -2,8 +2,7 @@ #define NVIM_MOVE_H #include <stdbool.h> -#include "nvim/buffer_defs.h" -#include "nvim/pos.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "move.h.generated.h" diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index a27fee4e90..e52adfa1a9 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -138,8 +138,8 @@ void mch_exit(int r) { exiting = true; - ui_builtin_stop(); ui_flush(); + ui_builtin_stop(); ml_close_all(true); // remove all memfiles if (!event_teardown() && r == 0) { diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 263b8b3a77..7c555da1a0 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -82,6 +82,7 @@ struct qfline_S { /// created using setqflist()/setloclist() with a title and/or user context /// information and entries can be added later using setqflist()/setloclist(). typedef struct qf_list_S { + unsigned qf_id; ///< Unique identifier for this list qfline_T *qf_start; ///< pointer to the first error qfline_T *qf_last; ///< pointer to the last error qfline_T *qf_ptr; ///< pointer to the current error @@ -116,7 +117,8 @@ struct qf_info_S { qf_list_T qf_lists[LISTCOUNT]; }; -static qf_info_T ql_info; /* global quickfix list */ +static qf_info_T ql_info; // global quickfix list +static unsigned last_qf_id = 0; // Last Used quickfix list id #define FMT_PATTERNS 10 /* maximum number of % recognized */ @@ -1224,6 +1226,7 @@ static void qf_new_list(qf_info_T *qi, char_u *qf_title) qi->qf_curlist = qi->qf_listcount++; memset(&qi->qf_lists[qi->qf_curlist], 0, (size_t)(sizeof(qf_list_T))); qf_store_title(qi, qi->qf_curlist, qf_title); + qi->qf_lists[qi->qf_curlist].qf_id = ++last_qf_id; } /* @@ -1467,6 +1470,9 @@ void copy_loclist(win_T *from, win_T *to) to_qfl->qf_index = from_qfl->qf_index; /* current index in the list */ + // Assign a new ID for the location list + to_qfl->qf_id = ++last_qf_id; + /* When no valid entries are present in the list, qf_ptr points to * the first item in the list */ if (to_qfl->qf_nonevalid) { @@ -2234,8 +2240,12 @@ void qf_list(exarg_T *eap) } } - if (qi->qf_lists[qi->qf_curlist].qf_nonevalid) - all = TRUE; + // Shorten all the file names, so that it is easy to read. + shorten_fnames(false); + + if (qi->qf_lists[qi->qf_curlist].qf_nonevalid) { + all = true; + } qfp = qi->qf_lists[qi->qf_curlist].qf_start; for (i = 1; !got_int && i <= qi->qf_lists[qi->qf_curlist].qf_count; ) { if ((qfp->qf_valid || all) && idx1 <= i && i <= idx2) { @@ -2414,7 +2424,7 @@ static void qf_free_items(qf_info_T *qi, int idx) while (qfl->qf_count && qfl->qf_start != NULL) { qfp = qfl->qf_start; qfpnext = qfp->qf_next; - if (qfl->qf_title != NULL && !stop) { + if (!stop) { xfree(qfp->qf_text); stop = (qfp == qfpnext); xfree(qfp->qf_pattern); @@ -2458,6 +2468,7 @@ static void qf_free(qf_info_T *qi, int idx) qfl->qf_title = NULL; tv_free(qfl->qf_ctx); qfl->qf_ctx = NULL; + qfl->qf_id = 0; } /* @@ -2937,6 +2948,10 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) /* Check if there is anything to display */ if (qi->qf_curlist < qi->qf_listcount) { + char_u dirname[MAXPATHL]; + + *dirname = NUL; + // Add one line for each error if (old_last == NULL) { qfp = qi->qf_lists[qi->qf_curlist].qf_start; @@ -2952,6 +2967,14 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) if (qfp->qf_type == 1) { // :helpgrep STRLCPY(IObuff, path_tail(errbuf->b_fname), sizeof(IObuff)); } else { + // shorten the file name if not done already + if (errbuf->b_sfname == NULL + || path_is_absolute(errbuf->b_sfname)) { + if (*dirname == NUL) { + os_dirname(dirname, MAXPATHL); + } + shorten_buf_fname(errbuf, dirname, false); + } STRLCPY(IObuff, errbuf->b_fname, sizeof(IObuff)); } len = (int)STRLEN(IObuff); @@ -4031,18 +4054,22 @@ static void unload_dummy_buffer(buf_T *buf, char_u *dirname_start) /// Add each quickfix error to list "list" as a dictionary. /// If qf_idx is -1, use the current list. Otherwise, use the specified list. -int get_errorlist(win_T *wp, int qf_idx, list_T *list) +int get_errorlist(const qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list) { - qf_info_T *qi = &ql_info; + const qf_info_T *qi = qi_arg; char_u buf[2]; qfline_T *qfp; int i; int bufnum; - if (wp != NULL) { - qi = GET_LOC_LIST(wp); - if (qi == NULL) - return FAIL; + if (qi == NULL) { + qi = &ql_info; + if (wp != NULL) { + qi = GET_LOC_LIST(wp); + if (qi == NULL) { + return FAIL; + } + } } if (qf_idx == -1) { @@ -4106,9 +4133,48 @@ enum { QF_GETLIST_NR = 0x4, QF_GETLIST_WINID = 0x8, QF_GETLIST_CONTEXT = 0x10, + QF_GETLIST_ID = 0x20, QF_GETLIST_ALL = 0xFF }; +// Parse text from 'di' and return the quickfix list items +static int qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict) +{ + int status = FAIL; + char_u *errorformat = p_efm; + dictitem_T *efm_di; + + // Only a List value is supported + if (di->di_tv.v_type == VAR_LIST && di->di_tv.vval.v_list != NULL) { + // If errorformat is supplied then use it, otherwise use the 'efm' + // option setting + if ((efm_di = tv_dict_find(what, S_LEN("efm"))) != NULL) { + if (efm_di->di_tv.v_type != VAR_STRING + || efm_di->di_tv.vval.v_string == NULL) { + return FAIL; + } + errorformat = efm_di->di_tv.vval.v_string; + } + + list_T *l = tv_list_alloc(kListLenMayKnow); + qf_info_T *qi = xmalloc(sizeof(*qi)); + memset(qi, 0, sizeof(*qi)); + qi->qf_refcount++; + + if (qf_init_ext(qi, 0, NULL, NULL, &di->di_tv, errorformat, + true, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) { + (void)get_errorlist(qi, NULL, 0, l); + qf_free(qi, 0); + } + xfree(qi); + + tv_dict_add_list(retdict, S_LEN("items"), l); + status = OK; + } + + return status; +} + /// Return quickfix/location list details (title) as a /// dictionary. 'what' contains the details to return. If 'list_idx' is -1, /// then current list is used. Otherwise the specified list is used. @@ -4117,17 +4183,22 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) qf_info_T *qi = &ql_info; dictitem_T *di; + if ((di = tv_dict_find(what, S_LEN("lines"))) != NULL) { + return qf_get_list_from_lines(what, di, retdict); + } + if (wp != NULL) { qi = GET_LOC_LIST(wp); - if (qi == NULL) { - // If querying for the size of the location list, return 0 - if (((di = tv_dict_find(what, S_LEN("nr"))) != NULL) - && (di->di_tv.v_type == VAR_STRING) - && strequal((const char *)di->di_tv.vval.v_string, "$")) { - return tv_dict_add_nr(retdict, S_LEN("nr"), 0); - } - return FAIL; + } + // List is not present or is empty + if (qi == NULL || qi->qf_listcount == 0) { + // If querying for the size of the list, return 0 + if (((di = tv_dict_find(what, S_LEN("nr"))) != NULL) + && (di->di_tv.v_type == VAR_STRING) + && (STRCMP(di->di_tv.vval.v_string, "$") == 0)) { + return tv_dict_add_nr(retdict, S_LEN("nr"), 0); } + return FAIL; } int status = OK; @@ -4143,44 +4214,51 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { return FAIL; } - } else if (qi->qf_listcount == 0) { // stack is empty - return FAIL; } - flags |= QF_GETLIST_NR; } else if (di->di_tv.v_type == VAR_STRING && strequal((const char *)di->di_tv.vval.v_string, "$")) { // Get the last quickfix list number - if (qi->qf_listcount > 0) { - qf_idx = qi->qf_listcount - 1; - } else { - qf_idx = -1; // Quickfix stack is empty - } - flags |= QF_GETLIST_NR; + qf_idx = qi->qf_listcount - 1; } else { return FAIL; } + flags |= QF_GETLIST_NR; } - if (qf_idx != -1) { - if (tv_dict_find(what, S_LEN("all")) != NULL) { - flags |= QF_GETLIST_ALL; - } - - if (tv_dict_find(what, S_LEN("title")) != NULL) { - flags |= QF_GETLIST_TITLE; - } - - if (tv_dict_find(what, S_LEN("winid")) != NULL) { - flags |= QF_GETLIST_WINID; - } - - if (tv_dict_find(what, S_LEN("context")) != NULL) { - flags |= QF_GETLIST_CONTEXT; + if ((di = tv_dict_find(what, S_LEN("id"))) != NULL) { + // Look for a list with the specified id + if (di->di_tv.v_type == VAR_NUMBER) { + // For zero, use the current list or the list specifed by 'nr' + if (di->di_tv.vval.v_number != 0) { + for (qf_idx = 0; qf_idx < qi->qf_listcount; qf_idx++) { + if (qi->qf_lists[qf_idx].qf_id == di->di_tv.vval.v_number) { + break; + } + } + if (qf_idx == qi->qf_listcount) { + return FAIL; // List not found + } + } + flags |= QF_GETLIST_ID; + } else { + return FAIL; } + } - if (tv_dict_find(what, S_LEN("items")) != NULL) { - flags |= QF_GETLIST_ITEMS; - } + if (tv_dict_find(what, S_LEN("all")) != NULL) { + flags |= QF_GETLIST_ALL; + } + if (tv_dict_find(what, S_LEN("title")) != NULL) { + flags |= QF_GETLIST_TITLE; + } + if (tv_dict_find(what, S_LEN("winid")) != NULL) { + flags |= QF_GETLIST_WINID; + } + if (tv_dict_find(what, S_LEN("context")) != NULL) { + flags |= QF_GETLIST_CONTEXT; + } + if (tv_dict_find(what, S_LEN("items")) != NULL) { + flags |= QF_GETLIST_ITEMS; } if (flags & QF_GETLIST_TITLE) { @@ -4201,7 +4279,7 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) } if ((status == OK) && (flags & QF_GETLIST_ITEMS)) { list_T *l = tv_list_alloc(kListLenMayKnow); - (void)get_errorlist(wp, qf_idx, l); + (void)get_errorlist(qi, NULL, qf_idx, l); tv_dict_add_list(retdict, S_LEN("items"), l); } @@ -4218,6 +4296,10 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) } } + if ((status == OK) && (flags & QF_GETLIST_ID)) { + status = tv_dict_add_nr(retdict, S_LEN("id"), qi->qf_lists[qf_idx].qf_id); + } + return status; } @@ -4336,6 +4418,7 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action, dictitem_T *di; int retval = FAIL; int newlist = false; + char_u *errorformat = p_efm; if (action == ' ' || qi->qf_curlist == qi->qf_listcount) { newlist = true; @@ -4374,6 +4457,22 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action, } } + if (!newlist && (di = tv_dict_find(what, S_LEN("id"))) != NULL) { + // Use the quickfix/location list with the specified id + if (di->di_tv.v_type == VAR_NUMBER) { + for (qf_idx = 0; qf_idx < qi->qf_listcount; qf_idx++) { + if (qi->qf_lists[qf_idx].qf_id == di->di_tv.vval.v_number) { + break; + } + } + if (qf_idx == qi->qf_listcount) { + return FAIL; // List not found + } + } else { + return FAIL; + } + } + if (newlist) { qi->qf_curlist = qf_idx; qf_new_list(qi, title); @@ -4401,16 +4500,20 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action, } } - if ((di = tv_dict_find(what, S_LEN("text"))) != NULL) { - // Only string and list values are supported - if ((di->di_tv.v_type == VAR_STRING - && di->di_tv.vval.v_string != NULL) - || (di->di_tv.v_type == VAR_LIST - && di->di_tv.vval.v_list != NULL)) { + if ((di = tv_dict_find(what, S_LEN("efm"))) != NULL) { + if (di->di_tv.v_type != VAR_STRING || di->di_tv.vval.v_string == NULL) { + return FAIL; + } + errorformat = di->di_tv.vval.v_string; + } + + if ((di = tv_dict_find(what, S_LEN("lines"))) != NULL) { + // Only a List value is supported + if (di->di_tv.v_type == VAR_LIST && di->di_tv.vval.v_list != NULL) { if (action == 'r') { qf_free_items(qi, qf_idx); } - if (qf_init_ext(qi, qf_idx, NULL, NULL, &di->di_tv, p_efm, + if (qf_init_ext(qi, qf_idx, NULL, NULL, &di->di_tv, errorformat, false, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) { retval = OK; } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 69c3d3067d..8ad2145400 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2012,7 +2012,7 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T } screen_line(row + wp->w_winrow, wp->w_wincol, wp->w_width, - wp->w_width, false, wp, wp->w_hl_attr_normal); + wp->w_width, false, wp, wp->w_hl_attr_normal, false); /* * Update w_cline_height and w_cline_folded if the cursor line was @@ -2900,7 +2900,7 @@ win_line ( && filler_todo <= 0 ) { screen_line(screen_row, wp->w_wincol, col, -wp->w_width, wp->w_p_rl, wp, - wp->w_hl_attr_normal); + wp->w_hl_attr_normal, false); // Pretend we have finished updating the window. Except when // 'cursorcolumn' is set. if (wp->w_p_cuc) { @@ -3989,7 +3989,7 @@ win_line ( } } screen_line(screen_row, wp->w_wincol, col, wp->w_width, wp->w_p_rl, wp, - wp->w_hl_attr_normal); + wp->w_hl_attr_normal, false); row++; /* @@ -4197,8 +4197,22 @@ win_line ( || (wp->w_p_list && lcs_eol != NUL && p_extra != at_end_str) || (n_extra != 0 && (c_extra != NUL || *p_extra != NUL))) ) { + bool wrap = wp->w_p_wrap // Wrapping enabled. + && filler_todo <= 0 // Not drawing diff filler lines. + && lcs_eol_one != -1 // Haven't printed the lcs_eol character. + && row != endrow - 1 // Not the last line being displayed. + && wp->w_width == Columns // Window spans the width of the screen. + && !wp->w_p_rl; // Not right-to-left. screen_line(screen_row, wp->w_wincol, col - boguscols, - wp->w_width, wp->w_p_rl, wp, wp->w_hl_attr_normal); + wp->w_width, wp->w_p_rl, wp, wp->w_hl_attr_normal, wrap); + if (wrap) { + // Force a redraw of the first column of the next line. + ScreenAttrs[LineOffset[screen_row + 1]] = -1; + + // Remember that the line wraps, used for modeless copy. + LineWraps[screen_row] = true; + } + boguscols = 0; ++row; ++screen_row; @@ -4225,28 +4239,6 @@ win_line ( break; } - if (ui_current_row() == screen_row - 1 - && filler_todo <= 0 - && wp->w_width == Columns) { - /* Remember that the line wraps, used for modeless copy. */ - LineWraps[screen_row - 1] = TRUE; - - // Special trick to make copy/paste of wrapped lines work with - // xterm/screen: write an extra character beyond the end of - // the line. This will work with all terminal types - // (regardless of the xn,am settings). - // Only do this if the cursor is on the current line - // (something has been written in it). - // Don't do this for double-width characters. - // Don't do this for a window not at the right screen border. - if (utf_off2cells(LineOffset[screen_row], - LineOffset[screen_row] + screen_Columns) != 2 - && utf_off2cells(LineOffset[screen_row - 1] + (int)Columns - 2, - LineOffset[screen_row] + screen_Columns) != 2) { - ui_add_linewrap(screen_row - 1); - } - } - col = 0; off = (unsigned)(current_ScreenLine - ScreenLines); if (wp->w_p_rl) { @@ -4312,9 +4304,11 @@ static int char_needs_redraw(int off_from, int off_to, int cols) * "rlflag" is TRUE in a rightleft window: * When TRUE and "clear_width" > 0, clear columns 0 to "endcol" * When FALSE and "clear_width" > 0, clear columns "endcol" to "clear_width" + * If "wrap" is true, then hint to the UI that "row" contains a line + * which has wrapped into the next row. */ -static void screen_line(int row, int coloff, int endcol, - int clear_width, int rlflag, win_T *wp, int bg_attr) +static void screen_line(int row, int coloff, int endcol, int clear_width, + int rlflag, win_T *wp, int bg_attr, bool wrap) { unsigned off_from; unsigned off_to; @@ -4475,7 +4469,7 @@ static void screen_line(int row, int coloff, int endcol, } if (clear_end > start_dirty) { ui_line(row, coloff+start_dirty, coloff+end_dirty, coloff+clear_end, - bg_attr); + bg_attr, wrap); } } @@ -5435,7 +5429,8 @@ void screen_puts_line_flush(bool set_cursor) if (set_cursor) { ui_cursor_goto(put_dirty_row, put_dirty_last); } - ui_line(put_dirty_row, put_dirty_first, put_dirty_last, put_dirty_last, 0); + ui_line(put_dirty_row, put_dirty_first, put_dirty_last, put_dirty_last, 0, + false); put_dirty_first = -1; put_dirty_last = 0; } @@ -5794,7 +5789,7 @@ void screen_fill(int start_row, int end_row, int start_col, int end_col, int c1, put_dirty_last = MAX(put_dirty_last, dirty_last); } else { int last = c2 != ' ' ? dirty_last : dirty_first + (c1 != ' '); - ui_line(row, dirty_first, last, dirty_last, attr); + ui_line(row, dirty_first, last, dirty_last, attr, false); } } diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 10cbd91e36..9a83a46add 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -15,7 +15,6 @@ export TMPDIR := $(abspath ../../../Xtest-tmpdir) SCRIPTS_DEFAULT = \ test14.out \ - test24.out \ test37.out \ test42.out \ test48.out \ @@ -35,7 +34,7 @@ SCRIPTS ?= $(SCRIPTS_DEFAULT) NEW_TESTS_ALOT := test_alot_utf8 test_alot NEW_TESTS_IN_ALOT := $(shell sed '/^source/ s/^source //;s/\.vim$$//' test_alot*.vim) # Ignored tests. -# test_alot_latin1: Nvim does not allow setting encoding. +# test_alot_latin: Nvim does not allow setting encoding. # test_arglist: ported to Lua, but kept for easier merging. # test_autochdir: ported to Lua, but kept for easier merging. # test_eval_func: used as include in old-style test (test_eval.in). diff --git a/src/nvim/testdir/sautest/autoload/foo.vim b/src/nvim/testdir/sautest/autoload/foo.vim new file mode 100644 index 0000000000..d7dcd5ce3d --- /dev/null +++ b/src/nvim/testdir/sautest/autoload/foo.vim @@ -0,0 +1,7 @@ +let g:loaded_foo_vim += 1 + +let foo#bar = {} + +func foo#bar.echo() + let g:called_foo_bar_echo += 1 +endfunc diff --git a/src/nvim/testdir/sautest/autoload/globone.vim b/src/nvim/testdir/sautest/autoload/globone.vim new file mode 100644 index 0000000000..98c9a10582 --- /dev/null +++ b/src/nvim/testdir/sautest/autoload/globone.vim @@ -0,0 +1 @@ +" used by Test_globpath() diff --git a/src/nvim/testdir/sautest/autoload/globtwo.vim b/src/nvim/testdir/sautest/autoload/globtwo.vim new file mode 100644 index 0000000000..98c9a10582 --- /dev/null +++ b/src/nvim/testdir/sautest/autoload/globtwo.vim @@ -0,0 +1 @@ +" used by Test_globpath() diff --git a/src/nvim/testdir/sautest/autoload/sourced.vim b/src/nvim/testdir/sautest/autoload/sourced.vim new file mode 100644 index 0000000000..f69f00cb53 --- /dev/null +++ b/src/nvim/testdir/sautest/autoload/sourced.vim @@ -0,0 +1,3 @@ +let g:loaded_sourced_vim += 1 +func! sourced#something() +endfunc diff --git a/src/nvim/testdir/test24.in b/src/nvim/testdir/test24.in Binary files differdeleted file mode 100644 index 292f403048..0000000000 --- a/src/nvim/testdir/test24.in +++ /dev/null diff --git a/src/nvim/testdir/test24.ok b/src/nvim/testdir/test24.ok deleted file mode 100644 index cd61210968..0000000000 --- a/src/nvim/testdir/test24.ok +++ /dev/null @@ -1,32 +0,0 @@ -start -test text test text -test text test text -test text test text -test text test text -test text test text -test text test text -test text test text x61 -test text test text x60-x64 -test text test text x78 5 -test text test text o143 -test text test text o140-o144 -test text test text o41 7 -test text test text \%x42 -test text test text \%o103 -test text test text [\x00] -test text test text [\x00-\x10] -test text test text [\x-z] -test text test text [\u-z] -xx xx a -xx aaaaa xx a -xx aaaaa xx a -xx Aaa xx -xx Aaaa xx -xx Aaa xx -xx foobar xA xx -xx an A xx -XX 9; -YY 77; - xyz - bcd - BB diff --git a/src/nvim/testdir/test_autoload.vim b/src/nvim/testdir/test_autoload.vim new file mode 100644 index 0000000000..7396c227c9 --- /dev/null +++ b/src/nvim/testdir/test_autoload.vim @@ -0,0 +1,17 @@ +" Tests for autoload + +set runtimepath=./sautest + +func Test_autoload_dict_func() + let g:loaded_foo_vim = 0 + let g:called_foo_bar_echo = 0 + call g:foo#bar.echo() + call assert_equal(1, g:loaded_foo_vim) + call assert_equal(1, g:called_foo_bar_echo) +endfunc + +func Test_source_autoload() + let g:loaded_sourced_vim = 0 + source sautest/autoload/sourced.vim + call assert_equal(1, g:loaded_sourced_vim) +endfunc diff --git a/src/nvim/testdir/test_comparators.vim b/src/nvim/testdir/test_comparators.vim new file mode 100644 index 0000000000..87be006cf2 --- /dev/null +++ b/src/nvim/testdir/test_comparators.vim @@ -0,0 +1,9 @@ +function Test_Comparators() + try + let oldisident=&isident + set isident+=# + call assert_equal(1, 1 is#1) + finally + let &isident=oldisident + endtry +endfunction diff --git a/src/nvim/testdir/test_escaped_glob.vim b/src/nvim/testdir/test_escaped_glob.vim new file mode 100644 index 0000000000..eaeac28d61 --- /dev/null +++ b/src/nvim/testdir/test_escaped_glob.vim @@ -0,0 +1,33 @@ +" Test whether glob()/globpath() return correct results with certain escaped +" characters. + +function SetUp() + " make sure glob() doesn't use the shell + set shell=doesnotexist + " consistent sorting of file names + set nofileignorecase +endfunction + +function Test_glob() + if !has('unix') + " This test fails on Windows because of the special characters in the + " filenames. Disable the test on non-Unix systems for now. + return + endif + call assert_equal("", glob('Xxx\{')) + call assert_equal("", glob('Xxx\$')) + w! Xxx{ + " } to fix highlighting + w! Xxx\$ + call assert_equal("Xxx{", glob('Xxx\{')) + call assert_equal("Xxx$", glob('Xxx\$')) + call delete('Xxx{') + call delete('Xxx$') +endfunction + +function Test_globpath() + call assert_equal(expand("sautest/autoload/globone.vim\nsautest/autoload/globtwo.vim"), + \ globpath('sautest/autoload', 'glob*.vim')) + call assert_equal([expand('sautest/autoload/globone.vim'), expand('sautest/autoload/globtwo.vim')], + \ globpath('sautest/autoload', 'glob*.vim', 0, 1)) +endfunction diff --git a/src/nvim/testdir/test_exec_while_if.vim b/src/nvim/testdir/test_exec_while_if.vim new file mode 100644 index 0000000000..d6afabff45 --- /dev/null +++ b/src/nvim/testdir/test_exec_while_if.vim @@ -0,0 +1,53 @@ +" Test for :execute, :while and :if + +function Test_exec_while_if() + new + + let i = 0 + while i < 12 + let i = i + 1 + if has("ebcdic") + execute "normal o" . i . "\047" + else + execute "normal o" . i . "\033" + endif + if i % 2 + normal Ax + if i == 9 + break + endif + if i == 5 + continue + else + let j = 9 + while j > 0 + if has("ebcdic") + execute "normal" j . "a" . j . "\x27" + else + execute "normal" j . "a" . j . "\x1b" + endif + let j = j - 1 + endwhile + endif + endif + if i == 9 + if has("ebcdic") + execute "normal Az\047" + else + execute "normal Az\033" + endif + endif + endwhile + unlet i j + + call assert_equal(["", + \ "1x999999999888888887777777666666555554444333221", + \ "2", + \ "3x999999999888888887777777666666555554444333221", + \ "4", + \ "5x", + \ "6", + \ "7x999999999888888887777777666666555554444333221", + \ "8", + \ "9x"], getline(1, 10)) +endfunction diff --git a/src/nvim/testdir/test_exists_autocmd.vim b/src/nvim/testdir/test_exists_autocmd.vim new file mode 100644 index 0000000000..7e44a72653 --- /dev/null +++ b/src/nvim/testdir/test_exists_autocmd.vim @@ -0,0 +1,26 @@ +" Test that groups and patterns are tested correctly when calling exists() for +" autocommands. + +function Test_AutoCommands() + let results=[] + augroup auexists + augroup END + call assert_true(exists("##BufEnter")) + call assert_false(exists("#BufEnter")) + au BufEnter * let g:entered=1 + call assert_true(exists("#BufEnter")) + call assert_false(exists("#auexists#BufEnter")) + augroup auexists + au BufEnter * let g:entered=1 + augroup END + call assert_true(exists("#auexists#BufEnter")) + call assert_false(exists("#BufEnter#*.test")) + au BufEnter *.test let g:entered=1 + call assert_true(exists("#BufEnter#*.test")) + edit testfile.test + call assert_false(exists("#BufEnter#<buffer>")) + au BufEnter <buffer> let g:entered=1 + call assert_true(exists("#BufEnter#<buffer>")) + edit testfile2.test + call assert_false(exists("#BufEnter#<buffer>")) +endfunction diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim index 6917378890..b6a545f959 100644 --- a/src/nvim/testdir/test_fold.vim +++ b/src/nvim/testdir/test_fold.vim @@ -1,5 +1,7 @@ " Test for folding +source view_util.vim + func PrepIndent(arg) return [a:arg] + repeat(["\t".a:arg], 5) endfu @@ -278,6 +280,7 @@ func Test_move_folds_around_manual() call assert_equal(0, foldlevel(6)) call assert_equal(9, foldclosedend(7)) call assert_equal([-1, 2, 2, 2, 2, -1, 7, 7, 7, -1], map(range(1, line('$')), 'foldclosed(v:val)')) + %d " Ensure moving around the edges still works. call setline(1, PrepIndent("a") + repeat(["a"], 3) + ["\ta"]) @@ -634,3 +637,40 @@ func Test_fold_move() set fdm& sw& fdl& enew! endfunc + +func Test_foldtext_recursive() + new + call setline(1, ['{{{', 'some text', '}}}']) + setlocal foldenable foldmethod=marker foldtext=foldtextresult(v\:foldstart) + " This was crashing because of endless recursion. + 2foldclose + redraw + call assert_equal(1, foldlevel(2)) + call assert_equal(1, foldclosed(2)) + call assert_equal(3, foldclosedend(2)) + bwipe! +endfunc + +func Test_fold_last_line_with_pagedown() + enew! + set fdm=manual + + let expect = '+-- 11 lines: 9---' + let content = range(1,19) + call append(0, content) + normal dd9G + normal zfG + normal zt + call assert_equal('9', getline(foldclosed('.'))) + call assert_equal('19', getline(foldclosedend('.'))) + call assert_equal(expect, ScreenLines(1, len(expect))[0]) + call feedkeys("\<C-F>", 'xt') + call assert_equal(expect, ScreenLines(1, len(expect))[0]) + call feedkeys("\<C-F>", 'xt') + call assert_equal(expect, ScreenLines(1, len(expect))[0]) + call feedkeys("\<C-B>\<C-F>\<C-F>", 'xt') + call assert_equal(expect, ScreenLines(1, len(expect))[0]) + + set fdm& + enew! +endfunc diff --git a/src/nvim/testdir/test_getcwd.vim b/src/nvim/testdir/test_getcwd.vim new file mode 100644 index 0000000000..15eab2abbb --- /dev/null +++ b/src/nvim/testdir/test_getcwd.vim @@ -0,0 +1,91 @@ +function! GetCwdInfo(win, tab) + let tab_changed = 0 + let mod = ":t" + if a:tab > 0 && a:tab != tabpagenr() + let tab_changed = 1 + exec "tabnext " . a:tab + endif + let bufname = fnamemodify(bufname(winbufnr(a:win)), mod) + if tab_changed + tabprevious + endif + if a:win == 0 && a:tab == 0 + let dirname = fnamemodify(getcwd(), mod) + let lflag = haslocaldir() + elseif a:tab == 0 + let dirname = fnamemodify(getcwd(a:win), mod) + let lflag = haslocaldir(a:win) + else + let dirname = fnamemodify(getcwd(a:win, a:tab), mod) + let lflag = haslocaldir(a:win, a:tab) + endif + return bufname . ' ' . dirname . ' ' . lflag +endfunction + +" Do all test in a separate window to avoid E211 when we recursively +" delete the Xtopdir directory during cleanup +function SetUp() + set visualbell + set nocp viminfo+=nviminfo + + " On windows a swapfile in Xtopdir prevents it from being cleaned up. + set noswapfile + + " On windows a stale "Xtopdir" directory may exist, remove it so that + " we start from a clean state. + call delete("Xtopdir", "rf") + new + call mkdir('Xtopdir') + cd Xtopdir + call mkdir('Xdir1') + call mkdir('Xdir2') + call mkdir('Xdir3') +endfunction + +let g:cwd=getcwd() +function TearDown() + q + exec "cd " . g:cwd + call delete("Xtopdir", "rf") +endfunction + +function Test_GetCwd() + new a + new b + new c + 3wincmd w + lcd Xdir1 + call assert_equal("a Xdir1 1", GetCwdInfo(0, 0)) + wincmd W + call assert_equal("b Xtopdir 0", GetCwdInfo(0, 0)) + wincmd W + lcd Xdir3 + call assert_equal("c Xdir3 1", GetCwdInfo(0, 0)) + call assert_equal("a Xdir1 1", GetCwdInfo(bufwinnr("a"), 0)) + call assert_equal("b Xtopdir 0", GetCwdInfo(bufwinnr("b"), 0)) + call assert_equal("c Xdir3 1", GetCwdInfo(bufwinnr("c"), 0)) + wincmd W + call assert_equal("a Xdir1 1", GetCwdInfo(bufwinnr("a"), tabpagenr())) + call assert_equal("b Xtopdir 0", GetCwdInfo(bufwinnr("b"), tabpagenr())) + call assert_equal("c Xdir3 1", GetCwdInfo(bufwinnr("c"), tabpagenr())) + + tabnew x + new y + new z + 3wincmd w + call assert_equal("x Xtopdir 0", GetCwdInfo(0, 0)) + wincmd W + lcd Xdir2 + call assert_equal("y Xdir2 1", GetCwdInfo(0, 0)) + wincmd W + lcd Xdir3 + call assert_equal("z Xdir3 1", GetCwdInfo(0, 0)) + call assert_equal("x Xtopdir 0", GetCwdInfo(bufwinnr("x"), 0)) + call assert_equal("y Xdir2 1", GetCwdInfo(bufwinnr("y"), 0)) + call assert_equal("z Xdir3 1", GetCwdInfo(bufwinnr("z"), 0)) + let tp_nr = tabpagenr() + tabrewind + call assert_equal("x Xtopdir 0", GetCwdInfo(3, tp_nr)) + call assert_equal("y Xdir2 1", GetCwdInfo(2, tp_nr)) + call assert_equal("z Xdir3 1", GetCwdInfo(1, tp_nr)) +endfunc diff --git a/src/nvim/testdir/test_largefile.vim b/src/nvim/testdir/test_largefile.vim index 1b3e02a0c8..3f9c2dc150 100644 --- a/src/nvim/testdir/test_largefile.vim +++ b/src/nvim/testdir/test_largefile.vim @@ -1,5 +1,5 @@ " Tests for large files -" This is only executed manually: "make test_largefile". +" This is only executed manually: "TEST_FILE=test_largefile.res make oldtest". " This is not run as part of "make test". func Test_largefile() diff --git a/src/nvim/testdir/test_maparg.vim b/src/nvim/testdir/test_maparg.vim new file mode 100644 index 0000000000..0fb878b04a --- /dev/null +++ b/src/nvim/testdir/test_maparg.vim @@ -0,0 +1,56 @@ +" Tests for maparg(). +" Also test utf8 map with a 0x80 byte. +if !has("multi_byte") + finish +endif + +function s:SID() + return str2nr(matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$')) +endfun + +function Test_maparg() + new + set cpo-=< + set encoding=utf8 + " Test maparg() with a string result + map foo<C-V> is<F4>foo + vnoremap <script> <buffer> <expr> <silent> bar isbar + let sid = s:SID() + call assert_equal("is<F4>foo", maparg('foo<C-V>')) + call assert_equal({'silent': 0, 'noremap': 0, 'lhs': 'foo<C-V>', + \ 'mode': ' ', 'nowait': 0, 'expr': 0, 'sid': sid, 'rhs': 'is<F4>foo', + \ 'buffer': 0}, maparg('foo<C-V>', '', 0, 1)) + call assert_equal({'silent': 1, 'noremap': 1, 'lhs': 'bar', 'mode': 'v', + \ 'nowait': 0, 'expr': 1, 'sid': sid, 'rhs': 'isbar', 'buffer': 1}, + \ maparg('bar', '', 0, 1)) + map <buffer> <nowait> foo bar + call assert_equal({'silent': 0, 'noremap': 0, 'lhs': 'foo', 'mode': ' ', + \ 'nowait': 1, 'expr': 0, 'sid': sid, 'rhs': 'bar', 'buffer': 1}, + \ maparg('foo', '', 0, 1)) + + map abc x<char-114>x + call assert_equal("xrx", maparg('abc')) + map abc y<S-char-114>y + call assert_equal("yRy", maparg('abc')) + + map abc <Nop> + call assert_equal("<Nop>", maparg('abc')) + unmap abc +endfunction + +function Test_range_map() + new + " Outside of the range, minimum + inoremap <Char-0x1040> a + execute "normal a\u1040\<Esc>" + " Inside of the range, minimum + inoremap <Char-0x103f> b + execute "normal a\u103f\<Esc>" + " Inside of the range, maximum + inoremap <Char-0xf03f> c + execute "normal a\uf03f\<Esc>" + " Outside of the range, maximum + inoremap <Char-0xf040> d + execute "normal a\uf040\<Esc>" + call assert_equal("abcd", getline(1)) +endfunction diff --git a/src/nvim/testdir/test_plus_arg_edit.vim b/src/nvim/testdir/test_plus_arg_edit.vim new file mode 100644 index 0000000000..71dbea1991 --- /dev/null +++ b/src/nvim/testdir/test_plus_arg_edit.vim @@ -0,0 +1,10 @@ +" Tests for complicated + argument to :edit command +function Test_edit() + call writefile(["foo|bar"], "Xfile1") + call writefile(["foo/bar"], "Xfile2") + edit +1|s/|/PIPE/|w Xfile1| e Xfile2|1 | s/\//SLASH/|w + call assert_equal(["fooPIPEbar"], readfile("Xfile1")) + call assert_equal(["fooSLASHbar"], readfile("Xfile2")) + call delete('Xfile1') + call delete('Xfile2') +endfunction diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index f603f46d63..624e642e7f 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1879,8 +1879,9 @@ func Xproperty_tests(cchar) call g:Xsetlist([], 'r', {'nr':2,'title':'Fruits','context':['Fruits']}) let l1=g:Xgetlist({'nr':1,'all':1}) let l2=g:Xgetlist({'nr':2,'all':1}) - let l1.nr=2 - let l2.nr=1 + let save_id = l1.id + let l1.id=l2.id + let l2.id=save_id call g:Xsetlist([], 'r', l1) call g:Xsetlist([], 'r', l2) let newl1=g:Xgetlist({'nr':1,'all':1}) @@ -2280,27 +2281,39 @@ func Xsetexpr_tests(cchar) call s:setup_commands(a:cchar) let t = ["File1:10:Line10", "File1:20:Line20"] - call g:Xsetlist([], ' ', {'text' : t}) - call g:Xsetlist([], 'a', {'text' : "File1:30:Line30"}) + call g:Xsetlist([], ' ', {'lines' : t}) + call g:Xsetlist([], 'a', {'lines' : ["File1:30:Line30"]}) let l = g:Xgetlist() call assert_equal(3, len(l)) call assert_equal(20, l[1].lnum) call assert_equal('Line30', l[2].text) - call g:Xsetlist([], 'r', {'text' : "File2:5:Line5"}) + call g:Xsetlist([], 'r', {'lines' : ["File2:5:Line5"]}) let l = g:Xgetlist() call assert_equal(1, len(l)) call assert_equal('Line5', l[0].text) - call assert_equal(-1, g:Xsetlist([], 'a', {'text' : 10})) + call assert_equal(-1, g:Xsetlist([], 'a', {'lines' : 10})) + call assert_equal(-1, g:Xsetlist([], 'a', {'lines' : "F1:10:L10"})) call g:Xsetlist([], 'f') " Add entries to multiple lists - call g:Xsetlist([], 'a', {'nr' : 1, 'text' : ["File1:10:Line10"]}) - call g:Xsetlist([], 'a', {'nr' : 2, 'text' : ["File2:20:Line20"]}) - call g:Xsetlist([], 'a', {'nr' : 1, 'text' : ["File1:15:Line15"]}) - call g:Xsetlist([], 'a', {'nr' : 2, 'text' : ["File2:25:Line25"]}) + call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["File1:10:Line10"]}) + call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["File2:20:Line20"]}) + call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["File1:15:Line15"]}) + call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["File2:25:Line25"]}) call assert_equal('Line15', g:Xgetlist({'nr':1, 'items':1}).items[1].text) call assert_equal('Line25', g:Xgetlist({'nr':2, 'items':1}).items[1].text) + + " Adding entries using a custom efm + set efm& + call g:Xsetlist([], ' ', {'efm' : '%f#%l#%m', + \ 'lines' : ["F1#10#L10", "F2#20#L20"]}) + call assert_equal(20, g:Xgetlist({'items':1}).items[1].lnum) + call g:Xsetlist([], 'a', {'efm' : '%f#%l#%m', 'lines' : ["F3:30:L30"]}) + call assert_equal('F3:30:L30', g:Xgetlist({'items':1}).items[2].text) + call assert_equal(20, g:Xgetlist({'items':1}).items[1].lnum) + call assert_equal(-1, g:Xsetlist([], 'a', {'efm' : [], + \ 'lines' : ['F1:10:L10']})) endfunc func Test_setexpr() @@ -2315,16 +2328,16 @@ func Xmultidirstack_tests(cchar) call g:Xsetlist([], 'f') Xexpr "" | Xexpr "" - call g:Xsetlist([], 'a', {'nr' : 1, 'text' : "Entering dir 'Xone/a'"}) - call g:Xsetlist([], 'a', {'nr' : 2, 'text' : "Entering dir 'Xtwo/a'"}) - call g:Xsetlist([], 'a', {'nr' : 1, 'text' : "one.txt:3:one one one"}) - call g:Xsetlist([], 'a', {'nr' : 2, 'text' : "two.txt:5:two two two"}) + call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["Entering dir 'Xone/a'"]}) + call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["Entering dir 'Xtwo/a'"]}) + call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["one.txt:3:one one one"]}) + call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["two.txt:5:two two two"]}) let l1 = g:Xgetlist({'nr':1, 'items':1}) let l2 = g:Xgetlist({'nr':2, 'items':1}) - call assert_equal('Xone/a/one.txt', bufname(l1.items[1].bufnr)) + call assert_equal(expand('Xone/a/one.txt'), bufname(l1.items[1].bufnr)) call assert_equal(3, l1.items[1].lnum) - call assert_equal('Xtwo/a/two.txt', bufname(l2.items[1].bufnr)) + call assert_equal(expand('Xtwo/a/two.txt'), bufname(l2.items[1].bufnr)) call assert_equal(5, l2.items[1].lnum) endfunc @@ -2352,10 +2365,10 @@ func Xmultifilestack_tests(cchar) call g:Xsetlist([], 'f') Xexpr "" | Xexpr "" - call g:Xsetlist([], 'a', {'nr' : 1, 'text' : "[one.txt]"}) - call g:Xsetlist([], 'a', {'nr' : 2, 'text' : "[two.txt]"}) - call g:Xsetlist([], 'a', {'nr' : 1, 'text' : "(3,5) one one one"}) - call g:Xsetlist([], 'a', {'nr' : 2, 'text' : "(5,9) two two two"}) + call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["[one.txt]"]}) + call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["[two.txt]"]}) + call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["(3,5) one one one"]}) + call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["(5,9) two two two"]}) let l1 = g:Xgetlist({'nr':1, 'items':1}) let l2 = g:Xgetlist({'nr':2, 'items':1}) @@ -2501,3 +2514,97 @@ func Test_add_qf() call XaddQf_tests('c') call XaddQf_tests('l') endfunc + +" Test for getting the quickfix list items from some text without modifying +" the quickfix stack +func XgetListFromLines(cchar) + call s:setup_commands(a:cchar) + call g:Xsetlist([], 'f') + + let l = g:Xgetlist({'lines' : ["File2:20:Line20", "File2:30:Line30"]}).items + call assert_equal(2, len(l)) + call assert_equal(30, l[1].lnum) + + call assert_equal({}, g:Xgetlist({'lines' : 10})) + call assert_equal({}, g:Xgetlist({'lines' : 'File1:10:Line10'})) + call assert_equal([], g:Xgetlist({'lines' : []}).items) + call assert_equal([], g:Xgetlist({'lines' : [10, 20]}).items) + + " Parse text using a custom efm + set efm& + let l = g:Xgetlist({'lines':['File3#30#Line30'], 'efm' : '%f#%l#%m'}).items + call assert_equal('Line30', l[0].text) + let l = g:Xgetlist({'lines':['File3:30:Line30'], 'efm' : '%f-%l-%m'}).items + call assert_equal('File3:30:Line30', l[0].text) + let l = g:Xgetlist({'lines':['File3:30:Line30'], 'efm' : [1,2]}) + call assert_equal({}, l) + call assert_fails("call g:Xgetlist({'lines':['abc'], 'efm':'%2'})", 'E376:') + call assert_fails("call g:Xgetlist({'lines':['abc'], 'efm':''})", 'E378:') + + " Make sure that the quickfix stack is not modified + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) +endfunc + +func Test_get_list_from_lines() + call XgetListFromLines('c') + call XgetListFromLines('l') +endfunc + +" Tests for the quickfix list id +func Xqfid_tests(cchar) + call s:setup_commands(a:cchar) + + call g:Xsetlist([], 'f') + call assert_equal({}, g:Xgetlist({'id':0})) + Xexpr '' + let start_id = g:Xgetlist({'id' : 0}).id + Xexpr '' | Xexpr '' + Xolder + call assert_equal(start_id, g:Xgetlist({'id':0, 'nr':1}).id) + call assert_equal(start_id + 1, g:Xgetlist({'id':0, 'nr':0}).id) + call assert_equal(start_id + 2, g:Xgetlist({'id':0, 'nr':'$'}).id) + call assert_equal({}, g:Xgetlist({'id':0, 'nr':99})) + call assert_equal(2, g:Xgetlist({'id':start_id + 1, 'nr':0}).nr) + call assert_equal({}, g:Xgetlist({'id':99, 'nr':0})) + call assert_equal({}, g:Xgetlist({'id':"abc", 'nr':0})) + + call g:Xsetlist([], 'a', {'id':start_id, 'context':[1,2]}) + call assert_equal([1,2], g:Xgetlist({'nr':1, 'context':1}).context) + call g:Xsetlist([], 'a', {'id':start_id+1, 'lines':['F1:10:L10']}) + call assert_equal('L10', g:Xgetlist({'nr':2, 'items':1}).items[0].text) + call assert_equal(-1, g:Xsetlist([], 'a', {'id':999, 'title':'Vim'})) + call assert_equal(-1, g:Xsetlist([], 'a', {'id':'abc', 'title':'Vim'})) + + let qfid = g:Xgetlist({'id':0, 'nr':0}) + call g:Xsetlist([], 'f') + call assert_equal({}, g:Xgetlist({'id':qfid, 'nr':0})) +endfunc + +func Test_qf_id() + call Xqfid_tests('c') + call Xqfid_tests('l') +endfunc + +" Test for shortening/simplifying the file name when opening the +" quickfix window or when displaying the quickfix list +func Test_shorten_fname() + if !has('unix') + return + endif + %bwipe + " Create a quickfix list with a absolute path filename + let fname = getcwd() . '/test_quickfix.vim' + call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'}) + call assert_equal(fname, bufname('test_quickfix.vim')) + " Opening the quickfix window should simplify the file path + cwindow + call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim')) + cclose + %bwipe + " Create a quickfix list with a absolute path filename + call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'}) + call assert_equal(fname, bufname('test_quickfix.vim')) + " Displaying the quickfix list should simplify the file path + silent! clist + call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim')) +endfunc diff --git a/src/nvim/testdir/test_regex_char_classes.vim b/src/nvim/testdir/test_regex_char_classes.vim new file mode 100644 index 0000000000..2192b5e8fc --- /dev/null +++ b/src/nvim/testdir/test_regex_char_classes.vim @@ -0,0 +1,58 @@ +" Tests for regexp with backslash and other special characters inside [] +" Also test backslash for hex/octal numbered character. + +function RunSTest(value, calls, expected) + new + call feedkeys("i" . a:value, "mx") + exec a:calls + call assert_equal(a:expected, getline(1), printf("wrong result for %s", a:calls)) + quit! +endfunction + +function RunXTest(value, search_exp, expected) + new + call feedkeys("i" . a:value, "mx") + call feedkeys("gg" . a:search_exp . "\nx", "mx") + call assert_equal(a:expected, getline(1), printf("wrong result for %s", a:search_exp)) + quit! +endfunction + + +function Test_x_search() + let res = "test text test text" + call RunXTest("test \\text test text", "/[\\x]", res) + call RunXTest("test \ttext test text", "/[\\t\\]]", res) + call RunXTest("test text ]test text", "/[]y]", res) + call RunXTest("test ]text test text", "/[\\]]", res) + call RunXTest("test text te^st text", "/[y^]", res) + call RunXTest("test te$xt test text", "/[$y]", res) + call RunXTest("test taext test text", "/[\\x61]", res) + call RunXTest("test tbext test text","/[\\x60-\\x64]", res) + call RunXTest("test 5text test text","/[\\x785]", res) + call RunXTest("testc text test text","/[\\o143]", res) + call RunXTest("tesdt text test text","/[\\o140-\\o144]", res) + call RunXTest("test7 text test text", "/[\\o417]", res) + call RunXTest("test text tBest text", "/\\%x42", res) + call RunXTest("test text teCst text", "/\\%o103", res) + call RunXTest("test text \<C-V>x00test text", "/[\\x00]", res) +endfunction + +function Test_s_search() + let res = "test text test text" + call RunSTest("test te\<C-V>x00xt t\<C-V>x04est t\<C-V>x10ext", "s/[\\x00-\\x10]//g", res) + call RunSTest("test \\xyztext test text", "s/[\\x-z]\\+//", res) + call RunSTest("test text tev\\uyst text", "s/[\\u-z]\\{2,}//", res) + call RunSTest("xx aaaaa xx a", "s/\\(a\\)\\+//", "xx xx a") + call RunSTest("xx aaaaa xx a", "s/\\(a*\\)\\+//", "xx aaaaa xx a") + call RunSTest("xx aaaaa xx a", "s/\\(a*\\)*//", "xx aaaaa xx a") + call RunSTest("xx aaaaa xx", "s/\\(a\\)\\{2,3}/A/", "xx Aaa xx") + call RunSTest("xx aaaaa xx", "s/\\(a\\)\\{-2,3}/A/", "xx Aaaa xx") + call RunSTest("xx aaa12aa xx", "s/\\(a\\)*\\(12\\)\\@>/A/", "xx Aaa xx") + call RunSTest("xx foobar xbar xx", "s/\\(foo\\)\\@<!bar/A/", "xx foobar xA xx") + call RunSTest("xx an file xx", "s/\\(an\\_s\\+\\)\\@<=file/A/", "xx an A xx") + call RunSTest("x= 9;", "s/^\\(\\h\\w*\\%(->\\|\\.\\)\\=\\)\\+=/XX/", "XX 9;") + call RunSTest("hh= 77;", "s/^\\(\\h\\w*\\%(->\\|\\.\\)\\=\\)\\+=/YY/", "YY 77;") + call RunSTest(" aaa ", "s/aaa/xyz/", " xyz ") + call RunSTest(" xyz", "s/~/bcd/", " bcd") + call RunSTest(" bcdbcdbcd", "s/~\\+/BB/", " BB") +endfunction diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 0781b03965..841294aaad 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1191,7 +1191,8 @@ static void tui_option_set(UI *ui, String name, Object value) static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol, Integer endcol, Integer clearcol, Integer clearattr, - const schar_T *chunk, const sattr_T *attrs) + Boolean wrap, const schar_T *chunk, + const sattr_T *attrs) { TUIData *data = ui->data; UGrid *grid = &data->grid; @@ -1212,6 +1213,21 @@ static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol, clear_region(ui, (int)linerow, (int)linerow, (int)endcol, (int)clearcol-1, cl_attrs); } + + if (wrap && ui->width == grid->width && linerow + 1 < grid->height) { + // Only do line wrapping if the grid width is equal to the terminal + // width and the line continuation is within the grid. + + if (endcol != grid->width) { + // Print the last cell of the row, if we haven't already done so. + cursor_goto(ui, (int)linerow, grid->width - 1); + print_cell(ui, &grid->cells[linerow][grid->width - 1]); + } + + // Wrap the cursor over to the next line. The next line will be + // printed immediately without an intervening newline. + final_column_wrap(ui); + } } static void invalidate(UI *ui, int top, int bot, int left, int right) diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 07aa032a50..e291111f82 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -80,7 +80,7 @@ static char uilog_last_event[1024] = { 0 }; #endif // UI_CALL invokes a function on all registered UI instances. The functions can -// have 0-5 arguments (configurable by SELECT_NTH). +// have 0-10 arguments (configurable by SELECT_NTH). // // See http://stackoverflow.com/a/11172679 for how it works. #ifdef _MSC_VER @@ -102,9 +102,9 @@ static char uilog_last_event[1024] = { 0 }; } \ } while (0) #endif -#define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, \ - MORE, MORE, MORE, MORE, MORE, ZERO, ignore) -#define SELECT_NTH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, ...) a10 +#define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, MORE, MORE, \ + MORE, MORE, MORE, MORE, ZERO, ignore) +#define SELECT_NTH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, ...) a11 #define UI_CALL_HELPER(c, ...) UI_CALL_HELPER2(c, __VA_ARGS__) // Resolves to UI_CALL_MORE or UI_CALL_ZERO. #define UI_CALL_HELPER2(c, ...) UI_CALL_##c(__VA_ARGS__) @@ -315,10 +315,11 @@ void ui_set_ext_option(UI *ui, UIExtension ext, bool active) } } -void ui_line(int row, int startcol, int endcol, int clearcol, int clearattr) +void ui_line(int row, int startcol, int endcol, int clearcol, int clearattr, + bool wrap) { size_t off = LineOffset[row]+(size_t)startcol; - UI_CALL(raw_line, 1, row, startcol, endcol, clearcol, clearattr, + UI_CALL(raw_line, 1, row, startcol, endcol, clearcol, clearattr, wrap, (const schar_T *)ScreenLines+off, (const sattr_T *)ScreenAttrs+off); if (p_wd) { // 'writedelay': flush & delay each time. int old_row = row, old_col = col; @@ -341,32 +342,6 @@ void ui_cursor_goto(int new_row, int new_col) pending_cursor_update = true; } -void ui_add_linewrap(int row) -{ - // TODO(bfredl): check that this actually still works - // and move to TUI module in that case. -#if 0 - // First make sure we are at the end of the screen line, - // then output the same character again to let the - // terminal know about the wrap. If the terminal doesn't - // auto-wrap, we overwrite the character. - if (ui_current_col() != Columns) { - screen_char(LineOffset[row] + (unsigned)Columns - 1, row, - (int)(Columns - 1)); - } - - // When there is a multi-byte character, just output a - // space to keep it simple. */ - if (ScreenLines[LineOffset[row] + (Columns - 1)][1] != 0) { - ui_putc(' '); - } else { - ui_puts(ScreenLines[LineOffset[row] + (Columns - 1)]); - } - // force a redraw of the first char on the next line - ScreenAttrs[LineOffset[row+1]] = (sattr_T)-1; -#endif -} - void ui_mode_info_set(void) { pending_mode_info_update = true; diff --git a/src/nvim/ui.h b/src/nvim/ui.h index 584d8a77c6..df489f569f 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -47,7 +47,7 @@ struct ui_t { // in to the public grid_line format. void (*raw_line)(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol, Integer clearcol, Integer clearattr, - const schar_T *chunk, const sattr_T *attrs); + Boolean wrap, const schar_T *chunk, const sattr_T *attrs); void (*event)(UI *ui, char *name, Array args, bool *args_consumed); void (*stop)(UI *ui); void (*inspect)(UI *ui, Dictionary *info); diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index a96a24bde7..ebd4651f4d 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -152,24 +152,24 @@ static void ui_bridge_raw_line_event(void **argv) UI *ui = UI(argv[0]); ui->raw_line(ui, PTR2INT(argv[1]), PTR2INT(argv[2]), PTR2INT(argv[3]), PTR2INT(argv[4]), PTR2INT(argv[5]), PTR2INT(argv[6]), - argv[7], argv[8]); - xfree(argv[7]); + PTR2INT(argv[7]), argv[8], argv[9]); xfree(argv[8]); + xfree(argv[9]); } static void ui_bridge_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol, Integer clearcol, Integer clearattr, - const schar_T *chunk, const sattr_T *attrs) + Boolean wrap, const schar_T *chunk, + const sattr_T *attrs) { size_t ncol = (size_t)(endcol-startcol); schar_T *c = xmemdup(chunk, ncol * sizeof(schar_T)); sattr_T *hl = xmemdup(attrs, ncol * sizeof(sattr_T)); - UI_BRIDGE_CALL(ui, raw_line, 9, ui, INT2PTR(grid), INT2PTR(row), + UI_BRIDGE_CALL(ui, raw_line, 10, ui, INT2PTR(grid), INT2PTR(row), INT2PTR(startcol), INT2PTR(endcol), INT2PTR(clearcol), - INT2PTR(clearattr), c, hl); + INT2PTR(clearattr), INT2PTR(wrap), c, hl); } - static void ui_bridge_suspend(UI *b) { UIBridgeData *data = (UIBridgeData *)b; |