diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/buffer.c | 250 | ||||
-rw-r--r-- | src/nvim/eval.c | 326 | ||||
-rw-r--r-- | src/nvim/eval.lua | 6 | ||||
-rw-r--r-- | src/nvim/ex_cmds.c | 630 | ||||
-rw-r--r-- | src/nvim/globals.h | 5 | ||||
-rw-r--r-- | src/nvim/sign_defs.h | 5 | ||||
-rw-r--r-- | src/nvim/testdir/test_signs.vim | 626 |
7 files changed, 1637 insertions, 211 deletions
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index a5ad1f1a11..da65682fbe 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -805,7 +805,7 @@ free_buffer_stuff( hash_init(&buf->b_vars->dv_hashtab); buf_init_changedtick(buf); uc_clear(&buf->b_ucmds); // clear local user commands - buf_delete_signs(buf); // delete any signs + buf_delete_signs(buf, (char_u *)"*"); // delete any signs bufhl_clear_all(buf); // delete any highligts map_clear_int(buf, MAP_ALL_MODES, true, false); // clear local mappings map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs @@ -5256,13 +5256,16 @@ bool find_win_for_buf(buf_T *buf, win_T **wp, tabpage_T **tp) } /* - * Insert the sign into the signlist. + * Insert a new sign into the signlist for buffer 'buf' between the 'prev' and + * 'next' signs. */ static void insert_sign( buf_T *buf, // buffer to store sign in signlist_T *prev, // previous sign entry signlist_T *next, // next sign entry int id, // sign ID + char_u *group, // sign group; NULL for global group + int prio, // sign priority linenr_T lnum, // line number which gets the mark int typenr // typenr of sign we are adding ) @@ -5271,6 +5274,11 @@ static void insert_sign( newsign->id = id; newsign->lnum = lnum; newsign->typenr = typenr; + if (group != NULL) + newsign->group = vim_strsave(group); + else + newsign->group = NULL; + newsign->priority = prio; newsign->next = next; newsign->prev = prev; if (next != NULL) { @@ -5279,8 +5287,8 @@ static void insert_sign( buf->b_signcols_max = -1; if (prev == NULL) { - /* When adding first sign need to redraw the windows to create the - * column for signs. */ + // When adding first sign need to redraw the windows to create the + // column for signs. if (buf->b_signlist == NULL) { redraw_buf_later(buf, NOT_VALID); changed_cline_bef_curs(); @@ -5385,11 +5393,74 @@ int buf_signcols(buf_T *buf) } /* + * Insert a new sign sorted by line number and sign priority. + */ +static void insert_sign_by_lnum_prio( + buf_T *buf, // buffer to store sign in + signlist_T *prev, // previous sign entry + int id, // sign ID + char_u *group, // sign group; NULL for global group + int prio, // sign priority + linenr_T lnum, // line number which gets the mark + int typenr // typenr of sign we are adding +) +{ + signlist_T *sign; + + // keep signs sorted by lnum and by priority: insert new sign at + // the proper position in the list for this lnum. + while (prev != NULL && prev->lnum == lnum && prev->priority <= prio) { + prev = prev->prev; + } + if (prev == NULL) { + sign = buf->b_signlist; + } else { + sign = prev->next; + } + + insert_sign(buf, prev, sign, id, group, prio, lnum, typenr); +} + +/* + * Returns TRUE if 'sign' is in 'group'. + * A sign can either be in the global group (sign->group == NULL) + * or in a named group. If 'group' is '*', then the sign is part of the group. + */ +int sign_in_group(signlist_T *sign, char_u *group) +{ + return ((group != NULL && STRCMP(group, "*") == 0) || + (group == NULL && sign->group == NULL) || + (group != NULL && sign->group != NULL && + STRCMP(group, sign->group) == 0)); +} + +/* + * Return information about a sign in a Dict + */ +dict_T * sign_get_info(signlist_T *sign) +{ + dict_T *d; + + if ((d = tv_dict_alloc()) == NULL) { + return NULL; + } + tv_dict_add_nr(d, S_LEN("id"), sign->id); + tv_dict_add_str(d, S_LEN("group"), (sign->group == NULL) ? (char_u *)"" : sign->group); + tv_dict_add_nr(d, S_LEN("lnum"), sign->lnum); + tv_dict_add_str(d, S_LEN("name"), sign_typenr2name(sign->typenr)); + tv_dict_add_nr(d, S_LEN("priority"), sign->priority); + + return d; +} + +/* * Add the sign into the signlist. Find the right spot to do it though. */ void buf_addsign( buf_T *buf, // buffer to store sign in int id, // sign ID + char_u *group, // sign group + int prio, // sign priority linenr_T lnum, // line number which gets the mark int typenr // typenr of sign we are adding ) @@ -5399,38 +5470,21 @@ void buf_addsign( signlist_T *prev; // the previous sign prev = NULL; - for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { - if (lnum == sign->lnum && id == sign->id) { - sign->typenr = typenr; - return; + FOR_ALL_SIGNS_IN_BUF(buf) { + if (lnum == sign->lnum && id == sign->id && + sign_in_group(sign, group)) { + // Update an existing sign + sign->typenr = typenr; + return; } else if ((lnum == sign->lnum && id != sign->id) || (id < 0 && lnum < sign->lnum)) { - // keep signs sorted by lnum: insert new sign at head of list for - // this lnum - while (prev != NULL && prev->lnum == lnum) { - prev = prev->prev; - } - if (prev == NULL) { - sign = buf->b_signlist; - } else { - sign = prev->next; - } - insert_sign(buf, prev, sign, id, lnum, typenr); + insert_sign_by_lnum_prio(buf, prev, id, group, prio, lnum, typenr); return; } prev = sign; } - // insert new sign at head of list for this lnum - while (prev != NULL && prev->lnum == lnum) { - prev = prev->prev; - } - if (prev == NULL) { - sign = buf->b_signlist; - } else { - sign = prev->next; - } - insert_sign(buf, prev, sign, id, lnum, typenr); + insert_sign_by_lnum_prio(buf, prev, id, group, prio, lnum, typenr); // Having more than one sign with _the same type_ and on the _same line_ is // unwanted, let's prevent it. @@ -5451,13 +5505,14 @@ void buf_addsign( linenr_T buf_change_sign_type( buf_T *buf, // buffer to store sign in int markId, // sign ID + char_u *group, // sign group int typenr // typenr of sign we are adding ) { signlist_T *sign; // a sign in the signlist - for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { - if (sign->id == markId) { + FOR_ALL_SIGNS_IN_BUF(buf) { + if (sign->id == markId && sign_in_group(sign, group)) { sign->typenr = typenr; return sign->lnum; } @@ -5484,7 +5539,7 @@ int buf_getsigntype(buf_T *buf, linenr_T lnum, SignType type, signlist_T *matches[9]; int nr_matches = 0; - for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { + FOR_ALL_SIGNS_IN_BUF(buf) { if (sign->lnum == lnum && (type == SIGN_ANY || (type == SIGN_TEXT @@ -5517,9 +5572,20 @@ int buf_getsigntype(buf_T *buf, linenr_T lnum, SignType type, return 0; } +/* + * Delete sign 'id' in group 'group' from buffer 'buf'. + * If 'id' is zero, then delete all the signs in group 'group'. Otherwise + * delete only the specified sign. + * If 'group' is '*', then delete the sign in all the groups. If 'group' is + * NULL, then delete the sign in the global group. Otherwise delete the sign in + * the specified group. + * Returns the line number of the deleted sign. If multiple signs are deleted, + * then returns the line number of the last sign deleted. + */ linenr_T buf_delsign( buf_T *buf, // buffer sign is stored in - int id // sign id + int id, // sign id + char_u *group// sign group ) { signlist_T **lastp; // pointer to pointer to current sign @@ -5532,13 +5598,16 @@ linenr_T buf_delsign( lnum = 0; for (sign = buf->b_signlist; sign != NULL; sign = next) { next = sign->next; - if (sign->id == id) { + if ((id == 0 || sign->id == id) && sign_in_group(sign, group)) { *lastp = next; if (next != NULL) { next->prev = sign->prev; } lnum = sign->lnum; + xfree(sign->group); xfree(sign); + // Check whether only one sign needs to be deleted + if (group == NULL || (*group != '*' && id != 0)) break; } else { lastp = &sign->next; @@ -5557,19 +5626,20 @@ linenr_T buf_delsign( /* - * Find the line number of the sign with the requested id. If the sign does - * not exist, return 0 as the line number. This will still let the correct file - * get loaded. + * Find the line number of the sign with the requested id in group 'group'. If + * the sign does not exist, return 0 as the line number. This will still let + * the correct file get loaded. */ int buf_findsign( buf_T *buf, // buffer to store sign in - int id // sign ID + int id, // sign ID + char_u *group // sign group ) { signlist_T *sign; // a sign in the signlist - for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { - if (sign->id == id) { + FOR_ALL_SIGNS_IN_BUF(buf) { + if (sign->id == id && sign_in_group(sign, group)){ return (int)sign->lnum; } } @@ -5577,6 +5647,49 @@ int buf_findsign( return 0; } +/* + * Return the sign at line 'lnum' in buffer 'buf'. Returns NULL if a sign is + * not found at the line. + */ +static signlist_T * buf_getsign_at_line( + buf_T *buf, // buffer whose sign we are searching for + linenr_T lnum // line number of sign +) +{ + signlist_T *sign; // a sign in the signlist + + FOR_ALL_SIGNS_IN_BUF(buf) { + if (sign->lnum == lnum) { + return sign; + } + } + + return NULL; +} + +/* + * Return the sign with identifier 'id' in group 'group' placed in buffer 'buf' + */ +signlist_T *buf_getsign_with_id( + buf_T *buf, // buffer whose sign we are searching for + int id, // sign identifier + char_u *group// sign group +) +{ + signlist_T *sign; // a sign in the signlist + + FOR_ALL_SIGNS_IN_BUF(buf) { + if (sign->id == id && sign_in_group(sign, group)) { + return sign; + } + } + + return NULL; +} + +/* + * Return the identifier of the sign at line number 'lnum' in buffer 'buf'. + */ int buf_findsign_id( buf_T *buf, // buffer whose sign we are searching for linenr_T lnum // line number of sign @@ -5584,10 +5697,9 @@ int buf_findsign_id( { signlist_T *sign; // a sign in the signlist - for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { - if (sign->lnum == lnum) { - return sign->id; - } + sign = buf_getsign_at_line(buf, lnum); + if (sign != NULL) { + return sign->id; } return 0; @@ -5597,8 +5709,10 @@ int buf_findsign_id( /* * Delete signs in buffer "buf". */ -void buf_delete_signs(buf_T *buf) +buf_delete_signs(buf_T *buf, char_u *group) { + signlist_T *sign; + signlist_T **lastp; // pointer to pointer to current sign signlist_T *next; // When deleting the last sign need to redraw the windows to remove the @@ -5608,10 +5722,19 @@ void buf_delete_signs(buf_T *buf) changed_cline_bef_curs(); } - while (buf->b_signlist != NULL) { - next = buf->b_signlist->next; - xfree(buf->b_signlist); - buf->b_signlist = next; + lastp = &buf->b_signlist; + for (sign = buf->b_signlist; sign != NULL; sign = next) { + next = sign->next; + if (sign_in_group(sign, group)) { + *lastp = next; + if (next != NULL) { + next->prev = sign->prev; + } + xfree(sign->group); + xfree(sign); + } else { + lastp = &sign->next; + } } buf->b_signcols_max = -1; } @@ -5623,7 +5746,7 @@ void buf_delete_all_signs(void) { FOR_ALL_BUFFERS(buf) { if (buf->b_signlist != NULL) { - buf_delete_signs(buf); + buf_delete_signs(buf, (char_u *)"*"); } } } @@ -5631,11 +5754,12 @@ void buf_delete_all_signs(void) /* * List placed signs for "rbuf". If "rbuf" is NULL do it for all buffers. */ -void sign_list_placed(buf_T *rbuf) +void sign_list_placed(buf_T *rbuf, char_u *sign_group) { buf_T *buf; - signlist_T *p; + signlist_T *sign; char lbuf[BUFSIZ]; + char group[BUFSIZ]; MSG_PUTS_TITLE(_("\n--- Signs ---")); msg_putchar('\n'); @@ -5650,11 +5774,21 @@ void sign_list_placed(buf_T *rbuf) MSG_PUTS_ATTR(lbuf, HL_ATTR(HLF_D)); msg_putchar('\n'); } - for (p = buf->b_signlist; p != NULL && !got_int; p = p->next) { - vim_snprintf(lbuf, BUFSIZ, _(" line=%" PRId64 " id=%d name=%s"), - (int64_t)p->lnum, p->id, sign_typenr2name(p->typenr)); - MSG_PUTS(lbuf); - msg_putchar('\n'); + FOR_ALL_SIGNS_IN_BUF(buf) { + if (!sign_in_group(sign, sign_group)) { + continue; + } + if (sign->group != NULL) { + vim_snprintf(group, BUFSIZ, " group=%s", sign->group); + } else { + group[0] = '\0'; + } + vim_snprintf(lbuf, BUFSIZ, _(" line=%ld id=%d%s name=%s " + "priority=%d"), + (long)sign->lnum, sign->id, group, + sign_typenr2name(sign->typenr), sign->priority); + MSG_PUTS(lbuf); + msg_putchar('\n'); } if (rbuf != NULL) { break; @@ -5675,7 +5809,7 @@ void sign_mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_a curbuf->b_signcols_max = -1; lastp = &curbuf->b_signlist; - for (sign = curbuf->b_signlist; sign != NULL; sign = next) { + FOR_ALL_SIGNS_IN_BUF(curbuf) { next = sign->next; if (sign->lnum >= line1 && sign->lnum <= line2) { if (amount == MAXLNUM) { diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 9f56b42fba..b276b570b2 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -9290,16 +9290,14 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) static list_T *get_buffer_signs(buf_T *buf) FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { + signlist_T *sign; + dict_T *d; list_T *const l = tv_list_alloc(kListLenMayKnow); - for (signlist_T *sign = buf->b_signlist; sign; sign = sign->next) { - dict_T *const d = tv_dict_alloc(); - - tv_dict_add_nr(d, S_LEN("id"), sign->id); - tv_dict_add_nr(d, S_LEN("lnum"), sign->lnum); - tv_dict_add_str(d, S_LEN("name"), - (const char *)sign_typenr2name(sign->typenr)); - tv_list_append_dict(l, d); + FOR_ALL_SIGNS_IN_BUF(buf) { + if ((d = sign_get_info(sign)) != NULL) { + tv_list_append_dict(l, d); + } } return l; } @@ -15439,6 +15437,318 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /* + * "sign_define()" function + */ + static void +f_sign_define(typval_T *argvars, typval_T *rettv) +{ + char_u *name; + dict_T *dict; + char_u *icon = NULL; + char_u *linehl = NULL; + char_u *text = NULL; + char_u *texthl = NULL; + char_u *numhl = NULL; + + rettv->vval.v_number = -1; + + name = tv_get_string_chk(&argvars[0]); + if (name == NULL) + return; + + if (argvars[1].v_type != VAR_UNKNOWN) + { + if (argvars[1].v_type != VAR_DICT) + { + EMSG(_(e_dictreq)); + return; + } + + // sign attributes + dict = argvars[1].vval.v_dict; + if (tv_dict_find(dict, (char_u *)"icon", -1) != NULL) + icon = tv_dict_get_string(dict, (char_u *)"icon", TRUE); + if (tv_dict_find(dict, (char_u *)"linehl", -1) != NULL) + linehl = tv_dict_get_string(dict, (char_u *)"linehl", TRUE); + if (tv_dict_find(dict, (char_u *)"text", -1) != NULL) + text = tv_dict_get_string(dict, (char_u *)"text", TRUE); + if (tv_dict_find(dict, (char_u *)"texthl", -1) != NULL) + texthl = tv_dict_get_string(dict, (char_u *)"texthl", TRUE); + if (tv_dict_find(dict, (char_u *)"numhl", -1) != NULL) + numhl = tv_dict_get_string(dict, (char_u *)"numhl", TRUE); + } + + if (sign_define_by_name(name, icon, linehl, text, texthl, numhl) == OK) + rettv->vval.v_number = 0; + + xfree(icon); + xfree(linehl); + xfree(text); + xfree(texthl); +} + +/* + * "sign_getdefined()" function + */ + static void +f_sign_getdefined(typval_T *argvars, typval_T *rettv) +{ + char_u *name = NULL; + + tv_list_alloc_ret(rettv, 0); + + if (argvars[0].v_type != VAR_UNKNOWN) + name = tv_get_string(&argvars[0]); + + sign_getlist(name, rettv->vval.v_list); +} + +/* + * "sign_getplaced()" function + */ + static void +f_sign_getplaced(typval_T *argvars, typval_T *rettv) +{ + buf_T *buf = NULL; + dict_T *dict; + dictitem_T *di; + linenr_T lnum = 0; + int sign_id = 0; + char_u *group = NULL; + int notanum = FALSE; + + tv_list_alloc_ret(rettv, 0); + + if (argvars[0].v_type != VAR_UNKNOWN) + { + // get signs placed in this buffer + buf = find_buffer(&argvars[0]); + if (buf == NULL) + { + EMSG2(_("E158: Invalid buffer name: %s"), + tv_get_string(&argvars[0])); + return; + } + + if (argvars[1].v_type != VAR_UNKNOWN) + { + if (argvars[1].v_type != VAR_DICT || + ((dict = argvars[1].vval.v_dict) == NULL)) + { + EMSG(_(e_dictreq)); + return; + } + if ((di = tv_dict_find(dict, (char_u *)"lnum", -1)) != NULL) + { + // get signs placed at this line + (void)tv_get_number_chk(&di->di_tv, ¬anum); + if (notanum) + return; + lnum = tv_get_lnum(&di->di_tv); + } + if ((di = tv_dict_find(dict, (char_u *)"id", -1)) != NULL) + { + // get sign placed with this identifier + sign_id = (int)tv_get_number_chk(&di->di_tv, ¬anum); + if (notanum) + return; + } + if ((di = tv_dict_find(dict, (char_u *)"group", -1)) != NULL) + { + group = tv_get_string_chk(&di->di_tv); + if (group == NULL) + return; + } + } + } + + sign_get_placed(buf, lnum, sign_id, group, rettv->vval.v_list); +} + +/* + * "sign_place()" function + */ + static void +f_sign_place(typval_T *argvars, typval_T *rettv) +{ + int sign_id; + char_u *group = NULL; + char_u *sign_name; + buf_T *buf; + dict_T *dict; + dictitem_T *di; + linenr_T lnum = 0; + int prio = SIGN_DEF_PRIO; + int notanum = FALSE; + + rettv->vval.v_number = -1; + + // Sign identifer + sign_id = (int)tv_get_number_chk(&argvars[0], ¬anum); + if (notanum) + return; + if (sign_id < 0) + { + EMSG(_(e_invarg)); + return; + } + + // Sign group + group = tv_get_string_chk(&argvars[1]); + if (group == NULL) + return; + if (group[0] == '\0') + group = NULL; // global sign group + else + { + group = vim_strsave(group); + if (group == NULL) + return; + } + + // Sign name + sign_name = tv_get_string_chk(&argvars[2]); + if (sign_name == NULL) + goto cleanup; + + // Buffer to place the sign + buf = find_buffer(&argvars[3]); + if (buf == NULL) + { + EMSG2(_("E158: Invalid buffer name: %s"), tv_get_string(&argvars[2])); + goto cleanup; + } + + if (argvars[4].v_type != VAR_UNKNOWN) + { + if (argvars[4].v_type != VAR_DICT || + ((dict = argvars[4].vval.v_dict) == NULL)) + { + EMSG(_(e_dictreq)); + goto cleanup; + } + + // Line number where the sign is to be placed + if ((di = tv_dict_find(dict, (char_u *)"lnum", -1)) != NULL) + { + (void)tv_get_number_chk(&di->di_tv, ¬anum); + if (notanum) + goto cleanup; + lnum = tv_get_lnum(&di->di_tv); + } + if ((di = tv_dict_find(dict, (char_u *)"priority", -1)) != NULL) + { + // Sign priority + prio = (int)tv_get_number_chk(&di->di_tv, ¬anum); + if (notanum) + goto cleanup; + } + } + + if (sign_place(&sign_id, group, sign_name, buf, lnum, prio) == OK) + rettv->vval.v_number = sign_id; + +cleanup: + xfree(group); +} + +/* + * "sign_undefine()" function + */ + static void +f_sign_undefine(typval_T *argvars, typval_T *rettv) +{ + char_u *name; + + rettv->vval.v_number = -1; + + if (argvars[0].v_type == VAR_UNKNOWN) + { + // Free all the signs + free_signs(); + rettv->vval.v_number = 0; + } + else + { + // Free only the specified sign + name = tv_get_string_chk(&argvars[0]); + if (name == NULL) + return; + + if (sign_undefine_by_name(name) == OK) + rettv->vval.v_number = 0; + } +} + +/* + * "sign_unplace()" function + */ + static void +f_sign_unplace(typval_T *argvars, typval_T *rettv) +{ + dict_T *dict; + dictitem_T *di; + int sign_id = 0; + buf_T *buf = NULL; + char_u *group = NULL; + + rettv->vval.v_number = -1; + + if (argvars[0].v_type != VAR_STRING) + { + EMSG(_(e_invarg)); + return; + } + + group = tv_get_string(&argvars[0]); + if (group[0] == '\0') + group = NULL; // global sign group + else + { + group = vim_strsave(group); + if (group == NULL) + return; + } + + if (argvars[1].v_type != VAR_UNKNOWN) + { + if (argvars[1].v_type != VAR_DICT) + { + EMSG(_(e_dictreq)); + return; + } + dict = argvars[1].vval.v_dict; + + if ((di = tv_dict_find(dict, (char_u *)"buffer", -1)) != NULL) + { + buf = find_buffer(&di->di_tv); + if (buf == NULL) + { + EMSG2(_("E158: Invalid buffer name: %s"), + tv_get_string(&di->di_tv)); + return; + } + } + if (tv_dict_find(dict, (char_u *)"id", -1) != NULL) + sign_id = tv_dict_get_number(dict, (char_u *)"id"); + } + + if (buf == NULL) + { + // Delete the sign in all the buffers + FOR_ALL_BUFFERS(buf) + if (sign_unplace(sign_id, group, buf) == OK) + rettv->vval.v_number = 0; + } + else + { + if (sign_unplace(sign_id, group, buf) == OK) + rettv->vval.v_number = 0; + } + xfree(group); +} + +/* * "simplify()" function */ static void f_simplify(typval_T *argvars, typval_T *rettv, FunPtr fptr) diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index dea00c3edd..f583123025 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -277,6 +277,12 @@ return { sha256={args=1}, shellescape={args={1, 2}}, shiftwidth={}, + sign_define={args={1, 2}}, + sign_getdefined={args={0, 1}}, + sign_getplaced={args={0, 2}}, + sign_place={args={4, 5}}, + sign_undefine={args={0, 1}}, + sign_unplace={args={1, 2}}, simplify={args=1}, sin={args=1, func="float_op_wrapper", data="&sin"}, sinh={args=1, func="float_op_wrapper", data="&sinh"}, diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index e7f4736613..4a589ec6e5 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -5589,6 +5589,275 @@ static int sign_cmd_idx( } /* + * Find a sign by name. Also returns pointer to the previous sign. + */ + static sign_T * +sign_find(char_u *name, sign_T **sp_prev) +{ + sign_T *sp; + + if (sp_prev != NULL) + *sp_prev = NULL; + for (sp = first_sign; sp != NULL; sp = sp->sn_next) + { + if (STRCMP(sp->sn_name, name) == 0) + break; + if (sp_prev != NULL) + *sp_prev = sp; + } + + return sp; +} + +/* + * Define a new sign or update an existing sign + */ +int sign_define_by_name( + char_u *name, + char_u *icon, + char_u *linehl, + char_u *text, + char_u *texthl, + char_u *numhl +) +{ + sign_T *sp_prev; + sign_T *sp; + + sp = sign_find(name, &sp_prev); + if (sp == NULL) + { + sign_T *lp; + int start = next_sign_typenr; + + // Allocate a new sign. + sp = xcalloc(1, sizeof(sign_T)); + + // Check that next_sign_typenr is not already being used. + // This only happens after wrapping around. Hopefully + // another one got deleted and we can use its number. + for (lp = first_sign; lp != NULL; ) { + if (lp->sn_typenr == next_sign_typenr) { + ++next_sign_typenr; + if (next_sign_typenr == MAX_TYPENR) { + next_sign_typenr = 1; + } + if (next_sign_typenr == start) { + xfree(sp); + EMSG(_("E612: Too many signs defined")); + return FAIL; + } + lp = first_sign; // start all over + continue; + } + lp = lp->sn_next; + } + + sp->sn_typenr = next_sign_typenr; + if (++next_sign_typenr == MAX_TYPENR) + next_sign_typenr = 1; // wrap around + + sp->sn_name = vim_strsave(name); + + // add the new sign to the list of signs + if (sp_prev == NULL) { + first_sign = sp; + } else { + sp_prev->sn_next = sp; + } + } + + // set values for a defined sign. + if (icon != NULL) + { + xfree(sp->sn_icon); + sp->sn_icon = vim_strsave(icon); + backslash_halve(sp->sn_icon); +# ifdef FEAT_SIGN_ICONS + if (gui.in_use) + { + out_flush(); + if (sp->sn_image != NULL) + gui_mch_destroy_sign(sp->sn_image); + sp->sn_image = gui_mch_register_sign(sp->sn_icon); + } +# endif + } + + if (text != NULL) + { + char_u *s; + char_u *endp; + int cells; + int len; + + endp = text + (int)STRLEN(text); + for (s = text; s + 1 < endp; ++s) { + if (*s == '\\') + { + // Remove a backslash, so that it is possible + // to use a space. + STRMOVE(s, s + 1); + --endp; + } + } + // Count cells and check for non-printable chars + cells = 0; + for (s = text; s < endp; s += (*mb_ptr2len)(s)) { + if (!vim_isprintc(utf_ptr2char(s))) { + break; + } + cells += utf_ptr2cells(s); + } + // Currently must be one or two display cells + if (s != endp || cells < 1 || cells > 2) { + EMSG2(_("E239: Invalid sign text: %s"), text); + return FAIL; + } + + xfree(sp->sn_text); + // Allocate one byte more if we need to pad up + // with a space. + len = (int)(endp - text + ((cells == 1) ? 1 : 0)); + sp->sn_text = vim_strnsave(text, len); + + if (cells == 1) + STRCPY(sp->sn_text + len - 1, " "); + } + + if (linehl != NULL) + sp->sn_line_hl = syn_check_group(linehl, (int)STRLEN(linehl)); + + if (texthl != NULL) + sp->sn_text_hl = syn_check_group(texthl, (int)STRLEN(texthl)); + + if (numhl != NULL) + sp->sn_num_hl = syn_check_group(numhl, (int)STRLEN(numhl)); + + return OK; +} + +/* + * Free the sign specified by 'name'. + */ + int +sign_undefine_by_name(char_u *name) +{ + sign_T *sp_prev; + sign_T *sp; + + sp = sign_find(name, &sp_prev); + if (sp == NULL) + { + EMSG2(_("E155: Unknown sign: %s"), name); + return FAIL; + } + sign_undefine(sp, sp_prev); + + return OK; +} + +/* + * List the signs matching 'name' + */ + static void +sign_list_by_name(char_u *name) +{ + sign_T *sp; + + sp = sign_find(name, NULL); + if (sp != NULL) + sign_list_defined(sp); + else + EMSG2(_("E155: Unknown sign: %s"), name); +} + +/* + * Place a sign at the specifed file location or update a sign. + */ +int sign_place( + int *sign_id, + char_u *sign_group, + char_u *sign_name, + buf_T *buf, + linenr_T lnum, + int prio +) +{ + sign_T *sp; + + // Check for reserved character '*' in group name + if (sign_group != NULL && (*sign_group == '*' || *sign_group == '\0')) { + return FAIL; + } + + for (sp = first_sign; sp != NULL; sp = sp->sn_next) { + if (STRCMP(sp->sn_name, sign_name) == 0) { + break; + } + } + if (sp == NULL) + { + EMSG2(_("E155: Unknown sign: %s"), sign_name); + return FAIL; + } + if (*sign_id == 0) + { + // Allocate a new sign id + int id = 1; + signlist_T *sign; + + while ((sign = buf_getsign_with_id(buf, id, sign_group)) != NULL) { + id++; + } + + *sign_id = id; + } + + if (lnum > 0) { + // ":sign place {id} line={lnum} name={name} file={fname}": + // place a sign + buf_addsign(buf, *sign_id, sign_group, prio, lnum, sp->sn_typenr); + } else { + // ":sign place {id} file={fname}": change sign type + lnum = buf_change_sign_type(buf, *sign_id, sign_group, sp->sn_typenr); + } + if (lnum > 0) { + redraw_buf_line_later(buf, lnum); + } else { + EMSG2(_("E885: Not possible to change sign %s"), sign_name); + return FAIL; + } + + return OK; +} + +/* + * Unplace the specified sign + */ + int +sign_unplace(int sign_id, char_u *sign_group, buf_T *buf) +{ + if (sign_id == 0) + { + // Delete all the signs in the specified buffer + redraw_buf_later(buf, NOT_VALID); + buf_delete_signs(buf, sign_group); + } else { + linenr_T lnum; + + // Delete only the specified signs + lnum = buf_delsign(buf, sign_id, sign_group); + if (lnum == 0) { + return FAIL; + } + redraw_buf_line_later(buf, lnum); + } + + return OK; +} + +/* * ":sign" command */ void ex_sign(exarg_T *eap) @@ -5597,7 +5866,6 @@ void ex_sign(exarg_T *eap) char_u *p; int idx; sign_T *sp; - sign_T *sp_prev; // Parse the subcommand. p = skiptowhite(arg); @@ -5618,6 +5886,13 @@ void ex_sign(exarg_T *eap) } else if (*arg == NUL) { EMSG(_("E156: Missing sign name")); } else { + char_u *name; + char_u *icon = NULL; + char_u *text = NULL; + char_u *linehl = NULL; + char_u *texthl = NULL; + char_u *numhl = NULL; + // Isolate the sign name. If it's a number skip leading zeroes, // so that "099" and "99" are the same sign. But keep "0". p = skiptowhite(arg); @@ -5627,57 +5902,11 @@ void ex_sign(exarg_T *eap) while (arg[0] == '0' && arg[1] != NUL) { arg++; } + name = vim_strsave(arg); - sp_prev = NULL; - for (sp = first_sign; sp != NULL; sp = sp->sn_next) { - if (STRCMP(sp->sn_name, arg) == 0) { - break; - } - sp_prev = sp; - } if (idx == SIGNCMD_DEFINE) { + int failed = FALSE; // ":sign define {name} ...": define a sign - if (sp == NULL) { - sign_T *lp; - int start = next_sign_typenr; - - // Allocate a new sign. - sp = xcalloc(1, sizeof(sign_T)); - - // Check that next_sign_typenr is not already being used. - // This only happens after wrapping around. Hopefully - // another one got deleted and we can use its number. - for (lp = first_sign; lp != NULL; ) { - if (lp->sn_typenr == next_sign_typenr) { - next_sign_typenr++; - if (next_sign_typenr == MAX_TYPENR) { - next_sign_typenr = 1; - } - if (next_sign_typenr == start) { - xfree(sp); - EMSG(_("E612: Too many signs defined")); - return; - } - lp = first_sign; // start all over - continue; - } - lp = lp->sn_next; - } - - sp->sn_typenr = next_sign_typenr; - if (++next_sign_typenr == MAX_TYPENR) { - next_sign_typenr = 1; // wrap around - } - - sp->sn_name = vim_strsave(arg); - - // add the new sign to the list of signs - if (sp_prev == NULL) { - first_sign = sp; - } else { - sp_prev->sn_next = sp; - } - } // set values for a defined sign. for (;;) { @@ -5688,88 +5917,62 @@ void ex_sign(exarg_T *eap) p = skiptowhite_esc(arg); if (STRNCMP(arg, "icon=", 5) == 0) { arg += 5; - xfree(sp->sn_icon); - sp->sn_icon = vim_strnsave(arg, (int)(p - arg)); - backslash_halve(sp->sn_icon); + icon = vim_strnsave(arg, (int)(p - arg)); } else if (STRNCMP(arg, "text=", 5) == 0) { - char_u *s; - int cells; - int len; - arg += 5; - for (s = arg; s + 1 < p; s++) { - if (*s == '\\') { - // Remove a backslash, so that it is possible - // to use a space. - STRMOVE(s, s + 1); - p--; - } - } - - // Count cells and check for non-printable chars - cells = 0; - for (s = arg; s < p; s += utfc_ptr2len(s)) { - if (!vim_isprintc(utf_ptr2char(s))) { - break; - } - cells += utf_ptr2cells(s); - } - // Currently must be one or two display cells - if (s != p || cells < 1 || cells > 2) { - *p = NUL; - EMSG2(_("E239: Invalid sign text: %s"), arg); - return; - } - - xfree(sp->sn_text); - // Allocate one byte more if we need to pad up - // with a space. - len = (int)(p - arg + ((cells == 1) ? 1 : 0)); - sp->sn_text = vim_strnsave(arg, len); - - if (cells == 1) { - STRCPY(sp->sn_text + len - 1, " "); - } + text = vim_strnsave(arg, (int)(p - arg)); } else if (STRNCMP(arg, "linehl=", 7) == 0) { arg += 7; - sp->sn_line_hl = syn_check_group(arg, (int)(p - arg)); + linehl = vim_strnsave(arg, (int)(p - arg)); } else if (STRNCMP(arg, "texthl=", 7) == 0) { arg += 7; - sp->sn_text_hl = syn_check_group(arg, (int)(p - arg)); + texthl = vim_strnsave(arg, (int)(p - arg)); } else if (STRNCMP(arg, "numhl=", 6) == 0) { arg += 6; - sp->sn_num_hl = syn_check_group(arg, (int)(p - arg)); + numhl = vim_strnsave(arg, (int)(p - arg)); } else { EMSG2(_(e_invarg2), arg); - return; + failed = TRUE; + break; } } - } else if (sp == NULL) { - EMSG2(_("E155: Unknown sign: %s"), arg); + + if (!failed) + sign_define_by_name(name, icon, linehl, text, texthl, numhl); + + xfree(icon); + xfree(text); + xfree(linehl); + xfree(texthl); } else if (idx == SIGNCMD_LIST) { // ":sign list {name}" - sign_list_defined(sp); + sign_list_by_name(name); } else { // ":sign undefine {name}" - sign_undefine(sp, sp_prev); + sign_undefine_by_name(name); } + + xfree(name); + return; } } else { int id = -1; linenr_T lnum = -1; char_u *sign_name = NULL; + char_u *group = NULL; + int prio = SIGN_DEF_PRIO; char_u *arg1; + int bufarg = FALSE; if (*arg == NUL) { if (idx == SIGNCMD_PLACE) { // ":sign place": list placed signs in all buffers - sign_list_placed(NULL); + sign_list_placed(NULL, NULL); } else if (idx == SIGNCMD_UNPLACE) { // ":sign unplace": remove placed sign at cursor id = buf_findsign_id(curwin->w_buffer, curwin->w_cursor.lnum); if (id > 0) { - buf_delsign(curwin->w_buffer, id); - redraw_buf_line_later(curwin->w_buffer, curwin->w_cursor.lnum); + sign_unplace(id, NULL, curwin->w_buffer); } else { EMSG(_("E159: Missing sign number")); } @@ -5797,9 +6000,7 @@ void ex_sign(exarg_T *eap) if (idx == SIGNCMD_UNPLACE && *arg == NUL) { // ":sign unplace {id}": remove placed sign by number FOR_ALL_BUFFERS(buf) { - if ((lnum = buf_delsign(buf, id)) != 0) { - redraw_buf_line_later(buf, lnum); - } + sign_unplace(id, NULL, buf); } return; } @@ -5810,7 +6011,7 @@ void ex_sign(exarg_T *eap) // Leave "arg" pointing to {fname}. buf_T *buf = NULL; - for (;;) { + while (*arg != NUL) { if (STRNCMP(arg, "line=", 5) == 0) { arg += 5; lnum = atoi((char *)arg); @@ -5832,9 +6033,21 @@ void ex_sign(exarg_T *eap) while (sign_name[0] == '0' && sign_name[1] != NUL) { sign_name++; } + } else if (STRNCMP(arg, "group=", 6) == 0) { + arg += 6; + group = arg; + arg = skiptowhite(arg); + if (*arg != NUL) { + *arg++ = NUL; + } + } else if (STRNCMP(arg, "priority=", 9) == 0) { + arg += 9; + prio = atoi((char *)arg); + arg = skiptowhite(arg); } else if (STRNCMP(arg, "file=", 5) == 0) { arg += 5; - buf = buflist_findname(arg); + buf = buflist_findname_exp(arg); + bufarg = TRUE; break; } else if (STRNCMP(arg, "buffer=", 7) == 0) { arg += 7; @@ -5842,6 +6055,7 @@ void ex_sign(exarg_T *eap) if (*skipwhite(arg) != NUL) { EMSG(_(e_trailing)); } + bufarg = TRUE; break; } else { EMSG(_(e_invarg)); @@ -5850,20 +6064,28 @@ void ex_sign(exarg_T *eap) arg = skipwhite(arg); } - if (buf == NULL) { + if ((!bufarg && group == NULL) || (group != NULL && *group == '\0')) { + // File or buffer is not specified or an empty group is used + EMSG(_(e_invarg)); + return; + } + + if (bufarg && buf == NULL) { EMSG2(_("E158: Invalid buffer name: %s"), arg); } else if (id <= 0 && !(idx == SIGNCMD_UNPLACE && id == -2)) { - if (lnum >= 0 || sign_name != NULL) { + if ((group == NULL) && (lnum >= 0 || sign_name != NULL)) { EMSG(_(e_invarg)); } else { // ":sign place file={fname}": list placed signs in one file - sign_list_placed(buf); + // ":sign place group={group} file={fname}" + // ":sign place group=* file={fname}" + sign_list_placed(buf, group); } } else if (idx == SIGNCMD_JUMP) { // ":sign jump {id} file={fname}" if (lnum >= 0 || sign_name != NULL) { EMSG(_(e_invarg)); - } else if ((lnum = buf_findsign(buf, id)) > 0) { + } else if ((lnum = buf_findsign(buf, id, group)) > 0) { // goto a sign ... if (buf_jump_open_win(buf) != NULL) { // ... in a current window @@ -5892,38 +6114,37 @@ void ex_sign(exarg_T *eap) if (lnum >= 0 || sign_name != NULL) { EMSG(_(e_invarg)); } else if (id == -2) { - // ":sign unplace * file={fname}" - redraw_buf_later(buf, NOT_VALID); - buf_delete_signs(buf); - } else { - // ":sign unplace {id} file={fname}" - lnum = buf_delsign(buf, id); - redraw_buf_line_later(buf, lnum); - } - } else if (sign_name != NULL) { - // idx == SIGNCMD_PLACE - for (sp = first_sign; sp != NULL; sp = sp->sn_next) { - if (STRCMP(sp->sn_name, sign_name) == 0) { - break; + if (buf != NULL) { + // ":sign unplace * file={fname}" + sign_unplace(0, group, buf); + } else { + // ":sign unplace * group=*": remove all placed signs + FOR_ALL_BUFFERS(buf) { + if (buf->b_signlist != NULL) { + buf_delete_signs(buf, group); + } + } } - } - if (sp == NULL) { - EMSG2(_("E155: Unknown sign: %s"), sign_name); - return; - } - if (lnum > 0) { - // ":sign place {id} line={lnum} name={name} file={fname}": - // place a sign - buf_addsign(buf, id, lnum, sp->sn_typenr); - } else { - // ":sign place {id} file={fname}": change sign type - lnum = buf_change_sign_type(buf, id, sp->sn_typenr); - } - if (lnum > 0) { - redraw_buf_line_later(buf, lnum); } else { - EMSG2(_("E885: Not possible to change sign %s"), sign_name); + if (buf != NULL) { + // ":sign unplace {id} file={fname}" + // ":sign unplace {id} group={group} file={fname}" + // ":sign unplace {id} group=* file={fname}" + sign_unplace(id, group, buf); + } else { + // ":sign unplace {id} group={group}": + // ":sign unplace {id} group=*": + // remove all placed signs in this group. + FOR_ALL_BUFFERS(buf) { + if (buf->b_signlist != NULL) { + sign_unplace(id, group, buf); + } + } + } } + } else if (sign_name != NULL && buf != NULL) { + // idx == SIGNCMD_PLACE + sign_place(&id, group, sign_name, buf, lnum, prio); } else { EMSG(_(e_invarg)); } @@ -5931,6 +6152,137 @@ void ex_sign(exarg_T *eap) } /* + * Return information about a specified sign + */ +static void sign_getinfo(sign_T *sp, dict_T *retdict) +{ + char_u *p; + + tv_dict_add_str(retdict, S_LEN("name"), (char_u *)sp->sn_name); + if (sp->sn_icon != NULL) { + tv_dict_add_str(retdict, S_LEN("icon"), (char_u *)sp->sn_icon); + } + if (sp->sn_text != NULL) { + tv_dict_add_str(retdict, S_LEN("text"), (char_u *)sp->sn_text); + } + if (sp->sn_line_hl > 0) { + p = get_highlight_name_ext(NULL, sp->sn_line_hl - 1, FALSE); + if (p == NULL) { + p = (char_u *)"NONE"; + } + tv_dict_add_str(retdict, S_LEN("linehl"), (char_u *)p); + } + if (sp->sn_text_hl > 0) { + p = get_highlight_name_ext(NULL, sp->sn_text_hl - 1, FALSE); + if (p == NULL) { + p = (char_u *)"NONE"; + } + tv_dict_add_str(retdict, S_LEN("texthl"), (char_u *)p); + } + if (sp->sn_num_hl > 0) { + p = get_highlight_name_ext(NULL, sp->sn_num_hl - 1, FALSE); + if (p == NULL) { + p = (char_u *)"NONE"; + } + tv_dict_add_str(retdict, S_LEN("numhl"), (char_u *)p); + } +} + +/* + * If 'name' is NULL, return a list of all the defined signs. + * Otherwise, return information about the specified sign. + */ +void sign_getlist(char_u *name, list_T *retlist) +{ + sign_T *sp = first_sign; + dict_T *dict; + + if (name != NULL) { + sp = sign_find(name, NULL); + if (sp == NULL) { + return; + } + } + + for (; sp != NULL && !got_int; sp = sp->sn_next) { + if ((dict = tv_dict_alloc()) == NULL) { + return; + } + tv_list_append_dict(retlist, dict); + sign_getinfo(sp, dict); + + if (name != NULL) { // handle only the specified sign + break; + } + } +} + +/* + * Return information about all the signs placed in a buffer + */ +static void sign_get_placed_in_buf( + buf_T *buf, + linenr_T lnum, + int sign_id, + char_u *sign_group, + list_T *retlist) +{ + dict_T *d; + list_T *l; + signlist_T *sign; + dict_T *sdict; + + if ((d = tv_dict_alloc()) == NULL) { + return; + } + tv_list_append_dict(retlist, d); + + tv_dict_add_nr(d, S_LEN("bufnr"), (long)buf->b_fnum); + + if ((l = tv_list_alloc(kListLenMayKnow)) == NULL) { + return; + } + tv_dict_add_list(d, S_LEN("signs"), l); + + FOR_ALL_SIGNS_IN_BUF(buf) { + if (!sign_in_group(sign, sign_group)) { + continue; + } + if ((lnum == 0 && sign_id == 0) || + (sign_id == 0 && lnum == sign->lnum) || + (lnum == 0 && sign_id == sign->id) || + (lnum == sign->lnum && sign_id == sign->id)) { + if ((sdict = sign_get_info(sign)) != NULL) { + tv_list_append_dict(l, sdict); + } + } + } +} + +/* + * Get a list of signs placed in buffer 'buf'. If 'num' is non-zero, return the + * sign placed at the line number. If 'lnum' is zero, return all the signs + * placed in 'buf'. If 'buf' is NULL, return signs placed in all the buffers. + */ +void sign_get_placed( + buf_T *buf, + linenr_T lnum, + int sign_id, + char_u *sign_group, + list_T *retlist) +{ + if (buf != NULL) { + sign_get_placed_in_buf(buf, lnum, sign_id, sign_group, retlist); + } else { + FOR_ALL_BUFFERS(buf) { + if (buf->b_signlist != NULL) { + sign_get_placed_in_buf(buf, 0, sign_id, sign_group, retlist); + } + } + } +} + +/* * List one sign. */ static void sign_list_defined(sign_T *sp) @@ -6050,7 +6402,6 @@ char_u * sign_typenr2name(int typenr) return (char_u *)_("[Deleted]"); } -#if defined(EXITFREE) /* * Undefine/free all signs. */ @@ -6059,7 +6410,6 @@ void free_signs(void) while (first_sign != NULL) sign_undefine(first_sign, NULL); } -#endif static enum { diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 9fa294ba87..9890cba637 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -482,6 +482,11 @@ EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer #define FOR_ALL_BUFFERS_BACKWARDS(buf) \ for (buf_T *buf = lastbuf; buf != NULL; buf = buf->b_prev) +// Iterate through all the signs placed in a buffer +#define FOR_ALL_SIGNS_IN_BUF(buf) \ + for (sign = buf->b_signlist; sign != NULL; sign = sign->next) + + /* * List of files being edited (global argument list). curwin->w_alist points * to this when the window is using the global argument list. diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h index b4f2709d45..313fb330ed 100644 --- a/src/nvim/sign_defs.h +++ b/src/nvim/sign_defs.h @@ -12,10 +12,15 @@ struct signlist int id; // unique identifier for each placed sign linenr_T lnum; // line number which has this sign int typenr; // typenr of sign + char_u *group; // sign group + int priority; // priority for highlighting signlist_T *next; // next signlist entry signlist_T *prev; // previous entry -- for easy reordering }; +// Default sign priority for highlighting +#define SIGN_DEF_PRIO 10 + // type argument for buf_getsigntype() and sign_get_attr() typedef enum { SIGN_ANY, diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim index 3960177acd..5e2ac3fb94 100644 --- a/src/nvim/testdir/test_signs.vim +++ b/src/nvim/testdir/test_signs.vim @@ -16,8 +16,8 @@ func Test_sign() try sign define Sign2 text=xy texthl=Title linehl=Error numhl=Number icon=../../pixmaps/stock_vim_find_help.png catch /E255:/ - " ignore error: E255: Couldn't read in sign data! - " This error can happen when running in gui. + " Ignore error: E255: Couldn't read in sign data! + " This error can happen when running in the GUI. " Some gui like Motif do not support the png icon format. endtry @@ -63,7 +63,7 @@ func Test_sign() " Check placed signs let a=execute('sign place') - call assert_equal("\n--- Signs ---\nSigns for [NULL]:\n line=3 id=41 name=Sign1\n", a) + call assert_equal("\n--- Signs ---\nSigns for [NULL]:\n line=3 id=41 name=Sign1 priority=10\n", a) " Unplace the sign and try jumping to it again should fail. sign unplace 41 @@ -112,7 +112,7 @@ func Test_sign() " Only 1 or 2 character text is allowed call assert_fails("sign define Sign4 text=abc linehl=Comment", 'E239:') call assert_fails("sign define Sign4 text= linehl=Comment", 'E239:') - call assert_fails("sign define Sign4 text=\ ab linehl=Comment", 'E239:') + call assert_fails("sign define Sign4 text=\\ ab linehl=Comment", 'E239:') " define sign with whitespace sign define Sign4 text=\ X linehl=Comment @@ -131,6 +131,28 @@ func Test_sign() sign define Sign4 text=\\ linehl=Comment sign undefine Sign4 + " Error cases + call assert_fails("exe 'sign place abc line=3 name=Sign1 buffer=' . bufnr('%')", 'E474:') + call assert_fails("exe 'sign unplace abc name=Sign1 buffer=' . bufnr('%')", 'E474:') + call assert_fails("exe 'sign place 1abc line=3 name=Sign1 buffer=' . bufnr('%')", 'E474:') + call assert_fails("exe 'sign unplace 2abc name=Sign1 buffer=' . bufnr('%')", 'E474:') + call assert_fails("sign unplace 2 *", 'E474:') + call assert_fails("exe 'sign place 1 line=3 name=Sign1 buffer=' . bufnr('%') a", 'E488:') + call assert_fails("exe 'sign place name=Sign1 buffer=' . bufnr('%')", 'E474:') + call assert_fails("exe 'sign place line=10 buffer=' . bufnr('%')", 'E474:') + call assert_fails("exe 'sign unplace 2 line=10 buffer=' . bufnr('%')", 'E474:') + call assert_fails("exe 'sign unplace 2 name=Sign1 buffer=' . bufnr('%')", 'E474:') + call assert_fails("exe 'sign place 2 line=3 buffer=' . bufnr('%')", 'E474:') + call assert_fails("sign place 2", 'E474:') + call assert_fails("sign place abc", 'E474:') + call assert_fails("sign place 5 line=3", 'E474:') + call assert_fails("sign place 5 name=Sign1", 'E474:') + call assert_fails("sign place 5 group=g1", 'E474:') + call assert_fails("sign place 5 group=*", 'E474:') + call assert_fails("sign place 5 priority=10", 'E474:') + call assert_fails("sign place 5 line=3 name=Sign1", 'E474:') + call assert_fails("sign place 5 group=g1 line=3 name=Sign1", 'E474:') + " After undefining the sign, we should no longer be able to place it. sign undefine Sign1 sign undefine Sign2 @@ -152,7 +174,7 @@ func Test_sign_undefine_still_placed() " Listing placed sign should show that sign is deleted. let a=execute('sign place') - call assert_equal("\n--- Signs ---\nSigns for foobar:\n line=1 id=41 name=[Deleted]\n", a) + call assert_equal("\n--- Signs ---\nSigns for foobar:\n line=1 id=41 name=[Deleted] priority=10\n", a) sign unplace 41 let a=execute('sign place') @@ -202,6 +224,8 @@ func Test_sign_completion() endfunc func Test_sign_invalid_commands() + sign define Sign1 text=x + call assert_fails('sign', 'E471:') call assert_fails('sign jump', 'E471:') call assert_fails('sign xxx', 'E160:') @@ -211,6 +235,52 @@ func Test_sign_invalid_commands() call assert_fails('sign list xxx', 'E155:') call assert_fails('sign place 1 buffer=999', 'E158:') call assert_fails('sign define Sign2 text=', 'E239:') + " Non-numeric identifier for :sign place + call assert_fails("exe 'sign place abc line=3 name=Sign1 buffer=' . bufnr('%')", 'E474:') + " Non-numeric identifier for :sign unplace + call assert_fails("exe 'sign unplace abc name=Sign1 buffer=' . bufnr('%')", 'E474:') + " Number followed by an alphabet as sign identifier for :sign place + call assert_fails("exe 'sign place 1abc line=3 name=Sign1 buffer=' . bufnr('%')", 'E474:') + " Number followed by an alphabet as sign identifier for :sign unplace + call assert_fails("exe 'sign unplace 2abc name=Sign1 buffer=' . bufnr('%')", 'E474:') + " Sign identifier and '*' for :sign unplace + call assert_fails("sign unplace 2 *", 'E474:') + " Trailing characters after buffer number for :sign place + call assert_fails("exe 'sign place 1 line=3 name=Sign1 buffer=' . bufnr('%') . 'xxx'", 'E488:') + " Trailing characters after buffer number for :sign unplace + call assert_fails("exe 'sign unplace 1 buffer=' . bufnr('%') . 'xxx'", 'E488:') + call assert_fails("exe 'sign unplace * buffer=' . bufnr('%') . 'xxx'", 'E488:') + call assert_fails("sign unplace 1 xxx", 'E474:') + call assert_fails("sign unplace * xxx", 'E474:') + call assert_fails("sign unplace xxx", 'E474:') + " Placing a sign without line number + call assert_fails("exe 'sign place name=Sign1 buffer=' . bufnr('%')", 'E474:') + " Placing a sign without sign name + call assert_fails("exe 'sign place line=10 buffer=' . bufnr('%')", 'E474:') + " Unplacing a sign with line number + call assert_fails("exe 'sign unplace 2 line=10 buffer=' . bufnr('%')", 'E474:') + " Unplacing a sign with sign name + call assert_fails("exe 'sign unplace 2 name=Sign1 buffer=' . bufnr('%')", 'E474:') + " Placing a sign without sign name + call assert_fails("exe 'sign place 2 line=3 buffer=' . bufnr('%')", 'E474:') + " Placing a sign with only sign identifier + call assert_fails("sign place 2", 'E474:') + " Placing a sign with only a name + call assert_fails("sign place abc", 'E474:') + " Placing a sign with only line number + call assert_fails("sign place 5 line=3", 'E474:') + " Placing a sign with only sign name + call assert_fails("sign place 5 name=Sign1", 'E474:') + " Placing a sign with only sign group + call assert_fails("sign place 5 group=g1", 'E474:') + call assert_fails("sign place 5 group=*", 'E474:') + " Placing a sign with only sign priority + call assert_fails("sign place 5 priority=10", 'E474:') + " Placing a sign without buffer number or file name + call assert_fails("sign place 5 line=3 name=Sign1", 'E474:') + call assert_fails("sign place 5 group=g1 line=3 name=Sign1", 'E474:') + + sign undefine Sign1 endfunc func Test_sign_delete_buffer() @@ -224,3 +294,549 @@ func Test_sign_delete_buffer() sign unplace 61 sign undefine Sign endfunc + +" Test for VimL functions for managing signs +func Test_sign_funcs() + " Remove all the signs + call sign_unplace('*') + call sign_undefine() + + " Tests for sign_define() + let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'} + call assert_equal(0, sign_define("sign1", attr)) + call assert_equal([{'name' : 'sign1', 'texthl' : 'Error', + \ 'linehl' : 'Search', 'text' : '=>'}], sign_getdefined()) + + " Define a new sign without attributes and then update it + call sign_define("sign2") + let attr = {'text' : '!!', 'linehl' : 'DiffAdd', 'texthl' : 'DiffChange', + \ 'icon' : 'sign2.ico'} + try + call sign_define("sign2", attr) + catch /E255:/ + " ignore error: E255: Couldn't read in sign data! + " This error can happen when running in gui. + endtry + call assert_equal([{'name' : 'sign2', 'texthl' : 'DiffChange', + \ 'linehl' : 'DiffAdd', 'text' : '!!', 'icon' : 'sign2.ico'}], + \ sign_getdefined("sign2")) + + " Test for a sign name with digits + call assert_equal(0, sign_define(0002, {'linehl' : 'StatusLine'})) + call assert_equal([{'name' : '2', 'linehl' : 'StatusLine'}], + \ sign_getdefined(0002)) + call sign_undefine(0002) + + " Tests for invalid arguments to sign_define() + call assert_fails('call sign_define("sign4", {"text" : "===>"})', 'E239:') + call assert_fails('call sign_define("sign5", {"text" : ""})', 'E239:') + call assert_fails('call sign_define([])', 'E730:') + call assert_fails('call sign_define("sign6", [])', 'E715:') + + " Tests for sign_getdefined() + call assert_equal([], sign_getdefined("none")) + call assert_fails('call sign_getdefined({})', 'E731:') + + " Tests for sign_place() + call writefile(repeat(["Sun is shining"], 30), "Xsign") + edit Xsign + + call assert_equal(10, sign_place(10, '', 'sign1', 'Xsign', + \ {'lnum' : 20})) + call assert_equal([{'bufnr' : bufnr(''), 'signs' : + \ [{'id' : 10, 'group' : '', 'lnum' : 20, 'name' : 'sign1', + \ 'priority' : 10}]}], sign_getplaced('Xsign')) + call assert_equal([{'bufnr' : bufnr(''), 'signs' : + \ [{'id' : 10, 'group' : '', 'lnum' : 20, 'name' : 'sign1', + \ 'priority' : 10}]}], + \ sign_getplaced('Xsign', {'lnum' : 20})) + call assert_equal([{'bufnr' : bufnr(''), 'signs' : + \ [{'id' : 10, 'group' : '', 'lnum' : 20, 'name' : 'sign1', + \ 'priority' : 10}]}], + \ sign_getplaced('Xsign', {'id' : 10})) + + " Tests for invalid arguments to sign_place() + call assert_fails('call sign_place([], "", "mySign", 1)', 'E745:') + call assert_fails('call sign_place(5, "", "mySign", -1)', 'E158:') + call assert_fails('call sign_place(-1, "", "sign1", "Xsign", [])', + \ 'E474:') + call assert_fails('call sign_place(-1, "", "sign1", "Xsign", + \ {"lnum" : 30})', 'E474:') + call assert_fails('call sign_place(10, "", "xsign1x", "Xsign", + \ {"lnum" : 30})', 'E155:') + call assert_fails('call sign_place(10, "", "", "Xsign", + \ {"lnum" : 30})', 'E155:') + call assert_fails('call sign_place(10, "", [], "Xsign", + \ {"lnum" : 30})', 'E730:') + call assert_fails('call sign_place(5, "", "sign1", "abcxyz.xxx", + \ {"lnum" : 10})', 'E158:') + call assert_fails('call sign_place(5, "", "sign1", "", {"lnum" : 10})', + \ 'E158:') + call assert_fails('call sign_place(5, "", "sign1", [], {"lnum" : 10})', + \ 'E158:') + call assert_fails('call sign_place(21, "", "sign1", "Xsign", + \ {"lnum" : -1})', 'E885:') + call assert_fails('call sign_place(22, "", "sign1", "Xsign", + \ {"lnum" : 0})', 'E885:') + call assert_equal(-1, sign_place(1, "*", "sign1", "Xsign", {"lnum" : 10})) + + " Tests for sign_getplaced() + call assert_equal([{'bufnr' : bufnr(''), 'signs' : + \ [{'id' : 10, 'group' : '', 'lnum' : 20, 'name' : 'sign1', + \ 'priority' : 10}]}], + \ sign_getplaced(bufnr('Xsign'))) + call assert_equal([{'bufnr' : bufnr(''), 'signs' : + \ [{'id' : 10, 'group' : '', 'lnum' : 20, 'name' : 'sign1', + \ 'priority' : 10}]}], + \ sign_getplaced()) + call assert_fails("call sign_getplaced('dummy.sign')", 'E158:') + call assert_fails('call sign_getplaced("")', 'E158:') + call assert_fails('call sign_getplaced(-1)', 'E158:') + call assert_fails('call sign_getplaced("Xsign", [])', 'E715:') + call assert_equal([{'bufnr' : bufnr(''), 'signs' : []}], + \ sign_getplaced('Xsign', {'lnum' : 1000000})) + call assert_fails("call sign_getplaced('Xsign', {'lnum' : []})", + \ 'E745:') + call assert_equal([{'bufnr' : bufnr(''), 'signs' : []}], + \ sign_getplaced('Xsign', {'id' : 44})) + call assert_fails("call sign_getplaced('Xsign', {'id' : []})", + \ 'E745:') + + " Tests for sign_unplace() + call sign_place(20, '', 'sign2', 'Xsign', {"lnum" : 30}) + call assert_equal(0, sign_unplace('', + \ {'id' : 20, 'buffer' : 'Xsign'})) + call assert_equal(-1, sign_unplace('', + \ {'id' : 30, 'buffer' : 'Xsign'})) + call sign_place(20, '', 'sign2', 'Xsign', {"lnum" : 30}) + call assert_fails("call sign_unplace('', + \ {'id' : 20, 'buffer' : 'buffer.c'})", 'E158:') + call assert_fails("call sign_unplace('', + \ {'id' : 20, 'buffer' : ''})", 'E158:') + call assert_fails("call sign_unplace('', + \ {'id' : 20, 'buffer' : 200})", 'E158:') + call assert_fails("call sign_unplace('', 'mySign')", 'E715:') + + " Tests for sign_undefine() + call assert_equal(0, sign_undefine("sign1")) + call assert_equal([], sign_getdefined("sign1")) + call assert_fails('call sign_undefine("none")', 'E155:') + call assert_fails('call sign_undefine([])', 'E730:') + + call delete("Xsign") + call sign_unplace('*') + call sign_undefine() + enew | only +endfunc + +" Tests for sign groups +func Test_sign_group() + enew | only + " Remove all the signs + call sign_unplace('*') + call sign_undefine() + + call writefile(repeat(["Sun is shining"], 30), "Xsign") + + let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'} + call assert_equal(0, sign_define("sign1", attr)) + + edit Xsign + let bnum = bufnr('%') + let fname = fnamemodify('Xsign', ':p') + + " Error case + call assert_fails("call sign_place(5, [], 'sign1', 'Xsign', + \ {'lnum' : 30})", 'E730:') + + " place three signs with the same identifier. One in the global group and + " others in the named groups + call assert_equal(5, sign_place(5, '', 'sign1', 'Xsign', + \ {'lnum' : 10})) + call assert_equal(5, sign_place(5, 'g1', 'sign1', bnum, {'lnum' : 20})) + call assert_equal(5, sign_place(5, 'g2', 'sign1', bnum, {'lnum' : 30})) + + " Test for sign_getplaced() with group + let s = sign_getplaced('Xsign') + call assert_equal(1, len(s[0].signs)) + call assert_equal(s[0].signs[0].group, '') + let s = sign_getplaced(bnum, {'group' : 'g2'}) + call assert_equal('g2', s[0].signs[0].group) + let s = sign_getplaced(bnum, {'group' : 'g3'}) + call assert_equal([], s[0].signs) + let s = sign_getplaced(bnum, {'group' : '*'}) + call assert_equal([{'id' : 5, 'group' : '', 'name' : 'sign1', 'lnum' : 10, + \ 'priority' : 10}, + \ {'id' : 5, 'group' : 'g1', 'name' : 'sign1', 'lnum' : 20, + \ 'priority' : 10}, + \ {'id' : 5, 'group' : 'g2', 'name' : 'sign1', 'lnum' : 30, + \ 'priority' : 10}], + \ s[0].signs) + + " Test for sign_getplaced() with id + let s = sign_getplaced(bnum, {'id' : 5}) + call assert_equal([{'id' : 5, 'group' : '', 'name' : 'sign1', 'lnum' : 10, + \ 'priority' : 10}], + \ s[0].signs) + let s = sign_getplaced(bnum, {'id' : 5, 'group' : 'g2'}) + call assert_equal( + \ [{'id' : 5, 'name' : 'sign1', 'lnum' : 30, 'group' : 'g2', + \ 'priority' : 10}], + \ s[0].signs) + let s = sign_getplaced(bnum, {'id' : 5, 'group' : '*'}) + call assert_equal([{'id' : 5, 'group' : '', 'name' : 'sign1', 'lnum' : 10, + \ 'priority' : 10}, + \ {'id' : 5, 'group' : 'g1', 'name' : 'sign1', 'lnum' : 20, + \ 'priority' : 10}, + \ {'id' : 5, 'group' : 'g2', 'name' : 'sign1', 'lnum' : 30, + \ 'priority' : 10}], + \ s[0].signs) + let s = sign_getplaced(bnum, {'id' : 5, 'group' : 'g3'}) + call assert_equal([], s[0].signs) + + " Test for sign_getplaced() with lnum + let s = sign_getplaced(bnum, {'lnum' : 20}) + call assert_equal([], s[0].signs) + let s = sign_getplaced(bnum, {'lnum' : 20, 'group' : 'g1'}) + call assert_equal( + \ [{'id' : 5, 'name' : 'sign1', 'lnum' : 20, 'group' : 'g1', + \ 'priority' : 10}], + \ s[0].signs) + let s = sign_getplaced(bnum, {'lnum' : 30, 'group' : '*'}) + call assert_equal( + \ [{'id' : 5, 'name' : 'sign1', 'lnum' : 30, 'group' : 'g2', + \ 'priority' : 10}], + \ s[0].signs) + let s = sign_getplaced(bnum, {'lnum' : 40, 'group' : '*'}) + call assert_equal([], s[0].signs) + + " Error case + call assert_fails("call sign_getplaced(bnum, {'group' : []})", + \ 'E730:') + + " Clear the sign in global group + call sign_unplace('', {'id' : 5, 'buffer' : bnum}) + let s = sign_getplaced(bnum, {'group' : '*'}) + call assert_equal([ + \ {'id' : 5, 'name' : 'sign1', 'lnum' : 20, 'group' : 'g1', + \ 'priority' : 10}, + \ {'id' : 5, 'name' : 'sign1', 'lnum' : 30, 'group' : 'g2', + \ 'priority' : 10}], + \ s[0].signs) + + " Clear the sign in one of the groups + call sign_unplace('g1', {'buffer' : 'Xsign'}) + let s = sign_getplaced(bnum, {'group' : '*'}) + call assert_equal([ + \ {'id' : 5, 'name' : 'sign1', 'lnum' : 30, 'group' : 'g2', + \ 'priority' : 10}], + \ s[0].signs) + + " Clear all the signs from the buffer + call sign_unplace('*', {'buffer' : bnum}) + call assert_equal([], sign_getplaced(bnum, {'group' : '*'})[0].signs) + + " Clear sign across groups using an identifier + call sign_place(25, '', 'sign1', bnum, {'lnum' : 10}) + call sign_place(25, 'g1', 'sign1', bnum, {'lnum' : 11}) + call sign_place(25, 'g2', 'sign1', bnum, {'lnum' : 12}) + call assert_equal(0, sign_unplace('*', {'id' : 25})) + call assert_equal([], sign_getplaced(bnum, {'group' : '*'})[0].signs) + + " Error case + call assert_fails("call sign_unplace([])", 'E474:') + + " Place a sign in the global group and try to delete it using a group + call assert_equal(5, sign_place(5, '', 'sign1', bnum, {'lnum' : 10})) + call assert_equal(-1, sign_unplace('g1', {'id' : 5})) + + " Place signs in multiple groups and delete all the signs in one of the + " group + call assert_equal(5, sign_place(5, '', 'sign1', bnum, {'lnum' : 10})) + call assert_equal(6, sign_place(6, '', 'sign1', bnum, {'lnum' : 11})) + call assert_equal(5, sign_place(5, 'g1', 'sign1', bnum, {'lnum' : 10})) + call assert_equal(5, sign_place(5, 'g2', 'sign1', bnum, {'lnum' : 10})) + call assert_equal(6, sign_place(6, 'g1', 'sign1', bnum, {'lnum' : 11})) + call assert_equal(6, sign_place(6, 'g2', 'sign1', bnum, {'lnum' : 11})) + call assert_equal(0, sign_unplace('g1')) + let s = sign_getplaced(bnum, {'group' : 'g1'}) + call assert_equal([], s[0].signs) + let s = sign_getplaced(bnum) + call assert_equal(2, len(s[0].signs)) + let s = sign_getplaced(bnum, {'group' : 'g2'}) + call assert_equal('g2', s[0].signs[0].group) + call assert_equal(0, sign_unplace('', {'id' : 5})) + call assert_equal(0, sign_unplace('', {'id' : 6})) + let s = sign_getplaced(bnum, {'group' : 'g2'}) + call assert_equal('g2', s[0].signs[0].group) + call assert_equal(0, sign_unplace('', {'buffer' : bnum})) + + call sign_unplace('*') + + " Test for :sign command and groups + exe 'sign place 5 line=10 name=sign1 file=' . fname + exe 'sign place 5 group=g1 line=10 name=sign1 file=' . fname + exe 'sign place 5 group=g2 line=10 name=sign1 file=' . fname + + " Test for :sign place group={group} file={fname} + let a = execute('sign place file=' . fname) + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n line=10 id=5 name=sign1 priority=10\n", a) + + let a = execute('sign place group=g2 file=' . fname) + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n line=10 id=5 group=g2 name=sign1 priority=10\n", a) + + let a = execute('sign place group=* file=' . fname) + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" . + \ " line=10 id=5 group=g2 name=sign1 priority=10\n" . + \ " line=10 id=5 group=g1 name=sign1 priority=10\n" . + \ " line=10 id=5 name=sign1 priority=10\n", a) + + let a = execute('sign place group=xyz file=' . fname) + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n", a) + + call sign_unplace('*') + + " Test for :sign place group={group} buffer={nr} + let bnum = bufnr('Xsign') + exe 'sign place 5 line=10 name=sign1 buffer=' . bnum + exe 'sign place 5 group=g1 line=11 name=sign1 buffer=' . bnum + exe 'sign place 5 group=g2 line=12 name=sign1 buffer=' . bnum + + let a = execute('sign place buffer=' . bnum) + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n line=10 id=5 name=sign1 priority=10\n", a) + + let a = execute('sign place group=g2 buffer=' . bnum) + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n line=12 id=5 group=g2 name=sign1 priority=10\n", a) + + let a = execute('sign place group=* buffer=' . bnum) + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" . + \ " line=10 id=5 name=sign1 priority=10\n" . + \ " line=11 id=5 group=g1 name=sign1 priority=10\n" . + \ " line=12 id=5 group=g2 name=sign1 priority=10\n", a) + + let a = execute('sign place group=xyz buffer=' . bnum) + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n", a) + + let a = execute('sign place group=*') + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" . + \ " line=10 id=5 name=sign1 priority=10\n" . + \ " line=11 id=5 group=g1 name=sign1 priority=10\n" . + \ " line=12 id=5 group=g2 name=sign1 priority=10\n", a) + + " Test for :sign unplace + exe 'sign unplace 5 group=g2 file=' . fname + call assert_equal([], sign_getplaced(bnum, {'group' : 'g2'})[0].signs) + + exe 'sign unplace 5 group=g1 buffer=' . bnum + call assert_equal([], sign_getplaced(bnum, {'group' : 'g1'})[0].signs) + + exe 'sign unplace 5 group=xy file=' . fname + call assert_equal(1, len(sign_getplaced(bnum, {'group' : '*'})[0].signs)) + + " Test for removing all the signs. Place the signs again for this test + exe 'sign place 5 group=g1 line=11 name=sign1 file=' . fname + exe 'sign place 5 group=g2 line=12 name=sign1 file=' . fname + exe 'sign place 6 line=20 name=sign1 file=' . fname + exe 'sign place 6 group=g1 line=21 name=sign1 file=' . fname + exe 'sign place 6 group=g2 line=22 name=sign1 file=' . fname + exe 'sign unplace 5 group=* file=' . fname + let a = execute('sign place group=*') + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" . + \ " line=20 id=6 name=sign1 priority=10\n" . + \ " line=21 id=6 group=g1 name=sign1 priority=10\n" . + \ " line=22 id=6 group=g2 name=sign1 priority=10\n", a) + + " Remove all the signs from the global group + exe 'sign unplace * file=' . fname + let a = execute('sign place group=*') + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" . + \ " line=21 id=6 group=g1 name=sign1 priority=10\n" . + \ " line=22 id=6 group=g2 name=sign1 priority=10\n", a) + + " Remove all the signs from a particular group + exe 'sign place 5 line=10 name=sign1 file=' . fname + exe 'sign place 5 group=g1 line=11 name=sign1 file=' . fname + exe 'sign place 5 group=g2 line=12 name=sign1 file=' . fname + exe 'sign unplace * group=g1 file=' . fname + let a = execute('sign place group=*') + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" . + \ " line=10 id=5 name=sign1 priority=10\n" . + \ " line=12 id=5 group=g2 name=sign1 priority=10\n" . + \ " line=22 id=6 group=g2 name=sign1 priority=10\n", a) + + " Remove all the signs from all the groups in a file + exe 'sign place 5 group=g1 line=11 name=sign1 file=' . fname + exe 'sign place 6 line=20 name=sign1 file=' . fname + exe 'sign place 6 group=g1 line=21 name=sign1 file=' . fname + exe 'sign unplace * group=* file=' . fname + let a = execute('sign place group=*') + call assert_equal("\n--- Signs ---\n", a) + + " Remove a particular sign id in a group from all the files + exe 'sign place 5 group=g1 line=11 name=sign1 file=' . fname + sign unplace 5 group=g1 + let a = execute('sign place group=*') + call assert_equal("\n--- Signs ---\n", a) + + " Remove a particular sign id in all the groups from all the files + exe 'sign place 5 line=10 name=sign1 file=' . fname + exe 'sign place 5 group=g1 line=11 name=sign1 file=' . fname + exe 'sign place 5 group=g2 line=12 name=sign1 file=' . fname + exe 'sign place 6 line=20 name=sign1 file=' . fname + exe 'sign place 6 group=g1 line=21 name=sign1 file=' . fname + exe 'sign place 6 group=g2 line=22 name=sign1 file=' . fname + sign unplace 5 group=* + let a = execute('sign place group=*') + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" . + \ " line=20 id=6 name=sign1 priority=10\n" . + \ " line=21 id=6 group=g1 name=sign1 priority=10\n" . + \ " line=22 id=6 group=g2 name=sign1 priority=10\n", a) + + " Remove all the signs from all the groups in all the files + exe 'sign place 5 line=10 name=sign1 file=' . fname + exe 'sign place 5 group=g1 line=11 name=sign1 file=' . fname + sign unplace * group=* + let a = execute('sign place group=*') + call assert_equal("\n--- Signs ---\n", a) + + " Error cases + call assert_fails("exe 'sign place 3 group= name=sign1 buffer=' . bnum", 'E474:') + + call delete("Xsign") + call sign_unplace('*') + call sign_undefine() + enew | only +endfunc + +" Tests for auto-generating the sign identifier +func Test_sign_id_autogen() + enew | only + call sign_unplace('*') + call sign_undefine() + + let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'} + call assert_equal(0, sign_define("sign1", attr)) + + call writefile(repeat(["Sun is shining"], 30), "Xsign") + edit Xsign + + call assert_equal(1, sign_place(0, '', 'sign1', 'Xsign', + \ {'lnum' : 10})) + call assert_equal(2, sign_place(2, '', 'sign1', 'Xsign', + \ {'lnum' : 12})) + call assert_equal(3, sign_place(0, '', 'sign1', 'Xsign', + \ {'lnum' : 14})) + call sign_unplace('', {'buffer' : 'Xsign', 'id' : 2}) + call assert_equal(2, sign_place(0, '', 'sign1', 'Xsign', + \ {'lnum' : 12})) + + call assert_equal(1, sign_place(0, 'g1', 'sign1', 'Xsign', + \ {'lnum' : 11})) + call assert_equal(0, sign_unplace('g1', {'id' : 1})) + call assert_equal(10, + \ sign_getplaced('Xsign', {'id' : 1})[0].signs[0].lnum) + + call delete("Xsign") + call sign_unplace('*') + call sign_undefine() + enew | only +endfunc + +" Test for sign priority +func Test_sign_priority() + enew | only + call sign_unplace('*') + call sign_undefine() + + let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Search'} + call sign_define("sign1", attr) + call sign_define("sign2", attr) + call sign_define("sign3", attr) + + " Place three signs with different priority in the same line + call writefile(repeat(["Sun is shining"], 30), "Xsign") + edit Xsign + let fname = fnamemodify('Xsign', ':p') + + call sign_place(1, 'g1', 'sign1', 'Xsign', + \ {'lnum' : 11, 'priority' : 50}) + call sign_place(2, 'g2', 'sign2', 'Xsign', + \ {'lnum' : 11, 'priority' : 100}) + call sign_place(3, '', 'sign3', 'Xsign', {'lnum' : 11}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 11, 'group' : 'g2', + \ 'priority' : 100}, + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11, 'group' : 'g1', + \ 'priority' : 50}, + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 11, 'group' : '', + \ 'priority' : 10}], + \ s[0].signs) + + " Error case + call assert_fails("call sign_place(1, 'g1', 'sign1', 'Xsign', + \ [])", 'E715:') + call sign_unplace('*') + + " Tests for the :sign place command with priority + sign place 5 line=10 name=sign1 priority=30 file=Xsign + sign place 5 group=g1 line=10 name=sign1 priority=20 file=Xsign + sign place 5 group=g2 line=10 name=sign1 priority=25 file=Xsign + let a = execute('sign place group=*') + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" . + \ " line=10 id=5 name=sign1 priority=30\n" . + \ " line=10 id=5 group=g2 name=sign1 priority=25\n" . + \ " line=10 id=5 group=g1 name=sign1 priority=20\n", a) + + " Test for :sign place group={group} + let a = execute('sign place group=g1') + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" . + \ " line=10 id=5 group=g1 name=sign1 priority=20\n", a) + + call sign_unplace('*') + call sign_undefine() + enew | only + call delete("Xsign") +endfunc + +" Tests for memory allocation failures in sign functions +func Test_sign_memfailures() + call writefile(repeat(["Sun is shining"], 30), "Xsign") + edit Xsign + + call test_alloc_fail(GetAllocId('sign_getdefined'), 0, 0) + call assert_fails('call sign_getdefined("sign1")', 'E342:') + call test_alloc_fail(GetAllocId('sign_getplaced'), 0, 0) + call assert_fails('call sign_getplaced("Xsign")', 'E342:') + call test_alloc_fail(GetAllocId('sign_define_by_name'), 0, 0) + let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'} + call assert_fails('call sign_define("sign1", attr)', 'E342:') + + let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'} + call sign_define("sign1", attr) + call test_alloc_fail(GetAllocId('sign_getlist'), 0, 0) + call assert_fails('call sign_getdefined("sign1")', 'E342:') + + call sign_place(3, 'g1', 'sign1', 'Xsign', {'lnum' : 10}) + call test_alloc_fail(GetAllocId('sign_getplaced_dict'), 0, 0) + call assert_fails('call sign_getplaced("Xsign")', 'E342:') + call test_alloc_fail(GetAllocId('sign_getplaced_list'), 0, 0) + call assert_fails('call sign_getplaced("Xsign")', 'E342:') + + call test_alloc_fail(GetAllocId('insert_sign'), 0, 0) + call assert_fails('call sign_place(4, "g1", "sign1", "Xsign", {"lnum" : 11})', + \ 'E342:') + + call test_alloc_fail(GetAllocId('sign_getinfo'), 0, 0) + call assert_fails('call getbufinfo()', 'E342:') + call sign_place(4, 'g1', 'sign1', 'Xsign', {'lnum' : 11}) + call test_alloc_fail(GetAllocId('sign_getinfo'), 0, 0) + call assert_fails('let binfo=getbufinfo("Xsign")', 'E342:') + call assert_equal([{'lnum': 11, 'id': 4, 'name': 'sign1', + \ 'priority': 10, 'group': 'g1'}], binfo[0].signs) + + call sign_unplace('*') + call sign_undefine() + enew | only + call delete("Xsign") +endfunc |