diff options
-rw-r--r-- | runtime/doc/eval.txt | 276 | ||||
-rw-r--r-- | runtime/doc/sign.txt | 153 | ||||
-rw-r--r-- | runtime/doc/usr_41.txt | 10 | ||||
-rw-r--r-- | src/nvim/buffer.c | 366 | ||||
-rw-r--r-- | src/nvim/buffer_defs.h | 28 | ||||
-rw-r--r-- | src/nvim/edit.c | 11 | ||||
-rw-r--r-- | src/nvim/eval.c | 476 | ||||
-rw-r--r-- | src/nvim/eval.lua | 8 | ||||
-rw-r--r-- | src/nvim/ex_cmds.c | 694 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 1 | ||||
-rw-r--r-- | src/nvim/ex_getln.c | 1 | ||||
-rw-r--r-- | src/nvim/fileio.c | 92 | ||||
-rw-r--r-- | src/nvim/globals.h | 5 | ||||
-rw-r--r-- | src/nvim/if_cscope.c | 23 | ||||
-rw-r--r-- | src/nvim/main.c | 3 | ||||
-rw-r--r-- | src/nvim/mark.c | 1 | ||||
-rw-r--r-- | src/nvim/mbyte.c | 38 | ||||
-rw-r--r-- | src/nvim/memline.c | 57 | ||||
-rw-r--r-- | src/nvim/memory.c | 1 | ||||
-rw-r--r-- | src/nvim/message.c | 55 | ||||
-rw-r--r-- | src/nvim/normal.c | 8 | ||||
-rw-r--r-- | src/nvim/screen.c | 4 | ||||
-rw-r--r-- | src/nvim/search.c | 14 | ||||
-rw-r--r-- | src/nvim/sign.c | 1746 | ||||
-rw-r--r-- | src/nvim/sign.h | 13 | ||||
-rw-r--r-- | src/nvim/sign_defs.h | 28 | ||||
-rw-r--r-- | src/nvim/syntax.c | 1 | ||||
-rw-r--r-- | src/nvim/testdir/test_signs.vim | 1153 | ||||
-rw-r--r-- | test/functional/ui/sign_spec.lua | 2 |
29 files changed, 3988 insertions, 1280 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index dfffe33b1f..cd5632e70c 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2028,6 +2028,8 @@ cursor({lnum}, {col} [, {off}]) cursor({list}) Number move cursor to position in {list} deepcopy({expr} [, {noref}]) any make a full copy of {expr} delete({fname} [, {flags}]) Number delete the file or directory {fname} +deletebufline({expr}, {first}[, {last}]) + Number delete lines from buffer {expr} dictwatcheradd({dict}, {pattern}, {callback}) Start watching a dictionary dictwatcherdel({dict}, {pattern}, {callback}) @@ -2289,6 +2291,17 @@ shellescape({string} [, {special}]) String escape {string} for use as shell command argument shiftwidth() Number effective value of 'shiftwidth' +sign_define({name} [, {dict}]) Number define or update a sign +sign_getdefined([{name}]) List get a list of defined signs +sign_getplaced([{expr} [, {dict}]]) + List get a list of placed signs +sign_jump({id}, {group}, {expr}) + Number jump to a sign +sign_place({id}, {group}, {name}, {expr} [, {dict}]) + Number place a sign +sign_undefine([{name}]) Number undefine a sign +sign_unplace({group} [, {dict}]) + Number unplace a sign simplify({filename}) String simplify filename as much as possible sin({expr}) Float sine of {expr} sinh({expr}) Float hyperbolic sine of {expr} @@ -3198,6 +3211,17 @@ delete({fname} [, {flags}]) *delete()* The result is a Number, which is 0 if the delete operation was successful and -1 when the deletion failed or partly failed. +deletebufline({expr}, {first}[, {last}]) *deletebufline()* + Delete lines {first} to {last} (inclusive) from buffer {expr}. + If {last} is omitted then delete line {first} only. + On success 0 is returned, on failure 1 is returned. + + For the use of {expr}, see |bufname()| above. + + {first} and {last} are used like with |setline()|. Note that + when using |line()| this refers to the current buffer. Use "$" + to refer to the last line in buffer {expr}. + dictwatcheradd({dict}, {pattern}, {callback}) *dictwatcheradd()* Adds a watcher to a dictionary. A dictionary watcher is identified by three components: @@ -3572,20 +3596,24 @@ extend({expr1}, {expr2} [, {expr3}]) *extend()* feedkeys({string} [, {mode}]) *feedkeys()* Characters in {string} are queued for processing as if they come from a mapping or were typed by the user. + By default the string is added to the end of the typeahead buffer, thus if a mapping is still being executed the characters come after them. Use the 'i' flag to insert before other characters, they will be executed next, before any characters from a mapping. + The function does not wait for processing of keys contained in {string}. + To include special keys into {string}, use double-quotes and "\..." notation |expr-quote|. For example, feedkeys("\<CR>") simulates pressing of the <Enter> key. But feedkeys('\<CR>') pushes 5 characters. - If {mode} is absent, keys are remapped. + {mode} is a String, which can contain these character flags: - 'm' Remap keys. This is default. + 'm' Remap keys. This is default. If {mode} is absent, + keys are remapped. 'n' Do not remap keys. 't' Handle keys as if typed; otherwise they are handled as if coming from a mapping. This matters for undo, @@ -3599,6 +3627,9 @@ feedkeys({string} [, {mode}]) *feedkeys()* will behave as if <Esc> is typed, to avoid getting stuck, waiting for a character to be typed before the script continues. + Note that if you manage to call feedkeys() while + executing commands, thus calling it recursively, the + all typehead will be consumed by the last call. '!' When used with 'x' will not end Insert mode. Can be used in a test when a timer is set to exit Insert mode a little later. Useful for testing CursorHoldI. @@ -7349,7 +7380,246 @@ shiftwidth() *shiftwidth()* endif < And then use s:sw() instead of &sw. +sign_define({name} [, {dict}]) *sign_define()* + Define a new sign named {name} or modify the attributes of an + existing sign. This is similar to the |:sign-define| command. + + Prefix {name} with a unique text to avoid name collisions. + There is no {group} like with placing signs. + + The {name} can be a String or a Number. The optional {dict} + argument specifies the sign attributes. The following values + are supported: + icon full path to the bitmap file for the sign. + linehl highlight group used for the whole line the + sign is placed in. + text text that is displayed when there is no icon + or the GUI is not being used. + texthl highlight group used for the text item + numhl highlight group used for 'number' column at the + associated line. Overrides |hl-LineNr|, + |hl-CursorLineNr|. + + If the sign named {name} already exists, then the attributes + of the sign are updated. + + Returns 0 on success and -1 on failure. + + Examples: > + call sign_define("mySign", {"text" : "=>", "texthl" : + \ "Error", "linehl" : "Search"}) +< +sign_getdefined([{name}]) *sign_getdefined()* + Get a list of defined signs and their attributes. + This is similar to the |:sign-list| command. + + If the {name} is not supplied, then a list of all the defined + signs is returned. Otherwise the attribute of the specified + sign is returned. + + Each list item in the returned value is a dictionary with the + following entries: + icon full path to the bitmap file of the sign + linehl highlight group used for the whole line the + sign is placed in. + name name of the sign + text text that is displayed when there is no icon + or the GUI is not being used. + texthl highlight group used for the text item + numhl highlight group used for 'number' column at the + associated line. Overrides |hl-LineNr|, + |hl-CursorLineNr|. + + Returns an empty List if there are no signs and when {name} is + not found. + + Examples: > + " Get a list of all the defined signs + echo sign_getdefined() + + " Get the attribute of the sign named mySign + echo sign_getdefined("mySign") +< +sign_getplaced([{expr} [, {dict}]]) *sign_getplaced()* + Return a list of signs placed in a buffer or all the buffers. + This is similar to the |:sign-place-list| command. + + If the optional buffer name {expr} is specified, then only the + list of signs placed in that buffer is returned. For the use + of {expr}, see |bufname()|. The optional {dict} can contain + the following entries: + group select only signs in this group + id select sign with this identifier + lnum select signs placed in this line. For the use + of {lnum}, see |line()|. + If {group} is '*', then signs in all the groups including the + global group are returned. If {group} is not supplied or is an + empty string, then only signs in the global group are + returned. If no arguments are supplied, then signs in the + global group placed in all the buffers are returned. + See |sign-group|. + + Each list item in the returned value is a dictionary with the + following entries: + bufnr number of the buffer with the sign + signs list of signs placed in {bufnr}. Each list + item is a dictionary with the below listed + entries + + The dictionary for each sign contains the following entries: + group sign group. Set to '' for the global group. + id identifier of the sign + lnum line number where the sign is placed + name name of the defined sign + priority sign priority + + The returned signs in a buffer are ordered by their line + number. + + Returns an empty list on failure or if there are no placed + signs. + + Examples: > + " Get a List of signs placed in eval.c in the + " global group + echo sign_getplaced("eval.c") + + " Get a List of signs in group 'g1' placed in eval.c + echo sign_getplaced("eval.c", {'group' : 'g1'}) + + " Get a List of signs placed at line 10 in eval.c + echo sign_getplaced("eval.c", {'lnum' : 10}) + + " Get sign with identifier 10 placed in a.py + echo sign_getplaced("a.py", {'id' : 10'}) + + " Get sign with id 20 in group 'g1' placed in a.py + echo sign_getplaced("a.py", {'group' : 'g1', + \ 'id' : 20'}) + + " Get a List of all the placed signs + echo sign_getplaced() +< + *sign_jump()* +sign_jump({id}, {group}, {expr}) + Open the buffer {expr} or jump to the window that contains + {expr} and position the cursor at sign {id} in group {group}. + This is similar to the |:sign-jump| command. + + For the use of {expr}, see |bufname()|. + + Returns the line number of the sign. Returns -1 if the + arguments are invalid. + + Example: > + " Jump to sign 10 in the current buffer + call sign_jump(10, '', '') +< + *sign_place()* +sign_place({id}, {group}, {name}, {expr} [, {dict}]) + Place the sign defined as {name} at line {lnum} in file {expr} + and assign {id} and {group} to sign. This is similar to the + |:sign-place| command. + + If the sign identifier {id} is zero, then a new identifier is + allocated. Otherwise the specified number is used. {group} is + the sign group name. To use the global sign group, use an + empty string. {group} functions as a namespace for {id}, thus + two groups can use the same IDs. Refer to |sign-identifier| + for more information. + + {name} refers to a defined sign. + {expr} refers to a buffer name or number. For the accepted + values, see |bufname()|. + + The optional {dict} argument supports the following entries: + lnum line number in the buffer {expr} where + the sign is to be placed. For the + accepted values, see |line()|. + priority priority of the sign. See + |sign-priority| for more information. + + If the optional {dict} is not specified, then it modifies the + placed sign {id} in group {group} to use the defined sign + {name}. + + Returns the sign identifier on success and -1 on failure. + + Examples: > + " Place a sign named sign1 with id 5 at line 20 in + " buffer json.c + call sign_place(5, '', 'sign1', 'json.c', + \ {'lnum' : 20}) + + " Updates sign 5 in buffer json.c to use sign2 + call sign_place(5, '', 'sign2', 'json.c') + + " Place a sign named sign3 at line 30 in + " buffer json.c with a new identifier + let id = sign_place(0, '', 'sign3', 'json.c', + \ {'lnum' : 30}) + + " Place a sign named sign4 with id 10 in group 'g3' + " at line 40 in buffer json.c with priority 90 + call sign_place(10, 'g3', 'sign4', 'json.c', + \ {'lnum' : 40, 'priority' : 90}) +< +sign_undefine([{name}]) *sign_undefine()* + Deletes a previously defined sign {name}. This is similar to + the |:sign-undefine| command. If {name} is not supplied, then + deletes all the defined signs. + + Returns 0 on success and -1 on failure. + + Examples: > + " Delete a sign named mySign + call sign_undefine("mySign") + + " Delete all the signs + call sign_undefine() +< +sign_unplace({group} [, {dict}]) *sign_unplace()* + Remove a previously placed sign in one or more buffers. This + is similar to the |:sign-unplace()| command. + + {group} is the sign group name. To use the global sign group, + use an empty string. If {group} is set to '*', then all the + groups including the global group are used. + The signs in {group} are selected based on the entries in + {dict}. The following optional entries in {dict} are + supported: + buffer buffer name or number. See |bufname()|. + id sign identifier + If {dict} is not supplied, then all the signs in {group} are + removed. + + Returns 0 on success and -1 on failure. + + Examples: > + " Remove sign 10 from buffer a.vim + call sign_unplace('', {'buffer' : "a.vim", 'id' : 10}) + " Remove sign 20 in group 'g1' from buffer 3 + call sign_unplace('g1', {'buffer' : 3, 'id' : 20}) + + " Remove all the signs in group 'g2' from buffer 10 + call sign_unplace('g2', {'buffer' : 10}) + + " Remove sign 30 in group 'g3' from all the buffers + call sign_unplace('g3', {'id' : 30}) + + " Remove all the signs placed in buffer 5 + call sign_unplace('*', {'buffer' : 5}) + + " Remove the signs in group 'g4' from all the buffers + call sign_unplace('g4') + + " Remove sign 40 from all the buffers + call sign_unplace('*', {'id' : 40}) + + " Remove all the placed signs from all the buffers + call sign_unplace('*') +< simplify({filename}) *simplify()* Simplify the file name as much as possible without changing the meaning. Shortcuts (on MS-Windows) or symbolic links (on @@ -8352,7 +8622,7 @@ undofile({name}) *undofile()* If {name} is empty undofile() returns an empty string, since a buffer without a file name will not write an undo file. Useful in combination with |:wundo| and |:rundo|. - When compiled without the +persistent_undo option this always + When compiled without the |+persistent_undo| option this always returns an empty string. undotree() *undotree()* diff --git a/runtime/doc/sign.txt b/runtime/doc/sign.txt index 273d2b984c..cf7e01bcea 100644 --- a/runtime/doc/sign.txt +++ b/runtime/doc/sign.txt @@ -45,6 +45,30 @@ The color of the column is set with the SignColumn group |hl-SignColumn|. Example to set the color: > :highlight SignColumn guibg=darkgrey +< + *sign-identifier* +Each placed sign is identified by a number called the sign identifier. This +identifier is used to jump to the sign or to remove the sign. The identifier +is assigned when placing the sign using the |sign-place| command or the +|sign_place()| function. Each sign identifier should be a unique number. If +multiple placed signs use the same identifier, then jumping to or removing a +sign becomes unpredictable. To avoid overlapping identifiers, sign groups can +be used. The |sign_place()| function can be called with a zero sign identifier +to allocate the next available identifier. + + *sign-group* +Each placed sign can be assigned to either the global group or a named group. +When placing a sign, if a group name is not supplied, or an empty string is +used, then the sign is placed in the global group. Otherwise the sign is +placed in the named group. The sign identifier is unique within a group. The +sign group allows Vim plugins to use unique signs without interfering with +other plugins using signs. + + *sign-priority* +Each placed sign is assigned a priority value. When multiple signs are placed +on the same line, the attributes of the sign with the highest priority is used +independent of the sign group. The default priority for a sign is 10. The +priority is assigned at the time of placing a sign. ============================================================================== 2. Commands *sign-commands* *:sig* *:sign* @@ -63,6 +87,8 @@ comment. If you do need that, use the |:execute| command. DEFINING A SIGN. *:sign-define* *E255* *E160* *E612* +See |sign_define()| for the equivalent Vim script function. + :sign define {name} {argument}... Define a new sign or set attributes for an existing sign. The {name} can either be a number (all digits) or a name @@ -99,13 +125,18 @@ DEFINING A SIGN. *:sign-define* *E255* *E160* *E612* DELETING A SIGN *:sign-undefine* *E155* +See |sign_undefine()| for the equivalent Vim script function. + :sign undefine {name} Deletes a previously defined sign. If signs with this {name} are still placed this will cause trouble. + LISTING SIGNS *:sign-list* *E156* +See |sign_getdefined()| for the equivalent Vim script function. + :sign list Lists all defined signs and their attributes. :sign list {name} @@ -114,6 +145,8 @@ LISTING SIGNS *:sign-list* *E156* PLACING SIGNS *:sign-place* *E158* +See |sign_place()| for the equivalent Vim script function. + :sign place {id} line={lnum} name={name} file={fname} Place sign defined as {name} at line {lnum} in file {fname}. *:sign-fname* @@ -129,8 +162,28 @@ PLACING SIGNS *:sign-place* *E158* to be done several times and making changes may not work as expected). -:sign place {id} line={lnum} name={name} buffer={nr} - Same, but use buffer {nr}. + The following optional sign attributes can be specified before + "file=": + group={group} Place sign in sign group {group} + priority={prio} Assign priority {prio} to sign + + By default, the sign is placed in the global sign group. + + By default, the sign is assigned a default priority of 10. To + assign a different priority value, use "priority={prio}" to + specify a value. The priority is used to determine the + highlight group used when multiple signs are placed on the + same line. + + Examples: > + :sign place 5 line=3 name=sign1 file=a.py + :sign place 6 group=g2 line=2 name=sign2 file=x.py + :sign place 9 group=g2 priority=50 line=5 + \ name=sign1 file=a.py +< +:sign place {id} line={lnum} name={name} [buffer={nr}] + Same, but use buffer {nr}. If the buffer argument is not + given, place the sign in the current buffer. *E885* :sign place {id} name={name} file={fname} @@ -139,50 +192,125 @@ PLACING SIGNS *:sign-place* *E158* This can be used to change the displayed sign without moving it (e.g., when the debugger has stopped at a breakpoint). -:sign place {id} name={name} buffer={nr} - Same, but use buffer {nr}. + The optional "group={group}" attribute can be used before + "file=" to select a sign in a particular group. + +:sign place {id} name={name} [buffer={nr}] + Same, but use buffer {nr}. If the buffer argument is not + given, use the current buffer. REMOVING SIGNS *:sign-unplace* *E159* +See |sign_unplace()| for the equivalent Vim script function. + :sign unplace {id} file={fname} Remove the previously placed sign {id} from file {fname}. See remark above about {fname} |:sign-fname|. +:sign unplace {id} group={group} file={fname} + Same but remove the sign {id} in sign group {group}. + +:sign unplace {id} group=* file={fname} + Same but remove the sign {id} from all the sign groups. + :sign unplace * file={fname} Remove all placed signs in file {fname}. +:sign unplace * group={group} file={fname} + Remove all placed signs in group {group} from file {fname}. + +:sign unplace * group=* file={fname} + Remove all placed signs in all the groups from file {fname}. + :sign unplace {id} buffer={nr} Remove the previously placed sign {id} from buffer {nr}. +:sign unplace {id} group={group} buffer={nr} + Remove the previously placed sign {id} in group {group} from + buffer {nr}. + +:sign unplace {id} group=* buffer={nr} + Remove the previously placed sign {id} in all the groups from + buffer {nr}. + :sign unplace * buffer={nr} Remove all placed signs in buffer {nr}. +:sign unplace * group={group} buffer={nr} + Remove all placed signs in group {group} from buffer {nr}. + +:sign unplace * group=* buffer={nr} + Remove all placed signs in all the groups from buffer {nr}. + :sign unplace {id} Remove the previously placed sign {id} from all files it appears in. +:sign unplace {id} group={group} + Remove the previously placed sign {id} in group {group} from + all files it appears in. + +:sign unplace {id} group=* + Remove the previously placed sign {id} in all the groups from + all the files it appears in. + :sign unplace * - Remove all placed signs. + Remove all placed signs in the global group from all the files. + +:sign unplace * group={group} + Remove all placed signs in group {group} from all the files. + +:sign unplace * group=* + Remove all placed signs in all the groups from all the files. :sign unplace - Remove the placed sign at the cursor position. + Remove a placed sign at the cursor position. If multiple signs + are placed in the line, then only one is removed. + +:sign unplace group={group} + Remove a placed sign in group {group} at the cursor + position. + +:sign unplace group=* + Remove a placed sign in any group at the cursor position. LISTING PLACED SIGNS *:sign-place-list* +See |sign_getplaced()| for the equivalent Vim script function. + :sign place file={fname} List signs placed in file {fname}. See remark above about {fname} |:sign-fname|. +:sign place group={group} file={fname} + List signs in group {group} placed in file {fname}. + +:sign place group=* file={fname} + List signs in all the groups placed in file {fname}. + + :sign place buffer={nr} List signs placed in buffer {nr}. -:sign place List placed signs in all files. +:sign place group={group} buffer={nr} + List signs in group {group} placed in buffer {nr}. + +:sign place group=* buffer={nr} + List signs in all the groups placed in buffer {nr}. + +:sign place group={group} + List placed signs in all sign groups in all the files. + +:sign place group=* + List placed signs in all sign groups in all files. JUMPING TO A SIGN *:sign-jump* *E157* +See |sign_jump()| for the equivalent Vim script function. + :sign jump {id} file={fname} Open the file {fname} or jump to the window that contains {fname} and position the cursor at sign {id}. @@ -190,9 +318,16 @@ JUMPING TO A SIGN *:sign-jump* *E157* If the file isn't displayed in window and the current file can not be |abandon|ed this fails. -:sign jump {id} buffer={nr} *E934* +:sign jump {id} group={group} file={fname} + Same but jump to the sign in group {group} + +:sign jump {id} [buffer={nr}] *E934* Same, but use buffer {nr}. This fails if buffer {nr} does not - have a name. + have a name. If the buffer argument is not given, use the + current buffer. + +:sign jump {id} group={group} [buffer={nr}] + Same but jump to the sign in group {group} vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 234aba1932..b8fd6cdf7b 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -916,6 +916,16 @@ Mappings: *mapping-functions* maparg() get rhs of a mapping wildmenumode() check if the wildmode is active +Signs: *sign-functions* + sign_define() define or update a sign + sign_getdefined() get a list of defined signs + sign_getplaced() get a list of placed signs + sign_jump() jump to a sign + sign_place() place a sign + sign_undefine() undefine a sign + sign_unplace() unplace a sign + + Testing: *test-functions* assert_equal() assert that two expressions values are equal assert_notequal() assert that two expressions values are not equal diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index a5ad1f1a11..078d4fe782 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -64,6 +64,7 @@ #include "nvim/quickfix.h" #include "nvim/regexp.h" #include "nvim/screen.h" +#include "nvim/sign.h" #include "nvim/spell.h" #include "nvim/strings.h" #include "nvim/syntax.h" @@ -804,9 +805,9 @@ free_buffer_stuff( vars_clear(&buf->b_vars->dv_hashtab); // free all internal variables 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 - bufhl_clear_all(buf); // delete any highligts + uc_clear(&buf->b_ucmds); // clear local user commands + 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 XFREE_CLEAR(buf->b_start_fenc); @@ -5255,51 +5256,12 @@ bool find_win_for_buf(buf_T *buf, win_T **wp, tabpage_T **tp) return false; } -/* - * Insert the sign into the signlist. - */ -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 - linenr_T lnum, // line number which gets the mark - int typenr // typenr of sign we are adding -) -{ - signlist_T *newsign = xmalloc(sizeof(signlist_T)); - newsign->id = id; - newsign->lnum = lnum; - newsign->typenr = typenr; - newsign->next = next; - newsign->prev = prev; - if (next != NULL) { - next->prev = newsign; - } - buf->b_signcols_max = -1; - - if (prev == NULL) { - /* 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(); - } - - // first sign in signlist - buf->b_signlist = newsign; - } - else { - prev->next = newsign; - } -} - static int sign_compare(const void *a1, const void *a2) { const signlist_T *s1 = *(const signlist_T **)a1; const signlist_T *s2 = *(const signlist_T **)a2; - // Sort by line number and the by id + // Sort by line number, priority and id if (s1->lnum > s2->lnum) { return 1; @@ -5307,12 +5269,18 @@ static int sign_compare(const void *a1, const void *a2) if (s1->lnum < s2->lnum) { return -1; } - if (s1->id > s2->id) { + if (s1->priority > s2->priority) { + return -1; + } + if (s1->priority < s2->priority) { return 1; } - if (s1->id < s2->id) { + if (s1->id > s2->id) { return -1; } + if (s1->id < s2->id) { + return 1; + } return 0; } @@ -5384,314 +5352,6 @@ int buf_signcols(buf_T *buf) return buf->b_signcols; } -/* - * 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 - linenr_T lnum, // line number which gets the mark - int typenr // typenr of sign we are adding -) -{ - signlist_T **lastp; // pointer to pointer to current sign - signlist_T *sign; // a sign in the signlist - 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; - } 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); - 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); - - // Having more than one sign with _the same type_ and on the _same line_ is - // unwanted, let's prevent it. - - lastp = &buf->b_signlist; - for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { - if (lnum == sign->lnum && sign->typenr == typenr && id != sign->id) { - *lastp = sign->next; - xfree(sign); - } else { - lastp = &sign->next; - } - } -} - -// For an existing, placed sign "markId" change the type to "typenr". -// Returns the line number of the sign, or zero if the sign is not found. -linenr_T buf_change_sign_type( - buf_T *buf, // buffer to store sign in - int markId, // sign ID - 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) { - sign->typenr = typenr; - return sign->lnum; - } - } - - return (linenr_T)0; -} - - -/// Gets a sign from a given line. -/// -/// @param buf Buffer in which to search -/// @param lnum Line in which to search -/// @param type Type of sign to look for -/// @param idx if there multiple signs, this index will pick the n-th -// out of the most `max_signs` sorted ascending by Id. -/// @param max_signs the number of signs, with priority for the ones -// with the highest Ids. -/// @return Identifier of the matching sign, or 0 -int buf_getsigntype(buf_T *buf, linenr_T lnum, SignType type, - int idx, int max_signs) -{ - signlist_T *sign; // a sign in a b_signlist - signlist_T *matches[9]; - int nr_matches = 0; - - for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { - if (sign->lnum == lnum - && (type == SIGN_ANY - || (type == SIGN_TEXT - && sign_get_text(sign->typenr) != NULL) - || (type == SIGN_LINEHL - && sign_get_attr(sign->typenr, SIGN_LINEHL) != 0) - || (type == SIGN_NUMHL - && sign_get_attr(sign->typenr, SIGN_NUMHL) != 0))) { - matches[nr_matches] = sign; - nr_matches++; - - if (nr_matches == ARRAY_SIZE(matches)) { - break; - } - } - } - - if (nr_matches > 0) { - if (nr_matches > max_signs) { - idx += nr_matches - max_signs; - } - - if (idx >= nr_matches) { - return 0; - } - - return matches[idx]->typenr; - } - - return 0; -} - -linenr_T buf_delsign( - buf_T *buf, // buffer sign is stored in - int id // sign id -) -{ - signlist_T **lastp; // pointer to pointer to current sign - signlist_T *sign; // a sign in a b_signlist - signlist_T *next; // the next sign in a b_signlist - linenr_T lnum; // line number whose sign was deleted - - buf->b_signcols_max = -1; - lastp = &buf->b_signlist; - lnum = 0; - for (sign = buf->b_signlist; sign != NULL; sign = next) { - next = sign->next; - if (sign->id == id) { - *lastp = next; - if (next != NULL) { - next->prev = sign->prev; - } - lnum = sign->lnum; - xfree(sign); - break; - } else { - lastp = &sign->next; - } - } - - /* When deleted the last sign needs to redraw the windows to remove the - * sign column. */ - if (buf->b_signlist == NULL) { - redraw_buf_later(buf, NOT_VALID); - changed_cline_bef_curs(); - } - - return lnum; -} - - -/* - * 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. - */ -int buf_findsign( - buf_T *buf, // buffer to store sign in - int id // sign ID -) -{ - signlist_T *sign; // a sign in the signlist - - for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { - if (sign->id == id) { - return (int)sign->lnum; - } - } - - return 0; -} - -int buf_findsign_id( - 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 (sign = buf->b_signlist; sign != NULL; sign = sign->next) { - if (sign->lnum == lnum) { - return sign->id; - } - } - - return 0; -} - - -/* - * Delete signs in buffer "buf". - */ -void buf_delete_signs(buf_T *buf) -{ - signlist_T *next; - - // When deleting the last sign need to redraw the windows to remove the - // sign column. Not when curwin is NULL (this means we're exiting). - if (buf->b_signlist != NULL && curwin != NULL){ - redraw_buf_later(buf, NOT_VALID); - changed_cline_bef_curs(); - } - - while (buf->b_signlist != NULL) { - next = buf->b_signlist->next; - xfree(buf->b_signlist); - buf->b_signlist = next; - } - buf->b_signcols_max = -1; -} - -/* - * Delete all signs in all buffers. - */ -void buf_delete_all_signs(void) -{ - FOR_ALL_BUFFERS(buf) { - if (buf->b_signlist != NULL) { - buf_delete_signs(buf); - } - } -} - -/* - * List placed signs for "rbuf". If "rbuf" is NULL do it for all buffers. - */ -void sign_list_placed(buf_T *rbuf) -{ - buf_T *buf; - signlist_T *p; - char lbuf[BUFSIZ]; - - MSG_PUTS_TITLE(_("\n--- Signs ---")); - msg_putchar('\n'); - if (rbuf == NULL) { - buf = firstbuf; - } else { - buf = rbuf; - } - while (buf != NULL && !got_int) { - if (buf->b_signlist != NULL) { - vim_snprintf(lbuf, BUFSIZ, _("Signs for %s:"), buf->b_fname); - 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'); - } - if (rbuf != NULL) { - break; - } - buf = buf->b_next; - } -} - -/* - * Adjust a placed sign for inserted/deleted lines. - */ -void sign_mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_after) -{ - signlist_T *sign; // a sign in a b_signlist - signlist_T *next; // the next sign in a b_signlist - signlist_T **lastp; // pointer to pointer to current sign - - curbuf->b_signcols_max = -1; - lastp = &curbuf->b_signlist; - - for (sign = curbuf->b_signlist; sign != NULL; sign = next) { - next = sign->next; - if (sign->lnum >= line1 && sign->lnum <= line2) { - if (amount == MAXLNUM) { - *lastp = next; - xfree(sign); - continue; - } else { - sign->lnum += amount; - } - } else if (sign->lnum > line2) { - sign->lnum += amount_after; - } - lastp = &sign->next; - } -} - // bufhl: plugin highlights associated with a buffer /// Get reference to line in kbtree_t diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 5e28a7b513..255aeb82b6 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -769,15 +769,15 @@ struct file_buffer { * spell buffer - used for spell info, never displayed and doesn't have a * file name. */ - bool b_help; /* TRUE for help file buffer (when set b_p_bt - is "help") */ - bool b_spell; /* True for a spell file buffer, most fields - are not used! Use the B_SPELL macro to - access b_spell without #ifdef. */ + bool b_help; // TRUE for help file buffer (when set b_p_bt + // is "help") + bool b_spell; // True for a spell file buffer, most fields + // are not used! Use the B_SPELL macro to + // access b_spell without #ifdef. - synblock_T b_s; /* Info related to syntax highlighting. w_s - * normally points to this, but some windows - * may use a different synblock_T. */ + synblock_T b_s; // Info related to syntax highlighting. w_s + // normally points to this, but some windows + // may use a different synblock_T. signlist_T *b_signlist; // list of signs to draw int b_signcols_max; // cached maximum number of sign columns @@ -1144,12 +1144,12 @@ struct window_S { int w_cline_row; /* starting row of the cursor line */ - colnr_T w_virtcol; /* column number of the cursor in the - buffer line, as opposed to the column - number we're at on the screen. This - makes a difference on lines which span - more than one screen line or when - w_leftcol is non-zero */ + colnr_T w_virtcol; // column number of the cursor in the + // buffer line, as opposed to the column + // number we're at on the screen. This + // makes a difference on lines which span + // more than one screen line or when + // w_leftcol is non-zero /* * w_wrow and w_wcol specify the cursor position in the window. diff --git a/src/nvim/edit.c b/src/nvim/edit.c index a8dd115074..bfd63aab7a 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -179,9 +179,9 @@ static int compl_no_insert = FALSE; /* FALSE: select & insert static int compl_no_select = FALSE; /* FALSE: select & insert TRUE: noselect */ -static int compl_used_match; /* Selected one of the matches. When - FALSE the match was edited or using - the longest common string. */ +static int compl_used_match; // Selected one of the matches. When + // FALSE the match was edited or using + // the longest common string. static int compl_was_interrupted = FALSE; /* didn't finish finding completions. */ @@ -2599,8 +2599,9 @@ void ins_compl_show_pum(void) do { if ((compl->cp_flags & ORIGINAL_TEXT) == 0 && (compl_leader == NULL - || ins_compl_equal(compl, compl_leader, lead_len))) - ++compl_match_arraysize; + || ins_compl_equal(compl, compl_leader, lead_len))) { + compl_match_arraysize++; + } compl = compl->cp_next; } while (compl != NULL && compl != compl_first_match); if (compl_match_arraysize == 0) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 9f56b42fba..1d15e04218 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -68,6 +68,7 @@ #include "nvim/screen.h" #include "nvim/search.h" #include "nvim/sha256.h" +#include "nvim/sign.h" #include "nvim/spell.h" #include "nvim/state.h" #include "nvim/strings.h" @@ -1113,6 +1114,19 @@ static void restore_vimvar(int idx, typval_T *save_tv) } } +/// If there is a window for "curbuf", make it the current window. +static void find_win_for_curbuf(void) +{ + wininfo_T *wip; + + for (wip = curbuf->b_wininfo; wip != NULL; wip = wip->wi_next) { + if (wip->wi_win != NULL) { + curwin = wip->wi_win; + break; + } + } +} + /* * Evaluate an expression to a list with suggestions. * For the "expr:" part of 'spellsuggest'. @@ -1125,7 +1139,7 @@ list_T *eval_spell_expr(char_u *badword, char_u *expr) list_T *list = NULL; char_u *p = skipwhite(expr); - /* Set "v:val" to the bad word. */ + // Set "v:val" to the bad word. prepare_vimvar(VV_VAL, &save_val); vimvars[VV_VAL].vv_type = VAR_STRING; vimvars[VV_VAL].vv_str = badword; @@ -7183,7 +7197,7 @@ static buf_T *tv_get_buf(typval_T *tv, int curtab_only) if (name[0] == '$' && name[1] == NUL) return lastbuf; - /* Ignore 'magic' and 'cpoptions' here to make scripts portable */ + // Ignore 'magic' and 'cpoptions' here to make scripts portable save_magic = p_magic; p_magic = TRUE; save_cpo = p_cpo; @@ -7195,13 +7209,29 @@ static buf_T *tv_get_buf(typval_T *tv, int curtab_only) p_magic = save_magic; p_cpo = save_cpo; - /* If not found, try expanding the name, like done for bufexists(). */ - if (buf == NULL) + // If not found, try expanding the name, like done for bufexists(). + if (buf == NULL) { buf = find_buffer(tv); + } return buf; } +/// Get the buffer from "arg" and give an error and return NULL if it is not +/// valid. +static buf_T * get_buf_arg(typval_T *arg) +{ + buf_T *buf; + + emsg_off++; + buf = tv_get_buf(arg, false); + emsg_off--; + if (buf == NULL) { + EMSG2(_("E158: Invalid buffer name: %s"), tv_get_string(arg)); + } + return buf; +} + /* * "bufname(expr)" function */ @@ -7999,6 +8029,85 @@ static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) callback_free(&callback); } +/// "deletebufline()" function +static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + buf_T *buf; + linenr_T first, last; + linenr_T lnum; + long count; + int is_curbuf; + buf_T *curbuf_save = NULL; + win_T *curwin_save = NULL; + + buf = tv_get_buf(&argvars[0], false); + if (buf == NULL) { + rettv->vval.v_number = 1; // FAIL + return; + } + is_curbuf = buf == curbuf; + + first = tv_get_lnum_buf(&argvars[1], buf); + if (argvars[2].v_type != VAR_UNKNOWN) { + last = tv_get_lnum_buf(&argvars[2], buf); + } else { + last = first; + } + + if (buf->b_ml.ml_mfp == NULL || first < 1 + || first > buf->b_ml.ml_line_count || last < first) { + rettv->vval.v_number = 1; // FAIL + return; + } + + if (!is_curbuf) { + curbuf_save = curbuf; + curwin_save = curwin; + curbuf = buf; + find_win_for_curbuf(); + } + if (last > curbuf->b_ml.ml_line_count) { + last = curbuf->b_ml.ml_line_count; + } + count = last - first + 1; + + // When coming here from Insert mode, sync undo, so that this can be + // undone separately from what was previously inserted. + if (u_sync_once == 2) { + u_sync_once = 1; // notify that u_sync() was called + u_sync(true); + } + + if (u_save(first - 1, last + 1) == FAIL) { + rettv->vval.v_number = 1; // FAIL + return; + } + + for (lnum = first; lnum <= last; lnum++) { + ml_delete(first, true); + } + + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer == buf) { + if (wp->w_cursor.lnum > last) { + wp->w_cursor.lnum -= count; + } else if (wp->w_cursor.lnum> first) { + wp->w_cursor.lnum = first; + } + if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) { + wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count; + } + } + } + check_cursor_col(); + deleted_lines_mark(first, count); + + if (!is_curbuf) { + curbuf = curbuf_save; + curwin = curwin_save; + } +} + /* * "did_filetype()" function */ @@ -9286,24 +9395,6 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/// Returns information about signs placed in a buffer as list of dicts. -static list_T *get_buffer_signs(buf_T *buf) - FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT -{ - 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); - } - return l; -} - /// Returns buffer options, variables and other attributes in a dictionary. static dict_T *get_buffer_info(buf_T *buf) { @@ -15438,6 +15529,347 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = get_sw_value(curbuf); } +/// "sign_define()" function +static void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *name; + dict_T *dict; + char *icon = NULL; + char *linehl = NULL; + char *text = NULL; + char *texthl = NULL; + char *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, "icon", -1) != NULL) { + icon = tv_dict_get_string(dict, "icon", true); + } + if (tv_dict_find(dict, "linehl", -1) != NULL) { + linehl = tv_dict_get_string(dict, "linehl", true); + } + if (tv_dict_find(dict, "text", -1) != NULL) { + text = tv_dict_get_string(dict, "text", true); + } + if (tv_dict_find(dict, "texthl", -1) != NULL) { + texthl = tv_dict_get_string(dict, "texthl", true); + } + if (tv_dict_find(dict, "numhl", -1) != NULL) { + numhl = tv_dict_get_string(dict, "numhl", true); + } + } + + if (sign_define_by_name((char_u *)name, (char_u *)icon, (char_u *)linehl, + (char_u *)text, (char_u *)texthl, (char_u *)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, FunPtr fptr) +{ + const char *name = NULL; + + tv_list_alloc_ret(rettv, 0); + + if (argvars[0].v_type != VAR_UNKNOWN) { + name = tv_get_string(&argvars[0]); + } + + sign_getlist((const char_u *)name, rettv->vval.v_list); +} + +/// "sign_getplaced()" function +static void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + buf_T *buf = NULL; + dict_T *dict; + dictitem_T *di; + linenr_T lnum = 0; + int sign_id = 0; + const char *group = NULL; + bool notanum = false; + + tv_list_alloc_ret(rettv, 0); + + if (argvars[0].v_type != VAR_UNKNOWN) { + // get signs placed in the specified buffer + buf = get_buf_arg(&argvars[0]); + if (buf == NULL) { + 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, "lnum", -1)) != NULL) { + // get signs placed at this line + lnum = (linenr_T)tv_get_number_chk(&di->di_tv, ¬anum); + if (notanum) { + return; + } + lnum = tv_get_lnum(&di->di_tv); + } + if ((di = tv_dict_find(dict, "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, "group", -1)) != NULL) { + group = tv_get_string_chk(&di->di_tv); + if (group == NULL) { + return; + } + if (*group == '\0') { // empty string means global group + group = NULL; + } + } + } + } + + sign_get_placed(buf, lnum, sign_id, (const char_u *)group, + rettv->vval.v_list); +} + +/// "sign_jump()" function +static void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int sign_id; + char *sign_group = NULL; + buf_T *buf; + bool 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 + const char * sign_group_chk = tv_get_string_chk(&argvars[1]); + if (sign_group_chk == NULL) { + return; + } + if (sign_group_chk[0] == '\0') { + sign_group = NULL; // global sign group + } else { + sign_group = xstrdup(sign_group_chk); + if (sign_group == NULL) { + return; + } + } + + // Buffer to place the sign + buf = get_buf_arg(&argvars[2]); + if (buf == NULL) { + goto cleanup; + } + + rettv->vval.v_number = sign_jump(sign_id, (char_u *)sign_group, buf); + +cleanup: + xfree(sign_group); +} + +/// "sign_place()" function +static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int sign_id; + char_u *group = NULL; + const char *sign_name; + buf_T *buf; + dict_T *dict; + dictitem_T *di; + linenr_T lnum = 0; + int prio = SIGN_DEF_PRIO; + bool 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 + const char *group_chk = tv_get_string_chk(&argvars[1]); + if (group_chk == NULL) { + return; + } + if (group_chk[0] == '\0') { + group = NULL; // global sign group + } else { + group = vim_strsave((const char_u *)group_chk); + 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 = get_buf_arg(&argvars[3]); + if (buf == NULL) { + 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, "lnum", -1)) != NULL) { + lnum = (linenr_T)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, "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, (const char_u *)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, FunPtr fptr) +{ + const char *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((const char_u *)name) == OK) { + rettv->vval.v_number = 0; + } + } +} + +/// "sign_unplace()" function +static void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + 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; + } + + const char *group_chk = tv_get_string(&argvars[0]); + if (group_chk[0] == '\0') { + group = NULL; // global sign group + } else { + group = vim_strsave((const char_u *)group_chk); + if (group == NULL) { + return; + } + } + + if (argvars[1].v_type != VAR_UNKNOWN) { + if (argvars[1].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + goto cleanup; + } + dict = argvars[1].vval.v_dict; + + if ((di = tv_dict_find(dict, "buffer", -1)) != NULL) { + buf = get_buf_arg(&di->di_tv); + if (buf == NULL) { + goto cleanup; + } + } + if (tv_dict_find(dict, "id", -1) != NULL) { + sign_id = tv_dict_get_number(dict, "id"); + } + } + + if (buf == NULL) { + // Delete the sign in all the buffers + FOR_ALL_BUFFERS(cbuf) { + if (sign_unplace(sign_id, group, cbuf, 0) == OK) { + rettv->vval.v_number = 0; + } + } + } else { + if (sign_unplace(sign_id, group, buf, 0) == OK) { + rettv->vval.v_number = 0; + } + } + +cleanup: + xfree(group); +} + /* * "simplify()" function */ diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index dea00c3edd..aff1ff2a74 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -75,6 +75,7 @@ return { cursor={args={1, 3}}, deepcopy={args={1, 2}}, delete={args={1,2}}, + deletebufline={args={2,3}}, dictwatcheradd={args=3}, dictwatcherdel={args=3}, did_filetype={}, @@ -277,6 +278,13 @@ 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_jump={args={3, 3}}, + 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..8722c03204 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -67,10 +67,6 @@ #include "nvim/os/input.h" #include "nvim/os/time.h" -/* - * Struct to hold the sign properties. - */ -typedef struct sign sign_T; /// Case matching style to use for :substitute typedef enum { @@ -5521,21 +5517,6 @@ void ex_helptags(exarg_T *eap) } } -struct sign -{ - sign_T *sn_next; // next sign in list - int sn_typenr; // type number of sign - char_u *sn_name; // name of sign - char_u *sn_icon; // name of pixmap - char_u *sn_text; // text used instead of pixmap - int sn_line_hl; // highlight ID for line - int sn_text_hl; // highlight ID for text - int sn_num_hl; // highlight ID for line number -}; - -static sign_T *first_sign = NULL; -static int next_sign_typenr = 1; - /* * ":helpclose": Close one help window */ @@ -5549,681 +5530,6 @@ void ex_helpclose(exarg_T *eap) } } -static char *cmds[] = { - "define", -#define SIGNCMD_DEFINE 0 - "undefine", -#define SIGNCMD_UNDEFINE 1 - "list", -#define SIGNCMD_LIST 2 - "place", -#define SIGNCMD_PLACE 3 - "unplace", -#define SIGNCMD_UNPLACE 4 - "jump", -#define SIGNCMD_JUMP 5 - NULL -#define SIGNCMD_LAST 6 -}; - -/* - * Find index of a ":sign" subcmd from its name. - * "*end_cmd" must be writable. - */ -static int sign_cmd_idx( - char_u *begin_cmd, /* begin of sign subcmd */ - char_u *end_cmd /* just after sign subcmd */ - ) -{ - int idx; - char save = *end_cmd; - - *end_cmd = NUL; - for (idx = 0; ; ++idx) { - if (cmds[idx] == NULL || STRCMP(begin_cmd, cmds[idx]) == 0) { - break; - } - } - *end_cmd = save; - return idx; -} - -/* - * ":sign" command - */ -void ex_sign(exarg_T *eap) -{ - char_u *arg = eap->arg; - char_u *p; - int idx; - sign_T *sp; - sign_T *sp_prev; - - // Parse the subcommand. - p = skiptowhite(arg); - idx = sign_cmd_idx(arg, p); - if (idx == SIGNCMD_LAST) { - EMSG2(_("E160: Unknown sign command: %s"), arg); - return; - } - arg = skipwhite(p); - - if (idx <= SIGNCMD_LIST) { - // Define, undefine or list signs. - if (idx == SIGNCMD_LIST && *arg == NUL) { - // ":sign list": list all defined signs - for (sp = first_sign; sp != NULL && !got_int; sp = sp->sn_next) { - sign_list_defined(sp); - } - } else if (*arg == NUL) { - EMSG(_("E156: Missing sign name")); - } else { - // 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); - if (*p != NUL) { - *p++ = NUL; - } - while (arg[0] == '0' && arg[1] != NUL) { - 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) { - // ":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 (;;) { - arg = skipwhite(p); - if (*arg == NUL) { - break; - } - 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); - } 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, " "); - } - } else if (STRNCMP(arg, "linehl=", 7) == 0) { - arg += 7; - sp->sn_line_hl = syn_check_group(arg, (int)(p - arg)); - } else if (STRNCMP(arg, "texthl=", 7) == 0) { - arg += 7; - sp->sn_text_hl = syn_check_group(arg, (int)(p - arg)); - } else if (STRNCMP(arg, "numhl=", 6) == 0) { - arg += 6; - sp->sn_num_hl = syn_check_group(arg, (int)(p - arg)); - } else { - EMSG2(_(e_invarg2), arg); - return; - } - } - } else if (sp == NULL) { - EMSG2(_("E155: Unknown sign: %s"), arg); - } else if (idx == SIGNCMD_LIST) { - // ":sign list {name}" - sign_list_defined(sp); - } else { - // ":sign undefine {name}" - sign_undefine(sp, sp_prev); - } - } - } else { - int id = -1; - linenr_T lnum = -1; - char_u *sign_name = NULL; - char_u *arg1; - - if (*arg == NUL) { - if (idx == SIGNCMD_PLACE) { - // ":sign place": list placed signs in all buffers - sign_list_placed(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); - } else { - EMSG(_("E159: Missing sign number")); - } - } else { - EMSG(_(e_argreq)); - } - return; - } - - if (idx == SIGNCMD_UNPLACE && arg[0] == '*' && arg[1] == NUL) { - // ":sign unplace *": remove all placed signs - buf_delete_all_signs(); - return; - } - - // first arg could be placed sign id - arg1 = arg; - if (ascii_isdigit(*arg)) { - id = getdigits_int(&arg); - if (!ascii_iswhite(*arg) && *arg != NUL) { - id = -1; - arg = arg1; - } else { - arg = skipwhite(arg); - 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); - } - } - return; - } - } - } - - // Check for line={lnum} name={name} and file={fname} or buffer={nr}. - // Leave "arg" pointing to {fname}. - - buf_T *buf = NULL; - for (;;) { - if (STRNCMP(arg, "line=", 5) == 0) { - arg += 5; - lnum = atoi((char *)arg); - arg = skiptowhite(arg); - } else if (STRNCMP(arg, "*", 1) == 0 && idx == SIGNCMD_UNPLACE) { - if (id != -1) { - EMSG(_(e_invarg)); - return; - } - id = -2; - arg = skiptowhite(arg + 1); - } else if (STRNCMP(arg, "name=", 5) == 0) { - arg += 5; - sign_name = arg; - arg = skiptowhite(arg); - if (*arg != NUL) { - *arg++ = NUL; - } - while (sign_name[0] == '0' && sign_name[1] != NUL) { - sign_name++; - } - } else if (STRNCMP(arg, "file=", 5) == 0) { - arg += 5; - buf = buflist_findname(arg); - break; - } else if (STRNCMP(arg, "buffer=", 7) == 0) { - arg += 7; - buf = buflist_findnr(getdigits_int(&arg)); - if (*skipwhite(arg) != NUL) { - EMSG(_(e_trailing)); - } - break; - } else { - EMSG(_(e_invarg)); - return; - } - arg = skipwhite(arg); - } - - if (buf == NULL) { - EMSG2(_("E158: Invalid buffer name: %s"), arg); - } else if (id <= 0 && !(idx == SIGNCMD_UNPLACE && id == -2)) { - if (lnum >= 0 || sign_name != NULL) { - EMSG(_(e_invarg)); - } else { - // ":sign place file={fname}": list placed signs in one file - sign_list_placed(buf); - } - } 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) { - // goto a sign ... - if (buf_jump_open_win(buf) != NULL) { - // ... in a current window - curwin->w_cursor.lnum = lnum; - check_cursor_lnum(); - beginline(BL_WHITE); - } else { - // ... not currently in a window - if (buf->b_fname == NULL) { - EMSG(_("E934: Cannot jump to a buffer that does not have a name")); - return; - } - size_t cmdlen = STRLEN(buf->b_fname) + 24; - char *cmd = xmallocz(cmdlen); - snprintf(cmd, cmdlen, "e +%" PRId64 " %s", - (int64_t)lnum, buf->b_fname); - do_cmdline_cmd(cmd); - xfree(cmd); - } - - foldOpenCursor(); - } else { - EMSGN(_("E157: Invalid sign ID: %" PRId64), id); - } - } else if (idx == SIGNCMD_UNPLACE) { - 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 (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); - } - } else { - EMSG(_(e_invarg)); - } - } -} - -/* - * List one sign. - */ -static void sign_list_defined(sign_T *sp) -{ - smsg("sign %s", sp->sn_name); - if (sp->sn_icon != NULL) { - msg_puts(" icon="); - msg_outtrans(sp->sn_icon); - msg_puts(_(" (not supported)")); - } - if (sp->sn_text != NULL) { - msg_puts(" text="); - msg_outtrans(sp->sn_text); - } - if (sp->sn_line_hl > 0) { - msg_puts(" linehl="); - const char *const p = get_highlight_name_ext(NULL, - sp->sn_line_hl - 1, false); - if (p == NULL) { - msg_puts("NONE"); - } else { - msg_puts(p); - } - } - if (sp->sn_text_hl > 0) { - msg_puts(" texthl="); - const char *const p = get_highlight_name_ext(NULL, - sp->sn_text_hl - 1, false); - if (p == NULL) { - msg_puts("NONE"); - } else { - msg_puts(p); - } - } - if (sp->sn_num_hl > 0) { - msg_puts(" numhl="); - const char *const p = get_highlight_name_ext(NULL, - sp->sn_num_hl - 1, false); - if (p == NULL) { - msg_puts("NONE"); - } else { - msg_puts(p); - } - } -} - -/* - * Undefine a sign and free its memory. - */ -static void sign_undefine(sign_T *sp, sign_T *sp_prev) -{ - xfree(sp->sn_name); - xfree(sp->sn_icon); - xfree(sp->sn_text); - if (sp_prev == NULL) - first_sign = sp->sn_next; - else - sp_prev->sn_next = sp->sn_next; - xfree(sp); -} - -/// Gets highlighting attribute for sign "typenr" corresponding to "type". -int sign_get_attr(int typenr, SignType type) -{ - sign_T *sp; - int sign_hl = 0; - - for (sp = first_sign; sp != NULL; sp = sp->sn_next) { - if (sp->sn_typenr == typenr) { - switch (type) { - case SIGN_TEXT: - sign_hl = sp->sn_text_hl; - break; - case SIGN_LINEHL: - sign_hl = sp->sn_line_hl; - break; - case SIGN_NUMHL: - sign_hl = sp->sn_num_hl; - break; - default: - abort(); - } - if (sign_hl > 0) { - return syn_id2attr(sign_hl); - } - break; - } - } - return 0; -} - -/* - * Get text mark for sign "typenr". - * Returns NULL if there isn't one. - */ -char_u * sign_get_text(int typenr) -{ - sign_T *sp; - - for (sp = first_sign; sp != NULL; sp = sp->sn_next) - if (sp->sn_typenr == typenr) - return sp->sn_text; - return NULL; -} - - -/* - * Get the name of a sign by its typenr. - */ -char_u * sign_typenr2name(int typenr) -{ - sign_T *sp; - - for (sp = first_sign; sp != NULL; sp = sp->sn_next) - if (sp->sn_typenr == typenr) - return sp->sn_name; - return (char_u *)_("[Deleted]"); -} - -#if defined(EXITFREE) -/* - * Undefine/free all signs. - */ -void free_signs(void) -{ - while (first_sign != NULL) - sign_undefine(first_sign, NULL); -} -#endif - -static enum -{ - EXP_SUBCMD, /* expand :sign sub-commands */ - EXP_DEFINE, /* expand :sign define {name} args */ - EXP_PLACE, /* expand :sign place {id} args */ - EXP_UNPLACE, /* expand :sign unplace" */ - EXP_SIGN_NAMES /* expand with name of placed signs */ -} expand_what; - -/// Function given to ExpandGeneric() to obtain the sign command -/// expansion. -char_u * get_sign_name(expand_T *xp, int idx) -{ - switch (expand_what) - { - case EXP_SUBCMD: - return (char_u *)cmds[idx]; - case EXP_DEFINE: { - char *define_arg[] = { "icon=", "linehl=", "text=", "texthl=", "numhl=", - NULL }; - return (char_u *)define_arg[idx]; - } - case EXP_PLACE: { - char *place_arg[] = { "line=", "name=", "file=", "buffer=", NULL }; - return (char_u *)place_arg[idx]; - } - case EXP_UNPLACE: { - char *unplace_arg[] = { "file=", "buffer=", NULL }; - return (char_u *)unplace_arg[idx]; - } - case EXP_SIGN_NAMES: { - // Complete with name of signs already defined - int current_idx = 0; - for (sign_T *sp = first_sign; sp != NULL; sp = sp->sn_next) { - if (current_idx++ == idx) { - return sp->sn_name; - } - } - } - return NULL; - default: - return NULL; - } -} - -/* - * Handle command line completion for :sign command. - */ -void set_context_in_sign_cmd(expand_T *xp, char_u *arg) -{ - char_u *p; - char_u *end_subcmd; - char_u *last; - int cmd_idx; - char_u *begin_subcmd_args; - - /* Default: expand subcommands. */ - xp->xp_context = EXPAND_SIGN; - expand_what = EXP_SUBCMD; - xp->xp_pattern = arg; - - end_subcmd = skiptowhite(arg); - if (*end_subcmd == NUL) - /* expand subcmd name - * :sign {subcmd}<CTRL-D>*/ - return; - - cmd_idx = sign_cmd_idx(arg, end_subcmd); - - // :sign {subcmd} {subcmd_args} - // | - // begin_subcmd_args - begin_subcmd_args = skipwhite(end_subcmd); - p = skiptowhite(begin_subcmd_args); - if (*p == NUL) - { - /* - * Expand first argument of subcmd when possible. - * For ":jump {id}" and ":unplace {id}", we could - * possibly expand the ids of all signs already placed. - */ - xp->xp_pattern = begin_subcmd_args; - switch (cmd_idx) - { - case SIGNCMD_LIST: - case SIGNCMD_UNDEFINE: - /* :sign list <CTRL-D> - * :sign undefine <CTRL-D> */ - expand_what = EXP_SIGN_NAMES; - break; - default: - xp->xp_context = EXPAND_NOTHING; - } - return; - } - - // Expand last argument of subcmd. - // - // :sign define {name} {args}... - // | - // p - - // Loop until reaching last argument. - do - { - p = skipwhite(p); - last = p; - p = skiptowhite(p); - } while (*p != NUL); - - p = vim_strchr(last, '='); - - // :sign define {name} {args}... {last}= - // | | - // last p - if (p == NULL) { - // Expand last argument name (before equal sign). - xp->xp_pattern = last; - switch (cmd_idx) - { - case SIGNCMD_DEFINE: - expand_what = EXP_DEFINE; - break; - case SIGNCMD_PLACE: - expand_what = EXP_PLACE; - break; - case SIGNCMD_JUMP: - case SIGNCMD_UNPLACE: - expand_what = EXP_UNPLACE; - break; - default: - xp->xp_context = EXPAND_NOTHING; - } - } - else - { - /* Expand last argument value (after equal sign). */ - xp->xp_pattern = p + 1; - switch (cmd_idx) - { - case SIGNCMD_DEFINE: - if (STRNCMP(last, "texthl", p - last) == 0 - || STRNCMP(last, "linehl", p - last) == 0 - || STRNCMP(last, "numhl", p - last) == 0) { - xp->xp_context = EXPAND_HIGHLIGHT; - } else if (STRNCMP(last, "icon", p - last) == 0) { - xp->xp_context = EXPAND_FILES; - } else { - xp->xp_context = EXPAND_NOTHING; - } - break; - case SIGNCMD_PLACE: - if (STRNCMP(last, "name", p - last) == 0) - expand_what = EXP_SIGN_NAMES; - else - xp->xp_context = EXPAND_NOTHING; - break; - default: - xp->xp_context = EXPAND_NOTHING; - } - } -} - /// Shows the effects of the :substitute command being typed ('inccommand'). /// If inccommand=split, shows a preview window and later restores the layout. static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index aa76355bad..527120ae9f 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -53,6 +53,7 @@ #include "nvim/regexp.h" #include "nvim/screen.h" #include "nvim/search.h" +#include "nvim/sign.h" #include "nvim/spell.h" #include "nvim/spellfile.h" #include "nvim/strings.h" diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 7b020a240e..25e92128ef 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -54,6 +54,7 @@ #include "nvim/regexp.h" #include "nvim/screen.h" #include "nvim/search.h" +#include "nvim/sign.h" #include "nvim/strings.h" #include "nvim/state.h" #include "nvim/syntax.h" diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 3c45a1ad2e..f97ae8778d 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -2833,13 +2833,12 @@ buf_write ( // XFREE_CLEAR(backup); // no backup file to delete } else if (!p_bk) { - /* - * We are not going to keep the backup file, so don't - * delete an existing one, and try to use another name instead. - * Change one character, just before the extension. - */ + // We are not going to keep the backup file, so don't + // delete an existing one, and try to use another name instead. + // Change one character, just before the extension. + // wp = backup + STRLEN(backup) - 1 - STRLEN(backup_ext); - if (wp < backup) { /* empty file name ??? */ + if (wp < backup) { // empty file name ??? wp = backup; } *wp = 'z'; @@ -2847,7 +2846,7 @@ buf_write ( && os_fileinfo((char *)backup, &file_info_new)) { --*wp; } - /* They all exist??? Must be something wrong. */ + // They all exist??? Must be something wrong. if (*wp == 'a') { XFREE_CLEAR(backup); } @@ -2969,18 +2968,17 @@ nobackup: } } if (backup != NULL) { - /* - * Delete any existing backup and move the current version - * to the backup. For safety, we don't remove the backup - * until the write has finished successfully. And if the - * 'backup' option is set, leave it around. - */ - /* - * If the renaming of the original file to the backup file - * works, quit here. - */ - if (vim_rename(fname, backup) == 0) + // Delete any existing backup and move the current version + // to the backup. For safety, we don't remove the backup + // until the write has finished successfully. And if the + // 'backup' option is set, leave it around. + + // If the renaming of the original file to the backup file + // works, quit here. + /// + if (vim_rename(fname, backup) == 0) { break; + } XFREE_CLEAR(backup); // don't do the rename below } @@ -2993,7 +2991,7 @@ nobackup: } #if defined(UNIX) - /* When using ":w!" and the file was read-only: make it writable */ + // When using ":w!" and the file was read-only: make it writable if (forceit && perm >= 0 && !(perm & 0200) && file_info_old.stat.st_uid == getuid() && vim_strchr(p_cpo, CPO_FWRITE) == NULL) { @@ -3003,12 +3001,12 @@ nobackup: } #endif - /* When using ":w!" and writing to the current file, 'readonly' makes no - * sense, reset it, unless 'Z' appears in 'cpoptions'. */ + // When using ":w!" and writing to the current file, 'readonly' makes no + // sense, reset it, unless 'Z' appears in 'cpoptions'. if (forceit && overwriting && vim_strchr(p_cpo, CPO_KEEPRO) == NULL) { - buf->b_p_ro = FALSE; - need_maketitle = TRUE; /* set window title later */ - status_redraw_all(); /* redraw status lines later */ + buf->b_p_ro = false; + need_maketitle = true; // set window title later + status_redraw_all(); // redraw status lines later } if (end > buf->b_ml.ml_line_count) @@ -3016,13 +3014,11 @@ nobackup: if (buf->b_ml.ml_flags & ML_EMPTY) start = end + 1; - /* - * If the original file is being overwritten, there is a small chance that - * we crash in the middle of writing. Therefore the file is preserved now. - * This makes all block numbers positive so that recovery does not need - * the original file. - * Don't do this if there is a backup file and we are exiting. - */ + // If the original file is being overwritten, there is a small chance that + // we crash in the middle of writing. Therefore the file is preserved now. + // This makes all block numbers positive so that recovery does not need + // the original file. + // Don't do this if there is a backup file and we are exiting. if (reset_changed && !newfile && overwriting && !(exiting && backup != NULL)) { ml_preserve(buf, false, !!p_fs); @@ -3033,36 +3029,34 @@ nobackup: } - /* Default: write the file directly. May write to a temp file for - * multi-byte conversion. */ + // Default: write the file directly. May write to a temp file for + // multi-byte conversion. wfname = fname; - /* Check for forced 'fileencoding' from "++opt=val" argument. */ + // Check for forced 'fileencoding' from "++opt=val" argument. if (eap != NULL && eap->force_enc != 0) { fenc = eap->cmd + eap->force_enc; fenc = enc_canonize(fenc); fenc_tofree = fenc; - } else + } else { fenc = buf->b_p_fenc; + } - /* - * Check if the file needs to be converted. - */ + // Check if the file needs to be converted. converted = need_conversion(fenc); - /* - * Check if UTF-8 to UCS-2/4 or Latin1 conversion needs to be done. Or - * Latin1 to Unicode conversion. This is handled in buf_write_bytes(). - * Prepare the flags for it and allocate bw_conv_buf when needed. - */ + // Check if UTF-8 to UCS-2/4 or Latin1 conversion needs to be done. Or + // Latin1 to Unicode conversion. This is handled in buf_write_bytes(). + // Prepare the flags for it and allocate bw_conv_buf when needed. if (converted && (enc_utf8 || STRCMP(p_enc, "latin1") == 0)) { wb_flags = get_fio_flags(fenc); if (wb_flags & (FIO_UCS2 | FIO_UCS4 | FIO_UTF16 | FIO_UTF8)) { - /* Need to allocate a buffer to translate into. */ - if (wb_flags & (FIO_UCS2 | FIO_UTF16 | FIO_UTF8)) + // Need to allocate a buffer to translate into. + if (wb_flags & (FIO_UCS2 | FIO_UTF16 | FIO_UTF8)) { write_info.bw_conv_buflen = bufsize * 2; - else /* FIO_UCS4 */ + } else { // FIO_UCS4 write_info.bw_conv_buflen = bufsize * 4; + } write_info.bw_conv_buf = verbose_try_malloc(write_info.bw_conv_buflen); if (!write_info.bw_conv_buf) { end = 0; @@ -3074,10 +3068,8 @@ nobackup: if (converted && wb_flags == 0) { # ifdef USE_ICONV - /* - * Use iconv() conversion when conversion is needed and it's not done - * internally. - */ + // Use iconv() conversion when conversion is needed and it's not done + // internally. write_info.bw_iconv_fd = (iconv_t)my_iconv_open(fenc, enc_utf8 ? (char_u *)"utf-8" : p_enc); if (write_info.bw_iconv_fd != (iconv_t)-1) { diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 4b4c18f773..1a493317e1 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -484,6 +484,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, sign) \ + for (sign = buf->b_signlist; sign != NULL; sign = sign->next) // NOLINT + + /* * 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/if_cscope.c b/src/nvim/if_cscope.c index 84721ee96c..a3eabed8a0 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -1524,12 +1524,10 @@ static void cs_fill_results(char *tagstr, size_t totmatches, int *nummatches_a, } totsofar++; - - } /* for all matches */ + } // for all matches (void)cs_read_prompt(i); - - } /* for all cscope connections */ + } // for all cscope connections if (totsofar == 0) { // No matches, free the arrays and return NULL in "*matches_p". @@ -1541,20 +1539,25 @@ static void cs_fill_results(char *tagstr, size_t totmatches, int *nummatches_a, *cntxts_p = cntxts; xfree(buf); -} /* cs_fill_results */ +} // cs_fill_results /* get the requested path components */ static char *cs_pathcomponents(char *path) { - if (p_cspc == 0) + if (p_cspc == 0) { return path; + } char *s = path + strlen(path) - 1; - for (int i = 0; i < p_cspc; ++i) - while (s > path && *--s != '/') continue; - if ((s > path && *s == '/')) - ++s; + for (int i = 0; i < p_cspc; i++) { + while (s > path && *--s != '/') { + continue; + } + } + if ((s > path && *s == '/')) { + s++; + } return s; } diff --git a/src/nvim/main.c b/src/nvim/main.c index 4e1c7dff57..ed8788af60 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -49,6 +49,7 @@ #include "nvim/popupmnu.h" #include "nvim/quickfix.h" #include "nvim/screen.h" +#include "nvim/sign.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/syntax.h" @@ -1337,6 +1338,8 @@ static void init_path(const char *exename) // shipped with Windows package. This also mimics SearchPath(). os_setenv_append_path(exepath); #endif + + init_signs(); } /// Get filename from command line, if any. diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 7809b6814f..2f2f2a7d74 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -29,6 +29,7 @@ #include "nvim/path.h" #include "nvim/quickfix.h" #include "nvim/search.h" +#include "nvim/sign.h" #include "nvim/strings.h" #include "nvim/ui.h" #include "nvim/os/os.h" diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index c161bad66f..683087bd7b 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -2085,31 +2085,31 @@ static char_u *iconv_string(const vimconv_T *const vcp, char_u *str, to = (char *)result + done; tolen = len - done - 2; - /* Avoid a warning for systems with a wrong iconv() prototype by - * casting the second argument to void *. */ + // Avoid a warning for systems with a wrong iconv() prototype by + // casting the second argument to void *. if (iconv(vcp->vc_fd, (void *)&from, &fromlen, &to, &tolen) != SIZE_MAX) { - /* Finished, append a NUL. */ + // Finished, append a NUL. *to = NUL; break; } - /* Check both ICONV_EINVAL and EINVAL, because the dynamically loaded - * iconv library may use one of them. */ + // Check both ICONV_EINVAL and EINVAL, because the dynamically loaded + // iconv library may use one of them. if (!vcp->vc_fail && unconvlenp != NULL && (ICONV_ERRNO == ICONV_EINVAL || ICONV_ERRNO == EINVAL)) { - /* Handle an incomplete sequence at the end. */ + // Handle an incomplete sequence at the end. *to = NUL; *unconvlenp = fromlen; break; - } - /* Check both ICONV_EILSEQ and EILSEQ, because the dynamically loaded - * iconv library may use one of them. */ - else if (!vcp->vc_fail - && (ICONV_ERRNO == ICONV_EILSEQ || ICONV_ERRNO == EILSEQ - || ICONV_ERRNO == ICONV_EINVAL || ICONV_ERRNO == EINVAL)) { - /* Can't convert: insert a '?' and skip a character. This assumes - * conversion from 'encoding' to something else. In other - * situations we don't know what to skip anyway. */ + } else if (!vcp->vc_fail + && (ICONV_ERRNO == ICONV_EILSEQ || ICONV_ERRNO == EILSEQ + || ICONV_ERRNO == ICONV_EINVAL || ICONV_ERRNO == EINVAL)) { + // Check both ICONV_EILSEQ and EILSEQ, because the dynamically loaded + // iconv library may use one of them. + + // Can't convert: insert a '?' and skip a character. This assumes + // conversion from 'encoding' to something else. In other + // situations we don't know what to skip anyway. *to++ = '?'; if (utf_ptr2cells((char_u *)from) > 1) { *to++ = '?'; @@ -2122,7 +2122,7 @@ static char_u *iconv_string(const vimconv_T *const vcp, char_u *str, XFREE_CLEAR(result); break; } - /* Not enough room or skipping illegal sequence. */ + // Not enough room or skipping illegal sequence. done = to - (char *)result; } @@ -2132,11 +2132,9 @@ static char_u *iconv_string(const vimconv_T *const vcp, char_u *str, } # if defined(DYNAMIC_ICONV) -/* - * Dynamically load the "iconv.dll" on Win32. - */ +// Dynamically load the "iconv.dll" on Win32. -#ifndef DYNAMIC_ICONV /* just generating prototypes */ +#ifndef DYNAMIC_ICONV // just generating prototypes # define HINSTANCE int #endif static HINSTANCE hIconvDLL = 0; diff --git a/src/nvim/memline.c b/src/nvim/memline.c index a4d2feb5e3..a69669f680 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -3356,47 +3356,38 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, break; } - /* - * A file name equal to old_fname is OK to use. - */ - if (old_fname != NULL && fnamecmp(fname, old_fname) == 0) + // A file name equal to old_fname is OK to use. + if (old_fname != NULL && fnamecmp(fname, old_fname) == 0) { break; + } - /* - * get here when file already exists - */ - if (fname[n - 2] == 'w' && fname[n - 1] == 'p') { /* first try */ - /* - * If we get here the ".swp" file really exists. - * Give an error message, unless recovering, no file name, we are - * viewing a help file or when the path of the file is different - * (happens when all .swp files are in one directory). - */ + // get here when file already exists + if (fname[n - 2] == 'w' && fname[n - 1] == 'p') { // first try + // If we get here the ".swp" file really exists. + // Give an error message, unless recovering, no file name, we are + // viewing a help file or when the path of the file is different + // (happens when all .swp files are in one directory). if (!recoverymode && buf_fname != NULL && !buf->b_help && !(buf->b_flags & BF_DUMMY)) { int fd; struct block0 b0; int differ = FALSE; - /* - * Try to read block 0 from the swap file to get the original - * file name (and inode number). - */ + // Try to read block 0 from the swap file to get the original + // file name (and inode number). fd = os_open(fname, O_RDONLY, 0); if (fd >= 0) { if (read_eintr(fd, &b0, sizeof(b0)) == sizeof(b0)) { - /* - * If the swapfile has the same directory as the - * buffer don't compare the directory names, they can - * have a different mountpoint. - */ + // If the swapfile has the same directory as the + // buffer don't compare the directory names, they can + // have a different mountpoint. if (b0.b0_flags & B0_SAME_DIR) { if (fnamecmp(path_tail(buf->b_ffname), path_tail(b0.b0_fname)) != 0 - || !same_directory((char_u *) fname, buf->b_ffname)) { - /* Symlinks may point to the same file even - * when the name differs, need to check the - * inode too. */ + || !same_directory((char_u *)fname, buf->b_ffname)) { + // Symlinks may point to the same file even + // when the name differs, need to check the + // inode too. expand_env(b0.b0_fname, NameBuff, MAXPATHL); if (fnamecmp_ino(buf->b_ffname, NameBuff, char_to_long(b0.b0_ino))) { @@ -3404,10 +3395,8 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, } } } else { - /* - * The name in the swap file may be - * "~user/path/file". Expand it first. - */ + // The name in the swap file may be + // "~user/path/file". Expand it first. expand_env(b0.b0_fname, NameBuff, MAXPATHL); if (fnamecmp_ino(buf->b_ffname, NameBuff, char_to_long(b0.b0_ino))) { @@ -3418,9 +3407,9 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, close(fd); } - /* give the ATTENTION message when there is an old swap file - * for the current file, and the buffer was not recovered. */ - if (differ == FALSE && !(curbuf->b_flags & BF_RECOVERED) + // give the ATTENTION message when there is an old swap file + // for the current file, and the buffer was not recovered. */ + if (differ == false && !(curbuf->b_flags & BF_RECOVERED) && vim_strchr(p_shm, SHM_ATTENTION) == NULL) { int choice = 0; diff --git a/src/nvim/memory.c b/src/nvim/memory.c index b8a29070ce..dced03f3d5 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -16,6 +16,7 @@ #include "nvim/message.h" #include "nvim/misc1.h" #include "nvim/ui.h" +#include "nvim/sign.h" #include "nvim/api/vim.h" #ifdef UNIT_TESTING diff --git a/src/nvim/message.c b/src/nvim/message.c index 07148cee76..c5b9187117 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1082,16 +1082,14 @@ void wait_return(int redraw) /* Put the character back in the typeahead buffer. Don't use the * stuff buffer, because lmaps wouldn't work. */ ins_char_typebuf(c); - do_redraw = TRUE; /* need a redraw even though there is - typeahead */ + do_redraw = true; // need a redraw even though there is + // typeahead } } - redir_off = FALSE; + redir_off = false; - /* - * If the user hits ':', '?' or '/' we get a command line from the next - * line. - */ + // If the user hits ':', '?' or '/' we get a command line from the next + // line. if (c == ':' || c == '?' || c == '/') { if (!exmode_active) cmdline_row = msg_row; @@ -1100,19 +1098,17 @@ void wait_return(int redraw) msg_ext_keep_after_cmdline = true; } - /* - * If the window size changed set_shellsize() will redraw the screen. - * Otherwise the screen is only redrawn if 'redraw' is set and no ':' - * typed. - */ + // If the window size changed set_shellsize() will redraw the screen. + // Otherwise the screen is only redrawn if 'redraw' is set and no ':' + // typed. tmpState = State; - State = oldState; /* restore State before set_shellsize */ + State = oldState; // restore State before set_shellsize setmouse(); msg_check(); - need_wait_return = FALSE; - did_wait_return = TRUE; - emsg_on_display = FALSE; /* can delete error message now */ - lines_left = -1; /* reset lines_left at next msg_start() */ + need_wait_return = false; + did_wait_return = true; + emsg_on_display = false; // can delete error message now + lines_left = -1; // reset lines_left at next msg_start() reset_last_sourcing(); if (keep_msg != NULL && vim_strsize(keep_msg) >= (Rows - cmdline_row - 1) * Columns + sc_col) { @@ -1183,25 +1179,25 @@ void msg_ext_set_kind(const char *msg_kind) */ void msg_start(void) { - int did_return = FALSE; + int did_return = false; if (!msg_silent) { XFREE_CLEAR(keep_msg); // don't display old message now } if (need_clr_eos) { - /* Halfway an ":echo" command and getting an (error) message: clear - * any text from the command. */ - need_clr_eos = FALSE; + // Halfway an ":echo" command and getting an (error) message: clear + // any text from the command. + need_clr_eos = false; msg_clr_eos(); } - if (!msg_scroll && full_screen) { /* overwrite last message */ + if (!msg_scroll && full_screen) { // overwrite last message msg_row = cmdline_row; msg_col = cmdmsg_rl ? Columns - 1 : 0; - } else if (msg_didout) { /* start message on next line */ + } else if (msg_didout) { // start message on next line msg_putchar('\n'); did_return = TRUE; if (exmode_active != EXMODE_NORMAL) @@ -1210,7 +1206,7 @@ void msg_start(void) if (!msg_didany || lines_left < 0) msg_starthere(); if (msg_silent == 0) { - msg_didout = FALSE; /* no output on current line yet */ + msg_didout = false; // no output on current line yet } if (ui_has(kUIMessages)) { @@ -2992,12 +2988,13 @@ int verbose_open(void) */ void give_warning(char_u *message, bool hl) FUNC_ATTR_NONNULL_ARG(1) { - /* Don't do this for ":silent". */ - if (msg_silent != 0) + // Don't do this for ":silent". + if (msg_silent != 0) { return; + } - /* Don't want a hit-enter prompt here. */ - ++no_wait_return; + // Don't want a hit-enter prompt here. + no_wait_return++; set_vim_var_string(VV_WARNINGMSG, (char *)message, -1); XFREE_CLEAR(keep_msg); @@ -3015,7 +3012,7 @@ void give_warning(char_u *message, bool hl) FUNC_ATTR_NONNULL_ARG(1) msg_nowait = true; // Don't wait for this message. msg_col = 0; - --no_wait_return; + no_wait_return--; } void give_warning2(char_u *const message, char_u *const a1, bool hl) diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 50abd226fc..d6e78d9ce4 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -4213,10 +4213,10 @@ dozet: set_fraction(curwin); break; - /* "z^", "z-" and "zb": put cursor at bottom of screen */ - case '^': /* Strange Vi behavior: <count>z^ finds line at top of window - * when <count> is at bottom of window, and puts that one at - * bottom of window. */ + // "z^", "z-" and "zb": put cursor at bottom of screen + case '^': // Strange Vi behavior: <count>z^ finds line at top of window + // when <count> is at bottom of window, and puts that one at + // bottom of window. if (cap->count0 != 0) { scroll_cursor_bot(0, true); curwin->w_cursor.lnum = curwin->w_topline; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index f4630de3ca..b0109b0824 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -103,6 +103,7 @@ #include "nvim/quickfix.h" #include "nvim/regexp.h" #include "nvim/search.h" +#include "nvim/sign.h" #include "nvim/spell.h" #include "nvim/state.h" #include "nvim/strings.h" @@ -220,7 +221,8 @@ void redraw_buf_later(buf_T *buf, int type) void redraw_buf_line_later(buf_T *buf, linenr_T line) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == buf) { + if (wp->w_buffer == buf + && line >= wp->w_topline && line < wp->w_botline) { redrawWinline(wp, line); } } diff --git a/src/nvim/search.c b/src/nvim/search.c index 3bd222b3de..e64233985b 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -87,13 +87,13 @@ static struct spat spats[2] = static int last_idx = 0; /* index in spats[] for RE_LAST */ -static char_u lastc[2] = {NUL, NUL}; /* last character searched for */ -static int lastcdir = FORWARD; /* last direction of character search */ -static int last_t_cmd = TRUE; /* last search t_cmd */ +static char_u lastc[2] = { NUL, NUL }; // last character searched for +static int lastcdir = FORWARD; // last direction of character search +static int last_t_cmd = true; // last search t_cmd static char_u lastc_bytes[MB_MAXBYTES + 1]; -static int lastc_bytelen = 1; /* >1 for multi-byte char */ +static int lastc_bytelen = 1; // >1 for multi-byte char -/* copy of spats[], for keeping the search patterns while executing autocmds */ +// copy of spats[], for keeping the search patterns while executing autocmds static struct spat saved_spats[2]; // copy of spats[RE_SEARCH], for keeping the search patterns while incremental // searching @@ -101,8 +101,8 @@ static struct spat saved_last_search_spat; static int saved_last_idx = 0; static int saved_no_hlsearch = 0; -static char_u *mr_pattern = NULL; /* pattern used by search_regcomp() */ -static int mr_pattern_alloced = FALSE; /* mr_pattern was allocated */ +static char_u *mr_pattern = NULL; // pattern used by search_regcomp() +static int mr_pattern_alloced = false; // mr_pattern was allocated /* * Type used by find_pattern_in_path() to remember which included files have diff --git a/src/nvim/sign.c b/src/nvim/sign.c new file mode 100644 index 0000000000..ac26fd0137 --- /dev/null +++ b/src/nvim/sign.c @@ -0,0 +1,1746 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// +// sign.c: functions for managing with signs +// + + +#include "nvim/vim.h" +#include "nvim/sign.h" +#include "nvim/ascii.h" +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/cursor.h" +#include "nvim/ex_docmd.h" +#include "nvim/edit.h" +#include "nvim/fold.h" +#include "nvim/move.h" +#include "nvim/screen.h" +#include "nvim/syntax.h" + +/// Struct to hold the sign properties. +typedef struct sign sign_T; + +struct sign +{ + sign_T *sn_next; // next sign in list + int sn_typenr; // type number of sign + char_u *sn_name; // name of sign + char_u *sn_icon; // name of pixmap +# ifdef FEAT_SIGN_ICONS + void *sn_image; // icon image +# endif + char_u *sn_text; // text used instead of pixmap + int sn_line_hl; // highlight ID for line + int sn_text_hl; // highlight ID for text + int sn_num_hl; // highlight ID for line number +}; + +static sign_T *first_sign = NULL; +static int next_sign_typenr = 1; + +static void sign_list_defined(sign_T *sp); +static void sign_undefine(sign_T *sp, sign_T *sp_prev); + +static char *cmds[] = { + "define", +#define SIGNCMD_DEFINE 0 + "undefine", +#define SIGNCMD_UNDEFINE 1 + "list", +#define SIGNCMD_LIST 2 + "place", +#define SIGNCMD_PLACE 3 + "unplace", +#define SIGNCMD_UNPLACE 4 + "jump", +#define SIGNCMD_JUMP 5 + NULL +#define SIGNCMD_LAST 6 +}; + + +static hashtab_T sg_table; // sign group (signgroup_T) hashtable +static int next_sign_id = 1; // next sign id in the global group + +/// Initialize data needed for managing signs +void init_signs(void) +{ + hash_init(&sg_table); // sign group hash table +} + +/// A new sign in group 'groupname' is added. If the group is not present, +/// create it. Otherwise reference the group. +/// +static signgroup_T * sign_group_ref(const char_u *groupname) +{ + hash_T hash; + hashitem_T *hi; + signgroup_T *group; + + hash = hash_hash(groupname); + hi = hash_lookup(&sg_table, (char *)groupname, STRLEN(groupname), hash); + if (HASHITEM_EMPTY(hi)) { + // new group + group = (signgroup_T *)xmalloc( + (unsigned)(sizeof(signgroup_T) + STRLEN(groupname))); + if (group == NULL) { + return NULL; + } + STRCPY(group->sg_name, groupname); + group->refcount = 1; + group->next_sign_id = 1; + hash_add_item(&sg_table, hi, group->sg_name, hash); + } else { + // existing group + group = HI2SG(hi); + group->refcount++; + } + + return group; +} + +/// A sign in group 'groupname' is removed. If all the signs in this group are +/// removed, then remove the group. +static void sign_group_unref(char_u *groupname) +{ + hashitem_T *hi; + signgroup_T *group; + + hi = hash_find(&sg_table, groupname); + if (!HASHITEM_EMPTY(hi)) { + group = HI2SG(hi); + group->refcount--; + if (group->refcount == 0) { + // All the signs in this group are removed + hash_remove(&sg_table, hi); + xfree(group); + } + } +} + +/// 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, const char_u *group) +{ + return ((group != NULL && STRCMP(group, "*") == 0) + || (group == NULL && sign->group == NULL) + || (group != NULL && sign->group != NULL + && STRCMP(group, sign->group->sg_name) == 0)); +} + +/// Get the next free sign identifier in the specified group +int sign_group_get_next_signid(buf_T *buf, const char_u *groupname) +{ + int id = 1; + signgroup_T *group = NULL; + signlist_T *sign; + hashitem_T *hi; + int found = false; + + if (groupname != NULL) { + hi = hash_find(&sg_table, groupname); + if (HASHITEM_EMPTY(hi)) { + return id; + } + group = HI2SG(hi); + } + + // Search for the next usuable sign identifier + while (!found) { + if (group == NULL) { + id = next_sign_id++; // global group + } else { + id = group->next_sign_id++; + } + + // Check whether this sign is already placed in the buffer + found = true; + FOR_ALL_SIGNS_IN_BUF(buf, sign) { + if (id == sign->id && sign_in_group(sign, groupname)) { + found = false; // sign identifier is in use + break; + } + } + } + + return id; +} + +/// 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 + const 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 *newsign = xmalloc(sizeof(signlist_T)); + newsign->id = id; + newsign->lnum = lnum; + newsign->typenr = typenr; + if (group != NULL) { + newsign->group = sign_group_ref(group); + if (newsign->group == NULL) { + xfree(newsign); + return; + } + } else { + newsign->group = NULL; + } + newsign->priority = prio; + newsign->next = next; + newsign->prev = prev; + if (next != NULL) { + next->prev = newsign; + } + buf->b_signcols_max = -1; + + if (prev == NULL) { + // 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(); + } + + // first sign in signlist + buf->b_signlist = newsign; + } else { + prev->next = newsign; + } +} + +/// 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 + const 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); +} + +/// Get the name of a sign by its typenr. +char_u * sign_typenr2name(int typenr) +{ + sign_T *sp; + + for (sp = first_sign; sp != NULL; sp = sp->sn_next) { + if (sp->sn_typenr == typenr) { + return sp->sn_name; + } + } + return (char_u *)_("[Deleted]"); +} + +/// 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 *)"" + : (char *)sign->group->sg_name)); + tv_dict_add_nr(d, S_LEN("lnum"), sign->lnum); + tv_dict_add_str(d, S_LEN("name"), (char *)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 + const char_u *groupname, // sign 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; // a sign in the signlist + signlist_T *prev; // the previous sign + + prev = NULL; + FOR_ALL_SIGNS_IN_BUF(buf, sign) { + if (lnum == sign->lnum && id == sign->id + && sign_in_group(sign, groupname)) { + // Update an existing sign + sign->typenr = typenr; + return; + } else if (lnum < sign->lnum) { + insert_sign_by_lnum_prio(buf, prev, id, groupname, prio, lnum, typenr); + return; + } + prev = sign; + } + + insert_sign_by_lnum_prio(buf, prev, id, groupname, prio, lnum, typenr); +} + +// For an existing, placed sign "markId" change the type to "typenr". +// Returns the line number of the sign, or zero if the sign is not found. +linenr_T buf_change_sign_type( + buf_T *buf, // buffer to store sign in + int markId, // sign ID + const char_u *group, // sign group + int typenr // typenr of sign we are adding +) +{ + signlist_T *sign; // a sign in the signlist + + FOR_ALL_SIGNS_IN_BUF(buf, sign) { + if (sign->id == markId && sign_in_group(sign, group)) { + sign->typenr = typenr; + return sign->lnum; + } + } + + return (linenr_T)0; +} + +/// Gets a sign from a given line. +/// +/// Return the type number of the sign at line number 'lnum' in buffer 'buf' +/// which has the attribute specified by 'type'. Returns 0 if a sign is not +/// found at the line number or it doesn't have the specified attribute. +/// @param buf Buffer in which to search +/// @param lnum Line in which to search +/// @param type Type of sign to look for +/// @param idx if there multiple signs, this index will pick the n-th +// out of the most `max_signs` sorted ascending by Id. +/// @param max_signs the number of signs, with priority for the ones +// with the highest Ids. +/// @return Identifier of the matching sign, or 0 +int buf_getsigntype(buf_T *buf, linenr_T lnum, SignType type, + int idx, int max_signs) +{ + signlist_T *sign; // a sign in a b_signlist + signlist_T *matches[9]; + int nr_matches = 0; + + FOR_ALL_SIGNS_IN_BUF(buf, sign) { + if (sign->lnum == lnum + && (type == SIGN_ANY + || (type == SIGN_TEXT + && sign_get_text(sign->typenr) != NULL) + || (type == SIGN_LINEHL + && sign_get_attr(sign->typenr, SIGN_LINEHL) != 0) + || (type == SIGN_NUMHL + && sign_get_attr(sign->typenr, SIGN_NUMHL) != 0))) { + matches[nr_matches] = sign; + nr_matches++; + // signlist is sorted with most important (priority, id), thus we + // may stop as soon as we have max_signs matches + if (nr_matches == ARRAY_SIZE(matches) || nr_matches >= max_signs) { + break; + } + } + } + + if (nr_matches > 0) { + if (idx >= nr_matches) { + return 0; + } + + return matches[nr_matches - idx -1]->typenr; + } + + 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 + linenr_T atlnum, // sign at this line, 0 - at any line + int id, // sign id + char_u *group // sign group +) +{ + signlist_T **lastp; // pointer to pointer to current sign + signlist_T *sign; // a sign in a b_signlist + signlist_T *next; // the next sign in a b_signlist + linenr_T lnum; // line number whose sign was deleted + + buf->b_signcols_max = -1; + lastp = &buf->b_signlist; + lnum = 0; + for (sign = buf->b_signlist; sign != NULL; sign = next) { + next = sign->next; + if ((id == 0 || sign->id == id) + && (atlnum == 0 || sign->lnum == atlnum) + && sign_in_group(sign, group)) { + *lastp = next; + if (next != NULL) { + next->prev = sign->prev; + } + lnum = sign->lnum; + if (sign->group != NULL) { + sign_group_unref(sign->group->sg_name); + } + xfree(sign); + redraw_buf_line_later(buf, lnum); + // Check whether only one sign needs to be deleted + // If deleting a sign with a specific identifier in a particular + // group or deleting any sign at a particular line number, delete + // only one sign. + if (group == NULL + || (*group != '*' && id != 0) + || (*group == '*' && atlnum != 0)) { + break; + } + } else { + lastp = &sign->next; + } + } + + // When deleted the last sign needs to redraw the windows to remove the + // sign column. + if (buf->b_signlist == NULL) { + redraw_buf_later(buf, NOT_VALID); + changed_cline_bef_curs(); + } + + return lnum; +} + + +/// 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 + char_u *group // sign group +) +{ + signlist_T *sign; // a sign in the signlist + + FOR_ALL_SIGNS_IN_BUF(buf, sign) { + if (sign->id == id && sign_in_group(sign, group)) { + return (int)sign->lnum; + } + } + + return 0; +} + +/// Return the sign at line 'lnum' in buffer 'buf'. Returns NULL if a sign is +/// not found at the line. If 'groupname' is NULL, searches in the global group. +static signlist_T * buf_getsign_at_line( + buf_T *buf, // buffer whose sign we are searching for + linenr_T lnum, // line number of sign + char_u *groupname // sign group name +) +{ + signlist_T *sign; // a sign in the signlist + + FOR_ALL_SIGNS_IN_BUF(buf, sign) { + if (sign->lnum == lnum && sign_in_group(sign, groupname)) { + 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 + char_u *groupname // sign group name +) +{ + signlist_T *sign; // a sign in the signlist + + sign = buf_getsign_at_line(buf, lnum, groupname); + if (sign != NULL) { + return sign->id; + } + + return 0; +} + +/// Delete signs in buffer "buf". +void 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 + // sign column. Not when curwin is NULL (this means we're exiting). + if (buf->b_signlist != NULL && curwin != NULL) { + changed_cline_bef_curs(); + } + + 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; + } + if (sign->group != NULL) { + sign_group_unref(sign->group->sg_name); + } + xfree(sign); + } else { + lastp = &sign->next; + } + } + buf->b_signcols_max = -1; +} + +/// List placed signs for "rbuf". If "rbuf" is NULL do it for all buffers. +void sign_list_placed(buf_T *rbuf, char_u *sign_group) +{ + buf_T *buf; + signlist_T *sign; + char lbuf[MSG_BUF_LEN]; + char group[MSG_BUF_LEN]; + + MSG_PUTS_TITLE(_("\n--- Signs ---")); + msg_putchar('\n'); + if (rbuf == NULL) { + buf = firstbuf; + } else { + buf = rbuf; + } + while (buf != NULL && !got_int) { + if (buf->b_signlist != NULL) { + vim_snprintf(lbuf, MSG_BUF_LEN, _("Signs for %s:"), buf->b_fname); + MSG_PUTS_ATTR(lbuf, HL_ATTR(HLF_D)); + msg_putchar('\n'); + } + FOR_ALL_SIGNS_IN_BUF(buf, sign) { + if (got_int) { + break; + } + if (!sign_in_group(sign, sign_group)) { + continue; + } + if (sign->group != NULL) { + vim_snprintf(group, MSG_BUF_LEN, _(" group=%s"), + sign->group->sg_name); + } else { + group[0] = '\0'; + } + vim_snprintf(lbuf, MSG_BUF_LEN, + _(" 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; + } + buf = buf->b_next; + } +} + +/// Adjust a placed sign for inserted/deleted lines. +void sign_mark_adjust( + linenr_T line1, + linenr_T line2, + long amount, + long amount_after +) +{ + signlist_T *sign; // a sign in a b_signlist + linenr_T new_lnum; // new line number to assign to sign + + curbuf->b_signcols_max = -1; + + FOR_ALL_SIGNS_IN_BUF(curbuf, sign) { + new_lnum = sign->lnum; + if (sign->lnum >= line1 && sign->lnum <= line2) { + if (amount != MAXLNUM) { + new_lnum += amount; + } + } else if (sign->lnum > line2) { + new_lnum += amount_after; + } + // If the new sign line number is past the last line in the buffer, + // then don't adjust the line number. Otherwise, it will always be past + // the last line and will not be visible. + if (sign->lnum >= line1 && new_lnum <= curbuf->b_ml.ml_line_count) { + sign->lnum = new_lnum; + } + } +} + +/// Find index of a ":sign" subcmd from its name. +/// "*end_cmd" must be writable. +static int sign_cmd_idx( + char_u *begin_cmd, // begin of sign subcmd + char_u *end_cmd // just after sign subcmd +) +{ + int idx; + char_u save = *end_cmd; + + *end_cmd = (char_u)NUL; + for (idx = 0; ; idx++) { + if (cmds[idx] == NULL || STRCMP(begin_cmd, cmds[idx]) == 0) { + break; + } + } + *end_cmd = save; + return idx; +} + +/// Find a sign by name. Also returns pointer to the previous sign. +static sign_T * sign_find(const 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; +} + +/// Allocate a new sign +static sign_T * alloc_new_sign(char_u *name) +{ + sign_T *sp; + 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 NULL; + } + 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); + + return sp; +} + +/// Initialize the icon information for a new sign +static void sign_define_init_icon(sign_T *sp, char_u *icon) +{ + 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 +} + +/// Initialize the text for a new sign +static int sign_define_init_text(sign_T *sp, char_u *text) +{ + char_u *s; + char_u *endp; + int cells; + size_t 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 = (size_t)(endp - text + ((cells == 1) ? 1 : 0)); + sp->sn_text = vim_strnsave(text, len); + + if (cells == 1) { + STRCPY(sp->sn_text + len - 1, " "); + } + + return OK; +} + +/// 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) { + sp = alloc_new_sign(name); + if (sp == NULL) { + return FAIL; + } + + // 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) { + sign_define_init_icon(sp, icon); + } + + if (text != NULL && (sign_define_init_text(sp, text) == FAIL)) { + return FAIL; + } + + 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(const 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 specified file location or update a sign. +int sign_place( + int *sign_id, + const char_u *sign_group, + const 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) { + *sign_id = sign_group_get_next_signid(buf, sign_group); + } + + 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, linenr_T atlnum) +{ + if (buf->b_signlist == NULL) { // No signs in the buffer + return OK; + } + 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, atlnum, sign_id, sign_group); + if (lnum == 0) { + return FAIL; + } + redraw_buf_line_later(buf, lnum); + } + + return OK; +} + +/// Unplace the sign at the current cursor line. +static void sign_unplace_at_cursor(char_u *groupname) +{ + int id = -1; + + id = buf_findsign_id(curwin->w_buffer, curwin->w_cursor.lnum, groupname); + if (id > 0) { + sign_unplace(id, groupname, curwin->w_buffer, curwin->w_cursor.lnum); + } else { + EMSG(_("E159: Missing sign number")); + } +} + +/// Jump to a sign. +linenr_T sign_jump(int sign_id, char_u *sign_group, buf_T *buf) +{ + linenr_T lnum; + + if ((lnum = buf_findsign(buf, sign_id, sign_group)) <= 0) { + EMSGN(_("E157: Invalid sign ID: %" PRId64), sign_id); + return -1; + } + + // goto a sign ... + if (buf_jump_open_win(buf) != NULL) { // ... in a current window + curwin->w_cursor.lnum = lnum; + check_cursor_lnum(); + beginline(BL_WHITE); + } else { // ... not currently in a window + if (buf->b_fname == NULL) { + EMSG(_("E934: Cannot jump to a buffer that does not have a name")); + return -1; + } + size_t cmdlen = STRLEN(buf->b_fname) + 24; + char *cmd = xmallocz(cmdlen); + snprintf(cmd, cmdlen, "e +%" PRId64 " %s", + (int64_t)lnum, buf->b_fname); + do_cmdline_cmd(cmd); + xfree(cmd); + } + + foldOpenCursor(); + + return lnum; +} + +/// ":sign define {name} ..." command +static void sign_define_cmd(char_u *sign_name, char_u *cmdline) +{ + char_u *arg; + char_u *p = cmdline; + char_u *icon = NULL; + char_u *text = NULL; + char_u *linehl = NULL; + char_u *texthl = NULL; + char_u *numhl = NULL; + int failed = false; + + // set values for a defined sign. + for (;;) { + arg = skipwhite(p); + if (*arg == NUL) { + break; + } + p = skiptowhite_esc(arg); + if (STRNCMP(arg, "icon=", 5) == 0) { + arg += 5; + icon = vim_strnsave(arg, (size_t)(p - arg)); + } else if (STRNCMP(arg, "text=", 5) == 0) { + arg += 5; + text = vim_strnsave(arg, (size_t)(p - arg)); + } else if (STRNCMP(arg, "linehl=", 7) == 0) { + arg += 7; + linehl = vim_strnsave(arg, (size_t)(p - arg)); + } else if (STRNCMP(arg, "texthl=", 7) == 0) { + arg += 7; + texthl = vim_strnsave(arg, (size_t)(p - arg)); + } else if (STRNCMP(arg, "numhl=", 6) == 0) { + arg += 6; + numhl = vim_strnsave(arg, (size_t)(p - arg)); + } else { + EMSG2(_(e_invarg2), arg); + failed = true; + break; + } + } + + if (!failed) { + sign_define_by_name(sign_name, icon, linehl, text, texthl, numhl); + } + + xfree(icon); + xfree(text); + xfree(linehl); + xfree(texthl); + xfree(numhl); +} + +/// ":sign place" command +static void sign_place_cmd( + buf_T *buf, + linenr_T lnum, + char_u *sign_name, + int id, + char_u *group, + int prio +) +{ + if (id <= 0) { + // List signs placed in a file/buffer + // :sign place file={fname} + // :sign place group={group} file={fname} + // :sign place group=* file={fname} + // :sign place buffer={nr} + // :sign place group={group} buffer={nr} + // :sign place group=* buffer={nr} + // :sign place + // :sign place group={group} + // :sign place group=* + if (lnum >= 0 || sign_name != NULL + || (group != NULL && *group == '\0')) { + EMSG(_(e_invarg)); + } else { + sign_list_placed(buf, group); + } + } else { + // Place a new sign + if (sign_name == NULL || buf == NULL + || (group != NULL && *group == '\0')) { + EMSG(_(e_invarg)); + return; + } + + sign_place(&id, group, sign_name, buf, lnum, prio); + } +} + +/// ":sign unplace" command +static void sign_unplace_cmd( + buf_T *buf, + linenr_T lnum, + char_u *sign_name, + int id, + char_u *group +) +{ + if (lnum >= 0 || sign_name != NULL || (group != NULL && *group == '\0')) { + EMSG(_(e_invarg)); + return; + } + + if (id == -2) { + if (buf != NULL) { + // :sign unplace * file={fname} + // :sign unplace * group={group} file={fname} + // :sign unplace * group=* file={fname} + // :sign unplace * buffer={nr} + // :sign unplace * group={group} buffer={nr} + // :sign unplace * group=* buffer={nr} + sign_unplace(0, group, buf, 0); + } else { + // :sign unplace * + // :sign unplace * group={group} + // :sign unplace * group=* + FOR_ALL_BUFFERS(cbuf) { + if (cbuf->b_signlist != NULL) { + buf_delete_signs(cbuf, group); + } + } + } + } else { + if (buf != NULL) { + // :sign unplace {id} file={fname} + // :sign unplace {id} group={group} file={fname} + // :sign unplace {id} group=* file={fname} + // :sign unplace {id} buffer={nr} + // :sign unplace {id} group={group} buffer={nr} + // :sign unplace {id} group=* buffer={nr} + sign_unplace(id, group, buf, 0); + } else { + if (id == -1) { + // :sign unplace group={group} + // :sign unplace group=* + sign_unplace_at_cursor(group); + } else { + // :sign unplace {id} + // :sign unplace {id} group={group} + // :sign unplace {id} group=* + FOR_ALL_BUFFERS(cbuf) { + sign_unplace(id, group, cbuf, 0); + } + } + } + } +} + +/// Jump to a placed sign commands: +/// :sign jump {id} file={fname} +/// :sign jump {id} buffer={nr} +/// :sign jump {id} group={group} file={fname} +/// :sign jump {id} group={group} buffer={nr} +static void sign_jump_cmd( + buf_T *buf, + linenr_T lnum, + char_u *sign_name, + int id, + char_u *group +) +{ + if (sign_name == NULL && group == NULL && id == -1) { + EMSG(_(e_argreq)); + return; + } + + if (buf == NULL || (group != NULL && *group == '\0') + || lnum >= 0 || sign_name != NULL) { + // File or buffer is not specified or an empty group is used + // or a line number or a sign name is specified. + EMSG(_(e_invarg)); + return; + } + + (void)sign_jump(id, group, buf); +} + +/// Parse the command line arguments for the ":sign place", ":sign unplace" and +/// ":sign jump" commands. +/// The supported arguments are: line={lnum} name={name} group={group} +/// priority={prio} and file={fname} or buffer={nr}. +static int parse_sign_cmd_args( + int cmd, + char_u *arg, + char_u **sign_name, + int *signid, + char_u **group, + int *prio, + buf_T **buf, + linenr_T *lnum +) +{ + char_u *arg1; + char_u *name; + char_u *filename = NULL; + int lnum_arg = false; + + // first arg could be placed sign id + arg1 = arg; + if (ascii_isdigit(*arg)) { + *signid = getdigits_int(&arg); + if (!ascii_iswhite(*arg) && *arg != NUL) { + *signid = -1; + arg = arg1; + } else { + arg = skipwhite(arg); + } + } + + while (*arg != NUL) { + if (STRNCMP(arg, "line=", 5) == 0) { + arg += 5; + *lnum = atoi((char *)arg); + arg = skiptowhite(arg); + lnum_arg = true; + } else if (STRNCMP(arg, "*", 1) == 0 && cmd == SIGNCMD_UNPLACE) { + if (*signid != -1) { + EMSG(_(e_invarg)); + return FAIL; + } + *signid = -2; + arg = skiptowhite(arg + 1); + } else if (STRNCMP(arg, "name=", 5) == 0) { + arg += 5; + name = arg; + arg = skiptowhite(arg); + if (*arg != NUL) { + *arg++ = NUL; + } + while (name[0] == '0' && name[1] != NUL) { + name++; + } + *sign_name = 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; + filename = arg; + *buf = buflist_findname_exp(arg); + break; + } else if (STRNCMP(arg, "buffer=", 7) == 0) { + arg += 7; + filename = arg; + *buf = buflist_findnr(getdigits_int(&arg)); + if (*skipwhite(arg) != NUL) { + EMSG(_(e_trailing)); + } + break; + } else { + EMSG(_(e_invarg)); + return FAIL; + } + arg = skipwhite(arg); + } + + if (filename != NULL && *buf == NULL) { + EMSG2(_("E158: Invalid buffer name: %s"), filename); + return FAIL; + } + + // If the filename is not supplied for the sign place or the sign jump + // command, then use the current buffer. + if (filename == NULL && ((cmd == SIGNCMD_PLACE && lnum_arg) + || cmd == SIGNCMD_JUMP)) { + *buf = curwin->w_buffer; + } + return OK; +} + +/// ":sign" command +void ex_sign(exarg_T *eap) +{ + char_u *arg = eap->arg; + char_u *p; + int idx; + sign_T *sp; + + // Parse the subcommand. + p = skiptowhite(arg); + idx = sign_cmd_idx(arg, p); + if (idx == SIGNCMD_LAST) { + EMSG2(_("E160: Unknown sign command: %s"), arg); + return; + } + arg = skipwhite(p); + + if (idx <= SIGNCMD_LIST) { + // Define, undefine or list signs. + if (idx == SIGNCMD_LIST && *arg == NUL) { + // ":sign list": list all defined signs + for (sp = first_sign; sp != NULL && !got_int; sp = sp->sn_next) { + sign_list_defined(sp); + } + } else if (*arg == NUL) { + EMSG(_("E156: Missing sign name")); + } else { + char_u *name; + + // 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); + if (*p != NUL) { + *p++ = NUL; + } + while (arg[0] == '0' && arg[1] != NUL) { + arg++; + } + name = vim_strsave(arg); + + if (idx == SIGNCMD_DEFINE) { + sign_define_cmd(name, p); + } else if (idx == SIGNCMD_LIST) { + // ":sign list {name}" + sign_list_by_name(name); + } else { + // ":sign undefine {name}" + 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; + buf_T *buf = NULL; + + // Parse command line arguments + if (parse_sign_cmd_args(idx, arg, &sign_name, &id, &group, &prio, + &buf, &lnum) == FAIL) { + return; + } + + if (idx == SIGNCMD_PLACE) { + sign_place_cmd(buf, lnum, sign_name, id, group, prio); + } else if (idx == SIGNCMD_UNPLACE) { + sign_unplace_cmd(buf, lnum, sign_name, id, group); + } else if (idx == SIGNCMD_JUMP) { + sign_jump_cmd(buf, lnum, sign_name, id, group); + } + } +} + +/// Return information about a specified sign +static void sign_getinfo(sign_T *sp, dict_T *retdict) +{ + const char *p; + + tv_dict_add_str(retdict, S_LEN("name"), (char *)sp->sn_name); + if (sp->sn_icon != NULL) { + tv_dict_add_str(retdict, S_LEN("icon"), (char *)sp->sn_icon); + } + if (sp->sn_text != NULL) { + tv_dict_add_str(retdict, S_LEN("text"), (char *)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 = "NONE"; + } + tv_dict_add_str(retdict, S_LEN("linehl"), (char *)p); + } + if (sp->sn_text_hl > 0) { + p = get_highlight_name_ext(NULL, sp->sn_text_hl - 1, false); + if (p == NULL) { + p = "NONE"; + } + tv_dict_add_str(retdict, S_LEN("texthl"), (char *)p); + } + if (sp->sn_num_hl > 0) { + p = get_highlight_name_ext(NULL, sp->sn_num_hl - 1, false); + if (p == NULL) { + p = "NONE"; + } + tv_dict_add_str(retdict, S_LEN("numhl"), (char *)p); + } +} + +/// If 'name' is NULL, return a list of all the defined signs. +/// Otherwise, return information about the specified sign. +void sign_getlist(const 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; + } + } +} + +/// Returns information about signs placed in a buffer as list of dicts. +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_ALL_SIGNS_IN_BUF(buf, sign) { + if ((d = sign_get_info(sign)) != NULL) { + tv_list_append_dict(l, d); + } + } + return l; +} + +/// 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, + const 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, sign) { + 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, + const 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(cbuf) { + if (cbuf->b_signlist != NULL) { + sign_get_placed_in_buf(cbuf, 0, sign_id, sign_group, retlist); + } + } + } +} + +# if defined(FEAT_SIGN_ICONS) || defined(PROTO) +/// Allocate the icons. Called when the GUI has started. Allows defining +/// signs before it starts. +void sign_gui_started(void) +{ + sign_T *sp; + + for (sp = first_sign; sp != NULL; sp = sp->sn_next) { + if (sp->sn_icon != NULL) { + sp->sn_image = gui_mch_register_sign(sp->sn_icon); + } + } +} +# endif + +/// List one sign. +static void sign_list_defined(sign_T *sp) +{ + smsg("sign %s", sp->sn_name); + if (sp->sn_icon != NULL) { + msg_puts(" icon="); + msg_outtrans(sp->sn_icon); + msg_puts(_(" (not supported)")); + } + if (sp->sn_text != NULL) { + msg_puts(" text="); + msg_outtrans(sp->sn_text); + } + if (sp->sn_line_hl > 0) { + msg_puts(" linehl="); + const char *const p = get_highlight_name_ext(NULL, + sp->sn_line_hl - 1, false); + if (p == NULL) { + msg_puts("NONE"); + } else { + msg_puts(p); + } + } + if (sp->sn_text_hl > 0) { + msg_puts(" texthl="); + const char *const p = get_highlight_name_ext(NULL, + sp->sn_text_hl - 1, false); + if (p == NULL) { + msg_puts("NONE"); + } else { + msg_puts(p); + } + } + if (sp->sn_num_hl > 0) { + msg_puts(" numhl="); + const char *const p = get_highlight_name_ext(NULL, + sp->sn_num_hl - 1, false); + if (p == NULL) { + msg_puts("NONE"); + } else { + msg_puts(p); + } + } +} + +/// Undefine a sign and free its memory. +static void sign_undefine(sign_T *sp, sign_T *sp_prev) +{ + xfree(sp->sn_name); + xfree(sp->sn_icon); + xfree(sp->sn_text); + if (sp_prev == NULL) { + first_sign = sp->sn_next; + } else { + sp_prev->sn_next = sp->sn_next; + } + xfree(sp); +} + +/// Gets highlighting attribute for sign "typenr" corresponding to "type". +int sign_get_attr(int typenr, SignType type) +{ + sign_T *sp; + int sign_hl = 0; + + for (sp = first_sign; sp != NULL; sp = sp->sn_next) { + if (sp->sn_typenr == typenr) { + switch (type) { + case SIGN_TEXT: + sign_hl = sp->sn_text_hl; + break; + case SIGN_LINEHL: + sign_hl = sp->sn_line_hl; + break; + case SIGN_NUMHL: + sign_hl = sp->sn_num_hl; + break; + default: + abort(); + } + if (sign_hl > 0) { + return syn_id2attr(sign_hl); + } + break; + } + } + return 0; +} + +/// Get text mark for sign "typenr". +/// Returns NULL if there isn't one. +char_u * sign_get_text(int typenr) +{ + sign_T *sp; + + for (sp = first_sign; sp != NULL; sp = sp->sn_next) { + if (sp->sn_typenr == typenr) { + return sp->sn_text; + } + } + return NULL; +} + +# if defined(FEAT_SIGN_ICONS) || defined(PROTO) +void * sign_get_image( + int typenr // the attribute which may have a sign +) +{ + sign_T *sp; + + for (sp = first_sign; sp != NULL; sp = sp->sn_next) { + if (sp->sn_typenr == typenr) { + return sp->sn_image; + } + } + return NULL; +} +# endif + +/// Undefine/free all signs. +void free_signs(void) +{ + while (first_sign != NULL) { + sign_undefine(first_sign, NULL); + } +} + +static enum +{ + EXP_SUBCMD, // expand :sign sub-commands + EXP_DEFINE, // expand :sign define {name} args + EXP_PLACE, // expand :sign place {id} args + EXP_UNPLACE, // expand :sign unplace" + EXP_SIGN_NAMES // expand with name of placed signs +} expand_what; + +/// Function given to ExpandGeneric() to obtain the sign command +/// expansion. +char_u * get_sign_name(expand_T *xp, int idx) +{ + switch (expand_what) { + case EXP_SUBCMD: + return (char_u *)cmds[idx]; + case EXP_DEFINE: { + char *define_arg[] = { "icon=", "linehl=", "text=", "texthl=", "numhl=", + NULL }; + return (char_u *)define_arg[idx]; + } + case EXP_PLACE: { + char *place_arg[] = { "line=", "name=", "group=", "priority=", "file=", + "buffer=", NULL }; + return (char_u *)place_arg[idx]; + } + case EXP_UNPLACE: { + char *unplace_arg[] = { "group=", "file=", "buffer=", NULL }; + return (char_u *)unplace_arg[idx]; + } + case EXP_SIGN_NAMES: { + // Complete with name of signs already defined + int current_idx = 0; + for (sign_T *sp = first_sign; sp != NULL; sp = sp->sn_next) { + if (current_idx++ == idx) { + return sp->sn_name; + } + } + } + return NULL; + default: + return NULL; + } +} + +/// Handle command line completion for :sign command. +void set_context_in_sign_cmd(expand_T *xp, char_u *arg) +{ + char_u *p; + char_u *end_subcmd; + char_u *last; + int cmd_idx; + char_u *begin_subcmd_args; + + // Default: expand subcommands. + xp->xp_context = EXPAND_SIGN; + expand_what = EXP_SUBCMD; + xp->xp_pattern = arg; + + end_subcmd = skiptowhite(arg); + if (*end_subcmd == NUL) { + // expand subcmd name + // :sign {subcmd}<CTRL-D> + return; + } + + cmd_idx = sign_cmd_idx(arg, end_subcmd); + + // :sign {subcmd} {subcmd_args} + // | + // begin_subcmd_args + begin_subcmd_args = skipwhite(end_subcmd); + p = skiptowhite(begin_subcmd_args); + if (*p == NUL) { + // + // Expand first argument of subcmd when possible. + // For ":jump {id}" and ":unplace {id}", we could + // possibly expand the ids of all signs already placed. + // + xp->xp_pattern = begin_subcmd_args; + switch (cmd_idx) { + case SIGNCMD_LIST: + case SIGNCMD_UNDEFINE: + // :sign list <CTRL-D> + // :sign undefine <CTRL-D> + expand_what = EXP_SIGN_NAMES; + break; + default: + xp->xp_context = EXPAND_NOTHING; + } + return; + } + + // Expand last argument of subcmd. + // + // :sign define {name} {args}... + // | + // p + + // Loop until reaching last argument. + do { + p = skipwhite(p); + last = p; + p = skiptowhite(p); + } while (*p != NUL); + + p = vim_strchr(last, '='); + + // :sign define {name} {args}... {last}= + // | | + // last p + if (p == NULL) { + // Expand last argument name (before equal sign). + xp->xp_pattern = last; + switch (cmd_idx) { + case SIGNCMD_DEFINE: + expand_what = EXP_DEFINE; + break; + case SIGNCMD_PLACE: + expand_what = EXP_PLACE; + break; + case SIGNCMD_JUMP: + case SIGNCMD_UNPLACE: + expand_what = EXP_UNPLACE; + break; + default: + xp->xp_context = EXPAND_NOTHING; + } + } else { + // Expand last argument value (after equal sign). + xp->xp_pattern = p + 1; + switch (cmd_idx) { + case SIGNCMD_DEFINE: + if (STRNCMP(last, "texthl", p - last) == 0 + || STRNCMP(last, "linehl", p - last) == 0 + || STRNCMP(last, "numhl", p - last) == 0) { + xp->xp_context = EXPAND_HIGHLIGHT; + } else if (STRNCMP(last, "icon", p - last) == 0) { + xp->xp_context = EXPAND_FILES; + } else { + xp->xp_context = EXPAND_NOTHING; + } + break; + case SIGNCMD_PLACE: + if (STRNCMP(last, "name", p - last) == 0) { + expand_what = EXP_SIGN_NAMES; + } else { + xp->xp_context = EXPAND_NOTHING; + } + break; + default: + xp->xp_context = EXPAND_NOTHING; + } + } +} + diff --git a/src/nvim/sign.h b/src/nvim/sign.h new file mode 100644 index 0000000000..e3a5a27c74 --- /dev/null +++ b/src/nvim/sign.h @@ -0,0 +1,13 @@ +#ifndef NVIM_SIGN_H +#define NVIM_SIGN_H + +#include <stdbool.h> +#include "nvim/buffer_defs.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/sign_defs.h" + + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "sign.h.generated.h" +#endif +#endif // NVIM_SIGN_H diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h index b4f2709d45..687c15bbd6 100644 --- a/src/nvim/sign_defs.h +++ b/src/nvim/sign_defs.h @@ -2,20 +2,38 @@ #define NVIM_SIGN_DEFS_H #include "nvim/pos.h" +#include "nvim/types.h" // signs: line annotations +// Sign group +typedef struct signgroup_S +{ + uint16_t refcount; // number of signs in this group + int next_sign_id; // next sign id for this group + char_u sg_name[1]; // sign group name +} signgroup_T; + +// Macros to get the sign group structure from the group name +#define SGN_KEY_OFF offsetof(signgroup_T, sg_name) +#define HI2SG(hi) ((signgroup_T *)((hi)->hi_key - SGN_KEY_OFF)) + typedef struct signlist signlist_T; struct signlist { - int id; // unique identifier for each placed sign - linenr_T lnum; // line number which has this sign - int typenr; // typenr of sign - signlist_T *next; // next signlist entry - signlist_T *prev; // previous entry -- for easy reordering + int id; // unique identifier for each placed sign + linenr_T lnum; // line number which has this sign + int typenr; // typenr of sign + signgroup_T *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/syntax.c b/src/nvim/syntax.c index 4460c13ac6..418756abc4 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -38,6 +38,7 @@ #include "nvim/macros.h" #include "nvim/regexp.h" #include "nvim/screen.h" +#include "nvim/sign.h" #include "nvim/strings.h" #include "nvim/syntax_defs.h" #include "nvim/terminal.h" diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim index 3960177acd..ef4b227215 100644 --- a/src/nvim/testdir/test_signs.vim +++ b/src/nvim/testdir/test_signs.vim @@ -14,25 +14,28 @@ func Test_sign() " the icon name when listing signs. sign define Sign1 text=x try - sign define Sign2 text=xy texthl=Title linehl=Error numhl=Number icon=../../pixmaps/stock_vim_find_help.png + sign define Sign2 text=xy texthl=Title linehl=Error + \ 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 " Test listing signs. let a=execute('sign list') - call assert_match("^\nsign Sign1 text=x \nsign Sign2 icon=../../pixmaps/stock_vim_find_help.png .*text=xy linehl=Error texthl=Title numhl=Number$", a) + call assert_match('^\nsign Sign1 text=x \nsign Sign2 ' . + \ 'icon=../../pixmaps/stock_vim_find_help.png .*text=xy ' . + \ 'linehl=Error texthl=Title$', a) let a=execute('sign list Sign1') call assert_equal("\nsign Sign1 text=x ", a) - " Split the window to the bottom to verify sign jump will stay in the current window - " if the buffer is displayed there. + " Split the window to the bottom to verify sign jump will stay in the + " current window if the buffer is displayed there. let bn = bufnr('%') let wn = winnr() - exe 'sign place 41 line=3 name=Sign1 buffer=' . bn + exe 'sign place 41 line=3 name=Sign1 buffer=' . bn 1 bot split exe 'sign jump 41 buffer=' . bufnr('%') @@ -59,16 +62,17 @@ func Test_sign() redraw " Check that we can't change sign. - call assert_fails("exe 'sign place 40 name=Sign1 buffer=' . bufnr('%')", 'E885:') + call assert_fails("sign place 40 name=Sign1 buffer=" . bufnr('%'), 'E885:') " 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 1 - call assert_fails("exe 'sign jump 41 buffer=' . bufnr('%')", 'E157:') + call assert_fails("sign jump 41 buffer=" . bufnr('%'), 'E157:') call assert_equal('a', getline('.')) " Unplace sign on current line. @@ -77,7 +81,7 @@ func Test_sign() sign unplace let a=execute('sign place') call assert_equal("\n--- Signs ---\n", a) - + " Try again to unplace sign on current line, it should fail this time. call assert_fails('sign unplace', 'E159:') @@ -87,12 +91,20 @@ func Test_sign() let a=execute('sign place') call assert_equal("\n--- Signs ---\n", a) + " Place a sign without specifying the filename or buffer + sign place 77 line=9 name=Sign2 + let a=execute('sign place') + call assert_equal("\n--- Signs ---\nSigns for [NULL]:\n" . + \ " line=9 id=77 name=Sign2 priority=10\n", a) + sign unplace * + " Check :jump with file=... edit foo call setline(1, ['A', 'B', 'C', 'D']) try - sign define Sign3 text=y texthl=DoesNotExist linehl=DoesNotExist icon=doesnotexist.xpm + sign define Sign3 text=y texthl=DoesNotExist linehl=DoesNotExist + \ icon=doesnotexist.xpm catch /E255:/ " ignore error: E255: it can happens for guis. endtry @@ -104,6 +116,16 @@ func Test_sign() exe 'sign jump 43 file=' . fn call assert_equal('B', getline('.')) + " Check for jumping to a sign in a hidden buffer + enew! | only! + edit foo + call setline(1, ['A', 'B', 'C', 'D']) + let fn = expand('%:p') + exe 'sign place 21 line=3 name=Sign3 file=' . fn + hide edit bar + exe 'sign jump 21 file=' . fn + call assert_equal('C', getline('.')) + " can't define a sign with a non-printable character as text call assert_fails("sign define Sign4 text=\e linehl=Comment", 'E239:') call assert_fails("sign define Sign4 text=a\e linehl=Comment", 'E239:') @@ -112,7 +134,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,11 +153,25 @@ func Test_sign() sign define Sign4 text=\\ linehl=Comment sign undefine Sign4 + " define a sign with a leading 0 in the name + sign unplace * + sign define 004 text=#> linehl=Comment + let a = execute('sign list 4') + call assert_equal("\nsign 4 text=#> linehl=Comment", a) + exe 'sign place 20 line=3 name=004 buffer=' . bufnr('') + let a = execute('sign place') + call assert_equal("\n--- Signs ---\nSigns for foo:\n" . + \ " line=3 id=20 name=4 priority=10\n", a) + exe 'sign unplace 20 buffer=' . bufnr('') + sign undefine 004 + call assert_fails('sign list 4', 'E155:') + " After undefining the sign, we should no longer be able to place it. sign undefine Sign1 sign undefine Sign2 sign undefine Sign3 - call assert_fails("exe 'sign place 41 line=3 name=Sign1 buffer=' . bufnr('%')", 'E155:') + call assert_fails("sign place 41 line=3 name=Sign1 buffer=" . + \ bufnr('%'), 'E155:') endfunc " Undefining placed sign is not recommended. @@ -152,7 +188,8 @@ 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') @@ -170,7 +207,8 @@ func Test_sign_completion() call assert_equal('"sign define Sign icon= linehl= numhl= text= texthl=', @:) call feedkeys(":sign define Sign linehl=Spell\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"sign define Sign linehl=SpellBad SpellCap SpellLocal SpellRare', @:) + call assert_equal('"sign define Sign linehl=SpellBad SpellCap ' . + \ 'SpellLocal SpellRare', @:) call writefile(['foo'], 'XsignOne') call writefile(['bar'], 'XsignTwo') @@ -183,25 +221,28 @@ func Test_sign_completion() call assert_equal('"sign undefine Sign1 Sign2', @:) call feedkeys(":sign place 1 \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"sign place 1 buffer= file= line= name=', @:) + call assert_equal('"sign place 1 buffer= file= group= line= name= priority=', + \ @:) call feedkeys(":sign place 1 name=\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"sign place 1 name=Sign1 Sign2', @:) call feedkeys(":sign unplace 1 \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"sign unplace 1 buffer= file=', @:) + call assert_equal('"sign unplace 1 buffer= file= group=', @:) call feedkeys(":sign list \<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"sign list Sign1 Sign2', @:) call feedkeys(":sign jump 1 \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"sign jump 1 buffer= file=', @:) + call assert_equal('"sign jump 1 buffer= file= group=', @:) sign undefine Sign1 sign undefine Sign2 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:') @@ -210,7 +251,61 @@ func Test_sign_invalid_commands() call assert_fails('sign undefine', 'E156:') call assert_fails('sign list xxx', 'E155:') call assert_fails('sign place 1 buffer=999', 'E158:') + call assert_fails('sign place 1 name=Sign1 buffer=999', 'E158:') + call assert_fails('sign place buffer=999', 'E158:') + call assert_fails('sign jump buffer=999', 'E158:') + call assert_fails('sign jump 1 file=', 'E158:') + call assert_fails('sign jump 1 group=', 'E474:') + call assert_fails('sign jump 1 name=', 'E474:') + call assert_fails('sign jump 1 name=Sign1', 'E474:') + call assert_fails('sign jump 1 line=100', '474:') call assert_fails('sign define Sign2 text=', 'E239:') + " Non-numeric identifier for :sign place + call assert_fails("sign place abc line=3 name=Sign1 buffer=" . bufnr(''), + \ 'E474:') + " Non-numeric identifier for :sign unplace + call assert_fails("sign unplace abc name=Sign1 buffer=" . bufnr(''), + \ 'E474:') + " Number followed by an alphabet as sign identifier for :sign place + call assert_fails("sign place 1abc line=3 name=Sign1 buffer=" . bufnr(''), + \ 'E474:') + " Number followed by an alphabet as sign identifier for :sign unplace + call assert_fails("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("sign place 1 line=3 name=Sign1 buffer=" . + \ bufnr('%') . 'xxx', 'E488:') + " Trailing characters after buffer number for :sign unplace + call assert_fails("sign unplace 1 buffer=" . bufnr('%') . 'xxx', 'E488:') + call assert_fails("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("sign place name=Sign1 buffer=" . bufnr('%'), 'E474:') + " Placing a sign without sign name + call assert_fails("sign place line=10 buffer=" . bufnr('%'), 'E474:') + " Unplacing a sign with line number + call assert_fails("sign unplace 2 line=10 buffer=" . bufnr('%'), 'E474:') + " Unplacing a sign with sign name + call assert_fails("sign unplace 2 name=Sign1 buffer=" . bufnr('%'), 'E474:') + " Placing a sign without sign name + call assert_fails("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 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:') + + sign undefine Sign1 endfunc func Test_sign_delete_buffer() @@ -224,3 +319,1023 @@ func Test_sign_delete_buffer() sign unplace 61 sign undefine Sign endfunc + +" Test for Vim script 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('%', {'lnum' : 20})) + call assert_equal([{'bufnr' : bufnr(''), 'signs' : + \ [{'id' : 10, 'group' : '', 'lnum' : 20, 'name' : 'sign1', + \ 'priority' : 10}]}], + \ sign_getplaced('', {'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_fails('call sign_place(22, "", "sign1", "Xsign", + \ {"lnum" : []})', 'E745:') + 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('g1', + \ {'id' : 20, 'buffer' : 200})", 'E158:') + call assert_fails("call sign_unplace('g1', '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('%') + + " 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' : ''}) + call assert_equal([{'id' : 5, 'group' : '', 'name' : 'sign1', 'lnum' : 10, + \ 'priority' : 10}], s[0].signs) + call assert_equal(1, len(s[0].signs)) + 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 + sign place 5 line=10 name=sign1 file=Xsign + sign place 5 group=g1 line=10 name=sign1 file=Xsign + sign place 5 group=g2 line=10 name=sign1 file=Xsign + + " Tests for the ':sign place' command + + " :sign place file={fname} + let a = execute('sign place file=Xsign') + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" . + \ " line=10 id=5 name=sign1 priority=10\n", a) + + " :sign place group={group} file={fname} + let a = execute('sign place group=g2 file=Xsign') + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" . + \ " line=10 id=5 group=g2 name=sign1 priority=10\n", a) + + " :sign place group=* file={fname} + let a = execute('sign place group=* file=Xsign') + 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) + + " Error case: non-existing group + let a = execute('sign place group=xyz file=Xsign') + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n", a) + + call sign_unplace('*') + 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 + + " :sign place buffer={fname} + 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) + + " :sign place group={group} buffer={fname} + 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) + + " :sign place group=* buffer={fname} + 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) + + " Error case: non-existing group + let a = execute('sign place group=xyz buffer=' . bnum) + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n", a) + + " :sign place + let a = execute('sign place') + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" . + \ " line=10 id=5 name=sign1 priority=10\n", a) + + " Place signs in more than one buffer and list the signs + split foo + set buftype=nofile + sign place 25 line=76 name=sign1 priority=99 file=foo + let a = execute('sign place') + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" . + \ " line=10 id=5 name=sign1 priority=10\n" . + \ "Signs for foo:\n" . + \ " line=76 id=25 name=sign1 priority=99\n", a) + close + bwipe foo + + " :sign place group={group} + let a = execute('sign place group=g1') + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" . + \ " line=11 id=5 group=g1 name=sign1 priority=10\n", a) + + " :sign place group=* + 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 jump' command with groups + sign jump 5 group=g1 file=Xsign + call assert_equal(11, line('.')) + call assert_equal('Xsign', bufname('')) + sign jump 5 group=g2 file=Xsign + call assert_equal(12, line('.')) + + " Test for :sign jump command without the filename or buffer + sign jump 5 + call assert_equal(10, line('.')) + sign jump 5 group=g1 + call assert_equal(11, line('.')) + + " Error cases + call assert_fails("sign place 3 group= name=sign1 buffer=" . bnum, 'E474:') + + call delete("Xsign") + call sign_unplace('*') + call sign_undefine() + enew | only +endfunc + +" Place signs used for ":sign unplace" command test +func Place_signs_for_test() + call sign_unplace('*') + + sign place 3 line=10 name=sign1 file=Xsign1 + sign place 3 group=g1 line=11 name=sign1 file=Xsign1 + sign place 3 group=g2 line=12 name=sign1 file=Xsign1 + sign place 4 line=15 name=sign1 file=Xsign1 + sign place 4 group=g1 line=16 name=sign1 file=Xsign1 + sign place 4 group=g2 line=17 name=sign1 file=Xsign1 + sign place 5 line=20 name=sign1 file=Xsign2 + sign place 5 group=g1 line=21 name=sign1 file=Xsign2 + sign place 5 group=g2 line=22 name=sign1 file=Xsign2 + sign place 6 line=25 name=sign1 file=Xsign2 + sign place 6 group=g1 line=26 name=sign1 file=Xsign2 + sign place 6 group=g2 line=27 name=sign1 file=Xsign2 +endfunc + +" Place multiple signs in a single line for test +func Place_signs_at_line_for_test() + call sign_unplace('*') + sign place 3 line=13 name=sign1 file=Xsign1 + sign place 3 group=g1 line=13 name=sign1 file=Xsign1 + sign place 3 group=g2 line=13 name=sign1 file=Xsign1 + sign place 4 line=13 name=sign1 file=Xsign1 + sign place 4 group=g1 line=13 name=sign1 file=Xsign1 + sign place 4 group=g2 line=13 name=sign1 file=Xsign1 +endfunc + +" Tests for the ':sign unplace' command +func Test_sign_unplace() + enew | only + " Remove all the signs + call sign_unplace('*') + call sign_undefine() + + " Create two files and define signs + call writefile(repeat(["Sun is shining"], 30), "Xsign1") + call writefile(repeat(["It is beautiful"], 30), "Xsign2") + + let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'} + call sign_define("sign1", attr) + + edit Xsign1 + let bnum1 = bufnr('%') + split Xsign2 + let bnum2 = bufnr('%') + + let signs1 = [{'id' : 3, 'name' : 'sign1', 'lnum' : 10, 'group' : '', + \ 'priority' : 10}, + \ {'id' : 3, 'name' : 'sign1', 'lnum' : 11, 'group' : 'g1', + \ 'priority' : 10}, + \ {'id' : 3, 'name' : 'sign1', 'lnum' : 12, 'group' : 'g2', + \ 'priority' : 10}, + \ {'id' : 4, 'name' : 'sign1', 'lnum' : 15, 'group' : '', + \ 'priority' : 10}, + \ {'id' : 4, 'name' : 'sign1', 'lnum' : 16, 'group' : 'g1', + \ 'priority' : 10}, + \ {'id' : 4, 'name' : 'sign1', 'lnum' : 17, 'group' : 'g2', + \ 'priority' : 10},] + let signs2 = [{'id' : 5, 'name' : 'sign1', 'lnum' : 20, 'group' : '', + \ 'priority' : 10}, + \ {'id' : 5, 'name' : 'sign1', 'lnum' : 21, 'group' : 'g1', + \ 'priority' : 10}, + \ {'id' : 5, 'name' : 'sign1', 'lnum' : 22, 'group' : 'g2', + \ 'priority' : 10}, + \ {'id' : 6, 'name' : 'sign1', 'lnum' : 25, 'group' : '', + \ 'priority' : 10}, + \ {'id' : 6, 'name' : 'sign1', 'lnum' : 26, 'group' : 'g1', + \ 'priority' : 10}, + \ {'id' : 6, 'name' : 'sign1', 'lnum' : 27, 'group' : 'g2', + \ 'priority' : 10},] + + " Test for :sign unplace {id} file={fname} + call Place_signs_for_test() + sign unplace 3 file=Xsign1 + sign unplace 6 file=Xsign2 + call assert_equal( + \ filter(copy(signs1), + \ {idx, val -> val.id != 3 || val.group != ''}), + \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs) + call assert_equal( + \ filter(copy(signs2), + \ {idx, val -> val.id != 6 || val.group != ''}), + \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs) + + " Test for :sign unplace {id} group={group} file={fname} + call Place_signs_for_test() + sign unplace 4 group=g1 file=Xsign1 + sign unplace 5 group=g2 file=Xsign2 + call assert_equal( + \ filter(copy(signs1), + \ {idx, val -> val.id != 4 || val.group != 'g1'}), + \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs) + call assert_equal( + \ filter(copy(signs2), + \ {idx, val -> val.id != 5 || val.group != 'g2'}), + \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs) + + " Test for :sign unplace {id} group=* file={fname} + call Place_signs_for_test() + sign unplace 3 group=* file=Xsign1 + sign unplace 6 group=* file=Xsign2 + call assert_equal( + \ filter(copy(signs1), + \ {idx, val -> val.id != 3}), + \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs) + call assert_equal( + \ filter(copy(signs2), + \ {idx, val -> val.id != 6}), + \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs) + + " Test for :sign unplace * file={fname} + call Place_signs_for_test() + sign unplace * file=Xsign1 + call assert_equal( + \ filter(copy(signs1), + \ {idx, val -> val.group != ''}), + \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs) + call assert_equal(signs2, sign_getplaced('Xsign2', {'group' : '*'})[0].signs) + + " Test for :sign unplace * group={group} file={fname} + call Place_signs_for_test() + sign unplace * group=g1 file=Xsign1 + sign unplace * group=g2 file=Xsign2 + call assert_equal( + \ filter(copy(signs1), + \ {idx, val -> val.group != 'g1'}), + \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs) + call assert_equal( + \ filter(copy(signs2), + \ {idx, val -> val.group != 'g2'}), + \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs) + + " Test for :sign unplace * group=* file={fname} + call Place_signs_for_test() + sign unplace * group=* file=Xsign2 + call assert_equal(signs1, sign_getplaced('Xsign1', {'group' : '*'})[0].signs) + call assert_equal([], sign_getplaced('Xsign2', {'group' : '*'})[0].signs) + + " Test for :sign unplace {id} buffer={nr} + call Place_signs_for_test() + exe 'sign unplace 3 buffer=' . bnum1 + exe 'sign unplace 6 buffer=' . bnum2 + call assert_equal( + \ filter(copy(signs1), + \ {idx, val -> val.id != 3 || val.group != ''}), + \ sign_getplaced(bnum1, {'group' : '*'})[0].signs) + call assert_equal( + \ filter(copy(signs2), + \ {idx, val -> val.id != 6 || val.group != ''}), + \ sign_getplaced(bnum2, {'group' : '*'})[0].signs) + + " Test for :sign unplace {id} group={group} buffer={nr} + call Place_signs_for_test() + exe 'sign unplace 4 group=g1 buffer=' . bnum1 + exe 'sign unplace 5 group=g2 buffer=' . bnum2 + call assert_equal( + \ filter(copy(signs1), + \ {idx, val -> val.id != 4 || val.group != 'g1'}), + \ sign_getplaced(bnum1, {'group' : '*'})[0].signs) + call assert_equal( + \ filter(copy(signs2), + \ {idx, val -> val.id != 5 || val.group != 'g2'}), + \ sign_getplaced(bnum2, {'group' : '*'})[0].signs) + + " Test for :sign unplace {id} group=* buffer={nr} + call Place_signs_for_test() + exe 'sign unplace 3 group=* buffer=' . bnum1 + exe 'sign unplace 6 group=* buffer=' . bnum2 + call assert_equal( + \ filter(copy(signs1), + \ {idx, val -> val.id != 3}), + \ sign_getplaced(bnum1, {'group' : '*'})[0].signs) + call assert_equal( + \ filter(copy(signs2), + \ {idx, val -> val.id != 6}), + \ sign_getplaced(bnum2, {'group' : '*'})[0].signs) + + " Test for :sign unplace * buffer={nr} + call Place_signs_for_test() + exe 'sign unplace * buffer=' . bnum1 + call assert_equal( + \ filter(copy(signs1), + \ {idx, val -> val.group != ''}), + \ sign_getplaced(bnum1, {'group' : '*'})[0].signs) + call assert_equal(signs2, sign_getplaced(bnum2, {'group' : '*'})[0].signs) + + " Test for :sign unplace * group={group} buffer={nr} + call Place_signs_for_test() + exe 'sign unplace * group=g1 buffer=' . bnum1 + exe 'sign unplace * group=g2 buffer=' . bnum2 + call assert_equal( + \ filter(copy(signs1), + \ {idx, val -> val.group != 'g1'}), + \ sign_getplaced(bnum1, {'group' : '*'})[0].signs) + call assert_equal( + \ filter(copy(signs2), + \ {idx, val -> val.group != 'g2'}), + \ sign_getplaced(bnum2, {'group' : '*'})[0].signs) + + " Test for :sign unplace * group=* buffer={nr} + call Place_signs_for_test() + exe 'sign unplace * group=* buffer=' . bnum2 + call assert_equal(signs1, sign_getplaced(bnum1, {'group' : '*'})[0].signs) + call assert_equal([], sign_getplaced(bnum2, {'group' : '*'})[0].signs) + + " Test for :sign unplace {id} + call Place_signs_for_test() + sign unplace 4 + sign unplace 6 + call assert_equal( + \ filter(copy(signs1), + \ {idx, val -> val.id != 4 || val.group != ''}), + \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs) + call assert_equal( + \ filter(copy(signs2), + \ {idx, val -> val.id != 6 || val.group != ''}), + \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs) + + " Test for :sign unplace {id} group={group} + call Place_signs_for_test() + sign unplace 4 group=g1 + sign unplace 6 group=g2 + call assert_equal( + \ filter(copy(signs1), + \ {idx, val -> val.id != 4 || val.group != 'g1'}), + \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs) + call assert_equal( + \ filter(copy(signs2), + \ {idx, val -> val.id != 6 || val.group != 'g2'}), + \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs) + + " Test for :sign unplace {id} group=* + call Place_signs_for_test() + sign unplace 3 group=* + sign unplace 5 group=* + call assert_equal( + \ filter(copy(signs1), + \ {idx, val -> val.id != 3}), + \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs) + call assert_equal( + \ filter(copy(signs2), + \ {idx, val -> val.id != 5}), + \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs) + + " Test for :sign unplace * + call Place_signs_for_test() + sign unplace * + call assert_equal( + \ filter(copy(signs1), + \ {idx, val -> val.group != ''}), + \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs) + call assert_equal( + \ filter(copy(signs2), + \ {idx, val -> val.group != ''}), + \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs) + + " Test for :sign unplace * group={group} + call Place_signs_for_test() + sign unplace * group=g1 + call assert_equal( + \ filter(copy(signs1), + \ {idx, val -> val.group != 'g1'}), + \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs) + call assert_equal( + \ filter(copy(signs2), + \ {idx, val -> val.group != 'g1'}), + \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs) + + " Test for :sign unplace * group=* + call Place_signs_for_test() + sign unplace * group=* + call assert_equal([], sign_getplaced('Xsign1', {'group' : '*'})[0].signs) + call assert_equal([], sign_getplaced('Xsign2', {'group' : '*'})[0].signs) + + " Negative test cases + call Place_signs_for_test() + sign unplace 3 group=xy file=Xsign1 + sign unplace * group=xy file=Xsign1 + silent! sign unplace * group=* file=FileNotPresent + call assert_equal(signs1, sign_getplaced('Xsign1', {'group' : '*'})[0].signs) + call assert_equal(signs2, sign_getplaced('Xsign2', {'group' : '*'})[0].signs) + + " Tests for removing sign at the current cursor position + + " Test for ':sign unplace' + let signs1 = [{'id' : 4, 'name' : 'sign1', 'lnum' : 13, 'group' : 'g2', + \ 'priority' : 10}, + \ {'id' : 4, 'name' : 'sign1', 'lnum' : 13, 'group' : 'g1', + \ 'priority' : 10}, + \ {'id' : 4, 'name' : 'sign1', 'lnum' : 13, 'group' : '', + \ 'priority' : 10}, + \ {'id' : 3, 'name' : 'sign1', 'lnum' : 13, 'group' : 'g2', + \ 'priority' : 10}, + \ {'id' : 3, 'name' : 'sign1', 'lnum' : 13, 'group' : 'g1', + \ 'priority' : 10}, + \ {'id' : 3, 'name' : 'sign1', 'lnum' : 13, 'group' : '', + \ 'priority' : 10},] + exe bufwinnr('Xsign1') . 'wincmd w' + call cursor(13, 1) + + " Should remove only one sign in the global group + call Place_signs_at_line_for_test() + sign unplace + call assert_equal( + \ filter(copy(signs1), + \ {idx, val -> val.id != 4 || val.group != ''}), + \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs) + " Should remove the second sign in the global group + sign unplace + call assert_equal( + \ filter(copy(signs1), + \ {idx, val -> val.group != ''}), + \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs) + + " Test for ':sign unplace group={group}' + call Place_signs_at_line_for_test() + " Should remove only one sign in group g1 + sign unplace group=g1 + call assert_equal( + \ filter(copy(signs1), + \ {idx, val -> val.id != 4 || val.group != 'g1'}), + \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs) + sign unplace group=g2 + call assert_equal( + \ filter(copy(signs1), + \ {idx, val -> val.id != 4 || val.group == ''}), + \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs) + + " Test for ':sign unplace group=*' + call Place_signs_at_line_for_test() + sign unplace group=* + sign unplace group=* + sign unplace group=* + call assert_equal( + \ filter(copy(signs1), + \ {idx, val -> val.id != 4}), + \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs) + sign unplace group=* + sign unplace group=* + sign unplace group=* + call assert_equal([], sign_getplaced('Xsign1', {'group' : '*'})[0].signs) + + call sign_unplace('*') + call sign_undefine() + enew | only + call delete("Xsign1") + call delete("Xsign2") +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(4, sign_place(0, '', 'sign1', 'Xsign', + \ {'lnum' : 12})) + + call assert_equal(1, sign_place(0, 'g1', 'sign1', 'Xsign', + \ {'lnum' : 11})) + " Check for the next generated sign id in this group + call assert_equal(2, sign_place(0, 'g1', 'sign1', 'Xsign', + \ {'lnum' : 12})) + 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 + + 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 assert_fails("call sign_place(1, 'g1', 'sign1', 'Xsign', + \ {'priority' : []})", 'E745:') + 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() + throw 'skipped: Nvim does not support test_alloc_fail()' + 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 + +" Test for auto-adjusting the line number of a placed sign. +func Test_sign_lnum_adjust() + enew! | only! + + sign define sign1 text=#> linehl=Comment + call setline(1, ['A', 'B', 'C', 'D', 'E']) + exe 'sign place 5 line=3 name=sign1 buffer=' . bufnr('') + let l = sign_getplaced(bufnr('')) + call assert_equal(3, l[0].signs[0].lnum) + + " Add some lines before the sign and check the sign line number + call append(2, ['BA', 'BB', 'BC']) + let l = sign_getplaced(bufnr('')) + call assert_equal(6, l[0].signs[0].lnum) + + " Delete some lines before the sign and check the sign line number + call deletebufline('%', 1, 2) + let l = sign_getplaced(bufnr('')) + call assert_equal(4, l[0].signs[0].lnum) + + " Insert some lines after the sign and check the sign line number + call append(5, ['DA', 'DB']) + let l = sign_getplaced(bufnr('')) + call assert_equal(4, l[0].signs[0].lnum) + + " Delete some lines after the sign and check the sign line number + call deletebufline('', 6, 7) + let l = sign_getplaced(bufnr('')) + call assert_equal(4, l[0].signs[0].lnum) + + " Break the undo. Otherwise the undo operation below will undo all the + " changes made by this function. + let &undolevels=&undolevels + + " Delete the line with the sign + call deletebufline('', 4) + let l = sign_getplaced(bufnr('')) + call assert_equal(4, l[0].signs[0].lnum) + + " Undo the delete operation + undo + let l = sign_getplaced(bufnr('')) + call assert_equal(5, l[0].signs[0].lnum) + + " Break the undo + let &undolevels=&undolevels + + " Delete few lines at the end of the buffer including the line with the sign + " Sign line number should not change (as it is placed outside of the buffer) + call deletebufline('', 3, 6) + let l = sign_getplaced(bufnr('')) + call assert_equal(5, l[0].signs[0].lnum) + + " Undo the delete operation. Sign should be restored to the previous line + undo + let l = sign_getplaced(bufnr('')) + call assert_equal(5, l[0].signs[0].lnum) + + sign unplace * group=* + sign undefine sign1 + enew! +endfunc + +" Test for changing the type of a placed sign +func Test_sign_change_type() + enew! | only! + + sign define sign1 text=#> linehl=Comment + sign define sign2 text=@@ linehl=Comment + + call setline(1, ['A', 'B', 'C', 'D']) + exe 'sign place 4 line=3 name=sign1 buffer=' . bufnr('') + let l = sign_getplaced(bufnr('')) + call assert_equal('sign1', l[0].signs[0].name) + exe 'sign place 4 name=sign2 buffer=' . bufnr('') + let l = sign_getplaced(bufnr('')) + call assert_equal('sign2', l[0].signs[0].name) + call sign_place(4, '', 'sign1', '') + let l = sign_getplaced(bufnr('')) + call assert_equal('sign1', l[0].signs[0].name) + + exe 'sign place 4 group=g1 line=4 name=sign1 buffer=' . bufnr('') + let l = sign_getplaced(bufnr(''), {'group' : 'g1'}) + call assert_equal('sign1', l[0].signs[0].name) + exe 'sign place 4 group=g1 name=sign2 buffer=' . bufnr('') + let l = sign_getplaced(bufnr(''), {'group' : 'g1'}) + call assert_equal('sign2', l[0].signs[0].name) + call sign_place(4, 'g1', 'sign1', '') + let l = sign_getplaced(bufnr(''), {'group' : 'g1'}) + call assert_equal('sign1', l[0].signs[0].name) + + sign unplace * group=* + sign undefine sign1 + sign undefine sign2 + enew! +endfunc + +" Test for the sign_jump() function +func Test_sign_jump_func() + enew! | only! + + sign define sign1 text=#> linehl=Comment + + edit foo + set buftype=nofile + call setline(1, ['A', 'B', 'C', 'D', 'E']) + call sign_place(5, '', 'sign1', '', {'lnum' : 2}) + call sign_place(5, 'g1', 'sign1', '', {'lnum' : 3}) + call sign_place(6, '', 'sign1', '', {'lnum' : 4}) + call sign_place(6, 'g1', 'sign1', '', {'lnum' : 5}) + split bar + set buftype=nofile + call setline(1, ['P', 'Q', 'R', 'S', 'T']) + call sign_place(5, '', 'sign1', '', {'lnum' : 2}) + call sign_place(5, 'g1', 'sign1', '', {'lnum' : 3}) + call sign_place(6, '', 'sign1', '', {'lnum' : 4}) + call sign_place(6, 'g1', 'sign1', '', {'lnum' : 5}) + + let r = sign_jump(5, '', 'foo') + call assert_equal(2, r) + call assert_equal(2, line('.')) + let r = sign_jump(6, 'g1', 'foo') + call assert_equal(5, r) + call assert_equal(5, line('.')) + let r = sign_jump(5, '', 'bar') + call assert_equal(2, r) + call assert_equal(2, line('.')) + + " Error cases + call assert_fails("call sign_jump(99, '', 'bar')", 'E157:') + call assert_fails("call sign_jump(0, '', 'foo')", 'E474:') + call assert_fails("call sign_jump(5, 'g5', 'foo')", 'E157:') + call assert_fails('call sign_jump([], "", "foo")', 'E745:') + call assert_fails('call sign_jump(2, [], "foo")', 'E730:') + call assert_fails('call sign_jump(2, "", {})', 'E158:') + call assert_fails('call sign_jump(2, "", "baz")', 'E158:') + + sign unplace * group=* + sign undefine sign1 + enew! | only! +endfunc diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index 74019046c0..2b38a2a58d 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -240,7 +240,7 @@ describe('Signs', function() :sign place | {9:--- Signs ---} | {10:Signs for [NULL]:} | - line=1 id=100000 name=piet | + line=1 id=100000 name=piet priority=10 | | {11:Press ENTER or type command to continue}^ | ]]) |