aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.asan-blacklist4
-rw-r--r--src/nvim/buffer.c1
-rw-r--r--src/nvim/buffer_defs.h1
-rw-r--r--src/nvim/eval.c5
-rw-r--r--src/nvim/event/loop.c19
-rw-r--r--src/nvim/if_cscope.c4
-rw-r--r--src/nvim/main.c2
-rw-r--r--src/nvim/option.c12
-rw-r--r--src/nvim/option_defs.h2
-rw-r--r--src/nvim/options.lua7
-rw-r--r--src/nvim/quickfix.c275
-rw-r--r--src/nvim/testdir/Makefile1
-rw-r--r--src/nvim/testdir/test_makeencoding.py67
-rw-r--r--src/nvim/testdir/test_makeencoding.vim106
-rw-r--r--src/nvim/testdir/test_quickfix.vim543
-rw-r--r--src/nvim/tui/tui.c42
16 files changed, 960 insertions, 131 deletions
diff --git a/src/.asan-blacklist b/src/.asan-blacklist
index 928d81bd5a..9d7f721a88 100644
--- a/src/.asan-blacklist
+++ b/src/.asan-blacklist
@@ -1,3 +1,7 @@
# multiqueue.h pointer arithmetic is not accepted by asan
fun:multiqueue_node_data
fun:tv_dict_watcher_node_data
+
+# Allocation in loop_schedule_deferred() is freed by loop_deferred_event(), but
+# this sometimes does not happen during teardown.
+func:loop_schedule_deferred
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 766003a021..21830539f5 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -1808,6 +1808,7 @@ void free_buf_options(buf_T *buf, int free_p_ff)
buf->b_p_ul = NO_LOCAL_UNDOLEVEL;
clear_string_option(&buf->b_p_lw);
clear_string_option(&buf->b_p_bkc);
+ clear_string_option(&buf->b_p_menc);
}
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index f1cbcb2627..5702ceaaef 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -637,6 +637,7 @@ struct file_buffer {
uint32_t b_p_fex_flags; ///< flags for 'formatexpr'
char_u *b_p_kp; ///< 'keywordprg'
int b_p_lisp; ///< 'lisp'
+ char_u *b_p_menc; ///< 'makeencoding'
char_u *b_p_mps; ///< 'matchpairs'
int b_p_ml; ///< 'modeline'
int b_p_ml_nobin; ///< b_p_ml saved for binary mode
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 6b04dd4eea..e6cb8cdec0 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -5183,6 +5183,8 @@ bool garbage_collect(bool testing)
ABORTING(set_ref_list)(sub.additional_elements, copyID);
}
+ ABORTING(set_ref_in_quickfix)(copyID);
+
bool did_free = false;
if (!abort) {
// 2. Free lists and dictionaries that are not referenced.
@@ -14611,7 +14613,8 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
return;
}
const char *const act = tv_get_string_chk(action_arg);
- if ((*act == 'a' || *act == 'r' || *act == ' ') && act[1] == NUL) {
+ if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f')
+ && act[1] == NUL) {
action = *act;
} else {
EMSG2(_(e_invact), act);
diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c
index 5adf16c0f3..55ef0261d9 100644
--- a/src/nvim/event/loop.c
+++ b/src/nvim/event/loop.c
@@ -30,19 +30,22 @@ void loop_init(Loop *loop, void *data)
uv_signal_init(&loop->uv, &loop->children_watcher);
uv_timer_init(&loop->uv, &loop->children_kill_timer);
uv_timer_init(&loop->uv, &loop->poll_timer);
+ loop->poll_timer.data = xmalloc(sizeof(bool)); // "timeout expired" flag
}
-void loop_poll_events(Loop *loop, int ms)
+/// @returns true if `ms` timeout was reached
+bool loop_poll_events(Loop *loop, int ms)
{
if (loop->recursive++) {
abort(); // Should not re-enter uv_run
}
uv_run_mode mode = UV_RUN_ONCE;
+ bool timeout_expired = false;
if (ms > 0) {
- // Use a repeating timeout of ms milliseconds to make sure
- // we do not block indefinitely for I/O.
+ *((bool *)loop->poll_timer.data) = false; // reset "timeout expired" flag
+ // Dummy timer to ensure UV_RUN_ONCE does not block indefinitely for I/O.
uv_timer_start(&loop->poll_timer, timer_cb, (uint64_t)ms, (uint64_t)ms);
} else if (ms == 0) {
// For ms == 0, do a non-blocking event poll.
@@ -52,11 +55,13 @@ void loop_poll_events(Loop *loop, int ms)
uv_run(&loop->uv, mode);
if (ms > 0) {
+ timeout_expired = *((bool *)loop->poll_timer.data);
uv_timer_stop(&loop->poll_timer);
}
loop->recursive--; // Can re-enter uv_run now
multiqueue_process_events(loop->fast_events);
+ return timeout_expired;
}
/// Schedules an event from another thread.
@@ -111,7 +116,7 @@ bool loop_close(Loop *loop, bool wait)
uv_mutex_destroy(&loop->mutex);
uv_close((uv_handle_t *)&loop->children_watcher, NULL);
uv_close((uv_handle_t *)&loop->children_kill_timer, NULL);
- uv_close((uv_handle_t *)&loop->poll_timer, NULL);
+ uv_close((uv_handle_t *)&loop->poll_timer, timer_close_cb);
uv_close((uv_handle_t *)&loop->async, NULL);
uint64_t start = wait ? os_hrtime() : 0;
while (true) {
@@ -163,5 +168,11 @@ static void async_cb(uv_async_t *handle)
static void timer_cb(uv_timer_t *handle)
{
+ bool *timeout_expired = handle->data;
+ *timeout_expired = true;
}
+static void timer_close_cb(uv_handle_t *handle)
+{
+ xfree(handle->data);
+}
diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c
index 3c02f5acbd..654b4630c5 100644
--- a/src/nvim/if_cscope.c
+++ b/src/nvim/if_cscope.c
@@ -1012,9 +1012,9 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose,
fclose(f);
if (use_ll) /* Use location list */
wp = curwin;
- /* '-' starts a new error list */
+ // '-' starts a new error list
if (qf_init(wp, tmp, (char_u *)"%f%*\\t%l%*\\t%m",
- *qfpos == '-', cmdline) > 0) {
+ *qfpos == '-', cmdline, NULL) > 0) {
if (postponed_split != 0) {
(void)win_split(postponed_split > 0 ? postponed_split : 0,
postponed_split_flags);
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 0346414697..0b24023ad0 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -1369,7 +1369,7 @@ static void handle_quickfix(mparm_T *paramp)
set_string_option_direct((char_u *)"ef", -1,
paramp->use_ef, OPT_FREE, SID_CARG);
vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef);
- if (qf_init(NULL, p_ef, p_efm, true, IObuff) < 0) {
+ if (qf_init(NULL, p_ef, p_efm, true, IObuff, p_menc) < 0) {
ui_linefeed();
mch_exit(3);
}
diff --git a/src/nvim/option.c b/src/nvim/option.c
index a345906200..192d2b0f78 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -2217,6 +2217,7 @@ void check_buf_options(buf_T *buf)
check_string_option(&buf->b_p_tsr);
check_string_option(&buf->b_p_lw);
check_string_option(&buf->b_p_bkc);
+ check_string_option(&buf->b_p_menc);
}
/*
@@ -2635,8 +2636,8 @@ did_set_string_option (
else if (varp == &p_ei) {
if (check_ei() == FAIL)
errmsg = e_invarg;
- /* 'encoding' and 'fileencoding' */
- } else if (varp == &p_enc || gvarp == &p_fenc) {
+ // 'encoding', 'fileencoding' and 'makeencoding'
+ } else if (varp == &p_enc || gvarp == &p_fenc || gvarp == &p_menc) {
if (gvarp == &p_fenc) {
if (!MODIFIABLE(curbuf) && opt_flags != OPT_GLOBAL) {
errmsg = e_modifiable;
@@ -5400,6 +5401,9 @@ void unset_global_local_option(char *name, void *from)
case PV_LW:
clear_string_option(&buf->b_p_lw);
break;
+ case PV_MENC:
+ clear_string_option(&buf->b_p_menc);
+ break;
}
}
@@ -5433,6 +5437,7 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags)
case PV_UL: return (char_u *)&(curbuf->b_p_ul);
case PV_LW: return (char_u *)&(curbuf->b_p_lw);
case PV_BKC: return (char_u *)&(curbuf->b_p_bkc);
+ case PV_MENC: return (char_u *)&(curbuf->b_p_menc);
}
return NULL; /* "cannot happen" */
}
@@ -5488,6 +5493,8 @@ static char_u *get_varp(vimoption_T *p)
? (char_u *)&(curbuf->b_p_ul) : p->var;
case PV_LW: return *curbuf->b_p_lw != NUL
? (char_u *)&(curbuf->b_p_lw) : p->var;
+ case PV_MENC: return *curbuf->b_p_menc != NUL
+ ? (char_u *)&(curbuf->b_p_menc) : p->var;
case PV_ARAB: return (char_u *)&(curwin->w_p_arab);
case PV_LIST: return (char_u *)&(curwin->w_p_list);
@@ -5885,6 +5892,7 @@ void buf_copy_options(buf_T *buf, int flags)
buf->b_p_qe = vim_strsave(p_qe);
buf->b_p_udf = p_udf;
buf->b_p_lw = empty_option;
+ buf->b_p_menc = empty_option;
/*
* Don't copy the options set by ex_help(), use the saved values,
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index 1b978137ae..864723a10c 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -489,6 +489,7 @@ EXTERN char_u *p_lcs; // 'listchars'
EXTERN int p_lz; // 'lazyredraw'
EXTERN int p_lpl; // 'loadplugins'
EXTERN int p_magic; // 'magic'
+EXTERN char_u *p_menc; // 'makeencoding'
EXTERN char_u *p_mef; // 'makeef'
EXTERN char_u *p_mp; // 'makeprg'
EXTERN char_u *p_cc; // 'colorcolumn'
@@ -736,6 +737,7 @@ enum {
, BV_KP
, BV_LISP
, BV_LW
+ , BV_MENC
, BV_MA
, BV_ML
, BV_MOD
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 45efd49391..35baaf948f 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -1457,6 +1457,13 @@ return {
defaults={if_true={vi=""}}
},
{
+ full_name='makeencoding', abbreviation='menc',
+ type='string', scope={'global', 'buffer'},
+ vi_def=true,
+ varname='p_menc',
+ defaults={if_true={vi=""}}
+ },
+ {
full_name='makeprg', abbreviation='mp',
type='string', scope={'global', 'buffer'},
secure=true,
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 7e2193b8bb..f85009dca8 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -85,6 +85,7 @@ typedef struct qf_list_S {
int qf_nonevalid; // TRUE if not a single valid entry found
char_u *qf_title; // title derived from the command that created
// the error list
+ typval_T *qf_ctx; // context set by setqflist/setloclist
} qf_list_T;
struct qf_info_S {
@@ -161,6 +162,7 @@ typedef struct {
buf_T *buf;
linenr_T buflnum;
linenr_T lnumlast;
+ vimconv_T vc;
} qfstate_T;
typedef struct {
@@ -194,19 +196,19 @@ typedef struct {
static char_u *qf_last_bufname = NULL;
static bufref_T qf_last_bufref = { NULL, 0, 0 };
-/*
- * Read the errorfile "efile" into memory, line by line, building the error
- * list. Set the error list's title to qf_title.
- * Return -1 for error, number of errors for success.
- */
-int
-qf_init (
- win_T *wp,
- char_u *efile,
- char_u *errorformat,
- int newlist, /* TRUE: start a new error list */
- char_u *qf_title
-)
+/// Read the errorfile "efile" into memory, line by line, building the error
+/// list. Set the error list's title to qf_title.
+///
+/// @params wp If non-NULL, make a location list
+/// @params efile If non-NULL, errorfile to parse
+/// @params errorformat 'errorformat' string used to parse the error lines
+/// @params newlist If true, create a new error list
+/// @params qf_title If non-NULL, title of the error list
+/// @params enc If non-NULL, encoding used to parse errors
+///
+/// @returns -1 for error, number of errors for success.
+int qf_init(win_T *wp, char_u *efile, char_u *errorformat, int newlist,
+ char_u *qf_title, char_u *enc)
{
qf_info_T *qi = &ql_info;
@@ -215,8 +217,8 @@ qf_init (
}
return qf_init_ext(qi, efile, curbuf, NULL, errorformat, newlist,
- (linenr_T)0, (linenr_T)0,
- qf_title);
+ (linenr_T)0, (linenr_T)0,
+ qf_title, enc);
}
// Maximum number of bytes allowed per line while reading an errorfile.
@@ -640,6 +642,22 @@ retry:
} else {
state->linebuf = IObuff;
}
+
+ // Convert a line if it contains a non-ASCII character
+ if (state->vc.vc_type != CONV_NONE && has_non_ascii(state->linebuf)) {
+ char_u *line = string_convert(&state->vc, state->linebuf, &state->linelen);
+ if (line != NULL) {
+ if (state->linelen < IOSIZE) {
+ STRLCPY(state->linebuf, line, state->linelen + 1);
+ xfree(line);
+ } else {
+ xfree(state->growbuf);
+ state->linebuf = state->growbuf = line;
+ state->growbufsiz = state->linelen < LINE_MAXLEN
+ ? state->linelen : LINE_MAXLEN;
+ }
+ }
+ }
return QF_OK;
}
@@ -785,7 +803,7 @@ restofline:
fields->type = *regmatch.startp[i];
}
if (fmt_ptr->flags == '+' && !qi->qf_multiscan) { // %+
- if (linelen > fields->errmsglen) {
+ if (linelen >= fields->errmsglen) {
// linelen + null terminator
fields->errmsg = xrealloc(fields->errmsg, linelen + 1);
fields->errmsglen = linelen + 1;
@@ -796,7 +814,7 @@ restofline:
continue;
}
len = (size_t)(regmatch.endp[i] - regmatch.startp[i]);
- if (len > fields->errmsglen) {
+ if (len >= fields->errmsglen) {
// len + null terminator
fields->errmsg = xrealloc(fields->errmsg, len + 1);
fields->errmsglen = len + 1;
@@ -873,7 +891,7 @@ restofline:
fields->namebuf[0] = NUL; // no match found, remove file name
fields->lnum = 0; // don't jump to this line
fields->valid = false;
- if (linelen > fields->errmsglen) {
+ if (linelen >= fields->errmsglen) {
// linelen + null terminator
fields->errmsg = xrealloc(fields->errmsg, linelen + 1);
fields->errmsglen = linelen + 1;
@@ -978,15 +996,15 @@ qf_init_ext(
buf_T *buf,
typval_T *tv,
char_u *errorformat,
- int newlist, /* TRUE: start a new error list */
- linenr_T lnumfirst, /* first line number to use */
- linenr_T lnumlast, /* last line number to use */
- char_u *qf_title
+ int newlist, // TRUE: start a new error list
+ linenr_T lnumfirst, // first line number to use
+ linenr_T lnumlast, // last line number to use
+ char_u *qf_title,
+ char_u *enc
)
{
- qfstate_T state = { NULL, 0, NULL, 0, NULL, NULL, NULL, NULL,
- NULL, NULL, 0, 0 };
- qffields_T fields = { NULL, NULL, 0, 0L, 0, false, NULL, 0, 0, 0 };
+ qfstate_T state;
+ qffields_T fields;
qfline_T *old_last = NULL;
bool adding = false;
static efm_T *fmt_first = NULL;
@@ -999,6 +1017,13 @@ qf_init_ext(
xfree(qf_last_bufname);
qf_last_bufname = NULL;
+ memset(&state, 0, sizeof(state));
+ memset(&fields, 0, sizeof(fields));
+ state.vc.vc_type = CONV_NONE;
+ if (enc != NULL && *enc != NUL) {
+ convert_setup(&state.vc, enc, p_enc);
+ }
+
fields.namebuf = xmalloc(CMDBUFFSIZE + 1);
fields.errmsglen = CMDBUFFSIZE + 1;
fields.errmsg = xmalloc(fields.errmsglen);
@@ -1150,6 +1175,10 @@ qf_init_end:
qf_update_buffer(qi, old_last);
+ if (state.vc.vc_type != CONV_NONE) {
+ convert_setup(&state.vc, NULL, NULL);
+ }
+
return retval;
}
@@ -1384,6 +1413,13 @@ void copy_loclist(win_T *from, win_T *to)
else
to_qfl->qf_title = NULL;
+ if (from_qfl->qf_ctx != NULL) {
+ to_qfl->qf_ctx = xcalloc(1, sizeof(typval_T));
+ tv_copy(from_qfl->qf_ctx, to_qfl->qf_ctx);
+ } else {
+ to_qfl->qf_ctx = NULL;
+ }
+
if (from_qfl->qf_count) {
qfline_T *from_qfp;
qfline_T *prevp;
@@ -2385,12 +2421,21 @@ static void qf_free(qf_info_T *qi, int idx)
qi->qf_lists[idx].qf_start = NULL;
qi->qf_lists[idx].qf_ptr = NULL;
qi->qf_lists[idx].qf_title = NULL;
+ tv_free(qi->qf_lists[idx].qf_ctx);
+ qi->qf_lists[idx].qf_ctx = NULL;
qi->qf_lists[idx].qf_index = 0;
+ qi->qf_lists[idx].qf_start = NULL;
+ qi->qf_lists[idx].qf_last = NULL;
+ qi->qf_lists[idx].qf_ptr = NULL;
+ qi->qf_lists[idx].qf_nonevalid = true;
qf_clean_dir_stack(&qi->qf_dir_stack);
qi->qf_directory = NULL;
qf_clean_dir_stack(&qi->qf_file_stack);
qi->qf_currfile = NULL;
+ qi->qf_multiline = false;
+ qi->qf_multiignore = false;
+ qi->qf_multiscan = false;
}
/*
@@ -3027,6 +3072,7 @@ void ex_make(exarg_T *eap)
qf_info_T *qi = &ql_info;
int res;
char_u *au_name = NULL;
+ char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc;
/* Redirect ":grep" to ":vimgrep" if 'grepprg' is "internal". */
if (grep_internal(eap->cmdidx)) {
@@ -3086,11 +3132,11 @@ void ex_make(exarg_T *eap)
res = qf_init(wp, fname, (eap->cmdidx != CMD_make
&& eap->cmdidx != CMD_lmake) ? p_gefm : p_efm,
- (eap->cmdidx != CMD_grepadd
- && eap->cmdidx != CMD_lgrepadd),
- *eap->cmdlinep);
- if (wp != NULL)
+ (eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd),
+ *eap->cmdlinep, enc);
+ if (wp != NULL) {
qi = GET_LOC_LIST(wp);
+ }
if (au_name != NULL) {
apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name,
curbuf->b_fname, TRUE, curbuf);
@@ -3432,19 +3478,18 @@ void ex_cfile(exarg_T *eap)
if (*eap->arg != NUL)
set_string_option_direct((char_u *)"ef", -1, eap->arg, OPT_FREE, 0);
- /*
- * This function is used by the :cfile, :cgetfile and :caddfile
- * commands.
- * :cfile always creates a new quickfix list and jumps to the
- * first error.
- * :cgetfile creates a new quickfix list but doesn't jump to the
- * first error.
- * :caddfile adds to an existing quickfix list. If there is no
- * quickfix list then a new list is created.
- */
+ char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc;
+ // This function is used by the :cfile, :cgetfile and :caddfile
+ // commands.
+ // :cfile always creates a new quickfix list and jumps to the
+ // first error.
+ // :cgetfile creates a new quickfix list but doesn't jump to the
+ // first error.
+ // :caddfile adds to an existing quickfix list. If there is no
+ // quickfix list then a new list is created.
if (qf_init(wp, p_ef, p_efm, (eap->cmdidx != CMD_caddfile
&& eap->cmdidx != CMD_laddfile),
- *eap->cmdlinep) > 0
+ *eap->cmdlinep, enc) > 0
&& (eap->cmdidx == CMD_cfile
|| eap->cmdidx == CMD_lfile)) {
if (au_name != NULL)
@@ -4028,6 +4073,7 @@ enum {
QF_GETLIST_ITEMS = 0x2,
QF_GETLIST_NR = 0x4,
QF_GETLIST_WINID = 0x8,
+ QF_GETLIST_CONTEXT = 0x10,
QF_GETLIST_ALL = 0xFF
};
@@ -4078,6 +4124,10 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict)
flags |= QF_GETLIST_WINID;
}
+ if (tv_dict_find(what, S_LEN("context")) != NULL) {
+ flags |= QF_GETLIST_CONTEXT;
+ }
+
if (flags & QF_GETLIST_TITLE) {
char_u *t = qi->qf_lists[qf_idx].qf_title;
if (t == NULL) {
@@ -4095,6 +4145,20 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict)
}
}
+ if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) {
+ if (qi->qf_lists[qf_idx].qf_ctx != NULL) {
+ di = tv_dict_item_alloc_len(S_LEN("context"));
+ if (di != NULL) {
+ tv_copy(qi->qf_lists[qf_idx].qf_ctx, &di->di_tv);
+ if (tv_dict_add(retdict, di) == FAIL) {
+ tv_dict_item_free(di);
+ }
+ }
+ } else {
+ status = tv_dict_add_str(retdict, S_LEN("context"), "");
+ }
+ }
+
return status;
}
@@ -4158,6 +4222,11 @@ static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title,
bufnum = 0;
}
+ // If the 'valid' field is present it overrules the detected value.
+ if (tv_dict_find(d, "valid", -1) != NULL) {
+ valid = (int)tv_dict_get_number(d, "valid");
+ }
+
int status = qf_add_entry(qi,
NULL, // dir
(char_u *)filename,
@@ -4213,7 +4282,10 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action)
if ((di = tv_dict_find(what, S_LEN("nr"))) != NULL) {
// Use the specified quickfix/location list
if (di->di_tv.v_type == VAR_NUMBER) {
- qf_idx = (int)di->di_tv.vval.v_number - 1;
+ // for zero use the current list
+ if (di->di_tv.vval.v_number != 0) {
+ qf_idx = (int)di->di_tv.vval.v_number - 1;
+ }
if (qf_idx < 0 || qf_idx >= qi->qf_listcount) {
return FAIL;
}
@@ -4240,9 +4312,75 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action)
}
}
+ if ((di = tv_dict_find(what, S_LEN("context"))) != NULL) {
+ tv_free(qi->qf_lists[qf_idx].qf_ctx);
+
+ typval_T *ctx = xcalloc(1, sizeof(typval_T));
+ tv_copy(&di->di_tv, ctx);
+ qi->qf_lists[qf_idx].qf_ctx = ctx;
+ }
+
return retval;
}
+// Find the non-location list window with the specified location list.
+static win_T * find_win_with_ll(qf_info_T *qi)
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if ((wp->w_llist == qi) && !bt_quickfix(wp->w_buffer)) {
+ return wp;
+ }
+ }
+
+ return NULL;
+}
+
+// Free the entire quickfix/location list stack.
+// If the quickfix/location list window is open, then clear it.
+static void qf_free_stack(win_T *wp, qf_info_T *qi)
+{
+ win_T *qfwin = qf_find_win(qi);
+
+ if (qfwin != NULL) {
+ // If the quickfix/location list window is open, then clear it
+ if (qi->qf_curlist < qi->qf_listcount) {
+ qf_free(qi, qi->qf_curlist);
+ }
+ qf_update_buffer(qi, NULL);
+ }
+
+ win_T *llwin = NULL;
+ win_T *orig_wp = wp;
+ if (wp != NULL && IS_LL_WINDOW(wp)) {
+ // If in the location list window, then use the non-location list
+ // window with this location list (if present)
+ llwin = find_win_with_ll(qi);
+ if (llwin != NULL) {
+ wp = llwin;
+ }
+ }
+
+ qf_free_all(wp);
+ if (wp == NULL) {
+ // quickfix list
+ qi->qf_curlist = 0;
+ qi->qf_listcount = 0;
+ } else if (IS_LL_WINDOW(orig_wp)) {
+ // If the location list window is open, then create a new empty location
+ // list
+ qf_info_T *new_ll = ll_new_list();
+
+ // first free the list reference in the location list window
+ ll_free_all(&orig_wp->w_llist_ref);
+
+ orig_wp->w_llist_ref = new_ll;
+ if (llwin != NULL) {
+ llwin->w_llist = new_ll;
+ new_ll->qf_refcount++;
+ }
+ }
+}
+
// Populate the quickfix list with the items supplied in the list
// of dictionaries. "title" will be copied to w:quickfix_title
// "action" is 'a' for add, 'r' for replace. Otherwise create a new list.
@@ -4256,7 +4394,10 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title,
qi = ll_get_or_alloc_list(wp);
}
- if (what != NULL) {
+ if (action == 'f') {
+ // Free the entire quickfix or location list stack
+ qf_free_stack(wp, qi);
+ } else if (what != NULL) {
retval = qf_set_properties(qi, what, action);
} else {
retval = qf_add_entries(qi, list, title, action);
@@ -4265,6 +4406,42 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title,
return retval;
}
+static bool mark_quickfix_ctx(qf_info_T *qi, int copyID)
+{
+ bool abort = false;
+
+ for (int i = 0; i < LISTCOUNT && !abort; i++) {
+ typval_T *ctx = qi->qf_lists[i].qf_ctx;
+ if (ctx != NULL && ctx->v_type != VAR_NUMBER
+ && ctx->v_type != VAR_STRING && ctx->v_type != VAR_FLOAT) {
+ abort = set_ref_in_item(ctx, copyID, NULL, NULL);
+ }
+ }
+
+ return abort;
+}
+
+/// Mark the context of the quickfix list and the location lists (if present) as
+/// "in use". So that garabage collection doesn't free the context.
+bool set_ref_in_quickfix(int copyID)
+{
+ bool abort = mark_quickfix_ctx(&ql_info, copyID);
+ if (abort) {
+ return abort;
+ }
+
+ FOR_ALL_TAB_WINDOWS(tp, win) {
+ if (win->w_llist != NULL) {
+ abort = mark_quickfix_ctx(win->w_llist, copyID);
+ if (abort) {
+ return abort;
+ }
+ }
+ }
+
+ return abort;
+}
+
/*
* ":[range]cbuffer [bufnr]" command.
* ":[range]caddbuffer [bufnr]" command.
@@ -4342,7 +4519,7 @@ void ex_cbuffer(exarg_T *eap)
if (qf_init_ext(qi, NULL, buf, NULL, p_efm,
(eap->cmdidx != CMD_caddbuffer
&& eap->cmdidx != CMD_laddbuffer),
- eap->line1, eap->line2, qf_title) > 0) {
+ eap->line1, eap->line2, qf_title, NULL) > 0) {
if (au_name != NULL) {
apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name,
curbuf->b_fname, true, curbuf);
@@ -4407,7 +4584,7 @@ void ex_cexpr(exarg_T *eap)
if (qf_init_ext(qi, NULL, NULL, &tv, p_efm,
(eap->cmdidx != CMD_caddexpr
&& eap->cmdidx != CMD_laddexpr),
- (linenr_T)0, (linenr_T)0, *eap->cmdlinep) > 0) {
+ (linenr_T)0, (linenr_T)0, *eap->cmdlinep, NULL) > 0) {
if (au_name != NULL) {
apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name,
curbuf->b_fname, true, curbuf);
@@ -4478,6 +4655,9 @@ void ex_helpgrep(exarg_T *eap)
}
}
+ // Autocommands may change the list. Save it for later comparison
+ qf_info_T *save_qi = qi;
+
regmatch.regprog = vim_regcomp(eap->arg, RE_MAGIC + RE_STRING);
regmatch.rm_ic = FALSE;
if (regmatch.regprog != NULL) {
@@ -4590,10 +4770,11 @@ void ex_helpgrep(exarg_T *eap)
if (au_name != NULL) {
apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name,
- curbuf->b_fname, TRUE, curbuf);
- if (!new_qi && qi != &ql_info && qf_find_buf(qi) == NULL)
- /* autocommands made "qi" invalid */
+ curbuf->b_fname, true, curbuf);
+ if (!new_qi && qi != save_qi && qf_find_buf(qi) == NULL) {
+ // autocommands made "qi" invalid
return;
+ }
}
/* Jump to first match. */
diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile
index e1faaccb84..1f8cf8a0e7 100644
--- a/src/nvim/testdir/Makefile
+++ b/src/nvim/testdir/Makefile
@@ -65,6 +65,7 @@ NEW_TESTS ?= \
test_increment_dbcs.res \
test_lambda.res \
test_langmap.res \
+ test_makeencoding.res \
test_marks.res \
test_match.res \
test_matchadd_conceal.res \
diff --git a/src/nvim/testdir/test_makeencoding.py b/src/nvim/testdir/test_makeencoding.py
new file mode 100644
index 0000000000..041edadc0a
--- /dev/null
+++ b/src/nvim/testdir/test_makeencoding.py
@@ -0,0 +1,67 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Test program for :make, :grep and :cgetfile.
+
+from __future__ import print_function, unicode_literals
+import locale
+import io
+import sys
+
+def set_output_encoding(enc=None):
+ """Set the encoding of stdout and stderr
+
+ arguments:
+ enc -- Encoding name.
+ If omitted, locale.getpreferredencoding() is used.
+ """
+ if enc is None:
+ enc = locale.getpreferredencoding()
+
+ def get_text_writer(fo, **kwargs):
+ kw = dict(kwargs)
+ kw.setdefault('errors', 'backslashreplace') # use \uXXXX style
+ kw.setdefault('closefd', False)
+
+ if sys.version_info[0] < 3:
+ # Work around for Python 2.x
+ # New line conversion isn't needed here. Done in somewhere else.
+ writer = io.open(fo.fileno(), mode='w', newline='', **kw)
+ write = writer.write # save the original write() function
+ enc = locale.getpreferredencoding()
+ def convwrite(s):
+ if isinstance(s, bytes):
+ write(s.decode(enc)) # convert to unistr
+ else:
+ write(s)
+ try:
+ writer.flush() # needed on Windows
+ except IOError:
+ pass
+ writer.write = convwrite
+ else:
+ writer = io.open(fo.fileno(), mode='w', **kw)
+ return writer
+
+ sys.stdout = get_text_writer(sys.stdout, encoding=enc)
+ sys.stderr = get_text_writer(sys.stderr, encoding=enc)
+
+
+def main():
+ enc = 'utf-8'
+ if len(sys.argv) > 1:
+ enc = sys.argv[1]
+ set_output_encoding(enc)
+
+ message_tbl = {
+ 'utf-8': 'ÀÈÌÒÙ こんにちは 你好',
+ 'latin1': 'ÀÈÌÒÙ',
+ 'cp932': 'こんにちは',
+ 'cp936': '你好',
+ }
+
+ print('Xfoobar.c(10) : %s (%s)' % (message_tbl[enc], enc))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/nvim/testdir/test_makeencoding.vim b/src/nvim/testdir/test_makeencoding.vim
new file mode 100644
index 0000000000..a3d5538a47
--- /dev/null
+++ b/src/nvim/testdir/test_makeencoding.vim
@@ -0,0 +1,106 @@
+" Tests for 'makeencoding'.
+if !has('multi_byte')
+ finish
+endif
+
+source shared.vim
+
+let s:python = PythonProg()
+if s:python == ''
+ " Can't run this test.
+ finish
+endif
+
+let s:script = 'test_makeencoding.py'
+
+let s:message_tbl = {
+ \ 'utf-8': 'ÀÈÌÒÙ こんにちは 你好',
+ \ 'latin1': 'ÀÈÌÒÙ',
+ \ 'cp932': 'こんにちは',
+ \ 'cp936': '你好',
+ \}
+
+
+" Tests for :cgetfile and :lgetfile.
+func Test_getfile()
+ set errorfile=Xerror.txt
+ set errorformat=%f(%l)\ :\ %m
+
+ " :cgetfile
+ for enc in keys(s:message_tbl)
+ let &makeencoding = enc
+ exec "silent !" . s:python . " " . s:script . " " . enc . " > " . &errorfile
+ cgetfile
+ copen
+ call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")",
+ \ getline('.'))
+ cclose
+ endfor
+
+ " :lgetfile
+ for enc in keys(s:message_tbl)
+ let &makeencoding = enc
+ exec "silent !" . s:python . " " . s:script . " " . enc . " > " . &errorfile
+ lgetfile
+ lopen
+ call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")",
+ \ getline('.'))
+ lclose
+ endfor
+
+ call delete(&errorfile)
+endfunc
+
+
+" Tests for :grep and :lgrep.
+func Test_grep()
+ let &grepprg = s:python
+ set grepformat=%f(%l)\ :\ %m
+
+ " :grep
+ for enc in keys(s:message_tbl)
+ let &makeencoding = enc
+ exec "silent grep! " . s:script . " " . enc
+ copen
+ call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")",
+ \ getline('.'))
+ cclose
+ endfor
+
+ " :lgrep
+ for enc in keys(s:message_tbl)
+ let &makeencoding = enc
+ exec "silent lgrep! " . s:script . " " . enc
+ lopen
+ call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")",
+ \ getline('.'))
+ lclose
+ endfor
+endfunc
+
+
+" Tests for :make and :lmake.
+func Test_make()
+ let &makeprg = s:python
+ set errorformat=%f(%l)\ :\ %m
+
+ " :make
+ for enc in keys(s:message_tbl)
+ let &makeencoding = enc
+ exec "silent make! " . s:script . " " . enc
+ copen
+ call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")",
+ \ getline('.'))
+ cclose
+ endfor
+
+ " :lmake
+ for enc in keys(s:message_tbl)
+ let &makeencoding = enc
+ exec "silent lmake! " . s:script . " " . enc
+ lopen
+ call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")",
+ \ getline('.'))
+ lclose
+ endfor
+endfunc
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index aff5fc2eed..30023dddc9 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -24,19 +24,21 @@ func s:setup_commands(cchar)
command! -nargs=* Xgetbuffer <mods>cgetbuffer <args>
command! -nargs=* Xaddbuffer <mods>caddbuffer <args>
command! -nargs=* Xrewind <mods>crewind <args>
- command! -nargs=* -bang Xnext <mods>cnext<bang> <args>
- command! -nargs=* -bang Xprev <mods>cprev<bang> <args>
+ command! -count -nargs=* -bang Xnext <mods><count>cnext<bang> <args>
+ command! -count -nargs=* -bang Xprev <mods><count>cprev<bang> <args>
command! -nargs=* -bang Xfirst <mods>cfirst<bang> <args>
command! -nargs=* -bang Xlast <mods>clast<bang> <args>
command! -nargs=* -bang Xnfile <mods>cnfile<bang> <args>
command! -nargs=* -bang Xpfile <mods>cpfile<bang> <args>
command! -nargs=* Xexpr <mods>cexpr <args>
- command! -nargs=* Xvimgrep <mods>vimgrep <args>
+ command! -range -nargs=* Xvimgrep <mods><count>vimgrep <args>
+ command! -nargs=* Xvimgrepadd <mods>vimgrepadd <args>
command! -nargs=* Xgrep <mods> grep <args>
command! -nargs=* Xgrepadd <mods> grepadd <args>
command! -nargs=* Xhelpgrep helpgrep <args>
let g:Xgetlist = function('getqflist')
let g:Xsetlist = function('setqflist')
+ call setqflist([], 'f')
else
command! -nargs=* -bang Xlist <mods>llist<bang> <args>
command! -nargs=* Xgetexpr <mods>lgetexpr <args>
@@ -54,19 +56,21 @@ func s:setup_commands(cchar)
command! -nargs=* Xgetbuffer <mods>lgetbuffer <args>
command! -nargs=* Xaddbuffer <mods>laddbuffer <args>
command! -nargs=* Xrewind <mods>lrewind <args>
- command! -nargs=* -bang Xnext <mods>lnext<bang> <args>
- command! -nargs=* -bang Xprev <mods>lprev<bang> <args>
+ command! -count -nargs=* -bang Xnext <mods><count>lnext<bang> <args>
+ command! -count -nargs=* -bang Xprev <mods><count>lprev<bang> <args>
command! -nargs=* -bang Xfirst <mods>lfirst<bang> <args>
command! -nargs=* -bang Xlast <mods>llast<bang> <args>
command! -nargs=* -bang Xnfile <mods>lnfile<bang> <args>
command! -nargs=* -bang Xpfile <mods>lpfile<bang> <args>
command! -nargs=* Xexpr <mods>lexpr <args>
- command! -nargs=* Xvimgrep <mods>lvimgrep <args>
+ command! -range -nargs=* Xvimgrep <mods><count>lvimgrep <args>
+ command! -nargs=* Xvimgrepadd <mods>lvimgrepadd <args>
command! -nargs=* Xgrep <mods> lgrep <args>
command! -nargs=* Xgrepadd <mods> lgrepadd <args>
command! -nargs=* Xhelpgrep lhelpgrep <args>
let g:Xgetlist = function('getloclist', [0])
let g:Xsetlist = function('setloclist', [0])
+ call setloclist(0, [], 'f')
endif
endfunc
@@ -74,6 +78,9 @@ endfunc
func XlistTests(cchar)
call s:setup_commands(a:cchar)
+ if a:cchar == 'l'
+ call assert_fails('llist', 'E776:')
+ endif
" With an empty list, command should return error
Xgetexpr []
silent! Xlist
@@ -85,49 +92,52 @@ func XlistTests(cchar)
\ 'non-error 3', 'Xtestfile3:3:1:Line3']
" List only valid entries
- redir => result
- Xlist
- redir END
- let l = split(result, "\n")
+ let l = split(execute('Xlist', ''), "\n")
call assert_equal([' 2 Xtestfile1:1 col 3: Line1',
\ ' 4 Xtestfile2:2 col 2: Line2',
\ ' 6 Xtestfile3:3 col 1: Line3'], l)
" List all the entries
- redir => result
- Xlist!
- redir END
- let l = split(result, "\n")
+ let l = split(execute('Xlist!', ''), "\n")
call assert_equal([' 1: non-error 1', ' 2 Xtestfile1:1 col 3: Line1',
\ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2',
\ ' 5: non-error 3', ' 6 Xtestfile3:3 col 1: Line3'], l)
" List a range of errors
- redir => result
- Xlist 3,6
- redir END
- let l = split(result, "\n")
+ let l = split(execute('Xlist 3,6', ''), "\n")
call assert_equal([' 4 Xtestfile2:2 col 2: Line2',
\ ' 6 Xtestfile3:3 col 1: Line3'], l)
- redir => result
- Xlist! 3,4
- redir END
- let l = split(result, "\n")
+ let l = split(execute('Xlist! 3,4', ''), "\n")
call assert_equal([' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l)
- redir => result
- Xlist -6,-4
- redir END
- let l = split(result, "\n")
+ let l = split(execute('Xlist -6,-4', ''), "\n")
call assert_equal([' 2 Xtestfile1:1 col 3: Line1'], l)
- redir => result
- Xlist! -5,-3
- redir END
- let l = split(result, "\n")
+ let l = split(execute('Xlist! -5,-3', ''), "\n")
call assert_equal([' 2 Xtestfile1:1 col 3: Line1',
\ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l)
+
+ " Test for '+'
+ let l = split(execute('Xlist! +2', ''), "\n")
+ call assert_equal([' 2 Xtestfile1:1 col 3: Line1',
+ \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l)
+
+ " Different types of errors
+ call g:Xsetlist([{'lnum':10,'col':5,'type':'W', 'text':'Warning','nr':11},
+ \ {'lnum':20,'col':10,'type':'e','text':'Error','nr':22},
+ \ {'lnum':30,'col':15,'type':'i','text':'Info','nr':33},
+ \ {'lnum':40,'col':20,'type':'x', 'text':'Other','nr':44},
+ \ {'lnum':50,'col':25,'type':"\<C-A>",'text':'one','nr':55}])
+ let l = split(execute('Xlist', ""), "\n")
+ call assert_equal([' 1:10 col 5 warning 11: Warning',
+ \ ' 2:20 col 10 error 22: Error',
+ \ ' 3:30 col 15 info 33: Info',
+ \ ' 4:40 col 20 x 44: Other',
+ \ ' 5:50 col 25 55: one'], l)
+
+ " Error cases
+ call assert_fails('Xlist abc', 'E488:')
endfunc
func Test_clist()
@@ -141,6 +151,9 @@ endfunc
func XageTests(cchar)
call s:setup_commands(a:cchar)
+ let list = [{'bufnr': 1, 'lnum': 1}]
+ call g:Xsetlist(list)
+
" Jumping to a non existent list should return error
silent! Xolder 99
call assert_true(v:errmsg ==# 'E380: At bottom of quickfix stack')
@@ -174,11 +187,7 @@ func XageTests(cchar)
endfunc
func Test_cage()
- let list = [{'bufnr': 1, 'lnum': 1}]
- call setqflist(list)
call XageTests('c')
-
- call setloclist(0, list)
call XageTests('l')
endfunc
@@ -187,6 +196,11 @@ endfunc
func XwindowTests(cchar)
call s:setup_commands(a:cchar)
+ " Opening the location list window without any errors should fail
+ if a:cchar == 'l'
+ call assert_fails('lopen', 'E776:')
+ endif
+
" Create a list with no valid entries
Xgetexpr ['non-error 1', 'non-error 2', 'non-error 3']
@@ -227,6 +241,19 @@ func XwindowTests(cchar)
" Calling cwindow should close the quickfix window with no valid errors
Xwindow
call assert_true(winnr('$') == 1)
+
+ if a:cchar == 'c'
+ " Opening the quickfix window in multiple tab pages should reuse the
+ " quickfix buffer
+ Xgetexpr ['Xtestfile1:1:3:Line1', 'Xtestfile2:2:2:Line2',
+ \ 'Xtestfile3:3:1:Line3']
+ Xopen
+ let qfbufnum = bufnr('%')
+ tabnew
+ Xopen
+ call assert_equal(qfbufnum, bufnr('%'))
+ new | only | tabonly
+ endif
endfunc
func Test_cwindow()
@@ -316,6 +343,23 @@ func XbufferTests(cchar)
\ l[3].lnum == 750 && l[3].col == 25 && l[3].text ==# 'Line 750')
enew!
+ " Check for invalid buffer
+ call assert_fails('Xbuffer 199', 'E474:')
+
+ " Check for unloaded buffer
+ edit Xtestfile1
+ let bnr = bufnr('%')
+ enew!
+ call assert_fails('Xbuffer ' . bnr, 'E681:')
+
+ " Check for invalid range
+ " Using Xbuffer will not run the range check in the cbuffer/lbuffer
+ " commands. So directly call the commands.
+ if (a:cchar == 'c')
+ call assert_fails('900,999cbuffer', 'E16:')
+ else
+ call assert_fails('900,999lbuffer', 'E16:')
+ endif
endfunc
func Test_cbuffer()
@@ -338,13 +382,22 @@ endfunc
func Xtest_browse(cchar)
call s:setup_commands(a:cchar)
+ " Jumping to first or next location list entry without any error should
+ " result in failure
+ if a:cchar == 'l'
+ call assert_fails('lfirst', 'E776:')
+ call assert_fails('lnext', 'E776:')
+ endif
+
call s:create_test_file('Xqftestfile1')
call s:create_test_file('Xqftestfile2')
Xgetexpr ['Xqftestfile1:5:Line5',
\ 'Xqftestfile1:6:Line6',
\ 'Xqftestfile2:10:Line10',
- \ 'Xqftestfile2:11:Line11']
+ \ 'Xqftestfile2:11:Line11',
+ \ 'RegularLine1',
+ \ 'RegularLine2']
Xfirst
call assert_fails('Xprev', 'E553')
@@ -356,6 +409,7 @@ func Xtest_browse(cchar)
call assert_equal('Xqftestfile1', bufname('%'))
call assert_equal(6, line('.'))
Xlast
+ Xprev
call assert_equal('Xqftestfile2', bufname('%'))
call assert_equal(11, line('.'))
call assert_fails('Xnext', 'E553')
@@ -364,6 +418,16 @@ func Xtest_browse(cchar)
call assert_equal('Xqftestfile1', bufname('%'))
call assert_equal(5, line('.'))
+ 10Xnext
+ call assert_equal('Xqftestfile2', bufname('%'))
+ call assert_equal(11, line('.'))
+ 10Xprev
+ call assert_equal('Xqftestfile1', bufname('%'))
+ call assert_equal(5, line('.'))
+
+ Xexpr ""
+ call assert_fails('Xnext', 'E42:')
+
call delete('Xqftestfile1')
call delete('Xqftestfile2')
endfunc
@@ -383,8 +447,32 @@ func s:test_xhelpgrep(cchar)
let title_text = ':lhelpgrep quickfix'
endif
call assert_true(w:quickfix_title =~ title_text, w:quickfix_title)
+
+ " Jumping to a help topic should open the help window
+ only
+ Xnext
+ call assert_true(&buftype == 'help')
+ call assert_true(winnr('$') == 2)
+ " Jumping to the next match should reuse the help window
+ Xnext
+ call assert_true(&buftype == 'help')
+ call assert_true(winnr() == 1)
+ call assert_true(winnr('$') == 2)
+ " Jumping to the next match from the quickfix window should reuse the help
+ " window
+ Xopen
+ Xnext
+ call assert_true(&buftype == 'help')
+ call assert_true(winnr() == 1)
+ call assert_true(winnr('$') == 2)
+
" This wipes out the buffer, make sure that doesn't cause trouble.
Xclose
+
+ new | only
+
+ " Search for non existing help string
+ call assert_fails('Xhelpgrep a1b2c3', 'E480:')
endfunc
func Test_helpgrep()
@@ -521,10 +609,7 @@ func Test_locationlist()
lrewind
enew
lopen
- lnext
- lnext
- lnext
- lnext
+ 4lnext
vert split
wincmd L
lopen
@@ -578,7 +663,7 @@ func Test_locationlist()
wincmd n | only
augroup! testgroup
- endfunc
+endfunc
func Test_locationlist_curwin_was_closed()
augroup testgroup
@@ -597,7 +682,7 @@ func Test_locationlist_curwin_was_closed()
call assert_fails('lrewind', 'E924:')
augroup! testgroup
- endfunc
+endfunc
func Test_locationlist_cross_tab_jump()
call writefile(['loclistfoo'], 'loclistfoo')
@@ -734,7 +819,7 @@ func Test_efm1()
call delete('Xerrorfile1')
call delete('Xerrorfile2')
call delete('Xtestfile')
- endfunc
+endfunc
" Test for quickfix directory stack support
func s:dir_stack_tests(cchar)
@@ -893,30 +978,44 @@ func Test_efm2()
call assert_equal(l[0].pattern, '^\VLine search text\$')
call assert_equal(l[0].lnum, 0)
+ let l = split(execute('clist', ''), "\n")
+ call assert_equal([' 1 Xtestfile:^\VLine search text\$: '], l)
+
" Test for %P, %Q and %t format specifiers
let lines=["[Xtestfile1]",
\ "(1,17) error: ';' missing",
\ "(21,2) warning: variable 'z' not defined",
\ "(67,3) error: end of file found before string ended",
+ \ "--",
\ "",
\ "[Xtestfile2]",
+ \ "--",
\ "",
\ "[Xtestfile3]",
\ "NEW compiler v1.1",
\ "(2,2) warning: variable 'x' not defined",
- \ "(67,3) warning: 's' already defined"
+ \ "(67,3) warning: 's' already defined",
+ \ "--"
\]
- set efm=%+P[%f],(%l\\,%c)%*[\ ]%t%*[^:]:\ %m,%-Q
+ set efm=%+P[%f]%r,(%l\\,%c)%*[\ ]%t%*[^:]:\ %m,%+Q--%r
+ " To exercise the push/pop file functionality in quickfix, the test files
+ " need to be created.
+ call writefile(['Line1'], 'Xtestfile1')
+ call writefile(['Line2'], 'Xtestfile2')
+ call writefile(['Line3'], 'Xtestfile3')
cexpr ""
for l in lines
caddexpr l
endfor
let l = getqflist()
- call assert_equal(9, len(l))
+ call assert_equal(12, len(l))
call assert_equal(21, l[2].lnum)
call assert_equal(2, l[2].col)
call assert_equal('w', l[2].type)
call assert_equal('e', l[3].type)
+ call delete('Xtestfile1')
+ call delete('Xtestfile2')
+ call delete('Xtestfile3')
" Tests for %E, %C and %Z format specifiers
let lines = ["Error 275",
@@ -968,6 +1067,25 @@ func Test_efm2()
call assert_equal(1, l[4].valid)
call assert_equal('unittests/dbfacadeTest.py', bufname(l[4].bufnr))
+ " The following sequence of commands used to crash Vim
+ set efm=%W%m
+ cgetexpr ['msg1']
+ let l = getqflist()
+ call assert_equal(1, len(l), string(l))
+ call assert_equal('msg1', l[0].text)
+ set efm=%C%m
+ lexpr 'msg2'
+ let l = getloclist(0)
+ call assert_equal(1, len(l), string(l))
+ call assert_equal('msg2', l[0].text)
+ lopen
+ call setqflist([], 'r')
+ caddbuf
+ let l = getqflist()
+ call assert_equal(1, len(l), string(l))
+ call assert_equal('|| msg2', l[0].text)
+
+ new | only
let &efm = save_efm
endfunc
@@ -1064,6 +1182,32 @@ func SetXlistTests(cchar, bnum)
call g:Xsetlist([])
let l = g:Xgetlist()
call assert_equal(0, len(l))
+
+ " Tests for setting the 'valid' flag
+ call g:Xsetlist([{'bufnr':a:bnum, 'lnum':4, 'valid':0}])
+ Xwindow
+ call assert_equal(1, winnr('$'))
+ let l = g:Xgetlist()
+ call g:Xsetlist(l)
+ call assert_equal(0, g:Xgetlist()[0].valid)
+ call g:Xsetlist([{'text':'Text1', 'valid':1}])
+ Xwindow
+ call assert_equal(2, winnr('$'))
+ Xclose
+ let save_efm = &efm
+ set efm=%m
+ Xgetexpr 'TestMessage'
+ let l = g:Xgetlist()
+ call g:Xsetlist(l)
+ call assert_equal(1, g:Xgetlist()[0].valid)
+ let &efm = save_efm
+
+ " Error cases:
+ " Refer to a non-existing buffer and pass a non-dictionary type
+ call assert_fails("call g:Xsetlist([{'bufnr':998, 'lnum':4}," .
+ \ " {'bufnr':999, 'lnum':5}])", 'E92:')
+ call g:Xsetlist([[1, 2,3]])
+ call assert_equal(0, len(g:Xgetlist()))
endfunc
func Test_setqflist()
@@ -1082,7 +1226,8 @@ func Xlist_empty_middle(cchar)
call s:setup_commands(a:cchar)
" create three quickfix lists
- Xvimgrep Test_ test_quickfix.vim
+ let @/ = 'Test_'
+ Xvimgrep // test_quickfix.vim
let testlen = len(g:Xgetlist())
call assert_true(testlen > 0)
Xvimgrep empty test_quickfix.vim
@@ -1290,18 +1435,18 @@ func Test_switchbuf()
let winid = win_getid()
cfirst | cnext
call assert_equal(winid, win_getid())
- cnext | cnext
+ 2cnext
call assert_equal(winid, win_getid())
- cnext | cnext
+ 2cnext
call assert_equal(winid, win_getid())
enew
set switchbuf=useopen
cfirst | cnext
call assert_equal(file1_winid, win_getid())
- cnext | cnext
+ 2cnext
call assert_equal(file2_winid, win_getid())
- cnext | cnext
+ 2cnext
call assert_equal(file2_winid, win_getid())
enew | only
@@ -1311,9 +1456,9 @@ func Test_switchbuf()
tabfirst
cfirst | cnext
call assert_equal(2, tabpagenr())
- cnext | cnext
+ 2cnext
call assert_equal(3, tabpagenr())
- cnext | cnext
+ 2cnext
call assert_equal(3, tabpagenr())
tabfirst | tabonly | enew
@@ -1351,11 +1496,25 @@ func Test_switchbuf()
call assert_equal(2, winnr('$'))
call assert_equal(1, bufwinnr('Xqftestfile3'))
+ " If only quickfix window is open in the current tabpage, jumping to an
+ " entry with 'switchubf' set to 'usetab' should search in other tabpages.
enew | only
+ set switchbuf=usetab
+ tabedit Xqftestfile1
+ tabedit Xqftestfile2
+ tabedit Xqftestfile3
+ tabfirst
+ copen | only
+ clast
+ call assert_equal(4, tabpagenr())
+ tabfirst | tabonly | enew | only
call delete('Xqftestfile1')
call delete('Xqftestfile2')
call delete('Xqftestfile3')
+ set switchbuf&vim
+
+ enew | only
endfunc
func Xadjust_qflnum(cchar)
@@ -1468,6 +1627,11 @@ endfunc
func XbottomTests(cchar)
call s:setup_commands(a:cchar)
+ " Calling lbottom without any errors should fail
+ if a:cchar == 'l'
+ call assert_fails('lbottom', 'E776:')
+ endif
+
call g:Xsetlist([{'filename': 'foo', 'lnum': 42}])
Xopen
let wid = win_getid()
@@ -1489,10 +1653,9 @@ endfunc
func HistoryTest(cchar)
call s:setup_commands(a:cchar)
- call assert_fails(a:cchar . 'older 99', 'E380:')
" clear all lists after the first one, then replace the first one.
call g:Xsetlist([])
- Xolder
+ call assert_fails('Xolder 99', 'E380:')
let entry = {'filename': 'foo', 'lnum': 42}
call g:Xsetlist([entry], 'r')
call g:Xsetlist([entry, entry])
@@ -1535,6 +1698,7 @@ func Xproperty_tests(cchar)
call assert_fails('call g:Xsetlist([], "a", [])', 'E715:')
" Set and get the title
+ call g:Xsetlist([])
Xopen
wincmd p
call g:Xsetlist([{'filename':'foo', 'lnum':27}])
@@ -1561,6 +1725,22 @@ func Xproperty_tests(cchar)
call g:Xsetlist([], ' ', {'title' : 'N3'})
call assert_equal('N2', g:Xgetlist({'nr':2, 'title':1}).title)
+ " Changing the title of an earlier quickfix list
+ call g:Xsetlist([], ' ', {'title' : 'NewTitle', 'nr' : 2})
+ call assert_equal('NewTitle', g:Xgetlist({'nr':2, 'title':1}).title)
+
+ " Changing the title of an invalid quickfix list
+ call assert_equal(-1, g:Xsetlist([], ' ',
+ \ {'title' : 'SomeTitle', 'nr' : 99}))
+ call assert_equal(-1, g:Xsetlist([], ' ',
+ \ {'title' : 'SomeTitle', 'nr' : 'abc'}))
+
+ if a:cchar == 'c'
+ copen
+ call assert_equal({'winid':win_getid()}, getqflist({'winid':1}))
+ cclose
+ endif
+
" Invalid arguments
call assert_fails('call g:Xgetlist([])', 'E715')
call assert_fails('call g:Xsetlist([], "a", [])', 'E715')
@@ -1568,16 +1748,81 @@ func Xproperty_tests(cchar)
call assert_equal(-1, s)
call assert_equal({}, g:Xgetlist({'abc':1}))
+ call assert_equal({}, g:Xgetlist({'nr':99, 'title':1}))
+ call assert_equal({}, g:Xgetlist({'nr':[], 'title':1}))
if a:cchar == 'l'
- call assert_equal({}, getloclist(99, {'title': 1}))
+ call assert_equal({}, getloclist(99, {'title': 1}))
endif
- endfunc
+
+ " Context related tests
+ call g:Xsetlist([], 'a', {'context':[1,2,3]})
+ call test_garbagecollect_now()
+ let d = g:Xgetlist({'context':1})
+ call assert_equal([1,2,3], d.context)
+ call g:Xsetlist([], 'a', {'context':{'color':'green'}})
+ let d = g:Xgetlist({'context':1})
+ call assert_equal({'color':'green'}, d.context)
+ call g:Xsetlist([], 'a', {'context':"Context info"})
+ let d = g:Xgetlist({'context':1})
+ call assert_equal("Context info", d.context)
+ call g:Xsetlist([], 'a', {'context':246})
+ let d = g:Xgetlist({'context':1})
+ call assert_equal(246, d.context)
+ if a:cchar == 'l'
+ " Test for copying context across two different location lists
+ new | only
+ let w1_id = win_getid()
+ let l = [1]
+ call setloclist(0, [], 'a', {'context':l})
+ new
+ let w2_id = win_getid()
+ call add(l, 2)
+ call assert_equal([1, 2], getloclist(w1_id, {'context':1}).context)
+ call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context)
+ unlet! l
+ call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context)
+ only
+ call setloclist(0, [], 'f')
+ call assert_equal({}, getloclist(0, {'context':1}))
+ endif
+
+ " Test for changing the context of previous quickfix lists
+ call g:Xsetlist([], 'f')
+ Xexpr "One"
+ Xexpr "Two"
+ Xexpr "Three"
+ call g:Xsetlist([], ' ', {'context' : [1], 'nr' : 1})
+ call g:Xsetlist([], ' ', {'context' : [2], 'nr' : 2})
+ " Also, check for setting the context using quickfix list number zero.
+ call g:Xsetlist([], ' ', {'context' : [3], 'nr' : 0})
+ call test_garbagecollect_now()
+ let l = g:Xgetlist({'nr' : 1, 'context' : 1})
+ call assert_equal([1], l.context)
+ let l = g:Xgetlist({'nr' : 2, 'context' : 1})
+ call assert_equal([2], l.context)
+ let l = g:Xgetlist({'nr' : 3, 'context' : 1})
+ call assert_equal([3], l.context)
+
+ " Test for changing the context through reference and for garbage
+ " collection of quickfix context
+ let l = ["red"]
+ call g:Xsetlist([], ' ', {'context' : l})
+ call add(l, "blue")
+ let x = g:Xgetlist({'context' : 1})
+ call add(x.context, "green")
+ call assert_equal(["red", "blue", "green"], l)
+ call assert_equal(["red", "blue", "green"], x.context)
+ unlet l
+ call test_garbagecollect_now()
+ let m = g:Xgetlist({'context' : 1})
+ call assert_equal(["red", "blue", "green"], m.context)
+endfunc
func Test_qf_property()
call Xproperty_tests('c')
call Xproperty_tests('l')
- endfunc
+endfunc
" Tests for the QuickFixCmdPre/QuickFixCmdPost autocommands
func QfAutoCmdHandler(loc, cmd)
@@ -1673,3 +1918,187 @@ func Test_dirstack_cleanup()
caddbuffer
let &efm = save_efm
endfunc
+
+" Tests for jumping to entries from the location list window and quickfix
+" window
+func Test_cwindow_jump()
+ set efm=%f%%%l%%%m
+ lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"]
+ lopen | only
+ lfirst
+ call assert_true(winnr('$') == 2)
+ call assert_true(winnr() == 1)
+ " Location list for the new window should be set
+ call assert_true(getloclist(0)[2].text == 'Line 30')
+
+ " Open a scratch buffer
+ " Open a new window and create a location list
+ " Open the location list window and close the other window
+ " Jump to an entry.
+ " Should create a new window and jump to the entry. The scrtach buffer
+ " should not be used.
+ enew | only
+ set buftype=nofile
+ below new
+ lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"]
+ lopen
+ 2wincmd c
+ lnext
+ call assert_true(winnr('$') == 3)
+ call assert_true(winnr() == 2)
+
+ " Open two windows with two different location lists
+ " Open the location list window and close the previous window
+ " Jump to an entry in the location list window
+ " Should open the file in the first window and not set the location list.
+ enew | only
+ lgetexpr ["F1%5%Line 5"]
+ below new
+ lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"]
+ lopen
+ 2wincmd c
+ lnext
+ call assert_true(winnr() == 1)
+ call assert_true(getloclist(0)[0].text == 'Line 5')
+
+ enew | only
+ cgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"]
+ copen
+ cnext
+ call assert_true(winnr('$') == 2)
+ call assert_true(winnr() == 1)
+
+ enew | only
+ set efm&vim
+endfunc
+
+func XvimgrepTests(cchar)
+ call s:setup_commands(a:cchar)
+
+ call writefile(['Editor:VIM vim',
+ \ 'Editor:Emacs EmAcS',
+ \ 'Editor:Notepad NOTEPAD'], 'Xtestfile1')
+ call writefile(['Linux', 'MacOS', 'MS-Windows'], 'Xtestfile2')
+
+ " Error cases
+ call assert_fails('Xvimgrep /abc *', 'E682:')
+
+ let @/=''
+ call assert_fails('Xvimgrep // *', 'E35:')
+
+ call assert_fails('Xvimgrep abc', 'E683:')
+ call assert_fails('Xvimgrep a1b2c3 Xtestfile1', 'E480:')
+ call assert_fails('Xvimgrep pat Xa1b2c3', 'E480:')
+
+ Xexpr ""
+ Xvimgrepadd Notepad Xtestfile1
+ Xvimgrepadd MacOS Xtestfile2
+ let l = g:Xgetlist()
+ call assert_equal(2, len(l))
+ call assert_equal('Editor:Notepad NOTEPAD', l[0].text)
+
+ Xvimgrep #\cvim#g Xtestfile?
+ let l = g:Xgetlist()
+ call assert_equal(2, len(l))
+ call assert_equal(8, l[0].col)
+ call assert_equal(12, l[1].col)
+
+ 1Xvimgrep ?Editor? Xtestfile*
+ let l = g:Xgetlist()
+ call assert_equal(1, len(l))
+ call assert_equal('Editor:VIM vim', l[0].text)
+
+ edit +3 Xtestfile2
+ Xvimgrep +\cemacs+j Xtestfile1
+ let l = g:Xgetlist()
+ call assert_equal('Xtestfile2', bufname(''))
+ call assert_equal('Editor:Emacs EmAcS', l[0].text)
+
+ call delete('Xtestfile1')
+ call delete('Xtestfile2')
+endfunc
+
+" Tests for the :vimgrep command
+func Test_vimgrep()
+ call XvimgrepTests('c')
+ call XvimgrepTests('l')
+endfunc
+
+func XfreeTests(cchar)
+ call s:setup_commands(a:cchar)
+
+ enew | only
+
+ " Deleting the quickfix stack should work even When the current list is
+ " somewhere in the middle of the stack
+ Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15']
+ Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25']
+ Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35']
+ Xolder
+ call g:Xsetlist([], 'f')
+ call assert_equal(0, len(g:Xgetlist()))
+
+ " After deleting the stack, adding a new list should create a stack with a
+ " single list.
+ Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15']
+ call assert_equal(1, g:Xgetlist({'all':1}).nr)
+
+ " Deleting the stack from a quickfix window should update/clear the
+ " quickfix/location list window.
+ Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15']
+ Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25']
+ Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35']
+ Xolder
+ Xwindow
+ call g:Xsetlist([], 'f')
+ call assert_equal(2, winnr('$'))
+ call assert_equal(1, line('$'))
+ Xclose
+
+ " Deleting the stack from a non-quickfix window should update/clear the
+ " quickfix/location list window.
+ Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15']
+ Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25']
+ Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35']
+ Xolder
+ Xwindow
+ wincmd p
+ call g:Xsetlist([], 'f')
+ call assert_equal(0, len(g:Xgetlist()))
+ wincmd p
+ call assert_equal(2, winnr('$'))
+ call assert_equal(1, line('$'))
+
+ " After deleting the location list stack, if the location list window is
+ " opened, then a new location list should be created. So opening the
+ " location list window again should not create a new window.
+ if a:cchar == 'l'
+ lexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15']
+ wincmd p
+ lopen
+ call assert_equal(2, winnr('$'))
+ endif
+ Xclose
+endfunc
+
+" Tests for the quickifx free functionality
+func Test_qf_free()
+ call XfreeTests('c')
+ call XfreeTests('l')
+endfunc
+
+" Test for buffer overflow when parsing lines and adding new entries to
+" the quickfix list.
+func Test_bufoverflow()
+ set efm=%f:%l:%m
+ cgetexpr ['File1:100:' . repeat('x', 1025)]
+
+ set efm=%+GCompiler:\ %.%#,%f:%l:%m
+ cgetexpr ['Compiler: ' . repeat('a', 1015), 'File1:10:Hello World']
+
+ set efm=%DEntering\ directory\ %f,%f:%l:%m
+ cgetexpr ['Entering directory ' . repeat('a', 1006),
+ \ 'File1:10:Hello World']
+ set efm&vim
+endfunc
+
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 021bb6eac4..dbe1222dc0 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -70,7 +70,6 @@ typedef struct {
UIBridgeData *bridge;
Loop *loop;
bool stop;
- uv_timer_t after_startup_timer;
unibi_var_t params[9];
char buf[OUTBUF_SIZE];
size_t bufpos;
@@ -291,18 +290,6 @@ static void terminfo_stop(UI *ui)
unibi_destroy(data->ut);
}
-static void after_startup_timer_cb(uv_timer_t *handle)
- FUNC_ATTR_NONNULL_ALL
-{
- UI *ui = handle->data;
- TUIData *data = ui->data;
- uv_timer_stop(&data->after_startup_timer);
-
- // Emit this after Nvim startup, not during. This works around a tmux
- // 2.3 bug(?) which caused slow drawing during startup. #7649
- unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting);
-}
-
static void tui_terminal_start(UI *ui)
{
TUIData *data = ui->data;
@@ -312,8 +299,16 @@ static void tui_terminal_start(UI *ui)
update_size(ui);
signal_watcher_start(&data->winch_handle, sigwinch_cb, SIGWINCH);
term_input_start(&data->input);
+}
+
+static void tui_terminal_after_startup(UI *ui)
+ FUNC_ATTR_NONNULL_ALL
+{
+ TUIData *data = ui->data;
- uv_timer_start(&data->after_startup_timer, after_startup_timer_cb, 500, 0);
+ // Emit this after Nvim startup, not during. This works around a tmux
+ // 2.3 bug(?) which caused slow drawing during startup. #7649
+ unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting);
}
static void tui_terminal_stop(UI *ui)
@@ -347,8 +342,6 @@ static void tui_main(UIBridgeData *bridge, UI *ui)
#ifdef UNIX
signal_watcher_start(&data->cont_handle, sigcont_cb, SIGCONT);
#endif
- uv_timer_init(&data->loop->uv, &data->after_startup_timer);
- data->after_startup_timer.data = ui;
#if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18
data->input.tk_ti_hook_fn = tui_tk_ti_getstr;
@@ -363,11 +356,21 @@ static void tui_main(UIBridgeData *bridge, UI *ui)
loop_schedule_deferred(&main_loop,
event_create(show_termcap_event, 1, data->ut));
+ // "Active" loop: first ~100 ms of startup.
+ for (size_t ms = 0; ms < 100 && !data->stop;) {
+ ms += (loop_poll_events(&tui_loop, 20) ? 20 : 1);
+ }
+ if (!data->stop) {
+ tui_terminal_after_startup(ui);
+ // Tickle `main_loop` with a dummy event, else the initial "focus-gained"
+ // terminal response may not get processed until user hits a key.
+ loop_schedule_deferred(&main_loop, event_create(tui_dummy_event, 0));
+ }
+ // "Passive" (I/O-driven) loop: TUI thread "main loop".
while (!data->stop) {
loop_poll_events(&tui_loop, -1); // tui_loop.events is never processed
}
- uv_close((uv_handle_t *)&data->after_startup_timer, NULL);
ui_bridge_stopped(bridge);
term_input_destroy(&data->input);
signal_watcher_stop(&data->cont_handle);
@@ -379,6 +382,10 @@ static void tui_main(UIBridgeData *bridge, UI *ui)
xfree(ui);
}
+static void tui_dummy_event(void **argv)
+{
+}
+
static void tui_scheduler(Event event, void *d)
{
UI *ui = d;
@@ -1100,6 +1107,7 @@ static void suspend_event(void **argv)
loop_poll_events(data->loop, -1);
}
tui_terminal_start(ui);
+ tui_terminal_after_startup(ui);
if (enable_mouse) {
tui_mouse_on(ui);
}